perf(editor): convert UI icons from PNG to WebP
The editor's /icons assets were ~10MB of oversized PNGs (a single toolbar icon up to 1.4MB). Convert every non-PWA icon to WebP (quality 92) and repoint all /icons/*.png references to .webp across packages/editor, packages/nodes, and apps/editor. PWA/platform icons (apple-touch-icon, icon-192, icon-512) stay PNG. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
5efcc834a8
commit
267dac7b1a
@@ -77,7 +77,7 @@ export const FloorplanCursorIndicatorOverlay = memo(function FloorplanCursorIndi
|
||||
}
|
||||
|
||||
if (mode === 'material-paint') {
|
||||
return { kind: 'asset', iconSrc: '/icons/paint.png' }
|
||||
return { kind: 'asset', iconSrc: '/icons/paint.webp' }
|
||||
}
|
||||
|
||||
return null
|
||||
|
||||
@@ -33,7 +33,7 @@ export function CameraActions({ hideOrbit = false }: { hideOrbit?: boolean }) {
|
||||
alt="Orbit Left"
|
||||
className="h-[28px] w-[28px] -scale-x-100 object-contain opacity-70 transition-opacity group-hover:opacity-100"
|
||||
height={28}
|
||||
src="/icons/rotate.png"
|
||||
src="/icons/rotate.webp"
|
||||
width={28}
|
||||
/>
|
||||
</ActionButton>
|
||||
@@ -50,7 +50,7 @@ export function CameraActions({ hideOrbit = false }: { hideOrbit?: boolean }) {
|
||||
alt="Orbit Right"
|
||||
className="h-[28px] w-[28px] object-contain opacity-70 transition-opacity group-hover:opacity-100"
|
||||
height={28}
|
||||
src="/icons/rotate.png"
|
||||
src="/icons/rotate.webp"
|
||||
width={28}
|
||||
/>
|
||||
</ActionButton>
|
||||
@@ -69,7 +69,7 @@ export function CameraActions({ hideOrbit = false }: { hideOrbit?: boolean }) {
|
||||
alt="Top View"
|
||||
className="h-[28px] w-[28px] object-contain opacity-70 transition-opacity group-hover:opacity-100"
|
||||
height={28}
|
||||
src="/icons/topview.png"
|
||||
src="/icons/topview.webp"
|
||||
width={28}
|
||||
/>
|
||||
</ActionButton>
|
||||
|
||||
@@ -24,7 +24,7 @@ type ControlConfig = {
|
||||
const controls: ControlConfig[] = [
|
||||
{
|
||||
id: 'select',
|
||||
imageSrc: '/icons/select.png',
|
||||
imageSrc: '/icons/select.webp',
|
||||
label: 'Select',
|
||||
shortcut: 'V',
|
||||
color: 'hover:bg-blue-500/20 hover:text-blue-400',
|
||||
@@ -32,7 +32,7 @@ const controls: ControlConfig[] = [
|
||||
},
|
||||
{
|
||||
id: 'zone',
|
||||
imageSrc: '/icons/zone.png',
|
||||
imageSrc: '/icons/zone.webp',
|
||||
label: 'Zone',
|
||||
shortcut: 'Z',
|
||||
color: 'hover:bg-green-500/20 hover:text-green-400',
|
||||
|
||||
@@ -8,9 +8,9 @@ export type FurnishToolConfig = {
|
||||
}
|
||||
|
||||
export const furnishTools: FurnishToolConfig[] = [
|
||||
{ id: 'item', iconSrc: '/icons/couch.png', label: 'Furniture', catalogCategory: 'furniture' },
|
||||
{ id: 'item', iconSrc: '/icons/appliance.png', label: 'Appliance', catalogCategory: 'appliance' },
|
||||
{ id: 'item', iconSrc: '/icons/kitchen.png', label: 'Kitchen', catalogCategory: 'kitchen' },
|
||||
{ id: 'item', iconSrc: '/icons/bathroom.png', label: 'Bathroom', catalogCategory: 'bathroom' },
|
||||
{ id: 'item', iconSrc: '/icons/tree.png', label: 'Outdoor', catalogCategory: 'outdoor' },
|
||||
{ id: 'item', iconSrc: '/icons/couch.webp', label: 'Furniture', catalogCategory: 'furniture' },
|
||||
{ id: 'item', iconSrc: '/icons/appliance.webp', label: 'Appliance', catalogCategory: 'appliance' },
|
||||
{ id: 'item', iconSrc: '/icons/kitchen.webp', label: 'Kitchen', catalogCategory: 'kitchen' },
|
||||
{ id: 'item', iconSrc: '/icons/bathroom.webp', label: 'Bathroom', catalogCategory: 'bathroom' },
|
||||
{ id: 'item', iconSrc: '/icons/tree.webp', label: 'Outdoor', catalogCategory: 'outdoor' },
|
||||
]
|
||||
|
||||
@@ -12,26 +12,26 @@ export type ToolConfig = {
|
||||
// for cursor/floorplan indicators. Roof-mounted accessories are intentionally
|
||||
// absent — they're placed from the roof inspector's "Add element" section.
|
||||
export const tools: ToolConfig[] = [
|
||||
{ id: 'wall', iconSrc: '/icons/wall.png', label: 'Wall' },
|
||||
{ id: 'door', iconSrc: '/icons/door.png', label: 'Door' },
|
||||
{ id: 'window', iconSrc: '/icons/window.png', label: 'Window' },
|
||||
{ id: 'stair', iconSrc: '/icons/stairs.png', label: 'Stairs' },
|
||||
{ id: 'roof', iconSrc: '/icons/roof.png', label: 'Gable Roof' },
|
||||
{ id: 'fence', iconSrc: '/icons/fence.png', label: 'Fence' },
|
||||
{ id: 'column', iconSrc: '/icons/column.png', label: 'Column' },
|
||||
{ id: 'elevator', iconSrc: '/icons/elevator.png', label: 'Elevator' },
|
||||
{ id: 'slab', iconSrc: '/icons/floor.png', label: 'Slab' },
|
||||
{ id: 'ceiling', iconSrc: '/icons/ceiling.png', label: 'Ceiling' },
|
||||
{ id: 'zone', iconSrc: '/icons/zone.png', label: 'Zone' },
|
||||
{ id: 'spawn', iconSrc: '/icons/spawn-point.png', label: 'Spawn Point' },
|
||||
{ id: 'shelf', iconSrc: '/icons/shelf.png', label: 'Shelf' },
|
||||
{ id: 'duct-segment', iconSrc: '/icons/duct.png', label: 'Duct' },
|
||||
{ id: 'duct-fitting', iconSrc: '/icons/duct-fitting.png', label: 'Duct Fitting' },
|
||||
{ id: 'duct-terminal', iconSrc: '/icons/registers.png', label: 'Register' },
|
||||
{ id: 'hvac-equipment', iconSrc: '/icons/HVAC.png', label: 'HVAC Unit' },
|
||||
{ id: 'pipe-segment', iconSrc: '/icons/dwv-pipes.png', label: 'DWV Pipe' },
|
||||
{ id: 'pipe-trap', iconSrc: '/icons/dwv-pipes.png', label: 'Trap' },
|
||||
{ id: 'pipe-fitting', iconSrc: '/icons/duct-fitting.png', label: 'Pipe Fitting' },
|
||||
{ id: 'lineset', iconSrc: '/icons/lineset.png', label: 'Lineset' },
|
||||
{ id: 'liquid-line', iconSrc: '/icons/lineset.png', label: 'Liquid Line' },
|
||||
{ id: 'wall', iconSrc: '/icons/wall.webp', label: 'Wall' },
|
||||
{ id: 'door', iconSrc: '/icons/door.webp', label: 'Door' },
|
||||
{ id: 'window', iconSrc: '/icons/window.webp', label: 'Window' },
|
||||
{ id: 'stair', iconSrc: '/icons/stairs.webp', label: 'Stairs' },
|
||||
{ id: 'roof', iconSrc: '/icons/roof.webp', label: 'Gable Roof' },
|
||||
{ id: 'fence', iconSrc: '/icons/fence.webp', label: 'Fence' },
|
||||
{ id: 'column', iconSrc: '/icons/column.webp', label: 'Column' },
|
||||
{ id: 'elevator', iconSrc: '/icons/elevator.webp', label: 'Elevator' },
|
||||
{ id: 'slab', iconSrc: '/icons/floor.webp', label: 'Slab' },
|
||||
{ id: 'ceiling', iconSrc: '/icons/ceiling.webp', label: 'Ceiling' },
|
||||
{ id: 'zone', iconSrc: '/icons/zone.webp', label: 'Zone' },
|
||||
{ id: 'spawn', iconSrc: '/icons/spawn-point.webp', label: 'Spawn Point' },
|
||||
{ id: 'shelf', iconSrc: '/icons/shelf.webp', label: 'Shelf' },
|
||||
{ id: 'duct-segment', iconSrc: '/icons/duct.webp', label: 'Duct' },
|
||||
{ id: 'duct-fitting', iconSrc: '/icons/duct-fitting.webp', label: 'Duct Fitting' },
|
||||
{ id: 'duct-terminal', iconSrc: '/icons/registers.webp', label: 'Register' },
|
||||
{ id: 'hvac-equipment', iconSrc: '/icons/HVAC.webp', label: 'HVAC Unit' },
|
||||
{ id: 'pipe-segment', iconSrc: '/icons/dwv-pipes.webp', label: 'DWV Pipe' },
|
||||
{ id: 'pipe-trap', iconSrc: '/icons/dwv-pipes.webp', label: 'Trap' },
|
||||
{ id: 'pipe-fitting', iconSrc: '/icons/duct-fitting.webp', label: 'Pipe Fitting' },
|
||||
{ id: 'lineset', iconSrc: '/icons/lineset.webp', label: 'Lineset' },
|
||||
{ id: 'liquid-line', iconSrc: '/icons/lineset.webp', label: 'Liquid Line' },
|
||||
]
|
||||
|
||||
@@ -226,7 +226,7 @@ function GuidesControl() {
|
||||
<img
|
||||
alt="Guides"
|
||||
className="h-[28px] w-[28px] object-contain"
|
||||
src="/icons/floorplan.png"
|
||||
src="/icons/floorplan.webp"
|
||||
/>
|
||||
<span className="absolute -right-1.5 -bottom-1 min-w-[14px] rounded-full bg-white/20 px-[3px] text-center font-medium text-[9px] text-white/70 leading-[14px]">
|
||||
{guides.length}
|
||||
@@ -265,7 +265,7 @@ function GuidesControl() {
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="flex h-7 w-7 shrink-0 items-center justify-center rounded-lg bg-background/80">
|
||||
<img alt="" className="h-4 w-4 object-contain" src="/icons/floorplan.png" />
|
||||
<img alt="" className="h-4 w-4 object-contain" src="/icons/floorplan.webp" />
|
||||
</span>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="font-medium text-foreground text-sm">Guide images</p>
|
||||
@@ -305,7 +305,7 @@ function GuidesControl() {
|
||||
<img
|
||||
alt=""
|
||||
className="h-3.5 w-3.5 shrink-0 object-contain opacity-70"
|
||||
src="/icons/floorplan.png"
|
||||
src="/icons/floorplan.webp"
|
||||
/>
|
||||
<p className="truncate font-medium text-foreground text-sm">
|
||||
{guide.name || `Guide image ${index + 1}`}
|
||||
@@ -466,7 +466,7 @@ function ScansControl() {
|
||||
variant="ghost"
|
||||
>
|
||||
<div className="relative">
|
||||
<img alt="Scans" className="h-[28px] w-[28px] object-contain" src="/icons/mesh.png" />
|
||||
<img alt="Scans" className="h-[28px] w-[28px] object-contain" src="/icons/mesh.webp" />
|
||||
<span className="absolute -right-1.5 -bottom-1 min-w-[14px] rounded-full bg-white/20 px-[3px] text-center font-medium text-[9px] text-white/70 leading-[14px]">
|
||||
{scans.length}
|
||||
</span>
|
||||
@@ -504,7 +504,7 @@ function ScansControl() {
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="flex h-7 w-7 shrink-0 items-center justify-center rounded-lg bg-background/80">
|
||||
<img alt="" className="h-4 w-4 object-contain" src="/icons/mesh.png" />
|
||||
<img alt="" className="h-4 w-4 object-contain" src="/icons/mesh.webp" />
|
||||
</span>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="font-medium text-foreground text-sm">Scans</p>
|
||||
@@ -544,7 +544,7 @@ function ScansControl() {
|
||||
<img
|
||||
alt=""
|
||||
className="h-3.5 w-3.5 shrink-0 object-contain opacity-70"
|
||||
src="/icons/mesh.png"
|
||||
src="/icons/mesh.webp"
|
||||
/>
|
||||
<p className="truncate font-medium text-foreground text-sm">
|
||||
{scan.name || `Scan ${index + 1}`}
|
||||
@@ -765,7 +765,7 @@ function ReferencesControl() {
|
||||
<img
|
||||
alt="References"
|
||||
className="h-[28px] w-[28px] object-contain"
|
||||
src="/icons/floorplan.png"
|
||||
src="/icons/floorplan.webp"
|
||||
/>
|
||||
<span className="absolute -right-1.5 -bottom-1 min-w-[14px] rounded-full bg-white/20 px-[3px] text-center font-medium text-[9px] text-white/70 leading-[14px]">
|
||||
{total}
|
||||
@@ -808,7 +808,7 @@ function ReferencesControl() {
|
||||
)}
|
||||
<ReferenceListSection
|
||||
emptyText={REFERENCES_EMPTY_TEXT}
|
||||
iconSrc="/icons/mesh.png"
|
||||
iconSrc="/icons/mesh.webp"
|
||||
nodes={scans}
|
||||
noun="scan"
|
||||
onError={setUploadError}
|
||||
@@ -819,7 +819,7 @@ function ReferencesControl() {
|
||||
<div className="h-px bg-border/45" />
|
||||
<ReferenceListSection
|
||||
emptyText={REFERENCES_EMPTY_TEXT}
|
||||
iconSrc="/icons/floorplan.png"
|
||||
iconSrc="/icons/floorplan.webp"
|
||||
nodes={guides}
|
||||
noun="guide image"
|
||||
onError={setUploadError}
|
||||
|
||||
@@ -6,26 +6,26 @@ export type NodeDisplay = {
|
||||
}
|
||||
|
||||
const TYPE_DEFAULTS: Record<string, NodeDisplay> = {
|
||||
item: { icon: '/icons/furniture.png', label: 'Item' },
|
||||
wall: { icon: '/icons/wall.png', label: 'Wall' },
|
||||
door: { icon: '/icons/door.png', label: 'Door' },
|
||||
window: { icon: '/icons/window.png', label: 'Window' },
|
||||
slab: { icon: '/icons/floor.png', label: 'Slab' },
|
||||
ceiling: { icon: '/icons/ceiling.png', label: 'Ceiling' },
|
||||
column: { icon: '/icons/column.png', label: 'Column' },
|
||||
elevator: { icon: '/icons/elevator.png', label: 'Elevator' },
|
||||
fence: { icon: '/icons/fence.png', label: 'Fence' },
|
||||
roof: { icon: '/icons/roof.png', label: 'Roof' },
|
||||
'roof-segment': { icon: '/icons/roof.png', label: 'Roof segment' },
|
||||
stair: { icon: '/icons/stair.png', label: 'Stair' },
|
||||
'stair-segment': { icon: '/icons/stair.png', label: 'Stair segment' },
|
||||
scan: { icon: '/icons/mesh.png', label: '3D Scan' },
|
||||
guide: { icon: '/icons/floorplan.png', label: 'Guide image' },
|
||||
item: { icon: '/icons/furniture.webp', label: 'Item' },
|
||||
wall: { icon: '/icons/wall.webp', label: 'Wall' },
|
||||
door: { icon: '/icons/door.webp', label: 'Door' },
|
||||
window: { icon: '/icons/window.webp', label: 'Window' },
|
||||
slab: { icon: '/icons/floor.webp', label: 'Slab' },
|
||||
ceiling: { icon: '/icons/ceiling.webp', label: 'Ceiling' },
|
||||
column: { icon: '/icons/column.webp', label: 'Column' },
|
||||
elevator: { icon: '/icons/elevator.webp', label: 'Elevator' },
|
||||
fence: { icon: '/icons/fence.webp', label: 'Fence' },
|
||||
roof: { icon: '/icons/roof.webp', label: 'Roof' },
|
||||
'roof-segment': { icon: '/icons/roof.webp', label: 'Roof segment' },
|
||||
stair: { icon: '/icons/stair.webp', label: 'Stair' },
|
||||
'stair-segment': { icon: '/icons/stair.webp', label: 'Stair segment' },
|
||||
scan: { icon: '/icons/mesh.webp', label: '3D Scan' },
|
||||
guide: { icon: '/icons/floorplan.webp', label: 'Guide image' },
|
||||
}
|
||||
|
||||
export function getNodeDisplay(node: AnyNode | null | undefined): NodeDisplay {
|
||||
if (!node) return { icon: '/icons/select.png', label: 'Selection' }
|
||||
const fallback = TYPE_DEFAULTS[node.type] ?? { icon: '/icons/select.png', label: node.type }
|
||||
if (!node) return { icon: '/icons/select.webp', label: 'Selection' }
|
||||
const fallback = TYPE_DEFAULTS[node.type] ?? { icon: '/icons/select.webp', label: node.type }
|
||||
// Item nodes carry an asset with its own thumbnail/name
|
||||
if (node.type === 'item') {
|
||||
return {
|
||||
|
||||
@@ -52,7 +52,7 @@ export const InspectorFooterContext = createContext<React.ReactNode>(null)
|
||||
|
||||
interface PanelWrapperProps {
|
||||
title: string
|
||||
/** Either a URL path (legacy panels pass `/icons/floor.png` etc.,
|
||||
/** Either a URL path (legacy panels pass `/icons/floor.webp` etc.,
|
||||
* rendered via next/image) OR a React node (registry-driven
|
||||
* inspector renders `<Icon icon="lucide:fence" />` from
|
||||
* `def.presentation.icon`). */
|
||||
|
||||
@@ -22,13 +22,13 @@ interface IconRailProps {
|
||||
|
||||
const sitePanel: { id: PanelId; iconSrc: string; label: string } = {
|
||||
id: 'site',
|
||||
iconSrc: '/icons/level.png',
|
||||
iconSrc: '/icons/level.webp',
|
||||
label: 'Site',
|
||||
}
|
||||
|
||||
const settingsPanel: { id: PanelId; iconSrc: string; label: string } = {
|
||||
id: 'settings',
|
||||
iconSrc: '/icons/settings.png',
|
||||
iconSrc: '/icons/settings.webp',
|
||||
label: 'Settings',
|
||||
}
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ export const CeilingTreeNode = memo(function CeilingTreeNode({
|
||||
expanded={expanded}
|
||||
hasChildren={children.length > 0}
|
||||
icon={
|
||||
<Image alt="" className="object-contain" height={14} src="/icons/ceiling.png" width={14} />
|
||||
<Image alt="" className="object-contain" height={14} src="/icons/ceiling.webp" width={14} />
|
||||
}
|
||||
isHovered={isHovered}
|
||||
isLast={isLast}
|
||||
|
||||
@@ -57,7 +57,7 @@ export const ChimneyTreeNode = memo(function ChimneyTreeNode({
|
||||
alt=""
|
||||
className="object-contain opacity-60"
|
||||
height={14}
|
||||
src="/icons/roof.png"
|
||||
src="/icons/roof.webp"
|
||||
width={14}
|
||||
/>
|
||||
</SnapTargetIcon>
|
||||
|
||||
@@ -51,7 +51,7 @@ export const ColumnTreeNode = memo(function ColumnTreeNode({
|
||||
expanded={false}
|
||||
hasChildren={false}
|
||||
icon={
|
||||
<Image alt="" className="object-contain" height={14} src="/icons/column.png" width={14} />
|
||||
<Image alt="" className="object-contain" height={14} src="/icons/column.webp" width={14} />
|
||||
}
|
||||
isHovered={isHovered}
|
||||
isLast={isLast}
|
||||
|
||||
@@ -57,7 +57,7 @@ export const DoorTreeNode = memo(function DoorTreeNode({
|
||||
hasChildren={false}
|
||||
icon={
|
||||
<SnapTargetIcon target={snapTarget}>
|
||||
<Image alt="" className="object-contain" height={14} src="/icons/door.png" width={14} />
|
||||
<Image alt="" className="object-contain" height={14} src="/icons/door.webp" width={14} />
|
||||
</SnapTargetIcon>
|
||||
}
|
||||
isHovered={isHovered}
|
||||
|
||||
@@ -63,7 +63,7 @@ export const DormerTreeNode = memo(function DormerTreeNode({
|
||||
alt=""
|
||||
className="object-contain opacity-60"
|
||||
height={14}
|
||||
src="/icons/roof.png"
|
||||
src="/icons/roof.webp"
|
||||
width={14}
|
||||
/>
|
||||
</SnapTargetIcon>
|
||||
|
||||
@@ -49,7 +49,7 @@ export const ElevatorTreeNode = memo(function ElevatorTreeNode({
|
||||
expanded={false}
|
||||
hasChildren={false}
|
||||
icon={
|
||||
<Image alt="" className="object-contain" height={14} src="/icons/elevator.png" width={14} />
|
||||
<Image alt="" className="object-contain" height={14} src="/icons/elevator.webp" width={14} />
|
||||
}
|
||||
isHovered={isHovered}
|
||||
isLast={isLast}
|
||||
|
||||
@@ -43,7 +43,7 @@ export const FenceTreeNode = memo(function FenceTreeNode({
|
||||
expanded={false}
|
||||
hasChildren={false}
|
||||
icon={
|
||||
<Image alt="" className="object-contain" height={14} src="/icons/fence.png" width={14} />
|
||||
<Image alt="" className="object-contain" height={14} src="/icons/fence.webp" width={14} />
|
||||
}
|
||||
isHovered={isHovered}
|
||||
isLast={isLast}
|
||||
|
||||
@@ -57,7 +57,7 @@ export const GutterTreeNode = memo(function GutterTreeNode({
|
||||
alt=""
|
||||
className="object-contain opacity-60"
|
||||
height={14}
|
||||
src="/icons/roof.png"
|
||||
src="/icons/roof.webp"
|
||||
width={14}
|
||||
/>
|
||||
</SnapTargetIcon>
|
||||
|
||||
@@ -356,13 +356,13 @@ const ReferenceItem = memo(function ReferenceItem({
|
||||
<img
|
||||
alt="Scan"
|
||||
className="h-3.5 w-3.5 shrink-0 object-contain opacity-70 transition-opacity group-hover/ref:opacity-100"
|
||||
src="/icons/mesh.png"
|
||||
src="/icons/mesh.webp"
|
||||
/>
|
||||
) : (
|
||||
<img
|
||||
alt="Guide"
|
||||
className="h-3.5 w-3.5 shrink-0 object-contain opacity-70 transition-opacity group-hover/ref:opacity-100"
|
||||
src="/icons/floorplan.png"
|
||||
src="/icons/floorplan.webp"
|
||||
/>
|
||||
)}
|
||||
<InlineRenameInput
|
||||
@@ -721,7 +721,7 @@ const LevelItem = memo(function LevelItem({
|
||||
'h-4 w-4 shrink-0 object-contain transition-all duration-200',
|
||||
!isSelected && 'opacity-60 grayscale',
|
||||
)}
|
||||
src="/icons/level.png"
|
||||
src="/icons/level.webp"
|
||||
/>
|
||||
<InlineRenameInput
|
||||
defaultName={getDefaultLevelName(level.level)}
|
||||
@@ -999,7 +999,7 @@ const LayerToggle = memo(function LayerToggle() {
|
||||
'mb-1 h-6 w-6 transition-all',
|
||||
activeTab !== 'structure' && 'opacity-50 grayscale',
|
||||
)}
|
||||
src="/icons/room.png"
|
||||
src="/icons/room.webp"
|
||||
/>
|
||||
Structure
|
||||
</div>
|
||||
@@ -1035,7 +1035,7 @@ const LayerToggle = memo(function LayerToggle() {
|
||||
'mb-1 h-6 w-6 transition-all',
|
||||
activeTab !== 'furnish' && 'opacity-50 grayscale',
|
||||
)}
|
||||
src="/icons/couch.png"
|
||||
src="/icons/couch.webp"
|
||||
/>
|
||||
Furnish
|
||||
</div>
|
||||
@@ -1072,7 +1072,7 @@ const LayerToggle = memo(function LayerToggle() {
|
||||
'mb-1 h-6 w-6 transition-all',
|
||||
activeTab !== 'zones' && 'opacity-50 grayscale',
|
||||
)}
|
||||
src="/icons/kitchen.png"
|
||||
src="/icons/kitchen.webp"
|
||||
/>
|
||||
Zones
|
||||
</div>
|
||||
@@ -1413,7 +1413,7 @@ const BuildingItem = memo(function BuildingItem({
|
||||
'h-5 w-5 object-contain transition-all',
|
||||
!isBuildingActive && 'opacity-60 grayscale',
|
||||
)}
|
||||
src="/icons/building.png"
|
||||
src="/icons/building.webp"
|
||||
/>
|
||||
<span className="truncate font-medium text-sm">{building.name || 'Building'}</span>
|
||||
</div>
|
||||
@@ -1569,7 +1569,7 @@ export function SitePanel({ projectId, onUploadAsset, onDeleteAsset }: SitePanel
|
||||
'h-5 w-5 object-contain transition-all',
|
||||
phase !== 'site' && 'opacity-60 grayscale',
|
||||
)}
|
||||
src="/icons/site-flag.png"
|
||||
src="/icons/site-flag.webp"
|
||||
/>
|
||||
<span className="font-medium text-sm">{siteNode.name || 'Site'}</span>
|
||||
</div>
|
||||
|
||||
@@ -15,13 +15,13 @@ import {
|
||||
import { TreeNodeActions } from './tree-node-actions'
|
||||
|
||||
const CATEGORY_ICONS: Record<string, string> = {
|
||||
door: '/icons/door.png',
|
||||
window: '/icons/window.png',
|
||||
furniture: '/icons/couch.png',
|
||||
appliance: '/icons/appliance.png',
|
||||
kitchen: '/icons/kitchen.png',
|
||||
bathroom: '/icons/bathroom.png',
|
||||
outdoor: '/icons/tree.png',
|
||||
door: '/icons/door.webp',
|
||||
window: '/icons/window.webp',
|
||||
furniture: '/icons/couch.webp',
|
||||
appliance: '/icons/appliance.webp',
|
||||
kitchen: '/icons/kitchen.webp',
|
||||
bathroom: '/icons/bathroom.webp',
|
||||
outdoor: '/icons/tree.webp',
|
||||
}
|
||||
|
||||
interface ItemTreeNodeProps {
|
||||
@@ -88,7 +88,7 @@ export const ItemTreeNode = memo(function ItemTreeNode({
|
||||
const handleStartEditing = useCallback(() => setIsEditing(true), [])
|
||||
const handleStopEditing = useCallback(() => setIsEditing(false), [])
|
||||
|
||||
const iconSrc = CATEGORY_ICONS[asset?.category ?? ''] || '/icons/couch.png'
|
||||
const iconSrc = CATEGORY_ICONS[asset?.category ?? ''] || '/icons/couch.webp'
|
||||
const snapTarget = resolveNodeSnapTarget(node)
|
||||
const defaultName = asset?.name || 'Item'
|
||||
const hasChildren = children.length > 0
|
||||
|
||||
@@ -40,7 +40,7 @@ export const RegistryTreeNode = memo(function RegistryTreeNode({
|
||||
|
||||
const presentation = node ? nodeRegistry.get(node.type)?.presentation : undefined
|
||||
const icon = presentation?.icon
|
||||
const iconSrc = icon?.kind === 'url' ? icon.src : '/icons/roof.png'
|
||||
const iconSrc = icon?.kind === 'url' ? icon.src : '/icons/roof.webp'
|
||||
const snapTarget = resolveNodeSnapTarget(node)
|
||||
const defaultName = node?.name || presentation?.label || 'Node'
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ export const RoofTreeNode = memo(function RoofTreeNode({
|
||||
expanded={expanded}
|
||||
hasChildren={segments.length > 0}
|
||||
icon={
|
||||
<Image alt="" className="object-contain" height={14} src="/icons/roof.png" width={14} />
|
||||
<Image alt="" className="object-contain" height={14} src="/icons/roof.webp" width={14} />
|
||||
}
|
||||
isDropTarget={isValidDropTarget && isDropTarget}
|
||||
isHovered={isHovered || isDropTarget}
|
||||
@@ -230,7 +230,7 @@ function RoofSegmentTreeNode({
|
||||
alt=""
|
||||
className="object-contain opacity-60"
|
||||
height={14}
|
||||
src="/icons/roof.png"
|
||||
src="/icons/roof.webp"
|
||||
width={14}
|
||||
/>
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ export const ShelfTreeNode = memo(function ShelfTreeNode({
|
||||
expanded={expanded}
|
||||
hasChildren={hasChildren}
|
||||
icon={
|
||||
<Image alt="" className="object-contain" height={14} src="/icons/shelf.png" width={14} />
|
||||
<Image alt="" className="object-contain" height={14} src="/icons/shelf.webp" width={14} />
|
||||
}
|
||||
isHovered={isHovered}
|
||||
isLast={isLast}
|
||||
|
||||
@@ -55,7 +55,7 @@ export const SlabTreeNode = memo(function SlabTreeNode({
|
||||
expanded={false}
|
||||
hasChildren={false}
|
||||
icon={
|
||||
<Image alt="" className="object-contain" height={14} src="/icons/floor.png" width={14} />
|
||||
<Image alt="" className="object-contain" height={14} src="/icons/floor.webp" width={14} />
|
||||
}
|
||||
isHovered={isHovered}
|
||||
isLast={isLast}
|
||||
|
||||
+1
-1
@@ -57,7 +57,7 @@ export const SolarPanelTreeNode = memo(function SolarPanelTreeNode({
|
||||
alt=""
|
||||
className="object-contain opacity-60"
|
||||
height={14}
|
||||
src="/icons/roof.png"
|
||||
src="/icons/roof.webp"
|
||||
width={14}
|
||||
/>
|
||||
</SnapTargetIcon>
|
||||
|
||||
@@ -54,7 +54,7 @@ export const SpawnTreeNode = memo(function SpawnTreeNode({
|
||||
alt=""
|
||||
className="object-contain"
|
||||
height={14}
|
||||
src="/icons/spawn-point.png"
|
||||
src="/icons/spawn-point.webp"
|
||||
width={14}
|
||||
/>
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ export const StairTreeNode = memo(function StairTreeNode({
|
||||
expanded={expanded}
|
||||
hasChildren={segments.length > 0}
|
||||
icon={
|
||||
<Image alt="" className="object-contain" height={14} src="/icons/stairs.png" width={14} />
|
||||
<Image alt="" className="object-contain" height={14} src="/icons/stairs.webp" width={14} />
|
||||
}
|
||||
isDropTarget={isValidDropTarget && isDropTarget}
|
||||
isHovered={isHovered || isDropTarget}
|
||||
@@ -206,7 +206,7 @@ function StairSegmentTreeNode({
|
||||
alt=""
|
||||
className="object-contain opacity-60"
|
||||
height={14}
|
||||
src="/icons/stairs.png"
|
||||
src="/icons/stairs.webp"
|
||||
width={14}
|
||||
/>
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ export const WallTreeNode = memo(function WallTreeNode({
|
||||
expanded={expanded}
|
||||
hasChildren={children.length > 0}
|
||||
icon={
|
||||
<Image alt="" className="object-contain" height={14} src="/icons/wall.png" width={14} />
|
||||
<Image alt="" className="object-contain" height={14} src="/icons/wall.webp" width={14} />
|
||||
}
|
||||
isHovered={isHovered}
|
||||
isLast={isLast}
|
||||
|
||||
@@ -57,7 +57,7 @@ export const WindowTreeNode = memo(function WindowTreeNode({
|
||||
hasChildren={false}
|
||||
icon={
|
||||
<SnapTargetIcon target={snapTarget}>
|
||||
<Image alt="" className="object-contain" height={14} src="/icons/window.png" width={14} />
|
||||
<Image alt="" className="object-contain" height={14} src="/icons/window.webp" width={14} />
|
||||
</SnapTargetIcon>
|
||||
}
|
||||
isHovered={isHovered}
|
||||
|
||||
@@ -6,9 +6,9 @@ export type SnapTarget = 'wall' | 'ceiling' | 'roof'
|
||||
export type SnapTargetBadgeSize = 'tile' | 'tree'
|
||||
|
||||
const SNAP_TARGET_ICONS: Record<SnapTarget, string> = {
|
||||
wall: '/icons/wall.png',
|
||||
ceiling: '/icons/ceiling.png',
|
||||
roof: '/icons/roof.png',
|
||||
wall: '/icons/wall.webp',
|
||||
ceiling: '/icons/ceiling.webp',
|
||||
roof: '/icons/roof.webp',
|
||||
}
|
||||
|
||||
const SNAP_TARGET_LABELS: Record<SnapTarget, string> = {
|
||||
|
||||
@@ -64,19 +64,19 @@ const levelModeBadgeLabels: Record<'manual' | 'stacked' | 'exploded' | 'solo', s
|
||||
const wallModeConfig = {
|
||||
up: {
|
||||
icon: (props: any) => (
|
||||
<img alt="Full Height" height={28} src="/icons/room.png" width={28} {...props} />
|
||||
<img alt="Full Height" height={28} src="/icons/room.webp" width={28} {...props} />
|
||||
),
|
||||
label: 'Full Height',
|
||||
},
|
||||
cutaway: {
|
||||
icon: (props: any) => (
|
||||
<img alt="Cutaway" height={28} src="/icons/wallcut.png" width={28} {...props} />
|
||||
<img alt="Cutaway" height={28} src="/icons/wallcut.webp" width={28} {...props} />
|
||||
),
|
||||
label: 'Cutaway',
|
||||
},
|
||||
down: {
|
||||
icon: (props: any) => (
|
||||
<img alt="Low" height={28} src="/icons/walllow.png" width={28} {...props} />
|
||||
<img alt="Low" height={28} src="/icons/walllow.webp" width={28} {...props} />
|
||||
),
|
||||
label: 'Low',
|
||||
},
|
||||
@@ -455,7 +455,7 @@ export const ViewerOverlay = ({
|
||||
<img
|
||||
alt="Scans"
|
||||
className="h-[28px] w-[28px] object-contain"
|
||||
src="/icons/mesh.png"
|
||||
src="/icons/mesh.webp"
|
||||
/>
|
||||
</ActionButton>
|
||||
)}
|
||||
@@ -476,7 +476,7 @@ export const ViewerOverlay = ({
|
||||
<img
|
||||
alt="Guides"
|
||||
className="h-[28px] w-[28px] object-contain"
|
||||
src="/icons/floorplan.png"
|
||||
src="/icons/floorplan.webp"
|
||||
/>
|
||||
</ActionButton>
|
||||
)}
|
||||
@@ -582,7 +582,7 @@ export const ViewerOverlay = ({
|
||||
<img
|
||||
alt="Orbit Left"
|
||||
className="h-[28px] w-[28px] -scale-x-100 object-contain opacity-70 transition-opacity group-hover:opacity-100"
|
||||
src="/icons/rotate.png"
|
||||
src="/icons/rotate.webp"
|
||||
/>
|
||||
</ActionButton>
|
||||
|
||||
@@ -597,7 +597,7 @@ export const ViewerOverlay = ({
|
||||
<img
|
||||
alt="Orbit Right"
|
||||
className="h-[28px] w-[28px] object-contain opacity-70 transition-opacity group-hover:opacity-100"
|
||||
src="/icons/rotate.png"
|
||||
src="/icons/rotate.webp"
|
||||
/>
|
||||
</ActionButton>
|
||||
|
||||
@@ -612,7 +612,7 @@ export const ViewerOverlay = ({
|
||||
<img
|
||||
alt="Top View"
|
||||
className="h-[28px] w-[28px] object-contain opacity-70 transition-opacity group-hover:opacity-100"
|
||||
src="/icons/topview.png"
|
||||
src="/icons/topview.webp"
|
||||
/>
|
||||
</ActionButton>
|
||||
</div>
|
||||
|
||||
@@ -220,7 +220,7 @@ export const boxVentDefinition: NodeDefinition<typeof BoxVentNode> = {
|
||||
presentation: {
|
||||
label: 'Box Vent',
|
||||
description: 'Small louvered exhaust vent that sits on a roof slope.',
|
||||
icon: { kind: 'url', src: '/icons/roof.png' },
|
||||
icon: { kind: 'url', src: '/icons/roof.webp' },
|
||||
paletteSection: 'structure',
|
||||
paletteOrder: 120,
|
||||
},
|
||||
|
||||
@@ -164,7 +164,7 @@ export default function BoxVentPanel() {
|
||||
|
||||
return (
|
||||
<PanelWrapper
|
||||
icon="/icons/roof.png"
|
||||
icon="/icons/roof.webp"
|
||||
onBack={node.roofSegmentId ? handleBack : undefined}
|
||||
onClose={handleClose}
|
||||
title={node.name || 'Box Vent'}
|
||||
|
||||
@@ -50,7 +50,7 @@ export const buildingDefinition: NodeDefinition<typeof BuildingNode> = {
|
||||
presentation: {
|
||||
label: 'Building',
|
||||
description: 'A building container holding one or more levels.',
|
||||
icon: { kind: 'url', src: '/icons/building.png' },
|
||||
icon: { kind: 'url', src: '/icons/building.webp' },
|
||||
paletteSection: 'site',
|
||||
paletteOrder: 6,
|
||||
},
|
||||
|
||||
@@ -157,7 +157,7 @@ export const ceilingDefinition: NodeDefinition<typeof CeilingNode> = {
|
||||
presentation: {
|
||||
label: 'Ceiling',
|
||||
description: 'A polygon-bounded ceiling surface that hosts ceiling-mounted items.',
|
||||
icon: { kind: 'url', src: '/icons/ceiling.png' },
|
||||
icon: { kind: 'url', src: '/icons/ceiling.webp' },
|
||||
paletteSection: 'structure',
|
||||
paletteOrder: 40,
|
||||
},
|
||||
|
||||
@@ -147,7 +147,7 @@ export function CeilingPanel() {
|
||||
|
||||
return (
|
||||
<PanelWrapper
|
||||
icon="/icons/ceiling.png"
|
||||
icon="/icons/ceiling.webp"
|
||||
onClose={handleClose}
|
||||
title={node.name || 'Ceiling'}
|
||||
width={320}
|
||||
|
||||
@@ -409,7 +409,7 @@ export const chimneyDefinition: NodeDefinition<typeof ChimneyNode> = {
|
||||
presentation: {
|
||||
label: 'Chimney',
|
||||
description: 'Vertical masonry stack on a roof segment.',
|
||||
icon: { kind: 'url', src: '/icons/roof.png' },
|
||||
icon: { kind: 'url', src: '/icons/roof.webp' },
|
||||
paletteSection: 'structure',
|
||||
paletteOrder: 122,
|
||||
},
|
||||
|
||||
@@ -339,7 +339,7 @@ export default function ChimneyPanel() {
|
||||
// Match the current store node against the preset table so the
|
||||
return (
|
||||
<PanelWrapper
|
||||
icon="/icons/roof.png"
|
||||
icon="/icons/roof.webp"
|
||||
onBack={node.roofSegmentId ? handleBack : undefined}
|
||||
onClose={handleClose}
|
||||
title={node.name || 'Chimney'}
|
||||
|
||||
@@ -382,7 +382,7 @@ export const columnDefinition: NodeDefinition<typeof ColumnNode> = {
|
||||
presentation: {
|
||||
label: 'Column',
|
||||
description: 'A parametric column with configurable cross-section, base, and capital.',
|
||||
icon: { kind: 'url', src: '/icons/column.png' },
|
||||
icon: { kind: 'url', src: '/icons/column.webp' },
|
||||
paletteSection: 'structure',
|
||||
paletteOrder: 70,
|
||||
},
|
||||
|
||||
@@ -313,7 +313,7 @@ export default function ColumnPanel() {
|
||||
|
||||
return (
|
||||
<PanelWrapper
|
||||
icon="/icons/column.png"
|
||||
icon="/icons/column.webp"
|
||||
onClose={handleClose}
|
||||
title={node.name || 'Column'}
|
||||
width={300}
|
||||
|
||||
@@ -153,7 +153,7 @@ export const cupolaDefinition: NodeDefinition<typeof CupolaNode> = {
|
||||
presentation: {
|
||||
label: 'Cupola',
|
||||
description: 'Louvered roof lantern with a dome or pyramid cap and optional finial.',
|
||||
icon: { kind: 'url', src: '/icons/roof.png' },
|
||||
icon: { kind: 'url', src: '/icons/roof.webp' },
|
||||
paletteSection: 'structure',
|
||||
paletteOrder: 122,
|
||||
},
|
||||
|
||||
@@ -137,7 +137,7 @@ export default function CupolaPanel() {
|
||||
|
||||
return (
|
||||
<PanelWrapper
|
||||
icon="/icons/roof.png"
|
||||
icon="/icons/roof.webp"
|
||||
onBack={node.roofSegmentId ? handleBack : undefined}
|
||||
onClose={handleClose}
|
||||
title={node.name || 'Cupola'}
|
||||
|
||||
@@ -251,7 +251,7 @@ export const doorDefinition: NodeDefinition<typeof DoorNode> = {
|
||||
presentation: {
|
||||
label: 'Door',
|
||||
description: 'A door cut into a wall. Animated open/close state.',
|
||||
icon: { kind: 'url', src: '/icons/door.png' },
|
||||
icon: { kind: 'url', src: '/icons/door.webp' },
|
||||
paletteSection: 'structure',
|
||||
paletteOrder: 50,
|
||||
},
|
||||
|
||||
@@ -516,7 +516,7 @@ export default function DoorPanel() {
|
||||
|
||||
return (
|
||||
<PanelWrapper
|
||||
icon="/icons/door.png"
|
||||
icon="/icons/door.webp"
|
||||
onClose={handleClose}
|
||||
title={node.name || 'Door'}
|
||||
width={320}
|
||||
|
||||
@@ -452,7 +452,7 @@ export const dormerDefinition: NodeDefinition<typeof DormerNode> = {
|
||||
presentation: {
|
||||
label: 'Dormer',
|
||||
description: 'House-shaped protrusion on a roof segment.',
|
||||
icon: { kind: 'url', src: '/icons/roof.png' },
|
||||
icon: { kind: 'url', src: '/icons/roof.webp' },
|
||||
paletteSection: 'structure',
|
||||
paletteOrder: 125,
|
||||
},
|
||||
|
||||
@@ -159,7 +159,7 @@ export default function DormerPanel() {
|
||||
|
||||
return (
|
||||
<PanelWrapper
|
||||
icon="/icons/roof.png"
|
||||
icon="/icons/roof.webp"
|
||||
onBack={node.roofSegmentId ? handleBack : undefined}
|
||||
onClose={handleClose}
|
||||
title={node.name || 'Dormer'}
|
||||
|
||||
@@ -197,7 +197,7 @@ export const downspoutDefinition: NodeDefinition<typeof DownspoutNode> = {
|
||||
presentation: {
|
||||
label: 'Downspout',
|
||||
description: 'Vertical drop pipe from a gutter outlet to the ground.',
|
||||
icon: { kind: 'url', src: '/icons/roof.png' },
|
||||
icon: { kind: 'url', src: '/icons/roof.webp' },
|
||||
paletteSection: 'structure',
|
||||
paletteOrder: 123,
|
||||
},
|
||||
|
||||
@@ -122,7 +122,7 @@ export const ductFittingDefinition: NodeDefinition<typeof DuctFittingNode> = {
|
||||
presentation: {
|
||||
label: 'Duct Fitting',
|
||||
description: 'Elbow, tee, reducer, or square-to-round transition connecting duct runs.',
|
||||
icon: { kind: 'url', src: '/icons/duct-fitting.png' },
|
||||
icon: { kind: 'url', src: '/icons/duct-fitting.webp' },
|
||||
paletteSection: 'structure',
|
||||
paletteOrder: 91,
|
||||
},
|
||||
|
||||
@@ -176,7 +176,7 @@ export const ductSegmentDefinition: NodeDefinition<typeof DuctSegmentNode> = {
|
||||
presentation: {
|
||||
label: 'Duct',
|
||||
description: 'HVAC duct run — polyline of round, rect, or flat-oval sections.',
|
||||
icon: { kind: 'url', src: '/icons/duct.png' },
|
||||
icon: { kind: 'url', src: '/icons/duct.webp' },
|
||||
paletteSection: 'structure',
|
||||
paletteOrder: 90,
|
||||
},
|
||||
|
||||
@@ -89,7 +89,7 @@ export const ductTerminalDefinition: NodeDefinition<typeof DuctTerminalNode> = {
|
||||
label: 'Register',
|
||||
description:
|
||||
'Duct terminal — supply register, ceiling diffuser, or return grille. Duct runs end at its collar.',
|
||||
icon: { kind: 'url', src: '/icons/registers.png' },
|
||||
icon: { kind: 'url', src: '/icons/registers.webp' },
|
||||
paletteSection: 'structure',
|
||||
paletteOrder: 93,
|
||||
},
|
||||
|
||||
@@ -256,7 +256,7 @@ export const elevatorDefinition: NodeDefinition<typeof ElevatorNode> = {
|
||||
presentation: {
|
||||
label: 'Elevator',
|
||||
description: 'A multi-level elevator shaft with configurable openings per level.',
|
||||
icon: { kind: 'url', src: '/icons/wallcut.png' },
|
||||
icon: { kind: 'url', src: '/icons/wallcut.webp' },
|
||||
paletteSection: 'structure',
|
||||
paletteOrder: 80,
|
||||
},
|
||||
|
||||
@@ -471,7 +471,7 @@ export default function ElevatorPanel() {
|
||||
|
||||
return (
|
||||
<PanelWrapper
|
||||
icon="/icons/elevator.png"
|
||||
icon="/icons/elevator.webp"
|
||||
onClose={handleClose}
|
||||
title={node.name || 'Elevator'}
|
||||
width={300}
|
||||
|
||||
@@ -160,7 +160,7 @@ export const eyebrowVentDefinition: NodeDefinition<typeof EyebrowVentNode> = {
|
||||
presentation: {
|
||||
label: 'Eyebrow Vent',
|
||||
description: 'Low curved lens-shaped roof vent with a louvered front.',
|
||||
icon: { kind: 'url', src: '/icons/roof.png' },
|
||||
icon: { kind: 'url', src: '/icons/roof.webp' },
|
||||
paletteSection: 'structure',
|
||||
paletteOrder: 123,
|
||||
},
|
||||
|
||||
@@ -139,7 +139,7 @@ export default function EyebrowVentPanel() {
|
||||
|
||||
return (
|
||||
<PanelWrapper
|
||||
icon="/icons/roof.png"
|
||||
icon="/icons/roof.webp"
|
||||
onBack={node.roofSegmentId ? handleBack : undefined}
|
||||
onClose={handleClose}
|
||||
title={node.name || 'Eyebrow Vent'}
|
||||
|
||||
@@ -230,7 +230,7 @@ export const fenceDefinition: NodeDefinition<typeof FenceNode> = {
|
||||
presentation: {
|
||||
label: 'Fence',
|
||||
description: 'A straight or curved fence segment with configurable posts and infill.',
|
||||
icon: { kind: 'url', src: '/icons/fence.png' },
|
||||
icon: { kind: 'url', src: '/icons/fence.webp' },
|
||||
paletteSection: 'structure',
|
||||
paletteOrder: 20,
|
||||
},
|
||||
|
||||
@@ -45,7 +45,7 @@ export const guideDefinition: NodeDefinition<typeof GuideNode> = {
|
||||
presentation: {
|
||||
label: 'Guide',
|
||||
description: 'A measurement / reference annotation (linear, area, or arc).',
|
||||
icon: { kind: 'url', src: '/icons/blueprint.png' },
|
||||
icon: { kind: 'url', src: '/icons/blueprint.webp' },
|
||||
paletteSection: 'site',
|
||||
paletteOrder: 30,
|
||||
},
|
||||
|
||||
@@ -181,7 +181,7 @@ export const gutterDefinition: NodeDefinition<typeof GutterNode> = {
|
||||
presentation: {
|
||||
label: 'Gutter',
|
||||
description: 'Rain-water channel running along the eave of a roof segment.',
|
||||
icon: { kind: 'url', src: '/icons/roof.png' },
|
||||
icon: { kind: 'url', src: '/icons/roof.webp' },
|
||||
paletteSection: 'structure',
|
||||
paletteOrder: 122,
|
||||
},
|
||||
|
||||
@@ -94,7 +94,7 @@ export const hvacEquipmentDefinition: NodeDefinition<typeof HvacEquipmentNode> =
|
||||
label: 'HVAC Unit',
|
||||
description:
|
||||
'Furnace, air handler, or condenser — duct runs connect to its supply/return collars.',
|
||||
icon: { kind: 'url', src: '/icons/HVAC.png' },
|
||||
icon: { kind: 'url', src: '/icons/HVAC.webp' },
|
||||
paletteSection: 'structure',
|
||||
paletteOrder: 92,
|
||||
},
|
||||
|
||||
@@ -320,7 +320,7 @@ export const itemDefinition: NodeDefinition<typeof ItemNode> = {
|
||||
presentation: {
|
||||
label: 'Item',
|
||||
description: 'A catalog-backed item (furniture, fixtures, decorations).',
|
||||
icon: { kind: 'url', src: '/icons/item.png' },
|
||||
icon: { kind: 'url', src: '/icons/item.webp' },
|
||||
paletteSection: 'furnish',
|
||||
paletteOrder: 10,
|
||||
},
|
||||
|
||||
@@ -100,7 +100,7 @@ export default function ItemPanel() {
|
||||
|
||||
return (
|
||||
<PanelWrapper
|
||||
icon={node.asset.thumbnail || '/icons/furniture.png'}
|
||||
icon={node.asset.thumbnail || '/icons/furniture.webp'}
|
||||
onClose={handleClose}
|
||||
title={node.name || node.asset.name}
|
||||
width={300}
|
||||
|
||||
@@ -51,7 +51,7 @@ export const levelDefinition: NodeDefinition<typeof LevelNode> = {
|
||||
presentation: {
|
||||
label: 'Level',
|
||||
description: 'A single floor of a building, holding walls / slabs / ceilings / items.',
|
||||
icon: { kind: 'url', src: '/icons/level.png' },
|
||||
icon: { kind: 'url', src: '/icons/level.webp' },
|
||||
paletteSection: 'site',
|
||||
paletteOrder: 7,
|
||||
},
|
||||
|
||||
@@ -120,7 +120,7 @@ export const linesetDefinition: NodeDefinition<typeof LinesetNode> = {
|
||||
label: 'Lineset',
|
||||
description:
|
||||
'Refrigerant lineset — copper suction + liquid pair joining a condenser to the indoor coil.',
|
||||
icon: { kind: 'url', src: '/icons/lineset.png' },
|
||||
icon: { kind: 'url', src: '/icons/lineset.webp' },
|
||||
paletteSection: 'structure',
|
||||
paletteOrder: 93,
|
||||
},
|
||||
|
||||
@@ -112,7 +112,7 @@ export const liquidLineDefinition: NodeDefinition<typeof LiquidLineNode> = {
|
||||
label: 'Liquid Line',
|
||||
description:
|
||||
'Standalone refrigerant liquid line — a thin bare-copper run; Follow mode traces an existing lineset.',
|
||||
icon: { kind: 'url', src: '/icons/lineset.png' },
|
||||
icon: { kind: 'url', src: '/icons/lineset.webp' },
|
||||
paletteSection: 'structure',
|
||||
paletteOrder: 94,
|
||||
},
|
||||
|
||||
@@ -93,7 +93,7 @@ export const pipeFittingDefinition: NodeDefinition<typeof PipeFittingNode> = {
|
||||
label: 'Pipe Fitting',
|
||||
description: 'DWV joint — elbow bend, 45° wye, or sanitary tee.',
|
||||
// Reuses the duct-fitting artwork — DWV fittings read the same in the UI.
|
||||
icon: { kind: 'url', src: '/icons/duct-fitting.png' },
|
||||
icon: { kind: 'url', src: '/icons/duct-fitting.webp' },
|
||||
paletteSection: 'structure',
|
||||
paletteOrder: 96,
|
||||
hidden: true,
|
||||
|
||||
@@ -118,7 +118,7 @@ export const pipeSegmentDefinition: NodeDefinition<typeof PipeSegmentNode> = {
|
||||
label: 'DWV Pipe',
|
||||
description:
|
||||
'Drain / waste / vent pipe run — waste lines fall at ¼″ per foot, vents run level or vertical.',
|
||||
icon: { kind: 'url', src: '/icons/dwv-pipes.png' },
|
||||
icon: { kind: 'url', src: '/icons/dwv-pipes.webp' },
|
||||
paletteSection: 'structure',
|
||||
paletteOrder: 95,
|
||||
},
|
||||
|
||||
@@ -205,7 +205,7 @@ export const ridgeVentDefinition: NodeDefinition<typeof RidgeVentNode> = {
|
||||
presentation: {
|
||||
label: 'Ridge Vent',
|
||||
description: 'Ventilation strip running along the ridge of a roof segment.',
|
||||
icon: { kind: 'url', src: '/icons/roof.png' },
|
||||
icon: { kind: 'url', src: '/icons/roof.webp' },
|
||||
paletteSection: 'structure',
|
||||
paletteOrder: 121,
|
||||
},
|
||||
|
||||
@@ -128,7 +128,7 @@ export default function RidgeVentPanel() {
|
||||
|
||||
return (
|
||||
<PanelWrapper
|
||||
icon="/icons/roof.png"
|
||||
icon="/icons/roof.webp"
|
||||
onBack={node.roofSegmentId ? handleBack : undefined}
|
||||
onClose={handleClose}
|
||||
title={node.name || 'Ridge Vent'}
|
||||
|
||||
@@ -313,7 +313,7 @@ export const roofSegmentDefinition: NodeDefinition<typeof RoofSegmentNode> = {
|
||||
presentation: {
|
||||
label: 'Roof Segment',
|
||||
description: 'A single pitched plane of a parent roof.',
|
||||
icon: { kind: 'url', src: '/icons/roof.png' },
|
||||
icon: { kind: 'url', src: '/icons/roof.webp' },
|
||||
paletteSection: 'structure',
|
||||
paletteOrder: 101,
|
||||
},
|
||||
|
||||
@@ -121,7 +121,7 @@ export default function RoofSegmentPanel() {
|
||||
|
||||
return (
|
||||
<PanelWrapper
|
||||
icon="/icons/roof.png"
|
||||
icon="/icons/roof.webp"
|
||||
onBack={handleBack}
|
||||
onClose={handleClose}
|
||||
title={node.name || 'Roof Segment'}
|
||||
|
||||
@@ -179,7 +179,7 @@ export const roofDefinition: NodeDefinition<typeof RoofNode> = {
|
||||
presentation: {
|
||||
label: 'Roof',
|
||||
description: 'A pitched / hip / gable roof composed of one or more segments.',
|
||||
icon: { kind: 'url', src: '/icons/roof.png' },
|
||||
icon: { kind: 'url', src: '/icons/roof.webp' },
|
||||
paletteSection: 'structure',
|
||||
paletteOrder: 100,
|
||||
},
|
||||
|
||||
@@ -249,7 +249,7 @@ export default function RoofPanel() {
|
||||
|
||||
return (
|
||||
<PanelWrapper
|
||||
icon="/icons/roof.png"
|
||||
icon="/icons/roof.webp"
|
||||
onClose={handleClose}
|
||||
title={node.name || 'Roof'}
|
||||
width={300}
|
||||
|
||||
@@ -42,7 +42,7 @@ export const scanDefinition: NodeDefinition<typeof ScanNode> = {
|
||||
presentation: {
|
||||
label: 'Scan',
|
||||
description: 'A captured mesh (LiDAR / photogrammetry) imported as a scene reference.',
|
||||
icon: { kind: 'url', src: '/icons/mesh.png' },
|
||||
icon: { kind: 'url', src: '/icons/mesh.webp' },
|
||||
paletteSection: 'site',
|
||||
paletteOrder: 40,
|
||||
},
|
||||
|
||||
@@ -265,7 +265,7 @@ export const shelfDefinition: NodeDefinition<typeof ShelfNode> = {
|
||||
presentation: {
|
||||
label: 'Shelf',
|
||||
description: 'A configurable shelving unit. Items host on each row.',
|
||||
icon: { kind: 'url', src: '/icons/shelf.png' },
|
||||
icon: { kind: 'url', src: '/icons/shelf.webp' },
|
||||
paletteSection: 'furnish',
|
||||
paletteOrder: 30,
|
||||
},
|
||||
|
||||
@@ -41,7 +41,7 @@ export const siteDefinition: NodeDefinition<typeof SiteNode> = {
|
||||
presentation: {
|
||||
label: 'Site',
|
||||
description: 'The top-level container holding buildings, zones, and the property boundary.',
|
||||
icon: { kind: 'url', src: '/icons/site-flag.png' },
|
||||
icon: { kind: 'url', src: '/icons/site-flag.webp' },
|
||||
paletteSection: 'site',
|
||||
paletteOrder: 5,
|
||||
},
|
||||
|
||||
@@ -271,7 +271,7 @@ export const skylightDefinition: NodeDefinition<typeof SkylightNode> = {
|
||||
presentation: {
|
||||
label: 'Skylight',
|
||||
description: 'Framed glass opening on a roof segment.',
|
||||
icon: { kind: 'url', src: '/icons/roof.png' },
|
||||
icon: { kind: 'url', src: '/icons/roof.webp' },
|
||||
paletteSection: 'structure',
|
||||
paletteOrder: 124,
|
||||
},
|
||||
|
||||
@@ -258,7 +258,7 @@ export default function SkylightPanel() {
|
||||
|
||||
return (
|
||||
<PanelWrapper
|
||||
icon="/icons/roof.png"
|
||||
icon="/icons/roof.webp"
|
||||
onBack={node.roofSegmentId ? handleBack : undefined}
|
||||
onClose={handleClose}
|
||||
title={node.name || 'Skylight'}
|
||||
|
||||
@@ -208,7 +208,7 @@ export const slabDefinition: NodeDefinition<typeof SlabNode> = {
|
||||
presentation: {
|
||||
label: 'Slab',
|
||||
description: 'A polygon-bounded floor surface that hosts items on top.',
|
||||
icon: { kind: 'url', src: '/icons/floor.png' },
|
||||
icon: { kind: 'url', src: '/icons/floor.webp' },
|
||||
paletteSection: 'structure',
|
||||
paletteOrder: 30,
|
||||
},
|
||||
|
||||
@@ -152,7 +152,7 @@ export function SlabPanel() {
|
||||
|
||||
return (
|
||||
<PanelWrapper
|
||||
icon="/icons/floor.png"
|
||||
icon="/icons/floor.webp"
|
||||
onClose={handleClose}
|
||||
title={node.name || 'Slab'}
|
||||
width={320}
|
||||
|
||||
@@ -272,7 +272,7 @@ export const solarPanelDefinition: NodeDefinition<typeof SolarPanelNode> = {
|
||||
presentation: {
|
||||
label: 'Solar Panel',
|
||||
description: 'Grid of photovoltaic panels mounted on a roof segment.',
|
||||
icon: { kind: 'url', src: '/icons/roof.png' },
|
||||
icon: { kind: 'url', src: '/icons/roof.webp' },
|
||||
paletteSection: 'structure',
|
||||
paletteOrder: 123,
|
||||
},
|
||||
|
||||
@@ -181,7 +181,7 @@ export default function SolarPanelPanel() {
|
||||
|
||||
return (
|
||||
<PanelWrapper
|
||||
icon="/icons/roof.png"
|
||||
icon="/icons/roof.webp"
|
||||
onBack={node.roofSegmentId ? handleBack : undefined}
|
||||
onClose={handleClose}
|
||||
title={node.name || 'Solar Panel'}
|
||||
|
||||
@@ -107,7 +107,7 @@ export const spawnDefinition: NodeDefinition<typeof SpawnNode> = {
|
||||
presentation: {
|
||||
label: 'Spawn Point',
|
||||
description: 'Player or camera origin within a level. One per level.',
|
||||
icon: { kind: 'url', src: '/icons/spawn-point.png' },
|
||||
icon: { kind: 'url', src: '/icons/spawn-point.webp' },
|
||||
paletteSection: 'structure',
|
||||
paletteOrder: 90, // bottom of structure list — matches legacy palette order
|
||||
},
|
||||
|
||||
@@ -95,7 +95,7 @@ export default function SpawnPanel() {
|
||||
|
||||
return (
|
||||
<PanelWrapper
|
||||
icon="/icons/spawn-point.png"
|
||||
icon="/icons/spawn-point.webp"
|
||||
onClose={handleClose}
|
||||
title="Spawn Point"
|
||||
width={300}
|
||||
|
||||
@@ -140,7 +140,7 @@ export const stairSegmentDefinition: NodeDefinition<typeof StairSegmentNode> = {
|
||||
presentation: {
|
||||
label: 'Stair Segment',
|
||||
description: 'A single flight of a parent stair.',
|
||||
icon: { kind: 'url', src: '/icons/stairs.png' },
|
||||
icon: { kind: 'url', src: '/icons/stairs.webp' },
|
||||
paletteSection: 'structure',
|
||||
paletteOrder: 111,
|
||||
},
|
||||
|
||||
@@ -121,7 +121,7 @@ export default function StairSegmentPanel() {
|
||||
|
||||
return (
|
||||
<PanelWrapper
|
||||
icon="/icons/stairs.png"
|
||||
icon="/icons/stairs.webp"
|
||||
onBack={handleBack}
|
||||
onClose={handleClose}
|
||||
title={node.name || 'Stair Segment'}
|
||||
|
||||
@@ -496,7 +496,7 @@ export const stairDefinition: NodeDefinition<typeof StairNode> = {
|
||||
label: 'Stair',
|
||||
description:
|
||||
'A stair composed of one or more flights with configurable treads, risers, railings.',
|
||||
icon: { kind: 'url', src: '/icons/stairs.png' },
|
||||
icon: { kind: 'url', src: '/icons/stairs.webp' },
|
||||
paletteSection: 'structure',
|
||||
paletteOrder: 110,
|
||||
},
|
||||
|
||||
@@ -226,7 +226,7 @@ export default function StairPanel() {
|
||||
|
||||
return (
|
||||
<PanelWrapper
|
||||
icon="/icons/stairs.png"
|
||||
icon="/icons/stairs.webp"
|
||||
onClose={handleClose}
|
||||
title={node.name || 'Staircase'}
|
||||
width={300}
|
||||
|
||||
@@ -126,7 +126,7 @@ export const turbineVentDefinition: NodeDefinition<typeof TurbineVentNode> = {
|
||||
presentation: {
|
||||
label: 'Turbine Vent',
|
||||
description: 'Wind-driven spinning whirlybird exhaust vent for a roof slope.',
|
||||
icon: { kind: 'url', src: '/icons/roof.png' },
|
||||
icon: { kind: 'url', src: '/icons/roof.webp' },
|
||||
paletteSection: 'structure',
|
||||
paletteOrder: 121,
|
||||
},
|
||||
|
||||
@@ -161,7 +161,7 @@ export default function TurbineVentPanel() {
|
||||
|
||||
return (
|
||||
<PanelWrapper
|
||||
icon="/icons/roof.png"
|
||||
icon="/icons/roof.webp"
|
||||
onBack={node.roofSegmentId ? handleBack : undefined}
|
||||
onClose={handleClose}
|
||||
title={node.name || 'Turbine Vent'}
|
||||
|
||||
@@ -115,7 +115,7 @@ export const wallDefinition: NodeDefinition<typeof WallNode> = {
|
||||
presentation: {
|
||||
label: 'Wall',
|
||||
description: 'A straight or curved wall segment. Hosts doors, windows, and wall-mounted items.',
|
||||
icon: { kind: 'url', src: '/icons/wall.png' },
|
||||
icon: { kind: 'url', src: '/icons/wall.webp' },
|
||||
paletteSection: 'structure',
|
||||
paletteOrder: 10,
|
||||
},
|
||||
|
||||
@@ -137,7 +137,7 @@ export default function WallPanel() {
|
||||
|
||||
return (
|
||||
<PanelWrapper
|
||||
icon="/icons/wall.png"
|
||||
icon="/icons/wall.webp"
|
||||
onClose={handleClose}
|
||||
title={node.name || 'Wall'}
|
||||
width={280}
|
||||
|
||||
@@ -229,7 +229,7 @@ export const windowDefinition: NodeDefinition<typeof WindowNode> = {
|
||||
presentation: {
|
||||
label: 'Window',
|
||||
description: 'A window cut into a wall. Animated open/close for opening windows.',
|
||||
icon: { kind: 'url', src: '/icons/window.png' },
|
||||
icon: { kind: 'url', src: '/icons/window.webp' },
|
||||
paletteSection: 'structure',
|
||||
paletteOrder: 60,
|
||||
},
|
||||
|
||||
@@ -383,7 +383,7 @@ export default function WindowPanel() {
|
||||
|
||||
return (
|
||||
<PanelWrapper
|
||||
icon="/icons/window.png"
|
||||
icon="/icons/window.webp"
|
||||
onClose={handleClose}
|
||||
title={node.name || 'Window'}
|
||||
width={320}
|
||||
|
||||
@@ -66,7 +66,7 @@ export const zoneDefinition: NodeDefinition<typeof ZoneNode> = {
|
||||
presentation: {
|
||||
label: 'Zone',
|
||||
description: 'A polygonal site zone (lawn, water, paving) with a TSL gradient material.',
|
||||
icon: { kind: 'url', src: '/icons/zone.png' },
|
||||
icon: { kind: 'url', src: '/icons/zone.webp' },
|
||||
paletteSection: 'site',
|
||||
paletteOrder: 20,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user