remove unused stuff, refactor frontend folder structure

This commit is contained in:
Eduard Gert
2026-05-28 16:26:13 +02:00
parent e09bc8894d
commit 51b243bdfa
92 changed files with 953 additions and 1629 deletions
-17
View File
@@ -1,17 +0,0 @@
import chroma from "chroma-js";
export const generateColorFromString = (str?: string) => {
if (!str) return "#f68330";
if (str.includes("System")) return "#808080";
if (str.toLowerCase().startsWith("netbird")) return "#f68330";
let hash = 0;
str.split("").forEach((char) => {
hash = char.charCodeAt(0) + ((hash << 5) - hash);
});
let colour = "#";
for (let i = 0; i < 3; i++) {
const value = (hash >> (i * 8)) & 0xff;
colour += value.toString(16).padStart(2, "0");
}
return chroma(colour).saturate(2).luminance(0.4).hex();
};
+35
View File
@@ -0,0 +1,35 @@
export const formatBytes = (bytes: number, decimals: number = 2): string => {
try {
if (bytes === 0) return "0 B";
const k = 1024;
const sizes = ["B", "KB", "MB", "GB", "TB"];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return (
parseFloat((bytes / Math.pow(k, i)).toFixed(decimals)) +
" " +
sizes[i]
);
} catch {
return "0 B";
}
};
export const latencyColor = (ms: number): string => {
if (ms <= 0) return "text-nb-gray-400";
if (ms < 100) return "text-green-400";
return "text-yellow-400";
};
export const formatRelative = (
unixSeconds: number,
nowMs: number = Date.now(),
): string | null => {
if (!Number.isFinite(unixSeconds) || unixSeconds <= 0) return null;
const diff = Math.max(0, Math.floor(nowMs / 1000 - unixSeconds));
if (diff < 60) return `${diff}s ago`;
if (diff < 3600) return `${Math.floor(diff / 60)}m ago`;
if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`;
return `${Math.floor(diff / 86400)}d ago`;
};
-29
View File
@@ -1,29 +0,0 @@
import { createContext, useContext, useState, type ReactNode } from "react";
export type NavSection = "peers" | "networks" | "exitNode";
type NavSectionContextValue = {
section: NavSection;
setSection: (s: NavSection) => void;
};
const NavSectionContext = createContext<NavSectionContextValue | null>(null);
export const useNavSection = (): NavSectionContextValue => {
const ctx = useContext(NavSectionContext);
if (!ctx) {
throw new Error(
"useNavSection must be used inside NavSectionProvider",
);
}
return ctx;
};
export const NavSectionProvider = ({ children }: { children: ReactNode }) => {
const [section, setSection] = useState<NavSection>("peers");
return (
<NavSectionContext.Provider value={{ section, setSection }}>
{children}
</NavSectionContext.Provider>
);
};
@@ -1,41 +0,0 @@
import { useLayoutEffect, useRef } from "react";
import { Window } from "@wailsio/runtime";
// useAutoSizeWindow resizes the current Wails window so its height matches
// the measured height of the content element the returned ref is attached
// to. Width stays fixed (Wails has no "fit-content-width" notion and the
// dialog-style session windows want a stable horizontal footprint).
//
// On first measurement the hook also calls Window.Show()/Focus() — the
// Go-side opens the window with Hidden: true so the user never sees the
// initial placeholder size snap to the measured size. Subsequent
// measurements (content changes after mount) only adjust the size.
//
// Re-measures via ResizeObserver so adding/removing content (e.g. the
// SessionAboutToExpire title swapping at countdown zero) keeps the chrome
// tight to the content with no scrollbar.
export function useAutoSizeWindow<T extends HTMLElement>(width: number) {
const ref = useRef<T | null>(null);
useLayoutEffect(() => {
const el = ref.current;
if (!el) return;
let shown = false;
const apply = () => {
const h = Math.ceil(el.getBoundingClientRect().height);
if (h <= 0) return;
void Window.SetSize(width, h)
.then(() => {
if (shown) return;
shown = true;
void Window.Show().catch(() => {});
void Window.Focus().catch(() => {});
})
.catch(() => {});
};
apply();
const ro = new ResizeObserver(apply);
ro.observe(el);
return () => ro.disconnect();
}, [width]);
return ref;
}
@@ -1,56 +0,0 @@
import { useEffect } from "react";
export type Shortcut = {
key: string; // e.g. "k", "Escape", "/"
cmd?: boolean; // requires Cmd (mac) / Ctrl (win/linux)
shift?: boolean;
alt?: boolean;
// When true (default), preventDefault is called on a match.
preventDefault?: boolean;
};
// Listens for a keyboard shortcut on the window and invokes `callback` on
// match. Disable conditionally via `enabled` to avoid stealing keys while a
// dialog/panel is in the foreground.
export const useKeyboardShortcut = (
shortcut: Shortcut,
callback: () => void,
enabled = true,
) => {
useEffect(() => {
if (!enabled) return;
const onKey = (e: KeyboardEvent) => {
if (e.key.toLowerCase() !== shortcut.key.toLowerCase()) return;
const mod = e.metaKey || e.ctrlKey;
if (!!shortcut.cmd !== mod) return;
if (!!shortcut.shift !== e.shiftKey) return;
if (!!shortcut.alt !== e.altKey) return;
if (shortcut.preventDefault !== false) e.preventDefault();
callback();
};
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, [
shortcut.key,
shortcut.cmd,
shortcut.shift,
shortcut.alt,
shortcut.preventDefault,
callback,
enabled,
]);
};
// True on macOS — use the ⌘ glyph; otherwise show "Ctrl".
export const isMac =
typeof navigator !== "undefined" &&
/Mac|iPhone|iPad|iPod/i.test(navigator.platform);
export const formatShortcut = (shortcut: Shortcut): string => {
const parts: string[] = [];
if (shortcut.cmd) parts.push(isMac ? "⌘" : "Ctrl");
if (shortcut.shift) parts.push(isMac ? "⇧" : "Shift");
if (shortcut.alt) parts.push(isMac ? "⌥" : "Alt");
parts.push(shortcut.key.length === 1 ? shortcut.key.toUpperCase() : shortcut.key);
return parts.join(isMac ? "" : "+");
};
-79
View File
@@ -1,79 +0,0 @@
import { createContext, useCallback, useContext, useEffect, useState, type ReactNode } from "react";
import { Window } from "@wailsio/runtime";
import { Preferences } from "@bindings/services";
import { ViewMode as ViewModePref } from "@bindings/preferences/models.js";
export type ViewMode = "default" | "advanced";
// Window widths per view. Height stays at whatever the window was first
// created with — we deliberately don't pass a fixed height to
// Window.SetSize because Wails' macOS implementation interprets it as the
// outer frame (windowSetSize → setFrame:), while the initial creation
// uses initWithContentRect:. The two differ by one title-bar height
// (~28px), so re-asserting 640 here would chop ~28px off the content
// area on the first switch and visually shift everything inside.
export const VIEW_WIDTH: Record<ViewMode, number> = {
default: 380,
advanced: 900,
};
type ViewModeContextValue = {
viewMode: ViewMode;
setViewMode: (mode: ViewMode) => void;
};
const ViewModeContext = createContext<ViewModeContextValue | null>(null);
export const ViewModeProvider = ({ children }: { children: ReactNode }) => {
const [viewMode, setMode] = useState<ViewMode>("default");
// Hydrate from the persisted preference. The Go side has already sized
// the main window to match (see main.go), so this only catches the
// React state and dropdown checkmark up — no resize is triggered here.
useEffect(() => {
let cancelled = false;
void Preferences.Get()
.then((prefs) => {
if (cancelled) return;
const saved = prefs?.viewMode as ViewMode | undefined;
if (saved === "default" || saved === "advanced") {
setMode(saved);
}
})
.catch(() => {});
return () => {
cancelled = true;
};
}, []);
const setViewMode = useCallback(
(mode: ViewMode) => {
setMode((prev) => {
if (prev === mode) return prev;
void (async () => {
// Reuse the live frame height instead of asserting a
// constant — keeps content area stable across switches
// (see VIEW_WIDTH comment above).
const size = await Window.Size().catch(() => null);
const width = VIEW_WIDTH[mode];
const height = size?.height ?? 640;
await Window.SetSize(width, height).catch(() => {});
})();
void Preferences.SetViewMode(mode as unknown as ViewModePref).catch(() => {});
return mode;
});
},
[],
);
return (
<ViewModeContext.Provider value={{ viewMode, setViewMode }}>
{children}
</ViewModeContext.Provider>
);
};
export const useViewMode = () => {
const ctx = useContext(ViewModeContext);
if (!ctx) throw new Error("useViewMode must be used inside ViewModeProvider");
return ctx;
};