Update agents, dashboard, kb +2 more (+14 ~20 -3)
This commit is contained in:
@@ -102,6 +102,149 @@
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.sort-select {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--text-secondary);
|
||||
font-size: var(--text-sm);
|
||||
padding: var(--space-1) var(--space-2);
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.sort-select option {
|
||||
background: var(--bg-base);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* ========== LIST VIEW - Windows Explorer style ========== */
|
||||
.file-grid.view-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
||||
gap: var(--space-1);
|
||||
}
|
||||
|
||||
.file-grid.view-list .file-item {
|
||||
flex-direction: row;
|
||||
padding: var(--space-1) var(--space-2);
|
||||
gap: var(--space-2);
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.file-grid.view-list .file-item:hover {
|
||||
background: var(--bg-surface-hover);
|
||||
}
|
||||
|
||||
.file-grid.view-list .file-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.file-grid.view-list .file-icon svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.file-grid.view-list .file-name {
|
||||
font-size: var(--text-sm);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.file-grid.view-list .file-meta {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ========== DETAILS VIEW - Windows Explorer style with columns ========== */
|
||||
.file-grid.view-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.file-grid.view-details .file-header {
|
||||
display: grid;
|
||||
grid-template-columns: 24px 1fr 100px 80px 120px;
|
||||
align-items: center;
|
||||
padding: var(--space-2) var(--space-3);
|
||||
gap: var(--space-3);
|
||||
background: var(--bg-surface);
|
||||
border-bottom: 2px solid var(--border);
|
||||
font-size: var(--text-xs);
|
||||
font-weight: 600;
|
||||
color: var(--text-muted);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.file-grid.view-details .file-header span {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.file-grid.view-details .file-header span:hover {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.file-grid.view-details .file-item {
|
||||
display: grid;
|
||||
grid-template-columns: 24px 1fr 100px 80px 120px;
|
||||
align-items: center;
|
||||
padding: var(--space-2) var(--space-3);
|
||||
gap: var(--space-3);
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-bottom: 1px solid var(--border);
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.file-grid.view-details .file-item:hover {
|
||||
background: var(--bg-surface-hover);
|
||||
}
|
||||
|
||||
.file-grid.view-details .file-icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.file-grid.view-details .file-icon svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.file-grid.view-details .file-name {
|
||||
font-size: var(--text-sm);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.file-grid.view-details .file-meta {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.file-grid.view-details .file-type,
|
||||
.file-grid.view-details .file-size,
|
||||
.file-grid.view-details .file-date {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--text-muted);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.file-grid.view-details .file-type {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
/* ========== TILES VIEW - Original grid style ========== */
|
||||
.file-grid.view-tiles {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
/* Content area */
|
||||
.content-area {
|
||||
flex: 1;
|
||||
@@ -269,6 +412,8 @@
|
||||
#markdownPreview code { background: var(--bg-surface); padding: 2px 6px; border-radius: 4px; font-family: var(--font-mono); }
|
||||
#markdownPreview pre { background: var(--bg-surface); padding: 1em; border-radius: 8px; overflow-x: auto; }
|
||||
#markdownPreview blockquote { border-left: 3px solid var(--accent); padding-left: 1em; margin-left: 0; color: var(--text-muted); }
|
||||
#markdownPreview a, #markdownPreview .file-link { color: var(--accent); text-decoration: none; }
|
||||
#markdownPreview a:hover, #markdownPreview .file-link:hover { text-decoration: underline; }
|
||||
|
||||
.preview-active #codeEditor { display: none; }
|
||||
.preview-active #markdownPreview { display: block; }
|
||||
@@ -348,9 +493,36 @@
|
||||
<span class="breadcrumb-item current" onclick="loadPath('')">~/clawd</span>
|
||||
</div>
|
||||
<div class="toolbar-actions">
|
||||
<!-- View Mode Toggle -->
|
||||
<div class="view-toggle" id="viewModeToggle">
|
||||
<button class="view-btn" data-view="list" onclick="setViewMode('list')" title="Listă">
|
||||
<i data-lucide="list"></i>
|
||||
</button>
|
||||
<button class="view-btn" data-view="details" onclick="setViewMode('details')" title="Detalii">
|
||||
<i data-lucide="layout-list"></i>
|
||||
</button>
|
||||
<button class="view-btn active" data-view="tiles" onclick="setViewMode('tiles')" title="Tiles">
|
||||
<i data-lucide="layout-grid"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Sort Toggle -->
|
||||
<div class="view-toggle">
|
||||
<select class="sort-select" id="sortBy" onchange="sortFiles()">
|
||||
<option value="name">Nume</option>
|
||||
<option value="type">Tip</option>
|
||||
<option value="size">Mărime</option>
|
||||
<option value="date">Dată</option>
|
||||
</select>
|
||||
<button class="view-btn" id="sortDirBtn" onclick="toggleSortDir()" title="Ordine">
|
||||
<i data-lucide="arrow-down-a-z" id="sortDirIcon"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Browse/Editor Toggle -->
|
||||
<div class="view-toggle">
|
||||
<button class="view-btn active" id="browseBtn" onclick="showBrowse()" title="Browse">
|
||||
<i data-lucide="layout-grid"></i>
|
||||
<i data-lucide="folder"></i>
|
||||
</button>
|
||||
<button class="view-btn" id="editorBtn" onclick="showEditor()" title="Editor">
|
||||
<i data-lucide="code"></i>
|
||||
@@ -435,6 +607,73 @@
|
||||
let currentFile = null;
|
||||
let originalContent = '';
|
||||
let isModified = false;
|
||||
let currentViewMode = localStorage.getItem('filesViewMode') || 'tiles';
|
||||
let currentSortBy = localStorage.getItem('filesSortBy') || 'name';
|
||||
let currentSortDir = localStorage.getItem('filesSortDir') || 'asc';
|
||||
let currentItems = [];
|
||||
|
||||
// Initialize view mode
|
||||
function initViewMode() {
|
||||
setViewMode(currentViewMode, false);
|
||||
document.getElementById('sortBy').value = currentSortBy;
|
||||
updateSortIcon();
|
||||
}
|
||||
|
||||
function setViewMode(mode, reload = true) {
|
||||
currentViewMode = mode;
|
||||
localStorage.setItem('filesViewMode', mode);
|
||||
|
||||
// Update buttons
|
||||
document.querySelectorAll('#viewModeToggle .view-btn').forEach(btn => {
|
||||
btn.classList.toggle('active', btn.dataset.view === mode);
|
||||
});
|
||||
|
||||
// Update grid class
|
||||
const grid = document.getElementById('fileGrid');
|
||||
grid.classList.remove('view-list', 'view-details', 'view-tiles');
|
||||
grid.classList.add('view-' + mode);
|
||||
|
||||
if (reload && currentItems.length > 0) {
|
||||
renderFileGrid(currentItems);
|
||||
}
|
||||
}
|
||||
|
||||
function sortFiles() {
|
||||
currentSortBy = document.getElementById('sortBy').value;
|
||||
localStorage.setItem('filesSortBy', currentSortBy);
|
||||
if (currentItems.length > 0) {
|
||||
renderFileGrid(currentItems);
|
||||
}
|
||||
}
|
||||
|
||||
function setSortBy(field) {
|
||||
if (currentSortBy === field) {
|
||||
toggleSortDir();
|
||||
} else {
|
||||
currentSortBy = field;
|
||||
document.getElementById('sortBy').value = field;
|
||||
localStorage.setItem('filesSortBy', currentSortBy);
|
||||
if (currentItems.length > 0) {
|
||||
renderFileGrid(currentItems);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function toggleSortDir() {
|
||||
currentSortDir = currentSortDir === 'asc' ? 'desc' : 'asc';
|
||||
localStorage.setItem('filesSortDir', currentSortDir);
|
||||
updateSortIcon();
|
||||
if (currentItems.length > 0) {
|
||||
renderFileGrid(currentItems);
|
||||
}
|
||||
}
|
||||
|
||||
function updateSortIcon() {
|
||||
const icon = document.getElementById('sortDirIcon');
|
||||
const iconName = currentSortDir === 'asc' ? 'arrow-down-a-z' : 'arrow-up-z-a';
|
||||
icon.setAttribute('data-lucide', iconName);
|
||||
lucide.createIcons();
|
||||
}
|
||||
|
||||
function showBrowse() {
|
||||
if (isModified && !confirm('Ai modificări nesalvate. Continui?')) return;
|
||||
@@ -498,7 +737,12 @@
|
||||
}
|
||||
|
||||
function renderFileGrid(items) {
|
||||
// Store items for re-rendering on view/sort change
|
||||
currentItems = items;
|
||||
|
||||
const grid = document.getElementById('fileGrid');
|
||||
grid.classList.remove('view-list', 'view-details', 'view-tiles');
|
||||
grid.classList.add('view-' + currentViewMode);
|
||||
|
||||
if (items.length === 0) {
|
||||
grid.innerHTML = `
|
||||
@@ -511,20 +755,82 @@
|
||||
return;
|
||||
}
|
||||
|
||||
items.sort((a, b) => {
|
||||
// Sort items
|
||||
const getExt = (name) => name.includes('.') ? name.split('.').pop().toLowerCase() : '';
|
||||
const sorted = [...items].sort((a, b) => {
|
||||
// Directories always first
|
||||
if (a.type !== b.type) return a.type === 'dir' ? -1 : 1;
|
||||
return a.name.localeCompare(b.name);
|
||||
|
||||
let cmp = 0;
|
||||
if (currentSortBy === 'name') {
|
||||
cmp = a.name.localeCompare(b.name);
|
||||
} else if (currentSortBy === 'date') {
|
||||
cmp = (a.mtime || 0) - (b.mtime || 0);
|
||||
} else if (currentSortBy === 'size') {
|
||||
cmp = (a.size || 0) - (b.size || 0);
|
||||
} else if (currentSortBy === 'type') {
|
||||
cmp = getExt(a.name).localeCompare(getExt(b.name));
|
||||
}
|
||||
return currentSortDir === 'asc' ? cmp : -cmp;
|
||||
});
|
||||
|
||||
grid.innerHTML = items.map(item => `
|
||||
<div class="file-item ${currentFile === item.path ? 'active' : ''}" onclick="handleClick('${item.path}', '${item.type}')">
|
||||
<div class="file-icon ${item.type === 'dir' ? 'folder' : ''}">
|
||||
<i data-lucide="${item.type === 'dir' ? 'folder' : getFileIcon(item.name)}"></i>
|
||||
// Add header for details view
|
||||
let headerHtml = '';
|
||||
if (currentViewMode === 'details') {
|
||||
const arrow = (field) => currentSortBy === field ? (currentSortDir === 'asc' ? ' ▲' : ' ▼') : '';
|
||||
headerHtml = `
|
||||
<div class="file-header">
|
||||
<span></span>
|
||||
<span onclick="setSortBy('name')" style="cursor:pointer">Nume${arrow('name')}</span>
|
||||
<span onclick="setSortBy('type')" style="cursor:pointer">Tip${arrow('type')}</span>
|
||||
<span onclick="setSortBy('size')" style="cursor:pointer">Mărime${arrow('size')}</span>
|
||||
<span onclick="setSortBy('date')" style="cursor:pointer">Dată${arrow('date')}</span>
|
||||
</div>
|
||||
<div class="file-name">${item.name}</div>
|
||||
${item.size ? `<div class="file-size">${formatSize(item.size)}</div>` : ''}
|
||||
</div>
|
||||
`).join('');
|
||||
`;
|
||||
}
|
||||
|
||||
grid.innerHTML = headerHtml + sorted.map(item => {
|
||||
const dateStr = item.mtime ? new Date(item.mtime * 1000).toLocaleString('ro-RO', {
|
||||
day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit'
|
||||
}) : '';
|
||||
const fileType = item.type === 'dir' ? 'Folder' : getFileType(item.name);
|
||||
const sizeStr = item.size !== undefined ? formatSize(item.size) : '-';
|
||||
|
||||
if (currentViewMode === 'details') {
|
||||
return `
|
||||
<div class="file-item ${currentFile === item.path ? 'active' : ''}" onclick="handleClick('${item.path}', '${item.type}')">
|
||||
<div class="file-icon ${item.type === 'dir' ? 'folder' : ''}">
|
||||
<i data-lucide="${item.type === 'dir' ? 'folder' : getFileIcon(item.name)}"></i>
|
||||
</div>
|
||||
<div class="file-name">${item.name}</div>
|
||||
<div class="file-meta">
|
||||
<span class="file-type">${fileType}</span>
|
||||
<span class="file-size">${sizeStr}</span>
|
||||
<span class="file-date">${dateStr || '-'}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
} else if (currentViewMode === 'list') {
|
||||
return `
|
||||
<div class="file-item ${currentFile === item.path ? 'active' : ''}" onclick="handleClick('${item.path}', '${item.type}')">
|
||||
<div class="file-icon ${item.type === 'dir' ? 'folder' : ''}">
|
||||
<i data-lucide="${item.type === 'dir' ? 'folder' : getFileIcon(item.name)}"></i>
|
||||
</div>
|
||||
<div class="file-name">${item.name}</div>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
// Tiles view - original style
|
||||
return `
|
||||
<div class="file-item ${currentFile === item.path ? 'active' : ''}" onclick="handleClick('${item.path}', '${item.type}')">
|
||||
<div class="file-icon ${item.type === 'dir' ? 'folder' : ''}">
|
||||
<i data-lucide="${item.type === 'dir' ? 'folder' : getFileIcon(item.name)}"></i>
|
||||
</div>
|
||||
<div class="file-name">${item.name}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}).join('');
|
||||
|
||||
lucide.createIcons();
|
||||
}
|
||||
@@ -549,6 +855,26 @@
|
||||
return icons[ext] || 'file';
|
||||
}
|
||||
|
||||
function getFileType(name) {
|
||||
const ext = name.split('.').pop().toLowerCase();
|
||||
const types = {
|
||||
'md': 'Markdown',
|
||||
'txt': 'Text',
|
||||
'json': 'JSON',
|
||||
'js': 'JavaScript',
|
||||
'py': 'Python',
|
||||
'html': 'HTML',
|
||||
'css': 'CSS',
|
||||
'sh': 'Shell',
|
||||
'yml': 'YAML',
|
||||
'yaml': 'YAML',
|
||||
'log': 'Log',
|
||||
'xsd': 'XML Schema',
|
||||
'pdf': 'PDF'
|
||||
};
|
||||
return types[ext] || ext.toUpperCase();
|
||||
}
|
||||
|
||||
function formatSize(bytes) {
|
||||
if (bytes < 1024) return bytes + ' B';
|
||||
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
|
||||
@@ -701,6 +1027,7 @@
|
||||
});
|
||||
|
||||
// Init
|
||||
initViewMode();
|
||||
loadPath(getPathFromURL());
|
||||
</script>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user