@hienlh/ppm 0.8.76 → 0.8.78
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/CHANGELOG.md +19 -0
- package/dist/web/assets/{browser-tab-D5GfU4Ja.js → browser-tab-DQJLMN11.js} +1 -1
- package/dist/web/assets/{chat-tab-BJeNwwUM.js → chat-tab-xp4ZqCGD.js} +4 -4
- package/dist/web/assets/{code-editor-CTjgdXh2.js → code-editor-De6Xs-Kq.js} +1 -1
- package/dist/web/assets/{database-viewer-QzEuetE6.js → database-viewer-CExMyEmq.js} +1 -1
- package/dist/web/assets/{diff-viewer-CvZ06EAH.js → diff-viewer-C7EECdwr.js} +1 -1
- package/dist/web/assets/git-graph-DqFYj10H.js +1 -0
- package/dist/web/assets/index-CiuAeXR3.js +37 -0
- package/dist/web/assets/keybindings-store-DYgvd7L0.js +1 -0
- package/dist/web/assets/{markdown-renderer-BVxlq4zO.js → markdown-renderer-CnBEa3kk.js} +1 -1
- package/dist/web/assets/{postgres-viewer-DP0FOQOa.js → postgres-viewer-DvMnxJ7g.js} +1 -1
- package/dist/web/assets/{settings-tab-CcmhnYpw.js → settings-tab-C0ltpIWq.js} +1 -1
- package/dist/web/assets/{sqlite-viewer-4a4hHLZk.js → sqlite-viewer-DR9KKOhW.js} +1 -1
- package/dist/web/assets/tab-store-BJw7OCmy.js +1 -0
- package/dist/web/assets/{terminal-tab-CKsBIgnq.js → terminal-tab-B__sTLzq.js} +1 -1
- package/dist/web/assets/{use-monaco-theme-BwIb9BHq.js → use-monaco-theme-BZiSwNRE.js} +1 -1
- package/dist/web/index.html +2 -2
- package/dist/web/sw.js +1 -1
- package/docs/code-standards.md +126 -0
- package/docs/project-changelog.md +36 -1
- package/docs/system-architecture.md +35 -3
- package/package.json +1 -1
- package/src/server/index.ts +5 -1
- package/src/server/routes/project-scoped.ts +2 -0
- package/src/server/routes/workspace.ts +35 -0
- package/src/services/db.service.ts +37 -1
- package/src/services/supervisor.ts +14 -10
- package/src/web/app.tsx +37 -35
- package/src/web/hooks/use-url-sync.ts +173 -21
- package/src/web/stores/panel-store.ts +63 -9
- package/src/web/stores/panel-utils.ts +145 -3
- package/dist/web/assets/git-graph-BQqdvSjX.js +0 -1
- package/dist/web/assets/index-5a-tMkk5.js +0 -37
- package/dist/web/assets/keybindings-store-zY8zbJ2c.js +0 -1
- package/dist/web/assets/tab-store-BCfMgMKM.js +0 -1
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { create } from "zustand";
|
|
2
|
-
import { randomId } from "@/lib/utils";
|
|
3
2
|
import type { Tab, TabType } from "./tab-store";
|
|
4
3
|
import {
|
|
5
4
|
type Panel,
|
|
@@ -13,18 +12,15 @@ import {
|
|
|
13
12
|
MAX_ROWS,
|
|
14
13
|
savePanelLayout,
|
|
15
14
|
loadPanelLayout,
|
|
15
|
+
deriveTabId,
|
|
16
16
|
} from "./panel-utils";
|
|
17
17
|
|
|
18
18
|
/** Tab types that can only have 1 instance per project */
|
|
19
|
-
const SINGLETON_TYPES = new Set<TabType>(["git-graph"]);
|
|
19
|
+
const SINGLETON_TYPES = new Set<TabType>(["git-graph", "settings"]);
|
|
20
20
|
|
|
21
21
|
/** Tab types removed in a prior version — filter them out when loading persisted state */
|
|
22
22
|
const OBSOLETE_TAB_TYPES = new Set(["projects", "git-status"]);
|
|
23
23
|
|
|
24
|
-
function generateTabId(): string {
|
|
25
|
-
return `tab-${randomId()}`;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
24
|
function pushHistory(history: string[], id: string): string[] {
|
|
29
25
|
const filtered = history.filter((h) => h !== id);
|
|
30
26
|
filtered.push(id);
|
|
@@ -47,6 +43,7 @@ export interface PanelStore {
|
|
|
47
43
|
|
|
48
44
|
// Project lifecycle
|
|
49
45
|
switchProject: (projectName: string) => void;
|
|
46
|
+
reloadProject: (projectName: string) => void;
|
|
50
47
|
|
|
51
48
|
// Panel focus
|
|
52
49
|
setFocusedPanel: (panelId: string) => void;
|
|
@@ -188,6 +185,25 @@ export const usePanelStore = create<PanelStore>()((set, get) => {
|
|
|
188
185
|
}
|
|
189
186
|
},
|
|
190
187
|
|
|
188
|
+
reloadProject: (projectName) => {
|
|
189
|
+
const { projectGrids, projectFocused, panels } = get();
|
|
190
|
+
// Clear in-memory cache so switchProject re-reads from localStorage
|
|
191
|
+
const newGrids = { ...projectGrids };
|
|
192
|
+
const newFocused = { ...projectFocused };
|
|
193
|
+
delete newGrids[projectName];
|
|
194
|
+
delete newFocused[projectName];
|
|
195
|
+
|
|
196
|
+
// Remove old panels belonging to this project from flat map
|
|
197
|
+
const oldGrid = projectGrids[projectName];
|
|
198
|
+
const oldPanelIds = oldGrid ? new Set(oldGrid.flat()) : new Set<string>();
|
|
199
|
+
const cleanedPanels = { ...panels };
|
|
200
|
+
for (const id of oldPanelIds) delete cleanedPanels[id];
|
|
201
|
+
|
|
202
|
+
set({ projectGrids: newGrids, projectFocused: newFocused, panels: cleanedPanels, currentProject: null });
|
|
203
|
+
// Re-trigger full load from localStorage
|
|
204
|
+
get().switchProject(projectName);
|
|
205
|
+
},
|
|
206
|
+
|
|
191
207
|
setFocusedPanel: (panelId) => {
|
|
192
208
|
if (get().panels[panelId]) set({ focusedPanelId: panelId });
|
|
193
209
|
},
|
|
@@ -197,10 +213,25 @@ export const usePanelStore = create<PanelStore>()((set, get) => {
|
|
|
197
213
|
const panel = get().panels[pid];
|
|
198
214
|
if (!panel) return "";
|
|
199
215
|
|
|
200
|
-
//
|
|
216
|
+
// Terminal: compute next available index if not provided
|
|
217
|
+
if (tabDef.type === "terminal" && !tabDef.metadata?.terminalIndex) {
|
|
218
|
+
const allTabs = Object.values(get().panels).flatMap((p) => p.tabs);
|
|
219
|
+
const terminalNums = allTabs
|
|
220
|
+
.filter((t) => t.type === "terminal")
|
|
221
|
+
.map((t) => {
|
|
222
|
+
const match = t.id.match(/^terminal:(\d+)/);
|
|
223
|
+
return match ? parseInt(match[1]!, 10) : 0;
|
|
224
|
+
});
|
|
225
|
+
const nextIndex = terminalNums.length > 0 ? Math.max(...terminalNums) + 1 : 1;
|
|
226
|
+
tabDef = { ...tabDef, metadata: { ...tabDef.metadata, terminalIndex: nextIndex } };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const baseId = deriveTabId(tabDef.type, tabDef.metadata);
|
|
230
|
+
|
|
231
|
+
// Singleton check — focus existing across ALL panels
|
|
201
232
|
if (SINGLETON_TYPES.has(tabDef.type)) {
|
|
202
233
|
for (const p of Object.values(get().panels)) {
|
|
203
|
-
const existing = p.tabs.find((t) => t.
|
|
234
|
+
const existing = p.tabs.find((t) => t.id === baseId);
|
|
204
235
|
if (existing) {
|
|
205
236
|
set((s) => ({
|
|
206
237
|
focusedPanelId: p.id,
|
|
@@ -219,7 +250,30 @@ export const usePanelStore = create<PanelStore>()((set, get) => {
|
|
|
219
250
|
}
|
|
220
251
|
}
|
|
221
252
|
|
|
222
|
-
|
|
253
|
+
// Non-singleton: dedup within SAME panel only
|
|
254
|
+
const currentPanel = get().panels[pid]!;
|
|
255
|
+
const existingInPanel = currentPanel.tabs.find((t) => t.id === baseId);
|
|
256
|
+
if (existingInPanel) {
|
|
257
|
+
set((s) => ({
|
|
258
|
+
panels: {
|
|
259
|
+
...s.panels,
|
|
260
|
+
[pid]: {
|
|
261
|
+
...currentPanel,
|
|
262
|
+
activeTabId: existingInPanel.id,
|
|
263
|
+
tabHistory: pushHistory(currentPanel.tabHistory, existingInPanel.id),
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
}));
|
|
267
|
+
persist();
|
|
268
|
+
return existingInPanel.id;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Check if same base ID exists in OTHER panels (split case)
|
|
272
|
+
const existsElsewhere = Object.values(get().panels).some(
|
|
273
|
+
(p) => p.id !== pid && p.tabs.some((t) => t.id === baseId),
|
|
274
|
+
);
|
|
275
|
+
const id = existsElsewhere ? `${baseId}@${pid}` : baseId;
|
|
276
|
+
|
|
223
277
|
const tab: Tab = { ...tabDef, id };
|
|
224
278
|
set((s) => {
|
|
225
279
|
const p = s.panels[pid]!;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomId } from "@/lib/utils";
|
|
2
|
-
import type { Tab } from "./tab-store";
|
|
2
|
+
import type { Tab, TabType } from "./tab-store";
|
|
3
3
|
|
|
4
4
|
// ---------------------------------------------------------------------------
|
|
5
5
|
// Panel types
|
|
@@ -69,6 +69,62 @@ export function findPanelPosition(grid: string[][], panelId: string): { row: num
|
|
|
69
69
|
return null;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// Deterministic tab IDs
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
/** Derive deterministic tab ID from type + metadata */
|
|
77
|
+
export function deriveTabId(type: TabType, metadata?: Record<string, unknown>): string {
|
|
78
|
+
switch (type) {
|
|
79
|
+
case "editor":
|
|
80
|
+
return `editor:${metadata?.filePath ?? "untitled"}`;
|
|
81
|
+
case "chat": {
|
|
82
|
+
const provider = metadata?.providerId ?? "default";
|
|
83
|
+
return `chat:${provider}/${metadata?.sessionId ?? randomId()}`;
|
|
84
|
+
}
|
|
85
|
+
case "terminal":
|
|
86
|
+
return `terminal:${metadata?.terminalIndex ?? 1}`;
|
|
87
|
+
case "database":
|
|
88
|
+
return `database:${metadata?.connectionId ?? "default"}:${metadata?.tableName ?? ""}`;
|
|
89
|
+
case "sqlite":
|
|
90
|
+
return `sqlite:${metadata?.filePath ?? "default"}`;
|
|
91
|
+
case "postgres":
|
|
92
|
+
return `postgres:${metadata?.connectionId ?? "default"}:${metadata?.tableName ?? ""}`;
|
|
93
|
+
case "git-graph":
|
|
94
|
+
return "git-graph";
|
|
95
|
+
case "git-diff":
|
|
96
|
+
return `git-diff:${metadata?.filePath ?? "unknown"}`;
|
|
97
|
+
case "settings":
|
|
98
|
+
return "settings";
|
|
99
|
+
case "browser":
|
|
100
|
+
return `browser:${metadata?.url ?? "blank"}`;
|
|
101
|
+
default:
|
|
102
|
+
return `${type}:${randomId()}`;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Migrate old random tab IDs to deterministic IDs */
|
|
107
|
+
export function migrateTabIds(layout: PanelLayout): PanelLayout {
|
|
108
|
+
const migrated = { ...layout, panels: { ...layout.panels } };
|
|
109
|
+
for (const [panelId, panel] of Object.entries(migrated.panels)) {
|
|
110
|
+
const newTabs = panel.tabs.map((tab) => {
|
|
111
|
+
if (tab.id.startsWith("tab-")) {
|
|
112
|
+
const newId = deriveTabId(tab.type, tab.metadata);
|
|
113
|
+
return { ...tab, id: newId };
|
|
114
|
+
}
|
|
115
|
+
return tab;
|
|
116
|
+
});
|
|
117
|
+
const idMap = new Map<string, string>();
|
|
118
|
+
panel.tabs.forEach((old, i) => {
|
|
119
|
+
if (old.id !== newTabs[i]!.id) idMap.set(old.id, newTabs[i]!.id);
|
|
120
|
+
});
|
|
121
|
+
const newActive = idMap.get(panel.activeTabId ?? "") ?? panel.activeTabId;
|
|
122
|
+
const newHistory = panel.tabHistory.map((h) => idMap.get(h) ?? h);
|
|
123
|
+
migrated.panels[panelId] = { ...panel, tabs: newTabs, activeTabId: newActive, tabHistory: newHistory };
|
|
124
|
+
}
|
|
125
|
+
return migrated;
|
|
126
|
+
}
|
|
127
|
+
|
|
72
128
|
// ---------------------------------------------------------------------------
|
|
73
129
|
// Persistence
|
|
74
130
|
// ---------------------------------------------------------------------------
|
|
@@ -81,20 +137,106 @@ function storageKey(projectName: string): string {
|
|
|
81
137
|
|
|
82
138
|
export function savePanelLayout(projectName: string, layout: PanelLayout): void {
|
|
83
139
|
try {
|
|
84
|
-
|
|
140
|
+
const withTimestamp = { ...layout, updatedAt: new Date().toISOString() };
|
|
141
|
+
localStorage.setItem(storageKey(projectName), JSON.stringify(withTimestamp));
|
|
142
|
+
// Debounced server sync
|
|
143
|
+
syncWorkspaceToServer(projectName, layout);
|
|
85
144
|
} catch { /* ignore */ }
|
|
86
145
|
}
|
|
87
146
|
|
|
88
147
|
export function loadPanelLayout(projectName: string): PanelLayout | null {
|
|
89
148
|
try {
|
|
90
149
|
const raw = localStorage.getItem(storageKey(projectName));
|
|
91
|
-
if (raw)
|
|
150
|
+
if (raw) {
|
|
151
|
+
const layout = JSON.parse(raw) as PanelLayout;
|
|
152
|
+
return migrateTabIds(layout);
|
|
153
|
+
}
|
|
92
154
|
} catch { /* ignore */ }
|
|
93
155
|
|
|
94
156
|
// Migrate from old tab-store format
|
|
95
157
|
return migrateOldTabStore(projectName);
|
|
96
158
|
}
|
|
97
159
|
|
|
160
|
+
// ---------------------------------------------------------------------------
|
|
161
|
+
// Server sync
|
|
162
|
+
// ---------------------------------------------------------------------------
|
|
163
|
+
|
|
164
|
+
export interface PanelLayoutWithTimestamp extends PanelLayout {
|
|
165
|
+
updatedAt: string;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Fetch workspace from server */
|
|
169
|
+
export async function fetchWorkspaceFromServer(
|
|
170
|
+
projectName: string,
|
|
171
|
+
): Promise<PanelLayoutWithTimestamp | null> {
|
|
172
|
+
try {
|
|
173
|
+
const headers: Record<string, string> = {};
|
|
174
|
+
const token = localStorage.getItem("ppm-auth-token");
|
|
175
|
+
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
176
|
+
|
|
177
|
+
const res = await fetch(`/api/project/${encodeURIComponent(projectName)}/workspace`, { headers });
|
|
178
|
+
if (!res.ok) return null;
|
|
179
|
+
const json = await res.json();
|
|
180
|
+
if (!json.ok || !json.data) return null;
|
|
181
|
+
return { ...json.data.layout, updatedAt: json.data.updatedAt };
|
|
182
|
+
} catch {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Save workspace to server (debounced per project) */
|
|
188
|
+
const syncTimers = new Map<string, ReturnType<typeof setTimeout>>();
|
|
189
|
+
|
|
190
|
+
function syncWorkspaceToServer(projectName: string, layout: PanelLayout): void {
|
|
191
|
+
const existing = syncTimers.get(projectName);
|
|
192
|
+
if (existing) clearTimeout(existing);
|
|
193
|
+
|
|
194
|
+
syncTimers.set(projectName, setTimeout(async () => {
|
|
195
|
+
syncTimers.delete(projectName);
|
|
196
|
+
try {
|
|
197
|
+
const headers: Record<string, string> = { "Content-Type": "application/json" };
|
|
198
|
+
const token = localStorage.getItem("ppm-auth-token");
|
|
199
|
+
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
200
|
+
|
|
201
|
+
const res = await fetch(`/api/project/${encodeURIComponent(projectName)}/workspace`, {
|
|
202
|
+
method: "PUT",
|
|
203
|
+
headers,
|
|
204
|
+
body: JSON.stringify({ layout }),
|
|
205
|
+
});
|
|
206
|
+
if (res.ok) {
|
|
207
|
+
const json = await res.json();
|
|
208
|
+
if (json.data?.updatedAt) {
|
|
209
|
+
const key = `${STORAGE_PREFIX}${projectName}`;
|
|
210
|
+
const raw = localStorage.getItem(key);
|
|
211
|
+
if (raw) {
|
|
212
|
+
const local = JSON.parse(raw);
|
|
213
|
+
local.updatedAt = json.data.updatedAt;
|
|
214
|
+
localStorage.setItem(key, JSON.stringify(local));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
} catch { /* silent fail — localStorage is still the cache */ }
|
|
219
|
+
}, 1500));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** Compare local vs server timestamps, return newer layout */
|
|
223
|
+
export function resolveWorkspaceConflict(
|
|
224
|
+
local: PanelLayoutWithTimestamp | null,
|
|
225
|
+
server: PanelLayoutWithTimestamp | null,
|
|
226
|
+
): PanelLayoutWithTimestamp | null {
|
|
227
|
+
if (!local && !server) return null;
|
|
228
|
+
if (!local) return server!;
|
|
229
|
+
if (!server) return local;
|
|
230
|
+
|
|
231
|
+
const localTime = new Date(local.updatedAt).getTime();
|
|
232
|
+
const serverTime = new Date(server.updatedAt).getTime();
|
|
233
|
+
return serverTime >= localTime ? server : local;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ---------------------------------------------------------------------------
|
|
237
|
+
// Old format migration
|
|
238
|
+
// ---------------------------------------------------------------------------
|
|
239
|
+
|
|
98
240
|
function migrateOldTabStore(projectName: string): PanelLayout | null {
|
|
99
241
|
try {
|
|
100
242
|
const raw = localStorage.getItem(`${OLD_STORAGE_PREFIX}${projectName}`);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{o as e}from"./chunk-CFjPhJqf.js";import{t}from"./react-nm2Ru1Pt.js";import"./react-dom-Bpkvzu3U.js";import{n,t as r}from"./jsx-runtime-BRW_vwa9.js";import{a as i,i as a,n as o,r as s,t as c}from"./input-BglMT33g.js";import{o as l,t as u}from"./tab-store-BCfMgMKM.js";import{t as d}from"./tag-CCtdV063.js";import"./dist-lF8CoYII.js";import{t as f}from"./utils-BNytJOb1.js";import{i as p,t as m}from"./api-client-BfBM3I7n.js";import{B as h,C as g,F as _,L as v,Q as y,S as b,T as x,V as S,X as C,_t as w,b as T,ct as E,g as D,h as O,ot as k,v as A,w as j,x as M,y as N,z as P}from"./index-5a-tMkk5.js";var F=n(`cherry`,[[`path`,{d:`M2 17a5 5 0 0 0 10 0c0-2.76-2.5-5-5-3-2.5-2-5 .24-5 3Z`,key:`cvxqlc`}],[`path`,{d:`M12 17a5 5 0 0 0 10 0c0-2.76-2.5-5-5-3-2.5-2-5 .24-5 3Z`,key:`1ostrc`}],[`path`,{d:`M7 14c3.22-2.91 4.29-8.75 5-12 1.66 2.38 4.94 9 5 12`,key:`hqx58h`}],[`path`,{d:`M22 9c-4.29 0-7.14-2.33-10-7 5.71 0 10 4.67 10 7Z`,key:`eykp1o`}]]),ee=n(`git-merge`,[[`circle`,{cx:`18`,cy:`18`,r:`3`,key:`1xkwt0`}],[`circle`,{cx:`6`,cy:`6`,r:`3`,key:`1lh9wr`}],[`path`,{d:`M6 21V9a9 9 0 0 0 9 9`,key:`7kw0sc`}]]),I=e(t(),1),L=[`#0085d9`,`#d73a49`,`#6f42c1`,`#2cbe4e`,`#e36209`,`#005cc5`,`#b31d28`,`#5a32a3`,`#22863a`,`#cb2431`];function te(e){let t=new Map;if(!e)return t;let n=new Set;for(let t of e.branches)if(!t.remote)for(let e of t.remotes)n.add(`remotes/${e}/${t.name}`);for(let r of e.branches)if(r.remote){if(n.has(r.name))continue;let e=t.get(r.commitHash)??[],i=r.name.replace(/^remotes\//,``);e.push({name:i,type:`branch`,remotes:[],current:!1}),t.set(r.commitHash,e)}else{let e=t.get(r.commitHash)??[];e.push({name:r.name,type:`branch`,remotes:r.remotes,current:r.current}),t.set(r.commitHash,e)}for(let n of e.commits)for(let e of n.refs)if(e.startsWith(`tag: `)){let r=e.replace(`tag: `,``),i=t.get(n.hash)??[];i.push({name:r,type:`tag`,remotes:[],current:!1}),t.set(n.hash,i)}return t}function ne(e){let t=new Map;if(!e)return{laneMap:t,maxLane:0,unloadedParentLanes:new Map};let n=0,r=0,i=new Map,a=new Set(e.commits.map(e=>e.hash)),o=[],s=()=>o.length>0?(o.sort((e,t)=>e-t),o.shift()):n++;for(let n of e.commits){let e=i.get(n.hash);e===void 0&&(e=s()),t.set(n.hash,e),e>r&&(r=e),i.delete(n.hash);let a=!1;for(let t=0;t<n.parents.length;t++){let o=n.parents[t];if(!i.has(o))if(t===0)i.set(o,e),a=!0;else{let e=s();i.set(o,e),e>r&&(r=e)}}a||o.push(e)}let c=new Map;for(let[e,t]of i)a.has(e)||c.set(e,t);return{laneMap:t,maxLane:r,unloadedParentLanes:c}}function re(e,t,n,r){if(!e)return[];let i=[],a=new Set(e.commits.map(e=>e.hash));for(let o=0;o<e.commits.length;o++){let s=e.commits[o],c=t.get(s.hash)??0,l=L[c%L.length];for(let u of s.parents){let d=e.commits.findIndex(e=>e.hash===u);if(d>=0){let e=t.get(u)??0,n=L[e%L.length],r=c*16+16/2,a=o*24+24/2,f=e*16+16/2,p=d*24+24/2,m,h=s.parents.indexOf(u)>0;if(r===f)m=`M ${r} ${a} L ${f} ${p}`;else if(h){let e=a+24;m=`M ${r} ${a} C ${r} ${e} ${f} ${a} ${f} ${e} L ${f} ${p}`}else{let e=p-24;m=`M ${r} ${a} L ${r} ${e} C ${r} ${p} ${f} ${e} ${f} ${p}`}let g=s.parents.indexOf(u)===0?l:n;i.push({d:m,color:g})}else if(!a.has(u)){let e=n.get(u)??c,t=L[e%L.length],a=c*16+16/2,s=o*24+24/2,d=e*16+16/2;if(a===d)i.push({d:`M ${a} ${s} L ${a} ${r}`,color:l});else{let e=s+24,n=`M ${a} ${s} C ${a} ${e} ${d} ${s} ${d} ${e} L ${d} ${r}`;i.push({d:n,color:t})}}}}return i}function R(e){let t=new Date(e),n=new Date().getTime()-t.getTime(),r=Math.floor(n/6e4);if(r<1)return`just now`;if(r<60)return`${r}m ago`;let i=Math.floor(r/60);if(i<24)return`${i}h ago`;let a=Math.floor(i/24);if(a<30)return`${a}d ago`;let o=Math.floor(a/30);return o<12?`${o}mo ago`:`${Math.floor(o/12)}y ago`}var z=200;function B(e){let[t,n]=(0,I.useState)(null),[r,i]=(0,I.useState)(!0),[a,o]=(0,I.useState)(!1),[s,c]=(0,I.useState)(!0),[l,d]=(0,I.useState)(null),[f,h]=(0,I.useState)(!1),[g,_]=(0,I.useState)(null),[v,y]=(0,I.useState)([]),[b,x]=(0,I.useState)(!1),[S,C]=(0,I.useState)(`__all__`),[w,T]=(0,I.useState)(``),[E,D]=(0,I.useState)(!1),{openTab:O}=u(),k=(0,I.useRef)(0),A=(0,I.useCallback)(async()=>{if(e)try{i(!0);let t=Math.max(z,k.current),r=await m.get(`${p(e)}/git/graph?max=${t}`);n(r),k.current=r.commits.length,c(r.commits.length>=t),d(null)}catch(e){d(e instanceof Error?e.message:`Failed to fetch graph`)}finally{i(!1)}},[e]),j=(0,I.useCallback)(async()=>{if(!(!e||a||!s))try{o(!0);let t=k.current,r=await m.get(`${p(e)}/git/graph?max=${z}&skip=${t}`);if(r.commits.length===0){c(!1);return}n(e=>{if(!e)return r;let t=new Set(e.commits.map(e=>e.hash)),n=r.commits.filter(e=>!t.has(e.hash)),i=new Set(e.branches.map(e=>e.name)),a=r.branches.filter(e=>!i.has(e.name));return{commits:[...e.commits,...n],branches:[...e.branches,...a],head:e.head}}),k.current=t+r.commits.length,c(r.commits.length>=z)}catch(e){d(e instanceof Error?e.message:`Failed to load more`)}finally{o(!1)}},[e,a,s]);(0,I.useEffect)(()=>{A();let e=setInterval(A,1e4);return()=>clearInterval(e)},[A]);let M=async(t,n)=>{if(e){h(!0);try{await m.post(`${p(e)}${t}`,n),await A()}catch(e){d(e instanceof Error?e.message:`Action failed`)}finally{h(!1)}}},N=()=>M(`/git/fetch`,{}),P=e=>M(`/git/checkout`,{ref:e}),F=e=>M(`/git/cherry-pick`,{hash:e}),ee=e=>M(`/git/revert`,{hash:e}),L=e=>M(`/git/merge`,{source:e}),R=e=>M(`/git/branch/delete`,{name:e}),B=e=>M(`/git/push`,{branch:e}),V=(e,t)=>M(`/git/tag`,{name:e,hash:t}),H=e=>navigator.clipboard.writeText(e),U=async(e,n)=>{if(t?.branches.some(t=>t.name===e||t.name.endsWith(`/${e}`))){if(!window.confirm(`Branch "${e}" already exists.\nDelete and recreate from this commit?`))return;await M(`/git/branch/delete`,{name:e})}await M(`/git/branch/create`,{name:e,from:n})},W=async t=>{if(e)try{let n=await m.get(`${p(e)}/git/pr-url?branch=${encodeURIComponent(t)}`);n.url&&window.open(n.url,`_blank`)}catch{}},G=async t=>{if(g?.hash===t.hash){_(null);return}_(t),x(!0);try{let n=t.parents[0]??``,r=n?`ref1=${encodeURIComponent(n)}&`:``,i=await m.get(`${p(e)}/git/diff-stat?${r}ref2=${encodeURIComponent(t.hash)}`);y(Array.isArray(i)?i:[])}catch{y([])}finally{x(!1)}},K=t=>O({type:`git-diff`,title:`Diff ${t.abbreviatedHash}`,closable:!0,metadata:{projectName:e,ref1:t.parents[0]??void 0,ref2:t.hash},projectId:e??null}),q=(0,I.useMemo)(()=>te(t),[t]),J=t?.branches.find(e=>e.current),Y=t?.head??``,X=(0,I.useMemo)(()=>{if(!t)return[];let e=t.commits;if(S!==`__all__`){let n=t.branches.find(e=>e.name===S);if(n){let r=new Set,i=[n.commitHash];for(;i.length>0;){let e=i.pop();if(r.has(e))continue;r.add(e);let n=t.commits.find(t=>t.hash===e);n&&i.push(...n.parents)}e=e.filter(e=>r.has(e.hash))}}if(w.trim()){let t=w.toLowerCase();e=e.filter(e=>e.subject.toLowerCase().includes(t)||e.authorName.toLowerCase().includes(t)||e.abbreviatedHash.includes(t)||e.hash.includes(t))}return e},[t,S,w]),Z=(0,I.useMemo)(()=>t?{...t,commits:X}:null,[t,X]),Q=(0,I.useMemo)(()=>ne(Z),[Z]),$=X.length*24+48;return{data:t,loading:r,loadingMore:a,hasMore:s,error:l,acting:f,selectedCommit:g,setSelectedCommit:_,commitFiles:v,loadingDetail:b,branchFilter:S,setBranchFilter:C,searchQuery:w,setSearchQuery:T,showSearch:E,setShowSearch:D,fetchGraph:A,fetchFromRemotes:N,loadMore:j,handleCheckout:P,handleCherryPick:F,handleRevert:ee,handleMerge:L,handleDeleteBranch:R,handlePushBranch:B,handleCreateBranch:U,handleCreateTag:V,handleCreatePr:W,copyHash:H,selectCommit:G,openDiffForCommit:K,commitLabels:q,currentBranch:J,headHash:Y,filteredCommits:X,filteredLanes:Q,svgHeight:$,svgPaths:(0,I.useMemo)(()=>re(Z,Q.laneMap,Q.unloadedParentLanes,$),[Z,Q.laneMap,Q.unloadedParentLanes,$])}}function V(e){let[t,n]=(0,I.useState)(e),r=(0,I.useRef)(e);r.current=t;let i=(0,I.useRef)(!1);return{widths:t,startResize:(e,t)=>{i.current=!0;let a=r.current[e]??80,o=r=>{if(!i.current)return;let o=`touches`in r?r.touches[0].clientX:r.clientX,s=Math.max(40,a+o-t);n(t=>({...t,[e]:s}))},s=()=>{i.current=!1,window.removeEventListener(`mousemove`,o),window.removeEventListener(`mouseup`,s),window.removeEventListener(`touchmove`,o),window.removeEventListener(`touchend`,s)};window.addEventListener(`mousemove`,o),window.addEventListener(`mouseup`,s),window.addEventListener(`touchmove`,o,{passive:!1}),window.addEventListener(`touchend`,s)}}}var H=r();function U({branches:e,branchFilter:t,onBranchFilterChange:n,searchQuery:r,onSearchQueryChange:i,showSearch:s,onToggleSearch:l,onFetch:u,onRefresh:d,onOpenSettings:f,loading:p,acting:m,projectName:g}){let v=e.filter(e=>!e.remote),[y,b]=(0,I.useState)(!1),[x,C]=(0,I.useState)(``),w=(0,I.useRef)(null);(0,I.useEffect)(()=>{if(!y)return;let e=e=>{w.current&&!w.current.contains(e.target)&&(b(!1),C(``))};return document.addEventListener(`mousedown`,e),()=>document.removeEventListener(`mousedown`,e)},[y]);let T=x?v.filter(e=>e.name.toLowerCase().includes(x.toLowerCase())):v,E=t===`__all__`?`Show All`:v.find(e=>e.name===t)?.name??`Show All`;return(0,H.jsx)(`div`,{className:`border-b bg-background`,children:(0,H.jsxs)(`div`,{className:`flex items-center gap-1.5 px-2 py-1.5`,children:[(0,H.jsxs)(`div`,{className:`flex items-center gap-1 text-xs text-muted-foreground shrink-0`,children:[(0,H.jsx)(`span`,{className:`font-semibold`,children:`Repo:`}),(0,H.jsx)(`span`,{className:`font-medium text-foreground truncate max-w-[120px]`,children:g??`—`})]}),(0,H.jsx)(`div`,{className:`w-px h-4 bg-border mx-1`}),(0,H.jsxs)(`div`,{className:`relative shrink-0`,ref:w,children:[(0,H.jsxs)(`button`,{type:`button`,className:`flex items-center gap-1 h-6 px-2 text-xs border rounded-md bg-transparent hover:bg-muted/50`,onClick:()=>{b(e=>!e),C(``)},children:[(0,H.jsx)(`span`,{className:`font-semibold text-muted-foreground`,children:`Branches:`}),(0,H.jsx)(`span`,{className:`max-w-[100px] truncate`,children:E}),(0,H.jsx)(a,{className:`size-3 opacity-50`})]}),y&&(0,H.jsxs)(`div`,{className:`absolute top-full left-0 mt-1 z-50 w-[220px] rounded-md border bg-popover shadow-md`,children:[(0,H.jsx)(`div`,{className:`p-1.5`,children:(0,H.jsx)(c,{className:`h-6 text-xs px-2`,placeholder:`Filter branches...`,value:x,onChange:e=>C(e.target.value),autoFocus:!0})}),(0,H.jsxs)(`div`,{className:`max-h-[200px] overflow-y-auto p-1`,children:[(0,H.jsx)(W,{label:`Show All`,selected:t===`__all__`,onClick:()=>{n(`__all__`),b(!1)}}),T.map(e=>(0,H.jsx)(W,{label:e.name,current:e.current,selected:t===e.name,onClick:()=>{n(e.name),b(!1)}},e.name)),T.length===0&&x&&(0,H.jsx)(`div`,{className:`px-2 py-1.5 text-xs text-muted-foreground`,children:`No branches found`})]})]})]}),(0,H.jsx)(`div`,{className:`flex-1`}),s&&(0,H.jsxs)(`div`,{className:`flex items-center gap-1`,children:[(0,H.jsx)(c,{className:`h-6 text-xs w-[160px] px-2`,placeholder:`Search commits...`,value:r,onChange:e=>i(e.target.value),autoFocus:!0}),(0,H.jsx)(o,{variant:`ghost`,size:`icon-xs`,onClick:l,children:(0,H.jsx)(_,{className:`size-3`})})]}),!s&&(0,H.jsx)(o,{variant:`ghost`,size:`icon-xs`,onClick:l,title:`Find`,children:(0,H.jsx)(h,{className:`size-3.5`})}),(0,H.jsx)(o,{variant:`ghost`,size:`icon-xs`,onClick:f,title:`Settings`,children:(0,H.jsx)(P,{className:`size-3.5`})}),(0,H.jsx)(o,{variant:`ghost`,size:`icon-xs`,onClick:u,disabled:m,title:`Fetch`,children:(0,H.jsx)(k,{className:`size-3.5`})}),(0,H.jsx)(o,{variant:`ghost`,size:`icon-xs`,onClick:d,disabled:m,title:`Refresh`,children:(0,H.jsx)(S,{className:`size-3.5 ${p?`animate-spin`:``}`})})]})})}function W({label:e,selected:t,current:n,onClick:r}){return(0,H.jsxs)(`button`,{type:`button`,className:`flex items-center gap-2 w-full px-2 py-1 text-xs rounded-sm hover:bg-accent hover:text-accent-foreground text-left`,onClick:r,children:[(0,H.jsx)(i,{className:`size-3 shrink-0 ${t?`opacity-100`:`opacity-0`}`}),(0,H.jsx)(`span`,{className:`truncate flex-1`,children:e}),n&&(0,H.jsx)(`span`,{className:`text-[10px] text-muted-foreground italic`,children:`current`})]})}function G({commits:e,laneMap:t,svgPaths:n,width:r,height:i,headHash:a}){return(0,H.jsxs)(`svg`,{width:r,height:i,children:[n.map((e,t)=>(0,H.jsx)(`path`,{d:e.d,stroke:e.color,strokeWidth:2,fill:`none`},t)),e.map((e,n)=>{let r=t.get(e.hash)??0,i=r*16+16/2,o=n*24+24/2,s=L[r%L.length],c=e.hash===a;return(0,H.jsx)(`circle`,{cx:i,cy:o,r:c?5:4,fill:s,stroke:c?`#000`:`none`,strokeWidth:c?2:0},e.hash)})]})}function K({name:e,type:t,remotes:n,isCurrent:r,color:i,currentBranch:a,onCheckout:o,onMerge:s,onPush:c,onCreatePr:u,onDelete:f}){return t===`tag`?(0,H.jsxs)(`span`,{className:`inline-flex items-center gap-0.5 px-1.5 py-0.5 rounded text-[10px] font-medium shrink-0 bg-amber-500/20 text-amber-500 border border-amber-500/30`,children:[(0,H.jsx)(d,{className:`size-2.5`}),e]}):(0,H.jsxs)(M,{children:[(0,H.jsx)(x,{asChild:!0,children:(0,H.jsxs)(`span`,{className:`inline-flex items-center rounded text-[10px] font-medium shrink-0 cursor-context-menu overflow-hidden`,style:{border:r?`1.5px solid ${i}`:`1px solid ${i}50`},children:[(0,H.jsxs)(`span`,{className:`inline-flex items-center gap-0.5 px-1.5 py-0.5`,style:{backgroundColor:r?i:`${i}30`,color:r?`#fff`:i},children:[(0,H.jsx)(y,{className:`size-2.5`}),e]}),n.map(e=>(0,H.jsx)(`span`,{className:`px-1.5 py-0.5 italic opacity-70`,style:{borderLeft:`1px solid ${i}40`,color:i,backgroundColor:`${i}15`},children:e},e))]})}),(0,H.jsxs)(b,{children:[(0,H.jsx)(g,{onClick:()=>o(e),children:`Checkout`}),(0,H.jsxs)(g,{onClick:()=>s(e),disabled:e===a?.name,children:[(0,H.jsx)(ee,{className:`size-3`}),`Merge into current`]}),(0,H.jsx)(j,{}),(0,H.jsxs)(g,{onClick:()=>c(e),children:[(0,H.jsx)(w,{className:`size-3`}),`Push`]}),(0,H.jsxs)(g,{onClick:()=>u(e),children:[(0,H.jsx)(l,{className:`size-3`}),`Create PR`]}),(0,H.jsx)(j,{}),(0,H.jsxs)(g,{variant:`destructive`,onClick:()=>f(e),disabled:e===a?.name,children:[(0,H.jsx)(v,{className:`size-3`}),`Delete`]})]})]})}function q({commit:e,lane:t,isSelected:n,isHead:r,labels:i,currentBranch:a,onSelect:o,onCheckout:c,onCherryPick:l,onRevert:u,onMerge:f,onDeleteBranch:p,onPushBranch:m,onCreatePr:h,onOpenCreateBranch:_,onOpenCreateTag:v,onOpenDiff:S,onCopyHash:C}){let w=L[t%L.length],T=i.filter(e=>e.type===`branch`),D=i.filter(e=>e.type===`tag`);return(0,H.jsxs)(M,{children:[(0,H.jsx)(x,{asChild:!0,children:(0,H.jsxs)(`tr`,{className:`hover:bg-muted/50 cursor-pointer border-b border-border/20 ${n?`bg-primary/10`:``} ${r?`font-medium`:``}`,style:{height:`24px`},onClick:o,children:[(0,H.jsx)(`td`,{className:`px-2 truncate max-w-0`,children:(0,H.jsxs)(`div`,{className:`flex items-center gap-1.5 min-w-0`,children:[T.map(e=>(0,H.jsx)(K,{name:e.name,type:`branch`,remotes:e.remotes,isCurrent:e.current,color:w,currentBranch:a,onCheckout:c,onMerge:f,onPush:m,onCreatePr:h,onDelete:p},`branch-${e.name}`)),D.map(e=>(0,H.jsx)(K,{name:e.name,type:`tag`,remotes:[],isCurrent:!1,color:w,currentBranch:a,onCheckout:c,onMerge:f,onPush:m,onCreatePr:h,onDelete:p},`tag-${e.name}`)),(0,H.jsx)(`span`,{className:`truncate text-xs`,children:e.subject})]})}),(0,H.jsx)(`td`,{className:`px-2 text-xs text-muted-foreground whitespace-nowrap shrink-0`,children:R(e.authorDate)}),(0,H.jsx)(`td`,{className:`px-2 text-xs text-muted-foreground truncate max-w-[120px]`,children:e.authorName}),(0,H.jsx)(`td`,{className:`px-2 text-xs text-muted-foreground font-mono whitespace-nowrap`,children:e.abbreviatedHash})]})}),(0,H.jsxs)(b,{children:[(0,H.jsx)(g,{onClick:()=>c(e.hash),children:`Checkout`}),(0,H.jsxs)(g,{onClick:()=>_(e.hash),children:[(0,H.jsx)(y,{className:`size-3`}),`Create Branch...`]}),(0,H.jsx)(j,{}),(0,H.jsxs)(g,{onClick:()=>l(e.hash),children:[(0,H.jsx)(F,{className:`size-3`}),`Cherry Pick`]}),(0,H.jsxs)(g,{onClick:()=>u(e.hash),children:[(0,H.jsx)(s,{className:`size-3`}),`Revert`]}),(0,H.jsxs)(g,{onClick:()=>v(e.hash),children:[(0,H.jsx)(d,{className:`size-3`}),`Create Tag...`]}),(0,H.jsx)(j,{}),(0,H.jsx)(g,{onClick:S,children:`View Diff`}),(0,H.jsxs)(g,{onClick:C,children:[(0,H.jsx)(E,{className:`size-3`}),`Copy Hash`]})]})]})}function J({commit:e,files:t,loadingDetail:n,projectName:r,onClose:i,copyHash:a}){let{openTab:s}=u();return(0,H.jsxs)(`div`,{className:`border-t bg-muted/30 max-h-[40%] overflow-auto`,children:[(0,H.jsxs)(`div`,{className:`px-3 py-2 border-b flex items-center justify-between`,children:[(0,H.jsxs)(`span`,{className:`text-sm font-medium truncate`,children:[e.abbreviatedHash,` — `,e.subject]}),(0,H.jsx)(o,{variant:`ghost`,size:`icon-xs`,onClick:i,children:`✕`})]}),(0,H.jsxs)(`div`,{className:`px-3 py-2 text-xs space-y-1`,children:[(0,H.jsxs)(`div`,{className:`flex gap-4`,children:[(0,H.jsx)(`span`,{className:`text-muted-foreground w-12 shrink-0`,children:`Author`}),(0,H.jsxs)(`span`,{children:[e.authorName,` <`,e.authorEmail,`>`]})]}),(0,H.jsxs)(`div`,{className:`flex gap-4`,children:[(0,H.jsx)(`span`,{className:`text-muted-foreground w-12 shrink-0`,children:`Date`}),(0,H.jsx)(`span`,{children:new Date(e.authorDate).toLocaleString()})]}),(0,H.jsxs)(`div`,{className:`flex gap-4`,children:[(0,H.jsx)(`span`,{className:`text-muted-foreground w-12 shrink-0`,children:`Hash`}),(0,H.jsx)(`span`,{className:`font-mono cursor-pointer hover:text-primary`,onClick:()=>a(e.hash),children:e.hash})]}),e.parents.length>0&&(0,H.jsxs)(`div`,{className:`flex gap-4`,children:[(0,H.jsx)(`span`,{className:`text-muted-foreground w-12 shrink-0`,children:`Parents`}),(0,H.jsx)(`span`,{className:`font-mono`,children:e.parents.map(e=>e.slice(0,7)).join(`, `)})]}),e.body&&(0,H.jsx)(`div`,{className:`mt-2 p-2 bg-background rounded text-xs whitespace-pre-wrap`,children:e.body})]}),(0,H.jsxs)(`div`,{className:`px-3 py-1 border-t`,children:[(0,H.jsx)(`div`,{className:`text-xs text-muted-foreground py-1`,children:n?`Loading files...`:`${t.length} file${t.length===1?``:`s`} changed`}),t.map(t=>(0,H.jsxs)(`div`,{className:`flex items-center gap-2 py-0.5 text-xs hover:bg-muted/50 rounded px-1 cursor-pointer`,onClick:()=>s({type:`git-diff`,title:`Diff ${f(t.path)}`,closable:!0,metadata:{projectName:r,ref1:e.parents[0]??void 0,ref2:e.hash,filePath:t.path},projectId:r}),children:[(0,H.jsx)(`span`,{className:`flex-1 truncate font-mono`,children:t.path}),t.additions>0&&(0,H.jsxs)(`span`,{className:`text-green-500`,children:[`+`,t.additions]}),t.deletions>0&&(0,H.jsxs)(`span`,{className:`text-red-500`,children:[`-`,t.deletions]})]},t.path))]})]})}function Y({type:e,hash:t,onClose:n,onCreateBranch:r,onCreateTag:i}){let[a,s]=(0,I.useState)(``),l=()=>{a.trim()&&(e===`branch`?r(a.trim(),t):i(a.trim(),t),n())};return(0,H.jsx)(O,{open:e!==null,onOpenChange:e=>{e||n()},children:(0,H.jsxs)(D,{children:[(0,H.jsx)(N,{children:(0,H.jsx)(T,{children:e===`branch`?`Create Branch`:`Create Tag`})}),(0,H.jsx)(c,{placeholder:e===`branch`?`Branch name`:`Tag name`,value:a,onChange:e=>s(e.target.value),onKeyDown:e=>{e.key===`Enter`&&l()},autoFocus:!0}),(0,H.jsxs)(A,{children:[(0,H.jsx)(o,{variant:`outline`,onClick:n,children:`Cancel`}),(0,H.jsx)(o,{disabled:!a.trim(),onClick:l,children:`Create`})]})]})})}function X({open:e,onClose:t,projectName:n,branches:r}){let i=r.filter(e=>!e.remote),a=r.filter(e=>e.remote),o=new Map;for(let e of a){let t=e.name.replace(/^remotes\//,``),n=t.indexOf(`/`);if(n<0)continue;let r=t.slice(0,n),i=t.slice(n+1),a=o.get(r)??[];a.push(i),o.set(r,a)}return(0,H.jsx)(O,{open:e,onOpenChange:e=>{e||t()},children:(0,H.jsxs)(D,{className:`max-w-md`,children:[(0,H.jsx)(N,{children:(0,H.jsx)(T,{children:`Repository Settings`})}),(0,H.jsxs)(`div`,{className:`space-y-4 text-sm`,children:[(0,H.jsxs)(Z,{title:`General`,children:[(0,H.jsx)(Q,{label:`Name`,value:n}),(0,H.jsx)(Q,{label:`Branches`,value:`${i.length} local, ${a.length} remote`})]}),(0,H.jsx)(Z,{title:`Local Branches`,children:i.map(e=>(0,H.jsxs)(`div`,{className:`flex items-center gap-2 py-0.5`,children:[(0,H.jsx)(`span`,{className:`text-xs ${e.current?`font-semibold text-primary`:`text-foreground`}`,children:e.name}),e.current&&(0,H.jsx)(`span`,{className:`text-[10px] text-muted-foreground italic`,children:`HEAD`}),e.remotes.length>0&&(0,H.jsxs)(`span`,{className:`text-[10px] text-muted-foreground`,children:[`(`,e.remotes.join(`, `),`)`]})]},e.name))}),(0,H.jsxs)(Z,{title:`Remotes`,children:[[...o.entries()].map(([e,t])=>(0,H.jsxs)(`div`,{className:`py-0.5`,children:[(0,H.jsx)(`span`,{className:`text-xs font-medium`,children:e}),(0,H.jsxs)(`span`,{className:`text-[10px] text-muted-foreground ml-2`,children:[t.length,` branch`,t.length===1?``:`es`]})]},e)),o.size===0&&(0,H.jsx)(`span`,{className:`text-xs text-muted-foreground`,children:`No remotes configured`})]})]})]})})}function Z({title:e,children:t}){return(0,H.jsxs)(`div`,{children:[(0,H.jsx)(`h4`,{className:`text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-1.5`,children:e}),(0,H.jsx)(`div`,{className:`pl-1`,children:t})]})}function Q({label:e,value:t}){return(0,H.jsxs)(`div`,{className:`flex items-center gap-3 py-0.5`,children:[(0,H.jsx)(`span`,{className:`text-xs text-muted-foreground w-16 shrink-0`,children:e}),(0,H.jsx)(`span`,{className:`text-xs`,children:t})]})}function $({metadata:e}){let t=e?.projectName,n=B(t),[r,i]=(0,I.useState)({type:null}),[a,o]=(0,I.useState)(!1),s=(typeof window<`u`&&window.innerWidth<768?6:10)*16+16,[c,l]=(0,I.useState)(s),u=(0,I.useRef)(s);u.current=c;let d=(0,I.useRef)(!1),f=e=>{d.current=!0;let t=u.current,n=n=>{if(!d.current)return;let r=`touches`in n?n.touches[0].clientX:n.clientX;l(Math.max(40,t+r-e))},r=()=>{d.current=!1,window.removeEventListener(`mousemove`,n),window.removeEventListener(`mouseup`,r),window.removeEventListener(`touchmove`,n),window.removeEventListener(`touchend`,r)};window.addEventListener(`mousemove`,n),window.addEventListener(`mouseup`,r),window.addEventListener(`touchmove`,n,{passive:!1}),window.addEventListener(`touchend`,r)},{widths:p,startResize:m}=V({date:80,author:120,commit:70}),h=(0,I.useRef)(n.loadMore);h.current=n.loadMore;let g=(0,I.useCallback)(e=>{let t=e.currentTarget;t.scrollHeight-t.scrollTop-t.clientHeight<200&&h.current()},[]);return t?n.loading&&!n.data?(0,H.jsx)(oe,{}):n.error&&!n.data?(0,H.jsx)(se,{error:n.error,onRetry:n.fetchGraph}):(0,H.jsxs)(`div`,{className:`flex flex-col h-full`,children:[(0,H.jsx)(U,{branches:n.data?.branches??[],branchFilter:n.branchFilter,onBranchFilterChange:n.setBranchFilter,searchQuery:n.searchQuery,onSearchQueryChange:n.setSearchQuery,showSearch:n.showSearch,onToggleSearch:()=>n.setShowSearch(!n.showSearch),onFetch:n.fetchFromRemotes,onRefresh:n.fetchGraph,onOpenSettings:()=>o(!0),loading:n.loading,acting:n.acting,projectName:t}),n.error&&(0,H.jsx)(`div`,{className:`px-3 py-1.5 text-xs text-destructive bg-destructive/10`,children:n.error}),(0,H.jsx)(`div`,{className:`flex-1 overflow-auto`,onScroll:g,children:(0,H.jsxs)(`div`,{className:`flex min-w-max md:min-w-0`,children:[(0,H.jsxs)(`div`,{className:`sticky left-0 z-10 shrink-0 bg-background relative overflow-hidden`,style:{width:`${c}px`},children:[(0,H.jsx)(`div`,{className:`text-[11px] font-semibold text-muted-foreground px-2 border-b bg-background sticky top-0 z-20`,style:{height:`24px`,lineHeight:`24px`},children:`Graph`}),(0,H.jsx)(G,{commits:n.filteredCommits,laneMap:n.filteredLanes.laneMap,svgPaths:n.svgPaths,width:(n.filteredLanes.maxLane+2)*16,height:n.svgHeight,headHash:n.headHash}),(0,H.jsx)(`div`,{className:`absolute top-0 right-0 w-1.5 h-full cursor-col-resize hover:bg-primary/30`,onMouseDown:e=>{e.preventDefault(),f(e.clientX)},onTouchStart:e=>f(e.touches[0].clientX)})]}),(0,H.jsxs)(`div`,{className:`flex-1 min-w-[400px]`,children:[(0,H.jsxs)(`table`,{className:`w-full border-collapse text-xs`,style:{tableLayout:`fixed`},children:[(0,H.jsxs)(`colgroup`,{children:[(0,H.jsx)(`col`,{}),(0,H.jsx)(`col`,{style:{width:`${p.date}px`}}),(0,H.jsx)(`col`,{style:{width:`${p.author}px`}}),(0,H.jsx)(`col`,{style:{width:`${p.commit}px`}})]}),(0,H.jsx)(`thead`,{className:`sticky top-0 z-10 bg-background`,children:(0,H.jsxs)(`tr`,{className:`border-b text-[11px] font-semibold text-muted-foreground`,style:{height:`24px`},children:[(0,H.jsx)(`th`,{className:`text-left px-2 font-semibold`,children:`Description`}),(0,H.jsx)(ie,{label:`Date`,colKey:`date`,onStartResize:m}),(0,H.jsx)(ie,{label:`Author`,colKey:`author`,onStartResize:m}),(0,H.jsx)(`th`,{className:`text-left px-2 font-semibold`,children:`Commit`})]})}),(0,H.jsx)(`tbody`,{children:n.filteredCommits.map(e=>(0,H.jsx)(q,{commit:e,lane:n.filteredLanes.laneMap.get(e.hash)??0,isSelected:n.selectedCommit?.hash===e.hash,isHead:e.hash===n.headHash,labels:n.commitLabels.get(e.hash)??[],currentBranch:n.currentBranch,onSelect:()=>n.selectCommit(e),onCheckout:n.handleCheckout,onCherryPick:n.handleCherryPick,onRevert:n.handleRevert,onMerge:n.handleMerge,onDeleteBranch:n.handleDeleteBranch,onPushBranch:n.handlePushBranch,onCreatePr:n.handleCreatePr,onOpenCreateBranch:e=>i({type:`branch`,hash:e}),onOpenCreateTag:e=>i({type:`tag`,hash:e}),onOpenDiff:()=>n.openDiffForCommit(e),onCopyHash:()=>n.copyHash(e.hash)},e.hash))})]}),n.loadingMore&&(0,H.jsxs)(`div`,{className:`flex items-center justify-center gap-2 py-3 text-xs text-muted-foreground`,children:[(0,H.jsx)(C,{className:`size-3.5 animate-spin`}),` Loading more commits...`]}),!n.hasMore&&n.data&&n.data.commits.length>0&&(0,H.jsxs)(`div`,{className:`text-center py-2 text-xs text-muted-foreground`,children:[n.data.commits.length,` commits loaded`]})]})]})}),n.selectedCommit&&t&&(0,H.jsx)(J,{commit:n.selectedCommit,files:n.commitFiles,loadingDetail:n.loadingDetail,projectName:t,onClose:()=>n.setSelectedCommit(null),copyHash:n.copyHash}),(0,H.jsx)(Y,{type:r.type,hash:r.hash,onClose:()=>i({type:null}),onCreateBranch:n.handleCreateBranch,onCreateTag:n.handleCreateTag}),(0,H.jsx)(X,{open:a,onClose:()=>o(!1),projectName:t,branches:n.data?.branches??[]})]}):(0,H.jsx)(ae,{msg:`No project selected.`})}function ie({label:e,colKey:t,onStartResize:n}){return(0,H.jsxs)(`th`,{className:`text-left px-2 font-semibold relative`,children:[e,(0,H.jsx)(`div`,{className:`absolute top-0 right-0 w-1.5 h-full cursor-col-resize hover:bg-primary/30`,onMouseDown:e=>{e.preventDefault(),n(t,e.clientX)},onTouchStart:e=>n(t,e.touches[0].clientX)})]})}function ae({msg:e}){return(0,H.jsx)(`div`,{className:`flex items-center justify-center h-full text-muted-foreground text-sm`,children:e})}function oe(){return(0,H.jsxs)(`div`,{className:`flex items-center justify-center h-full gap-2 text-muted-foreground`,children:[(0,H.jsx)(C,{className:`size-5 animate-spin`}),(0,H.jsx)(`span`,{className:`text-sm`,children:`Loading git graph...`})]})}function se({error:e,onRetry:t}){return(0,H.jsxs)(`div`,{className:`flex flex-col items-center justify-center h-full gap-2 text-destructive text-sm`,children:[(0,H.jsx)(`p`,{children:e}),(0,H.jsx)(o,{variant:`outline`,size:`sm`,onClick:t,children:`Retry`})]})}export{$ as GitGraph};
|