From 30318c62ea10b500ba4d724a9143bc33770c7af9 Mon Sep 17 00:00:00 2001 From: Abdenasser Date: Sat, 9 Nov 2024 18:23:24 +0100 Subject: [PATCH] adds resizable columns + other enhancements --- src/lib/components/ProcessTable.svelte | 232 ++++++++++++++++++++++-- src/lib/components/ThemeSwitcher.svelte | 13 +- src/lib/components/ToolBar.svelte | 50 ++++- src/routes/+page.svelte | 4 +- 4 files changed, 273 insertions(+), 26 deletions(-) diff --git a/src/lib/components/ProcessTable.svelte b/src/lib/components/ProcessTable.svelte index b80e7ee..dc0bffa 100644 --- a/src/lib/components/ProcessTable.svelte +++ b/src/lib/components/ProcessTable.svelte @@ -3,6 +3,7 @@ faThumbtack, faInfoCircle, faXmark, + faGripLinesVertical, } from "@fortawesome/free-solid-svg-icons"; import Fa from "svelte-fa"; import type { Process, Column } from "$lib/types"; @@ -19,6 +20,88 @@ export let onShowDetails: (process: Process) => void; export let onKillProcess: (process: Process) => void; + interface ResizeState { + leftColumnId: string | null; + rightColumnId: string | null; + startX: number; + leftStartWidth: number; + rightStartWidth: number; + } + + let resizing: ResizeState = { + leftColumnId: null, + rightColumnId: null, + startX: 0, + leftStartWidth: 0, + rightStartWidth: 0, + }; + + // Store column widths + let columnWidths: Record = {}; + + // Initialize default widths + $: { + columns.forEach((col) => { + if (!columnWidths[col.id]) { + columnWidths[col.id] = 100; // Default width + } + }); + } + + function handleResizeStart( + event: MouseEvent, + leftColId: string, + rightColId: string, + ) { + event.stopPropagation(); + + resizing = { + leftColumnId: leftColId, + rightColumnId: rightColId, + startX: event.pageX, + leftStartWidth: columnWidths[leftColId], + rightStartWidth: columnWidths[rightColId], + }; + + document.addEventListener("mousemove", handleResizeMove); + document.addEventListener("mouseup", handleResizeEnd); + } + + function handleResizeMove(event: MouseEvent) { + if (!resizing.leftColumnId || !resizing.rightColumnId) return; + + const delta = event.pageX - resizing.startX; + + // Ensure minimum width (50px) for both columns + const newLeftWidth = Math.max(50, resizing.leftStartWidth + delta); + const totalWidth = resizing.leftStartWidth + resizing.rightStartWidth; + const newRightWidth = Math.max(50, totalWidth - newLeftWidth); + + // Only update if both columns maintain minimum width + if (newLeftWidth >= 50 && newRightWidth >= 50) { + columnWidths = { + ...columnWidths, + [resizing.leftColumnId]: newLeftWidth, + [resizing.rightColumnId]: newRightWidth, + }; + } + } + + function handleResizeEnd() { + resizing = { + leftColumnId: null, + rightColumnId: null, + startX: 0, + leftStartWidth: 0, + rightStartWidth: 0, + }; + document.removeEventListener("mousemove", handleResizeMove); + document.removeEventListener("mouseup", handleResizeEnd); + + // Optional: Save to localStorage + localStorage.setItem("columnWidths", JSON.stringify(columnWidths)); + } + function getSortIndicator(field: keyof Process) { if (sortConfig.field !== field) return "↕"; return sortConfig.direction === "asc" ? "↑" : "↓"; @@ -90,20 +173,38 @@ - {#each columns.filter((col) => col.visible) as column} - {/each} - + @@ -252,27 +353,63 @@ } .th-content { + display: flex; + align-items: center; + justify-content: space-between; + padding: 8px; + cursor: pointer; + user-select: none; + } + + .th-label { display: flex; align-items: center; gap: 8px; + flex: 1; } - .sort-indicator { + .resize-handle { + width: 16px; + height: 16px; + display: flex; + align-items: center; + justify-content: center; + cursor: col-resize; color: var(--overlay0); - font-size: 12px; - opacity: 0.5; + opacity: 0; transition: all 0.2s ease; + margin-left: 8px; } - .sort-indicator.active { + /* Show handle on header hover */ + th:hover .resize-handle { + opacity: 1; + } + + .resize-handle:hover { + color: var(--blue); + } + + /* Active state during resize */ + .resize-handle:active { color: var(--blue); opacity: 1; } - .sortable:hover .sort-indicator { + .sort-indicator { + display: inline-flex; + opacity: 0; + transition: opacity 0.2s ease; + } + + .sort-indicator.active { opacity: 1; } + th:hover .sort-indicator { + opacity: 0.5; + } + .high-usage { background-color: color-mix(in srgb, var(--red) 10%, transparent); } @@ -306,7 +443,6 @@ position: sticky; right: 0; z-index: 2; - background: var(--base); border-left: 1px solid var(--surface0); width: 120px; } @@ -427,4 +563,66 @@ align-items: center; gap: 8px; } + + th { + position: relative; + min-width: 50px; + box-sizing: border-box; + } + + .resize-handle { + position: absolute; + right: 4px; + top: 50%; + transform: translateY(-50%); + width: 16px; + height: 16px; + display: flex; + align-items: center; + justify-content: center; + cursor: col-resize; + color: var(--overlay0); + opacity: 0; + transition: all 0.2s ease; + } + + /* Show handle on header hover */ + th:hover .resize-handle { + opacity: 1; + } + + .resize-handle:hover { + color: var(--blue); + } + + /* Active state during resize */ + .resize-handle:active { + color: var(--blue); + opacity: 1; + } + + /* Optional: Add a subtle background on hover */ + .resize-handle:hover::before { + content: ""; + position: absolute; + inset: -4px; + background: var(--surface0); + border-radius: 4px; + z-index: -1; + } + + /* Make sure the table doesn't shrink columns */ + table { + table-layout: fixed; + } + + td { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + th { + transition: width 0.05s ease; + } diff --git a/src/lib/components/ThemeSwitcher.svelte b/src/lib/components/ThemeSwitcher.svelte index 4a51259..56f638d 100644 --- a/src/lib/components/ThemeSwitcher.svelte +++ b/src/lib/components/ThemeSwitcher.svelte @@ -2,6 +2,11 @@ import { themeStore } from "$lib/stores"; import { themes } from "$lib/styles"; import { fade } from "svelte/transition"; + import Fa from "svelte-fa"; + import { + faChevronDown, + faChevronRight, + } from "@fortawesome/free-solid-svg-icons"; let showMenu = false; @@ -65,7 +70,13 @@ > - {showMenu ? "▼" : "▶"} + + {#if showMenu} + + {:else} + + {/if} + {#if showMenu} diff --git a/src/lib/components/ToolBar.svelte b/src/lib/components/ToolBar.svelte index de98b7c..867decf 100644 --- a/src/lib/components/ToolBar.svelte +++ b/src/lib/components/ToolBar.svelte @@ -1,6 +1,14 @@
onToggleSort(column.id)}> -
- {column.label} - - {getSortIndicator(column.id)} - + {#each columns.filter((col) => col.visible) as column, i} +
+
onToggleSort(column.id)}> +
+ {column.label} + + {getSortIndicator(column.id)} + +
+ {#if i < columns.filter((col) => col.visible).length - 1} +
+ handleResizeStart( + e, + column.id, + columns.filter((col) => col.visible)[i + 1].id, + )} + > + +
+ {/if}
ActionsActions