/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /** * Property set display component with edit support. */ import { useState, useEffect } from 'react'; import { Sparkles, PenLine, Building2 } from 'lucide-react'; import { PropertyEditor, type PropertyEditScope } from '../PropertyEditor'; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { Badge } from '@/components/ui/badge'; import { decodeIfcString, parsePropertyValue } from './encodingUtils'; import type { PropertySet } from './encodingUtils'; import { PropertyValueType } from '@ifc-lite/data'; export interface PropertySetCardProps { pset: PropertySet; modelId?: string; entityId?: number; enableEditing?: boolean; /** Whether this property set is inherited from the type entity */ isTypeProperty?: boolean; typeEditScope?: PropertyEditScope; /** `"PsetName:PropName"` of a row to transiently highlight + scroll to * (the bSDD "jump to added property" flow, issue #1107). */ focusedPropKey?: string | null; } export function PropertySetCard({ pset, modelId, entityId, enableEditing, isTypeProperty, typeEditScope, focusedPropKey }: PropertySetCardProps) { // Check if any property in this set is mutated const hasMutations = pset.properties.some(p => p.isMutated); const isNewPset = pset.isNewPset; // Row identity for the bSDD focus flow (issue #1107). The entityId is part of // the key so an occurrence pset and an inherited type pset of the SAME name // don't collide — only the card the property was actually added to matches. const keyFor = (propName: string) => `${entityId ?? ''}:${pset.name}:${propName}`; const containsFocused = focusedPropKey != null && pset.properties.some(p => keyFor(p.name) === focusedPropKey); // Self-control the collapse so a focused row can't hide inside a pset the user // previously collapsed — force it open when this card holds the focus target. const [open, setOpen] = useState(true); useEffect(() => { if (containsFocused) setOpen(true); }, [containsFocused]); // Dynamic styling based on mutation state and source const borderClass = isNewPset ? 'border-2 border-amber-400/50 dark:border-amber-500/30' : hasMutations ? 'border-2 border-purple-300/50 dark:border-purple-500/30' : isTypeProperty ? 'border-2 border-indigo-200/60 dark:border-indigo-800/40' : 'border-2 border-zinc-200 dark:border-zinc-800'; const bgClass = isNewPset ? 'bg-amber-50/30 dark:bg-amber-950/20' : hasMutations ? 'bg-purple-50/20 dark:bg-purple-950/10' : isTypeProperty ? 'bg-indigo-50/20 dark:bg-indigo-950/10' : 'bg-white dark:bg-zinc-950'; return ( {isNewPset && ( New property set (not in original model) )} {hasMutations && !isNewPset && ( Has modified properties )} {isTypeProperty && !isNewPset && !hasMutations && ( Inherited from type — edits apply to all instances of this type )} {decodeIfcString(pset.name)} {pset.properties.length}
{pset.properties.map((prop: { name: string; value: unknown; isMutated?: boolean; type?: number }) => { const parsed = parsePropertyValue(prop.value); const decodedName = decodeIfcString(prop.name); const isMutated = prop.isMutated; const propKey = keyFor(prop.name); const isFocused = focusedPropKey != null && focusedPropKey === propKey; return (
{/* Property name with type tooltip and mutation indicator */}
{isMutated && ( edited This property has been modified )} {parsed.ifcType ? ( {decodedName} {/* bg-primary tooltip: derive from primary-foreground so it reads on the blue/purple surface and in dark mode (#1218) */} {parsed.ifcType} ) : ( {decodedName} )}
{/* Property value - use PropertyEditor if editing enabled */} {enableEditing && modelId && entityId ? ( ) : ( {parsed.displayValue} )}
); })}
); }