Skip to content

Commit 14828e9

Browse files
committed
Implement auto-opening conflict resolver
Add autoOpenConflictResolverPath to state and handle it in MainView.
1 parent e38dbe5 commit 14828e9

3 files changed

Lines changed: 112 additions & 87 deletions

File tree

src/App.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,10 @@ const App: React.FC = () => {
321321
autoOpenConflictResolverPath: state.autoOpenConflictResolverPath,
322322
onAutoOpenConflictResolverConsumed: state.clearAutoOpenConflictResolverPath,
323323
onOpenConflictResolverForPath: state.openConflictResolverForPath,
324+
onConflictMergeContinue: () => { void state.runGitCommand(['mergeContinue'], tr('Merge fortgesetzt.', 'Merge continued.'), tr('Merge wird fortgesetzt...', 'Continuing merge...')); },
325+
onConflictMergeAbort: () => { void state.runGitCommand(['mergeAbort'], tr('Merge abgebrochen.', 'Merge aborted.'), tr('Merge wird abgebrochen...', 'Aborting merge...')); },
326+
onConflictRebaseContinue: () => { void state.runGitCommand(['rebaseContinue'], tr('Rebase fortgesetzt.', 'Rebase continued.'), tr('Rebase wird fortgesetzt...', 'Continuing rebase...')); },
327+
onConflictRebaseAbort: () => { void state.runGitCommand(['rebaseAbort'], tr('Rebase abgebrochen.', 'Rebase aborted.'), tr('Rebase wird abgebrochen...', 'Aborting rebase...')); },
324328
};
325329

