style: 完成所有文件的lint

This commit is contained in:
claude-code-best
2026-05-01 21:39:30 +08:00
parent d136872cc9
commit 6182015005
1333 changed files with 68255 additions and 77882 deletions

View File

@@ -1,25 +1,21 @@
import { useState, useEffect, useLayoutEffect, useCallback, useRef } from "react";
import { createPortal } from "react-dom";
import { Button } from "./ui/button";
import { StatusDot } from "./ui/connection-status";
import { ThemeToggle } from "./ui/theme-toggle";
import { Label } from "./ui/label";
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "./ui/input-group";
import { ACPClient, DEFAULT_SETTINGS, DisconnectRequestedError } from "../src/acp";
import type { ACPSettings, ConnectionState, BrowserToolParams, BrowserToolResult } from "../src/acp";
import { ChevronDown, FolderOpen, Globe, Image, KeyRound, ScanLine, X } from "lucide-react";
import { useQRScanner, type QRCodeData } from "../src/hooks";
import { useState, useEffect, useLayoutEffect, useCallback, useRef } from 'react';
import { createPortal } from 'react-dom';
import { Button } from './ui/button';
import { StatusDot } from './ui/connection-status';
import { ThemeToggle } from './ui/theme-toggle';
import { Label } from './ui/label';
import { InputGroup, InputGroupAddon, InputGroupInput } from './ui/input-group';
import { ACPClient, DEFAULT_SETTINGS, DisconnectRequestedError } from '../src/acp';
import type { ACPSettings, ConnectionState, BrowserToolParams, BrowserToolResult } from '../src/acp';
import { ChevronDown, FolderOpen, Globe, Image, KeyRound, ScanLine, X } from 'lucide-react';
import { useQRScanner, type QRCodeData } from '../src/hooks';
// Get token from the URL fragment so it is not sent in HTTP requests.
function getTokenFromUrl(): string | undefined {
try {
const url = new URL(window.location.href);
const hashParams = new URLSearchParams(url.hash.replace(/^#/, ""));
return hashParams.get("token") || undefined;
const hashParams = new URLSearchParams(url.hash.replace(/^#/, ''));
return hashParams.get('token') || undefined;
} catch {
return undefined;
}
@@ -30,12 +26,12 @@ function getTokenFromUrl(): string | undefined {
function inferProxyUrlFromPage(): string | undefined {
try {
const url = new URL(window.location.href);
const hashParams = new URLSearchParams(url.hash.replace(/^#/, ""));
const hashParams = new URLSearchParams(url.hash.replace(/^#/, ''));
// Only infer if we have a fragment token (indicates user came from server-printed URL)
if (!hashParams.has("token")) {
if (!hashParams.has('token')) {
return undefined;
}
const protocol = url.protocol === "https:" ? "wss:" : "ws:";
const protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
return `${protocol}//${url.host}/ws`;
} catch {
return undefined;
@@ -45,15 +41,15 @@ function inferProxyUrlFromPage(): string | undefined {
function scrubTokenFromUrl(): void {
try {
const url = new URL(window.location.href);
const hashParams = new URLSearchParams(url.hash.replace(/^#/, ""));
if (!hashParams.has("token")) {
const hashParams = new URLSearchParams(url.hash.replace(/^#/, ''));
if (!hashParams.has('token')) {
return;
}
hashParams.delete("token");
hashParams.delete('token');
const nextHash = hashParams.toString();
url.hash = nextHash ? `#${nextHash}` : "";
window.history.replaceState(null, "", url.toString());
url.hash = nextHash ? `#${nextHash}` : '';
window.history.replaceState(null, '', url.toString());
} catch {
return;
}
@@ -102,11 +98,11 @@ export function ACPConnect({
browserToolHandler,
showTokenInput = false,
inferFromUrl = false,
placeholder = "Proxy server URL",
placeholder = 'Proxy server URL',
showScanButton = false,
}: ACPConnectProps) {
const [settings, setSettings] = useState<ACPSettings>(() => getInitialSettings(inferFromUrl));
const [connectionState, setConnectionState] = useState<ConnectionState>("disconnected");
const [connectionState, setConnectionState] = useState<ConnectionState>('disconnected');
const [error, setError] = useState<string | null>(null);
const [isShaking, setIsShaking] = useState(false);
const [client, setClient] = useState<ACPClient | null>(null);
@@ -122,7 +118,7 @@ export function ACPConnect({
// Mark for auto-connect (will be triggered by settings useEffect)
pendingAutoConnectRef.current = true;
// Update settings - this will trigger auto-connect via useEffect
setSettings((prev) => ({
setSettings(prev => ({
...prev,
proxyUrl: data.url,
token: data.token,
@@ -163,9 +159,9 @@ export function ACPConnect({
stopScanning(); // Close the scanner overlay after album scan
}
// Reset input to allow re-selecting the same file
e.target.value = "";
e.target.value = '';
},
[scanFromFile, stopScanning]
[scanFromFile, stopScanning],
);
// Open file picker
@@ -203,7 +199,7 @@ export function ACPConnect({
// Auto-connect after QR scan (when pendingAutoConnectRef is set)
if (pendingAutoConnectRef.current) {
pendingAutoConnectRef.current = false;
client.connect().catch((e) => {
client.connect().catch(e => {
// Ignore disconnect requested - user cancelled intentionally
if (e instanceof DisconnectRequestedError) {
return;
@@ -219,7 +215,7 @@ export function ACPConnect({
// Notify parent when client is ready and auto-collapse on connect
useEffect(() => {
const isConnected = connectionState === "connected";
const isConnected = connectionState === 'connected';
onClientReady?.(isConnected ? client : null);
// Auto-collapse when connected for the first time
@@ -229,14 +225,14 @@ export function ACPConnect({
}
// Reset auto-collapse flag when disconnected
if (connectionState === "disconnected") {
if (connectionState === 'disconnected') {
hasAutoCollapsedRef.current = false;
}
}, [connectionState, client, onClientReady, onExpandedChange]);
const handleConnect = useCallback(async () => {
// Prevent duplicate connect calls if already connecting or connected
if (!client || connectionState === "connecting" || connectionState === "connected") {
if (!client || connectionState === 'connecting' || connectionState === 'connected') {
return;
}
setError(null);
@@ -263,7 +259,7 @@ export function ACPConnect({
}, [client]);
const updateSetting = <K extends keyof ACPSettings>(key: K, value: ACPSettings[K]) => {
setSettings((prev) => ({ ...prev, [key]: value }));
setSettings(prev => ({ ...prev, [key]: value }));
};
// Clear error when starting to scan
@@ -272,107 +268,93 @@ export function ACPConnect({
startScanning();
}, [startScanning]);
const isConnected = connectionState === "connected";
const isConnecting = connectionState === "connecting";
const isConnected = connectionState === 'connected';
const isConnecting = connectionState === 'connecting';
const handleInputKeyDown = useCallback((e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !isConnected && !isConnecting) {
e.preventDefault();
handleConnect();
}
}, [isConnected, isConnecting, handleConnect]);
const handleInputKeyDown = useCallback(
(e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !isConnected && !isConnecting) {
e.preventDefault();
handleConnect();
}
},
[isConnected, isConnecting, handleConnect],
);
// Format URL for display
const displayUrl = settings.proxyUrl.replace(/^wss?:\/\//, "").replace(/\/ws$/, "");
const displayUrl = settings.proxyUrl.replace(/^wss?:\/\//, '').replace(/\/ws$/, '');
// Get status label
const statusLabels: Record<ConnectionState, string> = {
disconnected: "Disconnected",
connecting: "Connecting...",
connected: "Connected",
error: "Error",
disconnected: 'Disconnected',
connecting: 'Connecting...',
connected: 'Connected',
error: 'Error',
};
return (
<div className="bg-background/80 backdrop-blur-sm">
<div className="max-w-md mx-auto border-b">
{/* Status Bar - Always visible */}
<button
onClick={() => onExpandedChange(!expanded)}
className="w-full flex items-center justify-between px-3 py-2 hover:bg-muted/50 transition-colors"
>
<div className="flex items-center gap-2">
<StatusDot state={connectionState} />
<span className="text-sm font-medium">{statusLabels[connectionState]}</span>
{isConnected && displayUrl && (
<span className="text-xs text-muted-foreground"> {displayUrl}</span>
)}
</div>
<div className="flex items-center gap-1">
<div onClick={(e) => e.stopPropagation()}>
<ThemeToggle />
{/* Status Bar - Always visible */}
<button
onClick={() => onExpandedChange(!expanded)}
className="w-full flex items-center justify-between px-3 py-2 hover:bg-muted/50 transition-colors"
>
<div className="flex items-center gap-2">
<StatusDot state={connectionState} />
<span className="text-sm font-medium">{statusLabels[connectionState]}</span>
{isConnected && displayUrl && <span className="text-xs text-muted-foreground"> {displayUrl}</span>}
</div>
<ChevronDown
className={`w-4 h-4 text-muted-foreground transition-transform duration-200 ${
expanded ? "rotate-180" : ""
}`}
/>
</div>
</button>
<div className="flex items-center gap-1">
<div onClick={e => e.stopPropagation()}>
<ThemeToggle />
</div>
<ChevronDown
className={`w-4 h-4 text-muted-foreground transition-transform duration-200 ${
expanded ? 'rotate-180' : ''
}`}
/>
</div>
</button>
{/* Expandable Settings Panel */}
<div
className="overflow-hidden transition-all duration-200 ease-out"
style={{
maxHeight: expanded ? maxHeight : 0,
opacity: expanded ? 1 : 0,
}}
>
<div ref={contentRef} className={`px-3 pb-3 pt-1 space-y-3 ${isShaking ? "animate-shake" : ""}`}>
{/* Hidden file input for album scanning */}
<input
ref={fileInputRef}
type="file"
accept="image/*"
onChange={handleFileSelect}
className="hidden"
/>
{/* Expandable Settings Panel */}
<div
className="overflow-hidden transition-all duration-200 ease-out"
style={{
maxHeight: expanded ? maxHeight : 0,
opacity: expanded ? 1 : 0,
}}
>
<div ref={contentRef} className={`px-3 pb-3 pt-1 space-y-3 ${isShaking ? 'animate-shake' : ''}`}>
{/* Hidden file input for album scanning */}
<input ref={fileInputRef} type="file" accept="image/*" onChange={handleFileSelect} className="hidden" />
{/* QR Scanner View - Portal to body to escape backdrop-blur containing block */}
{isScanning && createPortal(
<div className="fixed inset-0 z-50 bg-black flex flex-col">
<video
ref={videoRef}
className="flex-1 w-full object-cover"
/>
<Button
onClick={stopScanning}
variant="ghost"
size="sm"
className="absolute top-4 right-4 h-10 w-10 p-0 bg-black/50 hover:bg-black/70 text-white rounded-full"
>
<X className="h-5 w-5" />
</Button>
<div className="absolute bottom-16 left-0 right-0 flex flex-col items-center gap-3">
<Button
onClick={handleSelectFromAlbum}
variant="secondary"
size="sm"
className="h-9 px-4"
>
<Image className="h-4 w-4 mr-2" />
Select from Album
</Button>
<span className="text-sm text-white/80">
or point camera at QR code
</span>
</div>
</div>,
document.body
)}
{/* QR Scanner View - Portal to body to escape backdrop-blur containing block */}
{isScanning &&
createPortal(
<div className="fixed inset-0 z-50 bg-black flex flex-col">
<video ref={videoRef} className="flex-1 w-full object-cover" />
<Button
onClick={stopScanning}
variant="ghost"
size="sm"
className="absolute top-4 right-4 h-10 w-10 p-0 bg-black/50 hover:bg-black/70 text-white rounded-full"
>
<X className="h-5 w-5" />
</Button>
<div className="absolute bottom-16 left-0 right-0 flex flex-col items-center gap-3">
<Button onClick={handleSelectFromAlbum} variant="secondary" size="sm" className="h-9 px-4">
<Image className="h-4 w-4 mr-2" />
Select from Album
</Button>
<span className="text-sm text-white/80">or point camera at QR code</span>
</div>
</div>,
document.body,
)}
{/* Connection Settings - use invisible (not hidden) to preserve scrollHeight for animation */}
<div className={`space-y-3 ${isScanning ? "invisible" : ""}`}>
{/* Connection Settings - use invisible (not hidden) to preserve scrollHeight for animation */}
<div className={`space-y-3 ${isScanning ? 'invisible' : ''}`}>
{/* Server URL */}
<div className="space-y-1.5">
<Label htmlFor="proxy-url">Server</Label>
@@ -396,7 +378,7 @@ export function ACPConnect({
<InputGroupInput
id="proxy-url"
value={settings.proxyUrl}
onChange={(e) => updateSetting("proxyUrl", e.target.value)}
onChange={e => updateSetting('proxyUrl', e.target.value)}
onKeyDown={handleInputKeyDown}
placeholder={placeholder}
disabled={isConnected || isConnecting}
@@ -411,7 +393,7 @@ export function ACPConnect({
className="h-9 px-4"
type="button"
>
{isConnecting ? "..." : "Connect"}
{isConnecting ? '...' : 'Connect'}
</Button>
) : (
<Button
@@ -440,8 +422,8 @@ export function ACPConnect({
</InputGroupAddon>
<InputGroupInput
id="auth-token"
value={settings.token || ""}
onChange={(e) => updateSetting("token", e.target.value || undefined)}
value={settings.token || ''}
onChange={e => updateSetting('token', e.target.value || undefined)}
onKeyDown={handleInputKeyDown}
placeholder="For remote access"
disabled={isConnected || isConnecting}
@@ -465,8 +447,8 @@ export function ACPConnect({
</InputGroupAddon>
<InputGroupInput
id="working-dir"
value={settings.cwd || ""}
onChange={(e) => updateSetting("cwd", e.target.value || undefined)}
value={settings.cwd || ''}
onChange={e => updateSetting('cwd', e.target.value || undefined)}
onKeyDown={handleInputKeyDown}
placeholder="/path/to/project"
disabled={isConnected || isConnecting}
@@ -475,17 +457,13 @@ export function ACPConnect({
/>
</InputGroup>
</div>
</div>
{/* Error Message */}
{error && (
<div className="text-xs text-destructive bg-destructive/10 px-2 py-1.5 rounded">
{error}
</div>
)}
{/* Error Message */}
{error && <div className="text-xs text-destructive bg-destructive/10 px-2 py-1.5 rounded">{error}</div>}
</div>
</div>
</div>
</div>
</div>
);
}