import MediaModalPanel from './components/media-modal-panel'; import PresetList from './components/preset-list'; import LinkSelector from './components/link-selector'; import DimensionSelector from './components/dimension-selector'; import AnimationSelector from './components/animation-selector'; import AppearanceSelector from './components/appearance-selector'; import CameraControlsSelector from './components/camera-controls-selector'; import AccessibilitySelector from './components/accessibility-selector'; import { ModelViewer } from '../model-viewer/ModelViewer'; import { loadAttachment } from '../rest-api'; import { is3dModelData, loadPresets } from './state-manager'; import { eventHandling } from './ui-events'; // ---------------------------------------------------------------------- let viewer: ModelViewer | null = null; let executeToken = 0; /** Destroys the current Model viewer instance, if any. */ export function destroyViewer() { if (viewer) { viewer.destroy(); viewer = null; } } /** Creates a ModelViewer in the given wrapper. */ export function createViewer(modelUrl: string, canvasWrapper: HTMLElement): ModelViewer { destroyViewer(); const viewer = new ModelViewer(canvasWrapper, { modelUrl, enableRotate: true, enableZoom: true, enablePan: true, pluginVersion: (window as any).Press3D?.version, showOrbitWidget: true, orbitWidgetOptions: { position: 'top-left' }, zoom: (window as any).Press3D?.defaultZoom, }); // Explicitly disable camera controls for the stored state (shortcode defaults) // The admin viewer itself remains interactive because the constructor options above enabled the actual OrbitControls viewer.setEnableZoom(false); viewer.setEnableRotation(false); viewer.setEnablePan(false); return viewer; } function createCanvasWrapper() { const canvasWrapper = document.createElement('div'); canvasWrapper.classList.add('model-canvas-wrapper'); //canvasWrapper.style.border = '1px solid var(--wp-components-color-accent, var(--wp-admin-theme-color, #3858e9))'; return canvasWrapper; } /** Creates element. */ export function createMediaModalPanel(): MediaModalPanel { if (!customElements.get('media-modal-panel')) { customElements.define('media-modal-panel', MediaModalPanel); } if (!customElements.get('link-selector')) { customElements.define('link-selector', LinkSelector); } if (!customElements.get('dimension-selector')) { customElements.define('dimension-selector', DimensionSelector); } if (!customElements.get('animation-selector')) { customElements.define('animation-selector', AnimationSelector); } if (!customElements.get('appearance-selector')) { customElements.define('appearance-selector', AppearanceSelector); } if (!customElements.get('camera-controls-selector')) { customElements.define('camera-controls-selector', CameraControlsSelector); } if (!customElements.get('accessibility-selector')) { customElements.define('accessibility-selector', AccessibilitySelector); } return document.createElement('media-modal-panel') as MediaModalPanel; } /** Creates element. */ export function createPresetList(): PresetList { if (!customElements.get('preset-list')) { customElements.define('preset-list', PresetList); } return document.createElement('preset-list') as PresetList; } /** Main render logic for replacing image with 3D viewer and initializing UI. */ export async function execute(modalEl: Element, attachmentId: number | null) { const myToken = ++executeToken; if (!attachmentId) return; //console.log('Rendering 3D preview for attachment ID:', attachmentId); const data: any = await loadAttachment(attachmentId); if (myToken !== executeToken) return; // a newer navigation happened if (!is3dModelData(data)) return; const img = modalEl.querySelector('.attachment-details img.details-image') as HTMLImageElement | null; if (img) { img.style.display = 'none'; modalEl.querySelectorAll('.model-canvas-wrapper').forEach((el) => el.remove()); const canvasWrapper = createCanvasWrapper(); img.parentElement?.appendChild(canvasWrapper); viewer = createViewer(data?.source_url, canvasWrapper); await viewer.loadAsync(); if (myToken !== executeToken) return; // outdated run, stop before wiring UI let mediaModalPanel: MediaModalPanel = modalEl.querySelector('media-modal-panel'); if (!mediaModalPanel) { mediaModalPanel = createMediaModalPanel(); modalEl.querySelector('.attachment-info')?.prepend(mediaModalPanel); } let presetList: PresetList = mediaModalPanel.querySelector('preset-list') || modalEl.querySelector('preset-list'); if (!presetList) { presetList = createPresetList(); mediaModalPanel.getContentElement().appendChild(presetList); loadPresets(attachmentId, presetList, data); // Pass cached data to avoid duplicate request } // Set model URL to enable/disable shiny mode control based on file type mediaModalPanel.setModelUrl(data?.source_url); // Synchronize panel with viewer state after model loads const viewerState = viewer.getViewerState(); mediaModalPanel.setLightIntensity(viewerState.lightIntensity); mediaModalPanel.setColor(viewerState.color); mediaModalPanel.setShinyIntensity(viewerState.shinyIntensity); mediaModalPanel.setAutoRotate(viewerState.autoRotate); mediaModalPanel.setAutoRotateSpeed(viewerState.autoRotateSpeed); // Note: Camera controls are NOT synchronized - admin viewer is always controllable // Panel values are only used for frontend shortcode rendering eventHandling(attachmentId, viewer, mediaModalPanel, presetList); } }