git-history-ui 1.0.1 → 1.0.3
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/README.md +45 -244
- package/dist/backend/server.d.ts.map +1 -1
- package/dist/backend/server.js +30 -10
- package/dist/backend/server.js.map +1 -1
- package/dist/config/paths.d.ts +14 -0
- package/dist/config/paths.d.ts.map +1 -0
- package/dist/config/paths.js +35 -0
- package/dist/config/paths.js.map +1 -0
- package/package.json +8 -1
- package/public/index.html +0 -1
- package/.eslintrc.js +0 -21
- package/demo.js +0 -45
- package/frontend/.editorconfig +0 -17
- package/frontend/.vscode/extensions.json +0 -4
- package/frontend/.vscode/launch.json +0 -20
- package/frontend/.vscode/tasks.json +0 -42
- package/frontend/README.md +0 -59
- package/frontend/angular.json +0 -99
- package/frontend/package-lock.json +0 -10566
- package/frontend/package.json +0 -55
- package/frontend/proxy.conf.json +0 -7
- package/frontend/public/favicon.ico +0 -0
- package/frontend/src/app/app.component.ts +0 -598
- package/frontend/src/app/app.config.ts +0 -12
- package/frontend/src/app/app.css +0 -0
- package/frontend/src/app/app.html +0 -342
- package/frontend/src/app/app.routes.ts +0 -3
- package/frontend/src/app/app.spec.ts +0 -23
- package/frontend/src/app/app.ts +0 -12
- package/frontend/src/app/components/color-palette-selector/color-palette-selector.component.ts +0 -137
- package/frontend/src/app/components/commit-detail/commit-detail.component.ts +0 -327
- package/frontend/src/app/components/commit-graph/commit-graph.component.ts +0 -294
- package/frontend/src/app/components/commit-list/commit-list.component.ts +0 -199
- package/frontend/src/app/components/diff-viewer/diff-viewer.component.ts +0 -311
- package/frontend/src/app/models/color-palette.models.ts +0 -229
- package/frontend/src/app/models/git.models.ts +0 -39
- package/frontend/src/app/services/git.service.ts +0 -43
- package/frontend/src/index.html +0 -13
- package/frontend/src/main.ts +0 -6
- package/frontend/src/styles.css +0 -397
- package/frontend/tsconfig.app.json +0 -15
- package/frontend/tsconfig.json +0 -34
- package/frontend/tsconfig.spec.json +0 -14
- package/jest.config.js +0 -26
- package/src/__tests__/gitService.test.ts +0 -533
- package/src/__tests__/setup.ts +0 -25
- package/src/backend/dev-server.ts +0 -14
- package/src/backend/gitService.ts +0 -277
- package/src/backend/server.ts +0 -132
- package/src/cli.ts +0 -56
- package/tsconfig.json +0 -25
|
@@ -1,327 +0,0 @@
|
|
|
1
|
-
import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
|
|
2
|
-
import { CommonModule } from '@angular/common';
|
|
3
|
-
import { Commit, DiffFile } from '../../models/git.models';
|
|
4
|
-
|
|
5
|
-
@Component({
|
|
6
|
-
selector: 'app-commit-detail',
|
|
7
|
-
standalone: true,
|
|
8
|
-
imports: [CommonModule],
|
|
9
|
-
template: `
|
|
10
|
-
<div class="modal-overlay" (click)="onClose()">
|
|
11
|
-
<div class="modal-container" [class.dark]="isDarkMode" (click)="$event.stopPropagation()">
|
|
12
|
-
<div class="modal-header">
|
|
13
|
-
<h3 class="modal-title">Commit Details</h3>
|
|
14
|
-
<button (click)="onClose()" class="close-button">
|
|
15
|
-
<svg class="close-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
16
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
|
17
|
-
</svg>
|
|
18
|
-
</button>
|
|
19
|
-
</div>
|
|
20
|
-
|
|
21
|
-
<div class="modal-content" *ngIf="commit">
|
|
22
|
-
<div class="commit-details">
|
|
23
|
-
<div class="commit-header">
|
|
24
|
-
<div class="commit-meta">
|
|
25
|
-
<span class="commit-hash">{{ commit.hash }}</span>
|
|
26
|
-
<span class="commit-author">by {{ commit.author }}</span>
|
|
27
|
-
<span class="commit-date">{{ formatDate(commit.date) }}</span>
|
|
28
|
-
</div>
|
|
29
|
-
<h3 class="commit-message">{{ commit.message }}</h3>
|
|
30
|
-
</div>
|
|
31
|
-
|
|
32
|
-
<div class="commit-sections">
|
|
33
|
-
<div class="section">
|
|
34
|
-
<h4 class="section-title">Files Changed</h4>
|
|
35
|
-
<div class="file-list">
|
|
36
|
-
<div *ngFor="let diffFile of diffFiles" class="file-item">
|
|
37
|
-
<span class="file-name">{{ diffFile.file }}</span>
|
|
38
|
-
<button class="btn btn-primary" (click)="onFileClick(diffFile.file)">
|
|
39
|
-
View Diff
|
|
40
|
-
</button>
|
|
41
|
-
</div>
|
|
42
|
-
</div>
|
|
43
|
-
</div>
|
|
44
|
-
|
|
45
|
-
<div class="section" *ngIf="diffFiles.length > 0">
|
|
46
|
-
<h4 class="section-title">Diff Summary</h4>
|
|
47
|
-
<div class="diff-summary">
|
|
48
|
-
<div *ngFor="let file of diffFiles" class="diff-item">
|
|
49
|
-
<div class="diff-file-name">{{ file.file }}</div>
|
|
50
|
-
<div class="diff-stats">
|
|
51
|
-
+{{ file.additions }} -{{ file.deletions }}
|
|
52
|
-
</div>
|
|
53
|
-
</div>
|
|
54
|
-
</div>
|
|
55
|
-
</div>
|
|
56
|
-
</div>
|
|
57
|
-
</div>
|
|
58
|
-
</div>
|
|
59
|
-
</div>
|
|
60
|
-
</div>
|
|
61
|
-
`,
|
|
62
|
-
styles: [`
|
|
63
|
-
.modal-overlay {
|
|
64
|
-
position: fixed;
|
|
65
|
-
inset: 0;
|
|
66
|
-
background-color: rgba(0, 0, 0, 0.5);
|
|
67
|
-
z-index: 50;
|
|
68
|
-
display: flex;
|
|
69
|
-
align-items: center;
|
|
70
|
-
justify-content: center;
|
|
71
|
-
min-height: 100vh;
|
|
72
|
-
padding: 1rem;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
.modal-container {
|
|
76
|
-
background-color: white;
|
|
77
|
-
border-radius: 0.5rem;
|
|
78
|
-
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
|
|
79
|
-
max-width: 64rem;
|
|
80
|
-
width: 100%;
|
|
81
|
-
max-height: 100vh;
|
|
82
|
-
overflow-y: auto;
|
|
83
|
-
transition: background-color 0.2s ease, color 0.2s ease;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
.modal-header {
|
|
87
|
-
display: flex;
|
|
88
|
-
justify-content: space-between;
|
|
89
|
-
align-items: center;
|
|
90
|
-
padding: 1.5rem;
|
|
91
|
-
border-bottom: 1px solid #e5e7eb;
|
|
92
|
-
transition: border-bottom-color 0.2s ease;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
.modal-title {
|
|
96
|
-
font-size: 1.125rem;
|
|
97
|
-
font-weight: 600;
|
|
98
|
-
color: #111827;
|
|
99
|
-
transition: color 0.2s ease;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/* Force dark mode styles with higher specificity */
|
|
103
|
-
.modal-container.dark .modal-title,
|
|
104
|
-
.dark .modal-container .modal-title,
|
|
105
|
-
.dark .modal-title,
|
|
106
|
-
.dark .modal-header .modal-title,
|
|
107
|
-
.modal-container.dark .modal-header .modal-title {
|
|
108
|
-
color: #e0e0e0 !important;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
.close-button {
|
|
112
|
-
color: #6b7280;
|
|
113
|
-
background: none;
|
|
114
|
-
border: none;
|
|
115
|
-
cursor: pointer;
|
|
116
|
-
padding: 0.5rem;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
.close-button:hover {
|
|
120
|
-
color: #374151;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
.close-icon {
|
|
124
|
-
width: 1.5rem;
|
|
125
|
-
height: 1.5rem;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
.modal-content {
|
|
129
|
-
padding: 1.5rem;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
.commit-header {
|
|
133
|
-
border-bottom: 1px solid #e5e7eb;
|
|
134
|
-
padding-bottom: 1rem;
|
|
135
|
-
margin-bottom: 1rem;
|
|
136
|
-
transition: border-bottom-color 0.2s ease;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
.commit-meta {
|
|
140
|
-
display: flex;
|
|
141
|
-
align-items: center;
|
|
142
|
-
gap: 0.5rem;
|
|
143
|
-
margin-bottom: 0.5rem;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
.commit-hash {
|
|
147
|
-
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
148
|
-
font-size: 0.875rem;
|
|
149
|
-
color: #6b7280;
|
|
150
|
-
transition: color 0.2s ease;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
.commit-author {
|
|
154
|
-
font-size: 0.875rem;
|
|
155
|
-
color: #374151;
|
|
156
|
-
transition: color 0.2s ease;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
.commit-date {
|
|
160
|
-
font-size: 0.875rem;
|
|
161
|
-
color: #6b7280;
|
|
162
|
-
transition: color 0.2s ease;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
.commit-message {
|
|
166
|
-
font-size: 1.25rem;
|
|
167
|
-
font-weight: 600;
|
|
168
|
-
color: #111827;
|
|
169
|
-
transition: color 0.2s ease;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
.commit-sections {
|
|
173
|
-
display: grid;
|
|
174
|
-
grid-template-columns: 1fr 1fr;
|
|
175
|
-
gap: 1.5rem;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
.section-title {
|
|
179
|
-
font-size: 1.125rem;
|
|
180
|
-
font-weight: 500;
|
|
181
|
-
color: #111827;
|
|
182
|
-
margin-bottom: 0.75rem;
|
|
183
|
-
transition: color 0.2s ease;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
.file-list, .diff-summary {
|
|
187
|
-
display: flex;
|
|
188
|
-
flex-direction: column;
|
|
189
|
-
gap: 0.5rem;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
.file-item, .diff-item {
|
|
193
|
-
display: flex;
|
|
194
|
-
justify-content: space-between;
|
|
195
|
-
align-items: center;
|
|
196
|
-
padding: 0.5rem;
|
|
197
|
-
background-color: #f9fafb;
|
|
198
|
-
border-radius: 0.25rem;
|
|
199
|
-
transition: background-color 0.2s ease;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
.file-name, .diff-file-name {
|
|
203
|
-
font-size: 0.875rem;
|
|
204
|
-
color: #374151;
|
|
205
|
-
transition: color 0.2s ease;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
.diff-stats {
|
|
209
|
-
font-size: 0.75rem;
|
|
210
|
-
color: #6b7280;
|
|
211
|
-
transition: color 0.2s ease;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/* Dark mode styles */
|
|
215
|
-
.dark .modal-container {
|
|
216
|
-
background-color: #2d2d2d !important;
|
|
217
|
-
color: #e0e0e0 !important;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
.dark .modal-header {
|
|
221
|
-
border-color: #404040 !important;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
.dark .modal-title {
|
|
225
|
-
color: #e0e0e0 !important;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
.dark .close-button {
|
|
229
|
-
color: #9ca3af !important;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
.dark .close-button:hover {
|
|
233
|
-
color: #d1d5db !important;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
.dark .commit-header {
|
|
237
|
-
border-color: #404040 !important;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
.dark .commit-hash {
|
|
241
|
-
color: #9ca3af !important;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
.dark .commit-author {
|
|
245
|
-
color: #d1d5db !important;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
.dark .commit-date {
|
|
249
|
-
color: #9ca3af !important;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
.dark .commit-message {
|
|
253
|
-
color: #e0e0e0 !important;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
.dark .section-title {
|
|
257
|
-
color: #e0e0e0 !important;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
.dark .file-item,
|
|
261
|
-
.dark .diff-item {
|
|
262
|
-
background-color: #404040 !important;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
.dark .file-name,
|
|
266
|
-
.dark .diff-file-name {
|
|
267
|
-
color: #d1d5db !important;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
.dark .diff-stats {
|
|
271
|
-
color: #9ca3af !important;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
@media (max-width: 768px) {
|
|
275
|
-
.commit-sections {
|
|
276
|
-
grid-template-columns: 1fr;
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
`]
|
|
280
|
-
})
|
|
281
|
-
export class CommitDetailComponent implements OnInit {
|
|
282
|
-
@Input() commit: Commit | null = null;
|
|
283
|
-
@Input() diffFiles: DiffFile[] = [];
|
|
284
|
-
@Output() close = new EventEmitter<void>();
|
|
285
|
-
@Output() fileClick = new EventEmitter<string>();
|
|
286
|
-
|
|
287
|
-
isDarkMode = false;
|
|
288
|
-
|
|
289
|
-
ngOnInit() {
|
|
290
|
-
this.checkDarkMode();
|
|
291
|
-
this.setupDarkModeObserver();
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
private checkDarkMode() {
|
|
295
|
-
this.isDarkMode = document.documentElement.classList.contains('dark');
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
private setupDarkModeObserver() {
|
|
299
|
-
// Observe changes to the document element's class list
|
|
300
|
-
const observer = new MutationObserver((mutations) => {
|
|
301
|
-
mutations.forEach((mutation) => {
|
|
302
|
-
if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
|
|
303
|
-
this.checkDarkMode();
|
|
304
|
-
}
|
|
305
|
-
});
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
// Start observing the document element for class changes
|
|
309
|
-
observer.observe(document.documentElement, {
|
|
310
|
-
attributes: true,
|
|
311
|
-
attributeFilter: ['class']
|
|
312
|
-
});
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
onClose() {
|
|
316
|
-
this.close.emit();
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
onFileClick(file: string) {
|
|
320
|
-
this.fileClick.emit(file);
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
formatDate(dateString: string): string {
|
|
324
|
-
const date = new Date(dateString);
|
|
325
|
-
return date.toLocaleDateString() + ' ' + date.toLocaleTimeString();
|
|
326
|
-
}
|
|
327
|
-
}
|
|
@@ -1,294 +0,0 @@
|
|
|
1
|
-
import { Component, Input, Output, EventEmitter, ElementRef, ViewChild, AfterViewInit, OnChanges, SimpleChanges, OnDestroy } from '@angular/core';
|
|
2
|
-
import { CommonModule } from '@angular/common';
|
|
3
|
-
import * as d3 from 'd3';
|
|
4
|
-
import { Commit, CommitNode } from '../../models/git.models';
|
|
5
|
-
import { ColorPalette } from '../../models/color-palette.models';
|
|
6
|
-
|
|
7
|
-
@Component({
|
|
8
|
-
selector: 'app-commit-graph',
|
|
9
|
-
standalone: true,
|
|
10
|
-
imports: [CommonModule],
|
|
11
|
-
template: `
|
|
12
|
-
<div class="graph-container">
|
|
13
|
-
<svg #commitGraph width="100%" height="600"></svg>
|
|
14
|
-
</div>
|
|
15
|
-
`,
|
|
16
|
-
styles: [`
|
|
17
|
-
.graph-container {
|
|
18
|
-
background-color: white;
|
|
19
|
-
border-radius: 0.5rem;
|
|
20
|
-
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
21
|
-
padding: 1.5rem;
|
|
22
|
-
transition: background-color 0.2s ease;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/* Dark mode styles */
|
|
26
|
-
.dark .graph-container {
|
|
27
|
-
background-color: #2d2d2d;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/* SVG dark mode support */
|
|
31
|
-
.dark svg {
|
|
32
|
-
background-color: transparent;
|
|
33
|
-
}
|
|
34
|
-
`]
|
|
35
|
-
})
|
|
36
|
-
export class CommitGraphComponent implements AfterViewInit, OnChanges, OnDestroy {
|
|
37
|
-
@ViewChild('commitGraph', { static: true }) svgElement!: ElementRef;
|
|
38
|
-
@Input() commits: Commit[] = [];
|
|
39
|
-
@Input() colorPalette?: ColorPalette;
|
|
40
|
-
@Output() commitClick = new EventEmitter<Commit>();
|
|
41
|
-
|
|
42
|
-
private svg: any;
|
|
43
|
-
private g: any;
|
|
44
|
-
private observer: MutationObserver | null = null;
|
|
45
|
-
|
|
46
|
-
ngAfterViewInit() {
|
|
47
|
-
this.initializeGraph();
|
|
48
|
-
this.setupDarkModeObserver();
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
ngOnChanges(changes: SimpleChanges) {
|
|
52
|
-
if (changes['commits'] && this.svg) {
|
|
53
|
-
this.renderGraph();
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
ngOnDestroy() {
|
|
58
|
-
if (this.observer) {
|
|
59
|
-
this.observer.disconnect();
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
private initializeGraph() {
|
|
64
|
-
this.svg = d3.select(this.svgElement.nativeElement);
|
|
65
|
-
this.g = this.svg.append('g');
|
|
66
|
-
this.renderGraph();
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
private renderGraph() {
|
|
70
|
-
if (!this.commits.length) return;
|
|
71
|
-
|
|
72
|
-
this.g.selectAll('*').remove();
|
|
73
|
-
|
|
74
|
-
const width = this.svgElement.nativeElement.getBoundingClientRect().width;
|
|
75
|
-
const height = 600;
|
|
76
|
-
const margin = { top: 20, right: 20, bottom: 20, left: 20 };
|
|
77
|
-
|
|
78
|
-
// Get colors based on dark mode
|
|
79
|
-
const colors = this.getGraphColors();
|
|
80
|
-
|
|
81
|
-
// Create a more realistic git graph structure
|
|
82
|
-
const { nodes, links, branches } = this.createGitGraphStructure();
|
|
83
|
-
|
|
84
|
-
// Create force simulation for better layout
|
|
85
|
-
const simulation = d3.forceSimulation(nodes)
|
|
86
|
-
.force('link', d3.forceLink(links).id((d: any) => d.id).distance(100))
|
|
87
|
-
.force('charge', d3.forceManyBody().strength(-300))
|
|
88
|
-
.force('center', d3.forceCenter(width / 2, height / 2))
|
|
89
|
-
.force('collision', d3.forceCollide().radius(30));
|
|
90
|
-
|
|
91
|
-
// Create branch lines (different colors for different branches)
|
|
92
|
-
const branchGroups = this.g.selectAll('.branch-group')
|
|
93
|
-
.data(branches)
|
|
94
|
-
.enter().append('g')
|
|
95
|
-
.attr('class', 'branch-group');
|
|
96
|
-
|
|
97
|
-
branchGroups.selectAll('.branch-line')
|
|
98
|
-
.data((d: any) => d.links)
|
|
99
|
-
.enter().append('line')
|
|
100
|
-
.attr('class', 'branch-line')
|
|
101
|
-
.attr('stroke', (d: any, i: number) => this.getBranchColor(d.branch, colors))
|
|
102
|
-
.attr('stroke-width', 3)
|
|
103
|
-
.attr('opacity', 0.7);
|
|
104
|
-
|
|
105
|
-
// Create links between commits
|
|
106
|
-
this.g.selectAll('.link')
|
|
107
|
-
.data(links)
|
|
108
|
-
.enter().append('line')
|
|
109
|
-
.attr('class', 'link')
|
|
110
|
-
.attr('stroke', colors.link)
|
|
111
|
-
.attr('stroke-width', 2)
|
|
112
|
-
.attr('opacity', 0.5);
|
|
113
|
-
|
|
114
|
-
// Create commit nodes
|
|
115
|
-
const node = this.g.selectAll('.commit-node')
|
|
116
|
-
.data(nodes)
|
|
117
|
-
.enter().append('g')
|
|
118
|
-
.attr('class', 'commit-node')
|
|
119
|
-
.style('cursor', 'pointer');
|
|
120
|
-
|
|
121
|
-
// Add circles for nodes
|
|
122
|
-
node.append('circle')
|
|
123
|
-
.attr('r', 10)
|
|
124
|
-
.attr('fill', (d: any) => this.getNodeColor(d, colors))
|
|
125
|
-
.attr('stroke', colors.nodeStroke)
|
|
126
|
-
.attr('stroke-width', 2);
|
|
127
|
-
|
|
128
|
-
// Add commit hash text
|
|
129
|
-
node.append('text')
|
|
130
|
-
.attr('text-anchor', 'middle')
|
|
131
|
-
.attr('dy', 25)
|
|
132
|
-
.attr('fill', colors.text)
|
|
133
|
-
.attr('class', 'text-xs')
|
|
134
|
-
.text((d: any) => d.commit.hash.substring(0, 6));
|
|
135
|
-
|
|
136
|
-
// Add commit message on hover
|
|
137
|
-
node.append('title')
|
|
138
|
-
.text((d: any) => `${d.commit.message}\n${d.commit.author}\n${this.formatDate(d.commit.date)}`);
|
|
139
|
-
|
|
140
|
-
// Add click handlers
|
|
141
|
-
node.on('click', (event: any, d: any) => {
|
|
142
|
-
this.commitClick.emit(d.commit);
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
// Update positions on simulation tick
|
|
146
|
-
simulation.on('tick', () => {
|
|
147
|
-
// Update branch lines
|
|
148
|
-
branchGroups.selectAll('.branch-line')
|
|
149
|
-
.attr('x1', (d: any) => d.source.x)
|
|
150
|
-
.attr('y1', (d: any) => d.source.y)
|
|
151
|
-
.attr('x2', (d: any) => d.target.x)
|
|
152
|
-
.attr('y2', (d: any) => d.target.y);
|
|
153
|
-
|
|
154
|
-
// Update links
|
|
155
|
-
this.g.selectAll('.link')
|
|
156
|
-
.attr('x1', (d: any) => d.source.x)
|
|
157
|
-
.attr('y1', (d: any) => d.source.y)
|
|
158
|
-
.attr('x2', (d: any) => d.target.x)
|
|
159
|
-
.attr('y2', (d: any) => d.target.y);
|
|
160
|
-
|
|
161
|
-
// Update nodes
|
|
162
|
-
node.attr('transform', (d: any) => `translate(${d.x},${d.y})`);
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
private getGraphColors() {
|
|
167
|
-
const isDarkMode = document.documentElement.classList.contains('dark');
|
|
168
|
-
console.log('Graph colors - Dark mode:', isDarkMode);
|
|
169
|
-
|
|
170
|
-
if (this.colorPalette) {
|
|
171
|
-
return {
|
|
172
|
-
link: this.colorPalette.colors.link,
|
|
173
|
-
nodeFill: this.colorPalette.colors.nodeFill,
|
|
174
|
-
nodeStroke: this.colorPalette.colors.nodeStroke,
|
|
175
|
-
text: this.colorPalette.colors.graphText
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Fallback to default colors
|
|
180
|
-
if (isDarkMode) {
|
|
181
|
-
return {
|
|
182
|
-
link: '#4b5563', // gray-600 for dark mode
|
|
183
|
-
nodeFill: '#3b82f6', // blue-500 (same for both modes)
|
|
184
|
-
nodeStroke: '#1e40af', // blue-700 (same for both modes)
|
|
185
|
-
text: '#9ca3af' // gray-400 for dark mode
|
|
186
|
-
};
|
|
187
|
-
} else {
|
|
188
|
-
return {
|
|
189
|
-
link: '#cbd5e0', // gray-300 for light mode
|
|
190
|
-
nodeFill: '#3b82f6', // blue-500 (same for both modes)
|
|
191
|
-
nodeStroke: '#1e40af', // blue-700 (same for both modes)
|
|
192
|
-
text: '#6b7280' // gray-500 for light mode
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
private setupDarkModeObserver() {
|
|
198
|
-
// Observe changes to the document element's class list
|
|
199
|
-
this.observer = new MutationObserver((mutations) => {
|
|
200
|
-
mutations.forEach((mutation) => {
|
|
201
|
-
if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
|
|
202
|
-
// Re-render graph when dark mode changes
|
|
203
|
-
if (this.svg) {
|
|
204
|
-
this.renderGraph();
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
});
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
// Start observing the document element for class changes
|
|
211
|
-
this.observer.observe(document.documentElement, {
|
|
212
|
-
attributes: true,
|
|
213
|
-
attributeFilter: ['class']
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
private createGitGraphStructure() {
|
|
218
|
-
const nodes = this.commits.map((commit, i) => ({
|
|
219
|
-
id: commit.hash,
|
|
220
|
-
commit: commit,
|
|
221
|
-
branch: commit.branches[0] || 'main',
|
|
222
|
-
index: i,
|
|
223
|
-
isMerge: commit.parents && commit.parents.length > 1,
|
|
224
|
-
isBranch: commit.branches.length > 0
|
|
225
|
-
}));
|
|
226
|
-
|
|
227
|
-
const links = [];
|
|
228
|
-
const branches: any[] = [];
|
|
229
|
-
const branchColors = ['#3b82f6', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6', '#06b6d4'];
|
|
230
|
-
|
|
231
|
-
// Create links between commits
|
|
232
|
-
for (let i = 1; i < nodes.length; i++) {
|
|
233
|
-
const current = nodes[i];
|
|
234
|
-
const previous = nodes[i - 1];
|
|
235
|
-
|
|
236
|
-
links.push({
|
|
237
|
-
source: previous.id,
|
|
238
|
-
target: current.id,
|
|
239
|
-
branch: current.branch
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// Group commits by branch
|
|
244
|
-
const branchGroups = new Map();
|
|
245
|
-
nodes.forEach(node => {
|
|
246
|
-
const branch = node.branch;
|
|
247
|
-
if (!branchGroups.has(branch)) {
|
|
248
|
-
branchGroups.set(branch, []);
|
|
249
|
-
}
|
|
250
|
-
branchGroups.get(branch).push(node);
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
// Create branch structures
|
|
254
|
-
branchGroups.forEach((branchNodes, branchName) => {
|
|
255
|
-
const branchLinks = [];
|
|
256
|
-
for (let i = 1; i < branchNodes.length; i++) {
|
|
257
|
-
branchLinks.push({
|
|
258
|
-
source: branchNodes[i - 1],
|
|
259
|
-
target: branchNodes[i],
|
|
260
|
-
branch: branchName
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
branches.push({
|
|
265
|
-
name: branchName,
|
|
266
|
-
links: branchLinks,
|
|
267
|
-
color: branchColors[branches.length % branchColors.length]
|
|
268
|
-
});
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
return { nodes, links, branches };
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
private getBranchColor(branchName: string, colors: any): string {
|
|
275
|
-
const branchColors = ['#3b82f6', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6', '#06b6d4'];
|
|
276
|
-
const branchIndex = branchName.charCodeAt(0) % branchColors.length;
|
|
277
|
-
return branchColors[branchIndex];
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
private getNodeColor(node: any, colors: any): string {
|
|
281
|
-
if (node.isMerge) {
|
|
282
|
-
return '#8b5cf6'; // Purple for merge commits
|
|
283
|
-
} else if (node.isBranch) {
|
|
284
|
-
return '#10b981'; // Green for branch commits
|
|
285
|
-
} else {
|
|
286
|
-
return colors.nodeFill;
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
private formatDate(dateString: string): string {
|
|
291
|
-
const date = new Date(dateString);
|
|
292
|
-
return date.toLocaleDateString() + ' ' + date.toLocaleTimeString();
|
|
293
|
-
}
|
|
294
|
-
}
|