From 9f05bfd929cbc4079552b973ccd4bee56d4ad8b9 Mon Sep 17 00:00:00 2001 From: Tom Butcher Date: Sun, 7 Dec 2025 02:37:28 +0000 Subject: [PATCH] Enhanced ObjectDisplay component with spotlight data fetching and hyperlink support. Added props for showHyperlink and showSpotlight, and improved rendering logic for object names with optional popover tooltips. --- .../Dashboard/common/ObjectDisplay.jsx | 164 +++++++++++++++--- 1 file changed, 144 insertions(+), 20 deletions(-) diff --git a/src/components/Dashboard/common/ObjectDisplay.jsx b/src/components/Dashboard/common/ObjectDisplay.jsx index f2df715..1668194 100644 --- a/src/components/Dashboard/common/ObjectDisplay.jsx +++ b/src/components/Dashboard/common/ObjectDisplay.jsx @@ -1,24 +1,60 @@ import PropTypes from 'prop-types' -import { Typography, Flex, Badge, Tag } from 'antd' -import { useState, useEffect, useContext, useCallback } from 'react' +import { Typography, Flex, Badge, Tag, Popover } from 'antd' +import { useState, useEffect, useContext, useCallback, useRef } from 'react' +import { useNavigate } from 'react-router-dom' import { getModelByName } from '../../../database/ObjectModels' import { ApiServerContext } from '../context/ApiServerContext' import { AuthContext } from '../context/AuthContext' import merge from 'lodash/merge' import IdDisplay from './IdDisplay' +import SpotlightTooltip from './SpotlightTooltip' -const { Text } = Typography +const { Text, Link } = Typography -const ObjectDisplay = ({ object, objectType }) => { +const ObjectDisplay = ({ + object, + objectType, + showHyperlink = false, + showSpotlight = true +}) => { const [objectData, setObjectData] = useState(object) - const { subscribeToObjectUpdates, connected } = useContext(ApiServerContext) + const { subscribeToObjectUpdates, connected, fetchSpotlightData } = + useContext(ApiServerContext) const { token } = useContext(AuthContext) + const navigate = useNavigate() + const idRef = useRef(null) // Update event handler const updateObjectEventHandler = useCallback((value) => { setObjectData((prev) => merge({}, prev, value)) }, []) + // Detect minimal objects that only contain an _id + const isMinimalObject = useCallback((obj) => { + if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return false + const keys = Object.keys(obj) + return keys.length === 1 && keys[0] === '_id' && obj._id + }, []) + + // If only an _id is provided, fetch the full object via spotlight + const fetchFullObjectIfNeeded = useCallback( + async (obj) => { + if (!isMinimalObject(obj) || !objectType) return obj + try { + const model = getModelByName(objectType) + const spotlightQuery = `${model.prefix}:${obj._id}` + const spotlightResult = await fetchSpotlightData(spotlightQuery) + if (spotlightResult && typeof spotlightResult === 'object') { + return spotlightResult + } + } catch (err) { + console.error('Failed to fetch spotlight data:', err) + } + return obj + }, + [fetchSpotlightData, isMinimalObject, objectType] + ) + // Subscribe to object updates when component mounts useEffect(() => { if (object?._id && objectType && connected && token) { @@ -43,46 +79,132 @@ const ObjectDisplay = ({ object, objectType }) => { // Update local state when object prop changes useEffect(() => { - setObjectData(object) - }, [object]) - + if (idRef.current == object?._id) return + idRef.current = object?._id + let cancelled = false + const hydrateObject = async () => { + const fullObject = await fetchFullObjectIfNeeded(object) + if (!cancelled) setObjectData(fullObject) + } + hydrateObject() + return () => { + cancelled = true + } + }, [object, fetchFullObjectIfNeeded]) + console.log(objectData, objectType) if (!objectData) { return n/a } const model = getModelByName(objectType) const Icon = model.icon + const prefix = model.prefix + + // Get hyperlink URL from model's default actions + var hyperlink = null + const defaultModelActions = + model.actions?.filter((action) => action.default == true) || [] + const objectId = objectData._id + + if (defaultModelActions.length >= 1 && objectId) { + hyperlink = defaultModelActions[0].url(objectId) + } + + // Render name with hyperlink/spotlight support + const renderNameDisplay = () => { + if (!objectData?.name) return null + + const textElement = ( + + {objectData.name} + + ) + + // If hyperlink is enabled + if (showHyperlink && hyperlink != null) { + const linkElement = ( + navigate(hyperlink)} ellipsis> + {textElement} + + ) + + if (showSpotlight && objectId) { + return ( + + } + trigger={['hover', 'click']} + placement='topLeft' + arrow={false} + style={{ padding: 0 }} + > + {linkElement} + + ) + } + return linkElement + } + + // If hyperlink is disabled + if (showSpotlight && objectId) { + return ( + + } + trigger={['hover', 'click']} + placement='topLeft' + arrow={false} + > + {textElement} + + ) + } + return textElement + } + return ( - + - {objectData?.color ? : null} -
- {objectData?.name ? ( - - {objectData.name} - - ) : null} + {objectData?.color ? ( + + ) : null} +
+ {renderNameDisplay()} + {objectData?._id && !objectData?.name ? ( ) : null}
@@ -95,7 +217,9 @@ const ObjectDisplay = ({ object, objectType }) => { ObjectDisplay.propTypes = { object: PropTypes.object, objectType: PropTypes.string, - style: PropTypes.object + style: PropTypes.object, + showHyperlink: PropTypes.bool, + showSpotlight: PropTypes.bool } export default ObjectDisplay