diff --git a/packages/frontend-2/components/viewer/selection/KeyValuePair.vue b/packages/frontend-2/components/viewer/selection/KeyValuePair.vue index b9297e9af..1a38d2028 100644 --- a/packages/frontend-2/components/viewer/selection/KeyValuePair.vue +++ b/packages/frontend-2/components/viewer/selection/KeyValuePair.vue @@ -7,7 +7,7 @@ >
{{ kvp.key }}
@@ -66,14 +66,10 @@ import { LayoutMenu, type LayoutMenuItem } from '@speckle/ui-components' import { Ellipsis } from 'lucide-vue-next' import { useFilterUtilities } from '~~/lib/viewer/composables/ui' import { useInjectedViewer } from '~~/lib/viewer/composables/setup' +import type { KeyValuePair } from '~/components/viewer/selection/types' const props = defineProps<{ - kvp: Record & { - key: string - value: unknown - units?: string - backendPath?: string - } + kvp: KeyValuePair }>() const showActionsMenu = ref(false) @@ -86,38 +82,28 @@ const { const isUrlString = (v: unknown) => typeof v === 'string' && VALID_HTTP_URL.test(v) -const isCopyable = (kvp: Record) => { +const isCopyable = (kvp: KeyValuePair) => { return kvp.value !== null && kvp.value !== undefined && typeof kvp.value !== 'object' } -const isFilterable = (kvp: Record) => { - return isKvpFilterable( - kvp as Record & { key: string; backendPath?: string }, - availableFilters.value - ) +const isFilterable = (kvp: KeyValuePair) => { + return isKvpFilterable(kvp, availableFilters.value) } -const getDisabledReason = (kvp: Record) => { - return getFilterDisabledReason( - kvp as Record & { key: string; backendPath?: string }, - availableFilters.value - ) +const getDisabledReason = (kvp: KeyValuePair) => { + return getFilterDisabledReason(kvp, availableFilters.value) } -const handleFilterByProperty = (kvp: Record) => { - applyKvpFilter( - kvp as Record & { key: string; backendPath?: string }, - availableFilters.value - ) +const handleFilterByProperty = (kvp: KeyValuePair) => { + applyKvpFilter(kvp, availableFilters.value) } -const handleCopy = async (kvp: Record) => { +const handleCopy = async (kvp: KeyValuePair) => { const { copy } = useClipboard() if (isCopyable(kvp)) { - const keyName = kvp.key as string await copy(kvp.value as string, { - successMessage: `${keyName} copied to clipboard`, - failureMessage: `Failed to copy ${keyName} to clipboard` + successMessage: `${kvp.key} copied to clipboard`, + failureMessage: `Failed to copy ${kvp.key} to clipboard` }) } } diff --git a/packages/frontend-2/components/viewer/selection/Object.vue b/packages/frontend-2/components/viewer/selection/Object.vue index 62078487c..70e4a6258 100644 --- a/packages/frontend-2/components/viewer/selection/Object.vue +++ b/packages/frontend-2/components/viewer/selection/Object.vue @@ -97,6 +97,7 @@ import type { SpeckleObject } from '~~/lib/viewer/helpers/sceneExplorer' import { getHeaderAndSubheaderForSpeckleObject } from '~~/lib/object-sidebar/helpers' import { useInjectedViewerState } from '~~/lib/viewer/composables/setup' import { useHighlightedObjectsUtilities } from '~/lib/viewer/composables/ui' +import type { KeyValuePair } from '~/components/viewer/selection/types' const { ui: { @@ -194,20 +195,19 @@ const ignoredProps = [ ] const keyValuePairs = computed(() => { - const kvps = [] as (Record & { - key: string - value: unknown - backendPath?: string - })[] + const kvps: KeyValuePair[] = [] // handle revit paramters if (props.title === 'parameters') { const paramKeys = Object.keys(props.object) for (const prop of paramKeys) { - const param = props.object[prop] as Record - if (!param) continue + const param = props.object[prop] + if (!param || typeof param !== 'object' || param === null) continue + if (!('name' in param) || typeof param.name !== 'string') continue + if (!('value' in param)) continue + kvps.push({ - key: param.name as string, + key: param.name, type: typeof param.value, innerType: null, arrayLength: null, diff --git a/packages/frontend-2/components/viewer/selection/types.ts b/packages/frontend-2/components/viewer/selection/types.ts new file mode 100644 index 000000000..e6b0d11d7 --- /dev/null +++ b/packages/frontend-2/components/viewer/selection/types.ts @@ -0,0 +1,10 @@ +export type KeyValuePair = { + key: string + value: unknown + units?: string + type?: string + innerType?: string | null + arrayLength?: number | null + arrayPreview?: string | null + backendPath?: string +} diff --git a/packages/frontend-2/lib/viewer/composables/ui.ts b/packages/frontend-2/lib/viewer/composables/ui.ts index 50f9d3b39..4a85f803f 100644 --- a/packages/frontend-2/lib/viewer/composables/ui.ts +++ b/packages/frontend-2/lib/viewer/composables/ui.ts @@ -388,11 +388,11 @@ export function useFilterUtilities( * Determines if a key-value pair is filterable (with smart matching for nested properties) */ const isKvpFilterable = ( - kvp: Record & { key: string; backendPath?: string }, + kvp: { key: string; backendPath?: string }, availableFilters: PropertyInfo[] | null | undefined ): boolean => { // Use backendPath if available, otherwise fall back to display key - const backendKey = (kvp.backendPath as string | undefined) || (kvp.key as string) + const backendKey = kvp.backendPath || kvp.key // First check direct match const directMatch = availableFilters?.some((f) => f.key === backendKey) @@ -415,10 +415,10 @@ export function useFilterUtilities( * Gets a detailed reason why a property is disabled for filtering */ const getFilterDisabledReason = ( - kvp: Record & { key: string; backendPath?: string }, + kvp: { key: string; backendPath?: string }, availableFilters: PropertyInfo[] | null | undefined ): string => { - const backendKey = (kvp.backendPath as string | undefined) || (kvp.key as string) + const backendKey = kvp.backendPath || kvp.key const availableKeys = availableFilters?.map((f) => f.key) || [] // Check if it's not in available filters @@ -451,11 +451,11 @@ export function useFilterUtilities( * Applies a filter for a key-value pair (with smart matching) */ const applyKvpFilter = ( - kvp: Record & { key: string; backendPath?: string }, + kvp: { key: string; backendPath?: string }, availableFilters: PropertyInfo[] | null | undefined ): void => { // Use backendPath if available, otherwise fall back to display key - const backendKey = (kvp.backendPath as string | undefined) || (kvp.key as string) + const backendKey = kvp.backendPath || kvp.key // First try direct match let filter = availableFilters?.find((f: PropertyInfo) => f.key === backendKey)