@akiojin/gwt 4.2.0 → 4.3.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/dist/claude.d.ts +2 -0
- package/dist/claude.d.ts.map +1 -1
- package/dist/claude.js +49 -2
- package/dist/claude.js.map +1 -1
- package/dist/cli/ui/components/App.d.ts.map +1 -1
- package/dist/cli/ui/components/App.js +68 -68
- package/dist/cli/ui/components/App.js.map +1 -1
- package/dist/cli/ui/components/screens/BranchListScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/BranchListScreen.js +6 -1
- package/dist/cli/ui/components/screens/BranchListScreen.js.map +1 -1
- package/dist/client/assets/index-ChHC-Puh.css +1 -0
- package/dist/client/assets/index-PqK9jkug.js +78 -0
- package/dist/client/index.html +2 -2
- package/dist/config/builtin-tools.d.ts.map +1 -1
- package/dist/config/builtin-tools.js +3 -0
- package/dist/config/builtin-tools.js.map +1 -1
- package/dist/config/tools.d.ts.map +1 -1
- package/dist/config/tools.js +10 -1
- package/dist/config/tools.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/launcher.d.ts.map +1 -1
- package/dist/launcher.js +15 -0
- package/dist/launcher.js.map +1 -1
- package/dist/services/aiToolResolver.d.ts.map +1 -1
- package/dist/services/aiToolResolver.js +55 -8
- package/dist/services/aiToolResolver.js.map +1 -1
- package/dist/services/customToolResolver.d.ts.map +1 -1
- package/dist/services/customToolResolver.js +22 -17
- package/dist/services/customToolResolver.js.map +1 -1
- package/dist/utils/webui.js +1 -1
- package/dist/web/client/src/components/BranchGraph.d.ts +5 -0
- package/dist/web/client/src/components/BranchGraph.d.ts.map +1 -1
- package/dist/web/client/src/components/BranchGraph.js +35 -108
- package/dist/web/client/src/components/BranchGraph.js.map +1 -1
- package/dist/web/client/src/components/graph/BranchDetailPanel.d.ts +15 -0
- package/dist/web/client/src/components/graph/BranchDetailPanel.d.ts.map +1 -0
- package/dist/web/client/src/components/graph/BranchDetailPanel.js +57 -0
- package/dist/web/client/src/components/graph/BranchDetailPanel.js.map +1 -0
- package/dist/web/client/src/components/graph/BranchNode.d.ts +13 -0
- package/dist/web/client/src/components/graph/BranchNode.d.ts.map +1 -0
- package/dist/web/client/src/components/graph/BranchNode.js +103 -0
- package/dist/web/client/src/components/graph/BranchNode.js.map +1 -0
- package/dist/web/client/src/components/graph/ClusterNode.d.ts +13 -0
- package/dist/web/client/src/components/graph/ClusterNode.d.ts.map +1 -0
- package/dist/web/client/src/components/graph/ClusterNode.js +109 -0
- package/dist/web/client/src/components/graph/ClusterNode.js.map +1 -0
- package/dist/web/client/src/components/graph/SynapticCanvas.d.ts +17 -0
- package/dist/web/client/src/components/graph/SynapticCanvas.d.ts.map +1 -0
- package/dist/web/client/src/components/graph/SynapticCanvas.js +94 -0
- package/dist/web/client/src/components/graph/SynapticCanvas.js.map +1 -0
- package/dist/web/client/src/components/graph/SynapticEdge.d.ts +13 -0
- package/dist/web/client/src/components/graph/SynapticEdge.d.ts.map +1 -0
- package/dist/web/client/src/components/graph/SynapticEdge.js +113 -0
- package/dist/web/client/src/components/graph/SynapticEdge.js.map +1 -0
- package/dist/web/client/src/components/graph/graphUtils.d.ts +67 -0
- package/dist/web/client/src/components/graph/graphUtils.d.ts.map +1 -0
- package/dist/web/client/src/components/graph/graphUtils.js +175 -0
- package/dist/web/client/src/components/graph/graphUtils.js.map +1 -0
- package/dist/web/client/src/components/graph/index.d.ts +10 -0
- package/dist/web/client/src/components/graph/index.d.ts.map +1 -0
- package/dist/web/client/src/components/graph/index.js +10 -0
- package/dist/web/client/src/components/graph/index.js.map +1 -0
- package/dist/web/client/src/lib/websocket.d.ts.map +1 -1
- package/dist/web/client/src/lib/websocket.js +2 -1
- package/dist/web/client/src/lib/websocket.js.map +1 -1
- package/dist/web/client/vite.config.js +1 -1
- package/dist/web/server/env/importer.d.ts.map +1 -1
- package/dist/web/server/env/importer.js +4 -0
- package/dist/web/server/env/importer.js.map +1 -1
- package/dist/web/server/index.d.ts.map +1 -1
- package/dist/web/server/index.js +9 -0
- package/dist/web/server/index.js.map +1 -1
- package/dist/web/server/pty/manager.d.ts.map +1 -1
- package/dist/web/server/pty/manager.js +24 -1
- package/dist/web/server/pty/manager.js.map +1 -1
- package/dist/web/server/routes/sessions.d.ts.map +1 -1
- package/dist/web/server/routes/sessions.js +7 -0
- package/dist/web/server/routes/sessions.js.map +1 -1
- package/dist/web/server/tray.d.ts +1 -1
- package/dist/web/server/tray.d.ts.map +1 -1
- package/dist/web/server/tray.js +52 -34
- package/dist/web/server/tray.js.map +1 -1
- package/dist/web/server/websocket/handler.d.ts.map +1 -1
- package/dist/web/server/websocket/handler.js +4 -0
- package/dist/web/server/websocket/handler.js.map +1 -1
- package/package.json +5 -2
- package/src/claude.ts +57 -2
- package/src/cli/ui/__tests__/components/App.shortcuts.test.tsx +104 -0
- package/src/cli/ui/components/App.tsx +91 -81
- package/src/cli/ui/components/screens/BranchListScreen.tsx +6 -1
- package/src/cli/ui/types.ts +1 -1
- package/src/config/builtin-tools.ts +3 -0
- package/src/config/tools.ts +24 -1
- package/src/index.ts +3 -1
- package/src/launcher.ts +26 -0
- package/src/services/aiToolResolver.ts +75 -9
- package/src/services/customToolResolver.ts +32 -17
- package/src/utils/webui.ts +1 -1
- package/src/web/client/src/components/BranchGraph.tsx +51 -208
- package/src/web/client/src/components/graph/BranchDetailPanel.tsx +152 -0
- package/src/web/client/src/components/graph/BranchNode.tsx +200 -0
- package/src/web/client/src/components/graph/ClusterNode.tsx +211 -0
- package/src/web/client/src/components/graph/SynapticCanvas.tsx +171 -0
- package/src/web/client/src/components/graph/SynapticEdge.tsx +311 -0
- package/src/web/client/src/components/graph/graphUtils.ts +265 -0
- package/src/web/client/src/components/graph/index.ts +10 -0
- package/src/web/client/src/index.css +314 -29
- package/src/web/client/src/lib/websocket.ts +2 -1
- package/src/web/client/vite.config.ts +1 -1
- package/src/web/server/env/importer.ts +5 -0
- package/src/web/server/index.ts +10 -0
- package/src/web/server/pty/manager.ts +43 -1
- package/src/web/server/routes/sessions.ts +15 -0
- package/src/web/server/tray.ts +62 -46
- package/src/web/server/websocket/handler.ts +13 -0
- package/dist/client/assets/index-DsDNCy5f.css +0 -1
- package/dist/client/assets/index-v8smkNOL.js +0 -72
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ブランチ詳細パネル
|
|
3
|
+
*
|
|
4
|
+
* ノードクリック時に右側に表示されるサイドパネル
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React from "react";
|
|
8
|
+
import { Link } from "react-router-dom";
|
|
9
|
+
import { X } from "lucide-react";
|
|
10
|
+
import { Badge } from "@/components/ui/badge";
|
|
11
|
+
import { Button } from "@/components/ui/button";
|
|
12
|
+
import {
|
|
13
|
+
Card,
|
|
14
|
+
CardHeader,
|
|
15
|
+
CardContent,
|
|
16
|
+
CardFooter,
|
|
17
|
+
} from "@/components/ui/card";
|
|
18
|
+
import { cn } from "@/lib/utils";
|
|
19
|
+
import type { Branch } from "../../../../../types/api.js";
|
|
20
|
+
|
|
21
|
+
interface BranchDetailPanelProps {
|
|
22
|
+
branch: Branch | null;
|
|
23
|
+
onClose: () => void;
|
|
24
|
+
className?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function BranchDetailPanel({
|
|
28
|
+
branch,
|
|
29
|
+
onClose,
|
|
30
|
+
className,
|
|
31
|
+
}: BranchDetailPanelProps) {
|
|
32
|
+
if (!branch) return null;
|
|
33
|
+
|
|
34
|
+
const hasWorktree = Boolean(branch.worktreePath);
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div
|
|
38
|
+
className={cn(
|
|
39
|
+
"absolute right-0 top-0 h-full w-80 border-l bg-card/95 backdrop-blur-sm",
|
|
40
|
+
"animate-in slide-in-from-right duration-300",
|
|
41
|
+
className,
|
|
42
|
+
)}
|
|
43
|
+
>
|
|
44
|
+
<Card className="h-full rounded-none border-0">
|
|
45
|
+
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-4">
|
|
46
|
+
<div className="flex items-center gap-2">
|
|
47
|
+
<Badge variant={branch.type === "local" ? "local" : "remote"}>
|
|
48
|
+
{branch.type === "local" ? "Local" : "Remote"}
|
|
49
|
+
</Badge>
|
|
50
|
+
{hasWorktree && <Badge variant="success">Worktree</Badge>}
|
|
51
|
+
</div>
|
|
52
|
+
<Button
|
|
53
|
+
variant="ghost"
|
|
54
|
+
size="icon"
|
|
55
|
+
onClick={onClose}
|
|
56
|
+
className="h-8 w-8"
|
|
57
|
+
>
|
|
58
|
+
<X className="h-4 w-4" />
|
|
59
|
+
</Button>
|
|
60
|
+
</CardHeader>
|
|
61
|
+
|
|
62
|
+
<CardContent className="space-y-4">
|
|
63
|
+
{/* ブランチ名 */}
|
|
64
|
+
<div>
|
|
65
|
+
<p className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
|
|
66
|
+
Branch Name
|
|
67
|
+
</p>
|
|
68
|
+
<p className="mt-1 break-all font-mono text-sm">{branch.name}</p>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
{/* ベースブランチ */}
|
|
72
|
+
<div>
|
|
73
|
+
<p className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
|
|
74
|
+
Base Branch
|
|
75
|
+
</p>
|
|
76
|
+
<p className="mt-1 font-mono text-sm">
|
|
77
|
+
{branch.baseBranch ?? "Unknown"}
|
|
78
|
+
</p>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
{/* コミットメッセージ */}
|
|
82
|
+
<div>
|
|
83
|
+
<p className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
|
|
84
|
+
Last Commit
|
|
85
|
+
</p>
|
|
86
|
+
<p className="mt-1 text-sm text-muted-foreground">
|
|
87
|
+
{branch.commitMessage ?? "No commit message"}
|
|
88
|
+
</p>
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
{/* Divergence */}
|
|
92
|
+
{branch.divergence && (
|
|
93
|
+
<div>
|
|
94
|
+
<p className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
|
|
95
|
+
Divergence
|
|
96
|
+
</p>
|
|
97
|
+
<div className="mt-2 flex flex-wrap gap-2">
|
|
98
|
+
<Badge variant="outline" className="text-xs">
|
|
99
|
+
↑ {branch.divergence.ahead} ahead
|
|
100
|
+
</Badge>
|
|
101
|
+
<Badge variant="outline" className="text-xs">
|
|
102
|
+
↓ {branch.divergence.behind} behind
|
|
103
|
+
</Badge>
|
|
104
|
+
<Badge
|
|
105
|
+
variant={branch.divergence.upToDate ? "success" : "warning"}
|
|
106
|
+
className="text-xs"
|
|
107
|
+
>
|
|
108
|
+
{branch.divergence.upToDate ? "Up to date" : "Needs sync"}
|
|
109
|
+
</Badge>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
)}
|
|
113
|
+
|
|
114
|
+
{/* Worktree パス */}
|
|
115
|
+
{hasWorktree && (
|
|
116
|
+
<div>
|
|
117
|
+
<p className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
|
|
118
|
+
Worktree Path
|
|
119
|
+
</p>
|
|
120
|
+
<p className="mt-1 break-all font-mono text-xs text-muted-foreground">
|
|
121
|
+
{branch.worktreePath}
|
|
122
|
+
</p>
|
|
123
|
+
</div>
|
|
124
|
+
)}
|
|
125
|
+
|
|
126
|
+
{/* マージステータス */}
|
|
127
|
+
{branch.mergeStatus && (
|
|
128
|
+
<div>
|
|
129
|
+
<p className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
|
|
130
|
+
Merge Status
|
|
131
|
+
</p>
|
|
132
|
+
<Badge
|
|
133
|
+
variant={
|
|
134
|
+
branch.mergeStatus === "merged" ? "success" : "outline"
|
|
135
|
+
}
|
|
136
|
+
className="mt-1"
|
|
137
|
+
>
|
|
138
|
+
{branch.mergeStatus}
|
|
139
|
+
</Badge>
|
|
140
|
+
</div>
|
|
141
|
+
)}
|
|
142
|
+
</CardContent>
|
|
143
|
+
|
|
144
|
+
<CardFooter className="flex flex-col gap-2">
|
|
145
|
+
<Button asChild className="w-full">
|
|
146
|
+
<Link to={`/${encodeURIComponent(branch.name)}`}>View Details</Link>
|
|
147
|
+
</Button>
|
|
148
|
+
</CardFooter>
|
|
149
|
+
</Card>
|
|
150
|
+
</div>
|
|
151
|
+
);
|
|
152
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ブランチノードコンポーネント
|
|
3
|
+
*
|
|
4
|
+
* ソーマ(神経細胞体)をイメージした生物発光ノード
|
|
5
|
+
* 多層グロー効果と有機的なアニメーションで生命感を表現
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React, { memo, useMemo } from "react";
|
|
9
|
+
import { Handle, Position, type NodeProps } from "@xyflow/react";
|
|
10
|
+
import { cn } from "@/lib/utils";
|
|
11
|
+
import { getNodeColor, getNodeSize, type GraphNode } from "./graphUtils";
|
|
12
|
+
|
|
13
|
+
type BranchNodeProps = NodeProps<GraphNode>;
|
|
14
|
+
|
|
15
|
+
export const BranchNode = memo(function BranchNode({
|
|
16
|
+
data,
|
|
17
|
+
selected,
|
|
18
|
+
}: BranchNodeProps) {
|
|
19
|
+
const { branch } = data;
|
|
20
|
+
|
|
21
|
+
if (!branch) return null;
|
|
22
|
+
|
|
23
|
+
const size = getNodeSize(branch);
|
|
24
|
+
const _baseColor = getNodeColor(branch);
|
|
25
|
+
const hasWorktree = Boolean(branch.worktreePath);
|
|
26
|
+
const isMerged = branch.mergeStatus === "merged";
|
|
27
|
+
|
|
28
|
+
// ブランチ名を短縮
|
|
29
|
+
const displayName = useMemo(() => {
|
|
30
|
+
if (branch.name.length > 18) {
|
|
31
|
+
return `${branch.name.slice(0, 15)}...`;
|
|
32
|
+
}
|
|
33
|
+
return branch.name;
|
|
34
|
+
}, [branch.name]);
|
|
35
|
+
|
|
36
|
+
// ノードカラーをHSL値として抽出(動的スタイル用)
|
|
37
|
+
const colorStyle = useMemo(() => {
|
|
38
|
+
if (hasWorktree) {
|
|
39
|
+
return {
|
|
40
|
+
primary: "hsl(160 90% 45%)",
|
|
41
|
+
glow: "hsl(160 90% 45% / 0.4)",
|
|
42
|
+
inner: "hsl(160 90% 50% / 0.2)",
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
if (branch.type === "local") {
|
|
46
|
+
return {
|
|
47
|
+
primary: "hsl(280 85% 65%)",
|
|
48
|
+
glow: "hsl(280 85% 65% / 0.4)",
|
|
49
|
+
inner: "hsl(280 85% 70% / 0.2)",
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
primary: "hsl(200 90% 55%)",
|
|
54
|
+
glow: "hsl(200 90% 55% / 0.4)",
|
|
55
|
+
inner: "hsl(200 90% 60% / 0.2)",
|
|
56
|
+
};
|
|
57
|
+
}, [hasWorktree, branch.type]);
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<>
|
|
61
|
+
{/* 入力ハンドル - デンドライト接続点 */}
|
|
62
|
+
<Handle
|
|
63
|
+
type="target"
|
|
64
|
+
position={Position.Top}
|
|
65
|
+
className="!h-3 !w-3 !rounded-full !border-2 !bg-transparent transition-all duration-300"
|
|
66
|
+
style={{
|
|
67
|
+
borderColor: colorStyle.primary,
|
|
68
|
+
boxShadow: `0 0 8px ${colorStyle.glow}`,
|
|
69
|
+
}}
|
|
70
|
+
/>
|
|
71
|
+
|
|
72
|
+
{/* ソーマ本体 */}
|
|
73
|
+
<div
|
|
74
|
+
className={cn(
|
|
75
|
+
"group relative flex cursor-pointer items-center justify-center rounded-full",
|
|
76
|
+
"transition-all duration-500 ease-out",
|
|
77
|
+
hasWorktree && "animate-active-synapse",
|
|
78
|
+
!hasWorktree && "animate-soma-pulse",
|
|
79
|
+
selected && "ring-2 ring-offset-4 ring-offset-background",
|
|
80
|
+
isMerged && "opacity-50",
|
|
81
|
+
)}
|
|
82
|
+
style={{
|
|
83
|
+
width: size,
|
|
84
|
+
height: size,
|
|
85
|
+
background: `radial-gradient(circle at 30% 30%, ${colorStyle.inner}, transparent 60%)`,
|
|
86
|
+
border: `2px solid ${colorStyle.primary}`,
|
|
87
|
+
boxShadow: selected
|
|
88
|
+
? `0 0 30px ${colorStyle.glow}, inset 0 0 20px ${colorStyle.inner}, 0 0 0 4px ${colorStyle.primary}`
|
|
89
|
+
: undefined,
|
|
90
|
+
}}
|
|
91
|
+
>
|
|
92
|
+
{/* 外殻グロー */}
|
|
93
|
+
<div
|
|
94
|
+
className="absolute -inset-2 rounded-full opacity-30 blur-md"
|
|
95
|
+
style={{ backgroundColor: colorStyle.primary }}
|
|
96
|
+
/>
|
|
97
|
+
|
|
98
|
+
{/* 核膜 - 内側のリング */}
|
|
99
|
+
<div
|
|
100
|
+
className="absolute inset-2 rounded-full border opacity-40"
|
|
101
|
+
style={{ borderColor: colorStyle.primary }}
|
|
102
|
+
/>
|
|
103
|
+
|
|
104
|
+
{/* 核 - 中心部 */}
|
|
105
|
+
<div
|
|
106
|
+
className="relative z-10 flex items-center justify-center rounded-full shadow-lg"
|
|
107
|
+
style={{
|
|
108
|
+
width: size * 0.45,
|
|
109
|
+
height: size * 0.45,
|
|
110
|
+
background: `linear-gradient(135deg, ${colorStyle.primary}, ${colorStyle.inner})`,
|
|
111
|
+
boxShadow: `0 0 15px ${colorStyle.glow}`,
|
|
112
|
+
}}
|
|
113
|
+
>
|
|
114
|
+
{/* アイコン/タイプ表示 */}
|
|
115
|
+
<span className="text-xs font-bold text-background">
|
|
116
|
+
{branch.type === "local" ? "L" : "R"}
|
|
117
|
+
</span>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
{/* 細胞小器官 - 装飾ドット */}
|
|
121
|
+
{!isMerged && (
|
|
122
|
+
<>
|
|
123
|
+
<div
|
|
124
|
+
className="absolute h-1.5 w-1.5 rounded-full animate-vesicle-release"
|
|
125
|
+
style={{
|
|
126
|
+
backgroundColor: colorStyle.primary,
|
|
127
|
+
top: "20%",
|
|
128
|
+
right: "25%",
|
|
129
|
+
animationDelay: "0s",
|
|
130
|
+
}}
|
|
131
|
+
/>
|
|
132
|
+
<div
|
|
133
|
+
className="absolute h-1 w-1 rounded-full animate-vesicle-release"
|
|
134
|
+
style={{
|
|
135
|
+
backgroundColor: colorStyle.primary,
|
|
136
|
+
bottom: "25%",
|
|
137
|
+
left: "20%",
|
|
138
|
+
animationDelay: "0.7s",
|
|
139
|
+
}}
|
|
140
|
+
/>
|
|
141
|
+
</>
|
|
142
|
+
)}
|
|
143
|
+
|
|
144
|
+
{/* Worktree活性インジケータ */}
|
|
145
|
+
{hasWorktree && (
|
|
146
|
+
<div
|
|
147
|
+
className="absolute -right-1 -top-1 flex h-4 w-4 items-center justify-center rounded-full animate-synapse-spark"
|
|
148
|
+
style={{
|
|
149
|
+
backgroundColor: "hsl(160 90% 45%)",
|
|
150
|
+
boxShadow: "0 0 10px hsl(160 90% 45% / 0.8)",
|
|
151
|
+
}}
|
|
152
|
+
>
|
|
153
|
+
<span className="text-[8px] font-bold text-background">W</span>
|
|
154
|
+
</div>
|
|
155
|
+
)}
|
|
156
|
+
|
|
157
|
+
{/* マージ済みオーバーレイ */}
|
|
158
|
+
{isMerged && (
|
|
159
|
+
<div className="absolute inset-0 flex items-center justify-center rounded-full bg-background/60 backdrop-blur-sm">
|
|
160
|
+
<span className="text-[10px] font-medium text-muted-foreground">
|
|
161
|
+
merged
|
|
162
|
+
</span>
|
|
163
|
+
</div>
|
|
164
|
+
)}
|
|
165
|
+
|
|
166
|
+
{/* ホバーツールチップ */}
|
|
167
|
+
<div
|
|
168
|
+
className={cn(
|
|
169
|
+
"absolute -bottom-12 left-1/2 z-50 -translate-x-1/2",
|
|
170
|
+
"rounded-lg border border-border/50 bg-card/95 px-3 py-1.5 backdrop-blur-md",
|
|
171
|
+
"opacity-0 shadow-xl transition-all duration-300",
|
|
172
|
+
"group-hover:opacity-100 group-hover:-translate-y-1",
|
|
173
|
+
)}
|
|
174
|
+
style={{
|
|
175
|
+
boxShadow: `0 4px 20px hsl(220 30% 0% / 0.5), 0 0 20px ${colorStyle.glow}`,
|
|
176
|
+
}}
|
|
177
|
+
>
|
|
178
|
+
<p className="whitespace-nowrap text-xs font-medium text-foreground">
|
|
179
|
+
{displayName}
|
|
180
|
+
</p>
|
|
181
|
+
<p className="text-[10px] text-muted-foreground">
|
|
182
|
+
{branch.type === "local" ? "Local" : "Remote"}
|
|
183
|
+
{hasWorktree && " • Active"}
|
|
184
|
+
</p>
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
|
|
188
|
+
{/* 出力ハンドル - アクソン接続点 */}
|
|
189
|
+
<Handle
|
|
190
|
+
type="source"
|
|
191
|
+
position={Position.Bottom}
|
|
192
|
+
className="!h-3 !w-3 !rounded-full !border-2 !bg-transparent transition-all duration-300"
|
|
193
|
+
style={{
|
|
194
|
+
borderColor: colorStyle.primary,
|
|
195
|
+
boxShadow: `0 0 8px ${colorStyle.glow}`,
|
|
196
|
+
}}
|
|
197
|
+
/>
|
|
198
|
+
</>
|
|
199
|
+
);
|
|
200
|
+
});
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* クラスタノードコンポーネント
|
|
3
|
+
*
|
|
4
|
+
* ニューロン群(神経細胞集合体)をイメージした有機的ノード
|
|
5
|
+
* 変形する膜と軌道を描く核で複数ブランチのグループを表現
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React, { memo, useMemo } from "react";
|
|
9
|
+
import { Handle, Position, type NodeProps } from "@xyflow/react";
|
|
10
|
+
import { cn } from "@/lib/utils";
|
|
11
|
+
import type { GraphNode } from "./graphUtils";
|
|
12
|
+
|
|
13
|
+
type ClusterNodeProps = NodeProps<GraphNode>;
|
|
14
|
+
|
|
15
|
+
export const ClusterNode = memo(function ClusterNode({
|
|
16
|
+
data,
|
|
17
|
+
selected,
|
|
18
|
+
}: ClusterNodeProps) {
|
|
19
|
+
const { clusterSize = 0, expanded } = data;
|
|
20
|
+
|
|
21
|
+
// クラスタサイズに応じてノードサイズを調整
|
|
22
|
+
const baseSize = 70;
|
|
23
|
+
const size = Math.min(baseSize + clusterSize * 5, 120);
|
|
24
|
+
|
|
25
|
+
// 内部の核の数(最大8個)
|
|
26
|
+
const nucleiCount = Math.min(clusterSize, 8);
|
|
27
|
+
|
|
28
|
+
// 核の配置角度を計算
|
|
29
|
+
const nuclei = useMemo(() => {
|
|
30
|
+
return Array.from({ length: nucleiCount }).map((_, i) => {
|
|
31
|
+
const angle = (360 / nucleiCount) * i;
|
|
32
|
+
const delay = i * 0.5;
|
|
33
|
+
const orbitRadius = size * 0.28;
|
|
34
|
+
return { angle, delay, orbitRadius };
|
|
35
|
+
});
|
|
36
|
+
}, [nucleiCount, size]);
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<>
|
|
40
|
+
{/* 入力ハンドル */}
|
|
41
|
+
<Handle
|
|
42
|
+
type="target"
|
|
43
|
+
position={Position.Top}
|
|
44
|
+
className="!h-3 !w-3 !rounded-full !border-2 !bg-transparent transition-all duration-300"
|
|
45
|
+
style={{
|
|
46
|
+
borderColor: "hsl(185 100% 65%)",
|
|
47
|
+
boxShadow: "0 0 8px hsl(185 100% 65% / 0.4)",
|
|
48
|
+
}}
|
|
49
|
+
/>
|
|
50
|
+
|
|
51
|
+
{/* クラスタ本体 */}
|
|
52
|
+
<div
|
|
53
|
+
className={cn(
|
|
54
|
+
"group relative flex cursor-pointer items-center justify-center",
|
|
55
|
+
"transition-all duration-500 ease-out",
|
|
56
|
+
"animate-membrane-morph",
|
|
57
|
+
selected &&
|
|
58
|
+
"ring-2 ring-primary ring-offset-4 ring-offset-background",
|
|
59
|
+
)}
|
|
60
|
+
style={{
|
|
61
|
+
width: size,
|
|
62
|
+
height: size,
|
|
63
|
+
background: expanded
|
|
64
|
+
? "radial-gradient(circle at 40% 40%, hsl(185 100% 65% / 0.15), hsl(310 85% 60% / 0.08) 50%, transparent 70%)"
|
|
65
|
+
: "radial-gradient(circle at 40% 40%, hsl(220 30% 15% / 0.8), hsl(220 25% 10% / 0.6) 60%, transparent 80%)",
|
|
66
|
+
border: expanded
|
|
67
|
+
? "2px solid hsl(185 100% 65% / 0.6)"
|
|
68
|
+
: "2px dashed hsl(200 30% 30%)",
|
|
69
|
+
boxShadow: expanded
|
|
70
|
+
? "0 0 30px hsl(185 100% 65% / 0.3), inset 0 0 30px hsl(185 100% 65% / 0.1)"
|
|
71
|
+
: "0 0 20px hsl(220 30% 0% / 0.5), inset 0 0 20px hsl(220 30% 5% / 0.5)",
|
|
72
|
+
}}
|
|
73
|
+
>
|
|
74
|
+
{/* 外殻グロー */}
|
|
75
|
+
<div
|
|
76
|
+
className={cn(
|
|
77
|
+
"absolute -inset-3 rounded-full blur-lg transition-opacity duration-500",
|
|
78
|
+
expanded ? "opacity-40" : "opacity-20",
|
|
79
|
+
)}
|
|
80
|
+
style={{
|
|
81
|
+
backgroundColor: expanded
|
|
82
|
+
? "hsl(185 100% 65%)"
|
|
83
|
+
: "hsl(200 30% 40%)",
|
|
84
|
+
borderRadius: "inherit",
|
|
85
|
+
}}
|
|
86
|
+
/>
|
|
87
|
+
|
|
88
|
+
{/* 内部膜 */}
|
|
89
|
+
<div
|
|
90
|
+
className="absolute inset-3 rounded-full border border-dashed opacity-30"
|
|
91
|
+
style={{
|
|
92
|
+
borderColor: expanded ? "hsl(185 100% 65%)" : "hsl(200 30% 40%)",
|
|
93
|
+
borderRadius: "inherit",
|
|
94
|
+
}}
|
|
95
|
+
/>
|
|
96
|
+
|
|
97
|
+
{/* 軌道を描く核 */}
|
|
98
|
+
<div className="absolute inset-0 flex items-center justify-center">
|
|
99
|
+
{nuclei.map((nucleus, i) => (
|
|
100
|
+
<div
|
|
101
|
+
key={i}
|
|
102
|
+
className="absolute animate-nucleus-orbit"
|
|
103
|
+
style={{
|
|
104
|
+
animationDelay: `${-nucleus.delay}s`,
|
|
105
|
+
animationDuration: `${8 + i * 0.5}s`,
|
|
106
|
+
}}
|
|
107
|
+
>
|
|
108
|
+
<div
|
|
109
|
+
className="h-2.5 w-2.5 rounded-full animate-vesicle-release"
|
|
110
|
+
style={{
|
|
111
|
+
backgroundColor: expanded
|
|
112
|
+
? `hsl(${185 + i * 15} 80% 60%)`
|
|
113
|
+
: `hsl(${200 + i * 10} 50% 50%)`,
|
|
114
|
+
boxShadow: expanded
|
|
115
|
+
? `0 0 8px hsl(${185 + i * 15} 80% 60% / 0.8)`
|
|
116
|
+
: `0 0 4px hsl(${200 + i * 10} 50% 50% / 0.5)`,
|
|
117
|
+
animationDelay: `${nucleus.delay * 0.3}s`,
|
|
118
|
+
transform: `translateX(${nucleus.orbitRadius}px)`,
|
|
119
|
+
}}
|
|
120
|
+
/>
|
|
121
|
+
</div>
|
|
122
|
+
))}
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
{/* 中心核 - クラスタサイズ表示 */}
|
|
126
|
+
<div
|
|
127
|
+
className={cn(
|
|
128
|
+
"relative z-10 flex items-center justify-center rounded-full",
|
|
129
|
+
"border-2 backdrop-blur-sm transition-all duration-300",
|
|
130
|
+
)}
|
|
131
|
+
style={{
|
|
132
|
+
width: size * 0.4,
|
|
133
|
+
height: size * 0.4,
|
|
134
|
+
backgroundColor: expanded
|
|
135
|
+
? "hsl(220 30% 8% / 0.9)"
|
|
136
|
+
: "hsl(220 30% 10% / 0.95)",
|
|
137
|
+
borderColor: expanded
|
|
138
|
+
? "hsl(185 100% 65% / 0.8)"
|
|
139
|
+
: "hsl(200 30% 35%)",
|
|
140
|
+
boxShadow: expanded
|
|
141
|
+
? "0 0 20px hsl(185 100% 65% / 0.4), inset 0 0 15px hsl(185 100% 65% / 0.1)"
|
|
142
|
+
: "0 0 10px hsl(220 30% 0% / 0.5)",
|
|
143
|
+
}}
|
|
144
|
+
>
|
|
145
|
+
<span
|
|
146
|
+
className={cn(
|
|
147
|
+
"text-sm font-bold transition-colors duration-300",
|
|
148
|
+
expanded ? "text-primary" : "text-muted-foreground",
|
|
149
|
+
)}
|
|
150
|
+
>
|
|
151
|
+
{clusterSize}
|
|
152
|
+
</span>
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
{/* 展開/折りたたみインジケータ */}
|
|
156
|
+
<div
|
|
157
|
+
className={cn(
|
|
158
|
+
"absolute -right-2 -top-2 flex h-6 w-6 items-center justify-center rounded-full",
|
|
159
|
+
"border-2 text-xs font-bold transition-all duration-300",
|
|
160
|
+
)}
|
|
161
|
+
style={{
|
|
162
|
+
backgroundColor: expanded
|
|
163
|
+
? "hsl(185 100% 65%)"
|
|
164
|
+
: "hsl(220 30% 12%)",
|
|
165
|
+
borderColor: expanded ? "hsl(185 100% 70%)" : "hsl(200 30% 25%)",
|
|
166
|
+
color: expanded ? "hsl(220 30% 5%)" : "hsl(185 100% 65%)",
|
|
167
|
+
boxShadow: expanded
|
|
168
|
+
? "0 0 15px hsl(185 100% 65% / 0.6)"
|
|
169
|
+
: "0 0 8px hsl(220 30% 0% / 0.5)",
|
|
170
|
+
}}
|
|
171
|
+
>
|
|
172
|
+
{expanded ? "−" : "+"}
|
|
173
|
+
</div>
|
|
174
|
+
|
|
175
|
+
{/* ホバーツールチップ */}
|
|
176
|
+
<div
|
|
177
|
+
className={cn(
|
|
178
|
+
"absolute -bottom-14 left-1/2 z-50 -translate-x-1/2",
|
|
179
|
+
"rounded-lg border border-border/50 bg-card/95 px-3 py-2 backdrop-blur-md",
|
|
180
|
+
"opacity-0 shadow-xl transition-all duration-300",
|
|
181
|
+
"group-hover:opacity-100 group-hover:-translate-y-1",
|
|
182
|
+
)}
|
|
183
|
+
style={{
|
|
184
|
+
boxShadow:
|
|
185
|
+
"0 4px 20px hsl(220 30% 0% / 0.5), 0 0 20px hsl(185 100% 65% / 0.2)",
|
|
186
|
+
}}
|
|
187
|
+
>
|
|
188
|
+
<p className="whitespace-nowrap text-xs font-medium text-foreground">
|
|
189
|
+
{clusterSize} branches
|
|
190
|
+
</p>
|
|
191
|
+
<p className="text-[10px] text-muted-foreground">
|
|
192
|
+
Click to {expanded ? "collapse" : "expand"}
|
|
193
|
+
</p>
|
|
194
|
+
</div>
|
|
195
|
+
</div>
|
|
196
|
+
|
|
197
|
+
{/* 出力ハンドル */}
|
|
198
|
+
<Handle
|
|
199
|
+
type="source"
|
|
200
|
+
position={Position.Bottom}
|
|
201
|
+
className="!h-3 !w-3 !rounded-full !border-2 !bg-transparent transition-all duration-300"
|
|
202
|
+
style={{
|
|
203
|
+
borderColor: expanded ? "hsl(185 100% 65%)" : "hsl(200 30% 40%)",
|
|
204
|
+
boxShadow: expanded
|
|
205
|
+
? "0 0 8px hsl(185 100% 65% / 0.4)"
|
|
206
|
+
: "0 0 6px hsl(200 30% 40% / 0.3)",
|
|
207
|
+
}}
|
|
208
|
+
/>
|
|
209
|
+
</>
|
|
210
|
+
);
|
|
211
|
+
});
|