Compare commits
No commits in common. "be2109505f449019b180cd006392857c61da01fd" and "3f353c1f70dafaaa7bcec9ca147bf0dd6950ebc6" have entirely different histories.
be2109505f
...
3f353c1f70
@ -1,6 +1,6 @@
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { useContext, useEffect, useState, useRef } from 'react'
|
import { useContext, useEffect, useState, useRef } from 'react'
|
||||||
import { Input, Result, Typography, Flex, Progress } from 'antd'
|
import { Input, Result, Typography, Flex, Progress, Button } from 'antd'
|
||||||
import { LoadingOutlined } from '@ant-design/icons'
|
import { LoadingOutlined } from '@ant-design/icons'
|
||||||
import { ApiServerContext } from '../../context/ApiServerContext'
|
import { ApiServerContext } from '../../context/ApiServerContext'
|
||||||
import CopyButton from '../../common/CopyButton'
|
import CopyButton from '../../common/CopyButton'
|
||||||
@ -9,7 +9,7 @@ import OTPIcon from '../../../Icons/OTPIcon'
|
|||||||
const { Text } = Typography
|
const { Text } = Typography
|
||||||
|
|
||||||
const HostOTP = ({ id }) => {
|
const HostOTP = ({ id }) => {
|
||||||
const { fetchHostOTP } = useContext(ApiServerContext)
|
const { fetchHostOTP, sendObjectAction } = useContext(ApiServerContext)
|
||||||
const [hostObject, setHostObject] = useState(null)
|
const [hostObject, setHostObject] = useState(null)
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [timeRemaining, setTimeRemaining] = useState(0)
|
const [timeRemaining, setTimeRemaining] = useState(0)
|
||||||
@ -35,6 +35,13 @@ const HostOTP = ({ id }) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sendTestAction = () => {
|
||||||
|
console.log('Sending test action...')
|
||||||
|
sendObjectAction(id, 'host', { method: 'testMethod' }, (result) => {
|
||||||
|
console.log('Got callback', result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (hostObject === null && initialized == false) {
|
if (hostObject === null && initialized == false) {
|
||||||
setInitialized(true)
|
setInitialized(true)
|
||||||
@ -78,6 +85,7 @@ const HostOTP = ({ id }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex vertical align='center'>
|
<Flex vertical align='center'>
|
||||||
|
<Button onClick={sendTestAction}>Test Action</Button>
|
||||||
<Result
|
<Result
|
||||||
title={'Connect a Host.'}
|
title={'Connect a Host.'}
|
||||||
subTitle={<Text>Enter the following one time passcode.</Text>}
|
subTitle={<Text>Enter the following one time passcode.</Text>}
|
||||||
|
|||||||
@ -1,61 +1,19 @@
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { Typography, Flex } from 'antd'
|
import { Tag, Typography } from 'antd'
|
||||||
import { useState, useEffect, useContext, useCallback } from 'react'
|
|
||||||
import { getModelByName } from '../../../database/ObjectModels'
|
import { getModelByName } from '../../../database/ObjectModels'
|
||||||
import { ApiServerContext } from '../context/ApiServerContext'
|
|
||||||
import { AuthContext } from '../context/AuthContext'
|
|
||||||
import merge from 'lodash/merge'
|
|
||||||
|
|
||||||
const { Text } = Typography
|
const { Text } = Typography
|
||||||
|
|
||||||
const ObjectDisplay = ({ object, objectType }) => {
|
const ObjectDisplay = ({ object, objectType }) => {
|
||||||
const [objectData, setObjectData] = useState(object)
|
if (!object) {
|
||||||
const { subscribeToObjectUpdates, connected } = useContext(ApiServerContext)
|
|
||||||
const { token } = useContext(AuthContext)
|
|
||||||
|
|
||||||
// Update event handler
|
|
||||||
const updateObjectEventHandler = useCallback((value) => {
|
|
||||||
setObjectData((prev) => merge({}, prev, value))
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
// Subscribe to object updates when component mounts
|
|
||||||
useEffect(() => {
|
|
||||||
if (object?._id && objectType && connected && token) {
|
|
||||||
const objectUpdatesUnsubscribe = subscribeToObjectUpdates(
|
|
||||||
object._id,
|
|
||||||
objectType,
|
|
||||||
updateObjectEventHandler
|
|
||||||
)
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
if (objectUpdatesUnsubscribe) objectUpdatesUnsubscribe()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
object?._id,
|
|
||||||
objectType,
|
|
||||||
subscribeToObjectUpdates,
|
|
||||||
connected,
|
|
||||||
token,
|
|
||||||
updateObjectEventHandler
|
|
||||||
])
|
|
||||||
|
|
||||||
// Update local state when object prop changes
|
|
||||||
useEffect(() => {
|
|
||||||
setObjectData(object)
|
|
||||||
}, [object])
|
|
||||||
|
|
||||||
if (!objectData) {
|
|
||||||
return <Text type='secondary'>n/a</Text>
|
return <Text type='secondary'>n/a</Text>
|
||||||
}
|
}
|
||||||
|
|
||||||
const model = getModelByName(objectType)
|
const model = getModelByName(objectType)
|
||||||
const Icon = model.icon
|
const Icon = model.icon
|
||||||
return (
|
return (
|
||||||
<Flex gap={'small'}>
|
<Tag color='default' style={{ margin: 0 }} icon={<Icon />}>
|
||||||
<Icon />
|
{object?.name ? object.name : null}
|
||||||
<Text>{objectData?.name ? objectData.name : null}</Text>
|
</Tag>
|
||||||
</Flex>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -87,10 +87,8 @@ const ObjectTable = forwardRef(
|
|||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [lazyLoading, setLazyLoading] = useState(false)
|
const [lazyLoading, setLazyLoading] = useState(false)
|
||||||
|
|
||||||
const subscribedIdsRef = useRef([])
|
const [subscribedIds, setSubscribedIds] = useState([])
|
||||||
const [typeSubscribed, setTypeSubscribed] = useState(false)
|
const [typeSubscribed, setTypeSubscribed] = useState(false)
|
||||||
const unsubscribesRef = useRef([])
|
|
||||||
const updateEventHandlerRef = useRef()
|
|
||||||
|
|
||||||
const rowActions =
|
const rowActions =
|
||||||
model.actions?.filter((action) => action.row == true) || []
|
model.actions?.filter((action) => action.row == true) || []
|
||||||
@ -289,7 +287,6 @@ const ObjectTable = forwardRef(
|
|||||||
|
|
||||||
// Update event handler for real-time updates
|
// Update event handler for real-time updates
|
||||||
const updateEventHandler = useCallback((id, updatedData) => {
|
const updateEventHandler = useCallback((id, updatedData) => {
|
||||||
console.log('GOT UPDATE FOR', id)
|
|
||||||
setPages((prevPages) =>
|
setPages((prevPages) =>
|
||||||
prevPages.map((page) => ({
|
prevPages.map((page) => ({
|
||||||
...page,
|
...page,
|
||||||
@ -300,9 +297,6 @@ const ObjectTable = forwardRef(
|
|||||||
)
|
)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// Store the latest updateEventHandler in a ref
|
|
||||||
updateEventHandlerRef.current = updateEventHandler
|
|
||||||
|
|
||||||
const newEventHandler = useCallback(() => {
|
const newEventHandler = useCallback(() => {
|
||||||
console.log('GOT NEW EVENT')
|
console.log('GOT NEW EVENT')
|
||||||
reload()
|
reload()
|
||||||
@ -310,68 +304,44 @@ const ObjectTable = forwardRef(
|
|||||||
|
|
||||||
// Subscribe to real-time updates for all items
|
// Subscribe to real-time updates for all items
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (pages.length > 0 && connected == true) {
|
if (pages.length > 0 && connected) {
|
||||||
// Get all non-skeleton item IDs from all pages
|
const unsubscribes = []
|
||||||
const allItemIds = pages
|
// Subscribe to each item in all pages
|
||||||
.flatMap((page) => page.items || [])
|
pages.forEach((page) => {
|
||||||
.filter((item) => !item.isSkeleton)
|
if (page?.items && page?.items?.length > 0) {
|
||||||
.map((item) => item._id)
|
page.items.forEach((item) => {
|
||||||
.filter(Boolean)
|
if (!item.isSkeleton && !subscribedIds.includes(item?._id)) {
|
||||||
|
const unsubscribe = subscribeToObjectUpdates(
|
||||||
// Find new items that need subscription
|
item._id,
|
||||||
const newItemIds = allItemIds.filter(
|
type,
|
||||||
(id) => !subscribedIdsRef.current.includes(id)
|
(updateData) => {
|
||||||
)
|
updateEventHandler(item._id, updateData)
|
||||||
|
}
|
||||||
// Subscribe to new items only
|
)
|
||||||
newItemIds.forEach((itemId) => {
|
setSubscribedIds((prev) => [...prev, item._id])
|
||||||
console.log('SUB', itemId)
|
if (unsubscribe) {
|
||||||
const unsubscribe = subscribeToObjectUpdates(
|
unsubscribes.push(unsubscribe)
|
||||||
itemId,
|
}
|
||||||
type,
|
}
|
||||||
(updateData) => {
|
})
|
||||||
updateEventHandlerRef.current(itemId, updateData)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
subscribedIdsRef.current.push(itemId)
|
|
||||||
if (unsubscribe) {
|
|
||||||
unsubscribesRef.current.push(unsubscribe)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Clean up subscriptions for items that are no longer in the pages
|
return () => {
|
||||||
const currentSubscribedIds = subscribedIdsRef.current
|
// Clean up all subscriptions
|
||||||
const itemsToUnsubscribe = currentSubscribedIds.filter(
|
unsubscribes.forEach((unsubscribe) => {
|
||||||
(id) => !allItemIds.includes(id)
|
if (unsubscribe) unsubscribe()
|
||||||
)
|
})
|
||||||
|
}
|
||||||
itemsToUnsubscribe.forEach((itemId) => {
|
|
||||||
const index = subscribedIdsRef.current.indexOf(itemId)
|
|
||||||
if (index > -1) {
|
|
||||||
subscribedIdsRef.current.splice(index, 1)
|
|
||||||
const unsubscribe = unsubscribesRef.current[index]
|
|
||||||
if (unsubscribe) {
|
|
||||||
console.log('UNSUB', itemId)
|
|
||||||
unsubscribe()
|
|
||||||
}
|
|
||||||
unsubscribesRef.current.splice(index, 1)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}, [pages, type, subscribeToObjectUpdates, connected])
|
}, [
|
||||||
|
pages,
|
||||||
// Cleanup effect for component unmount
|
type,
|
||||||
useEffect(() => {
|
subscribeToObjectUpdates,
|
||||||
return () => {
|
updateEventHandler,
|
||||||
// Clean up all subscriptions when component unmounts
|
connected,
|
||||||
unsubscribesRef.current.forEach((unsubscribe) => {
|
subscribedIds
|
||||||
console.log('CALLING UNSUB on unmount')
|
])
|
||||||
if (unsubscribe) unsubscribe()
|
|
||||||
})
|
|
||||||
unsubscribesRef.current = []
|
|
||||||
subscribedIdsRef.current = []
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (connected == true && typeSubscribed == false) {
|
if (connected == true && typeSubscribed == false) {
|
||||||
|
|||||||
@ -7,8 +7,6 @@ import { AuthContext } from '../context/AuthContext'
|
|||||||
import ObjectProperty from './ObjectProperty'
|
import ObjectProperty from './ObjectProperty'
|
||||||
import InfoCircleIcon from '../../Icons/InfoCircleIcon'
|
import InfoCircleIcon from '../../Icons/InfoCircleIcon'
|
||||||
import { ApiServerContext } from '../context/ApiServerContext'
|
import { ApiServerContext } from '../context/ApiServerContext'
|
||||||
import merge from 'lodash/merge'
|
|
||||||
import { getModelByName } from '../../../database/ObjectModels'
|
|
||||||
|
|
||||||
const { Text } = Typography
|
const { Text } = Typography
|
||||||
|
|
||||||
@ -18,17 +16,7 @@ const SpotlightTooltip = ({ query, type }) => {
|
|||||||
const [initialized, setInitialized] = useState(false)
|
const [initialized, setInitialized] = useState(false)
|
||||||
|
|
||||||
const { token } = useContext(AuthContext)
|
const { token } = useContext(AuthContext)
|
||||||
const { fetchSpotlightData, subscribeToObjectUpdates, connected } =
|
const { fetchSpotlightData } = useContext(ApiServerContext)
|
||||||
useContext(ApiServerContext)
|
|
||||||
|
|
||||||
// Get the model for this type
|
|
||||||
const model = getModelByName(type)
|
|
||||||
const modelProperties = model.properties || []
|
|
||||||
|
|
||||||
// Update event handler
|
|
||||||
const updateObjectEventHandler = useCallback((value) => {
|
|
||||||
setSpotlightData((prev) => merge({}, prev, value))
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const fetchSpotlight = useCallback(async () => {
|
const fetchSpotlight = useCallback(async () => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
@ -45,28 +33,6 @@ const SpotlightTooltip = ({ query, type }) => {
|
|||||||
}
|
}
|
||||||
}, [token, fetchSpotlight, initialized])
|
}, [token, fetchSpotlight, initialized])
|
||||||
|
|
||||||
// Subscribe to object updates when component mounts
|
|
||||||
useEffect(() => {
|
|
||||||
if (spotlightData?._id && type && connected && token) {
|
|
||||||
const objectUpdatesUnsubscribe = subscribeToObjectUpdates(
|
|
||||||
spotlightData._id,
|
|
||||||
type,
|
|
||||||
updateObjectEventHandler
|
|
||||||
)
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
if (objectUpdatesUnsubscribe) objectUpdatesUnsubscribe()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
spotlightData?._id,
|
|
||||||
type,
|
|
||||||
subscribeToObjectUpdates,
|
|
||||||
connected,
|
|
||||||
token,
|
|
||||||
updateObjectEventHandler
|
|
||||||
])
|
|
||||||
|
|
||||||
if (!spotlightData && !loading) {
|
if (!spotlightData && !loading) {
|
||||||
return (
|
return (
|
||||||
<Card className='spotlight-tooltip'>
|
<Card className='spotlight-tooltip'>
|
||||||
@ -85,9 +51,29 @@ const SpotlightTooltip = ({ query, type }) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper to get nested property value
|
// Helper to determine property type based on key and value
|
||||||
const getNestedValue = (obj, path) => {
|
const getPropertyType = (key, value) => {
|
||||||
return path.split('.').reduce((current, key) => current?.[key], obj)
|
if (key === '_id') {
|
||||||
|
return 'id'
|
||||||
|
}
|
||||||
|
if (key === 'createdAt' || key === 'updatedAt') {
|
||||||
|
return 'dateTime'
|
||||||
|
}
|
||||||
|
if (typeof value === 'boolean') {
|
||||||
|
return 'bool'
|
||||||
|
}
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map of property names to user-friendly labels
|
||||||
|
const LABEL_MAP = {
|
||||||
|
name: 'Name',
|
||||||
|
state: 'State',
|
||||||
|
tags: 'Tags',
|
||||||
|
email: 'Email',
|
||||||
|
updatedAt: 'Updated At',
|
||||||
|
_id: 'ID'
|
||||||
|
// Add more mappings as needed
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -119,31 +105,33 @@ const SpotlightTooltip = ({ query, type }) => {
|
|||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
modelProperties
|
Object.entries(spotlightData).map(([key, value]) =>
|
||||||
.filter((prop) => {
|
value !== undefined &&
|
||||||
const value = getNestedValue(spotlightData, prop.name)
|
value !== null &&
|
||||||
return value !== undefined && value !== null && value !== ''
|
value !== '' &&
|
||||||
})
|
key !== 'objectType' ? (
|
||||||
.map((prop) => {
|
<Descriptions.Item
|
||||||
const value = getNestedValue(spotlightData, prop.name)
|
key={key}
|
||||||
return (
|
label={
|
||||||
<Descriptions.Item key={prop.name} label={prop.label}>
|
LABEL_MAP[key] || key.charAt(0).toUpperCase() + key.slice(1)
|
||||||
<ObjectProperty
|
}
|
||||||
type={prop.type}
|
>
|
||||||
value={value}
|
<ObjectProperty
|
||||||
objectData={spotlightData}
|
type={getPropertyType(key)}
|
||||||
objectType={prop.objectType || type}
|
value={value}
|
||||||
isEditing={false}
|
objectData={spotlightData}
|
||||||
longId={false}
|
objectType={type}
|
||||||
showSpotlight={false}
|
isEditing={false}
|
||||||
showLabel={false}
|
longId={false}
|
||||||
showName={false}
|
showSpotlight={false}
|
||||||
showId={false}
|
showLabel={false}
|
||||||
showQuantity={false}
|
showName={false}
|
||||||
/>
|
showId={false}
|
||||||
</Descriptions.Item>
|
showQuantity={false}
|
||||||
)
|
/>
|
||||||
})
|
</Descriptions.Item>
|
||||||
|
) : null
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
</Descriptions>
|
</Descriptions>
|
||||||
</Spin>
|
</Spin>
|
||||||
|
|||||||
@ -285,34 +285,21 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
if (!subscribedCallbacksRef.current.has(callbacksRefKey)) {
|
if (!subscribedCallbacksRef.current.has(callbacksRefKey)) {
|
||||||
subscribedCallbacksRef.current.set(callbacksRefKey, [])
|
subscribedCallbacksRef.current.set(callbacksRefKey, [])
|
||||||
}
|
}
|
||||||
|
|
||||||
const callbacksLength =
|
|
||||||
subscribedCallbacksRef.current.get(callbacksRefKey).length
|
|
||||||
|
|
||||||
if (callbacksLength <= 0) {
|
|
||||||
socketRef.current.emit(
|
|
||||||
'subscribeToObjectUpdate',
|
|
||||||
{
|
|
||||||
_id: id,
|
|
||||||
objectType: objectType
|
|
||||||
},
|
|
||||||
(result) => {
|
|
||||||
if (result.success) {
|
|
||||||
logger.info('Subscribed to id:', id, 'objectType:', objectType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
logger.info(
|
|
||||||
'Adding callback id:',
|
|
||||||
id,
|
|
||||||
'objectType:',
|
|
||||||
objectType,
|
|
||||||
'callbacks length:',
|
|
||||||
callbacksLength + 1
|
|
||||||
)
|
|
||||||
subscribedCallbacksRef.current.get(callbacksRefKey).push(callback)
|
subscribedCallbacksRef.current.get(callbacksRefKey).push(callback)
|
||||||
|
|
||||||
|
socketRef.current.emit(
|
||||||
|
'subscribeToObjectUpdate',
|
||||||
|
{
|
||||||
|
_id: id,
|
||||||
|
objectType: objectType
|
||||||
|
},
|
||||||
|
(result) => {
|
||||||
|
if (result.success) {
|
||||||
|
logger.info('Subscribed to id:', id, 'objectType:', objectType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// Return cleanup function
|
// Return cleanup function
|
||||||
return () => offObjectUpdatesEvent(id, objectType, callback)
|
return () => offObjectUpdatesEvent(id, objectType, callback)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -209,12 +209,7 @@ const AuthProvider = ({ children }) => {
|
|||||||
}, [token])
|
}, [token])
|
||||||
|
|
||||||
const setUnauthenticated = () => {
|
const setUnauthenticated = () => {
|
||||||
setToken(null)
|
setAuthenticated(false)
|
||||||
setExpiresAt(null)
|
|
||||||
setUserProfile(null)
|
|
||||||
sessionStorage.removeItem('authToken')
|
|
||||||
sessionStorage.removeItem('authExpiresAt')
|
|
||||||
sessionStorage.removeItem('user')
|
|
||||||
setShowUnauthorizedModal(true)
|
setShowUnauthorizedModal(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -126,16 +126,14 @@ export const Filament = {
|
|||||||
label: 'Diameter',
|
label: 'Diameter',
|
||||||
columnWidth: 150,
|
columnWidth: 150,
|
||||||
required: true,
|
required: true,
|
||||||
type: 'number',
|
type: 'mm'
|
||||||
suffix: 'mm'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'density',
|
name: 'density',
|
||||||
label: 'Density',
|
label: 'Density',
|
||||||
columnWidth: 150,
|
columnWidth: 150,
|
||||||
required: true,
|
required: true,
|
||||||
type: 'number',
|
type: 'density'
|
||||||
suffix: 'g/cm³'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'emptySpoolWeight',
|
name: 'emptySpoolWeight',
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user