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.

This commit is contained in:
Tom Butcher 2025-12-07 02:37:28 +00:00
parent 863f0ad90c
commit 9f05bfd929

View File

@ -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 <Text type='secondary'>n/a</Text>
}
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 = (
<Text ellipsis style={{ lineHeight: '1', paddingBottom: '2px' }}>
{objectData.name}
</Text>
)
// If hyperlink is enabled
if (showHyperlink && hyperlink != null) {
const linkElement = (
<Link onClick={() => navigate(hyperlink)} ellipsis>
{textElement}
</Link>
)
if (showSpotlight && objectId) {
return (
<Popover
content={
<SpotlightTooltip
query={prefix + ':' + objectId}
type={objectType}
/>
}
trigger={['hover', 'click']}
placement='topLeft'
arrow={false}
style={{ padding: 0 }}
>
{linkElement}
</Popover>
)
}
return linkElement
}
// If hyperlink is disabled
if (showSpotlight && objectId) {
return (
<Popover
content={
<SpotlightTooltip
query={prefix + ':' + objectId}
type={objectType}
/>
}
trigger={['hover', 'click']}
placement='topLeft'
arrow={false}
>
{textElement}
</Popover>
)
}
return textElement
}
return (
<Tag
style={{
margin: 0,
border: 'none',
minWidth: 0,
paddingBottom: '2.5px',
maxWidth: '100%'
}}
className='object-display-tag'
>
<Flex
gap={objectData?.color ? 'small' : '4px'}
gap={objectData?.color ? 'small' : '5px'}
align='center'
style={{ minWidth: 0 }}
style={{ minWidth: 0, height: '24px' }}
>
<Icon style={{ marginBottom: '-3px' }} />
<Icon />
<Flex gap={'small'} align='center' style={{ minWidth: 0 }}>
{objectData?.color ? <Badge color={objectData?.color} /> : null}
<div style={{ paddingTop: '1.5px', minWidth: 0 }}>
{objectData?.name ? (
<Text ellipsis style={{ lineHeight: '1' }}>
{objectData.name}
</Text>
) : null}
{objectData?.color ? (
<Badge
color={objectData?.color}
style={{ marginBottom: '1.5px' }}
/>
) : null}
<div style={{ minWidth: 0 }}>
{renderNameDisplay()}
{objectData?._id && !objectData?.name ? (
<IdDisplay
id={objectData?._id}
reference={objectData?._reference || undefined}
type={objectType}
longId={false}
showCopy={false}
showHyperlink={showHyperlink}
showSpotlight={showSpotlight}
/>
) : null}
</div>
@ -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