import app from '../app.js'; const api = app.getAPI(); const logger = app.getLogger(); class CodeEditorView extends HTMLElement { constructor() { super(); this.editor = null; this.file = null; this.previousView = null; this.isRendered = false; } connectedCallback() { logger.debug('CodeEditorView connected'); } disconnectedCallback() { logger.debug('CodeEditorView disconnected'); this.destroyEditor(); } async setFile(file, previousView = 'files') { if (this.isRendered) { logger.warn('Editor already rendered, skipping'); return; } this.file = file; this.previousView = previousView; this.isRendered = true; try { logger.debug('Loading file', { fileName: file.name }); const blob = await api.downloadFile(file.id); const content = await blob.text(); this.createUI(content); this.createEditor(content); } catch (error) { logger.error('Failed to load file', error); document.dispatchEvent(new CustomEvent('show-toast', { detail: { message: 'Failed to load file: ' + error.message, type: 'error' } })); window.history.back(); } } createUI(content) { this.innerHTML = `

${this.escapeHtml(this.file.name)}

${this.formatFileSize(this.file.size)} • ${new Date(this.file.created_at).toLocaleDateString()}

`; const backBtn = this.querySelector('#back-btn'); const saveBtn = this.querySelector('#save-btn'); const downloadBtn = this.querySelector('#download-btn'); backBtn.addEventListener('click', () => this.close()); saveBtn.addEventListener('click', () => this.save()); downloadBtn.addEventListener('click', () => this.downloadFile()); document.addEventListener('keydown', this.handleKeydown.bind(this)); } createEditor(content) { const textarea = this.querySelector('#editor-textarea'); if (!textarea) { logger.error('Textarea not found'); return; } textarea.value = content; const mode = this.getMode(this.file.name); logger.debug('Creating CodeMirror editor', { mode, fileSize: content.length }); this.editor = CodeMirror.fromTextArea(textarea, { mode: mode, lineNumbers: true, lineWrapping: true, indentUnit: 4, indentWithTabs: false, theme: 'default', readOnly: false, autofocus: true, extraKeys: { 'Ctrl-S': () => { this.save(); return false; }, 'Cmd-S': () => { this.save(); return false; }, 'Esc': () => { this.close(); return false; } } }); this.editor.setSize('100%', '100%'); setTimeout(() => { if (this.editor) { this.editor.refresh(); this.editor.focus(); logger.debug('Editor ready and focused'); } }, 100); } getMode(filename) { const ext = filename.split('.').pop().toLowerCase(); const modes = { 'js': 'javascript', 'json': { name: 'javascript', json: true }, 'py': 'python', 'md': 'markdown', 'html': 'htmlmixed', 'xml': 'xml', 'css': 'css', 'txt': 'text/plain', 'log': 'text/plain', 'sh': 'shell', 'yaml': 'yaml', 'yml': 'yaml' }; return modes[ext] || 'text/plain'; } escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } async downloadFile() { try { const blob = await api.downloadFile(this.file.id); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = this.file.name; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); document.body.removeChild(a); } catch (error) { console.error('Failed to download file:', error); } } formatFileSize(bytes) { const units = ['B', 'KB', 'MB', 'GB']; let size = bytes; let unitIndex = 0; while (size >= 1024 && unitIndex < units.length - 1) { size /= 1024; unitIndex++; } return `${size.toFixed(1)} ${units[unitIndex]}`; } handleKeydown(e) { if (e.key === 'Escape' && !this.editor.getOption('readOnly')) { this.close(); } } async save() { if (!this.editor) { logger.warn('No editor instance'); return; } const saveBtn = this.querySelector('#save-btn'); if (!saveBtn) return; try { saveBtn.disabled = true; saveBtn.textContent = 'Saving...'; const content = this.editor.getValue(); logger.debug('Saving file', { fileName: this.file.name, size: content.length }); await api.updateFile(this.file.id, content); logger.info('File saved successfully', { fileName: this.file.name }); document.dispatchEvent(new CustomEvent('show-toast', { detail: { message: 'File saved successfully!', type: 'success' } })); setTimeout(() => { this.close(); }, 500); } catch (error) { logger.error('Failed to save file', error); document.dispatchEvent(new CustomEvent('show-toast', { detail: { message: 'Failed to save: ' + error.message, type: 'error' } })); if (saveBtn) { saveBtn.disabled = false; saveBtn.textContent = 'Save & Close'; } } } close() { logger.debug('Closing editor'); window.history.back(); } hide() { logger.debug('Hiding editor'); this.destroyEditor(); this.remove(); } destroyEditor() { if (this.editor) { logger.debug('Destroying CodeMirror instance'); try { this.editor.toTextArea(); } catch (e) { logger.warn('Error destroying editor', e); } this.editor = null; } } } customElements.define('code-editor-view', CodeEditorView); export { CodeEditorView };