Spaces:
Running
Running
| class CustomToolsPanel extends HTMLElement { | |
| connectedCallback() { | |
| this.attachShadow({ mode: 'open' }); | |
| this.shadowRoot.innerHTML = ` | |
| <style> | |
| :host { | |
| display: flex; | |
| flex-direction: column; | |
| height: 100%; | |
| background: rgba(15, 23, 42, 0.5); | |
| } | |
| .tabs { | |
| display: flex; | |
| border-bottom: 1px solid #334155; | |
| } | |
| .tab-btn { | |
| flex: 1; | |
| background: transparent; | |
| border: none; | |
| color: #94a3b8; | |
| padding: 1rem; | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 0.5rem; | |
| transition: all 0.2s; | |
| } | |
| .tab-btn:hover { | |
| background: rgba(255, 255, 255, 0.05); | |
| color: #e2e8f0; | |
| } | |
| .tab-btn.active { | |
| color: #6366f1; | |
| border-bottom: 2px solid #6366f1; | |
| background: rgba(99, 102, 241, 0.05); | |
| } | |
| .panel-content { | |
| flex: 1; | |
| overflow: hidden; | |
| display: none; | |
| flex-direction: column; | |
| } | |
| .panel-content.active { | |
| display: flex; | |
| } | |
| .upload-area { | |
| flex: 1; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| padding: 2rem; | |
| border: 2px dashed #334155; | |
| margin: 1rem; | |
| border-radius: 0.5rem; | |
| text-align: center; | |
| color: #94a3b8; | |
| cursor: pointer; | |
| transition: border-color 0.2s; | |
| } | |
| .upload-area:hover { | |
| border-color: #6366f1; | |
| color: #818cf8; | |
| } | |
| #monaco-container { | |
| width: 100%; | |
| height: 100%; | |
| padding: 1rem; | |
| box-sizing: border-box; | |
| } | |
| .file-list { | |
| padding: 0 1rem; | |
| overflow-y: auto; | |
| } | |
| .file-item { | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| padding: 0.5rem; | |
| background: #1e293b; | |
| margin-bottom: 0.5rem; | |
| border-radius: 0.25rem; | |
| font-size: 0.875rem; | |
| } | |
| </style> | |
| <div class="tabs"> | |
| <button class="tab-btn active" data-tab="0"> | |
| <i data-feather="file-text"></i> Docs | |
| </button> | |
| <button class="tab-btn" data-tab="1"> | |
| <i data-feather="code"></i> Code | |
| </button> | |
| </div> | |
| <div id="panel-0" class="panel-content active"> | |
| <div class="upload-area" id="drop-zone"> | |
| <i data-feather="upload-cloud" style="width: 48px; height: 48px; margin-bottom: 1rem;"></i> | |
| <p>Drag & Drop PDF/Docx/Txt</p> | |
| <p style="font-size: 0.75rem; margin-top: 0.5rem;">or click to browse</p> | |
| <input type="file" id="file-input" multiple style="display: none;"> | |
| </div> | |
| <div class="file-list" id="file-list"> | |
| <!-- Uploaded files appear here --> | |
| </div> | |
| </div> | |
| <div id="panel-1" class="panel-content"> | |
| <div id="monaco-container"></div> | |
| </div> | |
| `; | |
| // Tab Logic | |
| const tabs = this.shadowRoot.querySelectorAll('.tab-btn'); | |
| const panels = this.shadowRoot.querySelectorAll('.panel-content'); | |
| tabs.forEach(tab => { | |
| tab.addEventListener('click', () => { | |
| tabs.forEach(t => t.classList.remove('active')); | |
| panels.forEach(p => p.classList.remove('active')); | |
| tab.classList.add('active'); | |
| this.shadowRoot.getElementById(`panel-${tab.dataset.tab}`).classList.add('active'); | |
| // Resize Monaco if code tab is active | |
| if(tab.dataset.tab === "1" && window.monacoEditor) { | |
| window.monacoEditor.layout(); | |
| } | |
| }); | |
| }); | |
| // File Upload Logic (Mock) | |
| const dropZone = this.shadowRoot.getElementById('drop-zone'); | |
| const fileInput = this.shadowRoot.getElementById('file-input'); | |
| const fileList = this.shadowRoot.getElementById('file-list'); | |
| const handleFiles = (files) => { | |
| Array.from(files).forEach(file => { | |
| const item = document.createElement('div'); | |
| item.className = 'file-item'; | |
| item.innerHTML = ` | |
| <i data-feather="file"></i> | |
| <span style="flex:1; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;">${file.name}</span> | |
| <i data-feather="check-circle" style="color: #34d399; width: 16px;"></i> | |
| `; | |
| fileList.prepend(item); | |
| feather.replace(); | |
| }); | |
| }; | |
| dropZone.addEventListener('click', () => fileInput.click()); | |
| fileInput.addEventListener('change', (e) => handleFiles(e.target.files)); | |
| dropZone.addEventListener('dragover', (e) => { | |
| e.preventDefault(); | |
| dropZone.style.borderColor = '#6366f1'; | |
| }); | |
| dropZone.addEventListener('dragleave', () => { | |
| dropZone.style.borderColor = '#334155'; | |
| }); | |
| dropZone.addEventListener('drop', (e) => { | |
| e.preventDefault(); | |
| dropZone.style.borderColor = '#334155'; | |
| handleFiles(e.dataTransfer.files); | |
| }); | |
| // Monaco Editor Loader | |
| this.loadMonaco(); | |
| // Listen for Switch Tab Event (from Script.js when code is generated) | |
| window.appEvents.addEventListener('switch-tab', (e) => { | |
| const index = e.detail.index; | |
| tabs[index].click(); | |
| }); | |
| // Listen for Code Update | |
| window.appEvents.addEventListener('update-code', (e) => { | |
| if(window.monacoEditor) { | |
| window.monacoEditor.setValue(e.detail.code); | |
| } | |
| }); | |
| feather.replace(); | |
| } | |
| loadMonaco() { | |
| if (document.getElementById('monaco-script')) return; // Already loaded | |
| const script = document.createElement('script'); | |
| script.id = 'monaco-script'; | |
| script.src = 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.44.0/min/vs/loader.min.js'; | |
| script.onload = () => { | |
| require.config({ paths: { vs: 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.44.0/min/vs' } }); | |
| require(['vs/editor/editor.main'], function() { | |
| // Delay slightly to ensure container is rendered and has dimensions | |
| setTimeout(() => { | |
| if(document.getElementById('monaco-container')) { | |
| // Access shadow root container | |
| const container = this.shadowRoot.getElementById('monaco-container'); | |
| // Actually, the require callback scope 'this' might be lost, but standard monaco usage usually targets global or ID | |
| // We need to be careful with Shadow DOM and Monaco. | |
| // Monaco appends to the body by default for overlays, but we want it inside our component. | |
| // Simplest way for this demo: Target by ID inside ShadowDOM | |
| // Because Monaco is a bit complex with ShadowDOM encapsulation (styles/events), | |
| // we might need to use global or trick it. | |
| // For this demo, let's attach to the specific ID if possible, or use window.monacoEditor for control. | |
| // Note: Monaco doesn't fully support ShadowDOM out of the box easily without configuration tweaks. | |
| // We will try to render it. If it fails, it might be due to the shadow root encapsulation blocking some global styles/events needed by Monaco's widget logic. | |
| // To make it robust, we often put the monaco container *outside* or pass specific options. | |
| // Let's try standard initialization on the element found in the shadowRoot of the custom element. | |
| const host = document.querySelector('custom-tools-panel').shadowRoot.getElementById('monaco-container'); | |
| window.monacoEditor = monaco.editor.create(host, { | |
| value: '# AI-generated code will appear here\n# Start coding your vibe...', | |
| language: 'python', | |
| theme: 'vs-dark', | |
| automaticLayout: true, | |
| minimap: { enabled: false } | |
| }); | |
| } | |
| }, 500); | |
| }.bind(this)); | |
| }; | |
| document.head.appendChild(script); | |
| } | |
| } | |
| customElements.define('custom-tools-panel', CustomToolsPanel); |