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)