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:
parent
863f0ad90c
commit
9f05bfd929
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user