From 1b66f376092d809f4f12612223191b52ef9b5fca Mon Sep 17 00:00:00 2001 From: Abdenasser Date: Sat, 9 Nov 2024 19:37:50 +0100 Subject: [PATCH 1/2] custom title bar + removing resizable columns functionality for now --- src-tauri/capabilities/default.json | 6 +- src-tauri/tauri.conf.json | 4 +- src/lib/components/ProcessTable.svelte | 236 ++----------------- src/lib/components/TitleBar.svelte | 112 +++++++++ src/routes/+page.svelte | 308 +++++++++++++++++++------ 5 files changed, 381 insertions(+), 285 deletions(-) create mode 100644 src/lib/components/TitleBar.svelte diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index fbe338b..c995634 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -5,7 +5,11 @@ "windows": ["main"], "permissions": [ "core:default", - "core:app:allow-version" + "core:app:allow-version", + "core:window:allow-start-dragging", + "core:window:allow-maximize", + "core:window:allow-minimize", + "core:window:allow-close" ] } \ No newline at end of file diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index e753646..b460e8d 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -40,7 +40,9 @@ "title": "NeoHtop", "width": 1280, "minWidth": 1120, - "minHeight": 700 + "minHeight": 700, + "titleBarStyle": "Overlay", + "hiddenTitle": true } ], "security": { diff --git a/src/lib/components/ProcessTable.svelte b/src/lib/components/ProcessTable.svelte index b2c1c2a..e0d5d2f 100644 --- a/src/lib/components/ProcessTable.svelte +++ b/src/lib/components/ProcessTable.svelte @@ -3,7 +3,6 @@ faThumbtack, faInfoCircle, faXmark, - faGripLinesVertical, } from "@fortawesome/free-solid-svg-icons"; import Fa from "svelte-fa"; import type { Process, Column } from "$lib/types"; @@ -20,88 +19,6 @@ 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" ? "↑" : "↓"; @@ -173,38 +90,20 @@ - {#each columns.filter((col) => col.visible) as column, i} - {/each} - + @@ -353,61 +252,25 @@ } .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; - } - - .resize-handle { - 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; - margin-left: 8px; - } - - /* 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; } .sort-indicator { - display: inline-flex; - opacity: 0; - transition: opacity 0.2s ease; + color: var(--overlay0); + font-size: 12px; + opacity: 0.5; + transition: all 0.2s ease; } .sort-indicator.active { + color: var(--blue); opacity: 1; } - th:hover .sort-indicator { - opacity: 0.5; + .sortable:hover .sort-indicator { + opacity: 1; } .high-usage { @@ -443,6 +306,7 @@ position: sticky; right: 0; z-index: 2; + background: var(--base); border-left: 1px solid var(--surface0); width: 120px; } @@ -563,66 +427,4 @@ 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/TitleBar.svelte b/src/lib/components/TitleBar.svelte new file mode 100644 index 0000000..85cfcec --- /dev/null +++ b/src/lib/components/TitleBar.svelte @@ -0,0 +1,112 @@ + + +
+
+
NeoHtop
+
+
+
+ + diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 8bc44e9..2d1181a 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -9,6 +9,7 @@ import { formatMemorySize, formatStatus } from "$lib/utils"; import { themeStore } from "$lib/stores"; import type { Process, SystemStats, Column } from "$lib/types"; + import TitleBar from "$lib/components/TitleBar.svelte"; let processes: Process[] = []; let systemStats: SystemStats | null = null; @@ -47,20 +48,28 @@ }, { id: "memory_usage", - label: "Memory", + label: "RAM", visible: true, format: (v) => (v / (1024 * 1024)).toFixed(1) + " MB", }, - { id: "command", label: "Command", visible: false }, - { id: "ppid", label: "Parent PID", visible: false }, - { id: "environ", label: "Environment Variables", visible: false }, - { id: "root", label: "Root", visible: false }, { id: "virtual_memory", - label: "Virtual Memory", - visible: false, + label: "VIRT", + visible: true, format: (v) => formatMemorySize(v), }, + { + id: "disk_usage", + label: "Disk R/W", + visible: true, + format: (v) => + `${(v[0] / (1024 * 1024)).toFixed(1)} / ${(v[1] / (1024 * 1024)).toFixed(1)} MB`, + }, + { id: "ppid", label: "Parent PID", visible: false }, + { id: "root", label: "Root", visible: false }, + { id: "command", label: "Command", visible: false }, + { id: "environ", label: "Environment Variables", visible: false }, + { id: "session_id", label: "Session ID", visible: false }, { id: "start_time", label: "Start Time", @@ -70,7 +79,7 @@ { id: "run_time", label: "Run Time", - visible: false, + visible: true, format: (v) => { const seconds = v; // v is the time the process has been running in seconds const hours = Math.floor(seconds / 3600); @@ -79,14 +88,6 @@ return `${hours}h ${minutes}m ${remainingSeconds}s`; // Format as HH:MM:SS }, }, - { - id: "disk_usage", - label: "Disk Usage read/write", - visible: false, - format: (v) => - `${(v[0] / (1024 * 1024)).toFixed(1)} / ${(v[1] / (1024 * 1024)).toFixed(1)} MB`, - }, - { id: "session_id", label: "Session ID", visible: false }, ]; let sortConfig = { @@ -245,9 +246,18 @@ selectedProcessPid = null; } + let minLoadingTimer: ReturnType; + const MIN_LOADING_TIME = 2000; // Show loading screen for at least 2 seconds + onMount(async () => { + const loadingPromise = Promise.all([getProcesses()]); + const timerPromise = new Promise((resolve) => { + minLoadingTimer = setTimeout(resolve, MIN_LOADING_TIME); + }); + try { - await Promise.all([getProcesses()]); + // Wait for both the data to load AND the minimum time to pass + await Promise.all([loadingPromise, timerPromise]); } finally { isLoading = false; } @@ -257,50 +267,58 @@ onDestroy(() => { if (intervalId) clearInterval(intervalId); + if (minLoadingTimer) clearTimeout(minLoadingTimer); }); {#if isLoading}
-
- Loading processes... +
+
NeoHtop
+
+
+
+ System Initialization...
{:else} -
- {#if systemStats} - - {/if} +
+ +
+ {#if systemStats} + + {/if} - + - {#if error} -
{error}
- {/if} + {#if error} +
{error}
+ {/if} - -
+ +
+ {/if} - - NeoHtop - Modern System Monitor - - - From b56dae83b8d1478c3030438380c7ffe7ab2d0433 Mon Sep 17 00:00:00 2001 From: Abdenasser Date: Sat, 9 Nov 2024 19:44:33 +0100 Subject: [PATCH 2/2] a better search clear button --- src/lib/components/ToolBar.svelte | 49 +++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/src/lib/components/ToolBar.svelte b/src/lib/components/ToolBar.svelte index 867decf..8238b54 100644 --- a/src/lib/components/ToolBar.svelte +++ b/src/lib/components/ToolBar.svelte @@ -7,7 +7,6 @@ faPause, faChevronDown, faChevronRight, - faArrowRotateRight, } from "@fortawesome/free-solid-svg-icons"; export let searchTerm: string; export let statusFilter: string = "all"; @@ -54,11 +53,16 @@
@@ -214,7 +218,9 @@ .search-input { width: 240px; - padding: 6px 12px; + height: 28px; + padding: 0 12px; + padding-right: 70px; border: 1px solid var(--surface1); border-radius: 6px; font-size: 12px; @@ -223,6 +229,43 @@ transition: all 0.2s ease; } + .btn-clear { + position: absolute; + right: 4px; + top: 50%; + transform: translateY(-50%); + padding: 4px 8px; + font-size: 11px; + color: var(--subtext0); + background: var(--surface1); + border: none; + border-radius: 4px; + cursor: pointer; + transition: all 0.2s ease; + } + + .btn-clear:hover { + background: var(--surface2); + color: var(--text); + } + + .search-input::-webkit-search-cancel-button { + -webkit-appearance: none; + height: 14px; + width: 14px; + margin-right: 4px; + background: var(--overlay0); + -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'%3E%3C/line%3E%3Cline x1='6' y1='6' x2='18' y2='18'%3E%3C/line%3E%3C/svg%3E") + no-repeat 50% 50%; + mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'%3E%3C/line%3E%3Cline x1='6' y1='6' x2='18' y2='18'%3E%3C/line%3E%3C/svg%3E") + no-repeat 50% 50%; + cursor: pointer; + } + + .search-input::-webkit-search-cancel-button:hover { + background: var(--text); + } + .search-input:hover { background-color: var(--surface1); }
-
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} + {#each columns.filter((col) => col.visible) as column} +
onToggleSort(column.id)}> +
+ {column.label} + + {getSortIndicator(column.id)} +
ActionsActions