diff --git a/internal/site/src/components/containers-table/containers-table-columns.tsx b/internal/site/src/components/containers-table/containers-table-columns.tsx index 77f5cde5..a2d43b27 100644 --- a/internal/site/src/components/containers-table/containers-table-columns.tsx +++ b/internal/site/src/components/containers-table/containers-table-columns.tsx @@ -15,7 +15,7 @@ import { import { EthernetIcon, HourglassIcon, SquareArrowRightEnterIcon } from "../ui/icons" import { Badge } from "../ui/badge" import { t } from "@lingui/core/macro" -import { $allSystemsById, $longestSystemNameLen } from "@/lib/stores" +import { $allSystemsById, $longestSystemName } from "@/lib/stores" import { useStore } from "@nanostores/react" import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip" @@ -63,10 +63,13 @@ export const containerChartCols: ColumnDef[] = [ header: ({ column }) => , cell: ({ getValue }) => { const allSystems = useStore($allSystemsById) - const longestName = useStore($longestSystemNameLen) + const longestName = useStore($longestSystemName) return ( -
- {allSystems[getValue() as string]?.name ?? ""} +
+ + {allSystems[getValue() as string]?.name ?? ""}
) }, diff --git a/internal/site/src/components/network-probes-table/network-probes-columns.tsx b/internal/site/src/components/network-probes-table/network-probes-columns.tsx index 01dac35e..c2a13230 100644 --- a/internal/site/src/components/network-probes-table/network-probes-columns.tsx +++ b/internal/site/src/components/network-probes-table/network-probes-columns.tsx @@ -26,7 +26,7 @@ import { DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" import { Trans } from "@lingui/react/macro" -import { $allSystemsById } from "@/lib/stores" +import { $allSystemsById, $longestSystemName } from "@/lib/stores" import { useStore } from "@nanostores/react" import { SystemStatus } from "@/lib/enums" import { Checkbox } from "@/components/ui/checkbox" @@ -52,8 +52,8 @@ const isMuted = (record: NetworkProbeRecord, systemRecord: SystemRecord | undefi !record.enabled || systemRecord?.status !== SystemStatus.Up export function getProbeColumns( - longestName = 0, - longestTarget = 0, + longestName = "", + longestTarget = "", { onEdit, onDelete, @@ -94,10 +94,13 @@ export function getProbeColumns( cell: ({ row, getValue }) => { const probe = row.original return ( -
+
-
- {getValue() as string} +
+ + {getValue() as string}
) @@ -115,15 +118,21 @@ export function getProbeColumns( header: ({ column }) => , cell: ({ getValue }) => { const system = useStore($allSystemsById)[getValue() as string] as SystemRecord | undefined + const longestSystemName = useStore($longestSystemName) const name = system?.name const status = system?.status as SystemStatus // undefined val is fine but makes lsp mad return useMemo( () => ( - +
- {name} - +
+ + {name} +
+
), [status, name] ) @@ -135,8 +144,11 @@ export function getProbeColumns( accessorFn: (record) => record.target, header: ({ column }) => , cell: ({ getValue }) => ( -
- {getValue() as string} +
+ + {getValue() as string}
), }, diff --git a/internal/site/src/components/network-probes-table/network-probes-table.tsx b/internal/site/src/components/network-probes-table/network-probes-table.tsx index 1168f076..6dbc9a59 100644 --- a/internal/site/src/components/network-probes-table/network-probes-table.tsx +++ b/internal/site/src/components/network-probes-table/network-probes-table.tsx @@ -34,7 +34,7 @@ import { useToast } from "@/components/ui/use-toast" import { isReadOnlyUser } from "@/lib/api" import { pb } from "@/lib/api" import { $allSystemsById } from "@/lib/stores" -import { cn, getVisualStringWidth, useBrowserStorage } from "@/lib/utils" +import { cn, useBrowserStorage } from "@/lib/utils" import type { NetworkProbeRecord } from "@/types" import { AddProbeDialog, EditProbeDialog } from "./probe-dialog" import { XIcon } from "lucide-react" @@ -61,14 +61,19 @@ export default function NetworkProbesTableNew({ const { toast } = useToast() const canManageProbes = !isReadOnlyUser() - const { longestName, longestTarget } = useMemo(() => { - let longestName = 0 - let longestTarget = 0 + const [longestName, longestTarget] = useMemo(() => { + let longestName = "" + let longestTarget = "" for (const p of probes) { - longestName = Math.max(longestName, getVisualStringWidth(p.name || p.target)) - longestTarget = Math.max(longestTarget, getVisualStringWidth(p.target)) + const name = p.name || p.target + if (name.length > longestName.length) { + longestName = name + } + if (p.target.length > longestTarget.length) { + longestTarget = p.target + } } - return { longestName, longestTarget } + return [longestName, longestTarget] }, [probes]) const runProbeBatch = useCallback( @@ -77,8 +82,7 @@ export default function NetworkProbesTableNew({ let inBatch = 0 for (const id of ids) { enqueue(batch, id) - inBatch++ - if (inBatch >= 20) { + if (++inBatch >= 20) { await batch.send() batch = pb.createBatch() inBatch = 0 diff --git a/internal/site/src/components/routes/system/charts/probes-charts.tsx b/internal/site/src/components/routes/system/charts/probes-charts.tsx index 82d82ac0..6badca3b 100644 --- a/internal/site/src/components/routes/system/charts/probes-charts.tsx +++ b/internal/site/src/components/routes/system/charts/probes-charts.tsx @@ -90,6 +90,7 @@ function ProbeChart({ grid={grid} > [] => [ { id: "system", @@ -123,8 +122,11 @@ export const createColumns = ( cell: ({ getValue }) => { const allSystems = useStore($allSystemsById) return ( -
- {allSystems[getValue() as string]?.name ?? ""} +
+ + {allSystems[getValue() as string]?.name ?? ""}
) }, @@ -134,12 +136,11 @@ export const createColumns = ( sortingFn: (a, b) => a.original.name.localeCompare(b.original.name), header: ({ column }) => , cell: ({ getValue }) => ( -
- {getValue() as string} +
+ + {getValue() as string}
), }, @@ -150,12 +151,11 @@ export const createColumns = ( ), cell: ({ getValue }) => ( -
- {getValue() as string} +
+ + {getValue() as string}
), }, @@ -309,7 +309,7 @@ export default function DisksTable({ systemId }: { systemId?: string }) { // Calculate the right width for the columns based on the longest strings among the displayed devices const { longestName, longestModel, longestDevice } = useMemo(() => { - const result = { longestName: 0, longestModel: 0, longestDevice: 0 } + const result = { longestName: "", longestModel: "", longestDevice: "" } if (!smartDevices || Object.keys(allSystems).length === 0) { return result } @@ -318,10 +318,16 @@ export default function DisksTable({ systemId }: { systemId?: string }) { if (!systemId && !seenSystems.has(device.system)) { seenSystems.add(device.system) const name = allSystems[device.system]?.name ?? "" - result.longestName = Math.max(result.longestName, getVisualStringWidth(name)) + if (name.length > result.longestName.length) { + result.longestName = name + } + } + if ((device.model ?? "").length > result.longestModel.length) { + result.longestModel = device.model ?? "" + } + if ((device.name ?? "").length > result.longestDevice.length) { + result.longestDevice = device.name ?? "" } - result.longestModel = Math.max(result.longestModel, getVisualStringWidth(device.model ?? "")) - result.longestDevice = Math.max(result.longestDevice, getVisualStringWidth(device.name ?? "")) } return result }, [smartDevices, systemId, allSystems]) diff --git a/internal/site/src/components/systems-table/systems-table-columns.tsx b/internal/site/src/components/systems-table/systems-table-columns.tsx index 432191fa..8aed4ae6 100644 --- a/internal/site/src/components/systems-table/systems-table-columns.tsx +++ b/internal/site/src/components/systems-table/systems-table-columns.tsx @@ -26,7 +26,7 @@ import { memo, useMemo, useRef, useState } from "react" import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip" import { isReadOnlyUser, pb } from "@/lib/api" import { BatteryState, ConnectionType, connectionTypeLabels, MeterState, SystemStatus } from "@/lib/enums" -import { $longestSystemNameLen, $userSettings } from "@/lib/stores" +import { $longestSystemName, $userSettings } from "@/lib/stores" import { cn, copyToClipboard, @@ -135,7 +135,7 @@ export function SystemsTableColumns(viewMode: "table" | "grid"): ColumnDef { const { name, id } = info.row.original - const longestName = useStore($longestSystemNameLen) + const longestName = useStore($longestSystemName) const linkUrl = getPagePath($router, "system", { id }) return ( @@ -145,8 +145,7 @@ export function SystemsTableColumns(viewMode: "table" | "grid"): ColumnDef { // set title on hover if text is truncated to show full name const a = e.currentTarget @@ -157,7 +156,10 @@ export function SystemsTableColumns(viewMode: "table" | "grid"): ColumnDef - {name} + + {name} diff --git a/internal/site/src/lib/stores.ts b/internal/site/src/lib/stores.ts index fd77788a..395948b0 100644 --- a/internal/site/src/lib/stores.ts +++ b/internal/site/src/lib/stores.ts @@ -70,7 +70,5 @@ export const $copyContent = atom("") /** Direction for localization */ export const $direction = atom<"ltr" | "rtl">("ltr") -/** Longest system name length. Used to set table column width. I know this - * is stupid but the table is virtualized and I know this will work. - */ -export const $longestSystemNameLen = atom(8) +/** Longest system name string. Used to reserve width in virtualized tables. */ +export const $longestSystemName = atom("") diff --git a/internal/site/src/lib/systemsManager.ts b/internal/site/src/lib/systemsManager.ts index 2ee58602..c1b35ba4 100644 --- a/internal/site/src/lib/systemsManager.ts +++ b/internal/site/src/lib/systemsManager.ts @@ -5,20 +5,17 @@ import { $allSystemsById, $allSystemsByName, $downSystems, - $longestSystemNameLen, + $longestSystemName, $pausedSystems, $upSystems, } from "@/lib/stores" -import { getVisualStringWidth, updateFavicon } from "@/lib/utils" +import { updateFavicon } from "@/lib/utils" import type { SystemRecord } from "@/types" import { SystemStatus } from "./enums" const COLLECTION = pb.collection("systems") const FIELDS_DEFAULT = "id,name,host,port,info,status" -/** Maximum system name length for display purposes */ -const MAX_SYSTEM_NAME_LENGTH = 22 - let initialized = false // biome-ignore lint/suspicious/noConfusingVoidType: typescript rocks let unsub: (() => void) | undefined | void @@ -72,16 +69,19 @@ export function init() { }) } -/** Update the longest system name length and favicon based on system status */ -function onSystemsChanged(_: Record, changedSystem: SystemRecord | undefined) { +/** Update the longest system name string and favicon based on system status */ +function onSystemsChanged(systems: Record, _changedSystem: SystemRecord | undefined) { const downSystemsStore = $downSystems.get() const downSystems = Object.values(downSystemsStore) - // Update longest system name length - const longestName = $longestSystemNameLen.get() - const nameLen = Math.min(MAX_SYSTEM_NAME_LENGTH, getVisualStringWidth(changedSystem?.name || "")) - if (nameLen > longestName) { - $longestSystemNameLen.set(nameLen) + let longestName = "" + for (const system of Object.values(systems)) { + if (system.name.length > longestName.length) { + longestName = system.name + } + } + if ($longestSystemName.get() !== longestName) { + $longestSystemName.set(longestName) } updateFavicon(downSystems.length) diff --git a/internal/site/src/lib/utils.ts b/internal/site/src/lib/utils.ts index 4571f7e2..1c4e79d4 100644 --- a/internal/site/src/lib/utils.ts +++ b/internal/site/src/lib/utils.ts @@ -443,30 +443,6 @@ export function runOnce any>(fn: T): T { }) as T } -/** Get the visual width of a string, accounting for full-width characters */ -export function getVisualStringWidth(str: string): number { - let width = 0 - for (const char of str) { - const code = char.codePointAt(0) || 0 - // Hangul Jamo and Syllables are often slightly thinner than Hanzi/Kanji - if ((code >= 0x1100 && code <= 0x115f) || (code >= 0xac00 && code <= 0xd7af)) { - width += 1.8 - continue - } - // Count CJK and other full-width characters as 2 units, others as 1 - // Arabic and Cyrillic are counted as 1 - const isFullWidth = - (code >= 0x2e80 && code <= 0x9fff) || // CJK Radicals, Symbols, and Ideographs - (code >= 0xf900 && code <= 0xfaff) || // CJK Compatibility Ideographs - (code >= 0xfe30 && code <= 0xfe6f) || // CJK Compatibility Forms - (code >= 0xff00 && code <= 0xff60) || // Fullwidth Forms - (code >= 0xffe0 && code <= 0xffe6) || // Fullwidth Symbols - code > 0xffff // Emojis and other supplementary plane characters - width += isFullWidth ? 2 : 1 - } - return width -} - /** Format seconds to hours, minutes, or seconds */ export function secondsToString(seconds: number, unit: "hour" | "minute" | "day"): string { const count = Math.floor(seconds / (unit === "hour" ? 3600 : unit === "minute" ? 60 : 86400))