feat(ui): show existing category mappings in revit mapper dropdown (#48)
* feat: poc - needs cleaning - just me, hacking * refactor: cleaning * chore: update available categories * feat: add Mixpanel tracking to revit mapper interactions (#50) * feat: add Mixpanel tracking to revit mapper interactions * fix: pr comments * fix: just mode * chore(interop-lite): rename event name prop --------- Co-authored-by: oguzhankoral <oguzhankoral@gmail.com> * revit mapper store * WIP * Fix form select base placeholder on select * refactor: convention, not composable * fix: deselecting objects through mapped mode * fix: eslinting ? * chore: remove console log --------- Co-authored-by: oguzhankoral <oguzhankoral@gmail.com>
This commit is contained in:
@@ -14,6 +14,7 @@ export interface IRevitMapperBinding extends IBinding<IMapperBindingEvents> {
|
||||
clearObjectsCategoryAssignment: (objectIds: string[]) => Promise<void>
|
||||
clearAllObjectsCategoryAssignments: () => Promise<void>
|
||||
getCurrentObjectsMappings: () => Promise<CategoryMapping[]>
|
||||
getCategoryMappingsForObjects: (objectIds: string[]) => Promise<string[]>
|
||||
|
||||
// Layer methods
|
||||
assignLayerToCategory: (layerIds: string[], categoryValue: string) => Promise<void>
|
||||
@@ -24,6 +25,7 @@ export interface IRevitMapperBinding extends IBinding<IMapperBindingEvents> {
|
||||
layerIds: string[],
|
||||
categoryValue: string
|
||||
) => Promise<string[]>
|
||||
getCategoryMappingsForLayers: (layerIds: string[]) => Promise<string[]>
|
||||
}
|
||||
|
||||
export interface IMapperBindingEvents extends IBindingSharedEvents {
|
||||
@@ -125,6 +127,21 @@ export class MockedMapperBinding implements IRevitMapperBinding {
|
||||
return Promise.resolve(['obj1', 'obj2', 'obj3'])
|
||||
}
|
||||
|
||||
public getCategoryMappingsForObjects(objectIds: string[]): Promise<string[]> {
|
||||
console.log('Mock: Getting category mappings for objects', { objectIds })
|
||||
// Mock returning some categories for testing
|
||||
return Promise.resolve(
|
||||
objectIds.length > 1 ? ['OST_Walls', 'OST_Doors'] : ['OST_Walls']
|
||||
)
|
||||
}
|
||||
|
||||
public getCategoryMappingsForLayers(layerIds: string[]): Promise<string[]> {
|
||||
console.log('Mock: Getting category mappings for layers', { layerIds })
|
||||
return Promise.resolve(
|
||||
layerIds.length > 1 ? ['OST_Floors', 'OST_Ceilings'] : ['OST_Floors']
|
||||
)
|
||||
}
|
||||
|
||||
public showDevTools(): Promise<void> {
|
||||
console.log('Braaaaa, no way!')
|
||||
return Promise.resolve()
|
||||
|
||||
@@ -5,97 +5,136 @@ import type { CategoryOption } from './types'
|
||||
*/
|
||||
export const REVIT_CATEGORIES: readonly CategoryOption[] = [
|
||||
// INFRASTRUCTURE
|
||||
{ value: 'OST_BridgeAbutments', label: 'Bridge Abutments' },
|
||||
{ value: 'OST_BridgeFraming', label: 'Bridge Framing' },
|
||||
{ value: 'OST_BridgeBearings', label: 'Bridge Bearings' },
|
||||
{ value: 'OST_BridgeAbutments', label: 'Abutments' },
|
||||
{ value: 'OST_AbutmentFoundations', label: 'Abutment Foundations' },
|
||||
{ value: 'OST_AbutmentPiles', label: 'Abutment Piles' },
|
||||
{ value: 'OST_AbutmentWalls', label: 'Abutment Walls' },
|
||||
{ value: 'OST_ApproachSlabs', label: 'Approach Slabs' },
|
||||
{ value: 'OST_BridgeBearings', label: 'Bearings' },
|
||||
{ value: 'OST_BridgeCables', label: 'Bridge Cables' },
|
||||
{ value: 'OST_BridgeDecks', label: 'Bridge Decks' },
|
||||
{ value: 'OST_BridgeFraming', label: 'Bridge Framing' },
|
||||
{ value: 'OST_BridgeArches', label: 'Bridge Arches' },
|
||||
{ value: 'OST_BridgeFramingCrossBracing', label: 'Bridge Framing - Cross Bracing' },
|
||||
{ value: 'OST_BridgeFramingDiaphragms', label: 'Bridge Framing - Diaphragms' },
|
||||
{ value: 'OST_BridgeGirders', label: 'Bridge Framing - Girders' },
|
||||
{ value: 'OST_BridgeFramingTrusses', label: 'Bridge Framing - Trusses' },
|
||||
{ value: 'OST_ExpansionJoints', label: 'Expansion Joints' },
|
||||
{ value: 'OST_BridgePiers', label: 'Bridge Piers' },
|
||||
{ value: 'OST_BridgePiers', label: 'Piers' },
|
||||
{ value: 'OST_PierCaps', label: 'Pier Caps' },
|
||||
{ value: 'OST_PierColumns', label: 'Pier Columns' },
|
||||
{ value: 'OST_BridgeFoundations', label: 'Pier Foundations' },
|
||||
{ value: 'OST_PierPiles', label: 'Pier Piles' },
|
||||
{ value: 'OST_BridgeTowers', label: 'Pier Towers' },
|
||||
{ value: 'OST_PierWalls', label: 'Pier Walls' },
|
||||
{ value: 'OST_StructuralTendons', label: 'Structural Tendons' },
|
||||
{ value: 'OST_VibrationManagement', label: 'Vibration Management' },
|
||||
{ value: 'OST_VibrationDampers', label: 'Vibration Dampers' },
|
||||
{ value: 'OST_VibrationIsolators', label: 'Vibration Isolators' },
|
||||
|
||||
// ARCHITECTURE
|
||||
{ value: 'OST_AudioVisualDevices', label: 'Audio Visual Devices' },
|
||||
{ value: 'OST_Casework', label: 'Casework' },
|
||||
{ value: 'OST_Ceilings', label: 'Ceilings' },
|
||||
{ value: 'OST_Columns', label: 'Columns' },
|
||||
{ value: 'OST_CurtainWallPanels', label: 'Curtain Panels' },
|
||||
// { value: 'OST_CurtaSystem', label: 'Curtain Systems' }, excluded as part of CNX-2299
|
||||
{ value: 'OST_CurtainWallMullions', label: 'Curtain Wall Mullions' },
|
||||
{ value: 'OST_Doors', label: 'Doors' },
|
||||
{ value: 'OST_Entourage', label: 'Entourage' },
|
||||
{ value: 'OST_FireProtection', label: 'Fire Protection' },
|
||||
{ value: 'OST_Floors', label: 'Floors' },
|
||||
{ value: 'OST_FoodServiceEquipment', label: 'Food Service Equipment' },
|
||||
{ value: 'OST_Furniture', label: 'Furniture' },
|
||||
{ value: 'OST_FurnitureSystems', label: 'Furniture Systems' },
|
||||
{ value: 'OST_GenericModel', label: 'Generic Models' },
|
||||
{ value: 'OST_Hardscape', label: 'Hardscape' },
|
||||
{ value: 'OST_Lines', label: 'Lines' },
|
||||
{ value: 'OST_Mass', label: 'Mass' },
|
||||
{ value: 'OST_MechanicalControlDevices', label: 'Mechanical Control Devices' },
|
||||
{ value: 'OST_MechanicalEquipment', label: 'Mechanical Equipment' },
|
||||
{ value: 'OST_MedicalEquipment', label: 'Medical Equipment' },
|
||||
{ value: 'OST_Parking', label: 'Parking' },
|
||||
{ value: 'OST_Parts', label: 'Parts' },
|
||||
{ value: 'OST_Planting', label: 'Planting' },
|
||||
{ value: 'OST_PlumbingEquipment', label: 'Plumbing Equipment' },
|
||||
{ value: 'OST_PlumbingFixtures', label: 'Plumbing Fixtures' },
|
||||
{ value: 'OST_StairsRailing', label: 'Railings' },
|
||||
{ value: 'OST_StairsRailingBaluster', label: 'Railings - Balusters' },
|
||||
{ value: 'OST_RailingSupport', label: 'Railings - Supports' },
|
||||
{ value: 'OST_RailingTermination', label: 'Railings - Terminations' },
|
||||
{ value: 'OST_Ramps', label: 'Ramps' },
|
||||
{ value: 'OST_Roads', label: 'Roads' },
|
||||
{ value: 'OST_Roofs', label: 'Roofs' },
|
||||
{ value: 'OST_Signage', label: 'Signage' },
|
||||
{ value: 'OST_Site', label: 'Site' },
|
||||
{ value: 'OST_SpecialtyEquipment', label: 'Specialty Equipment' },
|
||||
{ value: 'OST_Stairs', label: 'Stairs' },
|
||||
{ value: 'OST_TemporaryStructure', label: 'Temporary Structures' },
|
||||
{ value: 'OST_Topography', label: 'Topography' },
|
||||
{ value: 'OST_Toposolid', label: 'Toposolid' },
|
||||
{ value: 'OST_VerticalCirculation', label: 'Vertical Circulation' },
|
||||
{ value: 'OST_Walls', label: 'Walls' },
|
||||
{ value: 'OST_Windows', label: 'Windows' },
|
||||
|
||||
// ELECTRICAL
|
||||
{ value: 'OST_AudioVisualDevices', label: 'Audio Visual Devices' },
|
||||
{ value: 'OST_CableTray', label: 'Cable Tray' },
|
||||
{ value: 'OST_CableTrayFitting', label: 'Cable Tray Fittings' },
|
||||
{ value: 'OST_CableTray', label: 'Cable Trays' },
|
||||
{ value: 'OST_CommunicationDevices', label: 'Communication Devices' },
|
||||
{ value: 'OST_ConduitFittings', label: 'Conduit Fittings' },
|
||||
{ value: 'OST_Conduit', label: 'Conduits' },
|
||||
{ value: 'OST_ConduitFitting', label: 'Conduit Fittings' },
|
||||
{ value: 'OST_DataDevices', label: 'Data Devices' },
|
||||
{ value: 'OST_ElectricalEquipment', label: 'Electrical Equipment' },
|
||||
{ value: 'OST_ElectricalFixtures', label: 'Electrical Fixtures' },
|
||||
{ value: 'OST_FireAlarmDevices', label: 'Fire Alarm Devices' },
|
||||
{ value: 'OST_LightingDevices', label: 'Lighting Devices' },
|
||||
{ value: 'OST_LighintgDevices', label: 'Lighting Devices' },
|
||||
{ value: 'OST_LightingFixtures', label: 'Lighting Fixtures' },
|
||||
{ value: 'OST_NurseCallDevices', label: 'Nurse Call Devices' },
|
||||
{ value: 'OST_SecurityDevices', label: 'Security Devices' },
|
||||
{ value: 'OST_TelephoneDevices', label: 'Telephone Devices' },
|
||||
|
||||
// ARCHITECTURAL
|
||||
{ value: 'OST_Casework', label: 'Casework' },
|
||||
{ value: 'OST_Ceilings', label: 'Ceilings' },
|
||||
{ value: 'OST_Columns', label: 'Columns' },
|
||||
{ value: 'OST_CurtainWallMullions', label: 'Curtain Wall Mullions' },
|
||||
{ value: 'OST_CurtainWallPanels', label: 'Curtain Panels' },
|
||||
// OST_Curtain_Systems excluded as part of CNX-2299
|
||||
{ value: 'OST_Doors', label: 'Doors' },
|
||||
{ value: 'OST_Entourage', label: 'Entourage' },
|
||||
{ value: 'OST_Floors', label: 'Floors' },
|
||||
{ value: 'OST_FoodServiceEquipment', label: 'Food Service Equipment' },
|
||||
{ value: 'OST_Furniture', label: 'Furniture' },
|
||||
{ value: 'OST_FurnitureSystems', label: 'Furniture Systems' },
|
||||
{ value: 'OST_Hardscape', label: 'Hardscape' },
|
||||
{ value: 'OST_Parking', label: 'Parking' },
|
||||
{ value: 'OST_Planting', label: 'Planting' },
|
||||
{ value: 'OST_Railings', label: 'Railings' },
|
||||
{ value: 'OST_Ramps', label: 'Ramps' },
|
||||
{ value: 'OST_Roads', label: 'Roads' },
|
||||
{ value: 'OST_Roofs', label: 'Roofs' },
|
||||
{ value: 'OST_Site', label: 'Site' },
|
||||
{ value: 'OST_SpecialityEquipment', label: 'Speciality Equipment' },
|
||||
{ value: 'OST_Stairs', label: 'Stairs' },
|
||||
{ value: 'OST_Topography', label: 'Topography' },
|
||||
{ value: 'OST_Toposolid', label: 'Toposolid' },
|
||||
{ value: 'OST_Walls', label: 'Walls' },
|
||||
{ value: 'OST_Windows', label: 'Windows' },
|
||||
|
||||
// STRUCTURAL
|
||||
// OST_StructuralColumns excluded as part of CNX-2299
|
||||
{ value: 'OST_StructuralConnections', label: 'Structural Connections' },
|
||||
// STRUCTURE
|
||||
{ value: 'OST_Coupler', label: 'Structural Rebar Couplers' },
|
||||
{ value: 'OST_FabricAreas', label: 'Structural Fabric Areas' },
|
||||
{ value: 'OST_StructConnections', label: 'Structural Connections' },
|
||||
{ value: 'OST_StructConnectionAnchors', label: 'Structural Connections - Anchors' },
|
||||
{ value: 'OST_StructConnectionBolts', label: 'Structural Connections - Bolts' },
|
||||
{ value: 'OST_StructConnectionPlates', label: 'Structural Connections - Plates' },
|
||||
{ value: 'OST_StructConnectionProfiles', label: 'Structural Connections - Profiles' },
|
||||
{
|
||||
value: 'OST_StructConnectionShearStuds',
|
||||
label: 'Structural Connections - Shear Studs'
|
||||
},
|
||||
{ value: 'OST_StructConnectionWelds', label: 'Structural Connections - Welds' },
|
||||
// { value: 'OST_StructuralColumns', label: 'Structural Columns' }, excluded as part of CNX-2299
|
||||
{ value: 'OST_StructuralFoundation', label: 'Structural Foundations' },
|
||||
{ value: 'OST_StructuralFraming', label: 'Structural Framing' },
|
||||
{ value: 'OST_StructuralFramingSystem', label: 'Structural Beam Systems' },
|
||||
{ value: 'OST_StructuralFabricAreas', label: 'Structural Fabric Areas' },
|
||||
{ value: 'OST_Rebar', label: 'Rebar' },
|
||||
{ value: 'OST_StructuralStiffener', label: 'Structural Stiffeners' },
|
||||
{ value: 'OST_StructuralTendons', label: 'Structural Tendons' },
|
||||
{ value: 'OST_StructuralTruss', label: 'Structural Trusses' },
|
||||
{ value: 'OST_Rebar', label: 'Structural Rebar' },
|
||||
|
||||
// MECHANICAL
|
||||
{ value: 'OST_DuctTerminal', label: 'Air Terminals' },
|
||||
{ value: 'OST_DuctAccessory', label: 'Duct Accessories' },
|
||||
{ value: 'OST_DuctCurves', label: 'Ducts' },
|
||||
{ value: 'OST_DuctFitting', label: 'Duct Fittings' },
|
||||
{ value: 'OST_DuctSystem', label: 'Duct Systems' },
|
||||
{ value: 'OST_MechanicalEquipment', label: 'Mechanical Equipment' },
|
||||
{ value: 'OST_PlumbingEquipment', label: 'Plumbing Equipment' },
|
||||
{ value: 'OST_PlumbingFixtures', label: 'Plumbing Fixtures' },
|
||||
{ value: 'OST_PlaceHolderDucts', label: 'Duct Placeholders' },
|
||||
{ value: 'OST_DuctCurves', label: 'Ducts' },
|
||||
{ value: 'OST_MEPAncillaryFraming', label: 'MEP Ancillary Framing' },
|
||||
|
||||
// PIPING
|
||||
{ value: 'OST_PipeAccessory', label: 'Pipe Accessories' },
|
||||
{ value: 'OST_PipeCurves', label: 'Pipes' },
|
||||
{ value: 'OST_PipeFitting', label: 'Pipe Fittings' },
|
||||
{ value: 'OST_PlaceHolderPipes', label: 'Pipe Placeholders' },
|
||||
{ value: 'OST_PipeSegments', label: 'Pipe Segments' },
|
||||
{ value: 'OST_PipeCurves', label: 'Pipes' },
|
||||
{ value: 'OST_Sprinklers', label: 'Sprinklers' },
|
||||
|
||||
// GENERAL/MULTI-DISCIPLINE
|
||||
{ value: 'OST_FireProtection', label: 'Fire Protection' },
|
||||
{ value: 'OST_GenericModel', label: 'Generic Models' },
|
||||
{ value: 'OST_Lines', label: 'Lines' },
|
||||
{ value: 'OST_Mass', label: 'Mass' },
|
||||
{ value: 'OST_MedicalEquipment', label: 'Medical Equipment' },
|
||||
{ value: 'OST_Parts', label: 'Parts' },
|
||||
{ value: 'OST_Signage', label: 'Signage' },
|
||||
{ value: 'OST_TemporaryStructure', label: 'Temporary Structures' },
|
||||
{ value: 'OST_VerticalCirculation', label: 'Vertical Circulation' }
|
||||
{ value: 'OST_CableTrayRun', label: 'Cable Tray Runs' },
|
||||
{ value: 'OST_Coordination_Model', label: 'Coordination Model' },
|
||||
{ value: 'OST_DuctSystem', label: 'Duct Systems' },
|
||||
{ value: 'OST_PipingSystem', label: 'Piping Systems' },
|
||||
{ value: 'OST_StructuralFramingSystem', label: 'Structural Beam Systems' },
|
||||
{ value: 'OST_StructuralStiffener', label: 'Structural Stiffeners' }
|
||||
] as const
|
||||
|
||||
/**
|
||||
|
||||
+156
-33
@@ -55,9 +55,9 @@
|
||||
<div class="flex-1">
|
||||
<FormSelectBase
|
||||
key="label"
|
||||
v-model="selectedCategory"
|
||||
v-model="revitMapperStore.selectedCategory"
|
||||
name="categoryMapping"
|
||||
placeholder="Select a category"
|
||||
:placeholder="dropdownPlaceholder"
|
||||
label="Target Category"
|
||||
fixed-height
|
||||
size="sm"
|
||||
@@ -68,9 +68,9 @@
|
||||
:allow-unset="false"
|
||||
mount-menu-on-body
|
||||
>
|
||||
<template #something-selected="{ value }">
|
||||
<template #something-selected>
|
||||
<span class="text-primary text-xs">
|
||||
{{ Array.isArray(value) ? value[0]?.label : value?.label }}
|
||||
{{ displayLabel }}
|
||||
</span>
|
||||
</template>
|
||||
<template #option="{ item }">
|
||||
@@ -83,7 +83,7 @@
|
||||
<FormButton
|
||||
color="primary"
|
||||
size="sm"
|
||||
:disabled="!selectedCategory"
|
||||
:disabled="!revitMapperStore.selectedCategory?.value"
|
||||
@click="assignToCategory()"
|
||||
>
|
||||
Apply
|
||||
@@ -193,6 +193,7 @@
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { ArrowLeftIcon } from '@heroicons/vue/20/solid'
|
||||
import { useSelectionStore } from '~/store/selection'
|
||||
import { useRevitMapper } from '~/store/revitMapper'
|
||||
import type {
|
||||
Category,
|
||||
CategoryMapping,
|
||||
@@ -201,15 +202,17 @@ import type {
|
||||
|
||||
// Import categories
|
||||
import { getAvailableCategories, getCategoryLabel } from '~/lib/mapper/revit-categories'
|
||||
import { useMixpanel } from '~/lib/core/composables/mixpanel'
|
||||
|
||||
// === STORES ===
|
||||
const selectionStore = useSelectionStore()
|
||||
const revitMapperStore = useRevitMapper()
|
||||
const { selectionInfo } = storeToRefs(selectionStore)
|
||||
const { trackEvent } = useMixpanel()
|
||||
|
||||
// === STATE ===
|
||||
const selectedMappingMode = ref<string>('Selection')
|
||||
const mappingModeOptions = ['Selection', 'Layer']
|
||||
const selectedCategory = ref<Category | undefined>(undefined)
|
||||
const categoryOptions = ref<Category[]>([])
|
||||
const mappings = ref<CategoryMapping[]>([])
|
||||
|
||||
@@ -229,6 +232,11 @@ interface LayerOption {
|
||||
name: string
|
||||
}
|
||||
|
||||
// === MAPPING CATEGORY STATE MGMT ===
|
||||
const app = useNuxtApp()
|
||||
const { $revitMapperBinding, $baseBinding } = app
|
||||
// const categoryState = useRevitCategoryState(categoryOptions, $revitMapperBinding)
|
||||
|
||||
// === COMPUTED ===
|
||||
const hasTargetsSelected = computed(() => {
|
||||
if (selectedMappingMode.value === 'Selection') {
|
||||
@@ -248,9 +256,23 @@ const currentLayerMappings = computed(() => {
|
||||
return selectedMappingMode.value === 'Layer' ? layerMappings.value : []
|
||||
})
|
||||
|
||||
const dropdownPlaceholder = computed(() => {
|
||||
if (revitMapperStore.categoryStatus) {
|
||||
return revitMapperStore.categoryStatus?.isMultiple
|
||||
? 'Multiple categories'
|
||||
: 'Select a category'
|
||||
}
|
||||
return undefined
|
||||
})
|
||||
|
||||
const displayLabel = computed(() => {
|
||||
const multiple = revitMapperStore.categoryStatus?.isMultiple
|
||||
return multiple
|
||||
? 'Multiple categories'
|
||||
: revitMapperStore.selectedCategory?.label || ''
|
||||
})
|
||||
|
||||
// === METHODS ===
|
||||
const app = useNuxtApp()
|
||||
const { $revitMapperBinding, $baseBinding } = app
|
||||
|
||||
// Search predicate for category dropdown
|
||||
const searchFilterPredicate = (item: Category, query: string) => {
|
||||
@@ -297,6 +319,12 @@ const confirmModeChange = async () => {
|
||||
await $revitMapperBinding?.clearAllLayerCategoryAssignments()
|
||||
}
|
||||
|
||||
// Track the manual mode switch
|
||||
trackEvent('DUI3 Action', {
|
||||
name: 'Mapper Mode Changed',
|
||||
mode: selectedMappingMode.value
|
||||
})
|
||||
|
||||
// Switch mode
|
||||
selectedMappingMode.value = pendingMode.value
|
||||
await refreshMappings()
|
||||
@@ -312,19 +340,38 @@ const confirmModeChange = async () => {
|
||||
|
||||
// Assign selected objects/layers to the chosen category
|
||||
const assignToCategory = async () => {
|
||||
if (!selectedCategory.value || !hasTargetsSelected.value) return
|
||||
if (!revitMapperStore.selectedCategory?.value || !hasTargetsSelected.value) return
|
||||
|
||||
try {
|
||||
if (selectedMappingMode.value === 'Selection') {
|
||||
await $revitMapperBinding?.assignObjectsToCategory(
|
||||
selectionInfo.value?.selectedObjectIds || [],
|
||||
selectedCategory.value.value
|
||||
)
|
||||
} else if (selectedMappingMode.value === 'Layer') {
|
||||
await $revitMapperBinding?.assignLayerToCategory(
|
||||
selectedLayers.value.map((layer) => layer.id),
|
||||
selectedCategory.value.value
|
||||
)
|
||||
let assignedCount = 0
|
||||
const { selectedCategory } = storeToRefs(revitMapperStore)
|
||||
const categoryValue = selectedCategory?.value?.value
|
||||
|
||||
if (selectedMappingMode.value === 'Selection' && categoryValue) {
|
||||
const objectIds = selectionInfo.value?.selectedObjectIds || []
|
||||
await $revitMapperBinding?.assignObjectsToCategory(objectIds, categoryValue)
|
||||
assignedCount = objectIds.length
|
||||
|
||||
// Track the assignment
|
||||
trackEvent('DUI3 Action', {
|
||||
name: 'Mapper Assign Category',
|
||||
category: categoryValue,
|
||||
count: assignedCount,
|
||||
mappingType: 'object'
|
||||
})
|
||||
} else if (selectedMappingMode.value === 'Layer' && categoryValue) {
|
||||
const layerIds = selectedLayers.value.map((layer) => layer.id)
|
||||
await $revitMapperBinding?.assignLayerToCategory(layerIds, categoryValue)
|
||||
assignedCount = selectedLayers.value.length
|
||||
|
||||
// Track the assignment
|
||||
trackEvent('DUI3 Action', {
|
||||
name: 'Mapper Assign Category',
|
||||
category: categoryValue,
|
||||
count: assignedCount,
|
||||
mappingType: 'layer'
|
||||
})
|
||||
|
||||
selectedLayers.value = []
|
||||
}
|
||||
|
||||
@@ -384,9 +431,10 @@ const selectMappedObjects = async (mapping: CategoryMapping) => {
|
||||
}
|
||||
}
|
||||
|
||||
// Select mapped layers (highlight objects on those layers)
|
||||
// Select mapped layers (highlight objects AND restore UI state)
|
||||
const selectMappedLayers = async (layerMapping: LayerCategoryMapping) => {
|
||||
try {
|
||||
// 1. Highlight objects in Rhino
|
||||
const effectiveObjectIds =
|
||||
(await $revitMapperBinding?.getEffectiveObjectsForLayerMapping(
|
||||
layerMapping.layerIds,
|
||||
@@ -396,6 +444,24 @@ const selectMappedLayers = async (layerMapping: LayerCategoryMapping) => {
|
||||
if (effectiveObjectIds.length > 0) {
|
||||
await $baseBinding?.highlightObjects(effectiveObjectIds)
|
||||
}
|
||||
|
||||
// 2. Restore UI state - populate layer selection
|
||||
const layersToSelect = layerOptions.value.filter((layer) =>
|
||||
layerMapping.layerIds.includes(layer.id)
|
||||
)
|
||||
selectedLayers.value = layersToSelect
|
||||
|
||||
// 3. Pre-select category in dropdown
|
||||
const categoryToSelect = categoryOptions.value.find(
|
||||
(cat) => cat.value === layerMapping.categoryValue
|
||||
)
|
||||
|
||||
const { selectedCategory, currentCategories } = storeToRefs(revitMapperStore)
|
||||
|
||||
selectedCategory.value = categoryToSelect
|
||||
|
||||
// 4. Update reactive state
|
||||
currentCategories.value = [layerMapping.categoryValue]
|
||||
} catch (error) {
|
||||
console.error('Failed to highlight effective objects:', error)
|
||||
}
|
||||
@@ -470,9 +536,14 @@ const loadData = async () => {
|
||||
// Refresh both object and layer mappings
|
||||
const refreshMappings = async () => {
|
||||
try {
|
||||
if (!$revitMapperBinding) {
|
||||
console.warn('No revit mapper binding available')
|
||||
return
|
||||
}
|
||||
|
||||
const [rawMappings, rawLayerMappings] = await Promise.all([
|
||||
$revitMapperBinding?.getCurrentObjectsMappings() || [],
|
||||
$revitMapperBinding?.getCurrentLayerMappings() || []
|
||||
$revitMapperBinding.getCurrentObjectsMappings(),
|
||||
$revitMapperBinding.getCurrentLayerMappings()
|
||||
])
|
||||
|
||||
// Transform to resolve labels
|
||||
@@ -502,20 +573,51 @@ const loadAvailableLayers = async (): Promise<LayerOption[]> => {
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh just the layer mappings
|
||||
const refreshLayerMappings = async () => {
|
||||
try {
|
||||
const rawLayerMappings =
|
||||
(await $revitMapperBinding?.getCurrentLayerMappings()) || []
|
||||
// === WATCHER ===
|
||||
// Main watcher
|
||||
watch(
|
||||
() => ({
|
||||
mode: selectedMappingMode.value,
|
||||
objectIds: selectionInfo.value?.selectedObjectIds || [],
|
||||
layerIds: selectedLayers.value.map((l) => l.id)
|
||||
}),
|
||||
async ({ mode, objectIds, layerIds }) => {
|
||||
if (mode === 'Selection') {
|
||||
await revitMapperStore.updateFromTargets(objectIds, false)
|
||||
} else if (mode === 'Layer') {
|
||||
// In Layer mode, we need to watch both manual layer selection AND object selection
|
||||
// This keeps dropdowns clear when objects are deselected (like Selection mode)
|
||||
// while still supporting manual layer selection
|
||||
if (layerIds.length > 0) {
|
||||
// User has manually selected layers in UI - use layer mode logic
|
||||
await revitMapperStore.updateFromTargets(layerIds, true)
|
||||
} else {
|
||||
// No manual layer selection - use object mode logic (like Selection mode)
|
||||
// This handles the case where selectMappedLayers populated the UI but objects were deselected
|
||||
await revitMapperStore.updateFromTargets(objectIds, false)
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
)
|
||||
|
||||
layerMappings.value = rawLayerMappings.map((mapping) => ({
|
||||
...mapping,
|
||||
categoryLabel: getCategoryLabel(mapping.categoryValue)
|
||||
}))
|
||||
} catch (error) {
|
||||
console.error('Failed to refresh layer mappings:', error)
|
||||
// This handles clearing selectedLayers when objects are deselected in Layer mode
|
||||
watch(
|
||||
() => selectionInfo.value?.selectedObjectIds?.length || 0,
|
||||
async (newCount, oldCount) => {
|
||||
// Only act in Layer mode when selection count goes to 0 and we have selected layers
|
||||
if (
|
||||
selectedMappingMode.value === 'Layer' &&
|
||||
newCount === 0 &&
|
||||
oldCount > 0 &&
|
||||
selectedLayers.value.length > 0
|
||||
) {
|
||||
// nextTick to avoid interfering with the main watcher? not nice :(
|
||||
await nextTick()
|
||||
selectedLayers.value = []
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// === LIFECYCLE ===
|
||||
onMounted(async () => {
|
||||
@@ -537,5 +639,26 @@ onMounted(async () => {
|
||||
layerOptions.value = newLayers
|
||||
selectedLayers.value = []
|
||||
})
|
||||
|
||||
// Track mapper opened with the initial mode (after loadData determines it)
|
||||
trackEvent('DUI3 Action', {
|
||||
name: 'Mapper Opened',
|
||||
mode: selectedMappingMode.value
|
||||
})
|
||||
})
|
||||
|
||||
// Refresh just layer mappings
|
||||
const refreshLayerMappings = async () => {
|
||||
try {
|
||||
const rawLayerMappings =
|
||||
(await $revitMapperBinding?.getCurrentLayerMappings()) || []
|
||||
|
||||
layerMappings.value = rawLayerMappings.map((mapping) => ({
|
||||
...mapping,
|
||||
categoryLabel: getCategoryLabel(mapping.categoryValue)
|
||||
}))
|
||||
} catch (error) {
|
||||
console.error('Failed to refresh layer mappings:', error)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import type { Category } from '~/lib/bindings/definitions/IRevitMapperBinding'
|
||||
import { REVIT_CATEGORIES } from '~/lib/mapper/revit-categories'
|
||||
|
||||
export const useRevitMapper = defineStore('revitMapper', () => {
|
||||
const app = useNuxtApp()
|
||||
const { $revitMapperBinding } = app
|
||||
const currentCategories = ref<string[]>([])
|
||||
const selectedCategory = ref<Category | undefined>()
|
||||
const categoryOptions = REVIT_CATEGORIES
|
||||
|
||||
const categoryStatus = computed(() => {
|
||||
if (currentCategories.value.length === 0) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (currentCategories.value.length === 1) {
|
||||
const category = categoryOptions.find(
|
||||
(cat) => cat.value === currentCategories.value[0]
|
||||
)
|
||||
return category ? { label: category.label, value: category.value } : undefined
|
||||
}
|
||||
|
||||
return {
|
||||
label: 'Multiple categories',
|
||||
value: 'multiple',
|
||||
isMultiple: true
|
||||
}
|
||||
})
|
||||
|
||||
const updateFromTargets = async (
|
||||
targetIds: string[],
|
||||
isLayerMode: boolean
|
||||
): Promise<void> => {
|
||||
if (!targetIds.length || !$revitMapperBinding) {
|
||||
clear()
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// Call connector method based on mode
|
||||
const categories = isLayerMode
|
||||
? await $revitMapperBinding.getCategoryMappingsForLayers(targetIds)
|
||||
: await $revitMapperBinding.getCategoryMappingsForObjects(targetIds)
|
||||
|
||||
currentCategories.value = categories
|
||||
|
||||
// Update dropdown selection based on categories found
|
||||
if (categories.length === 1) {
|
||||
selectedCategory.value = categoryOptions.find(
|
||||
(cat) => cat.value === categories[0]
|
||||
)
|
||||
} else {
|
||||
// Multiple or no categories - clear dropdown selection
|
||||
selectedCategory.value = undefined
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to get category mappings:', error)
|
||||
clear()
|
||||
}
|
||||
}
|
||||
|
||||
const clear = () => {
|
||||
currentCategories.value = []
|
||||
selectedCategory.value = undefined
|
||||
}
|
||||
|
||||
return {
|
||||
currentCategories,
|
||||
selectedCategory,
|
||||
categoryStatus,
|
||||
updateFromTargets,
|
||||
clear
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user