From 1bd103acd89032afa49c16ecfc81f05b31d0942b Mon Sep 17 00:00:00 2001 From: Wassim SAMAD Date: Thu, 28 May 2026 10:06:14 -0400 Subject: [PATCH] chore(editor): delete legacy door/window presets UI (#339) Retires the door/window-only `PresetsPopover`, its `presets-context` adapter, and the `PresetThumbnailGenerator` ahead of the unified preset system landing via the items catalog. Drops the public exports (`PresetsPopover`, `PresetsAdapter`, `PresetsTab`, `PresetsProvider`, `usePresetsAdapter`) and the matching `presetsAdapter` prop / provider wrappers on ``, plus the `preset:generate-thumbnail` / `preset:thumbnail-updated` event types in core. Door and window panels now render the regular parametric inspector with no popover trigger; `materialPreset` stays untouched. Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/core/src/events/bus.ts | 6 - .../editor/src/components/editor/index.tsx | 110 ++-- .../editor/preset-thumbnail-generator.tsx | 125 ----- .../ui/panels/presets/presets-popover.tsx | 511 ------------------ .../editor/src/contexts/presets-context.tsx | 121 ----- packages/editor/src/index.tsx | 5 - packages/nodes/src/door/panel.tsx | 99 +--- packages/nodes/src/window/panel.tsx | 84 +-- 8 files changed, 52 insertions(+), 1009 deletions(-) delete mode 100644 packages/editor/src/components/editor/preset-thumbnail-generator.tsx delete mode 100644 packages/editor/src/components/ui/panels/presets/presets-popover.tsx delete mode 100644 packages/editor/src/contexts/presets-context.tsx diff --git a/packages/core/src/events/bus.ts b/packages/core/src/events/bus.ts index 143b17c48..1e92b7ca1 100644 --- a/packages/core/src/events/bus.ts +++ b/packages/core/src/events/bus.ts @@ -189,11 +189,6 @@ type WindowAnimationEvents = { } } -type PresetEvents = { - 'preset:generate-thumbnail': { presetId: string; nodeId: string } - 'preset:thumbnail-updated': { presetId: string; thumbnailUrl: string } -} - type ThumbnailEvents = { 'thumbnail:before-capture': undefined 'thumbnail:after-capture': undefined @@ -243,7 +238,6 @@ type EditorEvents = GridEvents & GuideEvents & DoorAnimationEvents & WindowAnimationEvents & - PresetEvents & ThumbnailEvents & SnapshotEvents & AIChatEvents diff --git a/packages/editor/src/components/editor/index.tsx b/packages/editor/src/components/editor/index.tsx index 03d36e75f..ac11aacf8 100644 --- a/packages/editor/src/components/editor/index.tsx +++ b/packages/editor/src/components/editor/index.tsx @@ -19,7 +19,6 @@ import { } from 'react' import { ViewerOverlay } from '../../components/viewer-overlay' import { ViewerZoneSystem } from '../../components/viewer-zone-system' -import { type PresetsAdapter, PresetsProvider } from '../../contexts/presets-context' import { type SaveStatus, useAutoSave } from '../../hooks/use-auto-save' import { useKeyboard } from '../../hooks/use-keyboard' import { @@ -61,7 +60,6 @@ import { FloatingActionMenu } from './floating-action-menu' import { FloatingBuildingActionMenu } from './floating-building-action-menu' import { FloorplanPanel } from './floorplan-panel' import { Grid } from './grid' -import { PresetThumbnailGenerator } from './preset-thumbnail-generator' import { NodeArrowHandles } from './node-arrow-handles' import { SelectionManager } from './selection-manager' import { SiteEdgeLabels } from './site-edge-labels' @@ -155,9 +153,6 @@ export interface EditorProps { sitePanelProps?: SitePanelProps extraSidebarPanels?: ExtraPanel[] - // Presets storage backend (defaults to localStorage) - presetsAdapter?: PresetsAdapter - // Command palette fallback when no commands match commandPaletteEmptyAction?: CommandPaletteEmptyAction } @@ -577,9 +572,7 @@ function PaintCursorBadge({ // grid lines when the user picks a finer snap (0.25 / 0.1 / 0.05). function SnapAwareGrid() { const gridSnapStep = useEditor((s) => s.gridSnapStep) - return ( - - ) + return } // ── Viewer scene content: memoized so doesn't re-render on mode/viewMode changes ── @@ -615,7 +608,6 @@ const ViewerSceneContent = memo(function ViewerSceneContent({ {isFirstPersonMode && } - {!isFirstPersonMode && } @@ -952,7 +944,6 @@ export default function Editor({ settingsPanelProps, sitePanelProps, extraSidebarPanels, - presetsAdapter, commandPaletteEmptyAction, }: EditorProps) { const isFirstPersonMode = useEditor((s) => s.isFirstPersonMode) @@ -1098,7 +1089,6 @@ export default function Editor({ - ) @@ -1142,7 +1132,7 @@ export default function Editor({ })) ?? [] return ( - + <> {showLoader && (
@@ -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 && ( - - )} -
- - {showSaveInput && ( -
- setSaveName(e.target.value)} - onKeyDown={(e) => { - if (e.key === 'Enter') handleSaveNew() - if (e.key === 'Escape') { - setShowSaveInput(false) - setSaveName('') - } - }} - placeholder="Preset name…" - value={saveName} - /> - - -
- )} - - {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 ( - - ) -} - -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}"? -
    - - -
    -
  • - ) - } - - 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 && ( - - - - - - - - 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" - > - - -
    -
    (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" - > - - -
    -