Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/renderer/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,9 @@
<button id="btn-open-ssh-folder" class="section-btn" title="Browse SSH remote files (requires active SSH connection in Live panel)" aria-label="Browse SSH remote files">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="2" width="20" height="20" rx="2"/><path d="M7 8l4 4-4 4"/><line x1="13" y1="16" x2="17" y2="16"/></svg>
</button>
<button id="btn-reveal-active-file" class="section-btn" title="Reveal the active file in the folder tree" aria-label="Reveal active file">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M12 2v3M12 19v3M2 12h3M19 12h3"/></svg>
</button>
</div>
<div class="folder-search-container">
<input type="text" id="folder-search-input" class="folder-search-input" placeholder="Search in folders..." disabled>
Expand Down
60 changes: 60 additions & 0 deletions src/renderer/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,7 @@ interface AppState {
// "Mute" patterns: rows whose text contains any of these are dimmed IN PLACE
// (kept for context, not removed) via a render-time class. Persisted globally.
mutePatterns: string[];
mutedSize: 'normal' | 'small' | 'tiny'; // opt-in smaller font for muted lines (mute menu)
// Traceback
tracebackResult: any | null;
tracebackSort: 'time' | 'score';
Expand Down Expand Up @@ -419,6 +420,7 @@ const state: AppState = {
hiddenLevels: new Set<string>(JSON.parse(localStorage.getItem('logan-hidden-levels') || '[]')),
levelBarFilterActive: false,
mutePatterns: JSON.parse(localStorage.getItem('logan-mute-patterns') || '[]'),
mutedSize: (localStorage.getItem('logan-muted-size') as 'normal' | 'small' | 'tiny') || 'normal',
tracebackResult: null,
tracebackSort: 'time' as 'time' | 'score',
tracebackFilterCat: 'all',
Expand Down Expand Up @@ -2876,6 +2878,18 @@ function clearMutePatterns(): void {
renderVisibleLines();
}

// Opt-in smaller font for muted lines (default 'normal' = as is). Applied via a body class
// so it works for already- and later-rendered rows without a re-render. Persisted.
function applyMutedSize(): void {
document.body.classList.toggle('muted-size-small', state.mutedSize === 'small');
document.body.classList.toggle('muted-size-tiny', state.mutedSize === 'tiny');
}
function setMutedSize(size: 'normal' | 'small' | 'tiny'): void {
state.mutedSize = size;
localStorage.setItem('logan-muted-size', size);
applyMutedSize();
}

function createLineElementPooled(line: LogLine): HTMLDivElement {
const div = lineElementPool.acquire();
div.dataset.lineNumber = String(line.lineNumber);
Expand Down Expand Up @@ -4740,6 +4754,12 @@ function handleContextMenu(event: MouseEvent): void {
menu.remove();
});
menu.appendChild(clearMutes);
// Muted line SIZE — opt-in smaller font so noise recedes (default As is). ✓ marks current.
for (const [label, val] of [['As is', 'normal'], ['Small', 'small'], ['Tiny', 'tiny']] as const) {
const it = menuItem(state.mutedSize === val ? '✓' : ' ', `Muted size: ${label}`);
it.addEventListener('click', () => { setMutedSize(val); menu.remove(); });
menu.appendChild(it);
}
menu.appendChild(menuSeparator());
}

Expand Down Expand Up @@ -5689,6 +5709,44 @@ function findSubfolder(entries: LocalFolderFile[], subdirPath: string): LocalFol
return null;
}

