Spaces:
Running
Running
| class AppWindow extends HTMLElement { | |
| connectedCallback() { | |
| this.attachShadow({ mode: 'open' }); | |
| const title = this.getAttribute('title') || 'Window'; | |
| const icon = this.getAttribute('icon') || 'square'; | |
| const width = this.getAttribute('width') || '600'; | |
| const height = this.getAttribute('height') || '400'; | |
| const x = this.getAttribute('x') || '100'; | |
| const y = this.getAttribute('y') || '100'; | |
| this.shadowRoot.innerHTML = ` | |
| <style> | |
| :host { | |
| position: absolute; | |
| width: ${width}px; | |
| height: ${height}px; | |
| left: ${x}px; | |
| top: ${y}px; | |
| background: #18181b; | |
| border: 1px solid #3f3f46; | |
| border-radius: 8px; | |
| box-shadow: 0 10px 40px rgba(0, 0, 0, 0.4); | |
| display: flex; | |
| flex-direction: column; | |
| overflow: hidden; | |
| transition: box-shadow 0.2s; | |
| animation: windowEnter 0.2s ease-out; | |
| } | |
| :host(.active-window) { | |
| box-shadow: 0 15px 50px rgba(0, 0, 0, 0.5); | |
| } | |
| .window-header { | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| background: linear-gradient(to bottom, #27272a, #1f1f23); | |
| border-bottom: 1px solid #3f3f46; | |
| padding: 4px 8px; | |
| cursor: move; | |
| user-select: none; | |
| } | |
| .window-title { | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| color: #e4e4e7; | |
| font-size: 12px; | |
| font-weight: 500; | |
| } | |
| .window-controls { | |
| display: flex; | |
| gap: 4px; | |
| } | |
| .window-control { | |
| width: 16px; | |
| height: 16px; | |
| border-radius: 50%; | |
| border: none; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| } | |
| .window-control.minimize { | |
| background: #fbbf24; | |
| } | |
| .window-control.maximize { | |
| background: #34d399; | |
| } | |
| .window-control.close { | |
| background: #ef4444; | |
| } | |
| .window-control:hover { | |
| opacity: 0.8; | |
| transform: scale(1.1); | |
| } | |
| .window-control i { | |
| width: 8px; | |
| height: 8px; | |
| color: rgba(0, 0, 0, 0.7); | |
| } | |
| .window-content { | |
| flex: 1; | |
| overflow: auto; | |
| background: #18181b; | |
| } | |
| .window-resize-handle { | |
| position: absolute; | |
| bottom: 0; | |
| right: 0; | |
| width: 16px; | |
| height: 16px; | |
| cursor: nwse-resize; | |
| background: linear-gradient(135deg, transparent 50%, #3f3f46 50%); | |
| border-bottom-right-radius: 8px; | |
| } | |
| @keyframes windowEnter { | |
| from { | |
| opacity: 0; | |
| transform: scale(0.9); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: scale(1); | |
| } | |
| } | |
| </style> | |
| <div class="window-header" id="window-header"> | |
| <div class="window-title"> | |
| <i data-feather="${icon}" style="width: 14px; height: 14px;"></i> | |
| <span>${title}</span> | |
| </div> | |
| <div class="window-controls"> | |
| <button class="window-control minimize" title="Minimizar"> | |
| <i data-feather="minus"></i> | |
| </button> | |
| <button class="window-control maximize" title="Maximizar"> | |
| <i data-feather="square"></i> | |
| </button> | |
| <button class="window-control close" title="Fechar"> | |
| <i data-feather="x"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <div class="window-content"> | |
| <slot></slot> | |
| </div> | |
| <div class="window-resize-handle" id="resize-handle"></div> | |
| `; | |
| this.windowId = this.id; | |
| this.isDragging = false; | |
| this.isResizing = false; | |
| this.initializeEventListeners(); | |
| // Re-render feather icons | |
| setTimeout(() => feather.replace(), 0); | |
| // Focus the window initially | |
| setTimeout(() => focusWindow(this.windowId), 100); | |
| } | |
| initializeEventListeners() { | |
| const header = this.shadowRoot.getElementById('window-header'); | |
| const resizeHandle = this.shadowRoot.getElementById('resize-handle'); | |
| const minimizeBtn = this.shadowRoot.querySelector('.window-control.minimize'); | |
| const maximizeBtn = this.shadowRoot.querySelector('.window-control.maximize'); | |
| const closeBtn = this.shadowRoot.querySelector('.window-control.close'); | |
| // Window dragging | |
| header.addEventListener('mousedown', (e) => { | |
| if (e.target.closest('.window-controls')) return; | |
| this.startDragging(e); | |
| }); | |
| // Window resizing | |
| resizeHandle.addEventListener('mousedown', (e) => { | |
| this.startResizing(e); | |
| }); | |
| // Window controls | |
| minimizeBtn.addEventListener('click', () => { | |
| minimizeWindow(this.windowId); | |
| }); | |
| maximizeBtn.addEventListener('click', () => { | |
| maximizeWindow(this.windowId); | |
| }); | |
| closeBtn.addEventListener('click', () => { | |
| closeWindow(this.windowId); | |
| }); | |
| // Focus on click | |
| this.addEventListener('mousedown', () => { | |
| focusWindow(this.windowId); | |
| }); | |
| // Global mouse events | |
| document.addEventListener('mousemove', (e) => this.handleMouseMove(e)); | |
| document.addEventListener('mouseup', () => this.handleMouseUp()); | |
| } | |
| startDragging(e) { | |
| this.isDragging = true; | |
| this.dragOffsetX = e.clientX - parseInt(this.style.left); | |
| this.dragOffsetY = e.clientY - parseInt(this.style.top); | |
| this.style.cursor = 'move'; | |
| } | |
| startResizing(e) { | |
| this.isResizing = true; | |
| this.resizeStartX = e.clientX; | |
| this.resizeStartY = e.clientY; | |
| this.resizeStartWidth = parseInt(this.style.width); | |
| this.resizeStartHeight = parseInt(this.style.height); | |
| e.stopPropagation(); | |
| } | |
| handleMouseMove(e) { | |
| if (this.isDragging) { | |
| const newX = Math.max(0, Math.min(e.clientX - this.dragOffsetX, window.innerWidth - parseInt(this.style.width || this.getAttribute('width')))); | |
| const newY = Math.max(0, Math.min(e.clientY - this.dragOffsetY, window.innerHeight - parseInt(this.style.height || this.getAttribute('height')) - 32)); | |
| this.style.left = `${newX}px`; | |
| this.style.top = `${newY}px`; | |
| } else if (this.isResizing) { | |
| const deltaX = e.clientX - this.resizeStartX; | |
| const deltaY = e.clientY - this.resizeStartY; | |
| const newWidth = Math.max(300, this.resizeStartWidth + deltaX); | |
| const newHeight = Math.max(200, this.resizeStartHeight + deltaY); | |
| this.style.width = `${newWidth}px`; | |
| this.style.height = `${newHeight}px`; | |
| } | |
| } | |
| handleMouseUp() { | |
| this.isDragging = false; | |
| this.isResizing = false; | |
| this.style.cursor = ''; | |
| } | |
| } | |
| customElements.define('app-window', AppWindow); |