Upload via GUI (7543 Dateien)
This commit is contained in:
@@ -0,0 +1,454 @@
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace GitManagerSetup;
|
||||
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
private readonly SetupMode _mode;
|
||||
private int _page;
|
||||
private bool _busy;
|
||||
private bool _done;
|
||||
private string? _installedExe;
|
||||
|
||||
private const int PageNumOptions = 1;
|
||||
private const int PageNumProgress = 2;
|
||||
|
||||
public MainWindow(SetupMode mode)
|
||||
{
|
||||
InitializeComponent();
|
||||
_mode = mode;
|
||||
VersionText.Text = $"Version {Installer.Version}";
|
||||
|
||||
var installed = Installer.InstalledDir;
|
||||
DirBox.Text = installed ?? Installer.DefaultDir;
|
||||
DesktopLinkBox.IsChecked = installed is null || Installer.DesktopLinkExists;
|
||||
|
||||
if (installed is not null)
|
||||
{
|
||||
UpdateHint.Visibility = Visibility.Visible;
|
||||
UpdateHintText.Text = Installer.InstalledVersion == Installer.Version
|
||||
? $"Git Manager GUI {Installer.Version} ist bereits installiert und wird neu installiert. Zugangsdaten und Einstellungen bleiben erhalten."
|
||||
: $"Die vorhandene Installation{(Installer.InstalledVersion is { } v ? $" (Version {v})" : "")} wird durch Version {Installer.Version} ersetzt. Zugangsdaten und Einstellungen bleiben erhalten.";
|
||||
}
|
||||
if (Installer.IsRunning(installed)) RunningHint.Visibility = Visibility.Visible;
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case SetupMode.Uninstall:
|
||||
Title = WindowTitle.Text = "Git Manager GUI deinstallieren";
|
||||
Steps.Visibility = Visibility.Collapsed;
|
||||
ShowOnly(PageUninstall);
|
||||
NextButton.Content = "Deinstallieren";
|
||||
BackButton.Visibility = Visibility.Collapsed;
|
||||
LoadDataInfo();
|
||||
break;
|
||||
case SetupMode.Update:
|
||||
// aus der App heraus: ohne Rückfragen direkt aktualisieren und danach wieder starten
|
||||
Title = WindowTitle.Text = "Git Manager GUI wird aktualisiert";
|
||||
Steps.Visibility = Visibility.Collapsed;
|
||||
Loaded += async (_, _) => await RunInstall(DirBox.Text, DesktopLinkBox.IsChecked == true, update: true);
|
||||
ShowOnly(PageProgress);
|
||||
ProgressTitle.Text = "Git Manager GUI wird aktualisiert …";
|
||||
BackButton.Visibility = Visibility.Hidden;
|
||||
break;
|
||||
default:
|
||||
ShowPage(0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Seiten ----------
|
||||
|
||||
private UIElement? _shownPage;
|
||||
|
||||
/// <summary>Seite wechseln – mit kurzem Einblenden von rechts.</summary>
|
||||
private void ShowOnly(UIElement page)
|
||||
{
|
||||
foreach (var p in new UIElement[] { PageWelcome, PageOptions, PageProgress, PageUninstall })
|
||||
p.Visibility = p == page ? Visibility.Visible : Visibility.Collapsed;
|
||||
if (page == _shownPage) return;
|
||||
_shownPage = page;
|
||||
|
||||
var ease = new CubicEase { EasingMode = EasingMode.EaseOut };
|
||||
var slide = new TranslateTransform(18, 0);
|
||||
page.RenderTransform = slide;
|
||||
slide.BeginAnimation(TranslateTransform.XProperty, new DoubleAnimation(18, 0, TimeSpan.FromMilliseconds(260)) { EasingFunction = ease });
|
||||
page.BeginAnimation(OpacityProperty, new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(220)));
|
||||
}
|
||||
|
||||
// Farben der Schritt-Anzeige in der (immer dunklen) Seitenleiste – Cyan wie in der App
|
||||
private static readonly Brush StepDoneFill = Frozen(Color.FromRgb(0x58, 0xD5, 0xFF));
|
||||
private static readonly Brush StepDoneGlyph = Frozen(Color.FromRgb(0x04, 0x10, 0x1F));
|
||||
private static readonly Brush StepCurrentFill = Brushes.White;
|
||||
private static readonly Brush StepCurrentText = Frozen(Color.FromRgb(0x13, 0x30, 0x5C));
|
||||
private static readonly Brush StepUpcoming = Frozen(Color.FromRgb(0x70, 0x83, 0xA4));
|
||||
private static readonly Brush StepUpcomingStroke = Frozen(Color.FromRgb(0x3A, 0x4E, 0x72));
|
||||
private static readonly Brush StepDoneText = Frozen(Color.FromRgb(0xAE, 0xBD, 0xD8));
|
||||
private static readonly Brush StepLineIdle = Frozen(Color.FromRgb(0x24, 0x36, 0x58));
|
||||
|
||||
private static Brush Frozen(Color c)
|
||||
{
|
||||
var b = new SolidColorBrush(c);
|
||||
b.Freeze();
|
||||
return b;
|
||||
}
|
||||
|
||||
private void UpdateSteps(int page)
|
||||
{
|
||||
var dots = new[] { Dot0, Dot1, Dot2 };
|
||||
var nums = new[] { Num0, Num1, Num2 };
|
||||
var labels = new[] { Label0, Label1, Label2 };
|
||||
var lines = new[] { Line0, Line1 };
|
||||
var iconFont = (FontFamily)FindResource("IconFont");
|
||||
var uiFont = (FontFamily)FindResource("UiFont");
|
||||
|
||||
for (int i = 0; i < dots.Length; i++)
|
||||
{
|
||||
bool done = i < page || (_done && i == page);
|
||||
bool current = i == page && !done;
|
||||
dots[i].Background = done ? StepDoneFill : current ? StepCurrentFill : Brushes.Transparent;
|
||||
dots[i].BorderBrush = done ? StepDoneFill : current ? StepCurrentFill : StepUpcomingStroke;
|
||||
nums[i].Text = done ? "" : (i + 1).ToString();
|
||||
nums[i].FontFamily = done ? iconFont : uiFont;
|
||||
nums[i].FontSize = done ? 11 : 12;
|
||||
nums[i].Foreground = done ? StepDoneGlyph : current ? StepCurrentText : StepUpcoming;
|
||||
labels[i].Foreground = current ? Brushes.White : done ? StepDoneText : StepUpcoming;
|
||||
labels[i].FontWeight = current ? FontWeights.SemiBold : FontWeights.Normal;
|
||||
}
|
||||
for (int i = 0; i < lines.Length; i++)
|
||||
lines[i].Background = i < page ? StepDoneFill : StepLineIdle;
|
||||
}
|
||||
|
||||
private void ShowPage(int page)
|
||||
{
|
||||
_page = page;
|
||||
ShowOnly(page switch { 0 => PageWelcome, PageNumOptions => PageOptions, _ => PageProgress });
|
||||
UpdateSteps(page);
|
||||
|
||||
BackButton.Visibility = page == PageNumOptions ? Visibility.Visible : Visibility.Hidden;
|
||||
NextButton.Content = page switch
|
||||
{
|
||||
PageNumOptions => "Installieren",
|
||||
PageNumProgress => "Fertigstellen",
|
||||
_ => "Weiter",
|
||||
};
|
||||
}
|
||||
|
||||
private void Back_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!_busy && _page > 0) ShowPage(_page - 1);
|
||||
}
|
||||
|
||||
private async void Next_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_busy) return;
|
||||
|
||||
if (_mode == SetupMode.Uninstall)
|
||||
{
|
||||
if (_done) { Close(); return; }
|
||||
await RunUninstall();
|
||||
return;
|
||||
}
|
||||
if (_mode == SetupMode.Update)
|
||||
{
|
||||
Close(); // nur nach einem Fehler erreichbar
|
||||
return;
|
||||
}
|
||||
|
||||
switch (_page)
|
||||
{
|
||||
case 0: ShowPage(PageNumOptions); break;
|
||||
case PageNumOptions:
|
||||
var dir = DirBox.Text.Trim();
|
||||
if (!IsFullPath(dir) || dir.IndexOfAny(Path.GetInvalidPathChars()) >= 0)
|
||||
{
|
||||
await Frage("Ungültiger Ordner", "Bitte einen vollständigen Ordner angeben, z. B. C:\\Programme\\Git Manager GUI.", "OK");
|
||||
return;
|
||||
}
|
||||
if (dir.TrimEnd('\\', '/').Length > Installer.MaxDirLength)
|
||||
{
|
||||
await Frage("Ordnerpfad zu lang",
|
||||
$"Der Pfad hat {dir.TrimEnd('\\', '/').Length} Zeichen, Windows erlaubt hier höchstens {Installer.MaxDirLength}. " +
|
||||
"Bitte einen kürzeren Ordner wählen.", "OK");
|
||||
return;
|
||||
}
|
||||
await RunInstall(dir, DesktopLinkBox.IsChecked == true, update: false);
|
||||
break;
|
||||
case PageNumProgress:
|
||||
// Start im Hintergrund: Process.Start kann blockieren, solange Windows
|
||||
// die EXE prüft – das Fenster darf dabei nie einfrieren
|
||||
if (LaunchBox.IsChecked == true)
|
||||
{
|
||||
_busy = true;
|
||||
NextButton.IsEnabled = false;
|
||||
NextButton.Content = "Wird gestartet …";
|
||||
await Task.Run(StartApp);
|
||||
_busy = false;
|
||||
}
|
||||
Close();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void ChangeDir_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
using var dlg = new System.Windows.Forms.FolderBrowserDialog
|
||||
{
|
||||
Description = "Installationsordner wählen",
|
||||
SelectedPath = Directory.Exists(DirBox.Text) ? DirBox.Text : Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||
ShowNewFolderButton = true,
|
||||
};
|
||||
if (dlg.ShowDialog() != System.Windows.Forms.DialogResult.OK) return;
|
||||
var chosen = dlg.SelectedPath;
|
||||
// wie bei üblichen Installern: Unterordner "Git Manager GUI" anhängen
|
||||
DirBox.Text = Path.GetFileName(chosen.TrimEnd('\\')).Equals(Installer.AppName, StringComparison.OrdinalIgnoreCase)
|
||||
? chosen : Path.Combine(chosen, Installer.AppName);
|
||||
}
|
||||
|
||||
// ---------- Installation ----------
|
||||
|
||||
private async Task RunInstall(string dir, bool desktop, bool update)
|
||||
{
|
||||
_busy = true;
|
||||
if (!update) ShowPage(PageNumProgress);
|
||||
SetButtons(false);
|
||||
var progress = new Progress<(double Value, string Text)>(p => SetProgress(p.Value, p.Text));
|
||||
try
|
||||
{
|
||||
await Task.Run(() => Installer.Install(dir, desktop, progress, waitForExit: update));
|
||||
_installedExe = Path.Combine(dir, Installer.ExeName);
|
||||
_done = true;
|
||||
|
||||
if (update)
|
||||
{
|
||||
ShowSuccess("Update abgeschlossen", $"Git Manager GUI {Installer.Version} wird gestartet …");
|
||||
StartApp();
|
||||
_busy = false;
|
||||
var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1.5) };
|
||||
timer.Tick += (_, _) => { timer.Stop(); Close(); };
|
||||
timer.Start();
|
||||
return;
|
||||
}
|
||||
|
||||
ShowSuccess("Alles bereit!", "Git Manager GUI ist installiert. Du findest es im Startmenü" + (desktop ? " und auf dem Desktop." : "."));
|
||||
DonePanel.Visibility = Visibility.Visible;
|
||||
ShowPage(PageNumProgress);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ProgressTitle.Text = update ? "Update fehlgeschlagen" : "Installation fehlgeschlagen";
|
||||
ProgressBarPanel.Visibility = Visibility.Collapsed;
|
||||
ProgressText.Text = ex.Message;
|
||||
ProgressText.Foreground = (Brush)FindResource("DangerBrush");
|
||||
NextButton.Content = "Schließen";
|
||||
_installedExe = null;
|
||||
}
|
||||
_busy = false;
|
||||
NextButton.IsEnabled = true;
|
||||
CloseButton.IsEnabled = true;
|
||||
BackButton.Visibility = Visibility.Hidden;
|
||||
CancelButton.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
/// <summary>Fortschritt ausblenden und den Haken mit kurzem "Aufploppen" zeigen.</summary>
|
||||
private void ShowSuccess(string title, string text)
|
||||
{
|
||||
ProgressBarPanel.Visibility = Visibility.Collapsed;
|
||||
ProgressTitle.Text = title;
|
||||
ProgressText.Text = text;
|
||||
SuccessIcon.Visibility = Visibility.Visible;
|
||||
var pop = new DoubleAnimation(0.4, 1, TimeSpan.FromMilliseconds(420)) { EasingFunction = new BackEase { EasingMode = EasingMode.EaseOut, Amplitude = 0.5 } };
|
||||
SuccessScale.BeginAnimation(ScaleTransform.ScaleXProperty, pop);
|
||||
SuccessScale.BeginAnimation(ScaleTransform.ScaleYProperty, pop);
|
||||
SuccessIcon.BeginAnimation(OpacityProperty, new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(200)));
|
||||
}
|
||||
|
||||
private void StartApp()
|
||||
{
|
||||
if (_installedExe is null || !File.Exists(_installedExe)) return;
|
||||
try
|
||||
{
|
||||
Process.Start(new ProcessStartInfo(_installedExe)
|
||||
{
|
||||
UseShellExecute = true,
|
||||
WorkingDirectory = Path.GetDirectoryName(_installedExe)!,
|
||||
});
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
// ---------- Deinstallation ----------
|
||||
|
||||
private async void LoadDataInfo()
|
||||
{
|
||||
var root = Installer.DataDir;
|
||||
if (!Directory.Exists(root))
|
||||
{
|
||||
DataInfo.Text = "Es wurden keine App-Daten gefunden.";
|
||||
RemoveDataBox.IsEnabled = false;
|
||||
return;
|
||||
}
|
||||
DataInfo.Text = $"Sie liegen in {root}. Größe wird berechnet …";
|
||||
var size = await Task.Run(Installer.DataSize);
|
||||
DataInfo.Text = $"Sie liegen in {root} ({Installer.FormatSize(size)}) und stehen wieder bereit, falls du Git Manager GUI später neu installierst.";
|
||||
}
|
||||
|
||||
private void RemoveDataBox_Click(object sender, RoutedEventArgs e) =>
|
||||
RemoveDataWarning.Visibility = RemoveDataBox.IsChecked == true ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
private async Task RunUninstall()
|
||||
{
|
||||
_busy = true;
|
||||
bool removeData = RemoveDataBox.IsChecked == true;
|
||||
ShowOnly(PageProgress);
|
||||
ProgressTitle.Text = "Git Manager GUI wird entfernt …";
|
||||
LaunchBox.Visibility = Visibility.Collapsed;
|
||||
SetButtons(false);
|
||||
var progress = new Progress<(double Value, string Text)>(p => SetProgress(p.Value, p.Text));
|
||||
try
|
||||
{
|
||||
await Task.Run(() => Installer.Uninstall(removeData, progress));
|
||||
ShowSuccess("Git Manager GUI wurde entfernt", removeData
|
||||
? "Programm, Zugangsdaten und Einstellungen wurden entfernt (die Daten liegen im Papierkorb)."
|
||||
: "Deine Zugangsdaten und Einstellungen liegen weiterhin in " + Installer.DataDir + " – falls du Git Manager GUI später wieder installierst.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ProgressTitle.Text = "Deinstallation fehlgeschlagen";
|
||||
ProgressBarPanel.Visibility = Visibility.Collapsed;
|
||||
ProgressText.Text = ex.Message;
|
||||
}
|
||||
_done = true;
|
||||
_busy = false;
|
||||
CloseButton.IsEnabled = true;
|
||||
NextButton.Content = "Schließen";
|
||||
NextButton.IsEnabled = true;
|
||||
CancelButton.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
// ---------- Helfer ----------
|
||||
|
||||
private void SetButtons(bool enabled)
|
||||
{
|
||||
NextButton.IsEnabled = enabled;
|
||||
BackButton.IsEnabled = enabled;
|
||||
CancelButton.IsEnabled = enabled;
|
||||
CloseButton.IsEnabled = enabled;
|
||||
}
|
||||
|
||||
private void SetProgress(double value, string text)
|
||||
{
|
||||
if (_done) return;
|
||||
ProgressText.Text = text;
|
||||
PercentText.Text = $"{(int)Math.Round(value * 100)} %";
|
||||
_progress = value;
|
||||
AnimateProgress();
|
||||
}
|
||||
|
||||
private double _progress;
|
||||
|
||||
private void AnimateProgress()
|
||||
{
|
||||
var target = Math.Max(0, ProgressTrack.ActualWidth * _progress);
|
||||
ProgressFill.BeginAnimation(WidthProperty,
|
||||
new DoubleAnimation(target, TimeSpan.FromMilliseconds(250)) { EasingFunction = new CubicEase() });
|
||||
}
|
||||
|
||||
// Kommt der erste Wert, bevor die Seite fertig angeordnet ist (Breite 0), nachträglich richtig setzen
|
||||
private void ProgressTrack_SizeChanged(object sender, SizeChangedEventArgs e) => AnimateProgress();
|
||||
|
||||
private static bool IsFullPath(string p) =>
|
||||
p.Length >= 3 && char.IsLetter(p[0]) && p[1] == ':' && (p[2] == '\\' || p[2] == '/');
|
||||
|
||||
private void Minimize_Click(object sender, RoutedEventArgs e) => WindowState = WindowState.Minimized;
|
||||
|
||||
private async void Cancel_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_busy || _dialog is not null) return;
|
||||
if (!_done && _mode == SetupMode.Install && _page > 0)
|
||||
{
|
||||
bool abbrechen = await Frage("Installation abbrechen?",
|
||||
"Git Manager GUI wird nicht installiert. Du kannst das Setup später jederzeit erneut starten.",
|
||||
"Ja, abbrechen", "Weiter installieren", "\uE897");
|
||||
if (!abbrechen) return;
|
||||
}
|
||||
Close();
|
||||
}
|
||||
|
||||
// ---------- eigene Rückfragen im Setup-Look ----------
|
||||
|
||||
private TaskCompletionSource<bool>? _dialog;
|
||||
|
||||
/// <summary>
|
||||
/// Zeigt eine Rückfrage über dem Fenster. Mit <paramref name="zurueck"/>: zwei Knöpfe –
|
||||
/// der hervorgehobene ist der sichere Weg zurück (liefert false), der andere bestätigt (true).
|
||||
/// Ohne: nur ein Hinweis mit einem Knopf (liefert true).
|
||||
/// </summary>
|
||||
private Task<bool> Frage(string titel, string text, string bestaetigen, string? zurueck = null, string symbol = "\uE7BA")
|
||||
{
|
||||
_dialog?.TrySetResult(false);
|
||||
_dialog = new TaskCompletionSource<bool>();
|
||||
DialogTitle.Text = titel;
|
||||
DialogText.Text = text;
|
||||
DialogIcon.Text = symbol;
|
||||
if (zurueck is null)
|
||||
{
|
||||
DialogSecondary.Visibility = Visibility.Collapsed;
|
||||
DialogPrimary.Content = bestaetigen;
|
||||
}
|
||||
else
|
||||
{
|
||||
DialogSecondary.Visibility = Visibility.Visible;
|
||||
DialogSecondary.Content = bestaetigen;
|
||||
DialogPrimary.Content = zurueck;
|
||||
}
|
||||
|
||||
DialogOverlay.Visibility = Visibility.Visible;
|
||||
var ein = new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(160));
|
||||
DialogOverlay.BeginAnimation(OpacityProperty, ein);
|
||||
var groesse = new DoubleAnimation(0.94, 1, TimeSpan.FromMilliseconds(220)) { EasingFunction = new CubicEase { EasingMode = EasingMode.EaseOut } };
|
||||
DialogScale.BeginAnimation(ScaleTransform.ScaleXProperty, groesse);
|
||||
DialogScale.BeginAnimation(ScaleTransform.ScaleYProperty, groesse);
|
||||
DialogPrimary.Focus();
|
||||
return _dialog.Task;
|
||||
}
|
||||
|
||||
private void SchliesseFrage(bool ergebnis)
|
||||
{
|
||||
if (_dialog is null) return;
|
||||
var tcs = _dialog;
|
||||
_dialog = null;
|
||||
DialogOverlay.Visibility = Visibility.Collapsed;
|
||||
tcs.TrySetResult(ergebnis);
|
||||
}
|
||||
|
||||
// hervorgehobener Knopf: bei Rückfragen "zurück" (false), beim reinen Hinweis "OK" (true)
|
||||
private void DialogPrimary_Click(object sender, RoutedEventArgs e) =>
|
||||
SchliesseFrage(DialogSecondary.Visibility != Visibility.Visible);
|
||||
|
||||
private void DialogSecondary_Click(object sender, RoutedEventArgs e) => SchliesseFrage(true);
|
||||
|
||||
protected override void OnPreviewKeyDown(System.Windows.Input.KeyEventArgs e)
|
||||
{
|
||||
// solange die Rückfrage offen ist: Enter = hervorgehobener Knopf, Esc = zurück
|
||||
if (_dialog is not null)
|
||||
{
|
||||
if (e.Key == System.Windows.Input.Key.Escape) { SchliesseFrage(DialogSecondary.Visibility != Visibility.Visible); e.Handled = true; return; }
|
||||
if (e.Key == System.Windows.Input.Key.Enter) { DialogPrimary_Click(this, new RoutedEventArgs()); e.Handled = true; return; }
|
||||
}
|
||||
base.OnPreviewKeyDown(e);
|
||||
}
|
||||
|
||||
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
|
||||
{
|
||||
if (_busy) e.Cancel = true; // während der Installation nicht schließen
|
||||
_dialog?.TrySetResult(false);
|
||||
base.OnClosing(e);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user