// Reveal the active file in the folder tree: expand the folder + the ancestor subdirs on
// its path, re-render, then scroll it into view + flash it. The active row already carries
// the `.active` class (see renderFolderEntries); this just makes it visible + findable.
function revealActiveFileInTree(): void {
if (!state.filePath) { showToast('No active file to reveal'); return; }
const target = state.filePath;
let matched = false;
for (const folder of state.folders) {
const base = folder.path.replace(/[\\/]+$/, '');
if (target !== base && !target.startsWith(base + '/') && !target.startsWith(base + '\\')) continue;
matched = true;
folder.collapsed = false;
// Un-collapse each ancestor directory of the file (those present in the loaded tree).
const rel = target.slice(base.length).replace(/^[\\/]+/, '');
const parts = rel.split(/[\\/]/);
parts.pop(); // drop the filename
let acc = base;
for (const part of parts) {
acc = acc + '/' + part;
const sub = findSubfolder(folder.files, acc) || findSubfolder(folder.files, acc.replace(/\//g, '\\'));
if (sub) sub.collapsed = false;
}
break;
}
if (!matched) { showToast('Active file isn’t under a folder in the tree — add its folder first'); return; }
renderFolderTree();
requestAnimationFrame(() => {
const el = elements.foldersList.querySelector('.folder-file.active') as HTMLElement | null;
if (el) {
el.scrollIntoView({ block: 'center', behavior: 'smooth' });
el.classList.add('reveal-flash');
setTimeout(() => el.classList.remove('reveal-flash'), 1400);
} else {
showToast('Located its folder, but the file row isn’t loaded yet — expand that folder');
}
});
}

function renderFolderTree(): void {
if (state.folders.length === 0) {
elements.foldersList.innerHTML = '<p class="placeholder">No folders open</p>';
Expand Down Expand Up @@ -22586,6 +22644,7 @@ function setupHelpTooltips(): void {
function init(): void {
// Detect platform and setup window controls
setupWindowControls();
applyMutedSize(); // restore the saved muted-line size (default: as is)

// Load user settings from localStorage
loadSettings();
Expand Down Expand Up @@ -23050,6 +23109,7 @@ function init(): void {
elements.btnLiveSshRefresh.addEventListener('click', () => refreshSshHosts());
elements.btnLiveSshManage.addEventListener('click', () => showSshProfileManager());
elements.btnOpenSshFolder.addEventListener('click', () => openSshFolder());
document.getElementById('btn-reveal-active-file')?.addEventListener('click', () => revealActiveFileInTree());

// Unified live connection event listeners (register once)
setupLiveEventListeners();
Expand Down
18 changes: 13 additions & 5 deletions src/renderer/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -1550,6 +1550,12 @@ body.platform-darwin .titlebar {
background: var(--accent-color);
color: white;
}
/* Brief flash when "Reveal active file" scrolls to it, so the eye catches it. */
.folder-file.reveal-flash { animation: folder-reveal-flash 1.3s ease-out; }
@keyframes folder-reveal-flash {
0%, 30% { box-shadow: inset 0 0 0 2px var(--accent-color, #5b8def); }
100% { box-shadow: inset 0 0 0 2px transparent; }
}

.folder-file-icon {
width: 14px;
Expand Down Expand Up @@ -2643,17 +2649,19 @@ button.is-cancelable:hover, .btn.is-cancelable:hover { filter: brightness(1.12);
to bring it back to full opacity so it's still readable on demand. */
.log-line.muted {
opacity: 0.28;
/* Smaller text so muted "noise" lines read as lighter/shorter. The ROW height stays
var(--line-height) — the virtual scroller needs a uniform per-line height — so this
shrinks the text within the row, not the row itself. Full size + opacity on hover. */
font-size: 0.78em;
transition: opacity 0.12s ease, font-size 0.1s ease;
}
.log-line.muted .line-number { font-size: 0.85em; }
.log-line.muted:hover {
opacity: 1;
font-size: 1em;
}
/* Opt-in muted-line SIZE (mute menu → "Muted size"). Default = As is (no class on body).
Shrinks the text within the fixed 20px row (the virtual scroller needs a uniform row
height, so this does NOT reduce the row height). Full size on hover regardless. */
.muted-size-small .log-line.muted { font-size: 0.82em; }
.muted-size-small .log-line.muted .line-number { font-size: 0.88em; }
.muted-size-tiny .log-line.muted { font-size: 0.66em; }
.muted-size-tiny .log-line.muted .line-number { font-size: 0.78em; }

.line-number {
min-width: 60px;
Expand Down
Loading