Compare commits

...
1 Commits
Author SHA1 Message Date
z0gSh1u 0116f6e968 chore: remove unused code 2026-08-15 17:51:11 +08:00
16 changed files with 8 additions and 378 deletions
-96
View File
@@ -1,96 +0,0 @@
/**
* Window Management Utilities
* Handles window state persistence and multi-window management
*/
import { BrowserWindow, screen } from 'electron';
interface WindowState {
x?: number;
y?: number;
width: number;
height: number;
isMaximized: boolean;
}
// Lazy-load electron-store (ESM module)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let windowStateStore: any = null;
async function getStore() {
if (!windowStateStore) {
const Store = (await import('electron-store')).default;
windowStateStore = new Store<{ windowState: WindowState }>({
name: 'window-state',
defaults: {
windowState: {
width: 1280,
height: 800,
isMaximized: false,
},
},
});
}
return windowStateStore;
}
/**
* Get saved window state with bounds validation
*/
export async function getWindowState(): Promise<WindowState> {
const store = await getStore();
const state = store.get('windowState');
// Validate that the window is visible on a screen
if (state.x !== undefined && state.y !== undefined) {
const displays = screen.getAllDisplays();
const isVisible = displays.some((display) => {
const { x, y, width, height } = display.bounds;
return (
state.x! >= x &&
state.x! < x + width &&
state.y! >= y &&
state.y! < y + height
);
});
if (!isVisible) {
// Reset position if not visible
delete state.x;
delete state.y;
}
}
return state;
}
/**
* Save window state
*/
export async function saveWindowState(win: BrowserWindow): Promise<void> {
const store = await getStore();
const isMaximized = win.isMaximized();
if (!isMaximized) {
const bounds = win.getBounds();
store.set('windowState', {
x: bounds.x,
y: bounds.y,
width: bounds.width,
height: bounds.height,
isMaximized,
});
} else {
store.set('windowState.isMaximized', true);
}
}
/**
* Track window state changes
*/
export function trackWindowState(win: BrowserWindow): void {
// Save state on window events
['resize', 'move', 'close'].forEach((event) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
win.on(event as any, () => saveWindowState(win));
});
}
+1
View File
@@ -95,6 +95,7 @@
"electron-updater": "^6.8.3",
"json5": "2.2.3",
"node-machine-id": "^1.1.12",
"pino": "^9.14.0",
"posthog-node": "^5.28.0",
"tar": "^6.2.1",
"timeago.js": "^4.0.2",
+7 -4
View File
@@ -31,6 +31,9 @@ importers:
node-machine-id:
specifier: ^1.1.12
version: 1.1.12
pino:
specifier: ^9.14.0
version: 9.14.0
posthog-node:
specifier: ^5.28.0
version: 5.28.5
@@ -4677,8 +4680,8 @@ packages:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'}
libsignal@git+https://git@github.com:whiskeysockets/libsignal-node.git#bcea72df9ec34d9d9140ab30619cf479c7c144c7:
resolution: {commit: bcea72df9ec34d9d9140ab30619cf479c7c144c7, repo: git@github.com:whiskeysockets/libsignal-node.git, type: git}
libsignal@https://codeload.github.com/whiskeysockets/libsignal-node/tar.gz/bcea72df9ec34d9d9140ab30619cf479c7c144c7:
resolution: {gitHosted: true, tarball: https://codeload.github.com/whiskeysockets/libsignal-node/tar.gz/bcea72df9ec34d9d9140ab30619cf479c7c144c7}
version: 6.0.0
lie@3.3.0:
@@ -9571,7 +9574,7 @@ snapshots:
'@cacheable/node-cache': 1.7.6
'@hapi/boom': 9.1.4
async-mutex: 0.5.0
libsignal: git+https://git@github.com:whiskeysockets/libsignal-node.git#bcea72df9ec34d9d9140ab30619cf479c7c144c7
libsignal: https://codeload.github.com/whiskeysockets/libsignal-node/tar.gz/bcea72df9ec34d9d9140ab30619cf479c7c144c7
lru-cache: 11.2.7
music-metadata: 11.12.3
p-queue: 9.1.0
@@ -11829,7 +11832,7 @@ snapshots:
prelude-ls: 1.2.1
type-check: 0.4.0
libsignal@git+https://git@github.com:whiskeysockets/libsignal-node.git#bcea72df9ec34d9d9140ab30619cf479c7c144c7:
libsignal@https://codeload.github.com/whiskeysockets/libsignal-node/tar.gz/bcea72df9ec34d9d9140ab30619cf479c7c144c7:
dependencies:
curve25519-js: 0.0.4
protobufjs: 7.5.8
-74
View File
@@ -1,74 +0,0 @@
/**
* Error Boundary Component
* Catches and displays errors in the component tree
*/
import { Component, ReactNode } from 'react';
import { AlertTriangle, RefreshCw } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
interface Props {
children: ReactNode;
fallback?: ReactNode;
}
interface State {
hasError: boolean;
error: Error | null;
}
export class ErrorBoundary extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
console.error('Error caught by boundary:', error, errorInfo);
}
handleReset = () => {
this.setState({ hasError: false, error: null });
};
render() {
if (this.state.hasError) {
if (this.props.fallback) {
return this.props.fallback;
}
return (
<div className="flex h-full items-center justify-center p-6">
<Card className="max-w-md">
<CardHeader>
<div className="flex items-center gap-2">
<AlertTriangle className="h-6 w-6 text-destructive" />
<CardTitle>Something went wrong</CardTitle>
</div>
<CardDescription>
An unexpected error occurred. Please try again.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{this.state.error && (
<pre className="rounded-lg bg-surface-input p-4 text-sm overflow-auto max-h-40">
{this.state.error.message}
</pre>
)}
<Button onClick={this.handleReset} className="w-full">
<RefreshCw className="mr-2 h-4 w-4" />
Try Again
</Button>
</CardContent>
</Card>
</div>
);
}
return this.props.children;
}
}
-47
View File
@@ -1,47 +0,0 @@
/**
* Status Badge Component
* Displays connection/state status with color coding
*/
import { cn } from '@/lib/utils';
import { Badge } from '@/components/ui/badge';
export type Status = 'connected' | 'disconnected' | 'connecting' | 'error' | 'running' | 'stopped' | 'starting' | 'reconnecting';
interface StatusBadgeProps {
status: Status;
label?: string;
showDot?: boolean;
}
const statusConfig: Record<Status, { label: string; variant: 'success' | 'secondary' | 'warning' | 'destructive' }> = {
connected: { label: 'Connected', variant: 'success' },
running: { label: 'Running', variant: 'success' },
disconnected: { label: 'Disconnected', variant: 'secondary' },
stopped: { label: 'Stopped', variant: 'secondary' },
connecting: { label: 'Connecting', variant: 'warning' },
starting: { label: 'Starting', variant: 'warning' },
reconnecting: { label: 'Reconnecting', variant: 'warning' },
error: { label: 'Error', variant: 'destructive' },
};
export function StatusBadge({ status, label, showDot = true }: StatusBadgeProps) {
const config = statusConfig[status];
const displayLabel = label || config.label;
return (
<Badge variant={config.variant} className="gap-1.5">
{showDot && (
<span
className={cn(
'h-1.5 w-1.5 rounded-full',
config.variant === 'success' && 'bg-green-600',
config.variant === 'secondary' && 'bg-muted-foreground',
config.variant === 'warning' && 'bg-yellow-600 animate-pulse',
config.variant === 'destructive' && 'bg-red-600'
)}
/>
)}
{displayLabel}
</Badge>
);
}
-49
View File
@@ -1,49 +0,0 @@
import * as React from 'react';
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
import { cn } from '@/lib/utils';
const DropdownMenu = DropdownMenuPrimitive.Root;
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
const DropdownMenuContent = React.forwardRef<
React.ComponentRef<typeof DropdownMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
'z-50 min-w-44 overflow-hidden rounded-md border border-border bg-surface-modal p-1 text-popover-foreground shadow-md',
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
className,
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
));
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
const DropdownMenuItem = React.forwardRef<
React.ComponentRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors',
'focus:bg-black/5 focus:text-foreground dark:focus:bg-white/10',
'data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className,
)}
{...props}
/>
));
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
export {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
};
-1
View File
@@ -1,5 +1,4 @@
export { rendererExtensionRegistry } from './registry';
export { registerRendererExtensionModule, loadRendererExtensions } from './loader';
export type {
RendererExtension,
NavItemDef,
-34
View File
@@ -1,34 +0,0 @@
import { rendererExtensionRegistry } from './registry';
import type { RendererExtension } from './types';
interface RendererExtensionManifest {
extensions?: {
renderer?: string[];
};
}
const registeredModules = new Map<string, () => RendererExtension>();
export function registerRendererExtensionModule(id: string, factory: () => RendererExtension): void {
registeredModules.set(id, factory);
}
export function loadRendererExtensions(manifest?: RendererExtensionManifest): void {
const extensionIds = manifest?.extensions?.renderer;
if (!extensionIds || extensionIds.length === 0) {
for (const [, factory] of registeredModules) {
rendererExtensionRegistry.register(factory());
}
return;
}
for (const id of extensionIds) {
const factory = registeredModules.get(id);
if (factory) {
rendererExtensionRegistry.register(factory());
} else {
console.warn(`[extensions] Renderer extension "${id}" not found in registered modules`);
}
}
}
-45
View File
@@ -1,45 +0,0 @@
import { useState, useRef, useEffect } from 'react';
/**
* A hook that ensures a loading state remains true for at least a minimum duration (e.g., 1000ms),
* preventing flickering for very fast loading times.
*
* @param isLoading - The actual loading state from the data source
* @param minDurationMs - Minimum duration to show loading (default: 1000)
*/
export function useMinLoading(isLoading: boolean, minDurationMs: number = 500) {
const [showLoading, setShowLoading] = useState(isLoading);
const startTime = useRef<number>(0);
// Guarantee that the loading UI activates immediately without any asynchronous delay
if (isLoading && !showLoading) {
setShowLoading(true);
}
// Record the actual timestamp in an effect to respect React purity rules
useEffect(() => {
if (isLoading && startTime.current === 0) {
startTime.current = Date.now();
}
}, [isLoading]);
useEffect(() => {
let timeout: NodeJS.Timeout;
if (!isLoading && showLoading) {
const elapsed = startTime.current > 0 ? Date.now() - startTime.current : 0;
const remaining = Math.max(0, minDurationMs - elapsed);
timeout = setTimeout(() => {
setShowLoading(false);
startTime.current = 0;
}, remaining);
}
return () => {
if (timeout) clearTimeout(timeout);
};
}, [isLoading, showLoading, minDurationMs]);
return isLoading || showLoading;
}
@@ -115,10 +115,6 @@ vi.mock('@/hooks/use-stick-to-bottom-instant', () => ({
})),
}));
vi.mock('@/hooks/use-min-loading', () => ({
useMinLoading: () => false,
}));
vi.mock('@/pages/Chat/ChatToolbar', () => ({
ChatToolbar: () => null,
}));
-4
View File
@@ -137,10 +137,6 @@ vi.mock('@/hooks/use-stick-to-bottom-instant', () => ({
}),
}));
vi.mock('@/hooks/use-min-loading', () => ({
useMinLoading: () => false,
}));
vi.mock('@/pages/Chat/ChatToolbar', () => ({
ChatToolbar: () => <div data-testid="mock-chat-toolbar" />,
}));
@@ -71,10 +71,6 @@ vi.mock('@/hooks/use-stick-to-bottom-instant', () => ({
}),
}));
vi.mock('@/hooks/use-min-loading', () => ({
useMinLoading: (value: boolean) => value,
}));
vi.mock('@/pages/Chat/ChatInput', () => ({
ChatInput: () => <div data-testid="chat-input" />,
}));
@@ -97,10 +97,6 @@ vi.mock('@/hooks/use-stick-to-bottom-instant', () => ({
})),
}));
vi.mock('@/hooks/use-min-loading', () => ({
useMinLoading: () => false,
}));
vi.mock('@/pages/Chat/ChatToolbar', () => ({ ChatToolbar: () => null }));
vi.mock('@/pages/Chat/ChatInput', () => ({ ChatInput: () => null }));
@@ -102,10 +102,6 @@ vi.mock('@/hooks/use-stick-to-bottom-instant', () => ({
})),
}));
vi.mock('@/hooks/use-min-loading', () => ({
useMinLoading: () => false,
}));
vi.mock('@/pages/Chat/ChatToolbar', () => ({ ChatToolbar: () => null }));
vi.mock('@/pages/Chat/ChatInput', () => ({ ChatInput: () => null }));
@@ -102,10 +102,6 @@ vi.mock('@/hooks/use-stick-to-bottom-instant', () => ({
}),
}));
vi.mock('@/hooks/use-min-loading', () => ({
useMinLoading: () => false,
}));
vi.mock('@/pages/Chat/ChatInput', () => ({
ChatInput: () => null,
}));
@@ -102,10 +102,6 @@ vi.mock('@/hooks/use-stick-to-bottom-instant', () => ({
})),
}));
vi.mock('@/hooks/use-min-loading', () => ({
useMinLoading: () => false,
}));
vi.mock('@/pages/Chat/ChatToolbar', () => ({
ChatToolbar: () => null,
}));