@atercates/claude-deck 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +123 -0
- package/app/api/claude/hidden/route.ts +66 -0
- package/app/api/claude/projects/[name]/sessions/route.ts +71 -0
- package/app/api/claude/projects/route.ts +44 -0
- package/app/api/code-search/available/route.ts +12 -0
- package/app/api/code-search/route.ts +47 -0
- package/app/api/dev-servers/[id]/logs/route.ts +23 -0
- package/app/api/dev-servers/[id]/restart/route.ts +20 -0
- package/app/api/dev-servers/[id]/route.ts +51 -0
- package/app/api/dev-servers/[id]/stop/route.ts +20 -0
- package/app/api/dev-servers/detect/route.ts +39 -0
- package/app/api/dev-servers/route.ts +48 -0
- package/app/api/exec/route.ts +60 -0
- package/app/api/files/content/route.ts +76 -0
- package/app/api/files/route.ts +37 -0
- package/app/api/files/upload-temp/route.ts +41 -0
- package/app/api/git/check/route.ts +54 -0
- package/app/api/git/clone/route.ts +99 -0
- package/app/api/git/commit/route.ts +75 -0
- package/app/api/git/discard/route.ts +38 -0
- package/app/api/git/file-content/route.ts +64 -0
- package/app/api/git/history/[hash]/diff/route.ts +38 -0
- package/app/api/git/history/[hash]/route.ts +34 -0
- package/app/api/git/history/route.ts +27 -0
- package/app/api/git/multi-status/route.ts +46 -0
- package/app/api/git/pr/route.ts +164 -0
- package/app/api/git/push/route.ts +64 -0
- package/app/api/git/stage/route.ts +40 -0
- package/app/api/git/status/route.ts +51 -0
- package/app/api/git/unstage/route.ts +46 -0
- package/app/api/groups/[...path]/route.ts +136 -0
- package/app/api/groups/route.ts +93 -0
- package/app/api/orchestrate/spawn/route.ts +45 -0
- package/app/api/orchestrate/workers/[id]/route.ts +89 -0
- package/app/api/orchestrate/workers/route.ts +31 -0
- package/app/api/projects/[id]/detect/route.ts +27 -0
- package/app/api/projects/[id]/dev-servers/[dsId]/route.ts +66 -0
- package/app/api/projects/[id]/dev-servers/route.ts +51 -0
- package/app/api/projects/[id]/repositories/[repoId]/route.ts +67 -0
- package/app/api/projects/[id]/repositories/route.ts +74 -0
- package/app/api/projects/[id]/route.ts +108 -0
- package/app/api/projects/detect/route.ts +33 -0
- package/app/api/projects/route.ts +59 -0
- package/app/api/sessions/[id]/claude-session/route.ts +42 -0
- package/app/api/sessions/[id]/fork/route.ts +74 -0
- package/app/api/sessions/[id]/mcp-config/route.ts +34 -0
- package/app/api/sessions/[id]/messages/route.ts +60 -0
- package/app/api/sessions/[id]/pr/route.ts +188 -0
- package/app/api/sessions/[id]/preview/route.ts +42 -0
- package/app/api/sessions/[id]/route.ts +229 -0
- package/app/api/sessions/[id]/send-keys/route.ts +119 -0
- package/app/api/sessions/[id]/summarize/route.ts +331 -0
- package/app/api/sessions/init-script/route.ts +84 -0
- package/app/api/sessions/route.ts +209 -0
- package/app/api/sessions/status/route.ts +237 -0
- package/app/api/system/route.ts +9 -0
- package/app/api/tmux/kill-all/route.ts +57 -0
- package/app/api/tmux/rename/route.ts +30 -0
- package/app/globals.css +174 -0
- package/app/icon.svg +11 -0
- package/app/layout.tsx +122 -0
- package/app/page.tsx +629 -0
- package/components/ChatMessage.tsx +65 -0
- package/components/ChatView.tsx +276 -0
- package/components/ClaudeProjects/ClaudeProjectCard.tsx +195 -0
- package/components/ClaudeProjects/ClaudeProjectsSection.tsx +89 -0
- package/components/ClaudeProjects/ClaudeSessionCard.tsx +100 -0
- package/components/ClaudeProjects/index.ts +1 -0
- package/components/CodeSearch/CodeSearchResults.tsx +177 -0
- package/components/ConductorPanel.tsx +256 -0
- package/components/DevServers/DevServerCard.tsx +311 -0
- package/components/DevServers/DevServersSection.tsx +91 -0
- package/components/DevServers/ServerLogsModal.tsx +151 -0
- package/components/DevServers/StartServerDialog.tsx +359 -0
- package/components/DevServers/index.ts +4 -0
- package/components/DiffViewer/DiffModal.tsx +151 -0
- package/components/DiffViewer/UnifiedDiff.tsx +185 -0
- package/components/DiffViewer/index.tsx +2 -0
- package/components/DirectoryPicker.tsx +355 -0
- package/components/FileExplorer/FileEditor.tsx +276 -0
- package/components/FileExplorer/FileTabs.tsx +118 -0
- package/components/FileExplorer/FileTree.tsx +214 -0
- package/components/FileExplorer/HtmlRenderer.tsx +16 -0
- package/components/FileExplorer/MarkdownRenderer.tsx +18 -0
- package/components/FileExplorer/index.tsx +520 -0
- package/components/FilePicker.tsx +339 -0
- package/components/FolderPicker.tsx +201 -0
- package/components/GitDrawer/FileEditDialog.tsx +400 -0
- package/components/GitDrawer/index.tsx +464 -0
- package/components/GitPanel/CommitForm.tsx +205 -0
- package/components/GitPanel/CommitHistory.tsx +174 -0
- package/components/GitPanel/CommitItem.tsx +196 -0
- package/components/GitPanel/FileChanges.tsx +414 -0
- package/components/GitPanel/GitPanelTabs.tsx +39 -0
- package/components/GitPanel/index.tsx +817 -0
- package/components/MessageInput.tsx +82 -0
- package/components/NewClaudeSessionDialog.tsx +166 -0
- package/components/NewSessionDialog/AdvancedSettings.tsx +78 -0
- package/components/NewSessionDialog/AgentSelector.tsx +37 -0
- package/components/NewSessionDialog/CreatingOverlay.tsx +94 -0
- package/components/NewSessionDialog/NewSessionDialog.types.ts +136 -0
- package/components/NewSessionDialog/ProjectSelector.tsx +146 -0
- package/components/NewSessionDialog/WorkingDirectoryInput.tsx +55 -0
- package/components/NewSessionDialog/WorktreeSection.tsx +92 -0
- package/components/NewSessionDialog/hooks/useNewSessionForm.ts +370 -0
- package/components/NewSessionDialog/index.tsx +106 -0
- package/components/NotificationSettings.tsx +127 -0
- package/components/PRCreationModal.tsx +272 -0
- package/components/Pane/DesktopTabBar.tsx +353 -0
- package/components/Pane/MobileTabBar.tsx +210 -0
- package/components/Pane/OpenInVSCode.tsx +69 -0
- package/components/Pane/PaneSkeletons.tsx +57 -0
- package/components/Pane/index.tsx +558 -0
- package/components/PaneLayout.tsx +60 -0
- package/components/Projects/DevServersSection.tsx +140 -0
- package/components/Projects/DirectoryField.tsx +92 -0
- package/components/Projects/NewProjectDialog.tsx +188 -0
- package/components/Projects/NewProjectDialog.types.ts +46 -0
- package/components/Projects/ProjectCard.tsx +276 -0
- package/components/Projects/ProjectSettingsDialog.tsx +811 -0
- package/components/Projects/hooks/useNewProjectForm.ts +249 -0
- package/components/Projects/index.ts +3 -0
- package/components/Providers.tsx +49 -0
- package/components/QuickSwitcher.tsx +306 -0
- package/components/SessionList/KillAllConfirm.tsx +46 -0
- package/components/SessionList/SelectionToolbar.tsx +164 -0
- package/components/SessionList/SessionList.types.ts +37 -0
- package/components/SessionList/SessionListHeader.tsx +71 -0
- package/components/SessionList/hooks/useSessionListMutations.ts +269 -0
- package/components/SessionList/index.tsx +189 -0
- package/components/ShellDrawer/index.tsx +106 -0
- package/components/SidebarFooter.tsx +55 -0
- package/components/Terminal/KeybarToggleButton.tsx +45 -0
- package/components/Terminal/ScrollToBottomButton.tsx +32 -0
- package/components/Terminal/SearchBar.tsx +71 -0
- package/components/Terminal/TerminalToolbar.tsx +551 -0
- package/components/Terminal/VirtualKeyboard.tsx +711 -0
- package/components/Terminal/constants.ts +20 -0
- package/components/Terminal/hooks/index.ts +5 -0
- package/components/Terminal/hooks/resize-handlers.ts +140 -0
- package/components/Terminal/hooks/terminal-init.ts +151 -0
- package/components/Terminal/hooks/touch-scroll.ts +155 -0
- package/components/Terminal/hooks/useTerminalConnection.ts +282 -0
- package/components/Terminal/hooks/useTerminalConnection.types.ts +39 -0
- package/components/Terminal/hooks/useTerminalSearch.ts +103 -0
- package/components/Terminal/hooks/websocket-connection.ts +274 -0
- package/components/Terminal/index.tsx +320 -0
- package/components/ThemeToggle.tsx +168 -0
- package/components/TmuxSessions.tsx +132 -0
- package/components/ToolCallDisplay.tsx +71 -0
- package/components/WorkerCard.tsx +245 -0
- package/components/a/ABadge.tsx +115 -0
- package/components/a/AButton.tsx +163 -0
- package/components/a/ADialog.tsx +93 -0
- package/components/a/ADropdownMenu.tsx +279 -0
- package/components/a/AIconButton.tsx +190 -0
- package/components/a/ASheet.tsx +150 -0
- package/components/a/ATooltip.tsx +77 -0
- package/components/a/index.ts +64 -0
- package/components/mobile/SwipeSidebar.tsx +122 -0
- package/components/ui/badge.tsx +41 -0
- package/components/ui/button.tsx +60 -0
- package/components/ui/context-menu.tsx +197 -0
- package/components/ui/dialog.tsx +143 -0
- package/components/ui/dropdown-menu.tsx +257 -0
- package/components/ui/input.tsx +21 -0
- package/components/ui/scroll-area.tsx +52 -0
- package/components/ui/select.tsx +159 -0
- package/components/ui/skeleton.tsx +111 -0
- package/components/ui/switch.tsx +31 -0
- package/components/ui/textarea.tsx +21 -0
- package/components/ui/tooltip.tsx +32 -0
- package/components/views/DesktopView.tsx +244 -0
- package/components/views/MobileView.tsx +110 -0
- package/components/views/types.ts +75 -0
- package/contexts/PaneContext.tsx +336 -0
- package/data/claude/index.ts +9 -0
- package/data/claude/keys.ts +6 -0
- package/data/claude/queries.ts +120 -0
- package/data/claude/useClaudeUpdates.ts +37 -0
- package/data/code-search/index.ts +2 -0
- package/data/code-search/keys.ts +7 -0
- package/data/code-search/queries.ts +61 -0
- package/data/dev-servers/index.ts +8 -0
- package/data/dev-servers/keys.ts +4 -0
- package/data/dev-servers/queries.ts +104 -0
- package/data/files/index.ts +3 -0
- package/data/files/keys.ts +4 -0
- package/data/files/queries.ts +25 -0
- package/data/git/keys.ts +15 -0
- package/data/git/queries.ts +395 -0
- package/data/groups/index.ts +1 -0
- package/data/groups/mutations.ts +95 -0
- package/data/projects/index.ts +10 -0
- package/data/projects/keys.ts +4 -0
- package/data/projects/queries.ts +193 -0
- package/data/repositories/index.ts +7 -0
- package/data/repositories/keys.ts +5 -0
- package/data/repositories/queries.ts +122 -0
- package/data/sessions/index.ts +12 -0
- package/data/sessions/keys.ts +8 -0
- package/data/sessions/queries.ts +218 -0
- package/data/statuses/index.ts +1 -0
- package/data/statuses/queries.ts +69 -0
- package/hooks/useCopyToClipboard.ts +48 -0
- package/hooks/useDevServersManager.ts +73 -0
- package/hooks/useDirectoryBrowser.ts +90 -0
- package/hooks/useDrawerAnimation.ts +27 -0
- package/hooks/useFileDrop.ts +87 -0
- package/hooks/useFileEditor.ts +184 -0
- package/hooks/useGroups.ts +37 -0
- package/hooks/useHomePath.ts +34 -0
- package/hooks/useKeyRepeat.ts +55 -0
- package/hooks/useKeybarVisibility.ts +42 -0
- package/hooks/useNotifications.ts +257 -0
- package/hooks/useProjects.ts +53 -0
- package/hooks/useSessionStatuses.ts +30 -0
- package/hooks/useSessions.ts +86 -0
- package/hooks/useSpeechRecognition.ts +124 -0
- package/hooks/useViewport.ts +32 -0
- package/hooks/useViewportHeight.ts +50 -0
- package/lib/async-operations.ts +35 -0
- package/lib/banner.ts +81 -0
- package/lib/claude/jsonl-cache.ts +86 -0
- package/lib/claude/jsonl-reader.ts +271 -0
- package/lib/claude/process-manager.ts +278 -0
- package/lib/claude/stream-parser.ts +173 -0
- package/lib/claude/types.ts +154 -0
- package/lib/claude/watcher.ts +71 -0
- package/lib/client/session-registry.ts +111 -0
- package/lib/code-search.ts +121 -0
- package/lib/db/index.ts +48 -0
- package/lib/db/migrations.ts +45 -0
- package/lib/db/queries.ts +460 -0
- package/lib/db/schema.ts +114 -0
- package/lib/db/types.ts +92 -0
- package/lib/db.ts +2 -0
- package/lib/dev-servers.ts +509 -0
- package/lib/diff-parser.ts +221 -0
- package/lib/env-setup.ts +285 -0
- package/lib/file-upload.ts +34 -0
- package/lib/file-utils.ts +50 -0
- package/lib/files.ts +207 -0
- package/lib/git-history.ts +294 -0
- package/lib/git-status.ts +391 -0
- package/lib/git.ts +257 -0
- package/lib/mcp-config.ts +81 -0
- package/lib/multi-repo-git.ts +179 -0
- package/lib/notifications.ts +219 -0
- package/lib/orchestration.ts +448 -0
- package/lib/panes.ts +232 -0
- package/lib/ports.ts +97 -0
- package/lib/pr-generation.ts +307 -0
- package/lib/pr.ts +234 -0
- package/lib/projects.ts +578 -0
- package/lib/providers/registry.ts +70 -0
- package/lib/providers.ts +121 -0
- package/lib/query-client.ts +14 -0
- package/lib/rangeSelectionUtils.ts +65 -0
- package/lib/status-detector.ts +375 -0
- package/lib/terminal-themes.ts +265 -0
- package/lib/theme-config.ts +327 -0
- package/lib/utils.ts +6 -0
- package/lib/worktrees.ts +262 -0
- package/mcp/orchestration-server.ts +438 -0
- package/package.json +139 -0
- package/postcss.config.mjs +7 -0
- package/public/icon.svg +10 -0
- package/public/icons/icon-128x128.png +0 -0
- package/public/icons/icon-144x144.png +0 -0
- package/public/icons/icon-152x152.png +0 -0
- package/public/icons/icon-192x192.png +0 -0
- package/public/icons/icon-384x384.png +0 -0
- package/public/icons/icon-512x512.png +0 -0
- package/public/icons/icon-72x72.png +0 -0
- package/public/icons/icon-96x96.png +0 -0
- package/public/manifest.json +61 -0
- package/public/sw.js +64 -0
- package/scripts/agent-os +91 -0
- package/scripts/install.sh +48 -0
- package/scripts/lib/ai-clis.sh +132 -0
- package/scripts/lib/commands.sh +487 -0
- package/scripts/lib/common.sh +89 -0
- package/scripts/lib/prerequisites.sh +462 -0
- package/scripts/setup.sh +134 -0
- package/server.ts +155 -0
- package/stores/fileOpen.ts +26 -0
- package/stores/index.ts +1 -0
- package/stores/initialPrompt.ts +24 -0
- package/stores/sessionSelection.ts +48 -0
- package/styles/themes.css +603 -0
- package/tsconfig.json +33 -0
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from "react";
|
|
4
|
+
import { X, Server, Container, Loader2, Play, FolderOpen } from "lucide-react";
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
import type { Project, ProjectDevServer } from "@/lib/db";
|
|
7
|
+
|
|
8
|
+
interface DetectedServer {
|
|
9
|
+
type: "node" | "docker";
|
|
10
|
+
name: string;
|
|
11
|
+
command: string;
|
|
12
|
+
ports: number[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface StartServerDialogProps {
|
|
16
|
+
project: Project;
|
|
17
|
+
projectDevServers?: ProjectDevServer[];
|
|
18
|
+
onStart: (opts: {
|
|
19
|
+
projectId: string;
|
|
20
|
+
type: "node" | "docker";
|
|
21
|
+
name: string;
|
|
22
|
+
command: string;
|
|
23
|
+
workingDirectory: string;
|
|
24
|
+
ports?: number[];
|
|
25
|
+
}) => Promise<void>;
|
|
26
|
+
onClose: () => void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function StartServerDialog({
|
|
30
|
+
project,
|
|
31
|
+
projectDevServers = [],
|
|
32
|
+
onStart,
|
|
33
|
+
onClose,
|
|
34
|
+
}: StartServerDialogProps) {
|
|
35
|
+
const [detected, setDetected] = useState<DetectedServer[]>([]);
|
|
36
|
+
const [loading, setLoading] = useState(true);
|
|
37
|
+
const [starting, setStarting] = useState(false);
|
|
38
|
+
const [error, setError] = useState<string | null>(null);
|
|
39
|
+
|
|
40
|
+
// Custom server form state
|
|
41
|
+
const [showCustom, setShowCustom] = useState(false);
|
|
42
|
+
const [customType, setCustomType] = useState<"node" | "docker">("node");
|
|
43
|
+
const [customName, setCustomName] = useState("");
|
|
44
|
+
const [customCommand, setCustomCommand] = useState("");
|
|
45
|
+
const [customPort, setCustomPort] = useState("3000");
|
|
46
|
+
|
|
47
|
+
// Detect available servers
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
async function detect() {
|
|
50
|
+
try {
|
|
51
|
+
const res = await fetch(
|
|
52
|
+
`/api/dev-servers/detect?projectId=${project.id}`
|
|
53
|
+
);
|
|
54
|
+
if (res.ok) {
|
|
55
|
+
const data = await res.json();
|
|
56
|
+
setDetected(data.servers || []);
|
|
57
|
+
}
|
|
58
|
+
} catch (err) {
|
|
59
|
+
console.error("Failed to detect servers:", err);
|
|
60
|
+
} finally {
|
|
61
|
+
setLoading(false);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
detect();
|
|
65
|
+
}, [project.id]);
|
|
66
|
+
|
|
67
|
+
const handleStartProjectServer = async (server: ProjectDevServer) => {
|
|
68
|
+
setStarting(true);
|
|
69
|
+
setError(null);
|
|
70
|
+
try {
|
|
71
|
+
await onStart({
|
|
72
|
+
projectId: project.id,
|
|
73
|
+
type: server.type,
|
|
74
|
+
name: server.name,
|
|
75
|
+
command: server.command,
|
|
76
|
+
workingDirectory: project.working_directory,
|
|
77
|
+
ports: server.port ? [server.port] : undefined,
|
|
78
|
+
});
|
|
79
|
+
onClose();
|
|
80
|
+
} catch (err) {
|
|
81
|
+
setError(err instanceof Error ? err.message : "Failed to start server");
|
|
82
|
+
} finally {
|
|
83
|
+
setStarting(false);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const handleStartDetected = async (server: DetectedServer) => {
|
|
88
|
+
setStarting(true);
|
|
89
|
+
setError(null);
|
|
90
|
+
try {
|
|
91
|
+
await onStart({
|
|
92
|
+
projectId: project.id,
|
|
93
|
+
type: server.type,
|
|
94
|
+
name: server.name,
|
|
95
|
+
command: server.command,
|
|
96
|
+
workingDirectory: project.working_directory,
|
|
97
|
+
ports: server.ports,
|
|
98
|
+
});
|
|
99
|
+
onClose();
|
|
100
|
+
} catch (err) {
|
|
101
|
+
setError(err instanceof Error ? err.message : "Failed to start server");
|
|
102
|
+
} finally {
|
|
103
|
+
setStarting(false);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const handleStartCustom = async () => {
|
|
108
|
+
if (!customName || !customCommand) {
|
|
109
|
+
setError("Name and command are required");
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
setStarting(true);
|
|
114
|
+
setError(null);
|
|
115
|
+
try {
|
|
116
|
+
const port = parseInt(customPort, 10);
|
|
117
|
+
await onStart({
|
|
118
|
+
projectId: project.id,
|
|
119
|
+
type: customType,
|
|
120
|
+
name: customName,
|
|
121
|
+
command: customCommand,
|
|
122
|
+
workingDirectory: project.working_directory,
|
|
123
|
+
ports: isNaN(port) ? undefined : [port],
|
|
124
|
+
});
|
|
125
|
+
onClose();
|
|
126
|
+
} catch (err) {
|
|
127
|
+
setError(err instanceof Error ? err.message : "Failed to start server");
|
|
128
|
+
} finally {
|
|
129
|
+
setStarting(false);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4">
|
|
135
|
+
<div
|
|
136
|
+
className={cn(
|
|
137
|
+
"w-full max-w-md rounded-xl",
|
|
138
|
+
"bg-background border-border border",
|
|
139
|
+
"shadow-2xl"
|
|
140
|
+
)}
|
|
141
|
+
>
|
|
142
|
+
{/* Header */}
|
|
143
|
+
<div className="border-border flex items-center justify-between border-b px-4 py-3">
|
|
144
|
+
<h2 className="text-lg font-semibold">Start Dev Server</h2>
|
|
145
|
+
<button
|
|
146
|
+
onClick={onClose}
|
|
147
|
+
className="hover:bg-muted rounded-md p-1 transition-colors"
|
|
148
|
+
>
|
|
149
|
+
<X className="h-5 w-5" />
|
|
150
|
+
</button>
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
{/* Content */}
|
|
154
|
+
<div className="space-y-4 p-4">
|
|
155
|
+
{/* Project info */}
|
|
156
|
+
<div className="text-muted-foreground text-sm">
|
|
157
|
+
Project:{" "}
|
|
158
|
+
<span className="text-foreground font-medium">{project.name}</span>
|
|
159
|
+
</div>
|
|
160
|
+
|
|
161
|
+
{/* Project dev servers */}
|
|
162
|
+
{projectDevServers.length > 0 && (
|
|
163
|
+
<div className="space-y-2">
|
|
164
|
+
<div className="flex items-center gap-2 text-sm font-medium">
|
|
165
|
+
<FolderOpen className="h-4 w-4" />
|
|
166
|
+
Project servers
|
|
167
|
+
</div>
|
|
168
|
+
{projectDevServers.map((server) => (
|
|
169
|
+
<button
|
|
170
|
+
key={server.id}
|
|
171
|
+
onClick={() => handleStartProjectServer(server)}
|
|
172
|
+
disabled={starting}
|
|
173
|
+
className={cn(
|
|
174
|
+
"border-primary/30 bg-primary/5 flex w-full items-center gap-3 rounded-lg border p-3",
|
|
175
|
+
"hover:bg-primary/10 text-left transition-colors",
|
|
176
|
+
"disabled:cursor-not-allowed disabled:opacity-50"
|
|
177
|
+
)}
|
|
178
|
+
>
|
|
179
|
+
{server.type === "docker" ? (
|
|
180
|
+
<Container className="h-5 w-5 text-blue-500" />
|
|
181
|
+
) : (
|
|
182
|
+
<Server className="h-5 w-5 text-green-500" />
|
|
183
|
+
)}
|
|
184
|
+
<div className="min-w-0 flex-1">
|
|
185
|
+
<div className="truncate font-medium">{server.name}</div>
|
|
186
|
+
<div className="text-muted-foreground truncate text-xs">
|
|
187
|
+
{server.command}
|
|
188
|
+
{server.port && ` (port ${server.port})`}
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
<Play className="text-primary h-4 w-4" />
|
|
192
|
+
</button>
|
|
193
|
+
))}
|
|
194
|
+
</div>
|
|
195
|
+
)}
|
|
196
|
+
|
|
197
|
+
{/* Detected servers */}
|
|
198
|
+
{loading ? (
|
|
199
|
+
<div className="flex items-center justify-center py-8">
|
|
200
|
+
<Loader2 className="text-muted-foreground h-5 w-5 animate-spin" />
|
|
201
|
+
<span className="text-muted-foreground ml-2 text-sm">
|
|
202
|
+
Detecting dev servers...
|
|
203
|
+
</span>
|
|
204
|
+
</div>
|
|
205
|
+
) : detected.length > 0 ? (
|
|
206
|
+
<div className="space-y-2">
|
|
207
|
+
<div className="text-sm font-medium">Detected servers</div>
|
|
208
|
+
{detected.map((server, i) => (
|
|
209
|
+
<button
|
|
210
|
+
key={i}
|
|
211
|
+
onClick={() => handleStartDetected(server)}
|
|
212
|
+
disabled={starting}
|
|
213
|
+
className={cn(
|
|
214
|
+
"border-border flex w-full items-center gap-3 rounded-lg border p-3",
|
|
215
|
+
"hover:bg-muted/50 text-left transition-colors",
|
|
216
|
+
"disabled:cursor-not-allowed disabled:opacity-50"
|
|
217
|
+
)}
|
|
218
|
+
>
|
|
219
|
+
{server.type === "docker" ? (
|
|
220
|
+
<Container className="h-5 w-5 text-blue-500" />
|
|
221
|
+
) : (
|
|
222
|
+
<Server className="h-5 w-5 text-green-500" />
|
|
223
|
+
)}
|
|
224
|
+
<div className="min-w-0 flex-1">
|
|
225
|
+
<div className="truncate font-medium">{server.name}</div>
|
|
226
|
+
<div className="text-muted-foreground truncate text-xs">
|
|
227
|
+
{server.command}
|
|
228
|
+
{server.ports.length > 0 && ` (port ${server.ports[0]})`}
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
<Play className="text-primary h-4 w-4" />
|
|
232
|
+
</button>
|
|
233
|
+
))}
|
|
234
|
+
</div>
|
|
235
|
+
) : (
|
|
236
|
+
<div className="text-muted-foreground py-4 text-center text-sm">
|
|
237
|
+
No dev servers detected automatically
|
|
238
|
+
</div>
|
|
239
|
+
)}
|
|
240
|
+
|
|
241
|
+
{/* Custom server form toggle */}
|
|
242
|
+
{!showCustom ? (
|
|
243
|
+
<button
|
|
244
|
+
onClick={() => setShowCustom(true)}
|
|
245
|
+
className="text-primary w-full text-sm hover:underline"
|
|
246
|
+
>
|
|
247
|
+
+ Add custom server
|
|
248
|
+
</button>
|
|
249
|
+
) : (
|
|
250
|
+
<div className="border-border space-y-3 border-t pt-4">
|
|
251
|
+
<div className="text-sm font-medium">Custom server</div>
|
|
252
|
+
|
|
253
|
+
{/* Type selector */}
|
|
254
|
+
<div className="flex gap-2">
|
|
255
|
+
<button
|
|
256
|
+
onClick={() => setCustomType("node")}
|
|
257
|
+
className={cn(
|
|
258
|
+
"flex flex-1 items-center justify-center gap-2 rounded-md py-2",
|
|
259
|
+
"border transition-colors",
|
|
260
|
+
customType === "node"
|
|
261
|
+
? "border-primary bg-primary/10 text-primary"
|
|
262
|
+
: "border-border hover:bg-muted"
|
|
263
|
+
)}
|
|
264
|
+
>
|
|
265
|
+
<Server className="h-4 w-4" />
|
|
266
|
+
Node.js
|
|
267
|
+
</button>
|
|
268
|
+
<button
|
|
269
|
+
onClick={() => setCustomType("docker")}
|
|
270
|
+
className={cn(
|
|
271
|
+
"flex flex-1 items-center justify-center gap-2 rounded-md py-2",
|
|
272
|
+
"border transition-colors",
|
|
273
|
+
customType === "docker"
|
|
274
|
+
? "border-primary bg-primary/10 text-primary"
|
|
275
|
+
: "border-border hover:bg-muted"
|
|
276
|
+
)}
|
|
277
|
+
>
|
|
278
|
+
<Container className="h-4 w-4" />
|
|
279
|
+
Docker
|
|
280
|
+
</button>
|
|
281
|
+
</div>
|
|
282
|
+
|
|
283
|
+
{/* Name input */}
|
|
284
|
+
<input
|
|
285
|
+
type="text"
|
|
286
|
+
placeholder="Server name"
|
|
287
|
+
value={customName}
|
|
288
|
+
onChange={(e) => setCustomName(e.target.value)}
|
|
289
|
+
className={cn(
|
|
290
|
+
"border-border bg-background w-full rounded-md border px-3 py-2",
|
|
291
|
+
"placeholder:text-muted-foreground text-sm",
|
|
292
|
+
"focus:ring-primary/50 focus:ring-2 focus:outline-none"
|
|
293
|
+
)}
|
|
294
|
+
/>
|
|
295
|
+
|
|
296
|
+
{/* Command input */}
|
|
297
|
+
<input
|
|
298
|
+
type="text"
|
|
299
|
+
placeholder={
|
|
300
|
+
customType === "docker"
|
|
301
|
+
? "Service name (e.g., web)"
|
|
302
|
+
: "Command (e.g., npm run dev)"
|
|
303
|
+
}
|
|
304
|
+
value={customCommand}
|
|
305
|
+
onChange={(e) => setCustomCommand(e.target.value)}
|
|
306
|
+
className={cn(
|
|
307
|
+
"border-border bg-background w-full rounded-md border px-3 py-2",
|
|
308
|
+
"placeholder:text-muted-foreground font-mono text-sm",
|
|
309
|
+
"focus:ring-primary/50 focus:ring-2 focus:outline-none"
|
|
310
|
+
)}
|
|
311
|
+
/>
|
|
312
|
+
|
|
313
|
+
{/* Port input (for Node.js only) */}
|
|
314
|
+
{customType === "node" && (
|
|
315
|
+
<input
|
|
316
|
+
type="text"
|
|
317
|
+
placeholder="Port (optional)"
|
|
318
|
+
value={customPort}
|
|
319
|
+
onChange={(e) => setCustomPort(e.target.value)}
|
|
320
|
+
className={cn(
|
|
321
|
+
"border-border bg-background w-full rounded-md border px-3 py-2",
|
|
322
|
+
"placeholder:text-muted-foreground text-sm",
|
|
323
|
+
"focus:ring-primary/50 focus:ring-2 focus:outline-none"
|
|
324
|
+
)}
|
|
325
|
+
/>
|
|
326
|
+
)}
|
|
327
|
+
|
|
328
|
+
{/* Start custom button */}
|
|
329
|
+
<button
|
|
330
|
+
onClick={handleStartCustom}
|
|
331
|
+
disabled={starting || !customName || !customCommand}
|
|
332
|
+
className={cn(
|
|
333
|
+
"flex w-full items-center justify-center gap-2 rounded-md py-2",
|
|
334
|
+
"bg-primary text-primary-foreground font-medium",
|
|
335
|
+
"hover:bg-primary/90 transition-colors",
|
|
336
|
+
"disabled:cursor-not-allowed disabled:opacity-50"
|
|
337
|
+
)}
|
|
338
|
+
>
|
|
339
|
+
{starting ? (
|
|
340
|
+
<Loader2 className="h-4 w-4 animate-spin" />
|
|
341
|
+
) : (
|
|
342
|
+
<Play className="h-4 w-4" />
|
|
343
|
+
)}
|
|
344
|
+
Start Server
|
|
345
|
+
</button>
|
|
346
|
+
</div>
|
|
347
|
+
)}
|
|
348
|
+
|
|
349
|
+
{/* Error message */}
|
|
350
|
+
{error && (
|
|
351
|
+
<div className="rounded-md bg-red-500/10 p-2 text-sm text-red-500">
|
|
352
|
+
{error}
|
|
353
|
+
</div>
|
|
354
|
+
)}
|
|
355
|
+
</div>
|
|
356
|
+
</div>
|
|
357
|
+
</div>
|
|
358
|
+
);
|
|
359
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { X, Plus, Minus, ChevronLeft } from "lucide-react";
|
|
4
|
+
import { Button } from "@/components/ui/button";
|
|
5
|
+
import { UnifiedDiff } from "./UnifiedDiff";
|
|
6
|
+
import { parseDiff, getDiffFileName, getDiffSummary } from "@/lib/diff-parser";
|
|
7
|
+
import { cn } from "@/lib/utils";
|
|
8
|
+
|
|
9
|
+
interface DiffModalProps {
|
|
10
|
+
diff: string;
|
|
11
|
+
fileName?: string;
|
|
12
|
+
onClose: () => void;
|
|
13
|
+
onStage?: () => void;
|
|
14
|
+
onUnstage?: () => void;
|
|
15
|
+
isStaged?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function DiffModal({
|
|
19
|
+
diff,
|
|
20
|
+
fileName,
|
|
21
|
+
onClose,
|
|
22
|
+
onStage,
|
|
23
|
+
onUnstage,
|
|
24
|
+
isStaged = false,
|
|
25
|
+
}: DiffModalProps) {
|
|
26
|
+
const parsedDiff = parseDiff(diff);
|
|
27
|
+
const displayName = fileName || getDiffFileName(parsedDiff);
|
|
28
|
+
const summary = getDiffSummary(parsedDiff);
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<div className="bg-background fixed inset-0 z-50 flex flex-col">
|
|
32
|
+
{/* Header */}
|
|
33
|
+
<div className="border-border bg-background/95 flex items-center gap-2 border-b p-3 backdrop-blur-sm">
|
|
34
|
+
<Button
|
|
35
|
+
variant="ghost"
|
|
36
|
+
size="icon-sm"
|
|
37
|
+
onClick={onClose}
|
|
38
|
+
className="h-9 w-9"
|
|
39
|
+
>
|
|
40
|
+
<ChevronLeft className="h-5 w-5" />
|
|
41
|
+
</Button>
|
|
42
|
+
|
|
43
|
+
<div className="min-w-0 flex-1">
|
|
44
|
+
<h3 className="truncate text-sm font-medium">{displayName}</h3>
|
|
45
|
+
<p className="text-muted-foreground text-xs">{summary}</p>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
{/* Stage/Unstage button */}
|
|
49
|
+
{(onStage || onUnstage) && (
|
|
50
|
+
<Button
|
|
51
|
+
variant={isStaged ? "outline" : "default"}
|
|
52
|
+
size="sm"
|
|
53
|
+
onClick={isStaged ? onUnstage : onStage}
|
|
54
|
+
className="h-9"
|
|
55
|
+
>
|
|
56
|
+
{isStaged ? (
|
|
57
|
+
<>
|
|
58
|
+
<Minus className="mr-1 h-4 w-4" />
|
|
59
|
+
Unstage
|
|
60
|
+
</>
|
|
61
|
+
) : (
|
|
62
|
+
<>
|
|
63
|
+
<Plus className="mr-1 h-4 w-4" />
|
|
64
|
+
Stage
|
|
65
|
+
</>
|
|
66
|
+
)}
|
|
67
|
+
</Button>
|
|
68
|
+
)}
|
|
69
|
+
|
|
70
|
+
<Button
|
|
71
|
+
variant="ghost"
|
|
72
|
+
size="icon-sm"
|
|
73
|
+
onClick={onClose}
|
|
74
|
+
className="h-9 w-9 md:hidden"
|
|
75
|
+
>
|
|
76
|
+
<X className="h-4 w-4" />
|
|
77
|
+
</Button>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
{/* Content */}
|
|
81
|
+
<div className="flex-1 overflow-auto p-3">
|
|
82
|
+
{diff ? (
|
|
83
|
+
<UnifiedDiff
|
|
84
|
+
diff={parsedDiff}
|
|
85
|
+
fileName={displayName}
|
|
86
|
+
expanded={true}
|
|
87
|
+
/>
|
|
88
|
+
) : (
|
|
89
|
+
<div className="text-muted-foreground flex h-full items-center justify-center">
|
|
90
|
+
<p className="text-sm">No changes to display</p>
|
|
91
|
+
</div>
|
|
92
|
+
)}
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
{/* Mobile action bar */}
|
|
96
|
+
<div className="border-border bg-background/95 safe-area-bottom flex items-center justify-between border-t p-3 backdrop-blur-sm md:hidden">
|
|
97
|
+
<div className="flex items-center gap-4">
|
|
98
|
+
{parsedDiff.additions > 0 && (
|
|
99
|
+
<span className="flex items-center gap-1 text-sm text-green-500">
|
|
100
|
+
<Plus className="h-4 w-4" />
|
|
101
|
+
{parsedDiff.additions}
|
|
102
|
+
</span>
|
|
103
|
+
)}
|
|
104
|
+
{parsedDiff.deletions > 0 && (
|
|
105
|
+
<span className="flex items-center gap-1 text-sm text-red-500">
|
|
106
|
+
<Minus className="h-4 w-4" />
|
|
107
|
+
{parsedDiff.deletions}
|
|
108
|
+
</span>
|
|
109
|
+
)}
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
{(onStage || onUnstage) && (
|
|
113
|
+
<Button
|
|
114
|
+
variant={isStaged ? "outline" : "default"}
|
|
115
|
+
size="default"
|
|
116
|
+
onClick={isStaged ? onUnstage : onStage}
|
|
117
|
+
className="min-h-[44px]"
|
|
118
|
+
>
|
|
119
|
+
{isStaged ? "Unstage" : "Stage"}
|
|
120
|
+
</Button>
|
|
121
|
+
)}
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
interface DiffViewProps {
|
|
128
|
+
diff: string;
|
|
129
|
+
fileName?: string;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Inline diff view (non-modal)
|
|
134
|
+
* For embedding in other components
|
|
135
|
+
*/
|
|
136
|
+
export function DiffView({ diff, fileName }: DiffViewProps) {
|
|
137
|
+
const parsedDiff = parseDiff(diff);
|
|
138
|
+
const displayName = fileName || getDiffFileName(parsedDiff);
|
|
139
|
+
|
|
140
|
+
if (!diff) {
|
|
141
|
+
return (
|
|
142
|
+
<div className="text-muted-foreground p-4 text-center">
|
|
143
|
+
<p className="text-sm">No changes to display</p>
|
|
144
|
+
</div>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return (
|
|
149
|
+
<UnifiedDiff diff={parsedDiff} fileName={displayName} expanded={true} />
|
|
150
|
+
);
|
|
151
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { ChevronDown, ChevronRight, Plus, Minus } from "lucide-react";
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
import type { ParsedDiff, DiffHunk, DiffLine } from "@/lib/diff-parser";
|
|
7
|
+
|
|
8
|
+
interface UnifiedDiffProps {
|
|
9
|
+
diff: ParsedDiff;
|
|
10
|
+
fileName: string;
|
|
11
|
+
expanded?: boolean;
|
|
12
|
+
onToggle?: () => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function UnifiedDiff({
|
|
16
|
+
diff,
|
|
17
|
+
fileName,
|
|
18
|
+
expanded = true,
|
|
19
|
+
onToggle,
|
|
20
|
+
}: UnifiedDiffProps) {
|
|
21
|
+
const [localExpanded, setLocalExpanded] = useState(expanded);
|
|
22
|
+
const isExpanded = onToggle ? expanded : localExpanded;
|
|
23
|
+
|
|
24
|
+
const handleToggle = () => {
|
|
25
|
+
if (onToggle) {
|
|
26
|
+
onToggle();
|
|
27
|
+
} else {
|
|
28
|
+
setLocalExpanded(!localExpanded);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div className="border-border overflow-hidden rounded-lg border">
|
|
34
|
+
{/* File header */}
|
|
35
|
+
<button
|
|
36
|
+
onClick={handleToggle}
|
|
37
|
+
className={cn(
|
|
38
|
+
"flex w-full items-center gap-2 px-3 py-2.5 text-sm",
|
|
39
|
+
"bg-muted/50 hover:bg-muted text-left transition-colors",
|
|
40
|
+
"min-h-[44px]" // Mobile touch target
|
|
41
|
+
)}
|
|
42
|
+
>
|
|
43
|
+
{isExpanded ? (
|
|
44
|
+
<ChevronDown className="text-muted-foreground h-4 w-4 flex-shrink-0" />
|
|
45
|
+
) : (
|
|
46
|
+
<ChevronRight className="text-muted-foreground h-4 w-4 flex-shrink-0" />
|
|
47
|
+
)}
|
|
48
|
+
|
|
49
|
+
<span className="flex-1 truncate font-mono text-xs">{fileName}</span>
|
|
50
|
+
|
|
51
|
+
{/* Stats */}
|
|
52
|
+
<span className="flex flex-shrink-0 items-center gap-2 text-xs">
|
|
53
|
+
{diff.additions > 0 && (
|
|
54
|
+
<span className="flex items-center gap-0.5 text-green-500">
|
|
55
|
+
<Plus className="h-3 w-3" />
|
|
56
|
+
{diff.additions}
|
|
57
|
+
</span>
|
|
58
|
+
)}
|
|
59
|
+
{diff.deletions > 0 && (
|
|
60
|
+
<span className="flex items-center gap-0.5 text-red-500">
|
|
61
|
+
<Minus className="h-3 w-3" />
|
|
62
|
+
{diff.deletions}
|
|
63
|
+
</span>
|
|
64
|
+
)}
|
|
65
|
+
</span>
|
|
66
|
+
</button>
|
|
67
|
+
|
|
68
|
+
{/* Diff content */}
|
|
69
|
+
{isExpanded && (
|
|
70
|
+
<div className="overflow-x-auto">
|
|
71
|
+
{diff.isBinary ? (
|
|
72
|
+
<div className="text-muted-foreground px-4 py-8 text-center text-sm">
|
|
73
|
+
Binary file not shown
|
|
74
|
+
</div>
|
|
75
|
+
) : diff.hunks.length === 0 ? (
|
|
76
|
+
<div className="text-muted-foreground px-4 py-8 text-center text-sm">
|
|
77
|
+
No changes
|
|
78
|
+
</div>
|
|
79
|
+
) : (
|
|
80
|
+
<div className="font-mono text-xs">
|
|
81
|
+
{diff.hunks.map((hunk, index) => (
|
|
82
|
+
<Hunk key={index} hunk={hunk} />
|
|
83
|
+
))}
|
|
84
|
+
</div>
|
|
85
|
+
)}
|
|
86
|
+
</div>
|
|
87
|
+
)}
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
interface HunkProps {
|
|
93
|
+
hunk: DiffHunk;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function Hunk({ hunk }: HunkProps) {
|
|
97
|
+
return (
|
|
98
|
+
<div>
|
|
99
|
+
{/* Hunk header */}
|
|
100
|
+
<div className="border-border border-y bg-blue-500/10 px-3 py-1 text-xs text-blue-400">
|
|
101
|
+
{hunk.header}
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
{/* Lines */}
|
|
105
|
+
<table className="w-full border-collapse">
|
|
106
|
+
<tbody>
|
|
107
|
+
{hunk.lines.map((line, index) => (
|
|
108
|
+
<DiffLineRow key={index} line={line} />
|
|
109
|
+
))}
|
|
110
|
+
</tbody>
|
|
111
|
+
</table>
|
|
112
|
+
</div>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
interface DiffLineRowProps {
|
|
117
|
+
line: DiffLine;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function DiffLineRow({ line }: DiffLineRowProps) {
|
|
121
|
+
const bgColor = getLineBgColor(line.type);
|
|
122
|
+
const textColor = getLineTextColor(line.type);
|
|
123
|
+
|
|
124
|
+
// Skip header lines in the main content
|
|
125
|
+
if (line.type === "header") {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return (
|
|
130
|
+
<tr className={cn("hover:bg-muted/30", bgColor)}>
|
|
131
|
+
{/* Old line number */}
|
|
132
|
+
<td className="text-muted-foreground border-border/50 w-12 border-r px-2 py-0.5 text-right tabular-nums select-none">
|
|
133
|
+
{line.oldLineNumber || ""}
|
|
134
|
+
</td>
|
|
135
|
+
|
|
136
|
+
{/* New line number */}
|
|
137
|
+
<td className="text-muted-foreground border-border/50 w-12 border-r px-2 py-0.5 text-right tabular-nums select-none">
|
|
138
|
+
{line.newLineNumber || ""}
|
|
139
|
+
</td>
|
|
140
|
+
|
|
141
|
+
{/* Line marker */}
|
|
142
|
+
<td className={cn("w-6 px-1 py-0.5 text-center select-none", textColor)}>
|
|
143
|
+
{getLineMarker(line.type)}
|
|
144
|
+
</td>
|
|
145
|
+
|
|
146
|
+
{/* Content */}
|
|
147
|
+
<td className={cn("px-2 py-0.5 whitespace-pre", textColor)}>
|
|
148
|
+
{line.content || " "}
|
|
149
|
+
</td>
|
|
150
|
+
</tr>
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function getLineBgColor(type: DiffLine["type"]): string {
|
|
155
|
+
switch (type) {
|
|
156
|
+
case "addition":
|
|
157
|
+
return "bg-green-500/10";
|
|
158
|
+
case "deletion":
|
|
159
|
+
return "bg-red-500/10";
|
|
160
|
+
default:
|
|
161
|
+
return "";
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function getLineTextColor(type: DiffLine["type"]): string {
|
|
166
|
+
switch (type) {
|
|
167
|
+
case "addition":
|
|
168
|
+
return "text-green-400";
|
|
169
|
+
case "deletion":
|
|
170
|
+
return "text-red-400";
|
|
171
|
+
default:
|
|
172
|
+
return "text-foreground";
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function getLineMarker(type: DiffLine["type"]): string {
|
|
177
|
+
switch (type) {
|
|
178
|
+
case "addition":
|
|
179
|
+
return "+";
|
|
180
|
+
case "deletion":
|
|
181
|
+
return "-";
|
|
182
|
+
default:
|
|
183
|
+
return "";
|
|
184
|
+
}
|
|
185
|
+
}
|