From 9349a499894f12a866b1b3f5cc2a23ceb9779eac Mon Sep 17 00:00:00 2001 From: Artemiy Vereshchinskiy Date: Fri, 22 Aug 2025 21:09:48 +0700 Subject: [PATCH 1/2] Add LMPG visualisation to dashboard UI --- .changeset/pretty-students-vanish.md | 9 + platform/dashboard/package.json | 6 +- platform/dashboard/src/elements/Sheet.tsx | 75 +- .../projects/components/GraphView.tsx | 537 +++++++++----- .../projects/components/ProjectRecords.tsx | 25 +- .../projects/stores/current-project.ts | 538 ++++++++++---- .../src/features/projects/useGraphData.ts | 517 +++++++++++++ .../src/features/records/stores/actionbar.ts | 1 - .../src/features/records/stores/batch.ts | 8 +- .../src/features/records/stores/mutations.ts | 6 - .../records/stores/related-actionbar.ts | 2 +- platform/dashboard/src/lib/queryClient.ts | 4 + pnpm-lock.yaml | 683 ++---------------- website/public/sitemap-0.xml | 26 +- 14 files changed, 1395 insertions(+), 1042 deletions(-) create mode 100644 .changeset/pretty-students-vanish.md create mode 100644 platform/dashboard/src/features/projects/useGraphData.ts create mode 100644 platform/dashboard/src/lib/queryClient.ts diff --git a/.changeset/pretty-students-vanish.md b/.changeset/pretty-students-vanish.md new file mode 100644 index 00000000..bc4b06fd --- /dev/null +++ b/.changeset/pretty-students-vanish.md @@ -0,0 +1,9 @@ +--- +'rushdb-dashboard': minor +'rushdb-website': minor +'rushdb-docs': minor +'@rushdb/javascript-sdk': minor +'rushdb-core': minor +--- + +Add LMPG visualisation to dashboard UI diff --git a/platform/dashboard/package.json b/platform/dashboard/package.json index 0d850e12..f84ed80f 100644 --- a/platform/dashboard/package.json +++ b/platform/dashboard/package.json @@ -48,14 +48,13 @@ "react-cool-virtual": "^0.7.0", "react-day-picker": "^8.8.2", "react-dom": "19.1.0", - "react-force-graph": "^1.45.4", + "react-force-graph-3d": "1.28.0", + "react-force-graph-2d": "1.28.0", "react-helmet-async": "^2.0.5", "react-hook-form": "~7.41.0", "react-joyride": "^2.9.3", "tailwind-merge": "^2.5.2", "tailwindcss-animate": "^1.0.7", - "three": "^0.171.0", - "three-spritetext": "^1.9.3", "yup": "^1.2.0" }, "devDependencies": { @@ -66,7 +65,6 @@ "@types/react": "19.1.0", "@types/react-dom": "19.1.0", "@types/react-window": "^1.8.5", - "@types/three": "^0.171.0", "@typescript-eslint/eslint-plugin": "^8.19.0", "@typescript-eslint/parser": "^8.19.0", "@vitejs/plugin-react-swc": "^3.3.0", diff --git a/platform/dashboard/src/elements/Sheet.tsx b/platform/dashboard/src/elements/Sheet.tsx index b7b22608..fc17695b 100644 --- a/platform/dashboard/src/elements/Sheet.tsx +++ b/platform/dashboard/src/elements/Sheet.tsx @@ -1,11 +1,6 @@ import type { DialogPortalProps, DialogProps } from '@radix-ui/react-dialog' import type { VariantProps } from 'class-variance-authority' -import type { - ComponentPropsWithoutRef, - ElementRef, - HTMLAttributes, - ReactNode -} from 'react' +import type { ComponentPropsWithoutRef, ElementRef, HTMLAttributes, ReactNode } from 'react' import { Content, Overlay, Portal, Root, Trigger } from '@radix-ui/react-dialog' import { cva } from 'class-variance-authority' @@ -27,9 +22,7 @@ const portalVariants = cva<{ } }) -interface SheetPortalProps - extends DialogPortalProps, - VariantProps {} +interface SheetPortalProps extends DialogPortalProps, VariantProps {} const SheetPortal = ({ children, position, ...props }: SheetPortalProps) => ( @@ -40,19 +33,18 @@ const SheetPortal = ({ children, position, ...props }: SheetPortalProps) => ( ) SheetPortal.displayName = Portal.displayName -const SheetOverlay = forwardRef< - ElementRef, - ComponentPropsWithoutRef ->(({ children, className, ...props }, ref) => ( - -)) +const SheetOverlay = forwardRef, ComponentPropsWithoutRef>( + ({ children, className, ...props }, ref) => ( + + ) +) SheetOverlay.displayName = Overlay.displayName const sheetVariants = cva<{ @@ -151,11 +143,7 @@ const SheetContent = forwardRef, DialogContentProps>( ({ children, className, position, size, ...props }, ref) => ( - + {children} @@ -163,29 +151,14 @@ const SheetContent = forwardRef, DialogContentProps>( ) SheetContent.displayName = Content.displayName -const SheetHeader = ({ - className, - ...props -}: HTMLAttributes) => ( -
+const SheetHeader = ({ className, ...props }: HTMLAttributes) => ( +
) SheetHeader.displayName = 'SheetHeader' -const SheetFooter = ({ - className, - ...props -}: HTMLAttributes) => ( +const SheetFooter = ({ className, ...props }: HTMLAttributes) => (
) @@ -197,17 +170,11 @@ export type TSheetProps = { } & DialogProps & VariantProps -export function Sheet({ - children, - position = 'right', - size = 'lg', - trigger, - ...props -}: TSheetProps) { +export function Sheet({ children, position = 'right', size = 'lg', trigger, ...props }: TSheetProps) { return ( {trigger && {trigger}} - + {children} diff --git a/platform/dashboard/src/features/projects/components/GraphView.tsx b/platform/dashboard/src/features/projects/components/GraphView.tsx index b054392a..73884c60 100644 --- a/platform/dashboard/src/features/projects/components/GraphView.tsx +++ b/platform/dashboard/src/features/projects/components/GraphView.tsx @@ -1,213 +1,360 @@ -import React, { useRef, useCallback, FC, useState, useEffect } from 'react' - -import { CSS2DRenderer } from 'three/addons/renderers/CSS2DRenderer.js' -import { ForceGraph3D } from 'react-force-graph' -import SpriteText from 'three-spritetext' -import { useStore } from '@nanostores/react' - -import { - $currentProjectLabels, - $filteredRecords, - $filteredRecordsRelations -} from '~/features/projects/stores/current-project.ts' -import { Renderer } from 'three' -import { $sheetRecordId } from '~/features/projects/stores/id.ts' -import { getLabelColor } from '~/features/labels' -import { DBRecord, DBRecordInstance, type Relation } from '@rushdb/javascript-sdk' - -export function getRelationCounts(tree: Output) { - const counts: Record = {} - - tree.nodes?.forEach((node) => { - counts[node.__id] = 0 - }) - - tree.links?.forEach((link) => { - const source = link.sourceId - const target = link.targetId - - counts[source] = Math.min((counts[source] || 0) + 1, 64) - counts[target] = Math.min((counts[target] || 0) + 1, 64) - }) - - return counts +import React, { useRef, useCallback, useState, useEffect, useLayoutEffect, Ref, RefObject } from 'react' + +import { useGraphData } from '../useGraphData' +import { NodeObject } from 'react-force-graph-3d' +import { $sheetRecordId } from '../stores/id' +import { IconButton } from '~/elements/IconButton' +import { Expand } from 'lucide-react' +import { Switch } from '~/elements/Switch.tsx' + +// Shared graph node/link types (mirrors 2DGraphView logic) +type GraphNode = { + id: string + type: 'record' | 'property' + name?: string + label?: string + propertyType?: string + val?: number + proptypes?: Record + neighbors?: GraphNode[] + links?: GraphLink[] + // force-graph runtime fields + x?: number + y?: number + z?: number + [k: string]: any } - -interface Output { - nodes: DBRecord[] - links: Relation[] +type GraphLink = { source: string | GraphNode; target: string | GraphNode; type: string } + +function stringToColor(str?: string) { + if (!str) return '#64748B' + let hash = 0 + for (let i = 0; i < str.length; i++) hash = str.charCodeAt(i) + ((hash << 5) - hash) + const h = hash % 360 + return `hsl(${h},65%,45%)` } -function createNodesAndLinks(data: Relation[], records?: DBRecordInstance[]): Output { - const nodesMap = new Map() +const color = (n: GraphNode, theme: 'light' | 'dark' = 'dark') => + n.type === 'property' ? + theme === 'dark' ? + '#6366F1' + : '#4F46E5' + : n.label ? stringToColor(n.label) + : theme === 'dark' ? '#10B981' + : '#059669' + +const linkColor = (l: GraphLink, theme: 'light' | 'dark' = 'dark') => + l.type === 'value' ? + theme === 'dark' ? + 'rgba(99,102,241,0.35)' + : 'rgba(79,70,229,0.4)' + : theme === 'dark' ? 'rgba(148,163,184,0.45)' + : 'rgba(100,116,139,0.5)' + +// Simple ResizeObserver hook (copied from 2D view) +function useResizeObserver( + ref: React.RefObject, + cb: (rect: DOMRectReadOnly) => void +) { + useLayoutEffect(() => { + if (!ref.current) return + const el = ref.current + const ro = new ResizeObserver((entries) => { + for (const entry of entries) cb(entry.contentRect) + }) + ro.observe(el) + return () => ro.disconnect() + }, [ref, cb]) +} - data.forEach(({ sourceId, sourceLabel, targetId, targetLabel }) => { - if (!nodesMap.has(sourceId)) { - nodesMap.set(sourceId, { __id: sourceId, __label: sourceLabel }) - } - if (!nodesMap.has(targetId)) { - nodesMap.set(targetId, { __id: targetId, __label: targetLabel }) +export const GraphView = () => { + const { + graphData, + showProperties, + setShowProperties, + loading, + totalRecordsLoaded, + totalRelationsLoaded, + hasMoreRecords, + hasMoreRelationships, + loadMoreRecords, + loadMoreRelationships, + maxPossibleRecords, + recordsTotal, + relationsTotal, + recordsLoadingProgress, + relationsLoadingProgress + } = useGraphData() + + const [mode, setMode] = useState<'2d' | '3d'>('2d') // default 2D + + // dynamic imports + const [FG2D, setFG2D] = useState(null) + const [FG3D, setFG3D] = useState(null) + useEffect(() => { + let cancelled = false + import('react-force-graph-2d').then((m) => !cancelled && setFG2D(() => m.default)) + // defer 3D until needed + if (mode === '3d' && !FG3D) { + import('react-force-graph-3d').then((m) => !cancelled && setFG3D(() => m.default)) } - }) - - records?.forEach((node) => { - if (!nodesMap.has(node.id())) { - nodesMap.set(node.id(), { __id: node.id(), __label: node.label() }) + return () => { + cancelled = true } + }, [mode, FG3D]) + + const containerRef = useRef(null) + const [dims, setDims] = useState({ w: 800, h: 600 }) + useResizeObserver(containerRef, (rect) => { + const w = Math.max(100, rect.width) + const h = Math.max(100, rect.height) + setDims((d) => (d.w === w && d.h === h ? d : { w, h })) }) - return { - nodes: Array.from(nodesMap.values()), - links: data - } -} - -const HEADER_HEIGHT = 182 -const FOOTER_HEIGHT = 61 - -export const GraphView: FC = () => { - const extraRenderers: Renderer[] = [new CSS2DRenderer() as unknown as Renderer] - - const { data: relations } = useStore($filteredRecordsRelations) - const { data: records } = useStore($filteredRecords) - - const [data, setData] = useState({ nodes: [], links: [] }) - const [weights, setWeights] = useState>({}) - - useEffect(() => { - const result = createNodesAndLinks(relations ?? [], records) - setData(result) - - const newWeights = getRelationCounts(result) - setWeights(newWeights) - }, [relations]) - - const fgRef = useRef(undefined) - - const handleClick = useCallback( - (node: any) => { - const distance = 260 - const distRatio = 1 + distance / Math.hypot(node.x, node.y, node.z) + const fg2dRef = useRef(null) + const fg3dRef = useRef(null) - fgRef.current?.cameraPosition( - { x: node.x * distRatio, y: node.y * distRatio, z: node.z * distRatio }, - node, - 1000 - ) - - $sheetRecordId.set(node.__id) - }, - [fgRef] - ) - - const handleLinkClick = useCallback( - (link: any) => { - const distance = 70 - - const sx = link.source.x - const sy = link.source.y - const sz = link.source.z - - const tx = link.target.x - const ty = link.target.y - const tz = link.target.z - - const dx = sx - tx - const dy = sy - ty - const dz = sz - tz - - const rdx = dz - const rdy = dy - const rdz = -dx - - const dist = Math.hypot(rdx, rdy, rdz) - - const distRatio = 1 + distance / dist - - const mx = (sx + tx) / 2 - const my = (sy + ty) / 2 - const mz = (sz + tz) / 2 - - const cx = mx + rdx * distRatio - const cy = my + rdy * distRatio - const cz = mz + rdz * distRatio - - fgRef.current?.cameraPosition({ x: cx, y: cy, z: cz }, { x: mx, y: my, z: mz }, 1000) - }, - [fgRef] - ) - const [canvasSize, setCanvasSize] = useState({ - width: window.innerWidth - 16, - height: window.innerHeight - HEADER_HEIGHT - FOOTER_HEIGHT - }) + const zoomToFit = useCallback(() => { + try { + if (mode === '2d' && fg2dRef.current?.zoomToFit) fg2dRef.current.zoomToFit(600, 60) + if (mode === '3d' && fg3dRef.current?.zoomToFit) fg3dRef.current.zoomToFit(600, 60) + } catch {} + }, [mode]) useEffect(() => { - const resize = () => { - setCanvasSize({ - width: window.innerWidth - 16, - height: window.innerHeight - HEADER_HEIGHT - FOOTER_HEIGHT - }) + // Auto-fit zoom when graph data changes significantly + if (graphData.nodes.length > 0) { + const timeoutId = setTimeout(zoomToFit, 1000) + return () => clearTimeout(timeoutId) } - window.addEventListener('resize', resize) - - return () => { - window.removeEventListener('resize', resize) + }, [graphData.nodes.length, zoomToFit]) + + // Hover highlight (2D only for now) + const [hoverNode, setHoverNode] = useState(null) + const [highlightNodes, setHighlightNodes] = useState>(new Set()) + const [highlightLinks, setHighlightLinks] = useState>(new Set()) + + const handleNodeHover = useCallback((node: GraphNode | null) => { + setHoverNode(node) + const newHNodes = new Set() + const newHLinks = new Set() + if (node) { + newHNodes.add(node) + node.neighbors?.forEach((n) => newHNodes.add(n)) + node.links?.forEach((l) => newHLinks.add(l)) } + setHighlightNodes(newHNodes) + setHighlightLinks(newHLinks) }, []) + const nodeCanvasObject = useCallback( + (node: GraphNode, ctx: CanvasRenderingContext2D, globalScale: number) => { + const radius = Math.max(4, Math.sqrt(node.val || 4) * 4) + ctx.beginPath() + ctx.fillStyle = color(node) + ctx.arc(node.x!, node.y!, radius, 0, 2 * Math.PI, false) + ctx.fill() + if (hoverNode === node) { + const label = node.name || node.label || node.id + const fontSize = 12 / globalScale + ctx.font = `${fontSize}px sans-serif` + ctx.textAlign = 'center' + ctx.textBaseline = 'top' + ctx.fillStyle = '#f1f5f9' + ctx.fillText(label, node.x!, node.y! + radius + 2) + } + if (highlightNodes.has(node)) { + const radius2 = radius * 1.1 + ctx.beginPath() + ctx.arc(node.x!, node.y!, radius2, 0, 2 * Math.PI, false) + ctx.strokeStyle = node === hoverNode ? '#3f81ff' : 'orange' + ctx.lineWidth = 2 + ctx.stroke() + } + }, + [hoverNode, highlightNodes] + ) + return ( - weights[node.__id as string]} - linkDirectionalArrowRelPos={1} - linkDirectionalArrowLength={4} - linkDirectionalArrowResolution={16} - nodeThreeObjectExtend={true} - onLinkClick={handleLinkClick} - nodeColor={(node) => { - const { data: labels } = $currentProjectLabels.get() - return getLabelColor(node.__label, Object.keys(labels ?? {}).indexOf(node.__label)) - }} - onNodeDragEnd={(node) => { - node.fx = node.x - node.fy = node.y - node.fz = node.z - }} - linkThreeObjectExtend={true} - linkThreeObject={(link) => { - const sprite = new SpriteText(`${link.sourceLabel} > ${link.targetLabel}`) - sprite.color = 'lightgrey' - sprite.textHeight = 1.5 - return sprite - }} - linkPositionUpdate={(sprite, { start, end }) => { - const middlePos = Object.assign( - // @ts-ignore - ...['x', 'y', 'z'].map((c) => ({ - // @ts-ignore - [c]: start[c] + (end[c] - start[c]) / 2 // calc middle point - })) - ) - - // Position sprite - Object.assign(sprite.position, middlePos) - }} - nodeLabel={(node) => `
${node.__label}: ${node.__id}
`} - onNodeClick={handleClick} - /> +
+ {!loading && mode === '2d' && FG2D && ( + { + node.fx = node.x + node.fy = node.y + }} + cooldownTime={8000} + enableNodeDrag + onNodeClick={(node: GraphNode) => { + if (node.type !== 'property') { + $sheetRecordId.set(node.id) + } + }} + onLinkHover={(l: GraphLink | null) => { + if (!l) { + setHighlightLinks(new Set()) + return + } + const newHLinks = new Set([l]) + const newHNodes = new Set() + if (typeof l.source !== 'string') newHNodes.add(l.source as GraphNode) + if (typeof l.target !== 'string') newHNodes.add(l.target as GraphNode) + setHighlightLinks(newHLinks) + setHighlightNodes(newHNodes) + }} + /> + )} + {!loading && mode === '3d' && FG3D && ( + { + node.fx = node.x + node.fy = node.y + node.fz = node.z + }} + nodeRelSize={6} + linkOpacity={0.25} + onNodeClick={(n: any) => { + // center camera + const distance = 260 + const distRatio = 1 + distance / Math.hypot(n.x, n.y, n.z) + fg3dRef.current?.cameraPosition( + { x: n.x * distRatio, y: n.y * distRatio, z: n.z * distRatio }, + n, + 1000 + ) + }} + /> + )} + + {loading && ( +
+
+
+
+
+
Loading Graph Data
+
+ Fetching {Math.min(recordsTotal || 0, maxPossibleRecords).toLocaleString()} records and + relations... +
+
+
+ Records: {totalRecordsLoaded.toLocaleString()} + {Math.round(recordsLoadingProgress)}% +
+
+
+
+
+ Relations: {totalRelationsLoaded.toLocaleString()} + {Math.round(relationsLoadingProgress)}% +
+
+
+
+
+
+
+ )} + + {!loading && ( +
+
+
+ Records: {totalRecordsLoaded.toLocaleString()} + {recordsTotal && ( + + / {Math.min(recordsTotal, maxPossibleRecords).toLocaleString()} + + )} + {totalRecordsLoaded >= maxPossibleRecords && (max)} +
+
+ Relations: {totalRelationsLoaded.toLocaleString()} + {relationsTotal && ( + + / {Math.min(relationsTotal, maxPossibleRecords).toLocaleString()} + + )} +
+ {(hasMoreRecords || hasMoreRelationships) && ( + + )} +
+
+ )} + +
+ {/* setMode('2d')} + > + 2D + + setMode('3d')} + > + 3D + */} + + + +
+ +
+
) } + +export default GraphView diff --git a/platform/dashboard/src/features/projects/components/ProjectRecords.tsx b/platform/dashboard/src/features/projects/components/ProjectRecords.tsx index cae227ab..a771b020 100644 --- a/platform/dashboard/src/features/projects/components/ProjectRecords.tsx +++ b/platform/dashboard/src/features/projects/components/ProjectRecords.tsx @@ -15,8 +15,9 @@ import { } from '../stores/current-project' import { $currentProjectVisibleFields } from '../stores/hidden-fields' import { $sheetRecordId } from '../stores/id' -import { GraphView } from '~/features/projects/components/GraphView.tsx' -import { Paginator } from '~/elements/Paginator.tsx' +import { Suspense, lazy } from 'react' +const GraphView = lazy(() => import('~/features/projects/components/GraphView.tsx')) +import { Spinner } from '~/elements/Spinner.tsx' import { RawApiView } from '~/features/projects/components/RawApiView.tsx' import { setTourStep } from '~/features/tour/stores/tour.ts' import { $router } from '~/lib/router.ts' @@ -59,19 +60,15 @@ function View() { case 'graph': return ( - <> + + +
+ } + > - {records?.length ? - - : null} - + ) default: diff --git a/platform/dashboard/src/features/projects/stores/current-project.ts b/platform/dashboard/src/features/projects/stores/current-project.ts index ca65bed1..31eaf5d4 100644 --- a/platform/dashboard/src/features/projects/stores/current-project.ts +++ b/platform/dashboard/src/features/projects/stores/current-project.ts @@ -10,6 +10,9 @@ import { DEFAULT_LIMIT } from '~/config' import { isViableSearchOperation } from '~/features/search/types' import { api } from '~/lib/api' import { createAsyncStore, createMutator } from '~/lib/fetcher' +import { queryClient } from '~/lib/queryClient' +import { map, onMount, onNotify } from 'nanostores' +import { isAnyObject } from '~/types' import { $searchParams, changeSearchParam, removeSearchParam } from '~/lib/router' import { $router, isProjectPage } from '~/lib/router' import { addOrRemove, clamp } from '~/lib/utils' @@ -24,6 +27,7 @@ import { isProjectEmpty } from '../utils' import { $currentProjectId } from './id' +import { LabelsResponse } from '~/features/labels' export const $recordView = atom('table') @@ -53,13 +57,66 @@ export const $currentProject = createAsyncStore({ export const $currentProjectFilters = atom([]) -export const $currentProjectLabels = createAsyncStore({ - key: '$currentProject', - async fetcher(init) { - return await api.labels.find({ init }) - }, - mustHaveDeps: [$currentProjectId] +// Refactored: $currentProjectLabels react-query bridge +async function fetchProjectLabels(): Promise<{ data: LabelsResponse } | undefined> { + const projectId = $currentProjectId.get() + if (!projectId) return + const response = await api.labels.find({} as any) + return { data: (response as any).data ?? response } +} +export const $currentProjectLabels = map<{ + data: LabelsResponse | undefined + loading: boolean + error?: string +}>({ + data: undefined, + loading: true, + error: undefined +}) +// @ts-ignore +$currentProjectLabels.refetch = async () => { + const projectId = $currentProjectId.get() + if (!projectId) { + $currentProjectLabels.set({ data: undefined, loading: false }) + return + } + const queryKey = ['project-labels', projectId] + $currentProjectLabels.set({ ...$currentProjectLabels.get(), loading: true }) + try { + const response = await queryClient.fetchQuery({ queryKey, queryFn: fetchProjectLabels }) + if (!response) return + $currentProjectLabels.set({ data: response.data as LabelsResponse, loading: false }) + } catch (error) { + if (error instanceof Error) { + $currentProjectLabels.set({ data: undefined, loading: false, error: error.message }) + } + } +} +onMount($currentProjectLabels, () => { + const run = () => { + const projectId = $currentProjectId.get() + if (!projectId) { + $currentProjectLabels.set({ data: undefined, loading: false }) + return + } + const queryKey = ['project-labels', projectId] + $currentProjectLabels.set({ ...$currentProjectLabels.get(), loading: true }) + queryClient + .fetchQuery({ queryKey, queryFn: fetchProjectLabels }) + .then((response) => { + if (!response) return + $currentProjectLabels.set({ data: response.data as LabelsResponse, loading: false }) + }) + .catch((error: unknown) => { + if (error instanceof Error) { + $currentProjectLabels.set({ data: undefined, loading: false, error: error.message }) + } + }) + } + run() + return () => {} }) +onNotify($currentProjectId as any, () => queueMicrotask(() => ($currentProjectLabels as any).refetch())) export const $activeLabels = atom([]) @@ -83,152 +140,369 @@ $searchParams.subscribe((value) => { setTimeout(() => $currentProjectFilters.set(filters), 10) }) -export const $filteredRecords = createAsyncStore({ - key: '$projectFilteredRecords', - async fetcher(init) { - const filters = $currentProjectFilters.get() - const orderBy = $recordsOrderBy.get() - const skip = $currentProjectRecordsSkip.get() - const limit = $currentProjectRecordsLimit.get() - const labels = $activeLabels.get() - const combineMode = $combineFilters.get() - const properties = filters.map(filterToSearchOperation) +// Refactored: $filteredRecords now backed by react-query while preserving nanostore API (data, loading, total, error, refetch) +type RecordsQueryData = { data: unknown[]; total?: number } - const order = Object.entries(orderBy ?? {}).reduce((acc, [key, direction]) => { - if (key === '__id') { - return direction as SortDirection - } +function buildRecordsQueryArgs() { + const filters = $currentProjectFilters.get() + const orderBy = $recordsOrderBy.get() + const skip = $currentProjectRecordsSkip.get() + const limit = $currentProjectRecordsLimit.get() + const labels = $activeLabels.get() + const combineMode = $combineFilters.get() + const properties = filters.map(filterToSearchOperation) - if (key && direction) { - // @ts-ignore - acc[key] = direction as SortDirection - } - return acc - }, {}) + const order = Object.entries(orderBy ?? {}).reduce((acc, [key, direction]) => { + if (key === '__id') { + return direction as SortDirection + } + if (key && direction) { + // @ts-ignore + acc[key] = direction as SortDirection + } + return acc + }, {}) - const { data, total } = await api.records.find( - { - where: - combineMode === 'or' ? { $or: convertToSearchQuery(properties) } : convertToSearchQuery(properties), - orderBy: order, - skip, - limit, - labels - }, - init - ) - return { data, total } - }, - mustHaveDeps: [$currentProjectId], - deps: [ - $currentProjectFilters, - $recordsOrderBy, - $currentProjectRecordsSkip, - $currentProjectRecordsLimit, - $activeLabels, - $combineFilters - ] -}) + const where = + combineMode === 'or' ? { $or: convertToSearchQuery(properties) } : convertToSearchQuery(properties) -export const $filteredRecordsRelations = createAsyncStore({ - key: '$currentRecordChildren', - async fetcher(init) { - const filters = $currentProjectFilters.get() - const orderBy = $recordsOrderBy.get() - const skip = $currentProjectRecordsSkip.get() - const limit = $currentProjectRecordsLimit.get() - const labels = $activeLabels.get() - const combineMode = $combineFilters.get() - const properties = filters.map(filterToSearchOperation) - - const order = Object.entries(orderBy ?? {}).reduce((acc, [key, direction]) => { - if (key === '__id') { - return direction as SortDirection - } - - if (key && direction) { - // @ts-ignore - acc[key] = direction as SortDirection - } - return acc - }, {}) - - const { data, total } = await api.relationships.find({ - searchQuery: { - where: - combineMode === 'or' ? { $or: convertToSearchQuery(properties) } : convertToSearchQuery(properties), - orderBy: order, - skip, - limit, - labels - }, - init - }) - return { data, total } - }, - mustHaveDeps: [$currentProjectId], - deps: [ - $currentProjectFilters, - $recordsOrderBy, - $currentProjectRecordsSkip, - $currentProjectRecordsLimit, - $activeLabels, - $combineFilters - ] + return { where, orderBy: order, skip, limit, labels } +} + +async function fetchRecords(): Promise { + const projectId = $currentProjectId.get() + if (!projectId) return + const args = buildRecordsQueryArgs() + const { data, total } = await api.records.find(args as any, { signal: undefined as any }) + return { data, total } +} + +// Bridge nanostore +export const $filteredRecords = map<{ + data: any[] | undefined + loading: boolean + error?: string + total?: number +}>({ + data: undefined, + loading: true, + error: undefined, + total: undefined }) -export const $currentProjectFields = createAsyncStore({ - key: '$currentProjectFields', - async fetcher(init) { - const projectId = $currentProjectId.get() +// Maintain refetch method compatibility +// @ts-ignore - augment store +$filteredRecords.refetch = async () => { + const projectId = $currentProjectId.get() + if (!projectId) { + $filteredRecords.set({ data: undefined, loading: false, total: 0 }) + return + } + const args = buildRecordsQueryArgs() + const queryKey = ['records', projectId, JSON.stringify(args)] + $filteredRecords.set({ ...$filteredRecords.get(), loading: true }) + try { + const response = await queryClient.fetchQuery({ queryKey, queryFn: fetchRecords }) + if (!response) return + $filteredRecords.set({ data: response.data as any[], total: response.total, loading: false }) + } catch (error) { + if (error instanceof Error) { + $filteredRecords.set({ data: undefined, loading: false, error: error.message, total: 0 }) + } + } +} +onMount($filteredRecords, () => { + const run = () => { + const projectId = $currentProjectId.get() if (!projectId) { + $filteredRecords.set({ data: undefined, loading: false, total: 0 }) return } + const args = buildRecordsQueryArgs() + const queryKey = ['records', projectId, JSON.stringify(args)] - const labels = $activeLabels.get() - const combineMode = $combineFilters.get() - let properties + $filteredRecords.set({ ...$filteredRecords.get(), loading: true }) - if (combineMode === 'and') { - // Fetch Properties that don't exist with $and grouping - properties = $currentProjectFilters.get().map(filterToSearchOperation) - } + queryClient + .fetchQuery({ queryKey, queryFn: fetchRecords }) + .then((response) => { + if (!response) return + $filteredRecords.set({ data: response.data as any[], total: response.total, loading: false }) + }) + .catch((error: unknown) => { + if (error instanceof Error) { + $filteredRecords.set({ data: undefined, loading: false, error: error.message, total: 0 }) + } + }) + } - return await api.properties.find({ - searchQuery: { - labels, - where: convertToSearchQuery(properties) - }, - init - }) - }, - deps: [$combineFilters, $currentProjectId, $activeLabels, $currentProjectFilters] + run() + return () => {} }) -export const $currentProjectSuggestedFields = createAsyncStore({ - key: '$currentProjectSuggestedFields', - async fetcher(init) { - const projectId = $currentProjectId.get() +// Re-run on dependency changes +for (const dep of [ + $currentProjectFilters, + $recordsOrderBy, + $currentProjectRecordsSkip, + $currentProjectRecordsLimit, + $activeLabels, + $combineFilters, + $currentProjectId +]) { + onNotify(dep as any, () => { + queueMicrotask(() => ($filteredRecords as any).refetch()) + }) +} + +// Refactored: $filteredRecordsRelations using react-query bridge +function buildRelationsQueryArgs() { + const filters = $currentProjectFilters.get() + const orderBy = $recordsOrderBy.get() + const skip = $currentProjectRecordsSkip.get() + const limit = $currentProjectRecordsLimit.get() + const labels = $activeLabels.get() + const combineMode = $combineFilters.get() + const properties = filters.map(filterToSearchOperation) + + const order = Object.entries(orderBy ?? {}).reduce((acc, [key, direction]) => { + if (key === '__id') { + return direction as SortDirection + } + if (key && direction) { + // @ts-ignore + acc[key] = direction as SortDirection + } + return acc + }, {}) + + const where = + combineMode === 'or' ? { $or: convertToSearchQuery(properties) } : convertToSearchQuery(properties) + + return { where, orderBy: order, skip, limit, labels } +} + +async function fetchRelations(): Promise<{ data: unknown[]; total?: number } | undefined> { + const projectId = $currentProjectId.get() + if (!projectId) return + const args = buildRelationsQueryArgs() + const { data, total } = await api.relationships.find({ searchQuery: args } as any) + return { data, total } +} +export const $filteredRecordsRelations = map<{ + data: any[] | undefined + loading: boolean + error?: string + total?: number +}>({ + data: undefined, + loading: true, + error: undefined, + total: undefined +}) +// @ts-ignore +$filteredRecordsRelations.refetch = async () => { + const projectId = $currentProjectId.get() + if (!projectId) { + $filteredRecordsRelations.set({ data: undefined, loading: false, total: 0 }) + return + } + const args = buildRelationsQueryArgs() + const queryKey = ['record-relations', projectId, JSON.stringify(args)] + $filteredRecordsRelations.set({ ...$filteredRecordsRelations.get(), loading: true }) + try { + const response = await queryClient.fetchQuery({ queryKey, queryFn: fetchRelations }) + if (!response) return + $filteredRecordsRelations.set({ data: response.data as any[], total: response.total, loading: false }) + } catch (error) { + if (error instanceof Error) { + $filteredRecordsRelations.set({ data: undefined, loading: false, error: error.message, total: 0 }) + } + } +} +onMount($filteredRecordsRelations, () => { + const run = () => { + const projectId = $currentProjectId.get() if (!projectId) { + $filteredRecordsRelations.set({ data: undefined, loading: false, total: 0 }) return } + const args = buildRelationsQueryArgs() + const queryKey = ['record-relations', projectId, JSON.stringify(args)] + $filteredRecordsRelations.set({ ...$filteredRecordsRelations.get(), loading: true }) + queryClient + .fetchQuery({ queryKey, queryFn: fetchRelations }) + .then((response) => { + if (!response) return + $filteredRecordsRelations.set({ data: response.data as any[], total: response.total, loading: false }) + }) + .catch((error: unknown) => { + if (error instanceof Error) { + $filteredRecordsRelations.set({ data: undefined, loading: false, error: error.message, total: 0 }) + } + }) + } + run() + return () => {} +}) +for (const dep of [ + $currentProjectFilters, + $recordsOrderBy, + $currentProjectRecordsSkip, + $currentProjectRecordsLimit, + $activeLabels, + $combineFilters, + $currentProjectId +]) { + onNotify(dep as any, () => queueMicrotask(() => ($filteredRecordsRelations as any).refetch())) +} - const labels = $activeLabels.get() - - let properties +// Refactored: $currentProjectFields react-query bridge +function buildFieldsQueryArgs() { + const labels = $activeLabels.get() + const combineMode = $combineFilters.get() + let properties + if (combineMode === 'and') { + properties = $currentProjectFilters.get().map(filterToSearchOperation) + } + return { labels, where: convertToSearchQuery(properties) } +} +async function fetchFields(): Promise<{ data: any[] } | undefined> { + const projectId = $currentProjectId.get() + if (!projectId) return + const args = buildFieldsQueryArgs() + const response = await api.properties.find({ searchQuery: args } as any) + return { data: (response as any).data ?? response } +} +export const $currentProjectFields = map<{ + data: any[] | undefined + loading: boolean + error?: string +}>({ + data: undefined, + loading: true, + error: undefined +}) +// @ts-ignore +$currentProjectFields.refetch = async () => { + const projectId = $currentProjectId.get() + if (!projectId) { + $currentProjectFields.set({ data: undefined, loading: false }) + return + } + const args = buildFieldsQueryArgs() + const queryKey = ['project-fields', projectId, JSON.stringify(args)] + $currentProjectFields.set({ ...$currentProjectFields.get(), loading: true }) + try { + const response = await queryClient.fetchQuery({ queryKey, queryFn: fetchFields }) + if (!response) return + $currentProjectFields.set({ data: response.data as any[], loading: false }) + } catch (error) { + if (error instanceof Error) { + $currentProjectFields.set({ data: undefined, loading: false, error: error.message }) + } + } +} +onMount($currentProjectFields, () => { + const run = () => { + const projectId = $currentProjectId.get() + if (!projectId) { + $currentProjectFields.set({ data: undefined, loading: false }) + return + } + const args = buildFieldsQueryArgs() + const queryKey = ['project-fields', projectId, JSON.stringify(args)] + $currentProjectFields.set({ ...$currentProjectFields.get(), loading: true }) + queryClient + .fetchQuery({ queryKey, queryFn: fetchFields }) + .then((response) => { + if (!response) return + $currentProjectFields.set({ data: response.data as any[], loading: false }) + }) + .catch((error: unknown) => { + if (error instanceof Error) { + $currentProjectFields.set({ data: undefined, loading: false, error: error.message }) + } + }) + } + run() + return () => {} +}) +for (const dep of [$combineFilters, $currentProjectId, $activeLabels, $currentProjectFilters]) { + onNotify(dep as any, () => queueMicrotask(() => ($currentProjectFields as any).refetch())) +} - return await api.properties.find({ - searchQuery: { - labels, - where: properties - }, - init - }) - }, - deps: [$currentProjectId, $activeLabels, $currentProjectFilters] +// Refactored: $currentProjectSuggestedFields react-query bridge +function buildSuggestedFieldsQueryArgs() { + const labels = $activeLabels.get() + return { labels } +} +async function fetchSuggestedFields(): Promise<{ data: any[] } | undefined> { + const projectId = $currentProjectId.get() + if (!projectId) return + const args = buildSuggestedFieldsQueryArgs() + const response = await api.properties.find({ searchQuery: args } as any) + return { data: (response as any).data ?? response } +} +export const $currentProjectSuggestedFields = map<{ + data: any[] | undefined + loading: boolean + error?: string +}>({ + data: undefined, + loading: true, + error: undefined }) +// @ts-ignore +$currentProjectSuggestedFields.refetch = async () => { + const projectId = $currentProjectId.get() + if (!projectId) { + $currentProjectSuggestedFields.set({ data: undefined, loading: false }) + return + } + const args = buildSuggestedFieldsQueryArgs() + const queryKey = ['project-suggested-fields', projectId, JSON.stringify(args)] + $currentProjectSuggestedFields.set({ ...$currentProjectSuggestedFields.get(), loading: true }) + try { + const response = await queryClient.fetchQuery({ queryKey, queryFn: fetchSuggestedFields }) + if (!response) return + $currentProjectSuggestedFields.set({ data: response.data as any[], loading: false }) + } catch (error) { + if (error instanceof Error) { + $currentProjectSuggestedFields.set({ data: undefined, loading: false, error: error.message }) + } + } +} +onMount($currentProjectSuggestedFields, () => { + const run = () => { + const projectId = $currentProjectId.get() + if (!projectId) { + $currentProjectSuggestedFields.set({ data: undefined, loading: false }) + return + } + const args = buildSuggestedFieldsQueryArgs() + const queryKey = ['project-suggested-fields', projectId, JSON.stringify(args)] + $currentProjectSuggestedFields.set({ ...$currentProjectSuggestedFields.get(), loading: true }) + queryClient + .fetchQuery({ queryKey, queryFn: fetchSuggestedFields }) + .then((response) => { + if (!response) return + $currentProjectSuggestedFields.set({ data: response.data as any[], loading: false }) + }) + .catch((error: unknown) => { + if (error instanceof Error) { + $currentProjectSuggestedFields.set({ data: undefined, loading: false, error: error.message }) + } + }) + } + run() + return () => {} +}) +for (const dep of [$currentProjectId, $activeLabels, $currentProjectFilters]) { + onNotify(dep as any, () => queueMicrotask(() => ($currentProjectSuggestedFields as any).refetch())) +} export const incrementRecordsPage = action($currentProjectRecordsSkip, 'incrementPage', (store) => { const limit = $currentProjectRecordsLimit.get() @@ -292,6 +566,8 @@ export const setRecordsSort = action($recordsOrderBy, 'setRecordsSort', (store, export const resetFilters = () => { removeSearchParam('query') + // Immediately clear filters store so dependent queries refetch without waiting for router subscription + $currentProjectFilters.set([]) } export const removeFilter = (filter: Filter) => { @@ -302,6 +578,8 @@ export const removeFilter = (filter: Filter) => { } changeSearchParam('query', encodeQuery(newFilters)) + // Proactively update store to trigger reactive refetch (router update may be async or skipped if value unchanged) + $currentProjectFilters.set(newFilters) } export const addFilter = (operation: AnySearchOperation) => { diff --git a/platform/dashboard/src/features/projects/useGraphData.ts b/platform/dashboard/src/features/projects/useGraphData.ts new file mode 100644 index 00000000..20cdacc0 --- /dev/null +++ b/platform/dashboard/src/features/projects/useGraphData.ts @@ -0,0 +1,517 @@ +'use client' +import { useCallback, useEffect, useRef, useState } from 'react' +import { atom, map, onMount, onNotify } from 'nanostores' + +import { DBRecordInstance, Property, Relation } from '@rushdb/javascript-sdk' +import { + $filteredRecords, + $filteredRecordsRelations, + $currentProjectFields, + $currentProjectFilters, + $recordsOrderBy, + $activeLabels, + $combineFilters +} from './stores/current-project' +import { $currentProjectId } from './stores/id' +import { useStore } from '@nanostores/react' +import { api } from '~/lib/api' +import { queryClient } from '~/lib/queryClient' +import { convertToSearchQuery, filterToSearchOperation } from './utils' +import { Sort, SortDirection } from '~/types' + +type GraphNode = { + id: string + type: 'record' | 'property' + name?: string + label?: string + propertyType?: string + val?: number + proptypes?: Record + neighbors?: GraphNode[] + links?: GraphLink[] + // force-graph runtime fields + x?: number + y?: number + z?: number + [k: string]: any +} +type GraphLink = { source: string | GraphNode; target: string | GraphNode; type: string } + +// Constants for graph data fetching +const GRAPH_BATCH_SIZE = 1000 +const GRAPH_MAX_RECORDS = 10000 +const GRAPH_MAX_BATCHES = GRAPH_MAX_RECORDS / GRAPH_BATCH_SIZE + +// Graph-specific stores for fetching large datasets +type GraphRecordsQueryData = { data: unknown[]; total?: number; hasMore: boolean } + +function buildGraphRecordsQueryArgs(skip = 0, limit = GRAPH_BATCH_SIZE) { + const filters = $currentProjectFilters.get() + const orderBy = $recordsOrderBy.get() + const labels = $activeLabels.get() + const combineMode = $combineFilters.get() + const properties = filters.map(filterToSearchOperation) + + const order = Object.entries(orderBy ?? {}).reduce((acc, [key, direction]) => { + if (key === '__id') { + return direction as SortDirection + } + if (key && direction) { + // @ts-ignore + acc[key] = direction as SortDirection + } + return acc + }, {}) + + const where = + combineMode === 'or' ? { $or: convertToSearchQuery(properties) } : convertToSearchQuery(properties) + + return { where, orderBy: order, skip, limit, labels } +} + +async function fetchGraphRecords( + skip = 0, + limit = GRAPH_BATCH_SIZE +): Promise { + const projectId = $currentProjectId.get() + if (!projectId) return + const args = buildGraphRecordsQueryArgs(skip, limit) + const { data, total } = await api.records.find(args as any, { signal: undefined as any }) + return { data, total, hasMore: (total || 0) > skip + limit } +} + +async function fetchGraphRelations( + skip = 0, + limit = GRAPH_BATCH_SIZE +): Promise { + const projectId = $currentProjectId.get() + if (!projectId) return + const args = buildGraphRecordsQueryArgs(skip, limit) + const { data, total } = await api.relationships.find({ searchQuery: args } as any) + return { data, total, hasMore: (total || 0) > skip + limit } +} + +// Graph records store that accumulates up to 10k records +export const $graphRecords = map<{ + data: any[] + loading: boolean + error?: string + total?: number + loadedBatches: number + hasMore: boolean +}>({ + data: [], + loading: false, + error: undefined, + total: undefined, + loadedBatches: 0, + hasMore: true +}) + +// Graph relations store that accumulates relations +export const $graphRelations = map<{ + data: any[] + loading: boolean + error?: string + total?: number + loadedBatches: number + hasMore: boolean +}>({ + data: [], + loading: false, + error: undefined, + total: undefined, + loadedBatches: 0, + hasMore: true +}) + +// Sequential batch loading for records +// @ts-ignore +$graphRecords.loadNextBatch = async () => { + const currentState = $graphRecords.get() + if (currentState.loading || !currentState.hasMore || currentState.loadedBatches >= GRAPH_MAX_BATCHES) { + return + } + + const projectId = $currentProjectId.get() + if (!projectId) { + $graphRecords.set({ data: [], loading: false, total: 0, loadedBatches: 0, hasMore: false }) + return + } + + const skip = currentState.loadedBatches * GRAPH_BATCH_SIZE + $graphRecords.set({ ...currentState, loading: true }) + + try { + const response = await fetchGraphRecords(skip, GRAPH_BATCH_SIZE) + if (!response) return + + const newData = [...currentState.data, ...response.data] + const newBatchCount = currentState.loadedBatches + 1 + const hasMore = response.hasMore && newBatchCount < GRAPH_MAX_BATCHES + + $graphRecords.set({ + data: newData, + total: response.total, + loading: false, + loadedBatches: newBatchCount, + hasMore + }) + + // Auto-load next batch if there's more data and we haven't hit the limit + if (hasMore && newData.length < GRAPH_MAX_RECORDS) { + setTimeout(() => ($graphRecords as any).loadNextBatch(), 100) + } + } catch (error) { + if (error instanceof Error) { + $graphRecords.set({ + ...currentState, + loading: false, + error: error.message + }) + } + } +} + +// Sequential batch loading for relations +// @ts-ignore +$graphRelations.loadNextBatch = async () => { + const currentState = $graphRelations.get() + if (currentState.loading || !currentState.hasMore || currentState.loadedBatches >= GRAPH_MAX_BATCHES) { + return + } + + const projectId = $currentProjectId.get() + if (!projectId) { + $graphRelations.set({ data: [], loading: false, total: 0, loadedBatches: 0, hasMore: false }) + return + } + + const skip = currentState.loadedBatches * GRAPH_BATCH_SIZE + $graphRelations.set({ ...currentState, loading: true }) + + try { + const response = await fetchGraphRelations(skip, GRAPH_BATCH_SIZE) + if (!response) return + + const newData = [...currentState.data, ...response.data] + const newBatchCount = currentState.loadedBatches + 1 + const hasMore = response.hasMore && newBatchCount < GRAPH_MAX_BATCHES + + $graphRelations.set({ + data: newData, + total: response.total, + loading: false, + loadedBatches: newBatchCount, + hasMore + }) + + // Auto-load next batch if there's more data and we haven't hit the limit + if (hasMore && newData.length < GRAPH_MAX_RECORDS) { + setTimeout(() => ($graphRelations as any).loadNextBatch(), 100) + } + } catch (error) { + if (error instanceof Error) { + $graphRelations.set({ + ...currentState, + loading: false, + error: error.message + }) + } + } +} + +// Reset function for both stores +// @ts-ignore +$graphRecords.reset = () => { + $graphRecords.set({ + data: [], + loading: false, + error: undefined, + total: undefined, + loadedBatches: 0, + hasMore: true + }) +} + +// @ts-ignore +$graphRelations.reset = () => { + $graphRelations.set({ + data: [], + loading: false, + error: undefined, + total: undefined, + loadedBatches: 0, + hasMore: true + }) +} + +// Start loading function - resets stores and begins batch loading +const startLoadingRecords = () => { + ;($graphRecords as any).reset() + setTimeout(() => ($graphRecords as any).loadNextBatch(), 0) +} + +const startLoadingRelations = () => { + ;($graphRelations as any).reset() + setTimeout(() => ($graphRelations as any).loadNextBatch(), 0) +} + +// Reset on dependency changes +for (const dep of [ + $currentProjectFilters, + $recordsOrderBy, + $activeLabels, + $combineFilters, + $currentProjectId +]) { + onNotify(dep as any, () => { + queueMicrotask(() => { + startLoadingRecords() + startLoadingRelations() + }) + }) +} + +// Initialize loading when stores are first mounted +onMount($graphRecords, () => { + const projectId = $currentProjectId.get() + if (projectId) { + startLoadingRecords() + } + return () => {} +}) + +onMount($graphRelations, () => { + const projectId = $currentProjectId.get() + if (projectId) { + startLoadingRelations() + } + return () => {} +}) + +const normalizeRecord = (r: any): GraphNode | null => { + const id = r.__id + if (!id) return null + const label = r.__label + const pt = r.__proptypes + return { + id, + type: 'record', + label, + name: (r.data?.name || r.data?.title || label) as string, + proptypes: pt, + properties: r, + val: 4 + } +} + +export const useGraphData = () => { + // Use graph-specific stores that can load up to 10k records + const recordsStore = useStore($graphRecords) + const relationsStore = useStore($graphRelations) + const fieldsStore = useStore($currentProjectFields) + + const [showProperties, setShowProperties] = useState(false) + const [dataVersion, setDataVersion] = useState(0) + + // Transform store data into refs for compatibility with existing graph logic + const foundRelationships = useRef>(new Map()) + const foundRecords = useRef>(new Map()) + const foundProperties = useRef>(new Map()) + + const [graphData, setGraphData] = useState<{ nodes: GraphNode[]; links: GraphLink[] }>({ + nodes: [], + links: [] + }) + + // Build graph data when underlying refs change + useEffect(() => { + const nodes = Array.from(foundRecords.current.values()) + .filter((r: any) => !!r.__id) + .map(normalizeRecord) + .filter((n: GraphNode | null): n is GraphNode => n !== null) + + const properties: GraphNode[] = Array.from(foundProperties.current.values()).map((p: any) => ({ + id: p.id || p.__id, + type: 'property', + name: p.name, + propertyType: p.type, + properties: p, + val: 1.5 + })) + + const recordIds = new Set(nodes.map((n) => n.id)) + const links: GraphLink[] = Array.from(foundRelationships.current.values()) + .filter((rel: any) => recordIds.has(rel.sourceId) && recordIds.has(rel.targetId)) + .map((rel: any) => ({ + source: rel.sourceId, + target: rel.targetId, + type: rel.type || 'rel' + })) + + const propertyLinks: GraphLink[] = [] + properties.forEach((prop) => { + nodes.forEach((node) => { + if (node.proptypes && prop.name && node.proptypes[prop.name] === prop.propertyType) { + propertyLinks.push({ source: prop.id, target: node.id, type: 'value' }) + } + }) + }) + + const finalNodes = showProperties ? [...nodes, ...properties] : nodes + const finalLinks = showProperties ? [...links, ...propertyLinks] : links + + // cross-link for highlighting + const nodeMap = new Map(finalNodes.map((n) => [n.id, n])) + finalLinks.forEach((l) => { + const a = typeof l.source === 'string' ? nodeMap.get(l.source) : l.source + const b = typeof l.target === 'string' ? nodeMap.get(l.target) : l.target + if (!a || !b) return + ;(a.neighbors || (a.neighbors = [])).push(b) + ;(b.neighbors || (b.neighbors = [])).push(a) + ;(a.links || (a.links = [])).push(l) + ;(b.links || (b.links = [])).push(l) + }) + + setGraphData({ nodes: finalNodes, links: finalLinks }) + }, [dataVersion, showProperties]) + + // Update refs when store data changes and increment version to trigger graph rebuild + useEffect(() => { + // Clear and rebuild records map + foundRecords.current.clear() + if (recordsStore.data && recordsStore.data.length > 0) { + recordsStore.data.forEach((record: any) => { + // Handle both DBRecordInstance and raw record formats + const recordData = record.data || record + if (recordData.__id) { + foundRecords.current.set(recordData.__id, recordData) + } + }) + } + setDataVersion((v) => v + 1) + }, [recordsStore.data]) + + useEffect(() => { + // Clear and rebuild relationships map + foundRelationships.current.clear() + if (relationsStore.data) { + relationsStore.data.forEach((relation: any) => { + const key = `${relation.sourceId}|${relation.targetId}|${relation.type}` + foundRelationships.current.set(key, relation) + }) + } + setDataVersion((v) => v + 1) + }, [relationsStore.data]) + + useEffect(() => { + // Clear and rebuild properties map + foundProperties.current.clear() + if (fieldsStore.data) { + fieldsStore.data.forEach((field: Property) => { + const key = field.id + if (key) { + foundProperties.current.set(key, field) + } + }) + } + setDataVersion((v) => v + 1) + }, [fieldsStore.data]) + + // Compute loading and error state from all stores + // Show loading if any store is actively loading OR if there's more data to load automatically + const isLoadingRecords = + recordsStore.loading || (recordsStore.hasMore && recordsStore.loadedBatches < GRAPH_MAX_BATCHES) + const isLoadingRelations = + relationsStore.loading || (relationsStore.hasMore && relationsStore.loadedBatches < GRAPH_MAX_BATCHES) + const loading = isLoadingRecords || isLoadingRelations || fieldsStore.loading + const error = recordsStore.error || relationsStore.error || fieldsStore.error + + // Fetch functions that trigger store loading + const fetchData = async (reset = false) => { + // Start loading both records and relations from scratch + startLoadingRecords() + startLoadingRelations() + // Also refresh fields + ;($currentProjectFields as any).refetch?.() + } + + const fetchRecords = async (reset = false) => { + startLoadingRecords() + } + + const fetchRelationships = async (reset = false) => { + startLoadingRelations() + } + + const fetchProperties = async (reset = false) => { + ;($currentProjectFields as any).refetch?.() + } + + // Manual batch loading functions + const loadMoreRecords = async () => { + if (recordsStore.hasMore && !recordsStore.loading) { + ;($graphRecords as any).loadNextBatch() + } + } + + const loadMoreRelationships = async () => { + if (relationsStore.hasMore && !relationsStore.loading) { + ;($graphRelations as any).loadNextBatch() + } + } + + // Computed hasMore flags + const hasMoreRecords = recordsStore.hasMore + const hasMoreRelationships = relationsStore.hasMore + + useEffect(() => { + // initial load - start loading graph data when component mounts + const projectId = $currentProjectId.get() + if (projectId) { + fetchData(true) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + return { + loading, + graphData, + setLoading: () => {}, // no-op since loading is computed from stores + error, + foundRecords, + foundRelationships, + foundProperties, + hasMoreRecords, + hasMoreRelationships, + showProperties, + setShowProperties, + fetchData, + fetchRecords, + fetchRelationships, + fetchProperties, + loadMoreRecords, + loadMoreRelationships, + dataVersion, + // Additional graph stats + totalRecordsLoaded: recordsStore.data.length, + totalRelationsLoaded: relationsStore.data.length, + recordsBatchesLoaded: recordsStore.loadedBatches, + relationsBatchesLoaded: relationsStore.loadedBatches, + maxPossibleRecords: GRAPH_MAX_RECORDS, + // Detailed loading states + isLoadingRecords, + isLoadingRelations, + recordsTotal: recordsStore.total, + relationsTotal: relationsStore.total, + recordsLoadingProgress: + recordsStore.total ? + Math.min(100, (recordsStore.data.length / Math.min(recordsStore.total, GRAPH_MAX_RECORDS)) * 100) + : 0, + relationsLoadingProgress: + relationsStore.total ? + Math.min(100, (relationsStore.data.length / Math.min(relationsStore.total, GRAPH_MAX_RECORDS)) * 100) + : 0 + } +} diff --git a/platform/dashboard/src/features/records/stores/actionbar.ts b/platform/dashboard/src/features/records/stores/actionbar.ts index 69faf4c4..fcd81452 100644 --- a/platform/dashboard/src/features/records/stores/actionbar.ts +++ b/platform/dashboard/src/features/records/stores/actionbar.ts @@ -71,7 +71,6 @@ export const batchDeleteSelected = createMutator({ ...body }) }, - invalidates: [$currentProjectLabels, $currentProjectFields, $filteredRecords], onSuccess: () => { resetRecordsSelection() toast({ diff --git a/platform/dashboard/src/features/records/stores/batch.ts b/platform/dashboard/src/features/records/stores/batch.ts index 16fc6f34..8641d0a4 100644 --- a/platform/dashboard/src/features/records/stores/batch.ts +++ b/platform/dashboard/src/features/records/stores/batch.ts @@ -1,10 +1,5 @@ import type { AnyRecord } from 'dns' -import { - $currentProjectFields, - $currentProjectLabels, - $filteredRecords -} from '~/features/projects/stores/current-project' import { api } from '~/lib/api' import { createMutator } from '~/lib/fetcher' import { DBRecordCreationOptions } from '@rushdb/javascript-sdk' @@ -18,6 +13,5 @@ export const createMany = createMutator<{ return await api.records.createMany({ init, data, label, options }) }, throwError: true, - onError: (error: unknown) => console.log({ error }), - invalidates: [$filteredRecords, $currentProjectLabels, $currentProjectFields] + onError: (error: unknown) => console.log({ error }) }) diff --git a/platform/dashboard/src/features/records/stores/mutations.ts b/platform/dashboard/src/features/records/stores/mutations.ts index 63191385..b4a8546d 100644 --- a/platform/dashboard/src/features/records/stores/mutations.ts +++ b/platform/dashboard/src/features/records/stores/mutations.ts @@ -1,10 +1,5 @@ import type { ApiParams, ApiResult } from '~/lib/api' -import { - $currentProjectFields, - $currentProjectLabels, - $filteredRecords -} from '~/features/projects/stores/current-project' import { api } from '~/lib/api' import { createMutator } from '~/lib/fetcher' @@ -12,7 +7,6 @@ export const deleteRecordMutation = createMutator< ApiParams, ApiResult >({ - invalidates: [$currentProjectLabels, $currentProjectFields, $filteredRecords], async fetcher({ init, id }) { if (!id) { return diff --git a/platform/dashboard/src/features/records/stores/related-actionbar.ts b/platform/dashboard/src/features/records/stores/related-actionbar.ts index 76d88f07..21da2011 100644 --- a/platform/dashboard/src/features/records/stores/related-actionbar.ts +++ b/platform/dashboard/src/features/records/stores/related-actionbar.ts @@ -71,7 +71,7 @@ export const batchDeleteRelatedSelected = createMutator({ ...body }) }, - invalidates: [$currentProjectLabels, $currentProjectFields, $currentRelatedRecords], + invalidates: [$currentRelatedRecords], onSuccess: () => { resetRelatedRecordsSelection() toast({ diff --git a/platform/dashboard/src/lib/queryClient.ts b/platform/dashboard/src/lib/queryClient.ts new file mode 100644 index 00000000..e1ec47c3 --- /dev/null +++ b/platform/dashboard/src/lib/queryClient.ts @@ -0,0 +1,4 @@ +import { QueryClient } from '@tanstack/react-query' + +// Singleton QueryClient for non-hook based query usage (bridging to nanostores) +export const queryClient = new QueryClient() diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b964e183..d9e05095 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -446,9 +446,12 @@ importers: react-dom: specifier: 19.1.0 version: 19.1.0(react@19.1.0) - react-force-graph: - specifier: ^1.45.4 - version: 1.45.4(react@19.1.0)(three@0.171.0) + react-force-graph-2d: + specifier: 1.28.0 + version: 1.28.0(react@19.1.0) + react-force-graph-3d: + specifier: 1.28.0 + version: 1.28.0(react@19.1.0) react-helmet-async: specifier: ^2.0.5 version: 2.0.5(react@19.1.0) @@ -464,12 +467,6 @@ importers: tailwindcss-animate: specifier: ^1.0.7 version: 1.0.7(tailwindcss@3.4.16(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@18.19.68)(typescript@5.7.2))) - three: - specifier: ^0.171.0 - version: 0.171.0 - three-spritetext: - specifier: ^1.9.3 - version: 1.9.3(three@0.171.0) yup: specifier: ^1.2.0 version: 1.5.0 @@ -495,9 +492,6 @@ importers: '@types/react-window': specifier: ^1.8.5 version: 1.8.8 - '@types/three': - specifier: ^0.171.0 - version: 0.171.0 '@typescript-eslint/eslint-plugin': specifier: ^8.19.0 version: 8.19.0(@typescript-eslint/parser@8.19.1(eslint@9.2.0)(typescript@5.7.2))(eslint@9.2.0)(typescript@5.7.2) @@ -697,16 +691,8 @@ importers: packages: - 3d-force-graph-ar@1.9.3: - resolution: {integrity: sha512-KHcwKVF8394ioKhc4h3y5H9jPBvw+lUmD1BJd1AEV/SO+FM324CXVYTvbGg2IuW0nOPR/ChXvlWhvIZaOtyeTg==} - engines: {node: '>=12'} - - 3d-force-graph-vr@2.4.3: - resolution: {integrity: sha512-os/IPpkWUqNnqWFQISaa5Snts1uUFf485SMPpPi2BIJLHbW9Jxt+1PQWc2rqT3tDFTh9J72BnxyuqegyMrqxdQ==} - engines: {node: '>=12'} - - 3d-force-graph@1.74.5: - resolution: {integrity: sha512-CyneQqxoFwTGOqBVe8DSED0uQrU3q4+xpgvl0kbNHctv/kdBRgOKSSJgyB7j+08mUKaHchelMmwNVmQf5XaJrA==} + 3d-force-graph@1.78.4: + resolution: {integrity: sha512-R722H4PRttwt3dddnV8+XRCQ9xQzWhI3j+8aXZ/Hlv4Yr6hR95/DjO/pMXpvQSQ1XDTV2dUl7sX2rajSm4k8SQ==} engines: {node: '>=12'} '@algolia/autocomplete-core@1.17.9': @@ -3933,10 +3919,6 @@ packages: '@sinclair/typebox@0.27.8': resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} - '@sindresorhus/is@0.14.0': - resolution: {integrity: sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==} - engines: {node: '>=6'} - '@sindresorhus/is@4.6.0': resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} engines: {node: '>=10'} @@ -4194,10 +4176,6 @@ packages: '@swc/types@0.1.17': resolution: {integrity: sha512-V5gRru+aD8YVyCOMAjMpWR1Ui577DD5KSJsHP8RAxopAH22jFz6GZd/qxqjO6MJHQhcsjvjOFXyDhyLQUnMveQ==} - '@szmarczak/http-timer@1.1.2': - resolution: {integrity: sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==} - engines: {node: '>=6'} - '@szmarczak/http-timer@5.0.1': resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==} engines: {node: '>=14.16'} @@ -4229,9 +4207,6 @@ packages: '@tsconfig/node16@1.0.4': resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} - '@tweenjs/tween.js@23.1.3': - resolution: {integrity: sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==} - '@tweenjs/tween.js@25.0.0': resolution: {integrity: sha512-XKLA6syeBUaPzx4j3qwMqzzq+V4uo72BnlbOjmuljLrRqdsd3qnzvZZoxvMHZ23ndsRS4aufU6JOZYpCbU6T1A==} @@ -4442,9 +4417,6 @@ packages: '@types/jsonwebtoken@9.0.1': resolution: {integrity: sha512-c5ltxazpWabia/4UzhIoaDcIza4KViOQhdbjRlfcIGVnsE3c3brkz9Z+F/EeJIECOQP7W7US2hNE930cWWkPiw==} - '@types/keyv@3.1.4': - resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} - '@types/mdast@3.0.15': resolution: {integrity: sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==} @@ -4531,9 +4503,6 @@ packages: '@types/react@19.1.0': resolution: {integrity: sha512-UaicktuQI+9UKyA4njtDOGBD/67t8YEBt2xdfqu8+gP9hqPUPsiXlNPcpS2gVdjmis5GKPG3fCxbQLVgxsQZ8w==} - '@types/responselike@1.0.3': - resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} - '@types/retry@0.12.0': resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==} @@ -4558,9 +4527,6 @@ packages: '@types/stack-utils@2.0.3': resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} - '@types/stats.js@0.17.3': - resolution: {integrity: sha512-pXNfAD3KHOdif9EQXZ9deK82HVNaXP5ZIF5RP2QG6OQFNTaY2YIetfrE9t528vEreGQvEPRDDc8muaoYeK0SxQ==} - '@types/stream-chain@2.1.0': resolution: {integrity: sha512-guDyAl6s/CAzXUOWpGK2bHvdiopLIwpGu8v10+lb9hnQOyo4oj/ZUQFOvqFjKGsE3wJP1fpIesCcMvbXuWsqOg==} @@ -4576,9 +4542,6 @@ packages: '@types/svgo@2.6.4': resolution: {integrity: sha512-l4cmyPEckf8moNYHdJ+4wkHvFxjyW6ulm9l4YGaOxeyBWPhBOT0gvni1InpFPdzx1dKf/2s62qGITwxNWnPQng==} - '@types/three@0.171.0': - resolution: {integrity: sha512-oLuT1SAsT+CUg/wxUTFHo0K3NtJLnx9sJhZWQJp/0uXqFpzSk1hRHmvWvpaAWSfvx2db0lVKZ5/wV0I0isD2mQ==} - '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} @@ -4591,9 +4554,6 @@ packages: '@types/validator@13.12.2': resolution: {integrity: sha512-6SlHBzUW8Jhf3liqrGGXyTJSIFe4nqlJ5A5KaMZ2l/vbM3Wh3KSybots/wfWVzNLK4D1NZluDlSQIbIEPx6oyA==} - '@types/webxr@0.5.20': - resolution: {integrity: sha512-JGpU6qiIJQKUuVSKx1GtQnHJGxRjtfGIhzO2ilq43VZZS//f1h1Sgexbdk+Lq+7569a6EYhOWrUpIruR/1Enmg==} - '@types/ws@8.5.13': resolution: {integrity: sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==} @@ -4863,9 +4823,6 @@ packages: '@webassemblyjs/wast-printer@1.14.1': resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==} - '@webgpu/types@0.1.52': - resolution: {integrity: sha512-eI883Nlag2hGIkhXxAnq8s4APpqXWuPL3Gbn2ghiU12UjLvfCbVqHK4XfXl3eLRTatqcMmeK7jws7IwWsGfbzw==} - '@xtuc/ieee754@1.2.0': resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} @@ -4929,16 +4886,6 @@ packages: resolution: {integrity: sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==} engines: {node: '>= 10.0.0'} - aframe-extras@7.5.2: - resolution: {integrity: sha512-rLjKZTMDVt+ial6/S8IH1lnKAQQgPclMNjuImh80nC67OTUT9yOEOZh79IqSOQD2YGs1DEwkl9hAkUSiPkUKCQ==} - - aframe-forcegraph-component@3.1.0: - resolution: {integrity: sha512-WJH++Au5LnIjISqkSkkQMN0PJdVzk5n7DSQe1iBy1juQm/FM0mIkHhW13BvmKwr1xgeA8NCQoLvMJFkhb6qX2g==} - - aframe@1.6.0: - resolution: {integrity: sha512-+P1n2xKGZQbCNW4lTwfue9in2KmfAwYD/BZOU5uXKrJCTegPyUZZX/haJRR9Rb33ij+KPj3vFdwT5ALaucXTNA==} - engines: {node: '>= 4.6.0', npm: '>= 2.15.9'} - agent-base@7.1.3: resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} engines: {node: '>= 14'} @@ -4994,9 +4941,6 @@ packages: alien-signals@0.4.14: resolution: {integrity: sha512-itUAVzhczTmP2U5yX67xVpsbbOiquusbWVyA9N+sy6+r6YVbFkahXvNCeEPWEOMhwDYwbVbGHFkVL03N9I5g+Q==} - an-array@1.0.0: - resolution: {integrity: sha512-M175GYI7RmsYu24Ok383yZQa3eveDfNnmhTe3OQ3bm70bEovz2gWenH+ST/n32M8lrwLWk74hcPds5CDRPe2wg==} - ansi-align@3.0.1: resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} @@ -5101,10 +5045,6 @@ packages: resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==} engines: {node: '>= 0.4'} - array-shuffle@1.0.1: - resolution: {integrity: sha512-PBqgo1Y2XWSksBzq3GFPEb798ZrW2snAcmr4drbVeF/6MT/5aBlkGJEvu5A/CzXHf4EjbHOj/ZowatjlIiVidA==} - engines: {node: '>=0.10.0'} - array-timsort@1.0.3: resolution: {integrity: sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==} @@ -5144,9 +5084,6 @@ packages: resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} engines: {node: '>=0.10.0'} - as-number@1.0.0: - resolution: {integrity: sha512-HkI/zLo2AbSRO4fqVkmyf3hms0bJDs3iboHqTrNuwTiCRvdYXM7HFhfhB6Dk51anV2LM/IMB83mtK9mHw4FlAg==} - asap@2.0.6: resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} @@ -5396,10 +5333,6 @@ packages: buffer-equal-constant-time@1.0.1: resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} - buffer-equal@0.0.1: - resolution: {integrity: sha512-RgSV6InVQ9ODPdLWJ5UAqBqJBOg370Nz6ZQtRzpt6nUjc8v0St97uJ4PYC6NztqIScrAXafKM3mZPMygSe1ggA==} - engines: {node: '>=0.4.0'} - buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} @@ -5433,10 +5366,6 @@ packages: resolution: {integrity: sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==} engines: {node: '>=14.16'} - cacheable-request@6.1.0: - resolution: {integrity: sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==} - engines: {node: '>=8'} - call-bind-apply-helpers@1.0.1: resolution: {integrity: sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==} engines: {node: '>= 0.4'} @@ -5492,15 +5421,9 @@ packages: resolution: {integrity: sha512-eNycxGS7oQ3IS/9QQY41f/aQjiO9Y/MtedhCgSdsbLSxC9EyUD8L3ehl/Q3Kfmvt8um79S45PBV+5Rxm5ztdSw==} engines: {node: '>=12'} - cardboard-vr-display@1.0.19: - resolution: {integrity: sha512-+MjcnWKAkb95p68elqZLDPzoiF/dGncQilLGvPBM5ZorABp/ao3lCs7nnRcYBckmuNkg1V/5rdGDKoUaCVsHzQ==} - ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} - centra@2.7.0: - resolution: {integrity: sha512-PbFMgMSrmgx6uxCdm57RUos9Tc3fclMvhLSATYN39XsDV29B89zZ3KA89jmY0vwSGazyU+uerqwa6t+KaodPcg==} - chalk@1.1.3: resolution: {integrity: sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==} engines: {node: '>=0.10.0'} @@ -5659,9 +5582,6 @@ packages: resolution: {integrity: sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==} engines: {node: '>=6'} - clone-response@1.0.3: - resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==} - clone@1.0.4: resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} engines: {node: '>=0.8'} @@ -6391,10 +6311,6 @@ packages: resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} engines: {node: '>=0.10'} - decompress-response@3.3.0: - resolution: {integrity: sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==} - engines: {node: '>=4'} - decompress-response@6.0.0: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} @@ -6407,10 +6323,6 @@ packages: babel-plugin-macros: optional: true - deep-assign@2.0.0: - resolution: {integrity: sha512-2QhG3Kxulu4XIF3WL5C5x0sc/S17JLgm1SfvDfIRsR/5m7ZGmcejII7fZ2RyWhN0UWIJm0TNM/eKow6LAn3evQ==} - engines: {node: '>=0.10.0'} - deep-diff@1.0.2: resolution: {integrity: sha512-aWS3UIVH+NPGCD1kki+DCU9Dua032iSsO43LqQpcs4R3+dVv7tX0qBGjiVHJHjplsoUM2XRO/KB92glqc68awg==} @@ -6432,9 +6344,6 @@ packages: defaults@1.0.4: resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} - defer-to-connect@1.1.3: - resolution: {integrity: sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==} - defer-to-connect@2.0.1: resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} engines: {node: '>=10'} @@ -6645,9 +6554,6 @@ packages: dom-serializer@2.0.0: resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} - dom-walk@0.1.2: - resolution: {integrity: sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==} - domelementtype@1.3.1: resolution: {integrity: sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==} @@ -6707,17 +6613,10 @@ packages: resolution: {integrity: sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==} engines: {node: '>=10'} - dtype@2.0.0: - resolution: {integrity: sha512-s2YVcLKdFGS0hpFqJaTwscsyt0E8nNFdmo73Ocd81xNPj4URI4rj6D60A+vFMIw7BXWlb4yRkEwfBqcZzPGiZg==} - engines: {node: '>= 0.8.0'} - dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} - duplexer3@0.1.5: - resolution: {integrity: sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==} - duplexer@0.1.2: resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} @@ -6784,9 +6683,6 @@ packages: end-of-stream@1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} - end-of-stream@1.4.5: - resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} - enhanced-resolve@5.17.1: resolution: {integrity: sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==} engines: {node: '>=10.13.0'} @@ -7289,9 +7185,6 @@ packages: resolution: {integrity: sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ==} engines: {node: '>=0.4.0'} - fflate@0.8.2: - resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} - figures@3.2.0: resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} engines: {node: '>=8'} @@ -7372,6 +7265,10 @@ packages: resolution: {integrity: sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==} deprecated: flatten is deprecated in favor of utility frameworks such as lodash. + float-tooltip@1.7.5: + resolution: {integrity: sha512-/kXzuDnnBqyyWyhDMH7+PfP8J/oXiAavGzcRxASOMRHFuReDtofizLLJsf7nnDLAfEaMW4pVWaXrAjtnglpEkg==} + engines: {node: '>=12'} + follow-redirects@1.15.9: resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} engines: {node: '>=4.0'} @@ -7388,8 +7285,8 @@ packages: resolution: {integrity: sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==} engines: {node: '>=0.10.0'} - force-graph@1.47.2: - resolution: {integrity: sha512-JGAURlgNGH2FrQGa6KcTiA2zreEW32ED6Mfh5UZHxERhyo3RabcftAVnVfgMziOtSl44bHuwdCuI4/afSTUZuQ==} + force-graph@1.50.1: + resolution: {integrity: sha512-CtldBdsUHLmlnerVYe09V9Bxi5iz8GZce1WdBSkwGAFgNFTYn6cW90NQ1lOh/UVm0NhktMRHKugXrS9Sl8Bl3A==} engines: {node: '>=12'} foreground-child@3.3.0: @@ -7530,10 +7427,6 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} - get-stream@4.1.0: - resolution: {integrity: sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==} - engines: {node: '>=6'} - get-stream@5.2.0: resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} engines: {node: '>=8'} @@ -7565,9 +7458,6 @@ packages: github-slugger@1.5.0: resolution: {integrity: sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==} - gl-preserve-state@1.0.0: - resolution: {integrity: sha512-zQZ25l3haD4hvgJZ6C9+s0ebdkW9y+7U2qxvGu1uWOJh8a4RU+jURIKEQhf8elIlFpMH6CrAY2tH0mYrRjet3Q==} - glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -7618,9 +7508,6 @@ packages: resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==} engines: {node: '>=10'} - global@4.4.0: - resolution: {integrity: sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==} - globals@11.12.0: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} @@ -7658,10 +7545,6 @@ packages: resolution: {integrity: sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==} engines: {node: '>=14.16'} - got@9.6.0: - resolution: {integrity: sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==} - engines: {node: '>=8.6'} - graceful-fs@4.2.10: resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} @@ -8215,9 +8098,6 @@ packages: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} - is-function@1.0.2: - resolution: {integrity: sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==} - is-generator-fn@2.1.0: resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} engines: {node: '>=6'} @@ -8644,9 +8524,6 @@ packages: engines: {node: '>=6'} hasBin: true - json-buffer@3.0.0: - resolution: {integrity: sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==} - json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -8730,9 +8607,6 @@ packages: resolution: {integrity: sha512-3IA6DYVhxhBabjSLTNO9S4+OliA3Qvb8pBQXMfC4WxXJgLwZgnfDl0BmB4z6nBMdznBsZ+CGM8DrGZ5hcguDZg==} hasBin: true - keyv@3.1.0: - resolution: {integrity: sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==} - keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -8794,9 +8668,6 @@ packages: layout-base@2.0.1: resolution: {integrity: sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==} - layout-bmfont-text@1.3.4: - resolution: {integrity: sha512-mceomHZ8W7pSKQhTdLvOe1Im4n37u8xa5Gr0J3KPCHRMO/9o7+goWIOzZcUUd+Xgzy3+22bvoIQ0OaN3LRtgaw==} - leac@0.6.0: resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==} @@ -8839,9 +8710,6 @@ packages: list-stylesheets@2.0.2: resolution: {integrity: sha512-maWparA9CnFw0Q7pRJb0DdIz7D6ngnNEBjBujQIRC+uuKH9EljC8bQHUGSuimOs54/lS1x+R3N/Xebca7UzXjg==} - load-bmfont@1.4.2: - resolution: {integrity: sha512-qElWkmjW9Oq1F9EI5Gt7aD9zcdHb9spJCW1L/dmPf7KzCCEJxq8nhHz5eCgI9aMf7vrG/wyaCqdsI+Iy9ZTlog==} - load-json-file@4.0.0: resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==} engines: {node: '>=4'} @@ -8937,14 +8805,6 @@ packages: lower-case@2.0.2: resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} - lowercase-keys@1.0.1: - resolution: {integrity: sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==} - engines: {node: '>=0.10.0'} - - lowercase-keys@2.0.0: - resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} - engines: {node: '>=8'} - lowercase-keys@3.0.0: resolution: {integrity: sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -9021,9 +8881,6 @@ packages: resolution: {integrity: sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==} engines: {node: '>=0.10.0'} - map-limit@0.0.1: - resolution: {integrity: sha512-pJpcfLPnIF/Sk3taPW21G/RQsEEirGaFpCW3oXRwH9dnFHPHNGjNyvh++rdmC2fNqEaTw2MhYJraoJWAHx8kEg==} - map-obj@1.0.1: resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} engines: {node: '>=0.10.0'} @@ -9204,9 +9061,6 @@ packages: mermaid@11.7.0: resolution: {integrity: sha512-/1/5R0rt0Z1Ak0CuznAnCF3HtQgayRXUz6SguzOwN4L+DuCobz0UxnQ+ZdTSZ3AugKVVh78tiVmsHpHWV25TCw==} - meshoptimizer@0.18.1: - resolution: {integrity: sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==} - methods@1.1.2: resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} engines: {node: '>= 0.6'} @@ -9453,10 +9307,6 @@ packages: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} - mimic-response@1.0.1: - resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} - engines: {node: '>=4'} - mimic-response@3.1.0: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} @@ -9465,9 +9315,6 @@ packages: resolution: {integrity: sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - min-document@2.19.0: - resolution: {integrity: sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==} - min-indent@1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} @@ -9754,9 +9601,6 @@ packages: resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} engines: {node: '>= 0.4.0'} - new-array@1.0.0: - resolution: {integrity: sha512-K5AyFYbuHZ4e/ti52y7k18q8UHsS78FlRd85w2Fmsd6AkuLipDihPflKC0p3PN5i8II7+uHxo+CtkLiJDfmS5A==} - next-mdx-remote@5.0.0: resolution: {integrity: sha512-RNNbqRpK9/dcIFZs/esQhuLA8jANqlH694yqoDBK8hkVdJUndzzGmnPHa2nyi90N4Z9VmzuSWNRpr5ItT3M7xQ==} engines: {node: '>=14', npm: '>=7'} @@ -9810,16 +9654,9 @@ packages: ngraph.random@1.1.0: resolution: {integrity: sha512-h25UdUN/g8U7y29TzQtRm/GvGr70lK37yQPvPKXXuVfs7gCm82WipYFZcksQfeKumtOemAzBIcT7lzzyK/edLw==} - nice-color-palettes@3.0.0: - resolution: {integrity: sha512-lL4AjabAAFi313tjrtmgm/bxCRzp4l3vCshojfV/ij3IPdtnRqv6Chcw+SqJUhbe7g3o3BecaqCJYUNLswGBhQ==} - hasBin: true - nice-try@1.0.5: resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} - nipplejs@0.10.2: - resolution: {integrity: sha512-XGxFY8C2DOtobf1fK+MXINTzkkXJLjZDDpfQhOUZf4TSytbc9s4bmA0lB9eKKM8iDivdr9NQkO7DpIQfsST+9g==} - no-case@2.3.2: resolution: {integrity: sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==} @@ -9887,17 +9724,10 @@ packages: resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} engines: {node: '>=0.10.0'} - normalize-url@4.5.1: - resolution: {integrity: sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==} - engines: {node: '>=8'} - normalize-url@8.0.1: resolution: {integrity: sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==} engines: {node: '>=14.16'} - nosleep.js@0.7.0: - resolution: {integrity: sha512-Z4B1HgvzR+en62ghwZf6BwAR6x4/pjezsiMcbF9KMLh7xoscpoYhaSXfY3lLkqC68AtW+/qLJ1lzvBIj0FGaTA==} - npm-run-all@4.1.5: resolution: {integrity: sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==} engines: {node: '>= 4'} @@ -9988,9 +9818,6 @@ packages: resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==} engines: {node: '>= 0.8'} - once@1.3.3: - resolution: {integrity: sha512-6vaNInhu+CHxtONf3zw3vq4SP2DOQhjBvIa3rNcG0+P7eKWlYH6Peu7rHizSloRU2EwMz6GraLieis9Ac9+p1w==} - once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -10036,10 +9863,6 @@ packages: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} - p-cancelable@1.1.0: - resolution: {integrity: sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==} - engines: {node: '>=6'} - p-cancelable@3.0.0: resolution: {integrity: sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==} engines: {node: '>=12.20'} @@ -10131,24 +9954,12 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} - parse-bmfont-ascii@1.0.6: - resolution: {integrity: sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==} - - parse-bmfont-binary@1.0.6: - resolution: {integrity: sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA==} - - parse-bmfont-xml@1.1.6: - resolution: {integrity: sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA==} - parse-entities@2.0.0: resolution: {integrity: sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==} parse-entities@4.0.2: resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} - parse-headers@2.0.5: - resolution: {integrity: sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA==} - parse-json@4.0.0: resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==} engines: {node: '>=4'} @@ -10265,10 +10076,6 @@ packages: pend@1.2.0: resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} - phin@3.7.1: - resolution: {integrity: sha512-GEazpTWwTZaEQ9RhL7Nyz0WwqilbqgLahDM3D0hxWwmVDI52nXEybHqiN6/elwpkJBhcuj+WbBu+QfT0uhPGfQ==} - engines: {node: '>= 8'} - pick-util@1.1.5: resolution: {integrity: sha512-H0MaM8T7wpQ/azvB12ChZw7kpSFzjsgv3Z+N7fUWnL1McTGSEeroCngcK4eOPiFQq08rAyKX3hadcAB1kUqfXA==} @@ -10969,6 +10776,9 @@ packages: resolution: {integrity: sha512-spBB5sgC4cv2YcW03f/IAUN1pgDJWNWD8FzkyY4mArLUMJW+KlQhlmUdKAHQuPfb00Jl5xIfImeOsf6YL8QK7Q==} engines: {node: '>=0.10.0'} + preact@10.27.1: + resolution: {integrity: sha512-V79raXEWch/rbqoNc7nT9E4ep7lu+mI3+sBmfRD4i1M73R3WLYcCtdI0ibxGVf4eQL8ZIz2nFacqEC+rmnOORQ==} + precinct@8.3.1: resolution: {integrity: sha512-pVppfMWLp2wF68rwHqBIpPBYY8Kd12lDhk8LVQzOwqllifVR15qNFyod43YLyFpurKRZQKnE7E4pofAagDOm2Q==} engines: {node: ^10.13 || ^12 || >=14} @@ -10983,10 +10793,6 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} - prepend-http@2.0.0: - resolution: {integrity: sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==} - engines: {node: '>=4'} - prettier-linter-helpers@1.0.0: resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} engines: {node: '>=6.0.0'} @@ -11184,9 +10990,6 @@ packages: pump@3.0.2: resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} - pump@3.0.3: - resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} - punycode.js@2.3.1: resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} engines: {node: '>=6'} @@ -11227,9 +11030,6 @@ packages: resolution: {integrity: sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg==} engines: {node: '>=0.6'} - quad-indices@2.0.1: - resolution: {integrity: sha512-6jtmCsEbGAh5npThXrBaubbTjPcF0rMbn57XCJVI7LkW8PUT56V+uIrRCCWCn85PSgJC9v8Pm5tnJDwmOBewvA==} - query-string@4.3.4: resolution: {integrity: sha512-O2XLNDBIg1DnTOa+2XrIwSiXEV8h2KImXUnjhhn2+UsvZ+Es2uyd5CCRTNQlDGbzUQOW3aYCBx9rVA6dzsiY7Q==} engines: {node: '>=0.10.0'} @@ -11302,8 +11102,14 @@ packages: react: 19.1.0 react-dom: 19.1.0 - react-force-graph@1.45.4: - resolution: {integrity: sha512-Mppx8pU2TjJm+NToLDpcBR/8zk/aXbhFvP2r8Mw1pdQXnScpVVVb/7rfvZObSvnE9R2RoEjBpr4Ily4thO/Pfw==} + react-force-graph-2d@1.28.0: + resolution: {integrity: sha512-NYA8GLxJnoZyLWjob8xea38B1cZqSGdcA8lDpvTc1hcJrpzFyBEHkeJ4xtFoJp66tsM4PAlj5af4HWnU0OQ3Sg==} + engines: {node: '>=12'} + peerDependencies: + react: 19.1.0 + + react-force-graph-3d@1.28.0: + resolution: {integrity: sha512-kTG7R677mRR8RY0LadcB5fSLnie/KRE73VX+L2wO8kJ7AAzHFFCQjjJa0qc7M75Hqetp+CGinEnVIqBg08nsMg==} engines: {node: '>=12'} peerDependencies: react: 19.1.0 @@ -11688,9 +11494,6 @@ packages: resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} hasBin: true - responselike@1.0.2: - resolution: {integrity: sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==} - responselike@3.0.0: resolution: {integrity: sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==} engines: {node: '>=14.16'} @@ -12352,12 +12155,6 @@ packages: engines: {node: '>=16 || 14 >=14.17'} hasBin: true - super-animejs@3.1.0: - resolution: {integrity: sha512-6MFAFJDRuvwkovxQZPruuyHinTa4rgj4hNLOndjcYYhZLckoXtVRY9rJPuq8p6c/tgZJrFYEAYAfJ2/hhNtUCA==} - - super-three@0.164.0: - resolution: {integrity: sha512-yMtOkw2hSXfIvGlwcghCbhHGsKRAmh8ksDeOo/0HI7KlEVoIYKHiYLYe9GF6QBViNwzKGpMIz77XUDRveZ4XJg==} - superagent@8.1.2: resolution: {integrity: sha512-6WTxW1EB6yCxV5VFOIPQruWGHqc3yI7hEmZK6h+pyk69Lk/Ut7rLUY6W/ONF2MjBuGjvmMiIpsrVJ2vjrHlslA==} engines: {node: '>=6.4.0 <13 || >=14'} @@ -12499,36 +12296,18 @@ packages: thread-stream@3.1.0: resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} - three-bmfont-text@https://codeload.github.com/dmarcos/three-bmfont-text/tar.gz/eed4878795be9b3e38cf6aec6b903f56acd1f695: - resolution: {tarball: https://codeload.github.com/dmarcos/three-bmfont-text/tar.gz/eed4878795be9b3e38cf6aec6b903f56acd1f695} - version: 3.0.0 - three-forcegraph@1.42.11: resolution: {integrity: sha512-MXESG+qXXzsZDaY1N0M3fW1sfgJinm/DjNwO9oS9XXT1NRK9KoRijYDt5ilzI8M4OdJ3pA4YdaV3nDBw2HPVOw==} engines: {node: '>=12'} peerDependencies: three: '>=0.118.3' - three-pathfinding@1.3.0: - resolution: {integrity: sha512-LKxMI3/YqdMYvt6AdE2vB6s5ueDFczt/DWoxhtPNgRsH6E0D8LMYQxz+eIrmKo0MQpDvMVzXYUMBk+b86+k97w==} - peerDependencies: - three: 0.x.x - - three-render-objects@1.32.1: - resolution: {integrity: sha512-HcbVhMFwPxtxrrQYe+pD8HFZmx22lYuYZeHXcZlDdxqWyr5wAZgjD+vX23oALrmP3i1LW8udheXxbntwYmA9sw==} + three-render-objects@1.40.4: + resolution: {integrity: sha512-Ukpu1pei3L5r809izvjsZxwuRcYLiyn6Uvy3lZ9bpMTdvj3i6PeX6w++/hs2ZS3KnEzGjb6YvTvh4UQuwHTDJg==} engines: {node: '>=12'} peerDependencies: three: '>=0.168' - three-spritetext@1.9.3: - resolution: {integrity: sha512-p7iEQr7anABRUJNOH2logMf1aRuWb026IshUC4aJ8F+bbMTn5Z43/Jd7/W3VihE3ToCwrWMl6u6VwgFMTw0jvA==} - engines: {node: '>=12'} - peerDependencies: - three: '>=0.86.0' - - three@0.164.1: - resolution: {integrity: sha512-iC/hUBbl1vzFny7f5GtqzVXYjMJKaTPxiCxXfrvVdBi1Sf+jhd1CAkitiFwC7mIBFCo3MrDLJG97yisoaWig0w==} - three@0.171.0: resolution: {integrity: sha512-Y/lAXPaKZPcEdkKjh0JOAHVv8OOnv/NDJqm0wjfCzyQmfKxV7zvkwsnBgPBKTzJHToSOhRGQAGbPJObT59B/PQ==} @@ -12579,10 +12358,6 @@ packages: resolution: {integrity: sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==} engines: {node: '>=0.10.0'} - to-readable-stream@1.0.0: - resolution: {integrity: sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==} - engines: {node: '>=6'} - to-regex-range@2.1.1: resolution: {integrity: sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==} engines: {node: '>=0.10.0'} @@ -12993,10 +12768,6 @@ packages: file-loader: optional: true - url-parse-lax@3.0.0: - resolution: {integrity: sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ==} - engines: {node: '>=4'} - urlpattern-polyfill@10.0.0: resolution: {integrity: sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==} @@ -13353,12 +13124,6 @@ packages: resolution: {integrity: sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==} engines: {node: '>=0.8.0'} - webvr-polyfill-dpdb@1.0.18: - resolution: {integrity: sha512-O0S1ZGEWyPvyZEkS2VbyV7mtir/NM9MNK3EuhbHPoJ8EHTky2pTXehjIl+IiDPr+Lldgx129QGt3NGly7rwRPw==} - - webvr-polyfill@0.10.12: - resolution: {integrity: sha512-trDJEVUQnRIVAnmImjEQ0BlL1NfuWl8+eaEdu+bs4g59c7OtETi/5tFkgEFDRaWEYwHntXs/uFF3OXZuutNGGA==} - whatwg-encoding@3.1.1: resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} engines: {node: '>=18'} @@ -13414,9 +13179,6 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} - word-wrapper@1.0.7: - resolution: {integrity: sha512-VOPBFCm9b6FyYKQYfn9AVn2dQvdR/YOVFV6IBRA1TBMJWKffvhEX1af6FMGrttILs2Q9ikCRhLqkbY2weW6dOQ==} - wordwrap@1.0.0: resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} @@ -13466,24 +13228,10 @@ packages: resolution: {integrity: sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==} engines: {node: '>=12'} - xhr@2.6.0: - resolution: {integrity: sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==} - xml-js@1.6.11: resolution: {integrity: sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==} hasBin: true - xml-parse-from-string@1.0.1: - resolution: {integrity: sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==} - - xml2js@0.5.0: - resolution: {integrity: sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==} - engines: {node: '>=4.0.0'} - - xmlbuilder@11.0.1: - resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} - engines: {node: '>=4.0'} - xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} @@ -13545,32 +13293,13 @@ packages: snapshots: - 3d-force-graph-ar@1.9.3(three@0.171.0): - dependencies: - aframe-forcegraph-component: 3.1.0(three@0.171.0) - kapsule: 1.16.0 - transitivePeerDependencies: - - three - - 3d-force-graph-vr@2.4.3(three@0.171.0): - dependencies: - accessor-fn: 1.5.1 - aframe: 1.6.0 - aframe-extras: 7.5.2 - aframe-forcegraph-component: 3.1.0(three@0.171.0) - kapsule: 1.16.0 - polished: 4.3.1 - transitivePeerDependencies: - - supports-color - - three - - 3d-force-graph@1.74.5: + 3d-force-graph@1.78.4: dependencies: accessor-fn: 1.5.1 kapsule: 1.16.0 three: 0.171.0 three-forcegraph: 1.42.11(three@0.171.0) - three-render-objects: 1.32.1(three@0.171.0) + three-render-objects: 1.40.4(three@0.171.0) '@algolia/autocomplete-core@1.17.9(@algolia/client-search@5.18.0)(algoliasearch@5.18.0)(search-insights@2.17.3)': dependencies: @@ -16697,7 +16426,7 @@ snapshots: jest-util: 29.7.0 slash: 3.0.0 - '@jest/core@29.7.0(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@18.19.68)(typescript@5.7.2))': + '@jest/core@29.7.0(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@20.5.1)(typescript@5.8.3))': dependencies: '@jest/console': 29.7.0 '@jest/reporters': 29.7.0 @@ -16711,7 +16440,7 @@ snapshots: exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@18.19.68)(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@18.19.68)(typescript@5.7.2)) + jest-config: 29.7.0(@types/node@18.19.68)(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@20.5.1)(typescript@5.8.3)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -17964,8 +17693,6 @@ snapshots: '@sinclair/typebox@0.27.8': {} - '@sindresorhus/is@0.14.0': {} - '@sindresorhus/is@4.6.0': {} '@sindresorhus/is@5.6.0': {} @@ -18238,10 +17965,6 @@ snapshots: dependencies: '@swc/counter': 0.1.3 - '@szmarczak/http-timer@1.1.2': - dependencies: - defer-to-connect: 1.1.3 - '@szmarczak/http-timer@5.0.1': dependencies: defer-to-connect: 2.0.1 @@ -18266,8 +17989,6 @@ snapshots: '@tsconfig/node16@1.0.4': {} - '@tweenjs/tween.js@23.1.3': {} - '@tweenjs/tween.js@25.0.0': {} '@types/acorn@4.0.6': @@ -18528,10 +18249,6 @@ snapshots: dependencies: '@types/node': 18.19.68 - '@types/keyv@3.1.4': - dependencies: - '@types/node': 18.19.68 - '@types/mdast@3.0.15': dependencies: '@types/unist': 2.0.11 @@ -18623,10 +18340,6 @@ snapshots: dependencies: csstype: 3.1.3 - '@types/responselike@1.0.3': - dependencies: - '@types/node': 18.19.68 - '@types/retry@0.12.0': {} '@types/revalidator@0.3.12': {} @@ -18656,8 +18369,6 @@ snapshots: '@types/stack-utils@2.0.3': {} - '@types/stats.js@0.17.3': {} - '@types/stream-chain@2.1.0': dependencies: '@types/node': 18.19.68 @@ -18682,15 +18393,6 @@ snapshots: dependencies: '@types/node': 18.19.68 - '@types/three@0.171.0': - dependencies: - '@tweenjs/tween.js': 23.1.3 - '@types/stats.js': 0.17.3 - '@types/webxr': 0.5.20 - '@webgpu/types': 0.1.52 - fflate: 0.8.2 - meshoptimizer: 0.18.1 - '@types/trusted-types@2.0.7': optional: true @@ -18700,8 +18402,6 @@ snapshots: '@types/validator@13.12.2': {} - '@types/webxr@0.5.20': {} - '@types/ws@8.5.13': dependencies: '@types/node': 20.19.2 @@ -19116,8 +18816,6 @@ snapshots: '@webassemblyjs/ast': 1.14.1 '@xtuc/long': 4.2.2 - '@webgpu/types@0.1.52': {} - '@xtuc/ieee754@1.2.0': {} '@xtuc/long@4.2.2': {} @@ -19166,32 +18864,6 @@ snapshots: address@1.2.2: {} - aframe-extras@7.5.2: - dependencies: - nipplejs: 0.10.2 - three: 0.164.1 - three-pathfinding: 1.3.0(three@0.164.1) - - aframe-forcegraph-component@3.1.0(three@0.171.0): - dependencies: - accessor-fn: 1.5.1 - three-forcegraph: 1.42.11(three@0.171.0) - transitivePeerDependencies: - - three - - aframe@1.6.0: - dependencies: - buffer: 6.0.3 - debug: 4.4.0 - deep-assign: 2.0.0 - load-bmfont: 1.4.2(debug@4.4.0) - super-animejs: 3.1.0 - three: super-three@0.164.0 - three-bmfont-text: https://codeload.github.com/dmarcos/three-bmfont-text/tar.gz/eed4878795be9b3e38cf6aec6b903f56acd1f695 - webvr-polyfill: 0.10.12 - transitivePeerDependencies: - - supports-color - agent-base@7.1.3: optional: true @@ -19266,8 +18938,6 @@ snapshots: alien-signals@0.4.14: optional: true - an-array@1.0.0: {} - ansi-align@3.0.1: dependencies: string-width: 4.2.3 @@ -19349,8 +19019,6 @@ snapshots: get-intrinsic: 1.2.7 is-string: 1.1.1 - array-shuffle@1.0.1: {} - array-timsort@1.0.3: {} array-union@2.1.0: {} @@ -19409,8 +19077,6 @@ snapshots: arrify@1.0.1: {} - as-number@1.0.0: {} - asap@2.0.6: {} assert-never@1.3.0: {} @@ -19483,7 +19149,7 @@ snapshots: axios@1.7.9: dependencies: - follow-redirects: 1.15.9(debug@4.4.0) + follow-redirects: 1.15.9 form-data: 4.0.1 proxy-from-env: 1.1.0 transitivePeerDependencies: @@ -19753,8 +19419,6 @@ snapshots: buffer-equal-constant-time@1.0.1: {} - buffer-equal@0.0.1: {} - buffer-from@1.1.2: {} buffer@5.7.1: @@ -19799,16 +19463,6 @@ snapshots: normalize-url: 8.0.1 responselike: 3.0.0 - cacheable-request@6.1.0: - dependencies: - clone-response: 1.0.3 - get-stream: 5.2.0 - http-cache-semantics: 4.2.0 - keyv: 3.1.0 - lowercase-keys: 2.0.0 - normalize-url: 4.5.1 - responselike: 1.0.2 - call-bind-apply-helpers@1.0.1: dependencies: es-errors: 1.3.0 @@ -19867,20 +19521,8 @@ snapshots: dependencies: tinycolor2: 1.6.0 - cardboard-vr-display@1.0.19: - dependencies: - gl-preserve-state: 1.0.0 - nosleep.js: 0.7.0 - webvr-polyfill-dpdb: 1.0.18 - ccount@2.0.1: {} - centra@2.7.0(debug@4.4.0): - dependencies: - follow-redirects: 1.15.9(debug@4.4.0) - transitivePeerDependencies: - - debug - chalk@1.1.3: dependencies: ansi-styles: 2.2.1 @@ -20076,10 +19718,6 @@ snapshots: kind-of: 6.0.3 shallow-clone: 3.0.1 - clone-response@1.0.3: - dependencies: - mimic-response: 1.0.1 - clone@1.0.4: {} clone@2.1.2: {} @@ -20374,13 +20012,13 @@ snapshots: typescript: 5.7.2 optional: true - create-jest@29.7.0(@types/node@18.19.68)(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@18.19.68)(typescript@5.7.2)): + create-jest@29.7.0(@types/node@18.19.68)(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@20.5.1)(typescript@5.8.3)): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@18.19.68)(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@18.19.68)(typescript@5.7.2)) + jest-config: 29.7.0(@types/node@18.19.68)(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@20.5.1)(typescript@5.8.3)) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -20848,20 +20486,12 @@ snapshots: decode-uri-component@0.2.2: {} - decompress-response@3.3.0: - dependencies: - mimic-response: 1.0.1 - decompress-response@6.0.0: dependencies: mimic-response: 3.1.0 dedent@1.5.3: {} - deep-assign@2.0.0: - dependencies: - is-obj: 1.0.1 - deep-diff@1.0.2: {} deep-extend@0.6.0: {} @@ -20878,8 +20508,6 @@ snapshots: dependencies: clone: 1.0.4 - defer-to-connect@1.1.3: {} - defer-to-connect@2.0.1: {} define-data-property@1.1.4: @@ -21115,8 +20743,6 @@ snapshots: domhandler: 5.0.3 entities: 4.5.0 - dom-walk@0.1.2: {} - domelementtype@1.3.1: {} domelementtype@2.3.0: {} @@ -21181,16 +20807,12 @@ snapshots: dotenv@8.6.0: {} - dtype@2.0.0: {} - dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.1 es-errors: 1.3.0 gopd: 1.2.0 - duplexer3@0.1.5: {} - duplexer@0.1.2: {} eastasianwidth@0.2.0: {} @@ -21243,10 +20865,6 @@ snapshots: dependencies: once: 1.4.0 - end-of-stream@1.4.5: - dependencies: - once: 1.4.0 - enhanced-resolve@5.17.1: dependencies: graceful-fs: 4.2.11 @@ -22092,8 +21710,6 @@ snapshots: dependencies: xml-js: 1.6.11 - fflate@0.8.2: {} - figures@3.2.0: dependencies: escape-string-regexp: 1.0.5 @@ -22201,9 +21817,13 @@ snapshots: flatten@1.0.3: {} - follow-redirects@1.15.9(debug@4.4.0): - optionalDependencies: - debug: 4.4.0 + float-tooltip@1.7.5: + dependencies: + d3-selection: 3.0.0 + kapsule: 1.16.0 + preact: 10.27.1 + + follow-redirects@1.15.9: {} for-each@0.3.3: dependencies: @@ -22211,7 +21831,7 @@ snapshots: for-in@1.0.2: {} - force-graph@1.47.2: + force-graph@1.50.1: dependencies: '@tweenjs/tween.js': 25.0.0 accessor-fn: 1.5.1 @@ -22224,6 +21844,7 @@ snapshots: d3-scale-chromatic: 3.1.0 d3-selection: 3.0.0 d3-zoom: 3.0.0 + float-tooltip: 1.7.5 index-array-by: 1.4.2 kapsule: 1.16.0 lodash-es: 4.17.21 @@ -22377,10 +21998,6 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.0.0 - get-stream@4.1.0: - dependencies: - pump: 3.0.3 - get-stream@5.2.0: dependencies: pump: 3.0.2 @@ -22418,8 +22035,6 @@ snapshots: github-slugger@1.5.0: {} - gl-preserve-state@1.0.0: {} - glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -22497,11 +22112,6 @@ snapshots: dependencies: ini: 2.0.0 - global@4.4.0: - dependencies: - min-document: 2.19.0 - process: 0.11.10 - globals@11.12.0: {} globals@14.0.0: {} @@ -22550,22 +22160,6 @@ snapshots: p-cancelable: 3.0.0 responselike: 3.0.0 - got@9.6.0: - dependencies: - '@sindresorhus/is': 0.14.0 - '@szmarczak/http-timer': 1.1.2 - '@types/keyv': 3.1.4 - '@types/responselike': 1.0.3 - cacheable-request: 6.1.0 - decompress-response: 3.3.0 - duplexer3: 0.1.5 - get-stream: 4.1.0 - lowercase-keys: 1.0.1 - mimic-response: 1.0.1 - p-cancelable: 1.1.0 - to-readable-stream: 1.0.0 - url-parse-lax: 3.0.0 - graceful-fs@4.2.10: {} graceful-fs@4.2.11: {} @@ -23024,7 +22618,7 @@ snapshots: http-proxy@1.18.1: dependencies: eventemitter3: 4.0.7 - follow-redirects: 1.15.9(debug@4.4.0) + follow-redirects: 1.15.9 requires-port: 1.0.0 transitivePeerDependencies: - debug @@ -23299,8 +22893,6 @@ snapshots: is-fullwidth-code-point@3.0.0: {} - is-function@1.0.2: {} - is-generator-fn@2.1.0: {} is-generator-function@1.0.10: @@ -23545,16 +23137,16 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@29.7.0(@types/node@18.19.68)(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@18.19.68)(typescript@5.7.2)): + jest-cli@29.7.0(@types/node@18.19.68)(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@20.5.1)(typescript@5.8.3)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@18.19.68)(typescript@5.7.2)) + '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@20.5.1)(typescript@5.8.3)) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@18.19.68)(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@18.19.68)(typescript@5.7.2)) + create-jest: 29.7.0(@types/node@18.19.68)(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@20.5.1)(typescript@5.8.3)) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@18.19.68)(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@18.19.68)(typescript@5.7.2)) + jest-config: 29.7.0(@types/node@18.19.68)(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@20.5.1)(typescript@5.8.3)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -23564,7 +23156,7 @@ snapshots: - supports-color - ts-node - jest-config@29.7.0(@types/node@18.19.68)(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@18.19.68)(typescript@5.7.2)): + jest-config@29.7.0(@types/node@18.19.68)(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@20.5.1)(typescript@5.8.3)): dependencies: '@babel/core': 7.26.0 '@jest/test-sequencer': 29.7.0 @@ -23822,10 +23414,10 @@ snapshots: jest@29.5.0(@types/node@18.19.68)(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@20.5.1)(typescript@5.8.3)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@18.19.68)(typescript@5.7.2)) + '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@20.5.1)(typescript@5.8.3)) '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@18.19.68)(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@18.19.68)(typescript@5.7.2)) + jest-cli: 29.7.0(@types/node@18.19.68)(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@20.5.1)(typescript@5.8.3)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -23834,10 +23426,10 @@ snapshots: jest@29.7.0(@types/node@18.19.68)(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@18.19.68)(typescript@5.7.2)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@18.19.68)(typescript@5.7.2)) + '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@20.5.1)(typescript@5.8.3)) '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@18.19.68)(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@18.19.68)(typescript@5.7.2)) + jest-cli: 29.7.0(@types/node@18.19.68)(ts-node@10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@20.5.1)(typescript@5.8.3)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -23886,8 +23478,6 @@ snapshots: jsesc@3.1.0: {} - json-buffer@3.0.0: {} - json-buffer@3.0.1: {} json-parse-better-errors@1.0.2: {} @@ -23984,10 +23574,6 @@ snapshots: dependencies: commander: 8.3.0 - keyv@3.1.0: - dependencies: - json-buffer: 3.0.0 - keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -24048,12 +23634,6 @@ snapshots: layout-base@2.0.1: {} - layout-bmfont-text@1.3.4: - dependencies: - as-number: 1.0.0 - word-wrapper: 1.0.7 - xtend: 4.0.2 - leac@0.6.0: {} leven@3.1.0: {} @@ -24101,19 +23681,6 @@ snapshots: cheerio: 1.0.0 pick-util: 1.1.5 - load-bmfont@1.4.2(debug@4.4.0): - dependencies: - buffer-equal: 0.0.1 - mime: 1.6.0 - parse-bmfont-ascii: 1.0.6 - parse-bmfont-binary: 1.0.6 - parse-bmfont-xml: 1.1.6 - phin: 3.7.1(debug@4.4.0) - xhr: 2.6.0 - xtend: 4.0.2 - transitivePeerDependencies: - - debug - load-json-file@4.0.0: dependencies: graceful-fs: 4.2.11 @@ -24199,10 +23766,6 @@ snapshots: dependencies: tslib: 2.8.1 - lowercase-keys@1.0.1: {} - - lowercase-keys@2.0.0: {} - lowercase-keys@3.0.0: {} lowlight@1.20.0: @@ -24303,10 +23866,6 @@ snapshots: map-cache@0.2.2: {} - map-limit@0.0.1: - dependencies: - once: 1.3.3 - map-obj@1.0.1: {} map-obj@4.3.0: {} @@ -24729,8 +24288,6 @@ snapshots: transitivePeerDependencies: - supports-color - meshoptimizer@0.18.1: {} - methods@1.1.2: {} micromark-core-commonmark@1.1.0: @@ -25291,16 +24848,10 @@ snapshots: mimic-fn@2.1.0: {} - mimic-response@1.0.1: {} - mimic-response@3.1.0: {} mimic-response@4.0.0: {} - min-document@2.19.0: - dependencies: - dom-walk: 0.1.2 - min-indent@1.0.1: {} mini-css-extract-plugin@2.9.2(webpack@5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.21.5)): @@ -25786,8 +25337,6 @@ snapshots: netmask@2.0.2: optional: true - new-array@1.0.0: {} - next-mdx-remote@5.0.0(@types/react@19.1.0)(acorn@8.14.0)(react@19.1.0): dependencies: '@babel/code-frame': 7.26.2 @@ -25862,17 +25411,8 @@ snapshots: ngraph.random@1.1.0: {} - nice-color-palettes@3.0.0: - dependencies: - got: 9.6.0 - map-limit: 0.0.1 - minimist: 1.2.8 - new-array: 1.0.0 - nice-try@1.0.5: {} - nipplejs@0.10.2: {} - no-case@2.3.2: dependencies: lower-case: 1.1.4 @@ -25937,12 +25477,8 @@ snapshots: normalize-range@0.1.2: {} - normalize-url@4.5.1: {} - normalize-url@8.0.1: {} - nosleep.js@0.7.0: {} - npm-run-all@4.1.5: dependencies: ansi-styles: 3.2.1 @@ -26044,10 +25580,6 @@ snapshots: on-headers@1.0.2: {} - once@1.3.3: - dependencies: - wrappy: 1.0.2 - once@1.4.0: dependencies: wrappy: 1.0.2 @@ -26107,8 +25639,6 @@ snapshots: object-keys: 1.1.1 safe-push-apply: 1.0.0 - p-cancelable@1.1.0: {} - p-cancelable@3.0.0: {} p-filter@2.1.0: @@ -26209,15 +25739,6 @@ snapshots: dependencies: callsites: 3.1.0 - parse-bmfont-ascii@1.0.6: {} - - parse-bmfont-binary@1.0.6: {} - - parse-bmfont-xml@1.1.6: - dependencies: - xml-parse-from-string: 1.0.1 - xml2js: 0.5.0 - parse-entities@2.0.0: dependencies: character-entities: 1.2.4 @@ -26237,8 +25758,6 @@ snapshots: is-decimal: 2.0.1 is-hexadecimal: 2.0.1 - parse-headers@2.0.5: {} - parse-json@4.0.0: dependencies: error-ex: 1.3.2 @@ -26340,12 +25859,6 @@ snapshots: pend@1.2.0: optional: true - phin@3.7.1(debug@4.4.0): - dependencies: - centra: 2.7.0(debug@4.4.0) - transitivePeerDependencies: - - debug - pick-util@1.1.5: dependencies: '@jonkemp/package-utils': 1.0.8 @@ -27177,6 +26690,8 @@ snapshots: posthtml-parser: 0.2.1 posthtml-render: 1.4.0 + preact@10.27.1: {} + precinct@8.3.1: dependencies: commander: 2.20.3 @@ -27214,8 +26729,6 @@ snapshots: prelude-ls@1.2.1: {} - prepend-http@2.0.0: {} - prettier-linter-helpers@1.0.0: dependencies: fast-diff: 1.3.0 @@ -27401,11 +26914,6 @@ snapshots: end-of-stream: 1.4.4 once: 1.4.0 - pump@3.0.3: - dependencies: - end-of-stream: 1.4.5 - once: 1.4.0 - punycode.js@2.3.1: {} punycode@2.3.1: {} @@ -27452,12 +26960,6 @@ snapshots: dependencies: side-channel: 1.1.0 - quad-indices@2.0.1: - dependencies: - an-array: 1.0.0 - dtype: 2.0.0 - is-buffer: 1.1.6 - query-string@4.3.4: dependencies: object-assign: 4.1.1 @@ -27532,18 +27034,19 @@ snapshots: react-dom: 19.1.0(react@19.1.0) tree-changes: 0.9.3 - react-force-graph@1.45.4(react@19.1.0)(three@0.171.0): + react-force-graph-2d@1.28.0(react@19.1.0): dependencies: - 3d-force-graph: 1.74.5 - 3d-force-graph-ar: 1.9.3(three@0.171.0) - 3d-force-graph-vr: 2.4.3(three@0.171.0) - force-graph: 1.47.2 + force-graph: 1.50.1 + prop-types: 15.8.1 + react: 19.1.0 + react-kapsule: 2.5.6(react@19.1.0) + + react-force-graph-3d@1.28.0(react@19.1.0): + dependencies: + 3d-force-graph: 1.78.4 prop-types: 15.8.1 react: 19.1.0 react-kapsule: 2.5.6(react@19.1.0) - transitivePeerDependencies: - - supports-color - - three react-helmet-async@2.0.5(react@19.1.0): dependencies: @@ -28050,10 +27553,6 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - responselike@1.0.2: - dependencies: - lowercase-keys: 1.0.1 - responselike@3.0.0: dependencies: lowercase-keys: 3.0.0 @@ -28850,10 +28349,6 @@ snapshots: pirates: 4.0.6 ts-interface-checker: 0.1.13 - super-animejs@3.1.0: {} - - super-three@0.164.0: {} - superagent@8.1.2: dependencies: component-emitter: 1.3.1 @@ -29094,13 +28589,6 @@ snapshots: real-require: 0.2.0 optional: true - three-bmfont-text@https://codeload.github.com/dmarcos/three-bmfont-text/tar.gz/eed4878795be9b3e38cf6aec6b903f56acd1f695: - dependencies: - array-shuffle: 1.0.1 - layout-bmfont-text: 1.3.4 - nice-color-palettes: 3.0.0 - quad-indices: 2.0.1 - three-forcegraph@1.42.11(three@0.171.0): dependencies: accessor-fn: 1.5.1 @@ -29115,24 +28603,15 @@ snapshots: three: 0.171.0 tinycolor2: 1.6.0 - three-pathfinding@1.3.0(three@0.164.1): - dependencies: - three: 0.164.1 - - three-render-objects@1.32.1(three@0.171.0): + three-render-objects@1.40.4(three@0.171.0): dependencies: '@tweenjs/tween.js': 25.0.0 accessor-fn: 1.5.1 + float-tooltip: 1.7.5 kapsule: 1.16.0 polished: 4.3.1 three: 0.171.0 - three-spritetext@1.9.3(three@0.171.0): - dependencies: - three: 0.171.0 - - three@0.164.1: {} - three@0.171.0: {} through2@4.0.2: @@ -29169,8 +28648,6 @@ snapshots: dependencies: kind-of: 3.2.2 - to-readable-stream@1.0.0: {} - to-regex-range@2.1.1: dependencies: is-number: 3.0.0 @@ -29662,10 +29139,6 @@ snapshots: optionalDependencies: file-loader: 6.2.0(webpack@5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.21.5)) - url-parse-lax@3.0.0: - dependencies: - prepend-http: 2.0.0 - urlpattern-polyfill@10.0.0: optional: true @@ -30086,12 +29559,6 @@ snapshots: websocket-extensions@0.1.4: {} - webvr-polyfill-dpdb@1.0.18: {} - - webvr-polyfill@0.10.12: - dependencies: - cardboard-vr-display: 1.0.19 - whatwg-encoding@3.1.1: dependencies: iconv-lite: 0.6.3 @@ -30170,8 +29637,6 @@ snapshots: word-wrap@1.2.5: {} - word-wrapper@1.0.7: {} - wordwrap@1.0.0: optional: true @@ -30207,26 +29672,10 @@ snapshots: xdg-basedir@5.1.0: {} - xhr@2.6.0: - dependencies: - global: 4.4.0 - is-function: 1.0.2 - parse-headers: 2.0.5 - xtend: 4.0.2 - xml-js@1.6.11: dependencies: sax: 1.4.1 - xml-parse-from-string@1.0.1: {} - - xml2js@0.5.0: - dependencies: - sax: 1.4.1 - xmlbuilder: 11.0.1 - - xmlbuilder@11.0.1: {} - xtend@4.0.2: {} y18n@5.0.8: {} diff --git a/website/public/sitemap-0.xml b/website/public/sitemap-0.xml index d759d062..b220123b 100644 --- a/website/public/sitemap-0.xml +++ b/website/public/sitemap-0.xml @@ -1,16 +1,16 @@ -https://rushdb.com2025-08-20T15:30:43.281Zdaily0.7 -https://rushdb.com/blog2025-08-20T15:30:43.283Zdaily0.7 -https://rushdb.com/pricing2025-08-20T15:30:43.283Zdaily0.7 -https://rushdb.com/blog/knowledge-graphs-semantic-reasoning-meets-graph-architecture2025-08-20T15:30:43.283Zdaily0.7 -https://rushdb.com/blog/labeled-property-graphs-a-comprehensive-guide-to-enhanced-graph-data-modeling2025-08-20T15:30:43.283Zdaily0.7 -https://rushdb.com/blog/rdf-a-comprehensive-guide-to-semantic-web-data-modeling2025-08-20T15:30:43.283Zdaily0.7 -https://rushdb.com/blog/rethinking-the-graph-how-labeled-meta-property-graphs-unlock-structure-without-sacrificing-flexibility2025-08-20T15:30:43.283Zdaily0.7 -https://rushdb.com/blog/breaking-down-the-hidden-costs-of-data-silos-in-scientific-labs2025-08-20T15:30:43.283Zdaily0.7 -https://rushdb.com/blog/labeled-meta-property-graphs-rushdb-s-revolutionary-approach-to-graph-database-architecture2025-08-20T15:30:43.283Zdaily0.7 -https://rushdb.com/blog/graph-databases-explained-property-graphs-vs-rdf-vs-knowledge-graphs-complete-developer-guide-20252025-08-20T15:30:43.283Zdaily0.7 -https://rushdb.com/blog/backendless-fullstack-development-react-useform-rushdb2025-08-20T15:30:43.283Zdaily0.7 -https://rushdb.com/blog/rushdb-the-zero-config-database-for-modern-apps-and-ai-solutions2025-08-20T15:30:43.283Zdaily0.7 -https://rushdb.com/blog/rushdb-selfhosted-quick-setup2025-08-20T15:30:43.283Zdaily0.7 +https://rushdb.com2025-08-22T14:00:08.637Zdaily0.7 +https://rushdb.com/blog2025-08-22T14:00:08.638Zdaily0.7 +https://rushdb.com/pricing2025-08-22T14:00:08.638Zdaily0.7 +https://rushdb.com/blog/knowledge-graphs-semantic-reasoning-meets-graph-architecture2025-08-22T14:00:08.638Zdaily0.7 +https://rushdb.com/blog/labeled-property-graphs-a-comprehensive-guide-to-enhanced-graph-data-modeling2025-08-22T14:00:08.638Zdaily0.7 +https://rushdb.com/blog/rdf-a-comprehensive-guide-to-semantic-web-data-modeling2025-08-22T14:00:08.638Zdaily0.7 +https://rushdb.com/blog/rethinking-the-graph-how-labeled-meta-property-graphs-unlock-structure-without-sacrificing-flexibility2025-08-22T14:00:08.638Zdaily0.7 +https://rushdb.com/blog/breaking-down-the-hidden-costs-of-data-silos-in-scientific-labs2025-08-22T14:00:08.638Zdaily0.7 +https://rushdb.com/blog/labeled-meta-property-graphs-rushdb-s-revolutionary-approach-to-graph-database-architecture2025-08-22T14:00:08.638Zdaily0.7 +https://rushdb.com/blog/graph-databases-explained-property-graphs-vs-rdf-vs-knowledge-graphs-complete-developer-guide-20252025-08-22T14:00:08.638Zdaily0.7 +https://rushdb.com/blog/backendless-fullstack-development-react-useform-rushdb2025-08-22T14:00:08.638Zdaily0.7 +https://rushdb.com/blog/rushdb-the-zero-config-database-for-modern-apps-and-ai-solutions2025-08-22T14:00:08.638Zdaily0.7 +https://rushdb.com/blog/rushdb-selfhosted-quick-setup2025-08-22T14:00:08.638Zdaily0.7 \ No newline at end of file From 80216f9365108856f04a7fba012e64bfa1853eef Mon Sep 17 00:00:00 2001 From: Artemy Vereshinsky Date: Mon, 25 Aug 2025 01:58:38 +0700 Subject: [PATCH 2/2] Update platform/dashboard/src/features/projects/components/GraphView.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../dashboard/src/features/projects/components/GraphView.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platform/dashboard/src/features/projects/components/GraphView.tsx b/platform/dashboard/src/features/projects/components/GraphView.tsx index 73884c60..b0832241 100644 --- a/platform/dashboard/src/features/projects/components/GraphView.tsx +++ b/platform/dashboard/src/features/projects/components/GraphView.tsx @@ -225,8 +225,8 @@ export const GraphView = () => { width={dims.w} height={dims.h} nodeId="id" - linkTarget="id" - linkSource="id" + linkTarget="target" + linkSource="source" linkColor={linkColor} onNodeDragEnd={(node: NodeObject) => { node.fx = node.x