diff --git a/js/Multiangle_Camera.js b/js/Multiangle_Camera.js index 0c8594b..35ce4ee 100644 --- a/js/Multiangle_Camera.js +++ b/js/Multiangle_Camera.js @@ -138,6 +138,32 @@ const WIDGET_STYLES = ` border-color: #64B4FF; } .synvow-multiangle-file-input { display: none; } + .synvow-multiangle-drop-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(233, 61, 130, 0.15); + border: 3px dashed #E93D82; + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + opacity: 0; + pointer-events: none; + transition: opacity 0.2s ease; + z-index: 100; + } + .synvow-multiangle-drop-overlay.active { + opacity: 1; + } + .synvow-multiangle-drop-text { + color: #E93D82; + font-size: 14px; + font-weight: 600; + text-shadow: 0 0 10px rgba(233, 61, 130, 0.5); + } `; function injectStyles() { @@ -158,6 +184,7 @@ const translations = { zoomFull: 'ZOOM', resetToDefaults: 'Reset to defaults', loadImage: 'Load image', + dropImageHere: 'Drop image here', frontView: 'front view', frontRightQuarterView: 'front-right quarter view', rightSideView: 'right side view', @@ -260,6 +287,9 @@ class CameraWidget { this.container.innerHTML = `
+
+ ${t('dropImageHere')} +
<sks> front view eye-level shot medium shot
@@ -296,7 +326,9 @@ class CameraWidget { `; const containerEl = this.container.querySelector('.synvow-multiangle-container'); + this.containerEl = containerEl; this.canvasContainer = containerEl.querySelector('.synvow-multiangle-canvas'); + this.dropOverlay = containerEl.querySelector('.synvow-multiangle-drop-overlay'); this.promptEl = containerEl.querySelector('.synvow-multiangle-prompt'); this.hValueEl = containerEl.querySelector('.synvow-multiangle-param-value.azimuth'); this.vValueEl = containerEl.querySelector('.synvow-multiangle-param-value.elevation'); @@ -554,7 +586,12 @@ class CameraWidget { }, { passive: false }); canvas.addEventListener('touchend', () => this.onPointerUp()); - canvas.addEventListener('wheel', (e) => this.onWheel(e), { passive: false }); + + // Drag and drop events + this.containerEl.addEventListener('dragenter', (e) => this.onDragEnter(e)); + this.containerEl.addEventListener('dragover', (e) => this.onDragOver(e)); + this.containerEl.addEventListener('dragleave', (e) => this.onDragLeave(e)); + this.containerEl.addEventListener('drop', (e) => this.onDrop(e)); new ResizeObserver(() => this.onResize()).observe(this.canvasContainer); } @@ -676,16 +713,88 @@ class CameraWidget { this.renderer.domElement.style.cursor = 'default'; } - onWheel(event) { + onDragEnter(event) { event.preventDefault(); - const sensitivity = 0.01; - let newDistance = this.liveDistance - event.deltaY * sensitivity; - newDistance = Math.max(0, Math.min(10, newDistance)); - this.liveDistance = newDistance; - this.state.distance = Math.round(this.liveDistance * 10) / 10; - this.updateVisuals(); - this.updateDisplay(); - this.notifyStateChange(); + event.stopPropagation(); + if (this.hasImageFile(event)) { + this.dropOverlay.classList.add('active'); + } + } + + onDragOver(event) { + event.preventDefault(); + event.stopPropagation(); + if (this.hasImageFile(event)) { + event.dataTransfer.dropEffect = 'copy'; + } + } + + onDragLeave(event) { + event.preventDefault(); + event.stopPropagation(); + // Only hide overlay if leaving the container entirely + const rect = this.containerEl.getBoundingClientRect(); + const x = event.clientX; + const y = event.clientY; + if (x < rect.left || x > rect.right || y < rect.top || y > rect.bottom) { + this.dropOverlay.classList.remove('active'); + } + } + + async onDrop(event) { + event.preventDefault(); + event.stopPropagation(); + this.dropOverlay.classList.remove('active'); + + const file = this.getImageFile(event); + if (file) { + await this.uploadFile(file); + } + } + + hasImageFile(event) { + if (event.dataTransfer?.types) { + for (const type of event.dataTransfer.types) { + if (type === 'Files') { + return true; + } + } + } + return false; + } + + getImageFile(event) { + const files = event.dataTransfer?.files; + if (files && files.length > 0) { + const file = files[0]; + if (file.type.startsWith('image/')) { + return file; + } + } + return null; + } + + async uploadFile(file) { + try { + const formData = new FormData(); + formData.append('image', file); + formData.append('overwrite', 'true'); + + const response = await api.fetchApi('/upload/image', { method: 'POST', body: formData }); + + if (response.ok) { + const result = await response.json(); + const filename = result.name; + const subfolder = result.subfolder || ''; + + this.state.uploadedImageFile = subfolder ? `${subfolder}/${filename}` : filename; + const imageUrl = api.apiURL(`/view?filename=${encodeURIComponent(filename)}&subfolder=${encodeURIComponent(subfolder)}&type=input`); + this.updateImage(imageUrl); + this.notifyStateChange(); + } + } catch (error) { + console.error('Error uploading image:', error); + } } onResize() {