css refactor
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user