css refactor

This commit is contained in:
2026-02-19 18:24:36 +03:00
parent 4de5b22d57
commit fdcbe32dfa
45 changed files with 1798 additions and 1857 deletions

View File

@@ -12,25 +12,21 @@
@UX_STATE: AutoScroll -> Automatically scrolls to bottom on new logs
-->
<script>
import { createEventDispatcher, onMount, afterUpdate } from "svelte";
import { createEventDispatcher, onMount, tick } from "svelte";
import LogFilterBar from "./LogFilterBar.svelte";
import LogEntryRow from "./LogEntryRow.svelte";
/**
* @PURPOSE Component properties and state.
* @PRE logs is an array of LogEntry objects.
*/
export let logs = [];
export let autoScroll = true;
let { logs = [], autoScroll = $bindable(true) } = $props();
const dispatch = createEventDispatcher();
let scrollContainer;
let selectedSource = "all";
let selectedLevel = "all";
let searchText = "";
let selectedSource = $state("all");
let selectedLevel = $state("all");
let searchText = $state("");
// Filtered logs based on current filters
$: filteredLogs = filterLogs(logs, selectedLevel, selectedSource, searchText);
let filteredLogs = $derived(
filterLogs(logs, selectedLevel, selectedSource, searchText),
);
function filterLogs(allLogs, level, source, search) {
return allLogs.filter((log) => {
@@ -52,17 +48,15 @@
});
}
// Extract unique sources from logs
$: availableSources = [...new Set(logs.map((l) => l.source).filter(Boolean))];
let availableSources = $derived([
...new Set(logs.map((l) => l.source).filter(Boolean)),
]);
function handleFilterChange(event) {
const { source, level, search } = event.detail;
selectedSource = source || "all";
selectedLevel = level || "all";
searchText = search || "";
console.log(
`[TaskLogPanel][Action] Filter: level=${selectedLevel}, source=${selectedSource}, search=${searchText}`,
);
dispatch("filterChange", { source, level });
}
@@ -77,8 +71,11 @@
if (autoScroll) scrollToBottom();
}
afterUpdate(() => {
scrollToBottom();
// Use $effect instead of afterUpdate for runes mode
$effect(() => {
// Track filteredLogs length to trigger scroll
filteredLogs.length;
tick().then(scrollToBottom);
});
onMount(() => {
@@ -86,14 +83,19 @@
});
</script>
<div class="log-panel">
<div class="flex flex-col h-full bg-terminal-bg overflow-hidden">
<!-- Filter Bar -->
<LogFilterBar {availableSources} on:filter-change={handleFilterChange} />
<!-- Log List -->
<div bind:this={scrollContainer} class="log-list">
<div
bind:this={scrollContainer}
class="flex-1 overflow-y-auto overflow-x-hidden"
>
{#if filteredLogs.length === 0}
<div class="empty-logs">
<div
class="flex flex-col items-center justify-center py-12 px-4 text-terminal-border gap-3"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="32"
@@ -109,7 +111,9 @@
<line x1="16" y1="17" x2="8" y2="17" />
<polyline points="10 9 9 9 8 9" />
</svg>
<span>No logs available</span>
<span class="text-[0.8125rem] text-terminal-text-muted"
>No logs available</span
>
</div>
{:else}
{#each filteredLogs as log}
@@ -119,129 +123,27 @@
</div>
<!-- Footer Stats -->
<div class="log-footer">
<span class="log-count">
<div
class="flex items-center justify-between py-1.5 px-3 border-t border-terminal-surface bg-terminal-bg"
>
<span class="font-mono text-[0.6875rem] text-terminal-text-muted">
{filteredLogs.length}{filteredLogs.length !== logs.length
? ` / ${logs.length}`
: ""} entries
</span>
<button
class="autoscroll-btn"
class:active={autoScroll}
class="flex items-center gap-1.5 bg-transparent border-none text-terminal-text-muted text-[0.6875rem] cursor-pointer py-px px-1.5 rounded transition-all hover:bg-terminal-surface hover:text-terminal-text-subtle
{autoScroll ? 'text-terminal-accent' : ''}"
on:click={toggleAutoScroll}
aria-label="Toggle auto-scroll"
>
{#if autoScroll}
<span class="pulse-dot"></span>
<span
class="inline-block w-[5px] h-[5px] rounded-full bg-terminal-accent animate-pulse"
></span>
{/if}
Auto-scroll {autoScroll ? "on" : "off"}
</button>
</div>
</div>
<!-- [/DEF:TaskLogPanel:Component] -->
<style>
.log-panel {
display: flex;
flex-direction: column;
height: 100%;
background-color: #0f172a;
overflow: hidden;
}
.log-list {
flex: 1;
overflow-y: auto;
overflow-x: hidden;
}
/* Custom scrollbar */
.log-list::-webkit-scrollbar {
width: 6px;
}
.log-list::-webkit-scrollbar-track {
background: #0f172a;
}
.log-list::-webkit-scrollbar-thumb {
background: #334155;
border-radius: 3px;
}
.log-list::-webkit-scrollbar-thumb:hover {
background: #475569;
}
.empty-logs {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 3rem 1rem;
color: #334155;
gap: 0.75rem;
}
.empty-logs span {
font-size: 0.8125rem;
color: #475569;
}
.log-footer {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.375rem 0.75rem;
border-top: 1px solid #1e293b;
background-color: #0f172a;
}
.log-count {
font-family: "JetBrains Mono", "Fira Code", monospace;
font-size: 0.6875rem;
color: #475569;
}
.autoscroll-btn {
display: flex;
align-items: center;
gap: 0.375rem;
background: none;
border: none;
color: #475569;
font-size: 0.6875rem;
cursor: pointer;
padding: 0.125rem 0.375rem;
border-radius: 0.25rem;
transition: all 0.15s;
}
.autoscroll-btn:hover {
background-color: #1e293b;
color: #94a3b8;
}
.autoscroll-btn.active {
color: #22d3ee;
}
.pulse-dot {
display: inline-block;
width: 5px;
height: 5px;
border-radius: 50%;
background-color: #22d3ee;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.3;
}
}
</style>