326330
return (

src/components/layout/MainView.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,10 @@ export const MainView: React.FC = () => {
248248
autoOpenConflictResolverPath,
249249
onAutoOpenConflictResolverConsumed,
250250
onOpenConflictResolverForPath,
251+
onConflictMergeContinue,
252+
onConflictMergeAbort,
253+
onConflictRebaseContinue,
254+
onConflictRebaseAbort,
251255
} = useAppContext();
252256
const { tr } = useI18n();
253257

@@ -365,6 +369,22 @@ export const MainView: React.FC = () => {
365369
{shouldShowPrimaryPaneHeader && (
366370
<div className={`pane-header pane-header-main${activeConflictPath ? ' pane-header-main--conflict' : ''}`}>
367371
<span className="pane-header-main-title">{primaryPaneTitle}</span>
372+
{activeConflictPath ? (
373+
<div className="pane-header-main-center">
374+
<div className="conflict-global-actions-rail conflict-global-actions-rail--topbar">
375+
<div className="conflict-action-group">
376+
<span className="conflict-action-group-label">Merge</span>
377+
<button className="staging-btn-sm conflict-action-btn" onClick={onConflictMergeContinue} disabled={isGitActionRunning} title="Merge abschliessen">Fortsetzen</button>
378+
<button className="staging-btn-sm danger conflict-action-btn conflict-action-btn--danger" onClick={onConflictMergeAbort} disabled={isGitActionRunning} title="Merge abbrechen">Abbrechen</button>
379+
</div>
380+
<div className="conflict-action-group">
381+
<span className="conflict-action-group-label">Rebase</span>
382+
<button className="staging-btn-sm conflict-action-btn" onClick={onConflictRebaseContinue} disabled={isGitActionRunning} title="Rebase fortsetzen">Fortsetzen</button>
383+
<button className="staging-btn-sm danger conflict-action-btn conflict-action-btn--danger" onClick={onConflictRebaseAbort} disabled={isGitActionRunning} title="Rebase abbrechen">Abbrechen</button>
384+
</div>
385+
</div>
386+
</div>
387+
) : null}
368388
{isSettingsView ? null : isReleaseView ? (
369389
<button
370390
className="icon-btn pane-header-nav-btn"

src/components/staging-area/ConflictResolverPanel.tsx

Lines changed: 88 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,6 @@ type ConflictResolverPanelProps = {
1414
onOpenConflictResolver?: (filePath: string) => void;
1515
isConflictBlockCountPending: boolean;
1616
totalConflictBlocksInView: number;
17-
mergeContinue: () => void;
18-
mergeAbort: () => void;
19-
rebaseContinue: () => void;
20-
rebaseAbort: () => void;
2117
conflictEditor: ConflictEditorState | null;
2218
isConflictEditorLoading: boolean;
2319
blockCountForPath: (path: string) => number;
@@ -49,10 +45,6 @@ export const ConflictResolverPanel: React.FC<ConflictResolverPanelProps> = ({
4945
onOpenConflictResolver,
5046
isConflictBlockCountPending,
5147
totalConflictBlocksInView,
52-
mergeContinue,
53-
mergeAbort,
54-
rebaseContinue,
55-
rebaseAbort,
5648
conflictEditor,
5749
isConflictEditorLoading,
5850
blockCountForPath,
@@ -85,63 +77,59 @@ export const ConflictResolverPanel: React.FC<ConflictResolverPanelProps> = ({
8577

8678
return (
8779
<div className={`staging-section conflict-section${isConflictOnly ? ' conflict-section--resolve' : ''}`}>
88-
{!onOpenConflictResolver && (
89-
<div className="conflict-global-nav conflict-global-nav--header" role="group" aria-label="Konflikt-Navigation ueber alle Dateien">
90-
<button
91-
className="conflict-global-nav-btn conflict-global-nav-btn--prev"
92-
onClick={() => { void navigateToPreviousConflict(); }}
93-
disabled={!hasPreviousConflictTarget || isNavigationBusy}
94-
>
95-
{'<'} Zurueck
96-
</button>
97-
<div className="conflict-global-nav-meta">
98-
<span className="conflict-global-nav-title">Konflikt-Navigation</span>
99-
<span className="conflict-global-nav-state">
100-
{isStructuredConflictViewLocked
101-
? 'Manuelle Marker-Bearbeitung erkannt - Vergleich voruebergehend pausiert'
102-
: (activeConflictFileIndex >= 0
103-
? `Datei ${activeConflictFileIndex + 1} von ${conflictPaths.length}`
104-
: 'Datei --')}
105-
{!isStructuredConflictViewLocked && conflictBlocks.length > 0
106-
? ` - Block ${safeSelectedConflictBlockIndex + 1} von ${conflictBlocks.length}`
107-
: (!isStructuredConflictViewLocked ? ' - Keine Konfliktmarker in dieser Datei' : '')}
80+
{!onOpenConflictResolver ? (
81+
<div className="conflict-resolver-header-shell">
82+
<div className="conflict-global-nav conflict-global-nav--header" role="group" aria-label="Konflikt-Navigation ueber alle Dateien">
83+
<button
84+
className="conflict-global-nav-btn conflict-global-nav-btn--prev"
85+
onClick={() => { void navigateToPreviousConflict(); }}
86+
disabled={!hasPreviousConflictTarget || isNavigationBusy}
87+
>
88+
{'<'} Zurueck
89+
</button>
90+
<div className="conflict-global-nav-meta">
91+
<span className="conflict-global-nav-title">Konflikt-Navigation</span>
92+
<span className="conflict-global-nav-state">
93+
{isStructuredConflictViewLocked
94+
? 'Manuelle Marker-Bearbeitung erkannt - Vergleich voruebergehend pausiert'
95+
: (activeConflictFileIndex >= 0
96+
? `Datei ${activeConflictFileIndex + 1} von ${conflictPaths.length}`
97+
: 'Datei --')}
98+
{!isStructuredConflictViewLocked && conflictBlocks.length > 0
99+
? ` - Block ${safeSelectedConflictBlockIndex + 1} von ${conflictBlocks.length}`
100+
: (!isStructuredConflictViewLocked ? ' - Keine Konfliktmarker in dieser Datei' : '')}
101+
</span>
102+
</div>
103+
<button
104+
className="conflict-global-nav-btn conflict-global-nav-btn--next"
105+
onClick={() => { void navigateToNextConflict(); }}
106+
disabled={!hasNextConflictTarget || isNavigationBusy}
107+
>
108+
Weiter {'>'}
109+
</button>
110+
</div>
111+
112+
<div className="staging-section-header conflict-section-header">
113+
<span style={{ color: 'var(--status-danger)' }}>Konflikte</span>
114+
<span className="staging-count" title="Konfliktbloecke (<<<<<<< ... >>>>>>>) in den sichtbaren Dateien, nicht nur Dateianzahl">
115+
{isConflictBlockCountPending && visibleConflicts.length > 0 ? '...' : totalConflictBlocksInView}
108116
</span>
117+
<span className="staging-stats-inline">
118+
{visibleConflicts.length} Datei{visibleConflicts.length !== 1 ? 'en' : ''}
119+
</span>
120+
<div style={{ flex: 1 }} />
109121
</div>
110-
<button
111-
className="conflict-global-nav-btn conflict-global-nav-btn--next"
112-
onClick={() => { void navigateToNextConflict(); }}
113-
disabled={!hasNextConflictTarget || isNavigationBusy}
114-
>
115-
Weiter {'>'}
116-
</button>
117122
</div>
118-
)}
119-
120-
<div className={`staging-section-header${!onOpenConflictResolver ? ' conflict-section-header' : ''}`}>
121-
<span style={{ color: 'var(--status-danger)' }}>Konflikte</span>
122-
<span className="staging-count" title="Konfliktbloecke (<<<<<<< ... >>>>>>>) in den sichtbaren Dateien, nicht nur Dateianzahl">
123-
{isConflictBlockCountPending && visibleConflicts.length > 0 ? '...' : totalConflictBlocksInView}
124-
</span>
125-
<span className="staging-stats-inline">
126-
{visibleConflicts.length} Datei{visibleConflicts.length !== 1 ? 'en' : ''}
127-
</span>
128-
<div style={{ flex: 1 }} />
129-
</div>
130-
131-
{!onOpenConflictResolver && (
132-
<div className="conflict-global-actions">
133-
<span className="conflict-global-actions-summary">
134-
{isConflictBlockCountPending && visibleConflicts.length > 0
135-
? 'Konfliktbloecke werden gezaehlt...'
136-
: `${totalConflictBlocksInView} Konfliktblock${totalConflictBlocksInView !== 1 ? 'e' : ''} in ${visibleConflicts.length} Datei${visibleConflicts.length !== 1 ? 'en' : ''}`}
123+
) : (
124+
<div className="staging-section-header">
125+
<span style={{ color: 'var(--status-danger)' }}>Konflikte</span>
126+
<span className="staging-count" title="Konfliktbloecke (<<<<<<< ... >>>>>>>) in den sichtbaren Dateien, nicht nur Dateianzahl">
127+
{isConflictBlockCountPending && visibleConflicts.length > 0 ? '...' : totalConflictBlocksInView}
137128
</span>
138-
<div className="conflict-global-actions-buttons">
139-
<button className="staging-btn-sm conflict-action-btn" onClick={mergeContinue} title="Merge abschliessen">Merge fortsetzen</button>
140-
<button className="staging-btn-sm danger conflict-action-btn conflict-action-btn--danger" onClick={mergeAbort} title="Merge abbrechen">Merge abbrechen</button>
141-
<div className="conflict-action-divider" />
142-
<button className="staging-btn-sm conflict-action-btn" onClick={rebaseContinue} title="Rebase fortsetzen">Rebase fortsetzen</button>
143-
<button className="staging-btn-sm danger conflict-action-btn conflict-action-btn--danger" onClick={rebaseAbort} title="Rebase abbrechen">Rebase abbrechen</button>
144-
</div>
129+
<span className="staging-stats-inline">
130+
{visibleConflicts.length} Datei{visibleConflicts.length !== 1 ? 'en' : ''}
131+
</span>
132+
<div style={{ flex: 1 }} />
145133
</div>
146134
)}
147135

@@ -294,32 +282,45 @@ export const ConflictResolverPanel: React.FC<ConflictResolverPanelProps> = ({
294282
<button className="staging-btn-sm" style={{ padding: '6px 24px', border: '1px solid var(--border-color)', background: 'var(--bg-dark)', fontWeight: 600, fontSize: '0.8rem' }} onClick={() => applyConflictChoiceToSelected('both')} disabled={conflictEditor.isSaving}>Beide Staende uebernehmen (Aktueller zuerst)</button>
295283
</div>
296284
</div>
297-
)}
298-
299-
<div className="conflict-manual-edit-root">
300-
<div style={{ padding: '12px 20px', borderBottom: '1px solid var(--line-subtle)', display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexShrink: 0, gap: '8px', flexWrap: 'wrap' }}>
301-
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
302-
<span style={{ fontWeight: 600, fontSize: '0.9rem', color: 'var(--text-primary)' }}>Manuelle Bearbeitung</span>
303-
<span style={{ fontSize: '0.7rem', color: 'var(--text-secondary)', maxWidth: '720px' }}>
304-
Gesamte Datei mit Zeilennummern; farbig: Aktueller Stand / Eingehender Stand / Marker. Scrollen fuer Kontext ausserhalb der Konfliktmarker.
305-
</span>
306-
</div>
307-
<div style={{ display: 'flex', gap: '8px' }}>
308-
<button className="staging-btn-sm" style={{ border: '1px solid var(--border-color)', padding: '4px 12px', background: 'var(--bg-dark)' }} onClick={resetConflictEditorDraft} disabled={!isConflictEditorDirty || conflictEditor.isSaving}>Aenderungen verwerfen</button>
309-
<button className="staging-btn-sm" style={{ border: '1px solid var(--border-color)', padding: '4px 12px', background: 'var(--bg-dark)' }} onClick={() => { void saveConflictEditor(false); }} disabled={conflictEditor.isSaving || !isConflictEditorDirty}>Speichern</button>
310-
<button className="staging-btn-sm" style={{ background: 'var(--status-success)', color: 'var(--on-accent)', border: 'none', padding: '4px 16px', fontWeight: 600 }} onClick={() => { void saveConflictEditor(true); }} disabled={conflictEditor.isSaving || conflictBlocks.length > 0}>Speichern + Geloest</button>
311-
</div>
312-
</div>
313-
<ConflictManualEditor
314-
ref={conflictManualScrollRef}
315-
content={conflictEditor.content}
316-
disabled={conflictEditor.isSaving}
317-
onChange={(next) => onConflictEditorContentChange(conflictEditor.filePath, next)}
318-
/>
319-
</div>
320-
</div>
321-
</div>
322-
)}
285+
)}
286+
287+
<div className="conflict-manual-edit-root">
288+
<div className="conflict-manual-action-bar">
289+
<span className="conflict-manual-action-title">Editor-Aktionen</span>
290+
<div className="conflict-manual-action-buttons">
291+
<button
292+
className="staging-btn-sm conflict-manual-action-btn"
293+
onClick={resetConflictEditorDraft}
294+
disabled={!isConflictEditorDirty || conflictEditor.isSaving}
295+
>
296+
Aenderungen verwerfen
297+
</button>
298+
<button
299+
className="staging-btn-sm conflict-manual-action-btn"
300+
onClick={() => { void saveConflictEditor(false); }}
301+
disabled={conflictEditor.isSaving || !isConflictEditorDirty}
302+
>
303+
Speichern
304+
</button>
305+
<button
306+
className="staging-btn-sm conflict-manual-action-btn conflict-manual-action-btn--success"
307+
onClick={() => { void saveConflictEditor(true); }}
308+
disabled={conflictEditor.isSaving || conflictBlocks.length > 0}
309+
>
310+
Speichern + Geloest
311+
</button>
312+
</div>
313+
</div>
314+
<ConflictManualEditor
315+
ref={conflictManualScrollRef}
316+
content={conflictEditor.content}
317+
disabled={conflictEditor.isSaving}
318+
onChange={(next) => onConflictEditorContentChange(conflictEditor.filePath, next)}
319+
/>
320+
</div>
321+
</div>
322+
</div>
323+
)}
323324
</div>
324325
</div>
325326
)}

0 commit comments

Comments
 (0)