@@ -1196,7 +1186,7 @@ export default function Editor({
>
)}
-
+ >
)
}
@@ -1207,54 +1197,52 @@ export default function Editor({
const overlayLeft = LAYOUT_PADDING + (isSidebarCollapsed ? 8 : sidebarWidth) + LAYOUT_GAP
return (
-
-
- {showLoader && (
-
-
-
- )}
+
+ {showLoader && (
+
+
+
+ )}
- {!isLoading && isPreviewMode ? (
- <>
-
useEditor.getState().setPreviewMode(false)} />
- {previewViewerContent}
- >
- ) : (
- <>
- {/* Sidebar */}
-
-
-
-
- {/* Viewer area */}
- {viewerCanvas}
-
- {/* Fixed UI overlays scoped to the viewer area */}
-
-
-
-
-
-
- {isFirstPersonMode && (
- useEditor.getState().setFirstPersonMode(false)} />
- )}
-
- >
- )}
-
-
+ {!isLoading && isPreviewMode ? (
+ <>
+
useEditor.getState().setPreviewMode(false)} />
+ {previewViewerContent}
+ >
+ ) : (
+ <>
+ {/* Sidebar */}
+
+
+
+
+ {/* Viewer area */}
+ {viewerCanvas}
+
+ {/* Fixed UI overlays scoped to the viewer area */}
+
+
+
+
+
+
+ {isFirstPersonMode && (
+ useEditor.getState().setFirstPersonMode(false)} />
+ )}
+
+ >
+ )}
+
)
}
diff --git a/packages/editor/src/components/editor/preset-thumbnail-generator.tsx b/packages/editor/src/components/editor/preset-thumbnail-generator.tsx
deleted file mode 100644
index d0a722591..000000000
--- a/packages/editor/src/components/editor/preset-thumbnail-generator.tsx
+++ /dev/null
@@ -1,125 +0,0 @@
-'use client'
-
-import { emitter, sceneRegistry } from '@pascal-app/core'
-import { useThree } from '@react-three/fiber'
-import { useCallback, useEffect } from 'react'
-import * as THREE from 'three'
-import { usePresetsAdapter } from '../../contexts/presets-context'
-
-const THUMBNAIL_SIZE = 1080
-const CAMERA_FOV = 45
-
-export const PresetThumbnailGenerator = () => {
- const gl = useThree((state) => state.gl)
- const scene = useThree((state) => state.scene)
- const adapter = usePresetsAdapter()
-
- const generate = useCallback(
- async ({ presetId, nodeId }: { presetId: string; nodeId: string }) => {
- const target = sceneRegistry.nodes.get(nodeId)
- if (!target) {
- console.error('❌ PresetThumbnail: node not found', nodeId)
- return
- }
-
- // Compute each mesh's transform relative to the target node (cancels world
- // position/rotation), so the item is always rendered at origin with a known
- // neutral orientation regardless of where it's placed in the scene.
- target.updateWorldMatrix(true, true)
- const targetInverse = new THREE.Matrix4().copy(target.matrixWorld).invert()
- const relMatrix = new THREE.Matrix4()
-
- const clones: THREE.Object3D[] = []
- target.traverse((obj) => {
- if (
- !(obj instanceof THREE.Mesh || obj instanceof THREE.Line || obj instanceof THREE.Points)
- )
- return
- const c = obj.clone(false) // shallow clone: copies geometry, material, visible — no children
- relMatrix.multiplyMatrices(targetInverse, obj.matrixWorld)
- relMatrix.decompose(c.position, c.quaternion, c.scale)
- scene.add(c)
- clones.push(c)
- })
-
- if (clones.length === 0) {
- console.error('❌ PresetThumbnail: no renderable objects found', nodeId)
- return
- }
-
- // Combined bounding box across all clones
- const box = new THREE.Box3()
- for (const c of clones) box.expandByObject(c)
-
- if (box.isEmpty()) {
- for (const c of clones) scene.remove(c)
- console.error('❌ PresetThumbnail: empty bounding box', nodeId)
- return
- }
-
- const sphere = new THREE.Sphere()
- box.getBoundingSphere(sphere)
-
- // Camera: aspect matches canvas (center-cropped to square after render)
- const { width, height } = gl.domElement
- const camera = new THREE.PerspectiveCamera(CAMERA_FOV, width / height, 0.01, 1000)
- const dir = new THREE.Vector3(-0.5, 0.5, 0.5).normalize()
- const fovRad = (CAMERA_FOV * Math.PI) / 180
- const dist = (sphere.radius / Math.tan(fovRad / 2)) * 1.3
- camera.position.copy(sphere.center).addScaledVector(dir, dist)
- camera.lookAt(sphere.center)
- camera.updateProjectionMatrix()
-
- // Hide all scene geometry except the clones — leave lights, cameras, etc. intact
- const cloneSet = new Set(clones)
- const snapshot = new Map()
- scene.traverse((obj) => {
- if (cloneSet.has(obj)) return
- if (
- !(obj instanceof THREE.Mesh || obj instanceof THREE.Line || obj instanceof THREE.Points)
- )
- return
- snapshot.set(obj, obj.visible)
- obj.visible = false
- })
-
- gl.render(scene, camera)
-
- // Restore visibility and remove clones
- snapshot.forEach((wasVisible, obj) => {
- obj.visible = wasVisible
- })
- for (const c of clones) scene.remove(c)
-
- // Center-crop to square and scale to THUMBNAIL_SIZE
- const minDim = Math.min(width, height)
- const sx = Math.round((width - minDim) / 2)
- const sy = Math.round((height - minDim) / 2)
- const offscreen = document.createElement('canvas')
- offscreen.width = THUMBNAIL_SIZE
- offscreen.height = THUMBNAIL_SIZE
- const ctx = offscreen.getContext('2d')!
- ctx.drawImage(gl.domElement, sx, sy, minDim, minDim, 0, 0, THUMBNAIL_SIZE, THUMBNAIL_SIZE)
-
- offscreen.toBlob(async (blob) => {
- if (!blob) {
- console.error('❌ PresetThumbnail: failed to create blob')
- return
- }
- if (!adapter.uploadPresetThumbnail) return
- const thumbnailUrl = await adapter.uploadPresetThumbnail(presetId, blob)
- if (thumbnailUrl) {
- emitter.emit('preset:thumbnail-updated', { presetId, thumbnailUrl })
- }
- }, 'image/png')
- },
- [gl, scene, adapter],
- )
-
- useEffect(() => {
- emitter.on('preset:generate-thumbnail', generate)
- return () => emitter.off('preset:generate-thumbnail', generate)
- }, [generate])
-
- return null
-}
diff --git a/packages/editor/src/components/ui/panels/presets/presets-popover.tsx b/packages/editor/src/components/ui/panels/presets/presets-popover.tsx
deleted file mode 100644
index c11005e88..000000000
--- a/packages/editor/src/components/ui/panels/presets/presets-popover.tsx
+++ /dev/null
@@ -1,511 +0,0 @@
-'use client'
-
-import { emitter } from '@pascal-app/core'
-import {
- BookMarked,
- Check,
- Globe,
- GlobeLock,
- MoreHorizontal,
- Pencil,
- Plus,
- Save,
- Trash2,
- Users,
- X,
-} from 'lucide-react'
-import { useCallback, useEffect, useState } from 'react'
-import type { PresetsTab } from '../../../../contexts/presets-context'
-import { cn } from '../../../../lib/utils'
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuTrigger,
-} from '../../primitives/dropdown-menu'
-import { Popover, PopoverContent, PopoverTrigger } from '../../primitives/popover'
-
-export type PresetType = 'door' | 'window'
-
-export interface PresetData {
- id: string
- type: string
- name: string
- data: Record
- thumbnail_url: string | null
- user_id: string | null
- is_community: boolean
- created_at: string
-}
-
-interface PresetsPopoverProps {
- type: PresetType
- children: React.ReactNode
- isAuthenticated?: boolean
- tabs?: PresetsTab[]
- onFetchPresets: (tab: PresetsTab) => Promise
- onApply: (data: Record) => void
- onSave: (name: string) => Promise
- onOverwrite: (id: string) => Promise
- onRename: (id: string, name: string) => Promise
- onDelete: (id: string) => Promise
- onToggleCommunity?: (id: string, current: boolean) => Promise
-}
-
-export function PresetsPopover({
- type,
- onApply,
- onSave,
- onOverwrite,
- onFetchPresets,
- onRename,
- onDelete,
- onToggleCommunity,
- children,
- isAuthenticated = false,
- tabs = ['community', 'mine'],
-}: PresetsPopoverProps) {
- const defaultTab = tabs[0] ?? 'mine'
- const [open, setOpen] = useState(false)
- const [tab, setTab] = useState(defaultTab)
- const [presets, setPresets] = useState([])
- const [loading, setLoading] = useState(false)
-
- const [showSaveInput, setShowSaveInput] = useState(false)
- const [saveName, setSaveName] = useState('')
- const [saving, setSaving] = useState(false)
-
- const [renamingId, setRenamingId] = useState(null)
- const [renameValue, setRenameValue] = useState('')
- const [deletingId, setDeletingId] = useState(null)
- const [overwrittenId, setOverwrittenId] = useState(null)
-
- const fetchPresets = useCallback(async () => {
- setLoading(true)
- try {
- const data = await onFetchPresets(tab)
- setPresets(data)
- } finally {
- setLoading(false)
- }
- }, [onFetchPresets, tab])
-
- useEffect(() => {
- if (open) fetchPresets()
- }, [open, fetchPresets])
-
- useEffect(() => {
- if (!isAuthenticated && tab === 'mine') setTab(defaultTab)
- }, [isAuthenticated, tab, defaultTab])
-
- useEffect(() => {
- const handler = ({ presetId, thumbnailUrl }: { presetId: string; thumbnailUrl: string }) => {
- setPresets((prev) =>
- prev.map((p) => (p.id === presetId ? { ...p, thumbnail_url: thumbnailUrl } : p)),
- )
- }
- emitter.on('preset:thumbnail-updated', handler)
- return () => emitter.off('preset:thumbnail-updated', handler)
- }, [])
-
- const handleSaveNew = async () => {
- if (!saveName.trim()) return
- setSaving(true)
- try {
- await onSave(saveName.trim())
- setSaveName('')
- setShowSaveInput(false)
- if (tab === 'mine') fetchPresets()
- else setTab('mine')
- } finally {
- setSaving(false)
- }
- }
-
- const handleRename = async (id: string) => {
- if (!renameValue.trim()) return
- await onRename(id, renameValue.trim())
- setPresets((prev) => prev.map((p) => (p.id === id ? { ...p, name: renameValue.trim() } : p)))
- setRenamingId(null)
- }
-
- const handleDelete = async (id: string) => {
- await onDelete(id)
- setPresets((prev) => prev.filter((p) => p.id !== id))
- setDeletingId(null)
- }
-
- const handleOverwrite = async (id: string) => {
- await onOverwrite(id)
- setOverwrittenId(id)
- setTimeout(() => setOverwrittenId(null), 1500)
- }
-
- const handleToggleCommunity = async (id: string, current: boolean) => {
- if (!onToggleCommunity) return
- await onToggleCommunity(id, current)
- setPresets((prev) => prev.map((p) => (p.id === id ? { ...p, is_community: !current } : p)))
- }
-
- const showTabs = tabs.length > 1
-
- return (
-
- {children}
-
-
-
-
-
- {type === 'door' ? 'Door' : 'Window'} Presets
-
-
- {isAuthenticated && (
-
{
- setShowSaveInput((v) => !v)
- setSaveName('')
- }}
- type="button"
- >
-
- Save new
-
- )}
-
-
- {showSaveInput && (
-
- setSaveName(e.target.value)}
- onKeyDown={(e) => {
- if (e.key === 'Enter') handleSaveNew()
- if (e.key === 'Escape') {
- setShowSaveInput(false)
- setSaveName('')
- }
- }}
- placeholder="Preset name…"
- value={saveName}
- />
-
-
-
- {
- setShowSaveInput(false)
- setSaveName('')
- }}
- type="button"
- >
-
-
-
- )}
-
- {showTabs && (
-
- {tabs.includes('community') && (
- setTab('community')}>
-
- Community
-
- )}
- {tabs.includes('mine') && (
- {
- if (isAuthenticated) setTab('mine')
- }}
- >
-
- My presets
-
- )}
-
- )}
-
-
- {loading ? (
-
- ) : presets.length === 0 ? (
-
- ) : (
-
- {presets.map((preset) => (
- {
- onApply(preset.data)
- setOpen(false)
- }}
- onDeleteCancel={() => setDeletingId(null)}
- onDeleteConfirm={() => handleDelete(preset.id)}
- onDeleteRequest={() => setDeletingId(preset.id)}
- onOverwrite={() => handleOverwrite(preset.id)}
- onRenameCancel={() => setRenamingId(null)}
- onRenameChange={setRenameValue}
- onRenameConfirm={() => handleRename(preset.id)}
- onStartRename={() => {
- setRenamingId(preset.id)
- setRenameValue(preset.name)
- }}
- onToggleCommunity={() => handleToggleCommunity(preset.id, preset.is_community)}
- overwrittenId={overwrittenId}
- preset={preset}
- renameValue={renameValue}
- renamingId={renamingId}
- showCommunityToggle={!!onToggleCommunity}
- />
- ))}
-
- )}
-
-
-
- )
-}
-
-function TabButton({
- active,
- onClick,
- disabled,
- children,
-}: {
- active: boolean
- onClick: () => void
- disabled?: boolean
- children: React.ReactNode
-}) {
- return (
-
- {children}
-
- )
-}
-
-function EmptyState({ tab, isAuthenticated }: { tab: PresetsTab; isAuthenticated: boolean }) {
- return (
-
-
-
- {tab === 'community'
- ? 'No community presets yet.'
- : isAuthenticated
- ? 'No presets saved yet. Use "Save new" to save the current configuration.'
- : 'Sign in to save and view your presets.'}
-
-
- )
-}
-
-interface PresetRowProps {
- preset: PresetData
- isMine: boolean
- showCommunityToggle: boolean
- renamingId: string | null
- renameValue: string
- deletingId: string | null
- overwrittenId: string | null
- onApply: () => void
- onOverwrite: () => void
- onToggleCommunity: () => void
- onStartRename: () => void
- onRenameChange: (v: string) => void
- onRenameConfirm: () => void
- onRenameCancel: () => void
- onDeleteRequest: () => void
- onDeleteConfirm: () => void
- onDeleteCancel: () => void
-}
-
-function PresetRow({
- preset,
- isMine,
- showCommunityToggle,
- renamingId,
- renameValue,
- deletingId,
- overwrittenId,
- onApply,
- onOverwrite,
- onToggleCommunity,
- onStartRename,
- onRenameChange,
- onRenameConfirm,
- onRenameCancel,
- onDeleteRequest,
- onDeleteConfirm,
- onDeleteCancel,
-}: PresetRowProps) {
- const isRenaming = renamingId === preset.id
- const isDeleting = deletingId === preset.id
- const justOverwritten = overwrittenId === preset.id
-
- if (isDeleting) {
- return (
-
- Delete "{preset.name}"?
-
-
- Delete
-
-
- Cancel
-
-
-
- )
- }
-
- if (isRenaming) {
- return (
-
- onRenameChange(e.target.value)}
- onKeyDown={(e) => {
- if (e.key === 'Enter') onRenameConfirm()
- if (e.key === 'Escape') onRenameCancel()
- }}
- value={renameValue}
- />
-
-
-
-
-
-
-
- )
- }
-
- return (
-
-
- {preset.thumbnail_url ? (
- // eslint-disable-next-line @next/next/no-img-element
-
- ) : (
-
- )}
-
-
-
-
- {preset.name}
-
- {isMine && preset.is_community && (
-
- )}
-
-
- {new Date(preset.created_at).toLocaleDateString()}
-
-
- {isMine && (
-
-
-
- {justOverwritten ? (
-
- ) : (
-
- )}
-
-
-
-
-
- Update with current
-
- {showCommunityToggle && (
-
- {preset.is_community ? (
- <>
-
- Remove from community
- >
- ) : (
- <>
-
- Share with community
- >
- )}
-
- )}
-
-
- Rename
-
-
-
- Delete
-
-
-
- )}
-
- )
-}
diff --git a/packages/editor/src/contexts/presets-context.tsx b/packages/editor/src/contexts/presets-context.tsx
deleted file mode 100644
index dd646f19f..000000000
--- a/packages/editor/src/contexts/presets-context.tsx
+++ /dev/null
@@ -1,121 +0,0 @@
-'use client'
-
-import { createContext, useContext } from 'react'
-import type { PresetData, PresetType } from '../components/ui/panels/presets/presets-popover'
-
-export type { PresetData, PresetType }
-
-export type PresetsTab = 'community' | 'mine'
-
-export interface PresetsAdapter {
- /** Tabs to show. Default: both. Standalone passes ['mine']. */
- tabs?: PresetsTab[]
- isAuthenticated?: boolean
- fetchPresets: (type: PresetType, tab: PresetsTab) => Promise
- savePreset: (
- type: PresetType,
- name: string,
- data: Record,
- ) => Promise
- overwritePreset: (type: PresetType, id: string, data: Record) => Promise
- renamePreset: (id: string, name: string) => Promise
- deletePreset: (id: string) => Promise
- togglePresetCommunity?: (id: string, current: boolean) => Promise
- uploadPresetThumbnail?: (presetId: string, blob: Blob) => Promise
-}
-
-const PRESETS_KEY = (type: string) => `pascal-presets-${type}`
-
-export const localStoragePresetsAdapter: PresetsAdapter = {
- tabs: ['mine'],
- isAuthenticated: true,
-
- fetchPresets: async (type, tab) => {
- if (tab === 'community') return []
- try {
- const raw = localStorage.getItem(PRESETS_KEY(type))
- return raw ? (JSON.parse(raw) as PresetData[]) : []
- } catch {
- return []
- }
- },
-
- savePreset: async (type, name, data) => {
- try {
- const id = Math.random().toString(36).slice(2, 10)
- const raw = localStorage.getItem(PRESETS_KEY(type))
- const presets: PresetData[] = raw ? JSON.parse(raw) : []
- presets.push({
- id,
- type,
- name,
- data,
- thumbnail_url: null,
- user_id: null,
- is_community: false,
- created_at: new Date().toISOString(),
- })
- localStorage.setItem(PRESETS_KEY(type), JSON.stringify(presets))
- return id
- } catch {
- return null
- }
- },
-
- overwritePreset: async (type, id, data) => {
- try {
- const raw = localStorage.getItem(PRESETS_KEY(type))
- if (!raw) return
- const presets: PresetData[] = JSON.parse(raw)
- localStorage.setItem(
- PRESETS_KEY(type),
- JSON.stringify(presets.map((p) => (p.id === id ? { ...p, data } : p))),
- )
- } catch {}
- },
-
- renamePreset: async (id, name) => {
- for (const type of ['door', 'window']) {
- try {
- const raw = localStorage.getItem(PRESETS_KEY(type))
- if (!raw) continue
- const presets: PresetData[] = JSON.parse(raw)
- localStorage.setItem(
- PRESETS_KEY(type),
- JSON.stringify(presets.map((p) => (p.id === id ? { ...p, name } : p))),
- )
- } catch {}
- }
- },
-
- deletePreset: async (id) => {
- for (const type of ['door', 'window']) {
- try {
- const raw = localStorage.getItem(PRESETS_KEY(type))
- if (!raw) continue
- const presets: PresetData[] = JSON.parse(raw)
- localStorage.setItem(PRESETS_KEY(type), JSON.stringify(presets.filter((p) => p.id !== id)))
- } catch {}
- }
- },
-}
-
-const PresetsContext = createContext(localStoragePresetsAdapter)
-
-export function PresetsProvider({
- adapter,
- children,
-}: {
- adapter?: PresetsAdapter
- children: React.ReactNode
-}) {
- return (
-
- {children}
-
- )
-}
-
-export function usePresetsAdapter(): PresetsAdapter {
- return useContext(PresetsContext)
-}
diff --git a/packages/editor/src/index.tsx b/packages/editor/src/index.tsx
index 476bf94c4..86c14ef86 100644
--- a/packages/editor/src/index.tsx
+++ b/packages/editor/src/index.tsx
@@ -131,9 +131,6 @@ export { CollectionsPopover } from './components/ui/panels/collections/collectio
// a kind-owned panel and need PanelWrapper for the chrome.
export { PanelWrapper } from './components/ui/panels/panel-wrapper'
export { ParametricInspector as Inspector } from './components/ui/panels/parametric-inspector'
-// Presets popover — used by kind-owned door / window panels for their
-// hardware / type / opening presets.
-export { PresetsPopover } from './components/ui/panels/presets/presets-popover'
export { PALETTE_COLORS } from './components/ui/primitives/color-dot'
export {
DropdownMenu,
@@ -153,8 +150,6 @@ export {
} from './components/ui/sidebar/panels/settings-panel'
export type { SitePanelProps } from './components/ui/sidebar/panels/site-panel'
export type { SidebarTab } from './components/ui/sidebar/tab-bar'
-export type { PresetsAdapter, PresetsTab } from './contexts/presets-context'
-export { PresetsProvider, usePresetsAdapter } from './contexts/presets-context'
export type { SaveStatus } from './hooks/use-auto-save'
// useDragAction is the React-side glue for the registry's DragAction
// primitive. Public so registry-driven kinds (Phase 5+ Stage D ports)
diff --git a/packages/nodes/src/door/panel.tsx b/packages/nodes/src/door/panel.tsx
index 57966d4ef..4bba801f2 100644
--- a/packages/nodes/src/door/panel.tsx
+++ b/packages/nodes/src/door/panel.tsx
@@ -1,29 +1,20 @@
'use client'
-import {
- type AnyNode,
- type AnyNodeId,
- DoorNode,
- emitter,
- useInteractive,
- useScene,
-} from '@pascal-app/core'
+import { type AnyNode, type AnyNodeId, DoorNode, useInteractive, useScene } from '@pascal-app/core'
import {
ActionButton,
ActionGroup,
cn,
PanelSection,
PanelWrapper,
- PresetsPopover,
SegmentedControl,
SliderControl,
ToggleControl,
triggerSFX,
useEditor,
- usePresetsAdapter,
} from '@pascal-app/editor'
import { useViewer } from '@pascal-app/viewer'
-import { BookMarked, Copy, DoorOpen, FlipHorizontal2, Move, Trash2 } from 'lucide-react'
+import { Copy, DoorOpen, FlipHorizontal2, Move, Trash2 } from 'lucide-react'
import { useCallback, useRef } from 'react'
const doorTypeOptions = [
@@ -151,8 +142,6 @@ export default function DoorPanel() {
value: unknown
} | null>(null)
- const adapter = usePresetsAdapter()
-
const node = useScene((s) =>
selectedId ? (s.nodes[selectedId as AnyNode['id']] as DoorNode | undefined) : undefined,
)
@@ -312,69 +301,6 @@ export default function DoorPanel() {
handleUpdate({ segments: updated })
}
- const getDoorPresetData = useCallback(() => {
- if (!node) return null
- return {
- doorCategory: node.doorCategory,
- doorType: node.doorType,
- leafCount: node.leafCount,
- operationState: node.operationState,
- slideDirection: node.slideDirection,
- trackStyle: node.trackStyle,
- garagePanelCount: node.garagePanelCount,
- width: node.width,
- height: node.height,
- frameThickness: node.frameThickness,
- frameDepth: node.frameDepth,
- openingKind: node.openingKind,
- openingShape: node.openingShape,
- openingRadiusMode: node.openingRadiusMode ?? 'all',
- openingTopRadii: node.openingTopRadii ?? [0.15, 0.15],
- cornerRadius: node.cornerRadius,
- archHeight: node.archHeight,
- openingRevealRadius: node.openingRevealRadius,
- contentPadding: node.contentPadding,
- hingesSide: node.hingesSide,
- swingDirection: node.swingDirection,
- threshold: node.threshold,
- thresholdHeight: node.thresholdHeight,
- handle: node.handle,
- handleHeight: node.handleHeight,
- handleSide: node.handleSide,
- doorCloser: node.doorCloser,
- panicBar: node.panicBar,
- panicBarHeight: node.panicBarHeight,
- segments: node.segments,
- }
- }, [node])
-
- const handleSavePreset = useCallback(
- async (name: string) => {
- const data = getDoorPresetData()
- if (!(data && selectedId)) return
- const presetId = await adapter.savePreset('door', name, data)
- if (presetId) emitter.emit('preset:generate-thumbnail', { presetId, nodeId: selectedId })
- },
- [getDoorPresetData, selectedId, adapter],
- )
-
- const handleOverwritePreset = useCallback(
- async (id: string) => {
- const data = getDoorPresetData()
- if (!(data && selectedId)) return
- await adapter.overwritePreset('door', id, data)
- emitter.emit('preset:generate-thumbnail', { presetId: id, nodeId: selectedId })
- },
- [getDoorPresetData, selectedId, adapter],
- )
-
- const handleApplyPreset = useCallback(
- (data: Record) => {
- handleUpdate(data as Partial)
- },
- [handleUpdate],
- )
-
if (!(node && node.type === 'door' && selectedId)) return null
const hSum = node.segments.reduce((s, seg) => s + seg.heightRatio, 0)
@@ -594,27 +520,6 @@ export default function DoorPanel() {
title={node.name || 'Door'}
width={320}
>
- {/* Presets strip */}
-
-
adapter.deletePreset(id)}
- onFetchPresets={(tab) => adapter.fetchPresets('door', tab)}
- onOverwrite={handleOverwritePreset}
- onRename={(id, name) => adapter.renamePreset(id, name)}
- onSave={handleSavePreset}
- onToggleCommunity={adapter.togglePresetCommunity}
- tabs={adapter.tabs}
- type="door"
- >
-
-
- Presets
-
-
-
-
(null)
- const adapter = usePresetsAdapter()
-
const node = useScene((s) =>
selectedId ? (s.nodes[selectedId as AnyNode['id']] as WindowNode | undefined) : undefined,
)
@@ -251,62 +246,6 @@ export default function WindowPanel() {
setSelection({ selectedIds: [] })
}, [node, setMovingNode, setSelection])
- const getWindowPresetData = useCallback(() => {
- if (!node) return null
- return {
- width: node.width,
- height: node.height,
- windowType: node.windowType,
- operationState: node.operationState,
- awningDirection: node.awningDirection,
- casementStyle: node.casementStyle,
- hingesSide: node.hingesSide,
- frameThickness: node.frameThickness,
- frameDepth: node.frameDepth,
- openingKind: node.openingKind,
- openingShape: node.openingShape,
- openingRadiusMode: node.openingRadiusMode ?? 'all',
- openingCornerRadii: node.openingCornerRadii ?? [0.15, 0.15, 0.15, 0.15],
- cornerRadius: node.cornerRadius,
- archHeight: node.archHeight,
- openingRevealRadius: node.openingRevealRadius,
- columnRatios: node.columnRatios,
- rowRatios: node.rowRatios,
- columnDividerThickness: node.columnDividerThickness,
- rowDividerThickness: node.rowDividerThickness,
- sill: node.sill,
- sillDepth: node.sillDepth,
- sillThickness: node.sillThickness,
- }
- }, [node])
-
- const handleSavePreset = useCallback(
- async (name: string) => {
- const data = getWindowPresetData()
- if (!(data && selectedId)) return
- const presetId = await adapter.savePreset('window', name, data)
- if (presetId) emitter.emit('preset:generate-thumbnail', { presetId, nodeId: selectedId })
- },
- [getWindowPresetData, selectedId, adapter],
- )
-
- const handleOverwritePreset = useCallback(
- async (id: string) => {
- const data = getWindowPresetData()
- if (!(data && selectedId)) return
- await adapter.overwritePreset('window', id, data)
- emitter.emit('preset:generate-thumbnail', { presetId: id, nodeId: selectedId })
- },
- [getWindowPresetData, selectedId, adapter],
- )
-
- const handleApplyPreset = useCallback(
- (data: Record) => {
- handleUpdate(data as Partial)
- },
- [handleUpdate],
- )
-
if (!(node && node.type === 'window' && selectedId)) return null
const numCols = node.columnRatios.length
@@ -447,27 +386,6 @@ export default function WindowPanel() {
title={node.name || 'Window'}
width={320}
>
- {/* Presets strip */}
-
-
adapter.deletePreset(id)}
- onFetchPresets={(tab) => adapter.fetchPresets('window', tab)}
- onOverwrite={handleOverwritePreset}
- onRename={(id, name) => adapter.renamePreset(id, name)}
- onSave={handleSavePreset}
- onToggleCommunity={adapter.togglePresetCommunity}
- tabs={adapter.tabs}
- type="window"
- >
-
-
- Presets
-
-
-
-