diff --git a/lib/web/ui/static/css/heroprompt.css b/lib/web/ui/static/css/heroprompt.css index c18af77f..eca3f093 100644 --- a/lib/web/ui/static/css/heroprompt.css +++ b/lib/web/ui/static/css/heroprompt.css @@ -1,243 +1,817 @@ -/* Heroprompt specific styles using UI project theme system */ +/* Enhanced HeroPrompt UI - Clean VSCode-like Design */ -/* Tree view specific styles */ -.tree { - font-size: 0.875rem; +/* Icon definitions using CSS pseudo-elements */ +.icon-collapse::before { + content: "⌄"; } -.tree .chev { - transition: transform 0.15s ease; - display: inline-block; - font-size: 0.75rem; - opacity: var(--menu-chevron-opacity); +.icon-refresh::before { + content: "↻"; } -.tree .toggle:checked+.dir-label .chev { - transform: rotate(90deg); +.icon-settings::before { + content: "⚙"; } -.tree .children { - margin-left: 16px; - border-left: 1px solid var(--border-primary); - padding-left: 8px; +.icon-manage::before { + content: "☰"; } -.tree .dir-label { - cursor: pointer; - padding: 0.25rem 0.5rem; - border-radius: 0.25rem; - transition: all 0.2s ease; - color: var(--text-primary); +.icon-search::before { + content: "🔍"; } -.tree .dir-label:hover { - background-color: var(--menu-item-hover-bg); - color: var(--menu-item-hover-text); +.icon-close::before { + content: "✕"; } -.tree .file { - padding: 0.25rem 0.5rem; - border-radius: 0.25rem; - transition: all 0.2s ease; +.icon-folder-open::before { + content: "📂"; } -.tree .file:hover { - background-color: var(--menu-item-hover-bg); +.icon-folder-closed::before { + content: "📁"; } -.tree .file a { - color: var(--text-primary); - text-decoration: none; - transition: color 0.2s ease; +.icon-file::before { + content: "📄"; } -.tree .file a:hover { - color: var(--link-hover-color); +.icon-selection::before { + content: "☑"; } -/* Tab content styling */ -.tab-pane { - height: calc(100vh - 200px); - overflow-y: auto; +.icon-prompt::before { + content: "✎"; +} + +.icon-chat::before { + content: "💬"; +} + +.icon-token::before { + content: "🔢"; +} + +.icon-export::before { + content: "↗"; +} + +.icon-import::before { + content: "↙"; +} + +.icon-copy::before { + content: "📋"; +} + +.icon-template::before { + content: "📝"; +} + +.icon-save::before { + content: "💾"; +} + +.icon-generate::before { + content: "⚡"; +} + +.icon-clear::before { + content: "🗑"; +} + +.icon-ai::before { + content: "🤖"; +} + +.icon-send::before { + content: "➤"; +} + +.icon-empty::before { + content: "∅"; +} + +/* Base layout improvements */ +.main { background-color: var(--bg-primary); color: var(--text-primary); } -/* Selection list styling */ -#selected { - max-height: 300px; - overflow-y: auto; +/* Explorer Panel */ +.explorer-panel { background-color: var(--bg-secondary); border: 1px solid var(--border-primary); - border-radius: 0.375rem; - padding: 0.5rem; + border-radius: 8px; + display: flex; + flex-direction: column; + overflow: hidden; } -#selected li { - background-color: var(--card-bg); - border: 1px solid var(--card-border); - color: var(--text-primary); -} - -#selected li:hover { +.explorer-header { + padding: 12px 16px; + border-bottom: 1px solid var(--border-primary); background-color: var(--bg-tertiary); } -/* Preview area styling */ -#preview { - background-color: var(--bg-secondary); - border: 1px solid var(--border-primary); - color: var(--text-primary); - font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace; - font-size: 0.75rem; - white-space: pre-wrap; - line-height: 1.4; - border-radius: 0.375rem; - padding: 0.75rem; -} - -/* Prompt output styling */ -#promptOutput { - background-color: var(--bg-secondary); - border: 1px solid var(--border-primary); - color: var(--text-primary); - font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace; - font-size: 0.75rem; - white-space: pre-wrap; - line-height: 1.4; - border-radius: 0.375rem; - padding: 0.75rem; -} - -/* Chat messages styling */ -#chatMessages { - background-color: var(--bg-secondary); - border: 1px solid var(--border-primary); - color: var(--text-primary); - border-radius: 0.375rem; - padding: 0.75rem; -} - -/* Form controls theme integration */ -.form-control { - background-color: var(--bg-primary); - border-color: var(--border-primary); - color: var(--text-primary); -} - -.form-control:focus { - background-color: var(--bg-primary); - border-color: var(--link-color); - color: var(--text-primary); - box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25); -} - -.form-control::placeholder { - color: var(--text-muted); -} - -/* Select elements */ -.form-select { - background-color: var(--bg-primary); - border-color: var(--border-primary); - color: var(--text-primary); -} - -.form-select:focus { - background-color: var(--bg-primary); - border-color: var(--link-color); - color: var(--text-primary); -} - -/* Button theme integration */ -.btn-outline-secondary { +.explorer-title { + font-size: 11px; + letter-spacing: 0.5px; color: var(--text-secondary); - border-color: var(--border-primary); + margin: 0; } -.btn-outline-secondary:hover { - background-color: var(--bg-tertiary); - border-color: var(--border-secondary); +.explorer-actions { + display: flex; + gap: 4px; +} + +.btn-ghost { + background: transparent; + border: none; + color: var(--text-secondary); + padding: 4px 6px; + border-radius: 4px; + font-size: 12px; + transition: all 0.2s ease; + cursor: pointer; +} + +.btn-ghost:hover { + background-color: var(--menu-item-hover-bg); color: var(--text-primary); } -.btn-outline-primary { - color: var(--link-color); +.workspace-selector { + margin-bottom: 12px; +} + +.modern-select { + background-color: var(--bg-primary); + border: 1px solid var(--border-primary); + color: var(--text-primary); + border-radius: 4px; + font-size: 13px; + padding: 6px 8px; +} + +.modern-select:focus { border-color: var(--link-color); + box-shadow: 0 0 0 2px rgba(0, 122, 255, 0.2); + outline: none; } -.btn-outline-primary:hover { - background-color: var(--link-color); +.search-container { + position: relative; +} + +.search-icon { + background-color: var(--bg-primary); + border: 1px solid var(--border-primary); + border-right: none; + color: var(--text-secondary); + font-size: 12px; + padding: 6px 8px; +} + +.modern-input { + background-color: var(--bg-primary); + border: 1px solid var(--border-primary); + border-left: none; + border-right: none; + color: var(--text-primary); + font-size: 13px; + padding: 6px 8px; +} + +.modern-input:focus { border-color: var(--link-color); - color: var(--text-light); + box-shadow: none; + outline: none; } -.btn-outline-danger { - color: var(--danger-color); - border-color: var(--danger-color); +.search-clear { + background-color: var(--bg-primary); + border: 1px solid var(--border-primary); + border-left: none; + border-radius: 0 4px 4px 0; + padding: 6px 8px; } -.btn-outline-danger:hover { - background-color: var(--danger-color); - border-color: var(--danger-color); - color: var(--text-light); +.explorer-content { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; + padding: 4px 6px; } -/* Badge styling */ -.badge { +.selection-controls { + padding: 4px 0; + border-bottom: 1px solid var(--border-primary); +} + +.selection-info .badge-selection { background-color: var(--bg-tertiary); color: var(--text-primary); + font-size: 11px; + padding: 2px 6px; + border-radius: 10px; + border: 1px solid var(--border-primary); } -/* Modal theme integration */ -.modal-content { - background-color: var(--card-bg); - border: 1px solid var(--card-border); - color: var(--text-primary); +.selection-actions { + display: flex; + gap: 8px; } -.modal-header { - border-bottom-color: var(--border-primary); +.btn-xs { + font-size: 11px; + padding: 2px 6px; } -.modal-footer { - border-top-color: var(--border-primary); -} - -/* Alert styling */ -.alert-success { - background-color: rgba(25, 135, 84, 0.1); - border-color: var(--success-color); - color: var(--success-color); -} - -.alert-danger { - background-color: rgba(220, 53, 69, 0.1); - border-color: var(--danger-color); - color: var(--danger-color); -} - -/* Tree node buttons */ -.tree .btn { - font-size: 0.75rem; - padding: 0.125rem 0.375rem; +/* Simple File Tree */ +.file-tree { + flex: 1; + overflow-y: auto; + padding: 2px 0; + font-size: 12px; line-height: 1.2; } -/* Workspace info styling */ -.workspace-info { - background-color: var(--bg-secondary); - border: 1px solid var(--border-primary); - border-radius: 0.375rem; - padding: 0.5rem; - font-size: 0.75rem; - color: var(--text-muted); +.file-tree::-webkit-scrollbar { + width: 6px; } -/* Loading states */ +.file-tree::-webkit-scrollbar-track { + background: var(--bg-secondary); +} + +.file-tree::-webkit-scrollbar-thumb { + background: var(--border-primary); + border-radius: 3px; +} + +.file-tree::-webkit-scrollbar-thumb:hover { + background: var(--text-secondary); +} + +.empty-state { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 200px; + color: var(--text-muted); + text-align: center; +} + +.empty-state i { + font-size: 24px; + margin-bottom: 8px; + opacity: 0.5; +} + +.empty-state p { + margin: 0; + font-size: 13px; +} + +/* Tree Items - Clean and Simple */ +.tree-item { + display: block; + margin: 0; + user-select: none; +} + +.tree-item-content { + display: flex; + align-items: center; + padding: 2px 4px; + border-radius: 3px; + cursor: pointer; + transition: background-color 0.15s ease; + min-height: 20px; +} + +.tree-item-content:hover { + background-color: var(--menu-item-hover-bg); +} + +.tree-checkbox { + margin: 0 6px 0 0; + cursor: pointer; + width: 12px; + height: 12px; +} + +.tree-expand-btn { + background: none; + border: none; + color: var(--text-secondary); + font-size: 10px; + width: 16px; + height: 16px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + margin-right: 2px; + border-radius: 2px; + transition: all 0.15s ease; +} + +.tree-expand-btn:hover { + background-color: var(--bg-tertiary); + color: var(--text-primary); +} + +.tree-expand-spacer { + width: 16px; + height: 16px; + margin-right: 2px; +} + +.tree-icon { + font-size: 12px; + margin-right: 6px; + width: 16px; + text-align: center; +} + +.tree-label { + flex: 1; + font-size: 12px; + color: var(--text-primary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + cursor: pointer; +} + +/* Workspace Panel */ +.workspace-panel { + background-color: var(--bg-secondary); + border: 1px solid var(--border-primary); + border-radius: 8px; + display: flex; + flex-direction: column; + overflow: hidden; +} + +.workspace-header { + background-color: var(--bg-tertiary); + border-bottom: 1px solid var(--border-primary); + padding: 0; +} + +.tab-navigation { + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px 16px; +} + +.modern-tabs { + display: flex; + gap: 2px; + border: none; +} + +.modern-tabs .nav-link { + background: transparent; + border: none; + color: var(--text-secondary); + padding: 8px 12px; + border-radius: 6px 6px 0 0; + font-size: 13px; + display: flex; + align-items: center; + gap: 6px; + transition: all 0.2s ease; + cursor: pointer; +} + +.modern-tabs .nav-link:hover { + background-color: var(--menu-item-hover-bg); + color: var(--text-primary); +} + +.modern-tabs .nav-link.active { + background-color: var(--bg-primary); + color: var(--text-primary); + border-bottom: 2px solid var(--link-color); +} + +.badge-count { + background-color: var(--bg-tertiary); + color: var(--text-primary); + font-size: 10px; + padding: 1px 4px; + border-radius: 8px; + min-width: 16px; + text-align: center; +} + +.workspace-actions { + display: flex; + align-items: center; + gap: 12px; +} + +.token-counter { + display: flex; + align-items: center; + gap: 4px; + font-size: 12px; + color: var(--text-secondary); +} + +.workspace-content { + flex: 1; + overflow: hidden; +} + +.tab-pane { + height: 100%; + overflow: hidden; + background-color: var(--bg-primary); +} + +/* Selection Workspace */ +.selection-workspace { + height: 100%; + display: flex; + flex-direction: column; + padding: 16px; +} + +.selection-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; + padding-bottom: 8px; + border-bottom: 1px solid var(--border-primary); +} + +.section-title { + font-size: 14px; + font-weight: 600; + color: var(--text-primary); + margin: 0; +} + +.selection-content { + flex: 1; + display: grid; + grid-template-columns: 1fr 1fr; + gap: 16px; + overflow: hidden; +} + +.selection-list-panel, +.preview-panel { + display: flex; + flex-direction: column; + background-color: var(--bg-secondary); + border: 1px solid var(--border-primary); + border-radius: 6px; + overflow: hidden; +} + +.panel-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px 12px; + background-color: var(--bg-tertiary); + border-bottom: 1px solid var(--border-primary); +} + +.panel-title { + font-size: 12px; + font-weight: 500; + color: var(--text-secondary); +} + +.selection-list, +.preview-content { + flex: 1; + overflow-y: auto; + padding: 8px; +} + +.selected-items { + list-style: none; + margin: 0; + padding: 0; +} + +.selected-items li { + display: flex; + justify-content: space-between; + align-items: center; + padding: 6px 8px; + margin: 2px 0; + background-color: var(--bg-primary); + border: 1px solid var(--border-primary); + border-radius: 4px; + font-size: 12px; +} + +.selected-items li:hover { + background-color: var(--bg-tertiary); +} + +.empty-selection, +.empty-preview { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 150px; + color: var(--text-muted); + text-align: center; +} + +.empty-selection i, +.empty-preview i { + font-size: 20px; + margin-bottom: 8px; + opacity: 0.5; +} + +.empty-selection p, +.empty-preview p { + margin: 4px 0; + font-size: 13px; +} + +.empty-selection small { + font-size: 11px; + opacity: 0.7; +} + +.file-preview { + background-color: var(--bg-primary); + color: var(--text-primary); + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace; + font-size: 11px; + line-height: 1.4; + white-space: pre-wrap; + padding: 12px; + overflow-y: auto; + height: 100%; +} + +/* Prompt Workspace */ +.prompt-workspace { + height: 100%; + display: grid; + grid-template-rows: 1fr 1fr; + gap: 16px; + padding: 16px; +} + +.prompt-editor, +.prompt-output { + display: flex; + flex-direction: column; + background-color: var(--bg-secondary); + border: 1px solid var(--border-primary); + border-radius: 6px; + overflow: hidden; +} + +.editor-header, +.output-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px 12px; + background-color: var(--bg-tertiary); + border-bottom: 1px solid var(--border-primary); +} + +.editor-content, +.output-content { + flex: 1; + padding: 12px; + overflow: hidden; +} + +.modern-textarea { + width: 100%; + background-color: var(--bg-primary); + border: 1px solid var(--border-primary); + color: var(--text-primary); + font-size: 13px; + line-height: 1.5; + padding: 12px; + border-radius: 4px; + resize: vertical; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; +} + +.modern-textarea:focus { + border-color: var(--link-color); + box-shadow: 0 0 0 2px rgba(0, 122, 255, 0.2); + outline: none; +} + +.editor-footer { + padding: 12px; + background-color: var(--bg-tertiary); + border-top: 1px solid var(--border-primary); + display: flex; + gap: 8px; +} + +.prompt-result { + background-color: var(--bg-primary); + color: var(--text-primary); + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace; + font-size: 11px; + line-height: 1.4; + white-space: pre-wrap; + padding: 12px; + overflow-y: auto; + height: 100%; + border: 1px solid var(--border-primary); + border-radius: 4px; +} + +.empty-output { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100px; + color: var(--text-muted); + text-align: center; +} + +.empty-output i { + font-size: 20px; + margin-bottom: 8px; + opacity: 0.5; +} + +.empty-output p { + margin: 0; + font-size: 13px; +} + +/* Chat Workspace */ +.chat-workspace { + height: 100%; + display: flex; + flex-direction: column; + padding: 16px; +} + +.chat-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; + padding-bottom: 8px; + border-bottom: 1px solid var(--border-primary); +} + +.chat-content { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; +} + +.chat-messages { + flex: 1; + background-color: var(--bg-secondary); + border: 1px solid var(--border-primary); + border-radius: 6px 6px 0 0; + overflow: hidden; +} + +.messages-container { + height: 100%; + overflow-y: auto; + padding: 16px; +} + +.welcome-message { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 150px; + color: var(--text-muted); + text-align: center; +} + +.welcome-message i { + font-size: 24px; + margin-bottom: 8px; + opacity: 0.5; +} + +.welcome-message p { + margin: 4px 0; + font-size: 14px; +} + +.welcome-message small { + font-size: 12px; + opacity: 0.7; +} + +.chat-input { + background-color: var(--bg-secondary); + border: 1px solid var(--border-primary); + border-top: none; + border-radius: 0 0 6px 6px; + padding: 12px; +} + +.input-container { + display: flex; + gap: 8px; + align-items: flex-end; +} + +.chat-textarea { + flex: 1; + background-color: var(--bg-primary); + border: 1px solid var(--border-primary); + color: var(--text-primary); + font-size: 13px; + line-height: 1.4; + padding: 8px 12px; + border-radius: 4px; + resize: none; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; +} + +.chat-textarea:focus { + border-color: var(--link-color); + box-shadow: 0 0 0 2px rgba(0, 122, 255, 0.2); + outline: none; +} + +.send-btn { + padding: 8px 12px; + border-radius: 4px; + display: flex; + align-items: center; + gap: 4px; +} + +/* Button styles */ +.btn { + border: none; + border-radius: 4px; + font-size: 13px; + font-weight: 500; + padding: 6px 12px; + cursor: pointer; + transition: all 0.2s ease; + display: inline-flex; + align-items: center; + gap: 4px; +} + +.btn-primary { + background-color: var(--link-color); + color: white; +} + +.btn-primary:hover { + background-color: var(--link-hover-color); +} + +.btn-secondary { + background-color: var(--bg-tertiary); + color: var(--text-primary); + border: 1px solid var(--border-primary); +} + +.btn-secondary:hover { + background-color: var(--menu-item-hover-bg); +} + +.btn-sm { + font-size: 12px; + padding: 4px 8px; +} + +/* Loading and status states */ .loading { opacity: 0.6; pointer-events: none; + position: relative; } .loading::after { @@ -245,9 +819,9 @@ position: absolute; top: 50%; left: 50%; - width: 20px; - height: 20px; - margin: -10px 0 0 -10px; + width: 16px; + height: 16px; + margin: -8px 0 0 -8px; border: 2px solid var(--border-primary); border-top-color: var(--link-color); border-radius: 50%; @@ -260,66 +834,68 @@ } } -/* Error states */ -.error-message { - color: var(--danger-color); - background-color: rgba(220, 53, 69, 0.1); - border: 1px solid var(--danger-color); - border-radius: 0.375rem; - padding: 0.5rem; - font-size: 0.875rem; -} - -/* Success states */ .success-message { color: var(--success-color); background-color: rgba(25, 135, 84, 0.1); border: 1px solid var(--success-color); - border-radius: 0.375rem; - padding: 0.5rem; - font-size: 0.875rem; + border-radius: 4px; + padding: 8px 12px; + font-size: 12px; } -/* Responsive adjustments */ +.error-message { + color: var(--danger-color); + background-color: rgba(220, 53, 69, 0.1); + border: 1px solid var(--danger-color); + border-radius: 4px; + padding: 8px 12px; + font-size: 12px; +} + +/* Responsive design */ @media (max-width: 768px) { - .tree { - font-size: 0.8rem; + .selection-content { + grid-template-columns: 1fr; + grid-template-rows: 1fr 1fr; } - .tab-pane { - height: calc(100vh - 150px); + .prompt-workspace { + grid-template-rows: 1fr 1fr; } - #preview, - #promptOutput { - font-size: 0.7rem; + .tab-navigation { + flex-direction: column; + gap: 8px; + align-items: stretch; + } + + .workspace-actions { + justify-content: center; } } -/* High contrast mode support */ +/* High contrast mode */ @media (prefers-contrast: high) { - .tree .dir-label:hover, - .tree .file:hover { + .tree-item-content:hover, + .btn-ghost:hover { background-color: var(--text-primary); color: var(--bg-primary); } - .btn-outline-primary, - .btn-outline-secondary, - .btn-outline-danger { + .btn-primary, + .btn-secondary { border-width: 2px; } } -/* Reduced motion support */ +/* Reduced motion */ @media (prefers-reduced-motion: reduce) { - .tree .chev, - .tree .dir-label, - .tree .file, - .form-control, - .btn { + .tree-expand-btn, + .btn, + .modern-tabs .nav-link, + .tree-item-content { transition: none; } diff --git a/lib/web/ui/static/js/heroprompt.js b/lib/web/ui/static/js/heroprompt.js index 3a9255c0..feb53b02 100644 --- a/lib/web/ui/static/js/heroprompt.js +++ b/lib/web/ui/static/js/heroprompt.js @@ -1,10 +1,17 @@ -console.log('Heroprompt UI loaded'); +console.log('Enhanced HeroPrompt UI loaded'); +// Global state let currentWs = localStorage.getItem('heroprompt-current-ws') || 'default'; -let selected = []; +let selected = new Set(); +let expandedDirs = new Set(); +let searchQuery = ''; +// Utility functions const el = (id) => document.getElementById(id); +const qs = (selector) => document.querySelector(selector); +const qsa = (selector) => document.querySelectorAll(selector); +// API helpers async function api(url) { try { const r = await fetch(url); @@ -13,8 +20,7 @@ async function api(url) { return { error: `HTTP ${r.status}` }; } return await r.json(); - } - catch (e) { + } catch (e) { console.warn(`API call error: ${url}`, e); return { error: 'request failed' }; } @@ -30,14 +36,13 @@ async function post(url, data) { return { error: `HTTP ${r.status}` }; } return await r.json(); - } - catch (e) { + } catch (e) { console.warn(`POST error: ${url}`, e); return { error: 'request failed' }; } } -// Bootstrap modal helpers +// Modal helpers function showModal(id) { const modalEl = el(id); if (modalEl) { @@ -54,277 +59,343 @@ function hideModal(id) { } } -// Tab switching with Bootstrap +// Tab management function switchTab(tabName) { - // Hide all tab panes - document.querySelectorAll('.tab-pane').forEach(pane => { - pane.style.display = 'none'; - pane.classList.remove('active'); - }); - - // Remove active class from all tabs - document.querySelectorAll('.tab').forEach(tab => { + // Update tab buttons + qsa('.tab').forEach(tab => { tab.classList.remove('active'); + if (tab.getAttribute('data-tab') === tabName) { + tab.classList.add('active'); + } }); - // Show selected tab pane - const targetPane = el(`tab-${tabName}`); - if (targetPane) { - targetPane.style.display = 'block'; - targetPane.classList.add('active'); - } - - // Add active class to clicked tab - const targetTab = document.querySelector(`.tab[data-tab="${tabName}"]`); - if (targetTab) { - targetTab.classList.add('active'); - } + // Update tab panes + qsa('.tab-pane').forEach(pane => { + pane.style.display = 'none'; + if (pane.id === `tab-${tabName}`) { + pane.style.display = 'block'; + } + }); } -// Initialize tab switching -document.addEventListener('DOMContentLoaded', function () { - document.querySelectorAll('.tab').forEach(tab => { - tab.addEventListener('click', function (e) { - e.preventDefault(); - const tabName = this.getAttribute('data-tab'); - switchTab(tabName); +// Simple and clean file tree implementation +class SimpleFileTree { + constructor(container) { + this.container = container; + this.loadedPaths = new Set(); + } + + createFileItem(item, path, depth = 0) { + const div = document.createElement('div'); + div.className = 'tree-item'; + div.style.paddingLeft = `${depth * 16}px`; + div.dataset.path = path; + div.dataset.type = item.type; + + const content = document.createElement('div'); + content.className = 'tree-item-content'; + + // Checkbox + const checkbox = document.createElement('input'); + checkbox.type = 'checkbox'; + checkbox.className = 'tree-checkbox'; + checkbox.checked = selected.has(path); + checkbox.addEventListener('change', () => { + if (checkbox.checked) { + selected.add(path); + } else { + selected.delete(path); + } + this.updateSelectionUI(); }); - }); -}); -// Checkbox-based collapsible tree -let nodeId = 0; + // Expand/collapse button for directories + let expandBtn = null; + if (item.type === 'directory') { + expandBtn = document.createElement('button'); + expandBtn.className = 'tree-expand-btn'; + expandBtn.innerHTML = expandedDirs.has(path) ? '▼' : '▶'; + expandBtn.addEventListener('click', (e) => { + e.stopPropagation(); + this.toggleDirectory(path, expandBtn); + }); + } else { + // Spacer for files to align with directories + expandBtn = document.createElement('span'); + expandBtn.className = 'tree-expand-spacer'; + } -function renderTree(displayName, fullPath) { - const c = document.createElement('div'); - c.className = 'tree'; - const ul = document.createElement('ul'); - ul.className = 'tree-root list-unstyled'; - const root = buildDirNode(displayName, fullPath, true); - ul.appendChild(root); - c.appendChild(ul); - return c; -} + // Icon + const icon = document.createElement('span'); + icon.className = 'tree-icon'; + icon.textContent = item.type === 'directory' ? '📁' : '📄'; -function buildDirNode(name, fullPath, expanded = false) { - const li = document.createElement('li'); - li.className = 'dir mb-1'; - const id = `tn_${nodeId++}`; - - const toggle = document.createElement('input'); - toggle.type = 'checkbox'; - toggle.className = 'toggle d-none'; - toggle.id = id; - if (expanded) toggle.checked = true; - - const label = document.createElement('label'); - label.htmlFor = id; - label.className = 'dir-label d-flex align-items-center text-decoration-none'; - label.style.cursor = 'pointer'; - - const icon = document.createElement('span'); - icon.className = 'chev me-1'; - icon.innerHTML = expanded ? '📂' : '📁'; - - const text = document.createElement('span'); - text.className = 'name flex-grow-1'; - text.textContent = name; - - label.appendChild(icon); - label.appendChild(text); - - const add = document.createElement('button'); - add.className = 'btn btn-sm btn-outline-primary ms-1'; - add.textContent = '+'; - add.title = 'Add directory to selection'; - add.onclick = (e) => { - e.stopPropagation(); - addDirToSelection(fullPath); - }; - - const children = document.createElement('ul'); - children.className = 'children list-unstyled ms-3'; - children.style.display = expanded ? 'block' : 'none'; - - toggle.addEventListener('change', async () => { - if (toggle.checked) { - children.style.display = 'block'; - icon.innerHTML = '📂'; - if (!li.dataset.loaded) { - await loadChildren(fullPath, children); - li.dataset.loaded = '1'; + // Label + const label = document.createElement('span'); + label.className = 'tree-label'; + label.textContent = item.name; + label.addEventListener('click', () => { + if (item.type === 'file') { + this.previewFile(path); + } else { + this.toggleDirectory(path, expandBtn); } + }); + + content.appendChild(checkbox); + content.appendChild(expandBtn); + content.appendChild(icon); + content.appendChild(label); + div.appendChild(content); + + return div; + } + + async toggleDirectory(dirPath, expandBtn) { + const isExpanded = expandedDirs.has(dirPath); + + if (isExpanded) { + // Collapse + expandedDirs.delete(dirPath); + expandBtn.innerHTML = '▶'; + this.removeChildren(dirPath); } else { - children.style.display = 'none'; - icon.innerHTML = '📁'; - } - }); - - // Load immediately if expanded by default - if (expanded) { - setTimeout(async () => { - await loadChildren(fullPath, children); - li.dataset.loaded = '1'; - }, 0); - } - - li.appendChild(toggle); - li.appendChild(label); - li.appendChild(add); - li.appendChild(children); - return li; -} - -function createFileNode(name, fullPath) { - const li = document.createElement('li'); - li.className = 'file d-flex align-items-center mb-1'; - - const icon = document.createElement('span'); - icon.className = 'me-2'; - icon.innerHTML = '📄'; - - const a = document.createElement('a'); - a.href = '#'; - a.className = 'text-decoration-none flex-grow-1'; - a.textContent = name; - a.onclick = (e) => { - e.preventDefault(); - previewFile(fullPath); - }; - - const add = document.createElement('button'); - add.className = 'btn btn-sm btn-outline-primary ms-1'; - add.textContent = '+'; - add.title = 'Add file to selection'; - add.onclick = (e) => { - e.stopPropagation(); - addFileToSelection(fullPath); - }; - - li.appendChild(icon); - li.appendChild(a); - li.appendChild(add); - return li; -} - -async function previewFile(filePath) { - const previewEl = el('preview'); - if (!previewEl) return; - - previewEl.innerHTML = '
No files selected
+ Use checkboxes in the explorer to select files +No workspaces available
+ Create one to get started +Select a workspace to browse files
+