/* 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/. */ import React, { useRef, useCallback, useEffect, useMemo, useSyncExternalStore } from 'react'; import { FolderOpen, Download, MousePointer2, PersonStanding, Ruler, Scissors, StickyNote, Eye, EyeOff, Equal, Crosshair, GitCompareArrows, Home, Maximize2, Grid3x3, ArrowUp, ArrowDown, ArrowLeft, ArrowRight, Box, HelpCircle, Loader2, Camera, Info, Layers2, SquareX, BoxSelect, Building2, Plus, PackagePlus, MessageSquare, ClipboardCheck, Puzzle, Palette, Orbit, Layout, LayoutTemplate, FileCode2, CalendarClock, Globe2, Sun, Move, PenLine, Undo2, Redo2, Boxes, Shapes, } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Switch } from '@/components/ui/switch'; import { Separator } from '@/components/ui/separator'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, DropdownMenuSub, DropdownMenuSubTrigger, DropdownMenuSubContent, } from '@/components/ui/dropdown-menu'; import { Progress } from '@/components/ui/progress'; import { useViewerStore, isIfcxDataStore } from '@/store'; import { goHomeFromStore, resetVisibilityForHomeFromStore } from '@/store/homeView'; import { executeBasketIsolate } from '@/store/basket/basketCommands'; import { useIfc } from '@/hooks/useIfc'; import { cn } from '@/lib/utils'; import { exportCsvFromBytes } from '@/lib/export/csv'; import { FileSpreadsheet, FileJson, FileText, Filter, Upload, Pencil, DraftingCompass } from 'lucide-react'; import { ExportDialog } from './ExportDialog'; import { GLBExportDialog } from './GLBExportDialog'; import { HbjsonExportDialog } from './HbjsonExportDialog'; import { BulkPropertyEditor } from './BulkPropertyEditor'; import { DataConnector } from './DataConnector'; import { ExportChangesButton } from './ExportChangesButton'; import { SearchInline } from './SearchInline'; import { recordRecentFiles, cacheFileBlobs } from '@/lib/recent-files'; import { ThemeSwitch } from './ThemeSwitch'; import { ExtensionToolbarSlot } from '@/components/extensions/ExtensionToolbarSlot'; import { toast } from '@/components/ui/toast'; import { closeActiveAnalysisExtension, getAnalysisExtensionsSnapshot, openAnalysisExtension, subscribeAnalysisExtensions, } from '@/services/analysis-extensions'; import { closePanelWindow } from '@/services/panel-windows'; type Tool = 'select' | 'walk' | 'measure' | 'section' | 'annotate' | 'addElement' | 'split' | 'spaceSketch'; type WorkspacePanel = 'script' | 'list' | 'bcf' | 'ids' | 'lens' | 'addElement' | string; // #region FIX: Move ToolButton OUTSIDE MainToolbar to prevent recreation on every render // This fixes Radix UI Tooltip's asChild prop becoming stale during re-renders interface ToolButtonProps { tool: Tool; icon: React.ElementType; label: string; shortcut?: string; activeTool: string; onToolChange: (tool: Tool) => void; /** * Tailwind classes applied when this tool is active. Defaults to the * shared `bg-primary text-primary-foreground` shape; pass a per-tool * accent (e.g. amber for Annotate) to set tools apart visually * without breaking the toolbar's tool-button rhythm. */ activeAccentClass?: string; } function ToolButton({ tool, icon: Icon, label, shortcut, activeTool, onToolChange, activeAccentClass, }: ToolButtonProps) { const isActive = activeTool === tool; return ( {label} {shortcut && ({shortcut})} ); } interface ClassVisibilityRowProps { /** Colored class glyph (caller sets the tint). */ icon: React.ReactNode; label: string; /** One-line plain-language hint about what the IFC class covers. */ description: string; checked: boolean; onChange: (next: boolean) => void; } /** * One row of the Visibility panel: colored class icon + label/description * on the left, a Switch on the right. The whole row is a