Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f75f3fb9c6 | ||
|
|
6e1cee9a59 | ||
|
|
2e26aaa41c | ||
|
|
eb13bd071b | ||
|
|
156b8e3041 | ||
|
|
9d3472dff0 | ||
|
|
0029668033 | ||
|
|
a809efd91a | ||
|
|
a902a0c5d7 | ||
|
|
7fa0db02b5 | ||
|
|
673affe1d1 | ||
|
|
65dd67cef5 | ||
|
|
ef68a305ce | ||
|
|
e154cc507e | ||
|
|
56b5cb17d0 | ||
|
|
e178605833 | ||
|
|
f4c16b173f | ||
|
|
2ad8777311 | ||
|
|
ce5a655758 | ||
|
|
7660b5de1a | ||
|
|
50705d1197 | ||
|
|
1f635d33cc | ||
|
|
38a75bc720 | ||
|
|
ead123c62b | ||
|
|
9b3aed6f0a | ||
|
|
99d9629e25 | ||
|
|
85a222a9fb | ||
|
|
dedb09b9c6 | ||
|
|
c45ad3ba32 | ||
|
|
937c72ce4a | ||
|
|
61d6d11a2f | ||
|
|
ef911afb06 | ||
|
|
4f9494ae7b | ||
|
|
37820af6dd | ||
|
|
9c6a69cc53 | ||
|
|
eb549d3975 | ||
|
|
b2314f6fe6 | ||
|
|
42fb8dfe9b | ||
|
|
54baf9b874 | ||
|
|
8d29040b57 | ||
|
|
8c7b2ddf47 | ||
|
|
03f4fa6fbc | ||
|
|
a57786fe54 | ||
|
|
6f73a87314 | ||
|
|
7ef5522258 | ||
|
|
860c714301 | ||
|
|
68133f4d0f | ||
|
|
75cc88a011 | ||
|
|
bac355eabb | ||
|
|
d7658c873f | ||
|
|
8830aac76c | ||
|
|
336d9e600b | ||
|
|
d6518698b1 | ||
|
|
e4585e50ee | ||
|
|
12a2f5faa9 | ||
|
|
8aede65bd7 | ||
|
|
ae68900363 | ||
|
|
81c541a0b5 | ||
|
|
089257d740 | ||
|
|
2ecc57c761 | ||
|
|
4654eed12c | ||
|
|
434a4e1c7b | ||
|
|
a0546c562d | ||
|
|
264f1b7e45 | ||
|
|
3b31c5893c | ||
|
|
047196c4d0 | ||
|
|
5571f225d5 | ||
|
|
565835418e |
@@ -0,0 +1,5 @@
|
||||
* text=auto eol=lf
|
||||
*.png binary
|
||||
*.ico binary
|
||||
*.exe binary
|
||||
*.blockmap binary
|
||||
@@ -0,0 +1,58 @@
|
||||
# ===== Zugangsdaten / Secrets (NIE hochladen!) =====
|
||||
/data/
|
||||
credentials.json
|
||||
*.env
|
||||
.env*
|
||||
*.pem
|
||||
*.key
|
||||
*.pfx
|
||||
*.p12
|
||||
|
||||
# ===== Abhängigkeiten =====
|
||||
node_modules/
|
||||
.npm/
|
||||
.cache/
|
||||
|
||||
# ===== Build-Ausgaben =====
|
||||
dist/
|
||||
out/
|
||||
release/
|
||||
*.exe
|
||||
*.blockmap
|
||||
*.sha256
|
||||
*.sha256sum
|
||||
|
||||
# ===== .NET Installer (Visual Studio / dotnet) =====
|
||||
installer/bin/
|
||||
installer/obj/
|
||||
**/bin/
|
||||
**/obj/
|
||||
.vs/
|
||||
*.user
|
||||
*.suo
|
||||
|
||||
# ===== Große gebündelte Binaries (MinGit, ca. 93 MB) =====
|
||||
vendor/mingit/
|
||||
|
||||
# ===== Laufzeitdaten / Backups / Repos =====
|
||||
/repos/
|
||||
/backup/
|
||||
*.zip
|
||||
*.rar
|
||||
*.7z
|
||||
|
||||
# ===== Logs =====
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# ===== Editor / OS =====
|
||||
.vscode/
|
||||
.idea/
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
desktop.ini
|
||||
|
||||
# ===== Claude Code (lokale Einstellungen) =====
|
||||
.claude/settings.local.json
|
||||
@@ -282,9 +282,58 @@ npm run build
|
||||
# Output: dist/git-manager-gui-Setup-2.x.x.exe
|
||||
```
|
||||
|
||||
### Abbrechbare Vorgänge
|
||||
|
||||
Uploads, Ordner-Downloads und Batch-Clones laufen über **ein** gemeinsames Abbruch-System:
|
||||
|
||||
- `main.js`: `beginCancellableUpload()` legt einen `AbortController` an, `endCancellableUpload()` räumt auf (immer im `finally`). Der IPC-Kanal `cancel-upload` bricht alle laufenden Vorgänge ab.
|
||||
- Das `signal` wird durchgereicht an `simple-git` (`abort`), `execGit`/`runGitAsync` (`signal`), `runLimited` und `uploadItemsViaGiteaApi` (axios `signal`).
|
||||
- Ein Abbruch ist **kein Fehler**: Er läuft als `UPLOAD_CANCELLED` durch die Kette (`createUploadCancelledError`, `isUploadCancelledError`, `throwIfUploadCancelled` in `src/git/apiHandler.js`), löst keinen API-Ausweichweg aus und kommt als `{ ok: false, cancelled: true }` im Renderer an.
|
||||
- Neuer abbrechbarer Handler: `beginCancellableUpload()` am Anfang, `signal` weiterreichen, im `catch` `isUploadCancelledError(e)` abfangen, im `finally` `endCancellableUpload()`. Im Renderer `showProgress(..., { cancellable: true })`.
|
||||
- Unter Windows beendet `spawn(signal)` nur `git.exe`; `execGit` beendet deshalb den ganzen Prozessbaum (`taskkill /T /F`) und kehrt sofort zurück.
|
||||
|
||||
### Pause, Sperre, Benachrichtigung, Release-Assistent
|
||||
|
||||
- **Pause:** `src/features/pauseGate.js` (`createPauseGate`). In `main.js` gibt es genau ein `transferGate`. Kontrollpunkte rufen `await transferGate.wait(signal)` auf (in `runLimited`, vor dem Staging der Git-Wege, zwischen den Paketen von `uploadItemsViaGiteaApi` über `waitIfPaused`, vor jedem Batch-Clone). Ein Abbruch löst Wartende sofort aus. Neue langlaufende Schritte brauchen einen eigenen Kontrollpunkt.
|
||||
- **Zugangsdaten-Sperre:** `src/features/uploadBlocklist.js` nutzt die Dateinamen-Regeln aus `secretScan.js`. `removeBlockedInPlace()` und `blockedSecretReason()` in `main.js` sind in allen Upload-Handlern eingebaut, die Ergebnisse tragen `blockedFiles`. Neuer Upload-Weg: dort ebenfalls aufrufen. Einstellung `uploadBlockSecrets`.
|
||||
- **Benachrichtigung:** `src/features/transferNotice.js` baut den Text aus dem Verlaufseintrag, `maybeNotifyTransfer()` in `main.js` zeigt ihn nur, wenn das Hauptfenster nicht vorn ist (Einstellung `notifyTransfers`).
|
||||
- **Release-Assistent:** `src/features/releaseAssist.js` (Version aus Dateiname, Versionsvergleich, Tag-Prüfung, Release-Text mit Prüfsummen). IPC `inspect-release-file` und `prepare-release`, Oberfläche in `showCreateReleaseModal()`. Das Prüfsummen-Format in `buildReleaseBody` muss zu `extractChecksumFromReleaseBody` in `updater.js` passen.
|
||||
|
||||
### Startanimation
|
||||
|
||||
Nach dem Vorbild des AeroMC-Launchers (`AeroMC-Launcher/src/startanimation.js`):
|
||||
|
||||
1. `createWindow()` berechnet Position und Größe (`centeredBounds`) und legt über `begin()` zuerst ein randloses, durchsichtiges Fenster mit `renderer/splash.html` an: das Logo dreht sich (CSS `rotateY`), darunter der Schriftzug.
|
||||
2. Das Hauptfenster entsteht danach mit denselben Maßen, verborgen, und lädt.
|
||||
3. `play()` wartet auf `did-finish-load` (höchstens 12 s) und mindestens 1,6 s Drehzeit. Dann zeigt es das Hauptfenster unsichtbar (Deckkraft 0, Mausereignisse aus), wartet auf die gezeichnete Oberfläche (Repo-Liste oder höchstens 1,5 s), fotografiert es mit `capturePage()` und lässt `aufbauen()` in `splash.js` das Bild von der Mitte aus aufziehen.
|
||||
4. Am Ende übernimmt das echte Fenster an derselben Stelle. Fehler und Zeitüberschreitungen zeigen das Hauptfenster immer trotzdem.
|
||||
|
||||
**Stolperstein:** `setIgnoreMouseEvents(true)` am Startfenster darf erst nach dem Zeigen und leicht verzögert aufgerufen werden (`IGNORE_MOUSE_DELAY_MS`). Davor aufgerufen wird das durchsichtige Fenster unter Windows weiß. `scripts/ui-smoke-splash.js` prüft das über eine Bildschirmaufnahme (Farbe an einer leeren Stelle muss der des Desktops entsprechen) und darf deshalb die Hardware-Beschleunigung nicht abschalten.
|
||||
|
||||
Beim Start mit `--hidden` und bei abgeschalteter Einstellung `showSplash` gibt es die Animation nicht. `scripts/ui-smoke-splash.js` prüft sie mit einem Ersatz-Hauptfenster. Im Entwicklungsmodus (`electron .`) startet die App wegen der vielen `node_modules`-Dateien deutlich langsamer als das gepackte Programm; die Animation selbst wird dadurch nicht langsamer.
|
||||
|
||||
### Oberflächen-Testlauf
|
||||
|
||||
`npm run test:ui` (`scripts/ui-smoke.js`) startet die App mit leerem eigenen `APPDATA`, steuert sie über den Debug-Port und prüft Toolbar-Layout bei drei Breiten, Fortschrittsbalken, Pause, Verlauf, Sperre, Release-Prüfung und Vorlagen. `scripts/ui-smoke-tray.js` prüft das Tray-Menüfenster in echtem Electron. Der Lauf berührt weder echte Zugangsdaten noch einen Server und dauert gut eine Minute.
|
||||
|
||||
### Tray-Menü
|
||||
|
||||
Das Tray-Symbol öffnet kein natives Menü, sondern ein eigenes kleines Fenster, denn das native Electron-Menü zeichnet unter Windows keine Hover-Markierung:
|
||||
|
||||
- `src/features/trayMenu.js`: Inhalt (`buildTrayTemplate`, `serializeTemplate`, `runAction`, `trayTooltip`), ohne Electron und damit testbar. Neuer Eintrag: dort mit `id` ergänzen, die Aktion in `trayTemplate()` in `main.js` verdrahten.
|
||||
- `src/features/trayPopup.js`: das Fenster (frameless, transparent, schließt bei Fokusverlust und Esc). Es wird 2 Sekunden nach dem Start vorgeladen, weil der erste Renderer-Start mehrere Sekunden dauert.
|
||||
- `renderer/tray-menu.html`, `renderer/tray-menu.js`, `tray-menu-preload.js`: Darstellung (hell/dunkel nach Windows-Design, Pfeiltasten und Enter) und die vier IPC-Kanäle.
|
||||
- Das Fenster ist von der Zoom-Einstellung ausgenommen und wird beim Beenden zerstört, sonst hielte das versteckte Fenster die App am Leben.
|
||||
|
||||
### Upload-Verlauf und Wiederholen
|
||||
|
||||
- `src/features/uploadHistory.js` (`buildEntry`, `filterEntries`, `createUploadHistory`): speichert die letzten 100 Uploads in `upload-history.json` im userData-Ordner. `main.js` umhüllt dafür `ipcMain.handle` für die Kanäle in `UPLOAD_HISTORY_CHANNELS`, neue Upload-Kanäle dort eintragen.
|
||||
- Teilfehler: `buildPartialUploadError` hängt die fehlgeschlagenen Einträge an den Fehler (`failedItems`). Die Handler geben sie als `retryItems` zurück, der Renderer sendet sie über den Parameter `items` erneut (`retryFailedUploads`).
|
||||
- `.gitignore`-Vorlagen: `src/features/gitignoreTemplates.js`.
|
||||
|
||||
### Version erhöhen
|
||||
|
||||
1. Version in `package.json` updaten: `"version": "2.1.4"`
|
||||
1. Version in `package.json` updaten: `"version": "2.2.1"`
|
||||
2. Changelog aktualisieren (optional)
|
||||
3. Build ausführen
|
||||
4. GitHub Release erstellen mit der .exe
|
||||
|
||||
@@ -1250,6 +1250,73 @@ tr:last-child td { border-bottom: none; }
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chapter">
|
||||
<h2 class="chapter-heading"><span class="num">12a</span> Uploads: Abbrechen, Wiederholen & Verlauf</h2>
|
||||
|
||||
<div class="feature-grid">
|
||||
<div class="feature-cell">
|
||||
<div class="icon">⏹️</div>
|
||||
<h4>Abbrechen</h4>
|
||||
<p>Während eines Uploads, Ordner-Downloads oder Batch-Clones erscheint im Fortschrittsbalken oben der Knopf <kbd>Abbrechen (Esc)</kbd>. Du kannst auch einfach <kbd>Esc</kbd> drücken, solange kein Dialog offen ist.</p>
|
||||
</div>
|
||||
<div class="feature-cell">
|
||||
<div class="icon">⏱️</div>
|
||||
<h4>Fortschritt</h4>
|
||||
<p>Der Balken zeigt Dateien, übertragene MB und eine geschätzte Restzeit, zum Beispiel <em>Upload: 48/120 Dateien · 19,2 MB / 48,0 MB · noch ca. 5 Sek.</em> Die Restzeit ist eine Hochrechnung und erscheint erst nach einigen Sekunden.</p>
|
||||
</div>
|
||||
<div class="feature-cell">
|
||||
<div class="icon">🔁</div>
|
||||
<h4>Nur Fehlgeschlagene wiederholen</h4>
|
||||
<p>Schlagen bei einem Upload einzelne Dateien fehl, fragt die App nach und sendet nur diese erneut (bis zu dreimal). Bereits hochgeladene Dateien bleiben unberührt.</p>
|
||||
</div>
|
||||
<div class="feature-cell">
|
||||
<div class="icon">⏸️</div>
|
||||
<h4>Pausieren und Fortsetzen</h4>
|
||||
<p>Neben <kbd>Abbrechen</kbd> im Fortschrittsbalken gibt es <kbd>Pausieren</kbd>. Die Übertragung hält zwischen den Schritten an (ein laufender Clone, Push oder ein Paket wird noch fertig) und läuft mit <kbd>Fortsetzen</kbd> weiter. Das geht auch über das Tray-Menü.</p>
|
||||
</div>
|
||||
<div class="feature-cell">
|
||||
<div class="icon">🕘</div>
|
||||
<h4>Upload-Verlauf</h4>
|
||||
<p>Über <kbd>🕘 Verlauf</kbd> in der Toolbar siehst du die letzten 100 Uploads mit Datum, Repo, Dateizahl, Dauer und Ergebnis. Filtere nach Erfolgreich, Abgebrochen oder Fehler und suche nach einem Repo-Namen. Ein Klick auf einen Eintrag öffnet das Repo, <kbd>CSV exportieren</kbd> speichert die gefilterte Liste für Excel.</p>
|
||||
</div>
|
||||
<div class="feature-cell">
|
||||
<div class="icon">🛡️</div>
|
||||
<h4>Zugangsdaten-Sperre</h4>
|
||||
<p>Dateien wie <code>credentials.json</code>, <code>.env</code>, private Schlüssel oder Keystores werden auf allen Upload-Wegen ausgelassen, auch wenn du sie in der Vorschau nicht abgewählt hast. Die App meldet, welche Dateien fehlen. Vorlagen wie <code>.env.example</code> bleiben erlaubt. Unter Einstellungen → Uploads lässt sich die Sperre abschalten, bei öffentlichen Repos solltest du sie aktiv lassen.</p>
|
||||
</div>
|
||||
<div class="feature-cell">
|
||||
<div class="icon">🔔</div>
|
||||
<h4>Windows-Meldung</h4>
|
||||
<p>Endet ein Upload, während die App im Tray versteckt, minimiert oder nicht im Vordergrund ist, meldet Windows das Ergebnis. Ein Klick auf die Meldung öffnet die App. Abgebrochene Uploads melden nichts. Unter Einstellungen → Uploads lässt es sich abschalten.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="warn-box">
|
||||
<strong>Grenzen des Abbrechens:</strong> Ein Push, der schon auf dem Server angekommen ist, lässt sich nicht zurücknehmen. Beim Upload über die Gitea-API bleiben Pakete, die schon als Commit verbucht sind, im Repo. Eine Migration kann nicht abgebrochen werden, weil der Server sie selbst ausführt. Das Repo-Backup hat seinen eigenen Abbrechen-Knopf.
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title">📦 Release für den Updater veröffentlichen</div>
|
||||
<p>Der Updater liest die Releases des Repos <code>M_Viper/Git-Manager-Gui</code> auf Gitea. So veröffentlichst du eine neue Version:</p>
|
||||
<ol class="steps">
|
||||
<li>Baue den Installer mit <code>npm run build</code>. Dabei entstehen <code>Git-Manager-GUI-Setup-X.Y.Z.exe</code> und die Prüfsummen-Datei <code>.sha256</code> in <code>dist/</code>.</li>
|
||||
<li>Öffne in der Repo-Ansicht <strong>🚀 Releases</strong> und lege ein neues Release an. Wähle bei <strong>Release Asset</strong> den Installer.</li>
|
||||
<li>Die App berechnet die SHA-256-Prüfsumme, gleicht sie mit der <code>.sha256</code>-Datei ab und füllt Tag und Name aus dem Dateinamen vor. Stimmen die Prüfsummen nicht überein, wird nichts veröffentlicht.</li>
|
||||
<li>Beim Erstellen prüft die App, dass der Tag eine Version wie <code>v2.2.1</code> ist, es ihn noch nicht gibt und er neuer ist als das letzte Release, sonst würde der Updater ihn nicht anbieten.</li>
|
||||
<li>Die Prüfsumme wird im Format, das der Updater liest, in den Release-Text geschrieben, und Installer und <code>.sha256</code> werden hochgeladen.</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title">📄 .gitignore im Repo anlegen</div>
|
||||
<ol class="steps">
|
||||
<li>Öffne ein Repository und klicke in der Repo-Leiste auf <strong>📄 .gitignore</strong>.</li>
|
||||
<li>Wähle die Vorlagen: Node.js, .NET, Electron, Python, Java, PHP, Unity. Der allgemeine Teil (Betriebssystem, Editor, <code>.env</code>, Logs) ist immer dabei.</li>
|
||||
<li>Mit <strong>Anlegen</strong> wird die Datei ins Repo geschrieben. Existiert schon eine, fragt die App vor dem Ersetzen nach.</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ──────────────────────────────────────────── -->
|
||||
<div class="part-break">
|
||||
<div class="part-label">TEIL F</div>
|
||||
@@ -1274,8 +1341,10 @@ tr:last-child td { border-bottom: none; }
|
||||
<tr><th>Einstellung</th><th>Standard</th><th>Beschreibung</th></tr>
|
||||
<tr><td>⭐ Favoriten anzeigen</td><td>An</td><td>Zeigt/versteckt die Favoriten-Sidebar und Stern-Buttons</td></tr>
|
||||
<tr><td>🕐 Zuletzt geöffnet</td><td>An</td><td>Verlaufs-Sidebar ein-/ausblenden</td></tr>
|
||||
<tr><td>🔍 Symbolgröße</td><td>Klein</td><td>Größe der Symbole in der Repo-Übersicht und im Datei-Explorer: Klein, Mittel oder Groß. Klein wirkt ruhiger und zeigt mehr auf einmal. Wird sofort angewendet (Einstellungen → Darstellung)</td></tr>
|
||||
<tr><td>⊞ Kompakt-Modus</td><td>Aus</td><td>Verdichtete Darstellung für kleinere Bildschirme</td></tr>
|
||||
<tr><td>🎨 Farbige Datei-Icons</td><td>An</td><td>Unterschiedliche Farben je Dateityp im Explorer</td></tr>
|
||||
<tr><td>✨ Startanimation</td><td>An</td><td>Beim Öffnen dreht sich das Logo, danach baut sich das Fenster von der Mitte aus auf. Beim Start mit Windows im Tray gibt es sie nie</td></tr>
|
||||
<tr><td>🚀 Mit Windows starten</td><td>Aus</td><td>App miniert im System-Tray beim Windows-Login starten</td></tr>
|
||||
</table>
|
||||
|
||||
@@ -1303,7 +1372,7 @@ tr:last-child td { border-bottom: none; }
|
||||
<tr><td><kbd>Ctrl</kbd>+<kbd>K</kbd></td><td>Fuzzy-Suche im Repository-Grid fokussieren</td></tr>
|
||||
<tr><td><kbd>Ctrl</kbd>+<kbd>S</kbd></td><td>Aktive Datei im Editor speichern</td></tr>
|
||||
<tr><td><kbd>Ctrl</kbd>+<kbd>F</kbd></td><td>Suchleiste im Editor öffnen</td></tr>
|
||||
<tr><td><kbd>Escape</kbd></td><td>Suchleiste im Editor schließen</td></tr>
|
||||
<tr><td><kbd>Escape</kbd></td><td>Suchleiste im Editor schließen · laufenden Upload/Download abbrechen (wenn kein Dialog offen ist)</td></tr>
|
||||
<tr><td><kbd>Rechtsklick</kbd></td><td>Kontextmenü auf Repository-Karte oder Sidebar-Eintrag</td></tr>
|
||||
<tr><td><kbd>Drag</kbd> raus</td><td>Datei/Ordner/Repo herunterladen</td></tr>
|
||||
<tr><td><kbd>Drag</kbd> rein</td><td>Datei/Ordner in aktuelles Verzeichnis hochladen</td></tr>
|
||||
|
||||
@@ -1,12 +1,39 @@
|
||||
# 🛠 Git Manager Explorer Pro
|
||||
|
||||
[](https://github.com/M-Viper/git-manager-gui/releases)
|
||||
[](https://github.com/M-Viper/git-manager-gui/releases)
|
||||
[](LICENSE)
|
||||
[](https://github.com/M-Viper/git-manager-gui)
|
||||
|
||||
**Eine moderne Desktop-Anwendung für vollständige Git-Repository-Verwaltung mit Gitea & GitHub.**
|
||||
Verwalte deine Repositories intuitiv ohne Kommandozeile — mit Favoriten, Verlauf, Editor und Batch-Aktionen.
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/screenshots/start.gif" alt="Programmstart: das Logo dreht sich, danach baut sich das Fenster von der Mitte aus auf" width="720">
|
||||
<br>
|
||||
<sub>✨ Der Start: Das Logo dreht sich, danach baut sich das Fenster von der Mitte aus auf</sub>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="docs/demo.mp4"><img src="docs/screenshots/thumbs/video.jpg" alt="Video: Rundgang durch die App" width="360"></a>
|
||||
<br>
|
||||
<sub>🎬 <a href="docs/demo.mp4"><b>Rundgang als Video ansehen</b></a> (1½ Minuten, mit Mauszeiger und Erklärungen, ohne Ton)</sub>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
## 🖼️ Bilder
|
||||
|
||||
Alle Bilder zeigen erfundene Beispieldaten. Ein Klick auf ein Vorschaubild öffnet es in voller Größe.
|
||||
|
||||
| | | |
|
||||
|:---:|:---:|:---:|
|
||||
| [](docs/screenshots/uebersicht.png)<br>📂 **Übersicht**<br><sub>Repos nach Besitzer, Favoriten, Heatmap</sub> | [](docs/screenshots/repo.png)<br>📁 **Repo-Ansicht**<br><sub>Dateien, Leiste für Commits, Releases, PRs</sub> | [](docs/screenshots/editor.png)<br>✏️ **Editor**<br><sub>Dateien direkt im Repo bearbeiten</sub> |
|
||||
| [](docs/screenshots/commits.png)<br>📊 **Commit-Verlauf**<br><sub>Autor, Zeit und Änderungen</sub> | [](docs/screenshots/releases.png)<br>📦 **Releases**<br><sub>Mit Dateien und Prüfsummen</sub> | [](docs/screenshots/release-assistent.png)<br>🚀 **Release-Assistent**<br><sub>Installer und Prüfsumme prüfen</sub> |
|
||||
| [](docs/screenshots/upload-vorschau.png)<br>🛡️ **Upload-Vorschau**<br><sub>Zugangsdaten werden gesperrt</sub> | [](docs/screenshots/fortschritt.png)<br>⏸️ **Fortschritt**<br><sub>Pausieren, Abbrechen, MB, Restzeit</sub> | [](docs/screenshots/verlauf.png)<br>🕘 **Upload-Verlauf**<br><sub>Filter, CSV-Export</sub> |
|
||||
| [](docs/screenshots/einstellungen.png)<br>⚙️ **Einstellungen**<br><sub>Sperre, Meldungen, Vorschau</sub> | [](docs/screenshots/tray-menue.png)<br>🔔 **Tray-Menü**<br><sub>Status, Pause, Abbrechen</sub> | |
|
||||
|
||||
<sub>Die Bilder entstehen mit `node scripts/make-screenshots.js` aus einem Demo-Server mit erfundenen Daten (siehe Entwicklerhandbuch).</sub>
|
||||
|
||||
---
|
||||
|
||||
## ✨ Features
|
||||
@@ -24,6 +51,15 @@ Verwalte deine Repositories intuitiv ohne Kommandozeile — mit Favoriten, Verla
|
||||
- 📤 **Drag & Drop Upload** — ziehe Dateien ins App-Fenster zum hochladen
|
||||
- 📥 **Drag & Drop Download** — ziehe Repos raus zum herunterladen
|
||||
- 🔄 **Rename & Move** — Dateien verwalten direkt im Editor
|
||||
- ⏹️ **Abbrechen** — Uploads, Downloads und Batch-Clones lassen sich per Knopf oder <kbd>Esc</kbd> stoppen
|
||||
- ⏱️ **Fortschritt mit MB und Restzeit** — bei großen Uploads siehst du, ob es noch läuft
|
||||
- 🔁 **Nur Fehlgeschlagene wiederholen** — nach einem Teilfehler nicht alles neu hochladen
|
||||
- ⏸️ **Pausieren und Fortsetzen** — Übertragungen anhalten, auch über das Tray-Menü
|
||||
- 🕘 **Upload-Verlauf** — die letzten 100 Uploads, filterbar nach Ergebnis und Repo, Klick öffnet das Repo, Export als CSV
|
||||
- 🛡️ **Zugangsdaten-Sperre** — `credentials.json`, `.env`, Schlüssel und Ähnliches werden auf allen Upload-Wegen nie hochgeladen
|
||||
- 🔔 **Windows-Meldung bei Upload-Ende** — wenn die App im Tray versteckt oder nicht im Vordergrund ist
|
||||
- 📄 **.gitignore anlegen** — Vorlagen für Node.js, .NET, Electron, Python, Java, PHP und Unity
|
||||
- 📦 **Release für den Updater** — Installer wählen, Prüfsumme abgleichen, Tag prüfen, `.sha256` mit hochladen
|
||||
|
||||
### 📊 Versionscontrol
|
||||
- 📊 **Commit-History** mit Filter nach Autor und Nachricht
|
||||
@@ -38,6 +74,7 @@ Verwalte deine Repositories intuitiv ohne Kommandozeile — mit Favoriten, Verla
|
||||
- 🔒 **Verschlüsselte Credentials** — alle Tokens AES-256 verschlüsselt
|
||||
- 🚀 **Auto-Startup** — App optional mit Windows starten
|
||||
- 🔄 **Auto-Update** — aktuelle Version automatisch verfügbar machen
|
||||
- 🔍 **Kleine Symbole** — ruhige Oberfläche mit kleinen Symbolen als Standard, Größe einstellbar (klein, mittel, groß)
|
||||
|
||||
---
|
||||
|
||||
@@ -131,7 +168,7 @@ npm start
|
||||
| <kbd>Ctrl</kbd>+<kbd>K</kbd> | Repo-Suche fokussieren |
|
||||
| <kbd>Ctrl</kbd>+<kbd>S</kbd> | Datei im Editor speichern |
|
||||
| <kbd>Ctrl</kbd>+<kbd>F</kbd> | Im Editor suchen |
|
||||
| <kbd>Escape</kbd> | Suche schließen |
|
||||
| <kbd>Escape</kbd> | Suche schließen · laufenden Upload/Download abbrechen (wenn kein Dialog offen ist) |
|
||||
| <kbd>Rechtsklick</kbd> | Kontextmenü (Repos & Dateien) |
|
||||
| <kbd>Drag</kbd> raus | Datei/Repo herunterladen |
|
||||
| <kbd>Drag</kbd> rein | Datei hochladen |
|
||||
@@ -176,6 +213,20 @@ Fehler gefunden oder Idee für neue Features?
|
||||
|
||||
## 📝 Changelog
|
||||
|
||||
### v2.2.1
|
||||
- ⏹️ Abbrechen-Knopf (und <kbd>Esc</kbd>) für Uploads, Ordner-Downloads und Batch-Clones
|
||||
- ⏸️ Pausieren und Fortsetzen im Fortschrittsbalken und im Tray-Menü
|
||||
- 🔁 „Nur Fehlgeschlagene wiederholen“ bei Teilfehlern von Drag-&-Drop- und Ordner-Uploads
|
||||
- ⏱️ Fortschrittsanzeige mit übertragenen MB und geschätzter Restzeit
|
||||
- 🕘 Upload-Verlauf mit Filter, Klick zum Öffnen des Repos und CSV-Export
|
||||
- 🛡️ Feste Sperre für Dateien mit Zugangsdaten auf allen Upload-Wegen (abschaltbar)
|
||||
- 🔔 Windows-Meldung, wenn ein Upload endet, während die App nicht vorn ist
|
||||
- 📄 `.gitignore`-Vorlagen im Repo, 📦 Release-Assistent für den Updater
|
||||
- 🎨 Aufgeräumte Toolbar mit eigener Repo-Leiste, neues Tray-Menü mit Hover-Markierung
|
||||
- 🔍 Kleine Symbole in Repo-Übersicht und Datei-Explorer als Standard (große Symbole wirken schnell erdrückend), Symbolgröße einstellbar
|
||||
- ✨ Startanimation wie bei AeroMC: das Logo dreht sich, danach baut sich das Fenster von der Mitte aus auf (abschaltbar, beim Autostart im Tray nie)
|
||||
- 🧪 `npm run test:ui`: Oberflächen-Testlauf
|
||||
|
||||
### v2.1.3 (13. Mai 2026)
|
||||
- ✨ Projektname wird korrekt ausgeblendet bei Wechsel zur Übersicht
|
||||
- 🐛 Bug-Fix: Zurück-Button setzt Gravur korrekt zurück
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert');
|
||||
const http = require('node:http');
|
||||
const fs = require('node:fs');
|
||||
const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
|
||||
require('../src/git/gitBinary.js').useBundledGit();
|
||||
const { registerFeatureIpc } = require('../src/features/featuresIpc.js');
|
||||
|
||||
// Drei Repos; das ZIP von "langsam" kommt in kleinen Stücken über ~3 s, damit ein Abbruch mitten hineinfällt
|
||||
function fakeGitea() {
|
||||
const server = http.createServer((req, res) => {
|
||||
const u = new URL(req.url, 'http://x');
|
||||
const send = (code, obj) => { res.statusCode = code; res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify(obj)); };
|
||||
if (u.pathname === '/api/v1/user/repos') {
|
||||
if (u.searchParams.get('page') !== '1') return send(200, []);
|
||||
const port = server.address().port;
|
||||
return send(200, ['eins', 'langsam', 'drei'].map(name => ({
|
||||
name, owner: { login: 'M_Viper' }, default_branch: 'main', empty: false,
|
||||
clone_url: `http://127.0.0.1:${port}/M_Viper/${name}.git`
|
||||
})));
|
||||
}
|
||||
const m = u.pathname.match(/^\/api\/v1\/repos\/M_Viper\/(\w+)\/archive\/main\.zip$/);
|
||||
if (m) {
|
||||
const total = 64 * 1024;
|
||||
res.writeHead(200, { 'Content-Type': 'application/zip', 'Content-Length': String(total) });
|
||||
if (m[1] !== 'langsam') return res.end(Buffer.alloc(total, 1));
|
||||
let sent = 0;
|
||||
const t = setInterval(() => {
|
||||
if (res.destroyed) return clearInterval(t);
|
||||
res.write(Buffer.alloc(2048, 1));
|
||||
sent += 2048;
|
||||
if (sent >= total) { clearInterval(t); res.end(); }
|
||||
}, 100);
|
||||
return;
|
||||
}
|
||||
send(404, { message: 'not found' });
|
||||
});
|
||||
return new Promise(r => server.listen(0, '127.0.0.1', () => r({ server, url: `http://127.0.0.1:${server.address().port}` })));
|
||||
}
|
||||
|
||||
function setup(url) {
|
||||
const handlers = {};
|
||||
const events = [];
|
||||
registerFeatureIpc({
|
||||
ipcMain: { handle: (name, fn) => { handlers[name] = fn; } },
|
||||
app: { getPath: () => fs.mkdtempSync(path.join(os.tmpdir(), 'gm-ud-')) },
|
||||
readCredentials: () => ({ giteaURL: url, giteaToken: 't' }),
|
||||
buildItemsFromPaths: async () => ({ items: [], skipped: [], ignored: [] }),
|
||||
clearGitTransportBlock: () => false,
|
||||
broadcast: (channel, p) => { if (channel === 'repo-backup-progress') events.push(p); }
|
||||
});
|
||||
return { call: (name, data) => handlers[name]({ sender: { send() {} } }, data), events };
|
||||
}
|
||||
|
||||
test('Backup meldet Fortschritt: Phasen, aktuelles Repo, Bytes und Ergebnis je Repo', async () => {
|
||||
const g = await fakeGitea();
|
||||
try {
|
||||
const { call, events } = setup(g.url);
|
||||
const target = fs.mkdtempSync(path.join(os.tmpdir(), 'gm-bk-'));
|
||||
await call('repo-backup-set-config', { targetDir: target });
|
||||
const r = await call('repo-backup-run-now', {});
|
||||
assert.strictEqual(r.summary.ok, 3, JSON.stringify(r));
|
||||
|
||||
const phases = [...new Set(events.map(e => e.phase))];
|
||||
assert.deepStrictEqual(phases, ['start', 'list', 'running', 'done']);
|
||||
assert.deepStrictEqual(events.find(e => e.phase === 'running').repos, ['M_Viper/eins', 'M_Viper/langsam', 'M_Viper/drei']);
|
||||
assert.ok(events.some(e => e.repo === 'M_Viper/langsam' && e.bytes > 0 && e.bytesTotal === 64 * 1024), 'Download-Fortschritt fehlt');
|
||||
const last = events[events.length - 1];
|
||||
assert.strictEqual(last.results.length, 3);
|
||||
assert.ok(last.results.every(x => x.ok && x.method === 'ZIP'));
|
||||
assert.ok(!events.some(e => 'abort' in e), 'interne Felder dürfen nicht nach außen gehen');
|
||||
|
||||
// Während kein Backup läuft, gibt es auch keinen Zwischenstand
|
||||
const state = await call('repo-backup-get-config', {});
|
||||
assert.strictEqual(state.running, false);
|
||||
assert.strictEqual(state.progress, null);
|
||||
} finally {
|
||||
g.server.close();
|
||||
}
|
||||
});
|
||||
|
||||
test('Abbrechen beendet den laufenden ZIP-Download sofort und überspringt den Rest', async () => {
|
||||
const g = await fakeGitea();
|
||||
try {
|
||||
const { call, events } = setup(g.url);
|
||||
const target = fs.mkdtempSync(path.join(os.tmpdir(), 'gm-bk-'));
|
||||
await call('repo-backup-set-config', { targetDir: target });
|
||||
const started = Date.now();
|
||||
const running = call('repo-backup-run-now', {});
|
||||
|
||||
// warten, bis der langsame Download läuft
|
||||
while (!events.some(e => e.repo === 'M_Viper/langsam' && e.bytes > 0)) {
|
||||
if (Date.now() - started > 10000) throw new Error('Download startete nicht');
|
||||
await new Promise(r => setTimeout(r, 50));
|
||||
}
|
||||
const state = await call('repo-backup-get-config', {});
|
||||
assert.strictEqual(state.running, true);
|
||||
assert.strictEqual(state.progress.repo, 'M_Viper/langsam');
|
||||
|
||||
assert.strictEqual((await call('repo-backup-cancel', {})).ok, true);
|
||||
const r = await running;
|
||||
assert.ok(Date.now() - started < 2500, 'Abbruch muss den Download sofort beenden');
|
||||
assert.strictEqual(r.ok, false);
|
||||
assert.strictEqual(r.summary.cancelled, true);
|
||||
assert.strictEqual(r.summary.ok, 1);
|
||||
assert.strictEqual(r.summary.skipped, 2);
|
||||
// keine halbe Datei zurücklassen
|
||||
const archiv = path.join(target, 'M_Viper', 'langsam-archiv');
|
||||
assert.deepStrictEqual(fs.existsSync(archiv) ? fs.readdirSync(archiv) : [], []);
|
||||
assert.strictEqual((await call('repo-backup-cancel', {})).ok, false);
|
||||
} finally {
|
||||
g.server.close();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert');
|
||||
const http = require('node:http');
|
||||
const { deleteGiteaPathsBatched } = require('../src/git/apiHandler.js');
|
||||
|
||||
// Minimaler Gitea-Nachbau: Repo-Info, Trees-API (seitenweise) und Mehrfach-Commit-Endpunkt.
|
||||
function fakeGitea(paths, { perPage = 1000, failPath = null } = {}) {
|
||||
const files = new Map(paths.map((p, i) => [p, `sha${i}`]));
|
||||
const commits = [];
|
||||
const server = http.createServer((req, res) => {
|
||||
const u = new URL(req.url, 'http://x');
|
||||
const send = (code, obj) => { res.statusCode = code; res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify(obj)); };
|
||||
if (req.method === 'GET' && u.pathname === '/api/v1/repos/o/r') return send(200, { default_branch: 'main' });
|
||||
if (req.method === 'GET' && u.pathname === '/api/v1/repos/o/r/git/trees/main') {
|
||||
const page = Number(u.searchParams.get('page') || 1);
|
||||
const all = [...files.keys()].map(p => ({ path: p, type: 'blob', sha: files.get(p) }));
|
||||
const slice = all.slice((page - 1) * perPage, page * perPage);
|
||||
return send(200, { tree: slice, truncated: page * perPage < all.length, total_count: all.length, page });
|
||||
}
|
||||
if (req.method === 'POST' && u.pathname === '/api/v1/repos/o/r/contents') {
|
||||
let body = '';
|
||||
req.on('data', c => body += c);
|
||||
req.on('end', () => {
|
||||
const { files: ops } = JSON.parse(body);
|
||||
if (failPath && ops.some(o => o.path === failPath)) return send(422, { message: 'kaputt' });
|
||||
for (const o of ops) {
|
||||
assert.strictEqual(o.operation, 'delete');
|
||||
assert.strictEqual(o.sha, files.get(o.path));
|
||||
files.delete(o.path);
|
||||
}
|
||||
commits.push(ops.length);
|
||||
send(201, {});
|
||||
});
|
||||
return;
|
||||
}
|
||||
send(404, { message: 'object does not exist' });
|
||||
});
|
||||
return new Promise(r => server.listen(0, '127.0.0.1', () => r({ server, files, commits, url: `http://127.0.0.1:${server.address().port}` })));
|
||||
}
|
||||
|
||||
test('Ordner wird paketweise gelöscht, Nachbarn bleiben', async () => {
|
||||
const paths = [...Array.from({ length: 2500 }, (_, i) => `mobile/build/f${i}.dex`), 'mobile/build.gradle.kts', 'mobile/src/A.kt'];
|
||||
const g = await fakeGitea(paths, { perPage: 1000 });
|
||||
try {
|
||||
const r = await deleteGiteaPathsBatched({ token: 't', url: g.url, owner: 'o', repo: 'r', prefix: 'mobile/build' });
|
||||
assert.strictEqual(r.deleted, 2500);
|
||||
assert.deepStrictEqual(g.commits, Array(25).fill(100));
|
||||
assert.deepStrictEqual([...g.files.keys()].sort(), ['mobile/build.gradle.kts', 'mobile/src/A.kt']);
|
||||
} finally { g.server.close(); }
|
||||
});
|
||||
|
||||
test('eine fehlerhafte Datei blockiert nicht das ganze Paket', async () => {
|
||||
const paths = Array.from({ length: 10 }, (_, i) => `x/f${i}`);
|
||||
const g = await fakeGitea(paths, { failPath: 'x/f3' });
|
||||
try {
|
||||
const r = await deleteGiteaPathsBatched({ token: 't', url: g.url, owner: 'o', repo: 'r', paths });
|
||||
assert.strictEqual(r.deleted, 9);
|
||||
assert.deepStrictEqual(r.failed.map(f => f.path), ['x/f3']);
|
||||
} finally { g.server.close(); }
|
||||
});
|
||||
@@ -0,0 +1,162 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert');
|
||||
const http = require('node:http');
|
||||
const fs = require('node:fs');
|
||||
const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
const { spawnSync } = require('node:child_process');
|
||||
|
||||
require('../src/git/gitBinary.js').useBundledGit();
|
||||
const { registerFeatureIpc, parsePorcelainZ } = require('../src/features/featuresIpc.js');
|
||||
const { scanForSecrets, checkName } = require('../src/features/secretScan.js');
|
||||
|
||||
// Minimaler Gitea-Nachbau für Diagnose, PRs/Issues und Backup
|
||||
function fakeGitea() {
|
||||
const calls = [];
|
||||
const comments = [];
|
||||
const server = http.createServer((req, res) => {
|
||||
const u = new URL(req.url, 'http://x');
|
||||
calls.push(`${req.method} ${u.pathname}`);
|
||||
const send = (code, obj) => { res.statusCode = code; res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify(obj)); };
|
||||
let body = '';
|
||||
req.on('data', c => body += c);
|
||||
req.on('end', () => {
|
||||
const p = u.pathname;
|
||||
if (p === '/api/v1/version') return send(200, { version: '1.24.0' });
|
||||
if (p === '/api/v1/user') return send(200, { login: 'M_Viper' });
|
||||
if (p === '/api/v1/user/repos') {
|
||||
if (u.searchParams.get('page') && u.searchParams.get('page') !== '1') return send(200, []);
|
||||
return send(200, [{ name: 'deskhub', owner: { login: 'M_Viper' }, default_branch: 'main', empty: false,
|
||||
clone_url: `http://127.0.0.1:${server.address().port}/M_Viper/deskhub.git` }]);
|
||||
}
|
||||
if (p === '/api/v1/repos/M_Viper/deskhub/pulls') return send(200, [{ number: 3, title: 'Fix', state: 'open', user: { login: 'a' }, labels: [], head: { label: 'fix' }, base: { label: 'main' }, mergeable: true }]);
|
||||
if (p === '/api/v1/repos/M_Viper/deskhub/issues' && req.method === 'GET') return send(200, [{ number: 5, title: 'Bug', state: 'open', labels: [{ name: 'bug', color: 'ff0000' }] }]);
|
||||
if (p === '/api/v1/repos/M_Viper/deskhub/issues' && req.method === 'POST') return send(201, { number: 6, ...JSON.parse(body), state: 'open', labels: [] });
|
||||
if (p === '/api/v1/repos/M_Viper/deskhub/issues/5/comments' && req.method === 'POST') { comments.push(JSON.parse(body).body); return send(201, {}); }
|
||||
if (p === '/api/v1/repos/M_Viper/deskhub/issues/5/comments') return send(200, comments.map((b, i) => ({ id: i, body: b, user: { login: 'M_Viper' } })));
|
||||
if (p === '/api/v1/repos/M_Viper/deskhub/issues/5') return send(200, { number: 5, title: 'Bug', state: 'open', labels: [] });
|
||||
if (p === '/api/v1/repos/M_Viper/deskhub/pulls/3/merge') return send(200, {});
|
||||
if (p === '/api/v1/repos/M_Viper/deskhub/archive/main.zip') {
|
||||
res.statusCode = 200; res.setHeader('Content-Type', 'application/zip'); return res.end(Buffer.from('PK\u0003\u0004fake'));
|
||||
}
|
||||
send(404, { message: 'not found' });
|
||||
});
|
||||
});
|
||||
return new Promise(r => server.listen(0, '127.0.0.1', () => r({ server, calls, comments, url: `http://127.0.0.1:${server.address().port}` })));
|
||||
}
|
||||
|
||||
function setup(url) {
|
||||
const handlers = {};
|
||||
const userData = fs.mkdtempSync(path.join(os.tmpdir(), 'gm-ud-'));
|
||||
registerFeatureIpc({
|
||||
ipcMain: { handle: (name, fn) => { handlers[name] = fn; } },
|
||||
app: { getPath: () => userData },
|
||||
readCredentials: () => ({ giteaURL: url, giteaToken: 'geheim123' }),
|
||||
buildItemsFromPaths: async (paths) => ({
|
||||
items: paths.map(p => ({ localFile: p, targetPath: path.basename(p) })), skipped: [], ignored: []
|
||||
}),
|
||||
clearGitTransportBlock: () => false
|
||||
});
|
||||
const call = (name, data) => handlers[name]({ sender: { send() {} } }, data);
|
||||
return { call, userData };
|
||||
}
|
||||
|
||||
test('Geheimnisse werden an Name und Inhalt erkannt, Vorlagen nicht', async () => {
|
||||
assert.ok(checkName('google-services.json'));
|
||||
assert.ok(checkName('.env'));
|
||||
assert.ok(checkName('release.keystore'));
|
||||
assert.strictEqual(checkName('.env.example'), null);
|
||||
assert.strictEqual(checkName('README.md'), null);
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gm-sec-'));
|
||||
const bot = path.join(dir, 'bot.js');
|
||||
fs.writeFileSync(bot, 'const TOKEN = "123456789:AA' + 'b'.repeat(33) + '";');
|
||||
const clean = path.join(dir, 'app.js');
|
||||
fs.writeFileSync(clean, 'console.log("hallo")');
|
||||
const found = await scanForSecrets([{ localFile: bot, size: fs.statSync(bot).size }, { localFile: clean, size: fs.statSync(clean).size }]);
|
||||
assert.match(found.get(bot), /Telegram/);
|
||||
assert.strictEqual(found.has(clean), false);
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test('git status -z wird korrekt gelesen (inkl. Umbenennung)', () => {
|
||||
const files = parsePorcelainZ(' M src/a.js\0?? neu.txt\0R neu/name.js\0alt/name.js\0D weg.txt\0');
|
||||
assert.deepStrictEqual(files.map(f => [f.code, f.path, f.from]), [['M', 'src/a.js', null], ['?', 'neu.txt', null], ['R', 'neu/name.js', 'alt/name.js'], ['D', 'weg.txt', null]]);
|
||||
});
|
||||
|
||||
test('Lokale Änderungen: Status, Diff und Commit nur der ausgewählten Dateien', async () => {
|
||||
const { call } = setup('http://127.0.0.1:1');
|
||||
const repo = fs.mkdtempSync(path.join(os.tmpdir(), 'gm-local-'));
|
||||
const git = (...a) => spawnSync('git', ['-C', repo, '-c', 'user.name=t', '-c', 'user.email=t@t', ...a], { encoding: 'utf8' });
|
||||
git('init', '-q');
|
||||
fs.writeFileSync(path.join(repo, 'a.txt'), 'eins\n');
|
||||
git('add', '.'); git('commit', '-qm', 'start');
|
||||
fs.writeFileSync(path.join(repo, 'a.txt'), 'zwei\n');
|
||||
fs.writeFileSync(path.join(repo, 'b.txt'), 'neu\n');
|
||||
|
||||
const st = await call('local-git', { action: 'status', folder: repo });
|
||||
assert.strictEqual(st.ok, true);
|
||||
assert.deepStrictEqual(st.files.map(f => f.path).sort(), ['a.txt', 'b.txt']);
|
||||
|
||||
const diff = await call('local-git', { action: 'diff', folder: repo, file: 'a.txt', code: 'M' });
|
||||
assert.match(diff.diff, /-eins/);
|
||||
assert.match(diff.diff, /\+zwei/);
|
||||
|
||||
const c = await call('local-git', { action: 'commit', folder: repo, files: ['a.txt'], message: 'nur a' });
|
||||
assert.strictEqual(c.ok, true, c.error);
|
||||
const after = await call('local-git', { action: 'status', folder: repo });
|
||||
assert.deepStrictEqual(after.files.map(f => f.path), ['b.txt']);
|
||||
|
||||
const notRepo = await call('local-git', { action: 'status', folder: os.tmpdir() + path.sep + 'gm-gibtsnicht-' + Date.now() });
|
||||
assert.strictEqual(notRepo.ok, false);
|
||||
fs.rmSync(repo, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test('Upload-Vorschau markiert Geheimnisse', async () => {
|
||||
const { call } = setup('http://127.0.0.1:1');
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gm-prev-'));
|
||||
const a = path.join(dir, 'main.kt'); fs.writeFileSync(a, 'fun main() {}');
|
||||
const g = path.join(dir, 'google-services.json'); fs.writeFileSync(g, '{}');
|
||||
const r = await call('preview-upload-paths', { paths: [a, g], destPath: '' });
|
||||
assert.strictEqual(r.ok, true);
|
||||
assert.strictEqual(r.secretCount, 1);
|
||||
assert.ok(r.files.find(f => f.localFile === g).secret);
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test('Diagnose, PRs/Issues und Backup gegen einen nachgebauten Server', async () => {
|
||||
const g = await fakeGitea();
|
||||
const { call } = setup(g.url);
|
||||
try {
|
||||
const d = await call('run-server-diagnosis', {});
|
||||
const byLabel = Object.fromEntries(d.checks.map(c => [c.label, c.status]));
|
||||
assert.strictEqual(byLabel['Gitea-API'], 'ok');
|
||||
assert.strictEqual(byLabel['Token'], 'ok');
|
||||
assert.strictEqual(byLabel['Erreichbar über IPv4'], 'ok');
|
||||
assert.ok(!JSON.stringify(d).includes('geheim123'), 'Token darf nicht in der Diagnose stehen');
|
||||
|
||||
const pulls = await call('gitea-collab', { action: 'listPulls', owner: 'M_Viper', repo: 'deskhub' });
|
||||
assert.strictEqual(pulls.items[0].head, 'fix');
|
||||
const issues = await call('gitea-collab', { action: 'listIssues', owner: 'M_Viper', repo: 'deskhub' });
|
||||
assert.strictEqual(issues.items[0].labels[0].name, 'bug');
|
||||
assert.strictEqual((await call('gitea-collab', { action: 'comment', owner: 'M_Viper', repo: 'deskhub', index: 5, body: 'Danke' })).ok, true);
|
||||
const detail = await call('gitea-collab', { action: 'get', owner: 'M_Viper', repo: 'deskhub', index: 5 });
|
||||
assert.strictEqual(detail.comments[0].body, 'Danke');
|
||||
assert.strictEqual((await call('gitea-collab', { action: 'merge', owner: 'M_Viper', repo: 'deskhub', index: 3, style: 'squash' })).ok, true);
|
||||
assert.strictEqual((await call('gitea-collab', { action: 'createIssue', owner: 'M_Viper', repo: 'deskhub', title: 'Neu' })).item.number, 6);
|
||||
|
||||
const target = fs.mkdtempSync(path.join(os.tmpdir(), 'gm-bk-'));
|
||||
await call('repo-backup-set-config', { targetDir: target, interval: 'daily', keep: 2 });
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const r = await call('repo-backup-run-now', {});
|
||||
assert.strictEqual(r.summary.ok, 1, JSON.stringify(r));
|
||||
await new Promise(res => setTimeout(res, 61000 / 60)); // eindeutige Zeitstempel
|
||||
}
|
||||
const zips = fs.readdirSync(path.join(target, 'M_Viper', 'deskhub-archiv'));
|
||||
assert.ok(zips.length <= 3 && zips.length >= 1);
|
||||
const state = await call('repo-backup-get-config', {});
|
||||
assert.strictEqual(state.config.lastResult.total, 1);
|
||||
fs.rmSync(target, { recursive: true, force: true });
|
||||
} finally {
|
||||
g.server.close();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert');
|
||||
const { listTemplates, buildGitignore } = require('../src/features/gitignoreTemplates.js');
|
||||
|
||||
test('listTemplates nennt alle Vorlagen mit Beschriftung', () => {
|
||||
const list = listTemplates();
|
||||
assert.deepStrictEqual(list.map(t => t.key), ['node', 'dotnet', 'electron', 'python', 'java', 'php', 'unity']);
|
||||
assert.ok(list.every(t => t.label));
|
||||
});
|
||||
|
||||
test('buildGitignore: Vorlagen für Python, Java, PHP und Unity enthalten ihre Kerneinträge', () => {
|
||||
assert.match(buildGitignore(['python']), /__pycache__\//);
|
||||
assert.match(buildGitignore(['java']), /target\//);
|
||||
assert.match(buildGitignore(['php']), /vendor\//);
|
||||
assert.match(buildGitignore(['unity']), /\[Ll\]ibrary\//);
|
||||
});
|
||||
|
||||
test('buildGitignore enthält immer den allgemeinen Teil', () => {
|
||||
const out = buildGitignore([]);
|
||||
assert.match(out, /\.env/);
|
||||
assert.doesNotMatch(out, /node_modules/);
|
||||
});
|
||||
|
||||
test('buildGitignore fügt gewählte Vorlagen genau einmal hinzu', () => {
|
||||
const out = buildGitignore(['node', 'node', 'electron']);
|
||||
assert.strictEqual(out.split('node_modules/').length - 1, 1);
|
||||
assert.match(out, /dist\//);
|
||||
assert.doesNotMatch(out, /\.vs\//);
|
||||
});
|
||||
|
||||
test('buildGitignore ignoriert unbekannte Schlüssel', () => {
|
||||
assert.doesNotThrow(() => buildGitignore(['gibt-es-nicht', '__proto__', null]));
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert');
|
||||
const http = require('node:http');
|
||||
const { bareHostname } = require('../src/utils/helpers.js');
|
||||
const { checkGiteaConnection } = require('../src/git/apiHandler.js');
|
||||
|
||||
function listen(host) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const server = http.createServer((req, res) => {
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify({ version: '1.22.0' }));
|
||||
});
|
||||
server.once('error', reject);
|
||||
server.listen(0, host, () => resolve(server));
|
||||
});
|
||||
}
|
||||
|
||||
test('bareHostname entfernt Klammern nur bei IPv6-Literalen', () => {
|
||||
assert.strictEqual(bareHostname(new URL('http://[2001:db8::1]:3000').hostname), '2001:db8::1');
|
||||
assert.strictEqual(bareHostname('[::1]'), '::1');
|
||||
assert.strictEqual(bareHostname('127.0.0.1'), '127.0.0.1');
|
||||
assert.strictEqual(bareHostname('git.example.com'), 'git.example.com');
|
||||
assert.strictEqual(bareHostname(undefined), '');
|
||||
});
|
||||
|
||||
test('Gitea-Verbindung über IPv4', async () => {
|
||||
const server = await listen('127.0.0.1');
|
||||
try {
|
||||
const r = await checkGiteaConnection({ url: `http://127.0.0.1:${server.address().port}` });
|
||||
assert.strictEqual(r.ok, true);
|
||||
} finally { server.close(); }
|
||||
});
|
||||
|
||||
test('Gitea-Verbindung über IPv6', async (t) => {
|
||||
let server;
|
||||
try { server = await listen('::1'); } catch { return t.skip('IPv6-Loopback nicht verfügbar'); }
|
||||
try {
|
||||
const r = await checkGiteaConnection({ url: `http://[::1]:${server.address().port}` });
|
||||
assert.strictEqual(r.ok, true);
|
||||
} finally { server.close(); }
|
||||
});
|
||||
@@ -0,0 +1,77 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert');
|
||||
const { createPauseGate } = require('../src/features/pauseGate.js');
|
||||
|
||||
const tick = () => new Promise((r) => setTimeout(r, 20));
|
||||
|
||||
test('Ohne Pause läuft wait() sofort durch', async () => {
|
||||
const gate = createPauseGate();
|
||||
await gate.wait();
|
||||
await gate.wait(new AbortController().signal);
|
||||
assert.strictEqual(gate.isPaused(), false);
|
||||
});
|
||||
|
||||
test('Während der Pause bleibt wait() stehen und läuft nach resume() weiter', async () => {
|
||||
const gate = createPauseGate();
|
||||
gate.pause();
|
||||
let done = false;
|
||||
const p = gate.wait().then(() => { done = true; });
|
||||
await tick();
|
||||
assert.strictEqual(done, false);
|
||||
assert.strictEqual(gate.waitingCount(), 1);
|
||||
gate.resume();
|
||||
await p;
|
||||
assert.strictEqual(done, true);
|
||||
assert.strictEqual(gate.waitingCount(), 0);
|
||||
});
|
||||
|
||||
test('resume() gibt alle Wartenden frei', async () => {
|
||||
const gate = createPauseGate();
|
||||
gate.pause();
|
||||
const all = Promise.all([gate.wait(), gate.wait(), gate.wait()]);
|
||||
await tick();
|
||||
assert.strictEqual(gate.waitingCount(), 3);
|
||||
gate.resume();
|
||||
await all;
|
||||
});
|
||||
|
||||
test('Abbruch während der Pause löst wait() mit dem Abbruchfehler aus', async () => {
|
||||
const gate = createPauseGate();
|
||||
gate.pause();
|
||||
const c = new AbortController();
|
||||
const p = gate.wait(c.signal);
|
||||
await tick();
|
||||
c.abort();
|
||||
await assert.rejects(p, (e) => e.code === 'UPLOAD_CANCELLED');
|
||||
assert.strictEqual(gate.waitingCount(), 0);
|
||||
});
|
||||
|
||||
test('Bereits abgebrochenes Signal wirft sofort, auch ohne Pause', async () => {
|
||||
const gate = createPauseGate();
|
||||
const c = new AbortController();
|
||||
c.abort();
|
||||
await assert.rejects(gate.wait(c.signal), (e) => e.code === 'UPLOAD_CANCELLED');
|
||||
});
|
||||
|
||||
test('onChange meldet nur echte Wechsel', () => {
|
||||
const states = [];
|
||||
const gate = createPauseGate({ onChange: (p) => states.push(p) });
|
||||
assert.strictEqual(gate.pause(), true);
|
||||
assert.strictEqual(gate.pause(), false);
|
||||
assert.strictEqual(gate.resume(), true);
|
||||
assert.strictEqual(gate.resume(), false);
|
||||
assert.deepStrictEqual(states, [true, false]);
|
||||
});
|
||||
|
||||
test('Ein abgebrochener Wartender blockiert die übrigen nicht', async () => {
|
||||
const gate = createPauseGate();
|
||||
gate.pause();
|
||||
const c = new AbortController();
|
||||
const a = gate.wait(c.signal).catch((e) => e.code);
|
||||
const b = gate.wait();
|
||||
await tick();
|
||||
c.abort();
|
||||
assert.strictEqual(await a, 'UPLOAD_CANCELLED');
|
||||
gate.resume();
|
||||
await b;
|
||||
});
|
||||
@@ -0,0 +1,76 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert');
|
||||
const fs = require('node:fs');
|
||||
const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
const { spawnSync } = require('node:child_process');
|
||||
|
||||
const { createPrefs, DEFAULTS } = require('../src/features/appPrefs.js');
|
||||
const { useBundledGit, applyGitIdentity, commitIdentity } = require('../src/git/gitBinary.js');
|
||||
const { createRepoBackup, excludeMatcher } = require('../src/backup/GiteaRepoBackup.js');
|
||||
|
||||
const tmp = (p) => fs.mkdtempSync(path.join(os.tmpdir(), p));
|
||||
|
||||
test('Einstellungen: Standardwerte, Bereinigung und ungültige E-Mail', () => {
|
||||
const file = path.join(tmp('gm-prefs-'), 'app-settings.json');
|
||||
const prefs = createPrefs(file);
|
||||
assert.deepStrictEqual(prefs.get(), DEFAULTS);
|
||||
|
||||
const r = prefs.set({ zoom: 400, theme: 'pink', closeAction: 'tray', gitName: 'Max <x>\n', unbekannt: 1, uploadSecretScan: 0 });
|
||||
assert.strictEqual(r.prefs.zoom, 150);
|
||||
assert.strictEqual(r.prefs.theme, 'dark');
|
||||
assert.strictEqual(r.prefs.closeAction, 'tray');
|
||||
assert.strictEqual(r.prefs.gitName, 'Max x');
|
||||
assert.strictEqual(r.prefs.uploadSecretScan, false);
|
||||
assert.ok(!('unbekannt' in r.prefs));
|
||||
|
||||
const bad = prefs.set({ gitEmail: 'keine-mail' });
|
||||
assert.deepStrictEqual(bad.rejected, ['gitEmail']);
|
||||
assert.strictEqual(bad.prefs.gitEmail, '');
|
||||
|
||||
// nach dem Neuladen noch da
|
||||
assert.strictEqual(createPrefs(file).get().closeAction, 'tray');
|
||||
});
|
||||
|
||||
test('Git-Identität greift nur als Rückfallwert und verschwindet beim Leeren', () => {
|
||||
useBundledGit();
|
||||
const userData = tmp('gm-ud-');
|
||||
const home = tmp('gm-home-');
|
||||
const env = { ...process.env, HOME: home, USERPROFILE: home };
|
||||
const ident = () => spawnSync('git', ['var', 'GIT_AUTHOR_IDENT'], { encoding: 'utf8', env: { ...env, GIT_CONFIG_SYSTEM: process.env.GIT_CONFIG_SYSTEM || '' } }).stdout;
|
||||
|
||||
assert.strictEqual(applyGitIdentity({ name: 'Test Name', email: 'test@example.com' }, userData), true);
|
||||
assert.match(ident(), /^Test Name <test@example\.com>/);
|
||||
|
||||
// Eine globale Einstellung hat Vorrang
|
||||
fs.writeFileSync(path.join(home, '.gitconfig'), '[user]\n\tname = Global\n\temail = global@example.com\n');
|
||||
assert.match(ident(), /^Global <global@example\.com>/);
|
||||
|
||||
applyGitIdentity({ name: '', email: '' }, userData);
|
||||
assert.strictEqual(process.env.GIT_CONFIG_SYSTEM, undefined);
|
||||
assert.ok(!fs.existsSync(path.join(userData, 'git-system.gitconfig')));
|
||||
assert.deepStrictEqual(commitIdentity({ gitName: '', gitEmail: '' }), { name: 'Git Manager GUI', email: 'gui@gitmanager.local' });
|
||||
});
|
||||
|
||||
test('Backup: Ausschlussmuster und Zeitpunkt für automatische Backups', () => {
|
||||
const m = excludeMatcher('test-*\nM_Viper/Alt?\n# Kommentar\nmy.repo');
|
||||
assert.strictEqual(m('M_Viper/test-1'), true);
|
||||
assert.strictEqual(m('M_Viper/Alt1'), true);
|
||||
assert.strictEqual(m('M_Viper/Alt12'), false);
|
||||
assert.strictEqual(m('x/my.repo'), true);
|
||||
assert.strictEqual(m('x/myXrepo'), false);
|
||||
|
||||
const configPath = path.join(tmp('gm-bk-'), 'repo-backup.json');
|
||||
const backup = createRepoBackup({ configPath, getContext() {}, giteaCall() {}, runGit() {}, isPackFailure() {}, redact: (s) => s });
|
||||
backup.setConfig({ targetDir: 'X:/irgendwo', interval: 'daily', time: '03:00' });
|
||||
const at = (s) => new Date(s);
|
||||
// noch nie gelaufen: vor 03:00 nicht, danach schon
|
||||
assert.strictEqual(backup.isDue(at('2026-10-22T02:59:00')), true, 'Slot von gestern 03:00 ist offen');
|
||||
fs.writeFileSync(configPath, JSON.stringify({ ...JSON.parse(fs.readFileSync(configPath, 'utf8')), lastRun: '2026-10-22T03:05:00' }));
|
||||
assert.strictEqual(backup.isDue(at('2026-10-22T20:00:00')), false, 'heute schon gelaufen');
|
||||
assert.strictEqual(backup.isDue(at('2026-10-23T02:59:00')), false, 'vor 03:00 noch nicht');
|
||||
assert.strictEqual(backup.isDue(at('2026-10-23T03:00:00')), true, 'ab 03:00 wieder fällig');
|
||||
|
||||
backup.setConfig({ time: '25:99' });
|
||||
assert.strictEqual(backup.getState().config.time, '');
|
||||
});
|
||||
@@ -0,0 +1,64 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert');
|
||||
const {
|
||||
parseVersionFromFileName, compareVersions, parseSidecar, checkNewTag, buildReleaseBody
|
||||
} = require('../src/features/releaseAssist.js');
|
||||
|
||||
const HASH = '5ca7f0c680b6d73a92eadb5e90f8164f7f3fa52071ffaac54803b0e5ec95f35d';
|
||||
|
||||
test('Version aus dem Dateinamen', () => {
|
||||
assert.strictEqual(parseVersionFromFileName('Git-Manager-GUI-Setup-2.2.1.exe'), '2.2.1');
|
||||
assert.strictEqual(parseVersionFromFileName('Git Manager GUI Setup 2.1.10.exe'), '2.1.10');
|
||||
assert.strictEqual(parseVersionFromFileName('setup.exe'), null);
|
||||
assert.strictEqual(parseVersionFromFileName(null), null);
|
||||
});
|
||||
|
||||
test('Versionen werden numerisch verglichen, nicht als Text', () => {
|
||||
assert.strictEqual(compareVersions('2.2.1', '2.2.0'), 1);
|
||||
assert.strictEqual(compareVersions('v2.10.0', 'v2.9.9'), 1);
|
||||
assert.strictEqual(compareVersions('2.2.1', 'v2.2.1'), 0);
|
||||
assert.strictEqual(compareVersions('2.1.9', '2.2.0'), -1);
|
||||
assert.deepStrictEqual(['v2.10.0', 'v2.9.0', 'v2.2.1'].sort(compareVersions), ['v2.2.1', 'v2.9.0', 'v2.10.0']);
|
||||
});
|
||||
|
||||
test('Sidecar-Datei: Hash wird gefunden, auch mit Großbuchstaben', () => {
|
||||
assert.strictEqual(parseSidecar(`${HASH} Git-Manager-GUI-Setup-2.2.1.exe\n`), HASH);
|
||||
assert.strictEqual(parseSidecar(HASH.toUpperCase()), HASH);
|
||||
assert.strictEqual(parseSidecar('kein Hash'), null);
|
||||
assert.strictEqual(parseSidecar('abc123'), null);
|
||||
});
|
||||
|
||||
test('Neuer Tag: Format, Duplikat und Reihenfolge werden geprüft', () => {
|
||||
const releases = [{ tag_name: 'v2.2.0' }, { tag_name: 'v2.1.9' }];
|
||||
assert.strictEqual(checkNewTag('v2.2.1', releases).ok, true);
|
||||
assert.strictEqual(checkNewTag('2.2.1', releases).ok, true);
|
||||
assert.strictEqual(checkNewTag('v2.10.0', releases).ok, true);
|
||||
|
||||
assert.match(checkNewTag('release-2', releases).reason, /Version wie v2\.2\.1/);
|
||||
assert.match(checkNewTag('v2.2.0', releases).reason, /gibt es schon/);
|
||||
assert.match(checkNewTag('V2.2.0', releases).reason, /gibt es schon/);
|
||||
assert.match(checkNewTag('v2.1.5', releases).reason, /nicht neuer/);
|
||||
});
|
||||
|
||||
test('Neuer Tag: ohne vorhandene Releases ist jede Version erlaubt', () => {
|
||||
assert.strictEqual(checkNewTag('v1.0.0', []).ok, true);
|
||||
assert.strictEqual(checkNewTag('v1.0.0', null).ok, true);
|
||||
assert.strictEqual(checkNewTag('v1.0.0', [{ tag_name: 'nightly' }]).ok, true);
|
||||
});
|
||||
|
||||
test('Release-Text: Prüfsummen im Format, das der Updater liest', () => {
|
||||
const body = buildReleaseBody('## Neu\n\n- Abbrechen\n', [{ name: 'Git-Manager-GUI-Setup-2.2.1.exe', sha256: HASH.toUpperCase() }]);
|
||||
assert.match(body, /^## Neu/);
|
||||
assert.ok(body.includes(`- Git-Manager-GUI-Setup-2.2.1.exe: ${HASH}`));
|
||||
|
||||
// genau so liest updater.js die Zeile
|
||||
const line = body.split('\n').find((l) => l.toLowerCase().includes('git-manager-gui-setup-2.2.1.exe') && /\b[a-f0-9]{64}\b/i.test(l));
|
||||
assert.ok(line);
|
||||
assert.strictEqual(line.match(/\b([a-fA-F0-9]{64})\b/)[1], HASH);
|
||||
});
|
||||
|
||||
test('Release-Text: ungültige Prüfsummen und leere Notizen', () => {
|
||||
assert.strictEqual(buildReleaseBody('Nur Text', [{ name: 'a.exe', sha256: 'zu kurz' }]), 'Nur Text');
|
||||
assert.strictEqual(buildReleaseBody('', []), '');
|
||||
assert.ok(buildReleaseBody('', [{ name: 'a.exe', sha256: HASH }]).startsWith('### Prüfsummen'));
|
||||
});
|
||||
@@ -0,0 +1,201 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert');
|
||||
const http = require('node:http');
|
||||
const fs = require('node:fs');
|
||||
const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
const { spawnSync } = require('node:child_process');
|
||||
const archiver = require('archiver');
|
||||
|
||||
require('../src/git/gitBinary.js').useBundledGit();
|
||||
const { registerFeatureIpc } = require('../src/features/featuresIpc.js');
|
||||
|
||||
const git = (cwd, ...a) => spawnSync('git', ['-c', 'user.name=t', '-c', 'user.email=t@t', ...a], { cwd, encoding: 'utf8' });
|
||||
const tmp = (p) => fs.mkdtempSync(path.join(os.tmpdir(), p));
|
||||
|
||||
function makeZip(file, entries) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const out = fs.createWriteStream(file);
|
||||
const zip = archiver('zip');
|
||||
out.on('close', resolve);
|
||||
zip.on('error', reject);
|
||||
zip.pipe(out);
|
||||
for (const [name, content] of entries) zip.append(content, { name });
|
||||
zip.finalize();
|
||||
});
|
||||
}
|
||||
|
||||
// Backup-Ordner wie von GiteaRepoBackup angelegt: Spiegel + ZIP-Stände
|
||||
async function makeBackupDir() {
|
||||
const work = tmp('gm-src-');
|
||||
git(work, 'init', '-q', '-b', 'main');
|
||||
fs.writeFileSync(path.join(work, 'a.txt'), 'hallo');
|
||||
git(work, 'add', '.'); git(work, 'commit', '-qm', 'eins');
|
||||
git(work, 'branch', 'feature'); git(work, 'tag', 'v1.0');
|
||||
const dir = tmp('gm-bk-');
|
||||
fs.mkdirSync(path.join(dir, 'M_Viper'));
|
||||
git(dir, 'clone', '-q', '--mirror', work, path.join(dir, 'M_Viper', 'deskhub.git'));
|
||||
const zipDir = path.join(dir, 'M_Viper', 'deskhub-archiv');
|
||||
fs.mkdirSync(zipDir);
|
||||
await makeZip(path.join(zipDir, 'deskhub-2026-10-01_10-00.zip'), [['deskhub/a.txt', 'alt'], ['deskhub/src/b.kt', 'fun b() {}']]);
|
||||
await makeZip(path.join(zipDir, 'deskhub-2026-10-20_10-00.zip'), [['deskhub/a.txt', 'neu'], ['deskhub/src/b.kt', 'fun b() {}']]);
|
||||
fs.utimesSync(path.join(zipDir, 'deskhub-2026-10-01_10-00.zip'), new Date('2026-10-01'), new Date('2026-10-01'));
|
||||
fs.writeFileSync(path.join(dir, 'backup-protokoll.json'), '{}');
|
||||
return { dir, work };
|
||||
}
|
||||
|
||||
// Gitea-Nachbau: neue Repos zeigen auf echte lokale Bare-Repos, damit der Push wirklich läuft
|
||||
function fakeGitea() {
|
||||
const created = [];
|
||||
const patches = [];
|
||||
const repoRoot = tmp('gm-remote-');
|
||||
const server = http.createServer((req, res) => {
|
||||
const u = new URL(req.url, 'http://x');
|
||||
const send = (code, obj) => { res.statusCode = code; res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify(obj)); };
|
||||
let body = '';
|
||||
req.on('data', c => body += c);
|
||||
req.on('end', () => {
|
||||
if (u.pathname === '/api/v1/user') return send(200, { login: 'M_Viper' });
|
||||
if (u.pathname === '/api/v1/user/repos' && req.method === 'POST') {
|
||||
const b = JSON.parse(body);
|
||||
if (b.name === 'deskhub') return send(409, { message: 'repository already exists' });
|
||||
const bare = path.join(repoRoot, `${b.name}.git`);
|
||||
spawnSync('git', ['init', '-q', '--bare', '-b', 'main', bare]);
|
||||
created.push(b);
|
||||
return send(201, { name: b.name, full_name: `M_Viper/${b.name}`, owner: { login: 'M_Viper' }, clone_url: bare, html_url: `http://gitea/M_Viper/${b.name}`, default_branch: 'main' });
|
||||
}
|
||||
if (u.pathname.startsWith('/api/v1/repos/M_Viper/') && req.method === 'PATCH') { patches.push(JSON.parse(body)); return send(200, {}); }
|
||||
send(404, { message: 'not found' });
|
||||
});
|
||||
});
|
||||
return new Promise(r => server.listen(0, '127.0.0.1', () => r({ server, created, patches, repoRoot, url: `http://127.0.0.1:${server.address().port}` })));
|
||||
}
|
||||
|
||||
function setup(url) {
|
||||
const handlers = {};
|
||||
const userData = tmp('gm-ud-');
|
||||
registerFeatureIpc({
|
||||
ipcMain: { handle: (name, fn) => { handlers[name] = fn; } },
|
||||
app: { getPath: () => userData },
|
||||
readCredentials: () => ({ giteaURL: url, giteaToken: 'geheim123' }),
|
||||
buildItemsFromPaths: async () => ({ items: [], skipped: [], ignored: [] }),
|
||||
clearGitTransportBlock: () => false
|
||||
});
|
||||
return (name, data) => handlers[name]({ sender: { send() {} } }, data);
|
||||
}
|
||||
|
||||
test('Sicherungen werden gefunden: Spiegel und ZIP-Stände, neuester zuerst', async () => {
|
||||
const { dir } = await makeBackupDir();
|
||||
const call = setup('http://127.0.0.1:1');
|
||||
const r = await call('repo-backup-list', { dir });
|
||||
assert.strictEqual(r.ok, true, r.error);
|
||||
assert.strictEqual(r.backups.length, 1);
|
||||
const b = r.backups[0];
|
||||
assert.deepStrictEqual([b.owner, b.repo], ['M_Viper', 'deskhub']);
|
||||
assert.ok(b.mirror);
|
||||
assert.deepStrictEqual(b.zips.map(z => z.file), ['deskhub-2026-10-20_10-00.zip', 'deskhub-2026-10-01_10-00.zip']);
|
||||
});
|
||||
|
||||
test('Lokal: Spiegel wird zur Arbeitskopie mit allen Branches, ZIP ohne Oberordner entpackt', async () => {
|
||||
const { dir } = await makeBackupDir();
|
||||
const call = setup('http://127.0.0.1:1');
|
||||
const { backups: [b] } = await call('repo-backup-list', { dir });
|
||||
|
||||
const dest1 = tmp('gm-dst-');
|
||||
const m = await call('repo-restore-local', { kind: 'mirror', source: b.mirror.path, destParent: dest1, repo: 'deskhub' });
|
||||
assert.strictEqual(m.ok, true, m.error);
|
||||
assert.strictEqual(fs.readFileSync(path.join(dest1, 'deskhub', 'a.txt'), 'utf8'), 'hallo');
|
||||
assert.match(git(path.join(dest1, 'deskhub'), 'branch', '-r').stdout, /feature/);
|
||||
|
||||
// Zweiter Versuch in denselben Ordner darf nichts überschreiben
|
||||
const again = await call('repo-restore-local', { kind: 'mirror', source: b.mirror.path, destParent: dest1, repo: 'deskhub' });
|
||||
assert.strictEqual(again.ok, false);
|
||||
assert.match(again.error, /existiert bereits/);
|
||||
|
||||
const dest2 = tmp('gm-dst-');
|
||||
const z = await call('repo-restore-local', { kind: 'zip', source: b.zips[0].path, destParent: dest2, repo: 'deskhub' });
|
||||
assert.strictEqual(z.ok, true, z.error);
|
||||
assert.strictEqual(z.files, 2);
|
||||
assert.strictEqual(fs.readFileSync(path.join(dest2, 'deskhub', 'a.txt'), 'utf8'), 'neu');
|
||||
assert.ok(fs.existsSync(path.join(dest2, 'deskhub', 'src', 'b.kt')));
|
||||
});
|
||||
|
||||
// ZIP von Hand bauen: archiver entschärft "../" schon beim Erzeugen, ein echter Angreifer nicht.
|
||||
function rawZip(file, entries) {
|
||||
const zlib = require('node:zlib');
|
||||
const locals = [];
|
||||
const centrals = [];
|
||||
let offset = 0;
|
||||
for (const [name, content] of entries) {
|
||||
const n = Buffer.from(name);
|
||||
const data = Buffer.from(content);
|
||||
const crc = zlib.crc32(data);
|
||||
const lh = Buffer.alloc(30);
|
||||
lh.writeUInt32LE(0x04034b50, 0); lh.writeUInt16LE(20, 4); lh.writeUInt32LE(crc, 14);
|
||||
lh.writeUInt32LE(data.length, 18); lh.writeUInt32LE(data.length, 22); lh.writeUInt16LE(n.length, 26);
|
||||
const ch = Buffer.alloc(46);
|
||||
ch.writeUInt32LE(0x02014b50, 0); ch.writeUInt16LE(20, 4); ch.writeUInt16LE(20, 6); ch.writeUInt32LE(crc, 16);
|
||||
ch.writeUInt32LE(data.length, 20); ch.writeUInt32LE(data.length, 24); ch.writeUInt16LE(n.length, 28); ch.writeUInt32LE(offset, 42);
|
||||
locals.push(lh, n, data);
|
||||
centrals.push(ch, n);
|
||||
offset += lh.length + n.length + data.length;
|
||||
}
|
||||
const cd = Buffer.concat(centrals);
|
||||
const end = Buffer.alloc(22);
|
||||
end.writeUInt32LE(0x06054b50, 0); end.writeUInt16LE(entries.length, 8); end.writeUInt16LE(entries.length, 10);
|
||||
end.writeUInt32LE(cd.length, 12); end.writeUInt32LE(offset, 16);
|
||||
fs.writeFileSync(file, Buffer.concat([...locals, cd, end]));
|
||||
}
|
||||
|
||||
test('Archive mit Pfaden außerhalb des Ziels werden abgewiesen', async () => {
|
||||
const d = tmp('gm-evil-');
|
||||
const zip = path.join(d, 'evil.zip');
|
||||
rawZip(zip, [['deskhub/ok.txt', 'ok'], ['deskhub/../../boese.txt', 'x']]);
|
||||
const unzipper = require('unzipper');
|
||||
const names = (await unzipper.Open.file(zip)).files.map(f => f.path);
|
||||
assert.ok(names.includes('deskhub/../../boese.txt'), 'Testarchiv muss den gefährlichen Pfad wirklich enthalten');
|
||||
const call = setup('http://127.0.0.1:1');
|
||||
const destParent = tmp('gm-dst-');
|
||||
const r = await call('repo-restore-local', { kind: 'zip', source: zip, destParent, repo: 'x' });
|
||||
assert.strictEqual(r.ok, false);
|
||||
assert.match(r.error, /Unsicherer Pfad/);
|
||||
assert.ok(!fs.existsSync(path.join(destParent, 'x', 'ok.txt')), 'auch harmlose Dateien dürfen dann nicht entpackt werden');
|
||||
assert.ok(!fs.existsSync(path.join(destParent, 'boese.txt')));
|
||||
});
|
||||
|
||||
test('Gitea: Spiegel und ZIP als neues Repo, Namenskonflikt und ZIP-Überschreiben werden abgefangen', async () => {
|
||||
const { dir } = await makeBackupDir();
|
||||
const g = await fakeGitea();
|
||||
try {
|
||||
const call = setup(g.url);
|
||||
const { backups: [b] } = await call('repo-backup-list', { dir });
|
||||
|
||||
const m = await call('repo-restore-gitea', { kind: 'mirror', source: b.mirror.path, owner: 'M_Viper', repo: 'deskhub', mode: 'new', newName: 'deskhub-wieder', isPrivate: true });
|
||||
assert.strictEqual(m.ok, true, m.error);
|
||||
const bare = path.join(g.repoRoot, 'deskhub-wieder.git');
|
||||
const refs = git(bare, 'for-each-ref', '--format=%(refname)').stdout;
|
||||
assert.match(refs, /refs\/heads\/main/);
|
||||
assert.match(refs, /refs\/heads\/feature/);
|
||||
assert.match(refs, /refs\/tags\/v1\.0/);
|
||||
assert.strictEqual(g.created[0].private, true);
|
||||
|
||||
const z = await call('repo-restore-gitea', { kind: 'zip', source: b.zips[0].path, owner: 'M_Viper', repo: 'deskhub', mode: 'new', newName: 'deskhub-zip' });
|
||||
assert.strictEqual(z.ok, true, z.error);
|
||||
assert.strictEqual(z.method, 'Git');
|
||||
const files = git(path.join(g.repoRoot, 'deskhub-zip.git'), 'ls-tree', '-r', '--name-only', 'main').stdout.trim().split('\n');
|
||||
assert.deepStrictEqual(files.sort(), ['a.txt', 'src/b.kt']);
|
||||
|
||||
const conflict = await call('repo-restore-gitea', { kind: 'mirror', source: b.mirror.path, owner: 'M_Viper', repo: 'deskhub', mode: 'new', newName: 'deskhub' });
|
||||
assert.strictEqual(conflict.ok, false);
|
||||
assert.match(conflict.error, /existiert bereits/);
|
||||
|
||||
const zipOverwrite = await call('repo-restore-gitea', { kind: 'zip', source: b.zips[0].path, owner: 'M_Viper', repo: 'deskhub', mode: 'overwrite' });
|
||||
assert.strictEqual(zipOverwrite.ok, false);
|
||||
assert.match(zipOverwrite.error, /nur mit einem Git-Spiegel/);
|
||||
|
||||
const badName = await call('repo-restore-gitea', { kind: 'zip', source: b.zips[0].path, owner: 'M_Viper', repo: 'deskhub', mode: 'new', newName: 'mit leerzeichen' });
|
||||
assert.strictEqual(badName.ok, false);
|
||||
} finally {
|
||||
g.server.close();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert');
|
||||
const { remainingMs, centeredBounds, MIN_SPIN_MS } = require('../src/features/splash.js');
|
||||
|
||||
test('Startanimation: wurde das Logo nie gezeigt, gibt es keine Wartezeit', () => {
|
||||
assert.strictEqual(remainingMs(0, 5000, 1600), 0);
|
||||
});
|
||||
|
||||
test('Startanimation: das Logo dreht sich mindestens minMs', () => {
|
||||
assert.strictEqual(remainingMs(1000, 1300, 1600), 1300);
|
||||
assert.strictEqual(remainingMs(1000, 1000, 1600), 1600);
|
||||
});
|
||||
|
||||
test('Startanimation: nach Ablauf von minMs wird nicht mehr gewartet', () => {
|
||||
assert.strictEqual(remainingMs(1000, 2600, 1600), 0);
|
||||
assert.strictEqual(remainingMs(1000, 9000, 1600), 0);
|
||||
});
|
||||
|
||||
test('Startanimation: Mindestdauer ist wie bei AeroMC 1,6 s', () => {
|
||||
assert.strictEqual(MIN_SPIN_MS, 1600);
|
||||
});
|
||||
|
||||
test('Startanimation: Fenster wird im Arbeitsbereich zentriert', () => {
|
||||
const b = centeredBounds({ x: 0, y: 0, width: 1920, height: 1040 }, 1200, 820);
|
||||
assert.deepStrictEqual(b, { x: 360, y: 110, width: 1200, height: 820 });
|
||||
});
|
||||
|
||||
test('Startanimation: Arbeitsbereich mit Versatz und mehrere Monitore', () => {
|
||||
const b = centeredBounds({ x: -1920, y: 40, width: 1920, height: 1000 }, 1200, 820);
|
||||
assert.deepStrictEqual(b, { x: -1560, y: 130, width: 1200, height: 820 });
|
||||
});
|
||||
|
||||
test('Startanimation: auf kleinen Bildschirmen wird das Fenster begrenzt', () => {
|
||||
const b = centeredBounds({ x: 0, y: 0, width: 1024, height: 700 }, 1200, 820);
|
||||
assert.deepStrictEqual(b, { x: 0, y: 0, width: 1024, height: 700 });
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert');
|
||||
const { describeTransfer } = require('../src/features/transferNotice.js');
|
||||
|
||||
test('Erfolg nennt Repo und Dateizahl', () => {
|
||||
assert.deepStrictEqual(describeTransfer({ status: 'ok', repo: 'a/b', files: 3 }), { title: 'Upload fertig', body: 'a/b: 3 Dateien hochgeladen.' });
|
||||
assert.match(describeTransfer({ status: 'ok', repo: 'a/b', files: 1 }).body, /1 Datei hochgeladen/);
|
||||
});
|
||||
|
||||
test('Erfolg ohne bekannte Dateizahl bleibt lesbar', () => {
|
||||
assert.strictEqual(describeTransfer({ status: 'ok', repo: 'a/b', files: null }).body, 'a/b: Upload hochgeladen.');
|
||||
});
|
||||
|
||||
test('Abgebrochene Uploads melden nichts', () => {
|
||||
assert.strictEqual(describeTransfer({ status: 'abgebrochen', repo: 'a/b' }), null);
|
||||
assert.strictEqual(describeTransfer(null), null);
|
||||
});
|
||||
|
||||
test('Fehler zeigt fehlgeschlagene Dateien oder den Fehlertext', () => {
|
||||
assert.strictEqual(describeTransfer({ status: 'fehler', repo: 'a/b', failed: 2 }).title, 'Upload fehlgeschlagen');
|
||||
assert.match(describeTransfer({ status: 'fehler', repo: 'a/b', failed: 2 }).body, /2 Dateien fehlgeschlagen/);
|
||||
assert.match(describeTransfer({ status: 'fehler', repo: 'a/b', error: 'Server nicht erreichbar' }).body, /Server nicht erreichbar/);
|
||||
assert.match(describeTransfer({ status: 'fehler', repo: '' }).body, /Unbekannter Fehler/);
|
||||
});
|
||||
|
||||
test('Langer Fehlertext wird gekürzt', () => {
|
||||
assert.ok(describeTransfer({ status: 'fehler', repo: 'a/b', error: 'x'.repeat(500) }).body.length < 200);
|
||||
});
|
||||
@@ -0,0 +1,133 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert');
|
||||
const { buildTrayTemplate, trayTooltip } = require('../src/features/trayMenu.js');
|
||||
|
||||
const noop = () => {};
|
||||
const base = { version: '2.2.1', onOpen: noop, onHistory: noop, onCancel: noop, onAutostart: noop, onQuit: noop };
|
||||
const labels = (tpl) => tpl.filter(i => i.label).map(i => i.label);
|
||||
const find = (tpl, label) => tpl.find(i => i.label === label);
|
||||
|
||||
test('Tooltip nennt Version und zeigt eine laufende Übertragung', () => {
|
||||
assert.strictEqual(trayTooltip('2.2.1', 0), 'Git Manager Explorer Pro v2.2.1');
|
||||
assert.match(trayTooltip('2.2.1', 2), /Übertragung läuft/);
|
||||
assert.match(trayTooltip('2.2.1', 1, true), /Übertragung pausiert/);
|
||||
assert.strictEqual(trayTooltip('2.2.1', 0, true), 'Git Manager Explorer Pro v2.2.1');
|
||||
});
|
||||
|
||||
test('Menü im Ruhezustand: Kopfzeile, Status, Aktionen; Abbrechen gesperrt', () => {
|
||||
const tpl = buildTrayTemplate(base);
|
||||
assert.deepStrictEqual(labels(tpl), [
|
||||
'Git Manager Explorer Pro v2.2.1',
|
||||
'Keine laufende Übertragung',
|
||||
'Fenster öffnen',
|
||||
'Upload-Verlauf',
|
||||
'Übertragung pausieren',
|
||||
'Laufende Übertragung abbrechen',
|
||||
'Mit Windows starten',
|
||||
'Beenden'
|
||||
]);
|
||||
assert.strictEqual(find(tpl, 'Laufende Übertragung abbrechen').enabled, false);
|
||||
assert.strictEqual(tpl[0].enabled, false);
|
||||
});
|
||||
|
||||
test('Menü bei laufender Übertragung: Zähler, Abbrechen freigeschaltet', () => {
|
||||
const one = buildTrayTemplate({ ...base, active: 1 });
|
||||
assert.ok(labels(one).includes('1 Übertragung läuft'));
|
||||
assert.strictEqual(find(one, 'Laufende Übertragung abbrechen').enabled, true);
|
||||
assert.ok(labels(buildTrayTemplate({ ...base, active: 3 })).includes('3 Übertragungen laufen'));
|
||||
});
|
||||
|
||||
test('Retry-Queue erscheint nur, wenn etwas wartet', () => {
|
||||
assert.ok(!labels(buildTrayTemplate(base)).some(l => l.startsWith('Retry-Queue')));
|
||||
assert.ok(labels(buildTrayTemplate({ ...base, queued: 4 })).includes('Retry-Queue: 4 wartend'));
|
||||
});
|
||||
|
||||
test('Autostart ist ein Häkchen und spiegelt den Zustand', () => {
|
||||
assert.strictEqual(find(buildTrayTemplate({ ...base, autostart: true }), 'Mit Windows starten').checked, true);
|
||||
const off = find(buildTrayTemplate(base), 'Mit Windows starten');
|
||||
assert.strictEqual(off.type, 'checkbox');
|
||||
assert.strictEqual(off.checked, false);
|
||||
});
|
||||
|
||||
test('Einträge rufen die übergebenen Aktionen auf', () => {
|
||||
const calls = [];
|
||||
const tpl = buildTrayTemplate({
|
||||
...base,
|
||||
active: 1,
|
||||
onOpen: () => calls.push('open'),
|
||||
onHistory: () => calls.push('history'),
|
||||
onCancel: () => calls.push('cancel'),
|
||||
onQuit: () => calls.push('quit')
|
||||
});
|
||||
for (const l of ['Fenster öffnen', 'Upload-Verlauf', 'Laufende Übertragung abbrechen', 'Beenden']) find(tpl, l).click();
|
||||
assert.deepStrictEqual(calls, ['open', 'history', 'cancel', 'quit']);
|
||||
});
|
||||
|
||||
const { serializeTemplate, runAction } = require('../src/features/trayMenu.js');
|
||||
const { computePosition } = require('../src/features/trayPopup.js');
|
||||
|
||||
test('serializeTemplate: nur Daten, keine Funktionen, Trenner bleiben erhalten', () => {
|
||||
const plain = serializeTemplate(buildTrayTemplate({ ...base, active: 1, autostart: true }));
|
||||
assert.ok(plain.every(i => Object.values(i).every(v => typeof v !== 'function')));
|
||||
assert.strictEqual(plain.filter(i => i.type === 'separator').length, 3);
|
||||
const cancel = plain.find(i => i.id === 'cancel');
|
||||
assert.deepStrictEqual(cancel, { id: 'cancel', label: 'Laufende Übertragung abbrechen', type: 'normal', enabled: true, checked: false });
|
||||
assert.strictEqual(plain.find(i => i.id === 'autostart').checked, true);
|
||||
assert.doesNotThrow(() => JSON.stringify(plain));
|
||||
});
|
||||
|
||||
test('runAction: führt Aktionen per id aus, Häkchen wird umgeschaltet', () => {
|
||||
const calls = [];
|
||||
const tpl = buildTrayTemplate({
|
||||
...base,
|
||||
active: 1,
|
||||
autostart: false,
|
||||
onOpen: () => calls.push('open'),
|
||||
onAutostart: (item) => calls.push(`autostart:${item.checked}`)
|
||||
});
|
||||
assert.strictEqual(runAction(tpl, 'open'), true);
|
||||
assert.strictEqual(runAction(tpl, 'autostart'), true);
|
||||
assert.deepStrictEqual(calls, ['open', 'autostart:true']);
|
||||
});
|
||||
|
||||
test('runAction: gesperrte oder unbekannte Einträge tun nichts', () => {
|
||||
let cancelled = false;
|
||||
const tpl = buildTrayTemplate({ ...base, active: 0, onCancel: () => { cancelled = true; } });
|
||||
assert.strictEqual(runAction(tpl, 'cancel'), false);
|
||||
assert.strictEqual(runAction(tpl, 'gibt-es-nicht'), false);
|
||||
assert.strictEqual(runAction(tpl, 'header'), false);
|
||||
assert.strictEqual(cancelled, false);
|
||||
});
|
||||
|
||||
test('computePosition: Menü bleibt im sichtbaren Bereich', () => {
|
||||
const wa = { x: 0, y: 0, width: 1920, height: 1040 };
|
||||
// Zeiger in der Taskleiste unten rechts: Menü sitzt direkt über der Taskleiste, nicht außerhalb rechts
|
||||
const bottomRight = computePosition({ x: 1900, y: 1060 }, wa, 286, 300);
|
||||
assert.strictEqual(bottomRight.y, 1040 - 300 - 8);
|
||||
assert.ok(bottomRight.x + 286 <= 1920 - 8);
|
||||
// Zeiger weit links: nicht links aus dem Bildschirm
|
||||
assert.ok(computePosition({ x: 2, y: 1060 }, wa, 286, 300).x >= 8);
|
||||
// Taskleiste oben
|
||||
assert.strictEqual(computePosition({ x: 500, y: 10 }, { ...wa, y: 40, height: 1000 }, 286, 300).y, 48);
|
||||
});
|
||||
|
||||
test('Pause: Eintrag wechselt zwischen pausieren und fortsetzen, nur bei laufender Übertragung wählbar', () => {
|
||||
const idle = buildTrayTemplate(base);
|
||||
assert.strictEqual(find(idle, 'Übertragung pausieren').enabled, false);
|
||||
|
||||
const running = buildTrayTemplate({ ...base, active: 1 });
|
||||
assert.strictEqual(find(running, 'Übertragung pausieren').enabled, true);
|
||||
|
||||
const paused = buildTrayTemplate({ ...base, active: 2, paused: true });
|
||||
assert.ok(labels(paused).includes('Übertragung fortsetzen'));
|
||||
assert.ok(labels(paused).includes('2 Übertragungen pausiert'));
|
||||
assert.ok(!labels(paused).includes('Übertragung pausieren'));
|
||||
assert.ok(labels(buildTrayTemplate({ ...base, active: 1, paused: true })).includes('1 Übertragung pausiert'));
|
||||
});
|
||||
|
||||
test('Pause-Eintrag ruft onPause auf', () => {
|
||||
const calls = [];
|
||||
const tpl = buildTrayTemplate({ ...base, active: 1, onPause: () => calls.push('pause') });
|
||||
assert.strictEqual(runAction(tpl, 'pause'), true);
|
||||
assert.deepStrictEqual(calls, ['pause']);
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert');
|
||||
const { blockedReason, partitionBlocked } = require('../src/features/uploadBlocklist.js');
|
||||
|
||||
test('Sperre: Dateien mit Zugangsdaten werden erkannt', () => {
|
||||
for (const name of ['credentials.json', 'data/credentials.json', 'C:\\proj\\.env', '.env.production', 'id_rsa', 'release.keystore', 'server.pem', 'secrets.json', '.npmrc']) {
|
||||
assert.ok(blockedReason(name, {}), `${name} sollte gesperrt sein`);
|
||||
}
|
||||
});
|
||||
|
||||
test('Sperre: normale Dateien und Vorlagen bleiben erlaubt', () => {
|
||||
for (const name of ['main.js', 'package.json', 'README.md', '.env.example', 'config.sample.json', 'id_rsa.pub', 'credentials.md', 'src/credentials/index.js']) {
|
||||
assert.strictEqual(blockedReason(name, {}), null, `${name} darf hochgeladen werden`);
|
||||
}
|
||||
});
|
||||
|
||||
test('Sperre: Einstellung kann sie abschalten, Standard ist an', () => {
|
||||
assert.ok(blockedReason('credentials.json', undefined));
|
||||
assert.ok(blockedReason('credentials.json', { uploadBlockSecrets: true }));
|
||||
assert.strictEqual(blockedReason('credentials.json', { uploadBlockSecrets: false }), null);
|
||||
});
|
||||
|
||||
test('partitionBlocked: trennt Einträge und behält die Reihenfolge', () => {
|
||||
const items = [
|
||||
{ localFile: 'a/main.js', targetPath: 'a/main.js' },
|
||||
{ localFile: 'a/data/credentials.json', targetPath: 'a/data/credentials.json' },
|
||||
{ localFile: 'a/.env', targetPath: 'a/.env' },
|
||||
{ localFile: 'a/README.md', targetPath: 'a/README.md' }
|
||||
];
|
||||
const { allowed, blocked } = partitionBlocked(items, {});
|
||||
assert.deepStrictEqual(allowed.map(i => i.localFile), ['a/main.js', 'a/README.md']);
|
||||
assert.deepStrictEqual(blocked.map(b => b.path), ['a/data/credentials.json', 'a/.env']);
|
||||
assert.ok(blocked.every(b => b.reason));
|
||||
assert.strictEqual(partitionBlocked(items, { uploadBlockSecrets: false }).blocked.length, 0);
|
||||
});
|
||||
@@ -0,0 +1,146 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert');
|
||||
const http = require('node:http');
|
||||
const fs = require('node:fs');
|
||||
const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
const {
|
||||
uploadItemsViaGiteaApi,
|
||||
createUploadCancelledError,
|
||||
isUploadCancelledError,
|
||||
throwIfUploadCancelled
|
||||
} = require('../src/git/apiHandler.js');
|
||||
|
||||
test('Abbruchfehler: Erkennung und throwIfUploadCancelled', () => {
|
||||
assert.strictEqual(isUploadCancelledError(createUploadCancelledError()), true);
|
||||
assert.strictEqual(isUploadCancelledError(new Error('anderer Fehler')), false);
|
||||
assert.strictEqual(isUploadCancelledError(null), false);
|
||||
|
||||
assert.doesNotThrow(() => throwIfUploadCancelled(undefined));
|
||||
assert.doesNotThrow(() => throwIfUploadCancelled(new AbortController().signal));
|
||||
const c = new AbortController();
|
||||
c.abort();
|
||||
assert.throws(() => throwIfUploadCancelled(c.signal), (e) => isUploadCancelledError(e));
|
||||
});
|
||||
|
||||
function makeTempFile() {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gm-cancel-'));
|
||||
const file = path.join(dir, 'a.txt');
|
||||
fs.writeFileSync(file, 'hallo');
|
||||
return { dir, file };
|
||||
}
|
||||
|
||||
test('API-Upload: bereits abgebrochenes Signal sendet nichts', async () => {
|
||||
const { dir, file } = makeTempFile();
|
||||
try {
|
||||
const c = new AbortController();
|
||||
c.abort();
|
||||
await assert.rejects(
|
||||
uploadItemsViaGiteaApi({
|
||||
token: 't', url: 'http://127.0.0.1:1', owner: 'o', repo: 'r', branch: 'main',
|
||||
items: [{ localFile: file, targetPath: 'a.txt' }], message: 'm', signal: c.signal
|
||||
}),
|
||||
(e) => isUploadCancelledError(e)
|
||||
);
|
||||
} finally { fs.rmSync(dir, { recursive: true, force: true }); }
|
||||
});
|
||||
|
||||
test('API-Upload: Abbruch während der Übertragung wirft Abbruchfehler und landet nicht in "failed"', async () => {
|
||||
const { dir, file } = makeTempFile();
|
||||
let postSeen = false;
|
||||
const server = http.createServer((req, res) => {
|
||||
if (req.method === 'POST') {
|
||||
postSeen = true;
|
||||
setTimeout(() => { res.writeHead(201, { 'content-type': 'application/json' }); res.end('{}'); }, 3000);
|
||||
return;
|
||||
}
|
||||
res.writeHead(200, { 'content-type': 'application/json' });
|
||||
res.end(JSON.stringify({ default_branch: 'main', tree: [], entries: [] }));
|
||||
});
|
||||
await new Promise(r => server.listen(0, '127.0.0.1', r));
|
||||
const { port } = server.address();
|
||||
try {
|
||||
const c = new AbortController();
|
||||
const started = Date.now();
|
||||
setTimeout(() => c.abort(), 400);
|
||||
await assert.rejects(
|
||||
uploadItemsViaGiteaApi({
|
||||
token: 't', url: `http://127.0.0.1:${port}`, owner: 'o', repo: 'r', branch: 'main',
|
||||
items: [{ localFile: file, targetPath: 'a.txt' }], message: 'm', signal: c.signal
|
||||
}),
|
||||
(e) => isUploadCancelledError(e)
|
||||
);
|
||||
assert.ok(postSeen, 'Der POST muss gestartet sein, sonst prüft der Test nichts');
|
||||
assert.ok(Date.now() - started < 2500, 'Abbruch muss sofort greifen, nicht erst nach der Serverantwort');
|
||||
} finally {
|
||||
server.closeAllConnections?.();
|
||||
await new Promise(r => server.close(r));
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
const { createPauseGate } = require('../src/features/pauseGate.js');
|
||||
|
||||
function gitea(onPost) {
|
||||
return http.createServer((req, res) => {
|
||||
if (req.method === 'POST') {
|
||||
onPost();
|
||||
res.writeHead(201, { 'content-type': 'application/json' });
|
||||
res.end('{}');
|
||||
return;
|
||||
}
|
||||
res.writeHead(200, { 'content-type': 'application/json' });
|
||||
res.end(JSON.stringify({ default_branch: 'main', tree: [], entries: [] }));
|
||||
});
|
||||
}
|
||||
|
||||
test('API-Upload: während der Pause wird nichts gesendet, nach resume() schon', async () => {
|
||||
const { dir, file } = makeTempFile();
|
||||
let posts = 0;
|
||||
const server = gitea(() => { posts++; });
|
||||
await new Promise(r => server.listen(0, '127.0.0.1', r));
|
||||
const { port } = server.address();
|
||||
const gate = createPauseGate();
|
||||
try {
|
||||
gate.pause();
|
||||
const upload = uploadItemsViaGiteaApi({
|
||||
token: 't', url: `http://127.0.0.1:${port}`, owner: 'o', repo: 'r', branch: 'main',
|
||||
items: [{ localFile: file, targetPath: 'a.txt' }], message: 'm',
|
||||
waitIfPaused: (s) => gate.wait(s)
|
||||
});
|
||||
await new Promise(r => setTimeout(r, 700));
|
||||
assert.strictEqual(posts, 0, 'Während der Pause darf kein Paket gesendet werden');
|
||||
gate.resume();
|
||||
const result = await upload;
|
||||
assert.strictEqual(posts, 1);
|
||||
assert.deepStrictEqual(result.uploaded, ['a.txt']);
|
||||
} finally {
|
||||
await new Promise(r => server.close(r));
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('API-Upload: Abbruch während der Pause beendet den Upload sofort', async () => {
|
||||
const { dir, file } = makeTempFile();
|
||||
let posts = 0;
|
||||
const server = gitea(() => { posts++; });
|
||||
await new Promise(r => server.listen(0, '127.0.0.1', r));
|
||||
const { port } = server.address();
|
||||
const gate = createPauseGate();
|
||||
try {
|
||||
gate.pause();
|
||||
const c = new AbortController();
|
||||
const upload = uploadItemsViaGiteaApi({
|
||||
token: 't', url: `http://127.0.0.1:${port}`, owner: 'o', repo: 'r', branch: 'main',
|
||||
items: [{ localFile: file, targetPath: 'a.txt' }], message: 'm',
|
||||
signal: c.signal, waitIfPaused: (s) => gate.wait(s)
|
||||
});
|
||||
setTimeout(() => c.abort(), 400);
|
||||
await assert.rejects(upload, (e) => isUploadCancelledError(e));
|
||||
assert.strictEqual(posts, 0);
|
||||
assert.strictEqual(gate.waitingCount(), 0);
|
||||
} finally {
|
||||
await new Promise(r => server.close(r));
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,113 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert');
|
||||
const { buildEntry, filterEntries, createUploadHistory } = require('../src/features/uploadHistory.js');
|
||||
|
||||
function memoryFs(initial) {
|
||||
const files = new Map(initial ? [['h.json', initial]] : []);
|
||||
return {
|
||||
files,
|
||||
readFileSync: (f) => { if (!files.has(f)) throw new Error('ENOENT'); return files.get(f); },
|
||||
writeFileSync: (f, c) => { files.set(f, c); }
|
||||
};
|
||||
}
|
||||
|
||||
test('buildEntry: erfolgreicher Upload', () => {
|
||||
const e = buildEntry('Drag & Drop', { owner: 'a', repo: 'b' }, { ok: true, results: [{}, {}, {}] }, 1000, null, 4000);
|
||||
assert.strictEqual(e.status, 'ok');
|
||||
assert.strictEqual(e.repo, 'a/b');
|
||||
assert.strictEqual(e.files, 3);
|
||||
assert.strictEqual(e.durationMs, 3000);
|
||||
assert.strictEqual(e.error, '');
|
||||
});
|
||||
|
||||
test('buildEntry: abgebrochen ist kein Fehler und trägt keinen Fehlertext', () => {
|
||||
const e = buildEntry('Ordner', { owner: 'a', repo: 'b' }, { ok: false, cancelled: true, error: 'Upload abgebrochen' }, 0, null, 10);
|
||||
assert.strictEqual(e.status, 'abgebrochen');
|
||||
assert.strictEqual(e.error, '');
|
||||
});
|
||||
|
||||
test('buildEntry: Fehler und geworfene Ausnahmen', () => {
|
||||
const failed = buildEntry('Dateien', { owner: 'a', repo: 'b' }, { ok: false, error: 'kaputt', failedCount: 2 }, 0, null, 10);
|
||||
assert.strictEqual(failed.status, 'fehler');
|
||||
assert.strictEqual(failed.error, 'kaputt');
|
||||
assert.strictEqual(failed.failed, 2);
|
||||
|
||||
const thrown = buildEntry('Dateien', {}, null, 0, new Error('boom'), 10);
|
||||
assert.strictEqual(thrown.status, 'fehler');
|
||||
assert.strictEqual(thrown.error, 'boom');
|
||||
assert.strictEqual(thrown.repo, '');
|
||||
});
|
||||
|
||||
test('buildEntry: Einzeldatei zählt als eine Datei, lange Fehler werden gekürzt', () => {
|
||||
assert.strictEqual(buildEntry('x', {}, { ok: true, singleFile: true }, 0, null, 1).files, 1);
|
||||
assert.strictEqual(buildEntry('x', {}, { ok: false, error: 'e'.repeat(1000) }, 0, null, 1).error.length, 300);
|
||||
});
|
||||
|
||||
test('filterEntries: nach Status und Repo-Suche', () => {
|
||||
const list = [
|
||||
{ repo: 'a/alpha', status: 'ok' },
|
||||
{ repo: 'a/beta', status: 'fehler' },
|
||||
{ repo: 'b/Alpha-2', status: 'abgebrochen' }
|
||||
];
|
||||
assert.strictEqual(filterEntries(list).length, 3);
|
||||
assert.deepStrictEqual(filterEntries(list, { status: 'fehler' }).map(e => e.repo), ['a/beta']);
|
||||
assert.deepStrictEqual(filterEntries(list, { query: 'ALPHA' }).map(e => e.repo), ['a/alpha', 'b/Alpha-2']);
|
||||
assert.strictEqual(filterEntries(list, { status: 'ok', query: 'beta' }).length, 0);
|
||||
});
|
||||
|
||||
test('Verlauf: neueste zuerst, auf max Einträge begrenzt', () => {
|
||||
const history = createUploadHistory({ file: 'h.json', max: 3, fs: memoryFs() });
|
||||
for (let i = 1; i <= 5; i++) history.record({ n: i });
|
||||
assert.deepStrictEqual(history.read().map(e => e.n), [5, 4, 3]);
|
||||
});
|
||||
|
||||
test('Verlauf: kaputte oder fehlende Datei ergibt eine leere Liste', () => {
|
||||
assert.deepStrictEqual(createUploadHistory({ file: 'h.json', fs: memoryFs() }).read(), []);
|
||||
assert.deepStrictEqual(createUploadHistory({ file: 'h.json', fs: memoryFs('{kaputt') }).read(), []);
|
||||
assert.deepStrictEqual(createUploadHistory({ file: 'h.json', fs: memoryFs('{"a":1}') }).read(), []);
|
||||
});
|
||||
|
||||
test('Verlauf: leeren', () => {
|
||||
const history = createUploadHistory({ file: 'h.json', fs: memoryFs() });
|
||||
history.record({ n: 1 });
|
||||
history.clear();
|
||||
assert.deepStrictEqual(history.read(), []);
|
||||
});
|
||||
|
||||
test('Verlauf: Schreibfehler werfen nicht', () => {
|
||||
const broken = { readFileSync: () => '[]', writeFileSync: () => { throw new Error('Platte voll'); } };
|
||||
assert.strictEqual(createUploadHistory({ file: 'h.json', fs: broken }).record({ n: 1 }), false);
|
||||
});
|
||||
|
||||
const { toCsv, formatDate } = require('../src/features/uploadHistory.js');
|
||||
|
||||
test('formatDate: TT.MM.JJJJ HH:MM:SS, ungültige Eingabe ergibt leeren Text', () => {
|
||||
assert.match(formatDate('2026-10-22T07:11:53.080Z'), /^\d{2}\.\d{2}\.2026 \d{2}:\d{2}:\d{2}$/);
|
||||
assert.strictEqual(formatDate('kein Datum'), '');
|
||||
});
|
||||
|
||||
test('toCsv: BOM, Semikolon, Windows-Zeilenenden, Kopfzeile', () => {
|
||||
const csv = toCsv([{ at: '2026-10-22T07:11:53.080Z', repo: 'a/b', kind: 'Drag & Drop', files: 3, failed: 0, status: 'ok', durationMs: 2500, error: '' }]);
|
||||
assert.strictEqual(csv.charCodeAt(0), 0xFEFF);
|
||||
const lines = csv.slice(1).split('\r\n');
|
||||
assert.strictEqual(lines[0], 'Datum;Repo;Art;Dateien;Fehlgeschlagen;Ergebnis;Dauer (s);Fehler');
|
||||
assert.match(lines[1], /;a\/b;Drag & Drop;3;0;Erfolgreich;2\.5;$/);
|
||||
assert.strictEqual(lines[2], '');
|
||||
});
|
||||
|
||||
test('toCsv: Anführungszeichen, Semikolon und Zeilenumbrüche werden maskiert', () => {
|
||||
const csv = toCsv([{ at: '2026-10-22T07:11:53Z', repo: 'a/b', kind: 'x', files: null, failed: 1, status: 'fehler', durationMs: 100, error: 'sagt "nein"; und\nmehr' }]);
|
||||
assert.ok(csv.includes('"sagt ""nein""; und\nmehr"'));
|
||||
assert.match(csv, /;Fehler;0\.1;/);
|
||||
});
|
||||
|
||||
test('toCsv: Excel-Formeln werden entschärft', () => {
|
||||
const csv = toCsv([{ at: '2026-10-22T07:11:53Z', repo: '=cmd|calc', kind: '+x', files: 1, failed: 0, status: 'ok', durationMs: 0, error: '@SUMME(A1)' }]);
|
||||
assert.ok(csv.includes(";'=cmd|calc;"));
|
||||
assert.ok(csv.includes(";'+x;"));
|
||||
assert.ok(csv.includes(";'@SUMME(A1)"));
|
||||
});
|
||||
|
||||
test('toCsv: leere Liste liefert nur die Kopfzeile', () => {
|
||||
assert.strictEqual(toCsv([]).slice(1).split('\r\n').filter(Boolean).length, 1);
|
||||
});
|
||||
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 310 KiB |
|
After Width: | Height: | Size: 165 KiB |
|
After Width: | Height: | Size: 212 KiB |
|
After Width: | Height: | Size: 311 KiB |
|
After Width: | Height: | Size: 204 KiB |
|
After Width: | Height: | Size: 303 KiB |
|
After Width: | Height: | Size: 319 KiB |
|
After Width: | Height: | Size: 354 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 57 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 349 KiB |
|
After Width: | Height: | Size: 180 KiB |
|
After Width: | Height: | Size: 165 KiB |
@@ -0,0 +1,19 @@
|
||||
<Application x:Class="GitManagerSetup.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<!-- [0] wird je nach Hell/Dunkel ersetzt -->
|
||||
<ResourceDictionary Source="Themes/Colors.Light.xaml"/>
|
||||
<ResourceDictionary Source="Themes/Controls.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<!-- Akzent wie in der App (Cyan → Blau) -->
|
||||
<SolidColorBrush x:Key="AccentBrush" Color="#2E7FE0"/>
|
||||
<SolidColorBrush x:Key="AccentHoverBrush" Color="#4590EA"/>
|
||||
<SolidColorBrush x:Key="OnAccentBrush" Color="White"/>
|
||||
<SolidColorBrush x:Key="AccentSubtleBrush" Color="#222E7FE0"/>
|
||||
<SolidColorBrush x:Key="AccentSubtleStrokeBrush" Color="#552E7FE0"/>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
@@ -0,0 +1,99 @@
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace GitManagerSetup;
|
||||
|
||||
public partial class App : Application
|
||||
{
|
||||
private static Mutex? _single;
|
||||
|
||||
protected override void OnStartup(StartupEventArgs e)
|
||||
{
|
||||
base.OnStartup(e);
|
||||
bool has(string arg) => e.Args.Any(a => a.Equals(arg, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
// Das Deinstallationsprogramm ist das Setup ohne angehängte App → ohne Anhang immer Deinstallation
|
||||
bool uninstall = has("--uninstall") || !Payload.Exists;
|
||||
bool silent = has("/S");
|
||||
bool update = has("--update");
|
||||
bool launch = has("/launch");
|
||||
var dirArg = e.Args.FirstOrDefault(a => a.StartsWith("/D=", StringComparison.OrdinalIgnoreCase))?.Substring(3);
|
||||
|
||||
_single = new Mutex(true, "GitManagerGUI-Setup", out bool first);
|
||||
if (!first)
|
||||
{
|
||||
Shutdown(0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Stille Installation/Deinstallation für Skripte:
|
||||
// Git-Manager-GUI-Setup.exe /S [/D=Ordner] [/launch] bzw. --uninstall /S
|
||||
if (silent)
|
||||
{
|
||||
var progress = new Progress<(double, string)>(_ => { });
|
||||
try
|
||||
{
|
||||
if (uninstall) Installer.Uninstall(removeData: false, progress);
|
||||
else
|
||||
{
|
||||
var dir = dirArg ?? Installer.InstalledDir ?? Installer.DefaultDir;
|
||||
Installer.Install(dir, desktopLink: Installer.InstalledDir is null || Installer.DesktopLinkExists,
|
||||
progress, waitForExit: true);
|
||||
if (launch) System.Diagnostics.Process.Start(Path.Combine(dir, Installer.ExeName));
|
||||
}
|
||||
Shutdown(0);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// ohne Oberfläche: Fehler für Skripte nachvollziehbar ablegen
|
||||
try { File.WriteAllText(Path.Combine(Path.GetTempPath(), "Git-Manager-GUI-Setup.log"), $"[{DateTime.Now}] {ex}"); } catch { }
|
||||
Shutdown(1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ApplyTheme();
|
||||
var mode = uninstall ? SetupMode.Uninstall : update ? SetupMode.Update : SetupMode.Install;
|
||||
new MainWindow(mode).Show();
|
||||
}
|
||||
|
||||
protected override void OnExit(ExitEventArgs e)
|
||||
{
|
||||
try { _single?.ReleaseMutex(); } catch { }
|
||||
base.OnExit(e);
|
||||
}
|
||||
|
||||
/// <summary>Hell/Dunkel wie in Windows eingestellt.</summary>
|
||||
private void ApplyTheme()
|
||||
{
|
||||
bool light = ReadDword(@"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize", "AppsUseLightTheme") != 0;
|
||||
Resources.MergedDictionaries[0] = new ResourceDictionary
|
||||
{
|
||||
Source = new Uri($"pack://application:,,,/Themes/Colors.{(light ? "Light" : "Dark")}.xaml"),
|
||||
};
|
||||
if (!light)
|
||||
{
|
||||
// im Dunkeln das Cyan der App, damit Text und Fortschrittsbalken gut lesbar bleiben
|
||||
var a = Color.FromRgb(0x4C, 0xB8, 0xF5);
|
||||
Resources["AccentBrush"] = new SolidColorBrush(a);
|
||||
Resources["AccentHoverBrush"] = new SolidColorBrush(Color.FromRgb(0x67, 0xC6, 0xF8));
|
||||
Resources["OnAccentBrush"] = new SolidColorBrush(Color.FromRgb(0x04, 0x10, 0x1F));
|
||||
Resources["AccentSubtleBrush"] = new SolidColorBrush(Color.FromArgb(0x30, a.R, a.G, a.B));
|
||||
Resources["AccentSubtleStrokeBrush"] = new SolidColorBrush(Color.FromArgb(0x60, a.R, a.G, a.B));
|
||||
}
|
||||
}
|
||||
|
||||
private static int ReadDword(string key, string name)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var k = Registry.CurrentUser.OpenSubKey(key);
|
||||
return k?.GetValue(name) is int i ? i : -1;
|
||||
}
|
||||
catch { return -1; }
|
||||
}
|
||||
}
|
||||
|
||||
public enum SetupMode { Install, Update, Uninstall }
|
||||
@@ -0,0 +1,46 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<!-- .NET Framework 4.8 ist in jedem Windows 10/11 enthalten → der Installer läuft ohne Zusatzinstallation -->
|
||||
<TargetFramework>net48</TargetFramework>
|
||||
<UseWPF>true</UseWPF>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
<GenerateDocumentationFile>false</GenerateDocumentationFile>
|
||||
<AssemblyName>Git-Manager-GUI-Setup</AssemblyName>
|
||||
<RootNamespace>GitManagerSetup</RootNamespace>
|
||||
<ApplicationIcon>..\assets\icon.ico</ApplicationIcon>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<Product>Git Manager GUI Setup</Product>
|
||||
<AssemblyTitle>Git Manager GUI Setup</AssemblyTitle>
|
||||
<Description>Installiert Git Manager GUI</Description>
|
||||
<Company>M_Viper</Company>
|
||||
<Authors>M_Viper</Authors>
|
||||
<Copyright>Copyright (c) M_Viper</Copyright>
|
||||
<NeutralLanguage>de-DE</NeutralLanguage>
|
||||
<!-- Version kommt beim Bauen aus package.json (scripts/build-setup.js) -->
|
||||
<Version Condition="'$(Version)' == ''">0.0.0</Version>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<!-- WinForms nur für den Ordner-Dialog; nicht global, sonst Namenskonflikte mit WPF -->
|
||||
<Using Remove="System.Windows.Forms" />
|
||||
<Using Remove="System.Drawing" />
|
||||
<Reference Include="Microsoft.VisualBasic" />
|
||||
<Reference Include="System.IO.Compression" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<!-- Themes\*.xaml bindet das SDK automatisch als Page ein -->
|
||||
<Resource Include="..\assets\icon.ico" Link="app.ico" />
|
||||
<Resource Include="..\assets\icon.png" Link="app.png" />
|
||||
</ItemGroup>
|
||||
<!--
|
||||
Die eigentliche App (entpackter Electron-Ordner als ZIP) wird NICHT hier eingebettet,
|
||||
sondern nach dem Bauen hinten an die .exe angehängt (scripts/build-setup.js, siehe Payload.cs).
|
||||
So muss der Compiler keine ~150 MB als Ressource verarbeiten, und das Deinstallationsprogramm
|
||||
ist einfach der vordere Teil der Datei ohne Anhang.
|
||||
-->
|
||||
</Project>
|
||||
@@ -0,0 +1,501 @@
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace GitManagerSetup;
|
||||
|
||||
/// <summary>Installation/Deinstallation – nur für den aktuellen Benutzer, ohne Administratorrechte.</summary>
|
||||
public static class Installer
|
||||
{
|
||||
public const string AppName = "Git Manager GUI";
|
||||
public const string Publisher = "M_Viper";
|
||||
// productName aus package.json → so heißt die EXE im entpackten Electron-Ordner
|
||||
public const string ExeName = "Git Manager GUI.exe";
|
||||
private const string ProcessName = "Git Manager GUI";
|
||||
// Gleicher Name wie beim früheren NSIS-Setup (electron-builder), damit eine alte Installation sauber ersetzt wird
|
||||
public const string UninstallerName = "Uninstall Git Manager GUI.exe";
|
||||
// wie "appId" in package.json – gleiche Taskleisten-/Benachrichtigungs-Zuordnung wie bisher
|
||||
public const string AppUserModelId = "com.viper.gitmanager";
|
||||
|
||||
// Schlüssel vom alten electron-builder-Setup (Version 2.1.7) übernommen → in "Apps" bleibt es ein einziger Eintrag
|
||||
private const string KeyId = "4156cc7f-a3e7-5b1d-9238-0af3e0f54ecd";
|
||||
private const string UninstallKey = @"Software\Microsoft\Windows\CurrentVersion\Uninstall\" + KeyId;
|
||||
private const string LegacyAppKey = @"Software\" + KeyId;
|
||||
private const string RunKey = @"Software\Microsoft\Windows\CurrentVersion\Run";
|
||||
private const string FileListName = "install-files.txt";
|
||||
|
||||
public static string Version
|
||||
{
|
||||
get
|
||||
{
|
||||
var info = typeof(Installer).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
|
||||
if (!string.IsNullOrEmpty(info)) return info!.Split('+')[0];
|
||||
var v = typeof(Installer).Assembly.GetName().Version!;
|
||||
return $"{v.Major}.{v.Minor}.{v.Build}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Pfad dieses Setups (bzw. des Deinstallationsprogramms).</summary>
|
||||
public static string SelfPath => Assembly.GetExecutingAssembly().Location;
|
||||
|
||||
public static string DefaultDir =>
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Programs", AppName);
|
||||
|
||||
/// <summary>App-Daten: Zugangsdaten (verschlüsselt), Einstellungen, Favoriten, Verlauf, Backup-Einstellung.</summary>
|
||||
public static string DataDir =>
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "git-manager-gui");
|
||||
|
||||
private static string StartMenuLink =>
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Programs), AppName + ".lnk");
|
||||
|
||||
private static string DesktopLink =>
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), AppName + ".lnk");
|
||||
|
||||
// ---------- vorhandene Installation ----------
|
||||
|
||||
private static string? RegValue(string name)
|
||||
{
|
||||
using var k = Registry.CurrentUser.OpenSubKey(UninstallKey);
|
||||
return k?.GetValue(name) as string;
|
||||
}
|
||||
|
||||
/// <summary>Ordner einer vorhandenen Installation (dieses Setups oder des alten NSIS-Setups), oder null.</summary>
|
||||
public static string? InstalledDir
|
||||
{
|
||||
get
|
||||
{
|
||||
var dir = RegValue("InstallLocation");
|
||||
if (string.IsNullOrWhiteSpace(dir))
|
||||
{
|
||||
// altes NSIS-Setup trägt keinen InstallLocation ein → aus dem Deinstallationsbefehl ablesen
|
||||
var m = Regex.Match(RegValue("UninstallString") ?? "", "^\"([^\"]+)\"");
|
||||
dir = m.Success ? Path.GetDirectoryName(m.Groups[1].Value) : null;
|
||||
}
|
||||
return dir is not null && File.Exists(Path.Combine(dir, ExeName)) ? dir : null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Stammt die vorhandene Installation noch vom alten NSIS-Setup?</summary>
|
||||
public static bool IsLegacyInstall =>
|
||||
InstalledDir is not null && (RegValue("UninstallString") ?? "").IndexOf("--uninstall", StringComparison.OrdinalIgnoreCase) < 0;
|
||||
|
||||
public static string? InstalledVersion => RegValue("DisplayVersion");
|
||||
|
||||
public static bool DesktopLinkExists => File.Exists(DesktopLink);
|
||||
|
||||
public static bool IsRunning(string? dir) => AppProcesses(dir, InstalledDir).Any();
|
||||
|
||||
// ---------- Installieren ----------
|
||||
|
||||
/// <param name="waitForExit">nach einem Update aus der App: ihr erst Zeit geben, sich selbst zu beenden</param>
|
||||
public static void Install(string dir, bool desktopLink, IProgress<(double, string)> progress, bool waitForExit = false)
|
||||
{
|
||||
if (!Environment.Is64BitOperatingSystem)
|
||||
throw new InvalidOperationException("Git Manager GUI benötigt ein 64-Bit-Windows (Windows 10 oder 11).");
|
||||
|
||||
var oldDir = InstalledDir;
|
||||
progress.Report((0.02, "Git Manager GUI wird beendet …"));
|
||||
CloseRunningApp(waitForExit ? 20000 : 0, dir, oldDir);
|
||||
|
||||
using var zip = Payload.OpenArchive();
|
||||
var topLevel = TopLevelNames(zip);
|
||||
|
||||
progress.Report((0.05, "Alte Version wird entfernt …"));
|
||||
Directory.CreateDirectory(dir);
|
||||
RemoveAppFiles(dir, topLevel);
|
||||
if (oldDir is not null && !SamePath(oldDir, dir))
|
||||
{
|
||||
// Installation wurde in einen anderen Ordner verlegt → alten Ordner aufräumen
|
||||
var oldExe = Path.Combine(oldDir, ExeName);
|
||||
DeleteIfPointsTo(StartMenuLink, oldExe);
|
||||
DeleteIfPointsTo(DesktopLink, oldExe);
|
||||
RemoveAppFiles(oldDir, topLevel);
|
||||
try { Directory.Delete(oldDir); } catch { } // nur wenn leer
|
||||
}
|
||||
|
||||
Extract(zip, dir, p => progress.Report((0.08 + p * 0.8, "Dateien werden kopiert …")));
|
||||
|
||||
progress.Report((0.9, "Deinstallationsprogramm wird angelegt …"));
|
||||
var exe = Path.Combine(dir, ExeName);
|
||||
var uninstaller = Path.Combine(dir, UninstallerName);
|
||||
if (!SamePath(SelfPath, uninstaller))
|
||||
{
|
||||
MoveAside(uninstaller);
|
||||
CopyStub(uninstaller);
|
||||
}
|
||||
File.WriteAllLines(Path.Combine(dir, FileListName), topLevel.Concat([UninstallerName, FileListName]));
|
||||
|
||||
progress.Report((0.94, "Verknüpfungen werden angelegt …"));
|
||||
Shortcut.Create(StartMenuLink, exe, "Repositories auf Gitea und GitHub verwalten", AppUserModelId);
|
||||
if (desktopLink) Shortcut.Create(DesktopLink, exe, "Repositories auf Gitea und GitHub verwalten", AppUserModelId);
|
||||
else DeleteIfPointsTo(DesktopLink, exe);
|
||||
|
||||
progress.Report((0.97, "Windows wird eingerichtet …"));
|
||||
Registry.CurrentUser.DeleteSubKeyTree(UninstallKey, throwOnMissingSubKey: false); // Reste des alten Setups
|
||||
Registry.CurrentUser.DeleteSubKeyTree(LegacyAppKey, throwOnMissingSubKey: false);
|
||||
using (var k = Registry.CurrentUser.CreateSubKey(UninstallKey))
|
||||
{
|
||||
long size = DirSize(dir) / 1024;
|
||||
k.SetValue("DisplayName", AppName);
|
||||
k.SetValue("DisplayVersion", Version);
|
||||
k.SetValue("Publisher", Publisher);
|
||||
k.SetValue("DisplayIcon", $"{exe},0");
|
||||
k.SetValue("InstallLocation", dir);
|
||||
k.SetValue("UninstallString", $"\"{uninstaller}\" --uninstall");
|
||||
k.SetValue("QuietUninstallString", $"\"{uninstaller}\" --uninstall /S");
|
||||
k.SetValue("URLInfoAbout", "https://git.viper.ipv64.net/M_Viper/Git-Manager-Gui");
|
||||
k.SetValue("InstallDate", DateTime.Now.ToString("yyyyMMdd"));
|
||||
k.SetValue("EstimatedSize", (int)Math.Min(int.MaxValue, size), RegistryValueKind.DWord);
|
||||
k.SetValue("NoModify", 1, RegistryValueKind.DWord);
|
||||
k.SetValue("NoRepair", 1, RegistryValueKind.DWord);
|
||||
}
|
||||
|
||||
progress.Report((0.98, "Installation wird abgeschlossen …"));
|
||||
Vorwaermen(exe);
|
||||
|
||||
progress.Report((1.0, "Fertig"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Startet die App einmal kurz mit "--gm-vorwaermen" – sie beendet sich
|
||||
/// damit sofort wieder (siehe main.js). Windows Defender prüft eine neue,
|
||||
/// noch unbekannte EXE beim ersten Start mehrere Sekunden lang (gemessen: ~8 s);
|
||||
/// das passiert so während des Fortschrittsbalkens und nicht erst beim Klick
|
||||
/// auf "Fertigstellen". Das Ergebnis merkt sich Windows, der echte Start ist
|
||||
/// danach sofort da.
|
||||
/// </summary>
|
||||
private static void Vorwaermen(string exe)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var p = Process.Start(new ProcessStartInfo(exe, "--gm-vorwaermen")
|
||||
{
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
WorkingDirectory = Path.GetDirectoryName(exe)!,
|
||||
});
|
||||
if (p != null && !p.WaitForExit(30000)) { try { p.Kill(); } catch { } }
|
||||
}
|
||||
catch { /* nur eine Beschleunigung – Fehler sind egal */ }
|
||||
}
|
||||
|
||||
private static List<string> TopLevelNames(ZipArchive zip) =>
|
||||
zip.Entries
|
||||
.Select(e => e.FullName.Replace('\\', '/').Split('/')[0])
|
||||
.Where(n => n.Length > 0)
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
|
||||
private static void Extract(ZipArchive zip, string dir, Action<double> report)
|
||||
{
|
||||
var root = Path.GetFullPath(dir).TrimEnd('\\') + "\\";
|
||||
long total = Math.Max(1, zip.Entries.Sum(e => e.Length)), done = 0;
|
||||
var buffer = new byte[1 << 20];
|
||||
int lastPercent = -1;
|
||||
|
||||
foreach (var entry in zip.Entries)
|
||||
{
|
||||
var target = Path.GetFullPath(Path.Combine(dir, entry.FullName));
|
||||
if (!target.StartsWith(root, StringComparison.OrdinalIgnoreCase)) continue; // nie außerhalb des Ordners schreiben
|
||||
if (entry.Name.Length == 0)
|
||||
{
|
||||
Directory.CreateDirectory(target);
|
||||
continue;
|
||||
}
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(target)!);
|
||||
long before = done;
|
||||
Retry(target, () =>
|
||||
{
|
||||
done = before;
|
||||
MoveAside(target);
|
||||
using var src = entry.Open();
|
||||
using var dst = File.Create(target);
|
||||
int n;
|
||||
while ((n = src.Read(buffer, 0, buffer.Length)) > 0)
|
||||
{
|
||||
dst.Write(buffer, 0, n);
|
||||
done += n;
|
||||
int percent = (int)(done * 100 / total);
|
||||
if (percent != lastPercent)
|
||||
{
|
||||
lastPercent = percent;
|
||||
report(Math.Min(1.0, (double)done / total));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
report(1.0);
|
||||
}
|
||||
|
||||
/// <summary>Setup ohne angehängte App als Deinstallationsprogramm ablegen (nur ein paar hundert KB).</summary>
|
||||
private static void CopyStub(string path)
|
||||
{
|
||||
long length = Payload.StubLength;
|
||||
using var src = File.OpenRead(SelfPath);
|
||||
using var dst = File.Create(path);
|
||||
var buffer = new byte[81920];
|
||||
long left = length;
|
||||
while (left > 0)
|
||||
{
|
||||
int n = src.Read(buffer, 0, (int)Math.Min(buffer.Length, left));
|
||||
if (n <= 0) break;
|
||||
dst.Write(buffer, 0, n);
|
||||
left -= n;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Deinstallieren ----------
|
||||
|
||||
public static void Uninstall(bool removeData, IProgress<(double, string)> progress)
|
||||
{
|
||||
// Nur im eingetragenen Installationsordner löschen – nie dort, wo das Setup zufällig liegt (z. B. Downloads)
|
||||
var dir = InstalledDir;
|
||||
var exe = dir is null ? "" : Path.Combine(dir, ExeName);
|
||||
|
||||
progress.Report((0.1, "Git Manager GUI wird beendet …"));
|
||||
CloseRunningApp(0, dir);
|
||||
|
||||
progress.Report((0.3, "Verknüpfungen werden entfernt …"));
|
||||
DeleteIfPointsTo(StartMenuLink, exe);
|
||||
DeleteIfPointsTo(DesktopLink, exe);
|
||||
|
||||
progress.Report((0.45, "Windows-Einträge werden entfernt …"));
|
||||
RemoveAutostart(exe);
|
||||
Registry.CurrentUser.DeleteSubKeyTree(UninstallKey, throwOnMissingSubKey: false);
|
||||
Registry.CurrentUser.DeleteSubKeyTree(LegacyAppKey, throwOnMissingSubKey: false);
|
||||
// Name/Logo für Windows-Benachrichtigungen (legt Electron selbst an)
|
||||
Registry.CurrentUser.DeleteSubKeyTree(@"Software\Classes\AppUserModelId\" + AppUserModelId, throwOnMissingSubKey: false);
|
||||
|
||||
if (removeData && Directory.Exists(DataDir))
|
||||
{
|
||||
progress.Report((0.6, "Zugangsdaten und Einstellungen werden in den Papierkorb gelegt …"));
|
||||
try
|
||||
{
|
||||
Microsoft.VisualBasic.FileIO.FileSystem.DeleteDirectory(DataDir,
|
||||
Microsoft.VisualBasic.FileIO.UIOption.OnlyErrorDialogs,
|
||||
Microsoft.VisualBasic.FileIO.RecycleOption.SendToRecycleBin);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
progress.Report((0.8, "Dateien werden entfernt …"));
|
||||
if (dir is null) { progress.Report((1.0, "Fertig")); return; }
|
||||
RemoveAppFiles(dir, null);
|
||||
|
||||
// Das laufende Deinstallationsprogramm kann sich nicht selbst löschen → kurz nach dem Beenden per cmd.
|
||||
// rmdir ohne /s entfernt den Ordner nur, wenn er danach leer ist.
|
||||
var self = SelfPath;
|
||||
if (IsUnder(self, dir))
|
||||
{
|
||||
Process.Start(new ProcessStartInfo("cmd.exe",
|
||||
$"/c ping 127.0.0.1 -n 3 > nul & del /f /q \"{self}\" & del /f /q \"{Path.Combine(dir, "*.old")}\" & rmdir \"{dir}\"")
|
||||
{
|
||||
CreateNoWindow = true, UseShellExecute = false, WindowStyle = ProcessWindowStyle.Hidden,
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
TryDelete(Path.Combine(dir, UninstallerName));
|
||||
try { Directory.Delete(dir); } catch { } // nur wenn leer
|
||||
}
|
||||
progress.Report((1.0, "Fertig"));
|
||||
}
|
||||
|
||||
/// <summary>Autostart-Einträge entfernen, die die App selbst gesetzt hat (Name vergibt Electron).</summary>
|
||||
private static void RemoveAutostart(string exe)
|
||||
{
|
||||
if (exe.Length == 0) return;
|
||||
using var run = Registry.CurrentUser.OpenSubKey(RunKey, writable: true);
|
||||
if (run is null) return;
|
||||
foreach (var name in run.GetValueNames())
|
||||
{
|
||||
if (run.GetValue(name) is string cmd && cmd.IndexOf(exe, StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
run.DeleteValue(name, throwOnMissingValue: false);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Dateien ----------
|
||||
|
||||
/// <summary>
|
||||
/// Entfernt die App-Dateien aus einem Installationsordner. Gelöscht wird nur, was nachweislich zur App gehört:
|
||||
/// Einträge aus der Dateiliste der letzten Installation bzw. die Namen aus dem neuen Paket –
|
||||
/// Ordner nur, wenn sie eindeutig von Electron stammen. Fremde Dateien im Ordner bleiben unangetastet.
|
||||
/// </summary>
|
||||
private static void RemoveAppFiles(string dir, IEnumerable<string>? extraNames)
|
||||
{
|
||||
if (!Directory.Exists(dir)) return;
|
||||
CleanupOldFiles(dir);
|
||||
var listFile = Path.Combine(dir, FileListName);
|
||||
var fromList = File.Exists(listFile) ? File.ReadAllLines(listFile).Where(l => l.Trim().Length > 0).ToList() : [];
|
||||
var names = fromList.Concat(extraNames ?? []).Concat([UninstallerName, FileListName])
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var name in names)
|
||||
{
|
||||
if (name.IndexOfAny(['\\', '/', ':']) >= 0 || name is "." or "..") continue;
|
||||
var path = Path.Combine(dir, name);
|
||||
if (SamePath(path, SelfPath)) continue; // das laufende Deinstallationsprogramm räumt cmd weg
|
||||
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
bool ours = fromList.Contains(name, StringComparer.OrdinalIgnoreCase)
|
||||
|| (name.Equals("resources", StringComparison.OrdinalIgnoreCase) && File.Exists(Path.Combine(path, "app.asar")))
|
||||
|| (name.Equals("locales", StringComparison.OrdinalIgnoreCase) && File.Exists(Path.Combine(path, "en-US.pak")));
|
||||
if (ours) Retry(path, () => Directory.Delete(path, recursive: true));
|
||||
}
|
||||
else if (File.Exists(path))
|
||||
{
|
||||
Retry(path, () => MoveAside(path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Längster Pfad im Paket. Windows erlaubt (ohne Sonderfreigabe) nur 259 Zeichen pro Pfad –
|
||||
/// der Installationsordner darf deshalb höchstens <see cref="MaxDirLength"/> Zeichen lang sein.
|
||||
/// </summary>
|
||||
private static int? _longestEntry;
|
||||
public static int MaxDirLength
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_longestEntry is null)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var zip = Payload.OpenArchive();
|
||||
_longestEntry = zip.Entries.Count == 0 ? 0 : zip.Entries.Max(e => e.FullName.Length);
|
||||
}
|
||||
catch { _longestEntry = 0; }
|
||||
}
|
||||
return 259 - 1 - _longestEntry.Value;
|
||||
}
|
||||
}
|
||||
|
||||
private static void Retry(string path, Action action)
|
||||
{
|
||||
for (int attempt = 1; ; attempt++)
|
||||
{
|
||||
try
|
||||
{
|
||||
action();
|
||||
return;
|
||||
}
|
||||
catch (PathTooLongException ex)
|
||||
{
|
||||
throw new IOException($"Der Installationsordner ist zu lang. Bitte einen kürzeren Ordner wählen (höchstens {MaxDirLength} Zeichen).", ex);
|
||||
}
|
||||
catch (Exception ex) when ((ex is IOException || ex is UnauthorizedAccessException) && attempt < 10)
|
||||
{
|
||||
Thread.Sleep(500);
|
||||
}
|
||||
catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException)
|
||||
{
|
||||
throw new IOException($"„{Path.GetFileName(path)}“ wird noch verwendet. Bitte Git Manager GUI beenden " +
|
||||
"(auch das Symbol im Infobereich neben der Uhr) und erneut versuchen.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Vorhandene Datei löschen oder – falls sie noch geöffnet ist (Explorer, Virenscanner) – beiseite umbenennen.
|
||||
/// Umbenennen klappt unter Windows auch bei geöffneten Dateien, überschreiben nicht.
|
||||
/// </summary>
|
||||
private static void MoveAside(string path)
|
||||
{
|
||||
if (!File.Exists(path)) return;
|
||||
try { File.Delete(path); return; } catch { }
|
||||
File.Move(path, $"{path}.{Guid.NewGuid():N}.old");
|
||||
}
|
||||
|
||||
private static void CleanupOldFiles(string dir)
|
||||
{
|
||||
if (!Directory.Exists(dir)) return;
|
||||
foreach (var f in Directory.GetFiles(dir, "*.old", SearchOption.AllDirectories))
|
||||
try { File.Delete(f); } catch { }
|
||||
}
|
||||
|
||||
private static void TryDelete(string path)
|
||||
{
|
||||
try { if (File.Exists(path)) File.Delete(path); } catch { }
|
||||
}
|
||||
|
||||
private static void DeleteIfPointsTo(string link, string target)
|
||||
{
|
||||
if (!File.Exists(link) || target.Length == 0) return;
|
||||
if (!SamePath(Shortcut.Target(link) ?? "", target)) return;
|
||||
// Explorer/Indexdienst halten Verknüpfungen manchmal kurz fest → mehrmals versuchen
|
||||
for (int i = 0; i < 10 && File.Exists(link); i++)
|
||||
{
|
||||
try { File.Delete(link); }
|
||||
catch { Thread.Sleep(300); }
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Prozesse ----------
|
||||
|
||||
private static IEnumerable<Process> AppProcesses(params string?[] dirs)
|
||||
{
|
||||
var roots = dirs.Where(d => !string.IsNullOrEmpty(d)).Select(d => d!).ToList();
|
||||
if (roots.Count == 0) yield break;
|
||||
// die App (mehrere Electron-Prozesse) und das mitgelieferte Git, falls gerade ein Upload läuft
|
||||
foreach (var p in Process.GetProcessesByName(ProcessName).Concat(Process.GetProcessesByName("git")))
|
||||
{
|
||||
string? file = null;
|
||||
try { file = p.MainModule?.FileName; } catch { }
|
||||
if (file is not null && roots.Any(r => IsUnder(file, r))) yield return p;
|
||||
}
|
||||
}
|
||||
|
||||
private static void CloseRunningApp(int waitMs, params string?[] dirs)
|
||||
{
|
||||
var until = DateTime.Now.AddMilliseconds(waitMs);
|
||||
while (waitMs > 0 && DateTime.Now < until && AppProcesses(dirs).Any())
|
||||
Thread.Sleep(300);
|
||||
|
||||
foreach (var p in AppProcesses(dirs).ToList())
|
||||
{
|
||||
try
|
||||
{
|
||||
p.Kill();
|
||||
p.WaitForExit(10000);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Helfer ----------
|
||||
|
||||
private static bool SamePath(string a, string b)
|
||||
{
|
||||
try { return string.Equals(Path.GetFullPath(a).TrimEnd('\\'), Path.GetFullPath(b).TrimEnd('\\'), StringComparison.OrdinalIgnoreCase); }
|
||||
catch { return false; }
|
||||
}
|
||||
|
||||
private static bool IsUnder(string path, string dir) =>
|
||||
Path.GetFullPath(path).StartsWith(Path.GetFullPath(dir).TrimEnd('\\') + "\\", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
private static long DirSize(string dir)
|
||||
{
|
||||
long sum = 0;
|
||||
try
|
||||
{
|
||||
foreach (var f in Directory.EnumerateFiles(dir, "*", SearchOption.AllDirectories))
|
||||
try { sum += new FileInfo(f).Length; } catch { }
|
||||
}
|
||||
catch { }
|
||||
return sum;
|
||||
}
|
||||
|
||||
public static long DataSize() => Directory.Exists(DataDir) ? DirSize(DataDir) : 0;
|
||||
|
||||
public static string FormatSize(long bytes) => bytes switch
|
||||
{
|
||||
< 1024 * 1024 => $"{Math.Max(1, bytes / 1024)} KB",
|
||||
< 1024L * 1024 * 1024 => $"{bytes / 1024.0 / 1024:0.#} MB",
|
||||
_ => $"{bytes / 1024.0 / 1024 / 1024:0.##} GB",
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,352 @@
|
||||
<Window x:Class="GitManagerSetup.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="Git Manager GUI Setup" Icon="app.ico" Width="780" Height="520" ResizeMode="CanMinimize"
|
||||
Background="{DynamicResource LayerBrush}" Foreground="{DynamicResource TextBrush}"
|
||||
FontFamily="{DynamicResource UiFont}" FontSize="14"
|
||||
TextOptions.TextFormattingMode="Display" UseLayoutRounding="True"
|
||||
WindowStartupLocation="CenterScreen">
|
||||
|
||||
<WindowChrome.WindowChrome>
|
||||
<WindowChrome CaptionHeight="36" ResizeBorderThickness="0" GlassFrameThickness="0,0,0,1"
|
||||
CornerRadius="0" UseAeroCaptionButtons="False"/>
|
||||
</WindowChrome.WindowChrome>
|
||||
|
||||
<Window.Resources>
|
||||
<!-- Markenfarben der Seitenleiste (in Hell und Dunkel gleich, wie die App) -->
|
||||
<SolidColorBrush x:Key="SideText" Color="#F5F8FF"/>
|
||||
<SolidColorBrush x:Key="SideTextSecondary" Color="#AEBDD8"/>
|
||||
<SolidColorBrush x:Key="SideTextMuted" Color="#7083A4"/>
|
||||
<LinearGradientBrush x:Key="BrandGradient" StartPoint="0,0" EndPoint="1,0">
|
||||
<GradientStop Color="#58D5FF" Offset="0"/>
|
||||
<GradientStop Color="#5C87FF" Offset="1"/>
|
||||
</LinearGradientBrush>
|
||||
|
||||
<Style x:Key="PageTitle" TargetType="TextBlock">
|
||||
<Setter Property="FontFamily" Value="{DynamicResource DisplayFont}"/>
|
||||
<Setter Property="FontSize" Value="26"/>
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
<Setter Property="Margin" Value="0,0,0,8"/>
|
||||
</Style>
|
||||
<Style x:Key="Body" TargetType="TextBlock">
|
||||
<Setter Property="TextWrapping" Value="Wrap"/>
|
||||
<Setter Property="LineHeight" Value="21"/>
|
||||
<Setter Property="Foreground" Value="{DynamicResource TextSecondaryBrush}"/>
|
||||
</Style>
|
||||
<Style x:Key="StepDot" TargetType="Border">
|
||||
<Setter Property="Width" Value="26"/>
|
||||
<Setter Property="Height" Value="26"/>
|
||||
<Setter Property="CornerRadius" Value="13"/>
|
||||
<Setter Property="BorderThickness" Value="1.5"/>
|
||||
</Style>
|
||||
<Style x:Key="StepNum" TargetType="TextBlock">
|
||||
<Setter Property="FontSize" Value="12"/>
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Center"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
</Style>
|
||||
<Style x:Key="StepLabel" TargetType="TextBlock">
|
||||
<Setter Property="Margin" Value="12,0,0,0"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
<Setter Property="FontSize" Value="13.5"/>
|
||||
</Style>
|
||||
<Style x:Key="StepLine" TargetType="Border">
|
||||
<Setter Property="Width" Value="2"/>
|
||||
<Setter Property="Height" Value="18"/>
|
||||
<Setter Property="Margin" Value="12,3,0,3"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Left"/>
|
||||
<Setter Property="CornerRadius" Value="1"/>
|
||||
</Style>
|
||||
<Style x:Key="FeatureIcon" TargetType="Border">
|
||||
<Setter Property="Width" Value="38"/>
|
||||
<Setter Property="Height" Value="38"/>
|
||||
<Setter Property="CornerRadius" Value="10"/>
|
||||
<Setter Property="Background" Value="{DynamicResource AccentSubtleBrush}"/>
|
||||
<Setter Property="VerticalAlignment" Value="Top"/>
|
||||
</Style>
|
||||
<Style x:Key="FeatureGlyph" TargetType="TextBlock">
|
||||
<Setter Property="FontFamily" Value="{DynamicResource IconFont}"/>
|
||||
<Setter Property="FontSize" Value="17"/>
|
||||
<Setter Property="Foreground" Value="{DynamicResource AccentBrush}"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Center"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
</Style>
|
||||
<Style x:Key="FeatureTitle" TargetType="TextBlock">
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
<Setter Property="FontSize" Value="13.5"/>
|
||||
</Style>
|
||||
<Style x:Key="FeatureText" TargetType="TextBlock">
|
||||
<Setter Property="FontSize" Value="12"/>
|
||||
<Setter Property="TextWrapping" Value="Wrap"/>
|
||||
<Setter Property="Margin" Value="0,2,0,0"/>
|
||||
<Setter Property="Foreground" Value="{DynamicResource TextSecondaryBrush}"/>
|
||||
</Style>
|
||||
<Style x:Key="Card" TargetType="Border">
|
||||
<Setter Property="Padding" Value="16,14"/>
|
||||
<Setter Property="CornerRadius" Value="8"/>
|
||||
<Setter Property="Background" Value="{DynamicResource ControlFillBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource ControlStrokeBrush}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
</Style>
|
||||
</Window.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="36"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="250"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- ===== Seitenleiste im Look der App ===== -->
|
||||
<Border Grid.RowSpan="3" ClipToBounds="True">
|
||||
<Border.Background>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="0.6,1">
|
||||
<GradientStop Color="#07111F" Offset="0"/>
|
||||
<GradientStop Color="#0D1729" Offset="0.5"/>
|
||||
<GradientStop Color="#13305C" Offset="1"/>
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
<Grid>
|
||||
<!-- Lichtschein hinter dem Logo und unten -->
|
||||
<Ellipse Width="320" Height="320" Margin="-120,-110,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" IsHitTestVisible="False">
|
||||
<Ellipse.Fill>
|
||||
<RadialGradientBrush>
|
||||
<GradientStop Color="#5558D5FF" Offset="0"/>
|
||||
<GradientStop Color="#0058D5FF" Offset="1"/>
|
||||
</RadialGradientBrush>
|
||||
</Ellipse.Fill>
|
||||
</Ellipse>
|
||||
<Ellipse Width="300" Height="300" Margin="0,0,-150,-150" HorizontalAlignment="Right" VerticalAlignment="Bottom" IsHitTestVisible="False">
|
||||
<Ellipse.Fill>
|
||||
<RadialGradientBrush>
|
||||
<GradientStop Color="#445C87FF" Offset="0"/>
|
||||
<GradientStop Color="#005C87FF" Offset="1"/>
|
||||
</RadialGradientBrush>
|
||||
</Ellipse.Fill>
|
||||
</Ellipse>
|
||||
|
||||
<StackPanel Margin="30,64,24,0">
|
||||
<Image Source="app.png" Width="68" Height="68" HorizontalAlignment="Left" RenderOptions.BitmapScalingMode="HighQuality">
|
||||
<Image.Effect>
|
||||
<DropShadowEffect Color="#58D5FF" BlurRadius="28" ShadowDepth="0" Opacity="0.6"/>
|
||||
</Image.Effect>
|
||||
</Image>
|
||||
<TextBlock Text="Git Manager" FontFamily="{DynamicResource DisplayFont}" FontSize="26" FontWeight="SemiBold"
|
||||
Foreground="{StaticResource SideText}" Margin="0,16,0,0"/>
|
||||
<TextBlock x:Name="VersionText" Foreground="{StaticResource SideTextSecondary}" FontSize="12" Margin="0,2,0,0"/>
|
||||
|
||||
<StackPanel x:Name="Steps" Margin="0,38,0,0">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Border x:Name="Dot0" Style="{StaticResource StepDot}"><TextBlock x:Name="Num0" Text="1" Style="{StaticResource StepNum}"/></Border>
|
||||
<TextBlock x:Name="Label0" Text="Willkommen" Style="{StaticResource StepLabel}"/>
|
||||
</StackPanel>
|
||||
<Border x:Name="Line0" Style="{StaticResource StepLine}"/>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Border x:Name="Dot1" Style="{StaticResource StepDot}"><TextBlock x:Name="Num1" Text="2" Style="{StaticResource StepNum}"/></Border>
|
||||
<TextBlock x:Name="Label1" Text="Optionen" Style="{StaticResource StepLabel}"/>
|
||||
</StackPanel>
|
||||
<Border x:Name="Line1" Style="{StaticResource StepLine}"/>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Border x:Name="Dot2" Style="{StaticResource StepDot}"><TextBlock x:Name="Num2" Text="3" Style="{StaticResource StepNum}"/></Border>
|
||||
<TextBlock x:Name="Label2" Text="Installation" Style="{StaticResource StepLabel}"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Text="© M_Viper" FontSize="11" Foreground="{StaticResource SideTextMuted}"
|
||||
VerticalAlignment="Bottom" Margin="30,0,0,20"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- Titelleiste -->
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="14,0,0,0">
|
||||
<Image Source="app.ico" Width="16" Height="16" RenderOptions.BitmapScalingMode="HighQuality"/>
|
||||
<TextBlock x:Name="WindowTitle" Text="Git Manager GUI Setup" FontSize="12" Margin="10,0,0,0" VerticalAlignment="Center"
|
||||
Foreground="{StaticResource SideTextSecondary}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Top">
|
||||
<Button Style="{StaticResource CaptionButton}" Content="" Click="Minimize_Click"
|
||||
WindowChrome.IsHitTestVisibleInChrome="True"/>
|
||||
<Button x:Name="CloseButton" Style="{StaticResource CloseCaptionButton}" Content="" Click="Cancel_Click"
|
||||
WindowChrome.IsHitTestVisibleInChrome="True"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- ===== Inhalt ===== -->
|
||||
<Grid Grid.Row="1" Grid.Column="1" Margin="40,18,40,0">
|
||||
|
||||
<!-- 0: Willkommen -->
|
||||
<StackPanel x:Name="PageWelcome">
|
||||
<TextBlock Text="Willkommen beim Git Manager" Style="{StaticResource PageTitle}"/>
|
||||
<TextBlock Style="{StaticResource Body}"
|
||||
Text="Der Assistent richtet Git Manager GUI in wenigen Schritten ein – nur für dein Benutzerkonto und ohne Administratorrechte."/>
|
||||
|
||||
<Grid Margin="0,24,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="20"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="18"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<DockPanel>
|
||||
<Border Style="{StaticResource FeatureIcon}" DockPanel.Dock="Left"><TextBlock Text="" Style="{StaticResource FeatureGlyph}"/></Border>
|
||||
<StackPanel Margin="12,0,0,0">
|
||||
<TextBlock Text="Gitea & GitHub" Style="{StaticResource FeatureTitle}"/>
|
||||
<TextBlock Text="Repos, Dateien, Releases und Commits an einem Ort" Style="{StaticResource FeatureText}"/>
|
||||
</StackPanel>
|
||||
</DockPanel>
|
||||
<DockPanel Grid.Column="2">
|
||||
<Border Style="{StaticResource FeatureIcon}" DockPanel.Dock="Left"><TextBlock Text="" Style="{StaticResource FeatureGlyph}"/></Border>
|
||||
<StackPanel Margin="12,0,0,0">
|
||||
<TextBlock Text="Git eingebaut" Style="{StaticResource FeatureTitle}"/>
|
||||
<TextBlock Text="Kein zusätzliches Programm nötig" Style="{StaticResource FeatureText}"/>
|
||||
</StackPanel>
|
||||
</DockPanel>
|
||||
<DockPanel Grid.Row="2">
|
||||
<Border Style="{StaticResource FeatureIcon}" DockPanel.Dock="Left"><TextBlock Text="" Style="{StaticResource FeatureGlyph}"/></Border>
|
||||
<StackPanel Margin="12,0,0,0">
|
||||
<TextBlock Text="PRs & Issues" Style="{StaticResource FeatureTitle}"/>
|
||||
<TextBlock Text="Ansehen, kommentieren und mergen" Style="{StaticResource FeatureText}"/>
|
||||
</StackPanel>
|
||||
</DockPanel>
|
||||
<DockPanel Grid.Row="2" Grid.Column="2">
|
||||
<Border Style="{StaticResource FeatureIcon}" DockPanel.Dock="Left"><TextBlock Text="" Style="{StaticResource FeatureGlyph}"/></Border>
|
||||
<StackPanel Margin="12,0,0,0">
|
||||
<TextBlock Text="Backup" Style="{StaticResource FeatureTitle}"/>
|
||||
<TextBlock Text="Alle Repos sichern und wiederherstellen" Style="{StaticResource FeatureText}"/>
|
||||
</StackPanel>
|
||||
</DockPanel>
|
||||
</Grid>
|
||||
|
||||
<Border x:Name="UpdateHint" Margin="0,26,0,0" Padding="14,10" CornerRadius="8" Visibility="Collapsed"
|
||||
Background="{DynamicResource AccentSubtleBrush}" BorderBrush="{DynamicResource AccentSubtleStrokeBrush}" BorderThickness="1">
|
||||
<DockPanel>
|
||||
<TextBlock DockPanel.Dock="Left" Text="" FontFamily="{DynamicResource IconFont}" FontSize="16"
|
||||
Foreground="{DynamicResource AccentBrush}" Margin="0,2,12,0" VerticalAlignment="Top"/>
|
||||
<TextBlock x:Name="UpdateHintText" TextWrapping="Wrap" LineHeight="20" FontSize="13"/>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
<Border x:Name="RunningHint" Margin="0,10,0,0" Padding="14,10" CornerRadius="8" Visibility="Collapsed"
|
||||
Background="{DynamicResource ControlFillBrush}" BorderBrush="{DynamicResource ControlStrokeBrush}" BorderThickness="1">
|
||||
<DockPanel>
|
||||
<TextBlock DockPanel.Dock="Left" Text="" FontFamily="{DynamicResource IconFont}" FontSize="16"
|
||||
Foreground="{DynamicResource AccentBrush}" Margin="0,2,12,0" VerticalAlignment="Top"/>
|
||||
<TextBlock TextWrapping="Wrap" LineHeight="20" FontSize="13"
|
||||
Text="Git Manager GUI läuft gerade und wird für die Installation automatisch beendet. Laufende Uploads gehen dabei verloren."/>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 1: Optionen -->
|
||||
<StackPanel x:Name="PageOptions" Visibility="Collapsed">
|
||||
<TextBlock Text="Optionen" Style="{StaticResource PageTitle}"/>
|
||||
<TextBlock Style="{StaticResource Body}" Text="Wohin soll Git Manager GUI installiert werden?"/>
|
||||
|
||||
<Border Style="{StaticResource Card}" Margin="0,18,0,0">
|
||||
<StackPanel>
|
||||
<TextBlock Text="Installationsordner" FontWeight="SemiBold" FontSize="13" Margin="0,0,0,10"/>
|
||||
<DockPanel>
|
||||
<Button DockPanel.Dock="Right" Content="Ändern …" Margin="8,0,0,0" Click="ChangeDir_Click"/>
|
||||
<TextBox x:Name="DirBox"/>
|
||||
</DockPanel>
|
||||
<TextBlock Style="{StaticResource Body}" FontSize="12" LineHeight="18" Margin="0,10,0,0"
|
||||
Text="Benötigt ca. 450 MB. Zugangsdaten, Einstellungen und Favoriten liegen getrennt davon und bleiben bei Updates erhalten."/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border Style="{StaticResource Card}" Margin="0,12,0,0">
|
||||
<StackPanel>
|
||||
<CheckBox x:Name="DesktopLinkBox" Content="Verknüpfung auf dem Desktop" IsChecked="True"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 2: Fortschritt / Fertig -->
|
||||
<StackPanel x:Name="PageProgress" Visibility="Collapsed">
|
||||
<Border x:Name="SuccessIcon" Width="64" Height="64" CornerRadius="32" HorizontalAlignment="Left" Margin="0,4,0,20"
|
||||
Background="{StaticResource BrandGradient}" RenderTransformOrigin="0.5,0.5" Visibility="Collapsed">
|
||||
<Border.RenderTransform><ScaleTransform x:Name="SuccessScale" ScaleX="1" ScaleY="1"/></Border.RenderTransform>
|
||||
<Border.Effect><DropShadowEffect Color="#58D5FF" BlurRadius="24" ShadowDepth="0" Opacity="0.5"/></Border.Effect>
|
||||
<TextBlock Text="" FontFamily="{DynamicResource IconFont}" FontSize="26" Foreground="#04101F"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<TextBlock x:Name="ProgressTitle" Text="Git Manager GUI wird installiert …" Style="{StaticResource PageTitle}"/>
|
||||
|
||||
<StackPanel x:Name="ProgressBarPanel" Margin="0,18,0,0">
|
||||
<TextBlock x:Name="PercentText" Text="0 %" FontFamily="{DynamicResource DisplayFont}" FontSize="44" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource AccentBrush}"/>
|
||||
<Border x:Name="ProgressTrack" SizeChanged="ProgressTrack_SizeChanged" Height="8" CornerRadius="4" Margin="0,10,0,0" Background="{DynamicResource SubtleSelectedBrush}">
|
||||
<Border x:Name="ProgressFill" HorizontalAlignment="Left" Width="0" CornerRadius="4" Background="{StaticResource BrandGradient}"/>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
<TextBlock x:Name="ProgressText" Style="{StaticResource Body}" Margin="0,12,0,0" Text="Einen Moment bitte."/>
|
||||
|
||||
<StackPanel x:Name="DonePanel" Visibility="Collapsed">
|
||||
<CheckBox x:Name="LaunchBox" Content="Git Manager GUI jetzt starten" IsChecked="True" Margin="0,24,0,0"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Deinstallation -->
|
||||
<StackPanel x:Name="PageUninstall" Visibility="Collapsed">
|
||||
<TextBlock Text="Git Manager GUI deinstallieren" Style="{StaticResource PageTitle}"/>
|
||||
<TextBlock Style="{StaticResource Body}"
|
||||
Text="Git Manager GUI wird von deinem PC entfernt: Programm, Verknüpfungen, Autostart und der Eintrag in den Windows-Einstellungen."/>
|
||||
<Border Style="{StaticResource Card}" Margin="0,22,0,0">
|
||||
<StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="" FontFamily="{DynamicResource IconFont}" FontSize="15" Foreground="{DynamicResource AccentBrush}"
|
||||
VerticalAlignment="Center"/>
|
||||
<TextBlock Text="Deine Zugangsdaten und Einstellungen bleiben erhalten" FontWeight="SemiBold" Margin="10,0,0,0" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
<TextBlock x:Name="DataInfo" Style="{StaticResource Body}" FontSize="12.5" Margin="0,6,0,14"/>
|
||||
<CheckBox x:Name="RemoveDataBox" Content="Auch Zugangsdaten, Einstellungen, Favoriten und Verlauf löschen"
|
||||
Checked="RemoveDataBox_Click" Unchecked="RemoveDataBox_Click"/>
|
||||
<TextBlock x:Name="RemoveDataWarning" Visibility="Collapsed" Margin="30,8,0,0" TextWrapping="Wrap" FontSize="12.5"
|
||||
Foreground="{DynamicResource DangerBrush}"
|
||||
Text="Die gespeicherten Tokens für Gitea und GitHub werden entfernt (sie landen im Papierkorb). Deine Repos auf dem Server, lokale Projektordner und Repo-Backups bleiben unangetastet."/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- Fußleiste -->
|
||||
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,14,40,24">
|
||||
<Button x:Name="BackButton" Content="Zurück" Width="110" Margin="0,0,8,0" Click="Back_Click"/>
|
||||
<Button x:Name="NextButton" Content="Weiter" Width="140" Margin="0,0,8,0" Style="{StaticResource AccentButton}" IsDefault="True" Click="Next_Click"/>
|
||||
<Button x:Name="CancelButton" Content="Abbrechen" Width="110" Click="Cancel_Click"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Eigene Rückfrage im Setup-Look statt Windows-Meldungsfenster (siehe Frage()) -->
|
||||
<Grid x:Name="DialogOverlay" Grid.RowSpan="3" Grid.ColumnSpan="2" Visibility="Collapsed" Background="#99000000">
|
||||
<Border x:Name="DialogCard" Width="420" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
Background="{DynamicResource LayerBrush}" BorderBrush="{DynamicResource ControlStrokeBrush}" BorderThickness="1"
|
||||
CornerRadius="10" Padding="26,22,22,18" Effect="{StaticResource DialogShadow}" RenderTransformOrigin="0.5,0.5">
|
||||
<Border.RenderTransform><ScaleTransform x:Name="DialogScale" ScaleX="1" ScaleY="1"/></Border.RenderTransform>
|
||||
<StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Border Width="34" Height="34" CornerRadius="17" Background="{DynamicResource AccentSubtleBrush}" VerticalAlignment="Top">
|
||||
<TextBlock x:Name="DialogIcon" Text="" FontFamily="{DynamicResource IconFont}" FontSize="16"
|
||||
Foreground="{DynamicResource AccentBrush}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<TextBlock x:Name="DialogTitle" FontFamily="{DynamicResource DisplayFont}" FontSize="18" FontWeight="SemiBold"
|
||||
VerticalAlignment="Center" Margin="12,0,0,0" TextWrapping="Wrap" MaxWidth="320"/>
|
||||
</StackPanel>
|
||||
<TextBlock x:Name="DialogText" TextWrapping="Wrap" LineHeight="20" Margin="46,8,0,0"
|
||||
Foreground="{DynamicResource TextSecondaryBrush}"/>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,22,0,0">
|
||||
<Button x:Name="DialogSecondary" MinWidth="120" Margin="0,0,8,0" Click="DialogSecondary_Click"/>
|
||||
<Button x:Name="DialogPrimary" MinWidth="130" Style="{StaticResource AccentButton}" Click="DialogPrimary_Click"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Text;
|
||||
|
||||
namespace GitManagerSetup;
|
||||
|
||||
/// <summary>
|
||||
/// Die App liegt als ZIP hinten an der Setup-.exe angehängt:
|
||||
/// [Setup.exe][ZIP][8 Byte: ZIP-Länge][8 Byte: "GITMGRPK"]
|
||||
/// Windows ignoriert angehängte Daten beim Starten einer .exe.
|
||||
/// </summary>
|
||||
public static class Payload
|
||||
{
|
||||
private static readonly byte[] Magic = Encoding.ASCII.GetBytes("GITMGRPK");
|
||||
private const int FooterSize = 16;
|
||||
|
||||
private static (long Offset, long Length)? _info;
|
||||
private static bool _checked;
|
||||
|
||||
/// <summary>Position und Länge des ZIPs in dieser .exe, oder null (z. B. beim Deinstallationsprogramm).</summary>
|
||||
public static (long Offset, long Length)? Info
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_checked) return _info;
|
||||
_checked = true;
|
||||
try
|
||||
{
|
||||
using var f = File.OpenRead(Installer.SelfPath);
|
||||
if (f.Length < FooterSize) return null;
|
||||
f.Seek(-FooterSize, SeekOrigin.End);
|
||||
var footer = new byte[FooterSize];
|
||||
if (f.Read(footer, 0, FooterSize) != FooterSize) return null;
|
||||
for (int i = 0; i < Magic.Length; i++)
|
||||
if (footer[8 + i] != Magic[i]) return null;
|
||||
long length = BitConverter.ToInt64(footer, 0);
|
||||
long offset = f.Length - FooterSize - length;
|
||||
if (length <= 0 || offset <= 0) return null;
|
||||
_info = (offset, length);
|
||||
}
|
||||
catch { _info = null; }
|
||||
return _info;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool Exists => Info is not null;
|
||||
|
||||
/// <summary>Länge des eigentlichen Setup-Programms ohne Anhang.</summary>
|
||||
public static long StubLength => Info?.Offset ?? new FileInfo(Installer.SelfPath).Length;
|
||||
|
||||
public static ZipArchive OpenArchive()
|
||||
{
|
||||
var info = Info ?? throw new InvalidOperationException("Die Installationsdaten fehlen in dieser Datei. Bitte das Setup neu herunterladen.");
|
||||
var file = File.OpenRead(Installer.SelfPath);
|
||||
return new ZipArchive(new SubStream(file, info.Offset, info.Length), ZipArchiveMode.Read, leaveOpen: false);
|
||||
}
|
||||
|
||||
/// <summary>Nur-Lese-Ausschnitt einer Datei, damit ZipArchive den Anhang wie eine eigene Datei sieht.</summary>
|
||||
private sealed class SubStream(Stream inner, long start, long length) : Stream
|
||||
{
|
||||
private long _pos;
|
||||
|
||||
public override bool CanRead => true;
|
||||
public override bool CanSeek => true;
|
||||
public override bool CanWrite => false;
|
||||
public override long Length => length;
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get => _pos;
|
||||
set => _pos = Math.Max(0, Math.Min(value, length));
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
long left = length - _pos;
|
||||
if (left <= 0) return 0;
|
||||
if (count > left) count = (int)left;
|
||||
inner.Position = start + _pos;
|
||||
int n = inner.Read(buffer, offset, count);
|
||||
_pos += n;
|
||||
return n;
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
Position = origin switch
|
||||
{
|
||||
SeekOrigin.Begin => offset,
|
||||
SeekOrigin.Current => _pos + offset,
|
||||
_ => length + offset,
|
||||
};
|
||||
return _pos;
|
||||
}
|
||||
|
||||
public override void Flush() { }
|
||||
public override void SetLength(long value) => throw new NotSupportedException();
|
||||
public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing) inner.Dispose();
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
using System.Text;
|
||||
|
||||
namespace GitManagerSetup;
|
||||
|
||||
/// <summary>
|
||||
/// Verknüpfungen (.lnk) über die Shell-Schnittstellen – inklusive AppUserModelID, damit
|
||||
/// Taskleiste und Windows-Benachrichtigungen dem Git Manager wie bisher zuordnen (wie beim alten NSIS-Setup).
|
||||
/// </summary>
|
||||
public static class Shortcut
|
||||
{
|
||||
public static void Create(string link, string target, string description, string appUserModelId)
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(link)!);
|
||||
var shellLink = (IShellLinkW)new CShellLink();
|
||||
try
|
||||
{
|
||||
shellLink.SetPath(target);
|
||||
shellLink.SetWorkingDirectory(Path.GetDirectoryName(target)!);
|
||||
shellLink.SetIconLocation(target, 0);
|
||||
shellLink.SetDescription(description);
|
||||
|
||||
var store = (IPropertyStore)shellLink;
|
||||
var key = new PropertyKey(new Guid("9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3"), 5); // PKEY_AppUserModel_ID
|
||||
var value = new PropVariant { vt = 31 /* VT_LPWSTR */, p = Marshal.StringToCoTaskMemUni(appUserModelId) };
|
||||
try
|
||||
{
|
||||
store.SetValue(ref key, ref value);
|
||||
store.Commit();
|
||||
}
|
||||
finally { Marshal.FreeCoTaskMem(value.p); }
|
||||
|
||||
((IPersistFile)shellLink).Save(link, true);
|
||||
}
|
||||
finally { Marshal.ReleaseComObject(shellLink); }
|
||||
}
|
||||
|
||||
/// <summary>Ziel einer vorhandenen Verknüpfung, oder null.</summary>
|
||||
public static string? Target(string link)
|
||||
{
|
||||
if (!File.Exists(link)) return null;
|
||||
var shellLink = (IShellLinkW)new CShellLink();
|
||||
try
|
||||
{
|
||||
((IPersistFile)shellLink).Load(link, 0);
|
||||
var sb = new StringBuilder(1024);
|
||||
shellLink.GetPath(sb, sb.Capacity, IntPtr.Zero, 0);
|
||||
return sb.ToString();
|
||||
}
|
||||
catch { return null; }
|
||||
finally { Marshal.ReleaseComObject(shellLink); }
|
||||
}
|
||||
|
||||
// ---------- COM ----------
|
||||
|
||||
[ComImport, Guid("00021401-0000-0000-C000-000000000046")]
|
||||
private class CShellLink { }
|
||||
|
||||
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("000214F9-0000-0000-C000-000000000046")]
|
||||
private interface IShellLinkW
|
||||
{
|
||||
void GetPath([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, int cchMaxPath, IntPtr pfd, uint fFlags);
|
||||
void GetIDList(out IntPtr ppidl);
|
||||
void SetIDList(IntPtr pidl);
|
||||
void GetDescription([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszName, int cchMaxName);
|
||||
void SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName);
|
||||
void GetWorkingDirectory([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir, int cchMaxPath);
|
||||
void SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir);
|
||||
void GetArguments([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszArgs, int cchMaxPath);
|
||||
void SetArguments([MarshalAs(UnmanagedType.LPWStr)] string pszArgs);
|
||||
void GetHotkey(out short pwHotkey);
|
||||
void SetHotkey(short wHotkey);
|
||||
void GetShowCmd(out int piShowCmd);
|
||||
void SetShowCmd(int iShowCmd);
|
||||
void GetIconLocation([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszIconPath, int cchIconPath, out int piIcon);
|
||||
void SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon);
|
||||
void SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, uint dwReserved);
|
||||
void Resolve(IntPtr hwnd, uint fFlags);
|
||||
void SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile);
|
||||
}
|
||||
|
||||
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99")]
|
||||
private interface IPropertyStore
|
||||
{
|
||||
void GetCount(out uint cProps);
|
||||
void GetAt(uint iProp, out PropertyKey pkey);
|
||||
void GetValue(ref PropertyKey key, out PropVariant pv);
|
||||
void SetValue(ref PropertyKey key, ref PropVariant pv);
|
||||
void Commit();
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||
private struct PropertyKey(Guid fmtid, uint pid)
|
||||
{
|
||||
public Guid fmtid = fmtid;
|
||||
public uint pid = pid;
|
||||
}
|
||||
|
||||
// PROPVARIANT: 16 Byte (32 Bit) bzw. 24 Byte (64 Bit)
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct PropVariant
|
||||
{
|
||||
public ushort vt;
|
||||
public ushort r1, r2, r3;
|
||||
public IntPtr p;
|
||||
public IntPtr p2;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<SolidColorBrush x:Key="BaseBrush" Color="#202020"/>
|
||||
<SolidColorBrush x:Key="LayerBrush" Color="#282828"/>
|
||||
<SolidColorBrush x:Key="LayerStrokeBrush" Color="#1A1A1A"/>
|
||||
<SolidColorBrush x:Key="SubtleHoverBrush" Color="#0FFFFFFF"/>
|
||||
<SolidColorBrush x:Key="SubtlePressedBrush" Color="#0AFFFFFF"/>
|
||||
<SolidColorBrush x:Key="SubtleSelectedBrush" Color="#14FFFFFF"/>
|
||||
<SolidColorBrush x:Key="ControlFillBrush" Color="#2D2D2D"/>
|
||||
<SolidColorBrush x:Key="ControlHoverBrush" Color="#323232"/>
|
||||
<SolidColorBrush x:Key="ControlPressedBrush" Color="#272727"/>
|
||||
<SolidColorBrush x:Key="ControlStrokeBrush" Color="#353535"/>
|
||||
<SolidColorBrush x:Key="ControlStrongStrokeBrush" Color="#8A8A8A"/>
|
||||
<SolidColorBrush x:Key="InputFocusBrush" Color="#1F1F1F"/>
|
||||
<SolidColorBrush x:Key="TextBrush" Color="#FFFFFF"/>
|
||||
<SolidColorBrush x:Key="TextSecondaryBrush" Color="#C8C8C8"/>
|
||||
<SolidColorBrush x:Key="TextTertiaryBrush" Color="#9A9A9A"/>
|
||||
<SolidColorBrush x:Key="FlyoutBrush" Color="#2C2C2C"/>
|
||||
<SolidColorBrush x:Key="FlyoutStrokeBrush" Color="#404040"/>
|
||||
<SolidColorBrush x:Key="DialogFooterBrush" Color="#202020"/>
|
||||
<SolidColorBrush x:Key="ScrollThumbBrush" Color="#9F9F9F"/>
|
||||
<SolidColorBrush x:Key="OverlayBrush" Color="#66000000"/>
|
||||
<SolidColorBrush x:Key="SuccessBrush" Color="#4CAF50"/>
|
||||
<SolidColorBrush x:Key="DangerBrush" Color="#FF99A4"/>
|
||||
<SolidColorBrush x:Key="FenceBrush" Color="#F21C1C1C"/>
|
||||
<Color x:Key="ShadowColor">#000000</Color>
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,27 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<SolidColorBrush x:Key="BaseBrush" Color="#F3F3F3"/>
|
||||
<SolidColorBrush x:Key="LayerBrush" Color="#FAFAFA"/>
|
||||
<SolidColorBrush x:Key="LayerStrokeBrush" Color="#E5E5E5"/>
|
||||
<SolidColorBrush x:Key="SubtleHoverBrush" Color="#0A000000"/>
|
||||
<SolidColorBrush x:Key="SubtlePressedBrush" Color="#06000000"/>
|
||||
<SolidColorBrush x:Key="SubtleSelectedBrush" Color="#0F000000"/>
|
||||
<SolidColorBrush x:Key="ControlFillBrush" Color="#FDFDFD"/>
|
||||
<SolidColorBrush x:Key="ControlHoverBrush" Color="#F6F6F6"/>
|
||||
<SolidColorBrush x:Key="ControlPressedBrush" Color="#F2F2F2"/>
|
||||
<SolidColorBrush x:Key="ControlStrokeBrush" Color="#E3E3E3"/>
|
||||
<SolidColorBrush x:Key="ControlStrongStrokeBrush" Color="#C4C4C4"/>
|
||||
<SolidColorBrush x:Key="InputFocusBrush" Color="#FFFFFF"/>
|
||||
<SolidColorBrush x:Key="TextBrush" Color="#1B1B1B"/>
|
||||
<SolidColorBrush x:Key="TextSecondaryBrush" Color="#5D5D5D"/>
|
||||
<SolidColorBrush x:Key="TextTertiaryBrush" Color="#8B8B8B"/>
|
||||
<SolidColorBrush x:Key="FlyoutBrush" Color="#F9F9F9"/>
|
||||
<SolidColorBrush x:Key="FlyoutStrokeBrush" Color="#DCDCDC"/>
|
||||
<SolidColorBrush x:Key="DialogFooterBrush" Color="#F3F3F3"/>
|
||||
<SolidColorBrush x:Key="ScrollThumbBrush" Color="#8A8A8A"/>
|
||||
<SolidColorBrush x:Key="OverlayBrush" Color="#4D000000"/>
|
||||
<SolidColorBrush x:Key="SuccessBrush" Color="#0F7B0F"/>
|
||||
<SolidColorBrush x:Key="DangerBrush" Color="#C42B1C"/>
|
||||
<SolidColorBrush x:Key="FenceBrush" Color="#F5F7F7F7"/>
|
||||
<Color x:Key="ShadowColor">#000000</Color>
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,651 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
|
||||
<!-- Schriften: Windows 11 nutzt "Segoe UI Variable" / "Segoe Fluent Icons", Windows 10 fällt auf Segoe UI / MDL2 zurück -->
|
||||
<FontFamily x:Key="UiFont">Segoe UI Variable Text, Segoe UI</FontFamily>
|
||||
<FontFamily x:Key="DisplayFont">Segoe UI Variable Display, Segoe UI</FontFamily>
|
||||
<FontFamily x:Key="IconFont">Segoe Fluent Icons, Segoe MDL2 Assets</FontFamily>
|
||||
|
||||
<DropShadowEffect x:Key="FlyoutShadow" BlurRadius="24" ShadowDepth="6" Direction="270" Opacity="0.18"
|
||||
Color="Black"/>
|
||||
<DropShadowEffect x:Key="DialogShadow" BlurRadius="40" ShadowDepth="12" Direction="270" Opacity="0.28"
|
||||
Color="Black"/>
|
||||
|
||||
<!-- ===== Buttons ===== -->
|
||||
<Style TargetType="Button">
|
||||
<Setter Property="FontFamily" Value="{DynamicResource UiFont}"/>
|
||||
<Setter Property="FontSize" Value="14"/>
|
||||
<Setter Property="Foreground" Value="{DynamicResource TextBrush}"/>
|
||||
<Setter Property="Background" Value="{DynamicResource ControlFillBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource ControlStrokeBrush}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="Padding" Value="14,5"/>
|
||||
<Setter Property="MinHeight" Value="32"/>
|
||||
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="Bd" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="4" Padding="{TemplateBinding Padding}"
|
||||
SnapsToDevicePixels="True">
|
||||
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="Center" RecognizesAccessKey="True"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="{DynamicResource ControlHoverBrush}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="{DynamicResource ControlPressedBrush}"/>
|
||||
<Setter Property="Opacity" Value="0.85"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsKeyboardFocused" Value="True">
|
||||
<Setter TargetName="Bd" Property="BorderBrush" Value="{DynamicResource TextBrush}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter Property="Opacity" Value="0.4"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="AccentButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
|
||||
<Setter Property="Foreground" Value="{DynamicResource OnAccentBrush}"/>
|
||||
<Setter Property="Background" Value="{DynamicResource AccentBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource AccentBrush}"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="Bd" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="1" CornerRadius="4" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="True">
|
||||
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="Center" RecognizesAccessKey="True"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="{DynamicResource AccentHoverBrush}"/>
|
||||
<Setter TargetName="Bd" Property="BorderBrush" Value="{DynamicResource AccentHoverBrush}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter Property="Opacity" Value="0.8"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsKeyboardFocused" Value="True">
|
||||
<Setter TargetName="Bd" Property="BorderBrush" Value="{DynamicResource TextBrush}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter Property="Opacity" Value="0.4"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- Transparenter Button (Symbolleisten, Sidebar) -->
|
||||
<Style x:Key="SubtleButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Padding" Value="9,6"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="Bd" Background="{TemplateBinding Background}" CornerRadius="4" Padding="{TemplateBinding Padding}">
|
||||
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="{DynamicResource SubtleHoverBrush}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="{DynamicResource SubtlePressedBrush}"/>
|
||||
<Setter Property="Opacity" Value="0.75"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter Property="Opacity" Value="0.35"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- Titelleisten-Buttons -->
|
||||
<Style x:Key="CaptionButton" TargetType="Button">
|
||||
<Setter Property="Width" Value="46"/>
|
||||
<Setter Property="Height" Value="32"/>
|
||||
<Setter Property="FontFamily" Value="{DynamicResource IconFont}"/>
|
||||
<Setter Property="FontSize" Value="10"/>
|
||||
<Setter Property="Foreground" Value="{DynamicResource TextBrush}"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
|
||||
<Setter Property="IsTabStop" Value="False"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="Bd" Background="{TemplateBinding Background}">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="{DynamicResource SubtleHoverBrush}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="{DynamicResource SubtlePressedBrush}"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="CloseCaptionButton" TargetType="Button" BasedOn="{StaticResource CaptionButton}">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="Bd" Background="Transparent">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="#C42B1C"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="#C83C30"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- ===== TextBox ===== -->
|
||||
<Style TargetType="TextBox">
|
||||
<Setter Property="FontFamily" Value="{DynamicResource UiFont}"/>
|
||||
<Setter Property="FontSize" Value="14"/>
|
||||
<Setter Property="Foreground" Value="{DynamicResource TextBrush}"/>
|
||||
<Setter Property="CaretBrush" Value="{DynamicResource TextBrush}"/>
|
||||
<Setter Property="SelectionBrush" Value="{DynamicResource AccentBrush}"/>
|
||||
<Setter Property="Background" Value="{DynamicResource ControlFillBrush}"/>
|
||||
<Setter Property="Padding" Value="10,6"/>
|
||||
<Setter Property="MinHeight" Value="32"/>
|
||||
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="TextBox">
|
||||
<Grid>
|
||||
<Border x:Name="Bd" Background="{TemplateBinding Background}" CornerRadius="4"
|
||||
BorderBrush="{DynamicResource ControlStrokeBrush}" BorderThickness="1"/>
|
||||
<!-- Unterstrich wie bei Windows 11 -->
|
||||
<Border x:Name="Line" Height="1" VerticalAlignment="Bottom" Margin="3,0" CornerRadius="0,0,4,4"
|
||||
Background="{DynamicResource ControlStrongStrokeBrush}"/>
|
||||
<ScrollViewer x:Name="PART_ContentHost" Margin="{TemplateBinding Padding}" VerticalAlignment="Center"
|
||||
Focusable="False" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden"/>
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="{DynamicResource ControlHoverBrush}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsKeyboardFocused" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="{DynamicResource InputFocusBrush}"/>
|
||||
<Setter TargetName="Line" Property="Height" Value="2"/>
|
||||
<Setter TargetName="Line" Property="Margin" Value="1,0"/>
|
||||
<Setter TargetName="Line" Property="Background" Value="{DynamicResource AccentBrush}"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- ===== Scrollbar (schmal, wird beim Überfahren breiter) ===== -->
|
||||
<Style x:Key="ScrollThumb" TargetType="Thumb">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Thumb">
|
||||
<Border Background="{DynamicResource ScrollThumbBrush}" CornerRadius="3"/>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="ScrollBar">
|
||||
<Setter Property="Width" Value="12"/>
|
||||
<Setter Property="MinWidth" Value="12"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ScrollBar">
|
||||
<Grid Background="Transparent">
|
||||
<Track x:Name="PART_Track" IsDirectionReversed="True">
|
||||
<Track.Thumb>
|
||||
<Thumb x:Name="Th" Style="{StaticResource ScrollThumb}" Width="3" HorizontalAlignment="Center" Opacity="0.7"/>
|
||||
</Track.Thumb>
|
||||
</Track>
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Th" Property="Width" Value="6"/>
|
||||
<Setter TargetName="Th" Property="Opacity" Value="1"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="Orientation" Value="Horizontal">
|
||||
<Setter Property="Width" Value="Auto"/>
|
||||
<Setter Property="MinWidth" Value="0"/>
|
||||
<Setter Property="Height" Value="12"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ScrollBar">
|
||||
<Grid Background="Transparent">
|
||||
<Track x:Name="PART_Track">
|
||||
<Track.Thumb>
|
||||
<Thumb x:Name="Th" Style="{StaticResource ScrollThumb}" Height="3" VerticalAlignment="Center" Opacity="0.7"/>
|
||||
</Track.Thumb>
|
||||
</Track>
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Th" Property="Height" Value="6"/>
|
||||
<Setter TargetName="Th" Property="Opacity" Value="1"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<!-- ===== ToolTip ===== -->
|
||||
<Style TargetType="ToolTip">
|
||||
<Setter Property="FontFamily" Value="{DynamicResource UiFont}"/>
|
||||
<Setter Property="FontSize" Value="12"/>
|
||||
<Setter Property="Foreground" Value="{DynamicResource TextBrush}"/>
|
||||
<Setter Property="HasDropShadow" Value="False"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ToolTip">
|
||||
<Border Margin="8,4,8,12" Padding="9,6" CornerRadius="4" MaxWidth="320"
|
||||
Background="{DynamicResource FlyoutBrush}" BorderBrush="{DynamicResource FlyoutStrokeBrush}" BorderThickness="1"
|
||||
Effect="{StaticResource FlyoutShadow}">
|
||||
<ContentPresenter>
|
||||
<ContentPresenter.Resources>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="TextWrapping" Value="Wrap"/>
|
||||
</Style>
|
||||
</ContentPresenter.Resources>
|
||||
</ContentPresenter>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- ===== Kontextmenü ===== -->
|
||||
<Style TargetType="ContextMenu">
|
||||
<Setter Property="FontFamily" Value="{DynamicResource UiFont}"/>
|
||||
<Setter Property="FontSize" Value="14"/>
|
||||
<Setter Property="Foreground" Value="{DynamicResource TextBrush}"/>
|
||||
<Setter Property="HasDropShadow" Value="False"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ContextMenu">
|
||||
<Border Margin="12,4,12,16" Padding="0,4" CornerRadius="8" MinWidth="200"
|
||||
Background="{DynamicResource FlyoutBrush}" BorderBrush="{DynamicResource FlyoutStrokeBrush}" BorderThickness="1"
|
||||
Effect="{StaticResource FlyoutShadow}">
|
||||
<ItemsPresenter KeyboardNavigation.DirectionalNavigation="Cycle"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="{x:Static MenuItem.SeparatorStyleKey}" TargetType="Separator">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Separator">
|
||||
<Border Height="1" Margin="0,4" Background="{DynamicResource FlyoutStrokeBrush}"/>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="MenuItem">
|
||||
<Setter Property="Foreground" Value="{DynamicResource TextBrush}"/>
|
||||
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="MenuItem">
|
||||
<Border x:Name="Bd" Background="Transparent" CornerRadius="4" Margin="4,1" Padding="11,0" Height="34">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="28"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<ContentPresenter ContentSource="Icon" VerticalAlignment="Center" HorizontalAlignment="Left"
|
||||
TextElement.FontFamily="{DynamicResource IconFont}" TextElement.FontSize="16"/>
|
||||
<ContentPresenter Grid.Column="1" ContentSource="Header" VerticalAlignment="Center" RecognizesAccessKey="True"/>
|
||||
<TextBlock x:Name="Gesture" Grid.Column="2" Text="{TemplateBinding InputGestureText}" FontSize="12"
|
||||
VerticalAlignment="Center" Margin="24,0,0,0" Foreground="{DynamicResource TextTertiaryBrush}"/>
|
||||
<TextBlock x:Name="Arrow" Grid.Column="2" Text="" FontFamily="{DynamicResource IconFont}" FontSize="11"
|
||||
VerticalAlignment="Center" Margin="24,0,0,0" Visibility="Collapsed"
|
||||
Foreground="{DynamicResource TextSecondaryBrush}"/>
|
||||
<Popup x:Name="PART_Popup" Placement="Right" HorizontalOffset="-4" VerticalOffset="-12"
|
||||
IsOpen="{Binding IsSubmenuOpen, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
AllowsTransparency="True" Focusable="False" PopupAnimation="Fade">
|
||||
<Border Margin="12,4,12,16" Padding="0,4" CornerRadius="8" MinWidth="180"
|
||||
Background="{DynamicResource FlyoutBrush}" BorderBrush="{DynamicResource FlyoutStrokeBrush}"
|
||||
BorderThickness="1" Effect="{StaticResource FlyoutShadow}">
|
||||
<ScrollViewer MaxHeight="420" VerticalScrollBarVisibility="Auto">
|
||||
<ItemsPresenter KeyboardNavigation.DirectionalNavigation="Cycle"/>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
</Popup>
|
||||
</Grid>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsHighlighted" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="{DynamicResource SubtleHoverBrush}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="Role" Value="SubmenuHeader">
|
||||
<Setter TargetName="Arrow" Property="Visibility" Value="Visible"/>
|
||||
<Setter TargetName="Gesture" Property="Visibility" Value="Collapsed"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter Property="Opacity" Value="0.4"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- ===== Listen ===== -->
|
||||
<Style TargetType="ListBox">
|
||||
<Setter Property="Foreground" Value="{DynamicResource TextBrush}"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/>
|
||||
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ListBox">
|
||||
<ScrollViewer Focusable="False" Padding="{TemplateBinding Padding}">
|
||||
<ItemsPresenter/>
|
||||
</ScrollViewer>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- Navigationseintrag in der Seitenleiste -->
|
||||
<Style x:Key="NavItem" TargetType="ListBoxItem">
|
||||
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
|
||||
<Setter Property="Height" Value="38"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ListBoxItem">
|
||||
<Grid Margin="4,1" Background="Transparent">
|
||||
<Border x:Name="Bd" CornerRadius="4" BorderThickness="1" BorderBrush="Transparent" Background="Transparent"/>
|
||||
<Rectangle x:Name="Pill" Width="3" Height="16" RadiusX="1.5" RadiusY="1.5" HorizontalAlignment="Left"
|
||||
Fill="{DynamicResource AccentBrush}" Visibility="Collapsed"/>
|
||||
<ContentPresenter Margin="14,0,12,0" VerticalAlignment="Center"/>
|
||||
<!-- Einfügelinie beim Umsortieren -->
|
||||
<Border x:Name="InsertTop" Height="2" CornerRadius="1" VerticalAlignment="Top" Margin="6,-2,6,0"
|
||||
Background="{DynamicResource AccentBrush}" Visibility="Collapsed"/>
|
||||
<Border x:Name="InsertBottom" Height="2" CornerRadius="1" VerticalAlignment="Bottom" Margin="6,0,6,-2"
|
||||
Background="{DynamicResource AccentBrush}" Visibility="Collapsed"/>
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="{DynamicResource SubtleHoverBrush}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="{DynamicResource SubtleSelectedBrush}"/>
|
||||
<Setter TargetName="Pill" Property="Visibility" Value="Visible"/>
|
||||
</Trigger>
|
||||
<Trigger Property="Tag" Value="before">
|
||||
<Setter TargetName="InsertTop" Property="Visibility" Value="Visible"/>
|
||||
</Trigger>
|
||||
<Trigger Property="Tag" Value="after">
|
||||
<Setter TargetName="InsertBottom" Property="Visibility" Value="Visible"/>
|
||||
</Trigger>
|
||||
<Trigger Property="Tag" Value="drop">
|
||||
<Setter TargetName="Bd" Property="Background" Value="{DynamicResource AccentSubtleBrush}"/>
|
||||
<Setter TargetName="Bd" Property="BorderBrush" Value="{DynamicResource AccentBrush}"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- Icon-Kachel -->
|
||||
<Style x:Key="TileItem" TargetType="ListBoxItem">
|
||||
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ListBoxItem">
|
||||
<Border x:Name="Bd" Margin="2" CornerRadius="6" BorderThickness="1" BorderBrush="Transparent" Background="Transparent">
|
||||
<ContentPresenter/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="{DynamicResource SubtleHoverBrush}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="{DynamicResource AccentSubtleBrush}"/>
|
||||
<Setter TargetName="Bd" Property="BorderBrush" Value="{DynamicResource AccentSubtleStrokeBrush}"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- ===== Listenansicht-Zeile ===== -->
|
||||
<Style x:Key="RowItem" TargetType="ListBoxItem">
|
||||
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ListBoxItem">
|
||||
<Grid Margin="0,1">
|
||||
<Border x:Name="Bd" CornerRadius="4" Background="Transparent"/>
|
||||
<Rectangle x:Name="Pill" Width="3" Height="16" RadiusX="1.5" RadiusY="1.5" HorizontalAlignment="Left"
|
||||
Fill="{DynamicResource AccentBrush}" Visibility="Collapsed"/>
|
||||
<ContentPresenter/>
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="{DynamicResource SubtleHoverBrush}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="{DynamicResource AccentSubtleBrush}"/>
|
||||
<Setter TargetName="Pill" Property="Visibility" Value="Visible"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- ===== Karte (Einstellungen) ===== -->
|
||||
<Style x:Key="Card" TargetType="Border">
|
||||
<Setter Property="Background" Value="{DynamicResource ControlFillBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource ControlStrokeBrush}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="CornerRadius" Value="6"/>
|
||||
<Setter Property="Padding" Value="18,14"/>
|
||||
<Setter Property="Margin" Value="0,0,0,4"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="SectionTitle" TargetType="TextBlock">
|
||||
<Setter Property="FontSize" Value="14"/>
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
<Setter Property="Margin" Value="2,24,0,8"/>
|
||||
</Style>
|
||||
|
||||
<!-- ===== Kippschalter (Windows 11) ===== -->
|
||||
<Style x:Key="ToggleSwitch" TargetType="CheckBox">
|
||||
<Setter Property="Foreground" Value="{DynamicResource TextBrush}"/>
|
||||
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="CheckBox">
|
||||
<StackPanel Orientation="Horizontal" Background="Transparent">
|
||||
<TextBlock x:Name="State" Text="Aus" VerticalAlignment="Center" Margin="0,0,12,0" MinWidth="24" TextAlignment="Right"/>
|
||||
<Grid Width="40" Height="20">
|
||||
<Border x:Name="Track" CornerRadius="10" BorderThickness="1"
|
||||
Background="{DynamicResource SubtleSelectedBrush}" BorderBrush="{DynamicResource ControlStrongStrokeBrush}"/>
|
||||
<Ellipse x:Name="Knob" Width="12" Height="12" HorizontalAlignment="Left" Margin="4,0,0,0"
|
||||
Fill="{DynamicResource TextSecondaryBrush}"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsChecked" Value="True">
|
||||
<Setter TargetName="Track" Property="Background" Value="{DynamicResource AccentBrush}"/>
|
||||
<Setter TargetName="Track" Property="BorderBrush" Value="{DynamicResource AccentBrush}"/>
|
||||
<Setter TargetName="Knob" Property="Fill" Value="{DynamicResource OnAccentBrush}"/>
|
||||
<Setter TargetName="Knob" Property="HorizontalAlignment" Value="Right"/>
|
||||
<Setter TargetName="Knob" Property="Margin" Value="0,0,4,0"/>
|
||||
<Setter TargetName="State" Property="Text" Value="Ein"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Knob" Property="Width" Value="14"/>
|
||||
<Setter TargetName="Knob" Property="Height" Value="14"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter Property="Opacity" Value="0.4"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- ===== Kontrollkästchen ===== -->
|
||||
<Style TargetType="CheckBox">
|
||||
<Setter Property="Foreground" Value="{DynamicResource TextBrush}"/>
|
||||
<Setter Property="FontFamily" Value="{DynamicResource UiFont}"/>
|
||||
<Setter Property="FontSize" Value="14"/>
|
||||
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="CheckBox">
|
||||
<StackPanel Orientation="Horizontal" Background="Transparent">
|
||||
<Border x:Name="Box" Width="20" Height="20" CornerRadius="4" BorderThickness="1" VerticalAlignment="Center"
|
||||
Background="{DynamicResource ControlFillBrush}" BorderBrush="{DynamicResource ControlStrongStrokeBrush}">
|
||||
<TextBlock x:Name="Check" Text="" FontFamily="{DynamicResource IconFont}" FontSize="12"
|
||||
Foreground="{DynamicResource OnAccentBrush}" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
Visibility="Collapsed"/>
|
||||
</Border>
|
||||
<ContentPresenter Margin="10,0,0,0" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsChecked" Value="True">
|
||||
<Setter TargetName="Box" Property="Background" Value="{DynamicResource AccentBrush}"/>
|
||||
<Setter TargetName="Box" Property="BorderBrush" Value="{DynamicResource AccentBrush}"/>
|
||||
<Setter TargetName="Check" Property="Visibility" Value="Visible"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Box" Property="Opacity" Value="0.85"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter Property="Opacity" Value="0.4"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- ===== Segment-Auswahl (z. B. Hell | Dunkel | System) ===== -->
|
||||
<Style x:Key="SegmentRadio" TargetType="RadioButton">
|
||||
<Setter Property="Foreground" Value="{DynamicResource TextBrush}"/>
|
||||
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
|
||||
<Setter Property="Padding" Value="14,6"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="RadioButton">
|
||||
<Border x:Name="Bd" CornerRadius="4" Padding="{TemplateBinding Padding}" Margin="2" Background="Transparent">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="{DynamicResource SubtleHoverBrush}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsChecked" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="{DynamicResource AccentBrush}"/>
|
||||
<Setter Property="Foreground" Value="{DynamicResource OnAccentBrush}"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="SegmentGroup" TargetType="Border">
|
||||
<Setter Property="Background" Value="{DynamicResource SubtlePressedBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource ControlStrokeBrush}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="CornerRadius" Value="6"/>
|
||||
<Setter Property="Padding" Value="1"/>
|
||||
</Style>
|
||||
|
||||
<!-- ===== Farb- und Symbolauswahl ===== -->
|
||||
<Style x:Key="SwatchRadio" TargetType="RadioButton">
|
||||
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="RadioButton">
|
||||
<Grid Width="34" Height="34" Margin="0,0,6,6" Background="Transparent">
|
||||
<Ellipse x:Name="Ring" Stroke="{DynamicResource TextBrush}" StrokeThickness="2" Visibility="Collapsed"/>
|
||||
<Ellipse Margin="5" Fill="{TemplateBinding Background}" Stroke="{DynamicResource ControlStrokeBrush}"/>
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsChecked" Value="True">
|
||||
<Setter TargetName="Ring" Property="Visibility" Value="Visible"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="GlyphRadio" TargetType="RadioButton">
|
||||
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
|
||||
<Setter Property="Foreground" Value="{DynamicResource TextBrush}"/>
|
||||
<Setter Property="FontFamily" Value="{DynamicResource IconFont}"/>
|
||||
<Setter Property="FontSize" Value="16"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="RadioButton">
|
||||
<Border x:Name="Bd" Width="36" Height="36" Margin="0,0,4,4" CornerRadius="4" BorderThickness="1"
|
||||
BorderBrush="Transparent" Background="Transparent">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="{DynamicResource SubtleHoverBrush}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsChecked" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="{DynamicResource AccentSubtleBrush}"/>
|
||||
<Setter TargetName="Bd" Property="BorderBrush" Value="{DynamicResource AccentBrush}"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="1.0.0.0" name="GitManager.Setup"/>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||
<security>
|
||||
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<!-- Installation nur für den aktuellen Benutzer → keine Administratorrechte -->
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||
</application>
|
||||
</compatibility>
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
</assembly>
|
||||
@@ -0,0 +1,4 @@
|
||||
// <autogenerated />
|
||||
using System;
|
||||
using System.Reflection;
|
||||
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
|
||||
@@ -0,0 +1,77 @@
|
||||
#pragma checksum "..\..\..\App.xaml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "D722851FC43B542A95C365A4D89BC7E17AC3C32D"
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
using System.Windows.Automation;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Controls.Ribbon;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Forms.Integration;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Markup;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
using System.Windows.Media.Effects;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Media.Media3D;
|
||||
using System.Windows.Media.TextFormatting;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
using System.Windows.Shell;
|
||||
|
||||
|
||||
namespace GitManagerSetup {
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// App
|
||||
/// </summary>
|
||||
public partial class App : System.Windows.Application {
|
||||
|
||||
private bool _contentLoaded;
|
||||
|
||||
/// <summary>
|
||||
/// InitializeComponent
|
||||
/// </summary>
|
||||
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "10.0.13.0")]
|
||||
public void InitializeComponent() {
|
||||
if (_contentLoaded) {
|
||||
return;
|
||||
}
|
||||
_contentLoaded = true;
|
||||
System.Uri resourceLocater = new System.Uri("/Git-Manager-GUI-Setup;V0.0.0.0;component/app.xaml", System.UriKind.Relative);
|
||||
|
||||
#line 1 "..\..\..\App.xaml"
|
||||
System.Windows.Application.LoadComponent(this, resourceLocater);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Application Entry Point.
|
||||
/// </summary>
|
||||
[System.STAThreadAttribute()]
|
||||
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "10.0.13.0")]
|
||||
public static void Main() {
|
||||
GitManagerSetup.App app = new GitManagerSetup.App();
|
||||
app.InitializeComponent();
|
||||
app.Run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
|
||||
</startup>
|
||||
</configuration>
|
||||
@@ -0,0 +1,20 @@
|
||||
Git-Manager-GUI-Setup
|
||||
0.0.0.0
|
||||
|
||||
winexe
|
||||
C#
|
||||
.cs
|
||||
C:\Users\Viper\Desktop\git-manager-gui\installer\obj\Debug\net48\
|
||||
GitManagerSetup
|
||||
none
|
||||
false
|
||||
TRACE;DEBUG;NETFRAMEWORK;NET48;NET30_OR_GREATER;NET35_OR_GREATER;NET40_OR_GREATER;NET45_OR_GREATER;NET451_OR_GREATER;NET452_OR_GREATER;NET46_OR_GREATER;NET461_OR_GREATER;NET462_OR_GREATER;NET47_OR_GREATER;NET471_OR_GREATER;NET472_OR_GREATER;NET48_OR_GREATER
|
||||
C:\Users\Viper\Desktop\git-manager-gui\installer\App.xaml
|
||||
41549299691
|
||||
|
||||
8-482891414
|
||||
23-602304815
|
||||
MainWindow.xaml;Themes\Colors.Dark.xaml;Themes\Colors.Light.xaml;Themes\Controls.xaml;
|
||||
|
||||
False
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: System.Reflection.AssemblyCompanyAttribute("M_Viper")]
|
||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||
[assembly: System.Reflection.AssemblyCopyrightAttribute("Copyright (c) M_Viper")]
|
||||
[assembly: System.Reflection.AssemblyDescriptionAttribute("Installiert Git Manager GUI")]
|
||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("0.0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("Git Manager GUI Setup")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("Git Manager GUI Setup")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("0.0.0.0")]
|
||||
[assembly: System.Resources.NeutralResourcesLanguageAttribute("de-DE")]
|
||||
|
||||
// Von der MSBuild WriteCodeFragment-Klasse generiert.
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
9c3aaae1d47c807c810117538379b6f2752577cee8cc71a0a6017da12833e788
|
||||
@@ -0,0 +1,14 @@
|
||||
is_global = true
|
||||
build_property.ApplicationManifest = app.manifest
|
||||
build_property.StartupObject =
|
||||
build_property.ApplicationDefaultFont =
|
||||
build_property.ApplicationHighDpiMode =
|
||||
build_property.ApplicationUseCompatibleTextRendering =
|
||||
build_property.ApplicationVisualStyles =
|
||||
build_property.RootNamespace = GitManagerSetup
|
||||
build_property.ProjectDir = C:\Users\Viper\Desktop\git-manager-gui\installer\
|
||||
build_property.EnableComHosting =
|
||||
build_property.EnableGeneratedComInterfaceComImportInterop =
|
||||
build_property.CsWinRTUseWindowsUIXamlProjections = false
|
||||
build_property.EffectiveAnalysisLevelStyle =
|
||||
build_property.EnableCodeStyleSeverity =
|
||||
@@ -0,0 +1,6 @@
|
||||
// <auto-generated/>
|
||||
global using System;
|
||||
global using System.Collections.Generic;
|
||||
global using System.Linq;
|
||||
global using System.Threading;
|
||||
global using System.Threading.Tasks;
|
||||
@@ -0,0 +1,676 @@
|
||||
#pragma checksum "..\..\..\MainWindow.xaml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "248163D303017091F6C4A571FD0B5DF4D5DBF414"
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
using System.Windows.Automation;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Controls.Ribbon;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Forms.Integration;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Markup;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
using System.Windows.Media.Effects;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Media.Media3D;
|
||||
using System.Windows.Media.TextFormatting;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
using System.Windows.Shell;
|
||||
|
||||
|
||||
namespace GitManagerSetup {
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// MainWindow
|
||||
/// </summary>
|
||||
public partial class MainWindow : System.Windows.Window, System.Windows.Markup.IComponentConnector {
|
||||
|
||||
|
||||
#line 140 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.TextBlock VersionText;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 142 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.StackPanel Steps;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 144 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Border Dot0;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 144 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.TextBlock Num0;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 145 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.TextBlock Label0;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 147 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Border Line0;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 149 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Border Dot1;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 149 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.TextBlock Num1;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 150 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.TextBlock Label1;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 152 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Border Line1;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 154 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Border Dot2;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 154 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.TextBlock Num2;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 155 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.TextBlock Label2;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 168 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.TextBlock WindowTitle;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 174 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Button CloseButton;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 182 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.StackPanel PageWelcome;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 229 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Border UpdateHint;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 234 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.TextBlock UpdateHintText;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 237 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Border RunningHint;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 249 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.StackPanel PageOptions;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 258 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.TextBox DirBox;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 267 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.CheckBox DesktopLinkBox;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 273 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.StackPanel PageProgress;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 274 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Border SuccessIcon;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 276 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Media.ScaleTransform SuccessScale;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 281 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.TextBlock ProgressTitle;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 283 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.StackPanel ProgressBarPanel;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 284 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.TextBlock PercentText;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 286 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Border ProgressTrack;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 287 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Border ProgressFill;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 290 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.TextBlock ProgressText;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 292 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.StackPanel DonePanel;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 293 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.CheckBox LaunchBox;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 298 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.StackPanel PageUninstall;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 309 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.TextBlock DataInfo;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 310 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.CheckBox RemoveDataBox;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 312 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.TextBlock RemoveDataWarning;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 322 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Button BackButton;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 323 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Button NextButton;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 324 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Button CancelButton;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 328 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Grid DialogOverlay;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 329 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Border DialogCard;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 332 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Media.ScaleTransform DialogScale;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 336 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.TextBlock DialogIcon;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 339 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.TextBlock DialogTitle;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 342 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.TextBlock DialogText;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 345 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Button DialogSecondary;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 346 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Button DialogPrimary;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
private bool _contentLoaded;
|
||||
|
||||
/// <summary>
|
||||
/// InitializeComponent
|
||||
/// </summary>
|
||||
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "10.0.13.0")]
|
||||
public void InitializeComponent() {
|
||||
if (_contentLoaded) {
|
||||
return;
|
||||
}
|
||||
_contentLoaded = true;
|
||||
System.Uri resourceLocater = new System.Uri("/Git-Manager-GUI-Setup;V0.0.0.0;component/mainwindow.xaml", System.UriKind.Relative);
|
||||
|
||||
#line 1 "..\..\..\MainWindow.xaml"
|
||||
System.Windows.Application.LoadComponent(this, resourceLocater);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
}
|
||||
|
||||
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "10.0.13.0")]
|
||||
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes")]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")]
|
||||
void System.Windows.Markup.IComponentConnector.Connect(int connectionId, object target) {
|
||||
switch (connectionId)
|
||||
{
|
||||
case 1:
|
||||
this.VersionText = ((System.Windows.Controls.TextBlock)(target));
|
||||
return;
|
||||
case 2:
|
||||
this.Steps = ((System.Windows.Controls.StackPanel)(target));
|
||||
return;
|
||||
case 3:
|
||||
this.Dot0 = ((System.Windows.Controls.Border)(target));
|
||||
return;
|
||||
case 4:
|
||||
this.Num0 = ((System.Windows.Controls.TextBlock)(target));
|
||||
return;
|
||||
case 5:
|
||||
this.Label0 = ((System.Windows.Controls.TextBlock)(target));
|
||||
return;
|
||||
case 6:
|
||||
this.Line0 = ((System.Windows.Controls.Border)(target));
|
||||
return;
|
||||
case 7:
|
||||
this.Dot1 = ((System.Windows.Controls.Border)(target));
|
||||
return;
|
||||
case 8:
|
||||
this.Num1 = ((System.Windows.Controls.TextBlock)(target));
|
||||
return;
|
||||
case 9:
|
||||
this.Label1 = ((System.Windows.Controls.TextBlock)(target));
|
||||
return;
|
||||
case 10:
|
||||
this.Line1 = ((System.Windows.Controls.Border)(target));
|
||||
return;
|
||||
case 11:
|
||||
this.Dot2 = ((System.Windows.Controls.Border)(target));
|
||||
return;
|
||||
case 12:
|
||||
this.Num2 = ((System.Windows.Controls.TextBlock)(target));
|
||||
return;
|
||||
case 13:
|
||||
this.Label2 = ((System.Windows.Controls.TextBlock)(target));
|
||||
return;
|
||||
case 14:
|
||||
this.WindowTitle = ((System.Windows.Controls.TextBlock)(target));
|
||||
return;
|
||||
case 15:
|
||||
|
||||
#line 172 "..\..\..\MainWindow.xaml"
|
||||
((System.Windows.Controls.Button)(target)).Click += new System.Windows.RoutedEventHandler(this.Minimize_Click);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
return;
|
||||
case 16:
|
||||
this.CloseButton = ((System.Windows.Controls.Button)(target));
|
||||
|
||||
#line 174 "..\..\..\MainWindow.xaml"
|
||||
this.CloseButton.Click += new System.Windows.RoutedEventHandler(this.Cancel_Click);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
return;
|
||||
case 17:
|
||||
this.PageWelcome = ((System.Windows.Controls.StackPanel)(target));
|
||||
return;
|
||||
case 18:
|
||||
this.UpdateHint = ((System.Windows.Controls.Border)(target));
|
||||
return;
|
||||
case 19:
|
||||
this.UpdateHintText = ((System.Windows.Controls.TextBlock)(target));
|
||||
return;
|
||||
case 20:
|
||||
this.RunningHint = ((System.Windows.Controls.Border)(target));
|
||||
return;
|
||||
case 21:
|
||||
this.PageOptions = ((System.Windows.Controls.StackPanel)(target));
|
||||
return;
|
||||
case 22:
|
||||
|
||||
#line 257 "..\..\..\MainWindow.xaml"
|
||||
((System.Windows.Controls.Button)(target)).Click += new System.Windows.RoutedEventHandler(this.ChangeDir_Click);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
return;
|
||||
case 23:
|
||||
this.DirBox = ((System.Windows.Controls.TextBox)(target));
|
||||
return;
|
||||
case 24:
|
||||
this.DesktopLinkBox = ((System.Windows.Controls.CheckBox)(target));
|
||||
return;
|
||||
case 25:
|
||||
this.PageProgress = ((System.Windows.Controls.StackPanel)(target));
|
||||
return;
|
||||
case 26:
|
||||
this.SuccessIcon = ((System.Windows.Controls.Border)(target));
|
||||
return;
|
||||
case 27:
|
||||
this.SuccessScale = ((System.Windows.Media.ScaleTransform)(target));
|
||||
return;
|
||||
case 28:
|
||||
this.ProgressTitle = ((System.Windows.Controls.TextBlock)(target));
|
||||
return;
|
||||
case 29:
|
||||
this.ProgressBarPanel = ((System.Windows.Controls.StackPanel)(target));
|
||||
return;
|
||||
case 30:
|
||||
this.PercentText = ((System.Windows.Controls.TextBlock)(target));
|
||||
return;
|
||||
case 31:
|
||||
this.ProgressTrack = ((System.Windows.Controls.Border)(target));
|
||||
|
||||
#line 286 "..\..\..\MainWindow.xaml"
|
||||
this.ProgressTrack.SizeChanged += new System.Windows.SizeChangedEventHandler(this.ProgressTrack_SizeChanged);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
return;
|
||||
case 32:
|
||||
this.ProgressFill = ((System.Windows.Controls.Border)(target));
|
||||
return;
|
||||
case 33:
|
||||
this.ProgressText = ((System.Windows.Controls.TextBlock)(target));
|
||||
return;
|
||||
case 34:
|
||||
this.DonePanel = ((System.Windows.Controls.StackPanel)(target));
|
||||
return;
|
||||
case 35:
|
||||
this.LaunchBox = ((System.Windows.Controls.CheckBox)(target));
|
||||
return;
|
||||
case 36:
|
||||
this.PageUninstall = ((System.Windows.Controls.StackPanel)(target));
|
||||
return;
|
||||
case 37:
|
||||
this.DataInfo = ((System.Windows.Controls.TextBlock)(target));
|
||||
return;
|
||||
case 38:
|
||||
this.RemoveDataBox = ((System.Windows.Controls.CheckBox)(target));
|
||||
|
||||
#line 311 "..\..\..\MainWindow.xaml"
|
||||
this.RemoveDataBox.Checked += new System.Windows.RoutedEventHandler(this.RemoveDataBox_Click);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
#line 311 "..\..\..\MainWindow.xaml"
|
||||
this.RemoveDataBox.Unchecked += new System.Windows.RoutedEventHandler(this.RemoveDataBox_Click);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
return;
|
||||
case 39:
|
||||
this.RemoveDataWarning = ((System.Windows.Controls.TextBlock)(target));
|
||||
return;
|
||||
case 40:
|
||||
this.BackButton = ((System.Windows.Controls.Button)(target));
|
||||
|
||||
#line 322 "..\..\..\MainWindow.xaml"
|
||||
this.BackButton.Click += new System.Windows.RoutedEventHandler(this.Back_Click);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
return;
|
||||
case 41:
|
||||
this.NextButton = ((System.Windows.Controls.Button)(target));
|
||||
|
||||
#line 323 "..\..\..\MainWindow.xaml"
|
||||
this.NextButton.Click += new System.Windows.RoutedEventHandler(this.Next_Click);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
return;
|
||||
case 42:
|
||||
this.CancelButton = ((System.Windows.Controls.Button)(target));
|
||||
|
||||
#line 324 "..\..\..\MainWindow.xaml"
|
||||
this.CancelButton.Click += new System.Windows.RoutedEventHandler(this.Cancel_Click);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
return;
|
||||
case 43:
|
||||
this.DialogOverlay = ((System.Windows.Controls.Grid)(target));
|
||||
return;
|
||||
case 44:
|
||||
this.DialogCard = ((System.Windows.Controls.Border)(target));
|
||||
return;
|
||||
case 45:
|
||||
this.DialogScale = ((System.Windows.Media.ScaleTransform)(target));
|
||||
return;
|
||||
case 46:
|
||||
this.DialogIcon = ((System.Windows.Controls.TextBlock)(target));
|
||||
return;
|
||||
case 47:
|
||||
this.DialogTitle = ((System.Windows.Controls.TextBlock)(target));
|
||||
return;
|
||||
case 48:
|
||||
this.DialogText = ((System.Windows.Controls.TextBlock)(target));
|
||||
return;
|
||||
case 49:
|
||||
this.DialogSecondary = ((System.Windows.Controls.Button)(target));
|
||||
|
||||
#line 345 "..\..\..\MainWindow.xaml"
|
||||
this.DialogSecondary.Click += new System.Windows.RoutedEventHandler(this.DialogSecondary_Click);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
return;
|
||||
case 50:
|
||||
this.DialogPrimary = ((System.Windows.Controls.Button)(target));
|
||||
|
||||
#line 346 "..\..\..\MainWindow.xaml"
|
||||
this.DialogPrimary.Click += new System.Windows.RoutedEventHandler(this.DialogPrimary_Click);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
return;
|
||||
}
|
||||
this._contentLoaded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"format": 1,
|
||||
"restore": {
|
||||
"C:\\Users\\Viper\\Desktop\\git-manager-gui\\installer\\GitManagerSetup.csproj": {}
|
||||
},
|
||||
"projects": {
|
||||
"C:\\Users\\Viper\\Desktop\\git-manager-gui\\installer\\GitManagerSetup.csproj": {
|
||||
"version": "0.0.0",
|
||||
"restore": {
|
||||
"projectUniqueName": "C:\\Users\\Viper\\Desktop\\git-manager-gui\\installer\\GitManagerSetup.csproj",
|
||||
"projectName": "Git-Manager-GUI-Setup",
|
||||
"projectPath": "C:\\Users\\Viper\\Desktop\\git-manager-gui\\installer\\GitManagerSetup.csproj",
|
||||
"packagesPath": "C:\\Users\\Viper\\.nuget\\packages\\",
|
||||
"outputPath": "C:\\Users\\Viper\\Desktop\\git-manager-gui\\installer\\obj\\",
|
||||
"projectStyle": "PackageReference",
|
||||
"configFilePaths": [
|
||||
"C:\\Users\\Viper\\AppData\\Roaming\\NuGet\\NuGet.Config"
|
||||
],
|
||||
"originalTargetFrameworks": [
|
||||
"net48"
|
||||
],
|
||||
"sources": {
|
||||
"https://api.nuget.org/v3/index.json": {}
|
||||
},
|
||||
"frameworks": {
|
||||
"net48": {
|
||||
"targetAlias": "net48",
|
||||
"projectReferences": {}
|
||||
}
|
||||
},
|
||||
"warningProperties": {
|
||||
"warnAsError": [
|
||||
"NU1605"
|
||||
]
|
||||
},
|
||||
"restoreAuditProperties": {
|
||||
"enableAudit": "true",
|
||||
"auditLevel": "low",
|
||||
"auditMode": "direct"
|
||||
},
|
||||
"SdkAnalysisLevel": "10.0.100"
|
||||
},
|
||||
"frameworks": {
|
||||
"net48": {
|
||||
"targetAlias": "net48",
|
||||
"dependencies": {
|
||||
"Microsoft.NETFramework.ReferenceAssemblies": {
|
||||
"suppressParent": "All",
|
||||
"target": "Package",
|
||||
"version": "[1.0.3, )",
|
||||
"autoReferenced": true
|
||||
}
|
||||
},
|
||||
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.113\\RuntimeIdentifierGraph.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<RestoreSuccess Condition=" '$(RestoreSuccess)' == '' ">True</RestoreSuccess>
|
||||
<RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool>
|
||||
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile>
|
||||
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
|
||||
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\Viper\.nuget\packages\</NuGetPackageFolders>
|
||||
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
|
||||
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">7.0.0</NuGetToolVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<SourceRoot Include="C:\Users\Viper\.nuget\packages\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<Import Project="$(NuGetPackageRoot)microsoft.netframework.referenceassemblies.net48\1.0.3\build\Microsoft.NETFramework.ReferenceAssemblies.net48.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.netframework.referenceassemblies.net48\1.0.3\build\Microsoft.NETFramework.ReferenceAssemblies.net48.targets')" />
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,4 @@
|
||||
// <autogenerated />
|
||||
using System;
|
||||
using System.Reflection;
|
||||
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
|
||||
@@ -0,0 +1,77 @@
|
||||
#pragma checksum "..\..\..\App.xaml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "D722851FC43B542A95C365A4D89BC7E17AC3C32D"
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
using System.Windows.Automation;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Controls.Ribbon;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Forms.Integration;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Markup;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
using System.Windows.Media.Effects;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Media.Media3D;
|
||||
using System.Windows.Media.TextFormatting;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
using System.Windows.Shell;
|
||||
|
||||
|
||||
namespace GitManagerSetup {
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// App
|
||||
/// </summary>
|
||||
public partial class App : System.Windows.Application {
|
||||
|
||||
private bool _contentLoaded;
|
||||
|
||||
/// <summary>
|
||||
/// InitializeComponent
|
||||
/// </summary>
|
||||
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "10.0.13.0")]
|
||||
public void InitializeComponent() {
|
||||
if (_contentLoaded) {
|
||||
return;
|
||||
}
|
||||
_contentLoaded = true;
|
||||
System.Uri resourceLocater = new System.Uri("/Git-Manager-GUI-Setup;component/app.xaml", System.UriKind.Relative);
|
||||
|
||||
#line 1 "..\..\..\App.xaml"
|
||||
System.Windows.Application.LoadComponent(this, resourceLocater);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Application Entry Point.
|
||||
/// </summary>
|
||||
[System.STAThreadAttribute()]
|
||||
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "10.0.13.0")]
|
||||
public static void Main() {
|
||||
GitManagerSetup.App app = new GitManagerSetup.App();
|
||||
app.InitializeComponent();
|
||||
app.Run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
|
||||
</startup>
|
||||
</configuration>
|
||||
@@ -0,0 +1,20 @@
|
||||
Git-Manager-GUI-Setup
|
||||
|
||||
|
||||
winexe
|
||||
C#
|
||||
.cs
|
||||
C:\Users\Viper\Desktop\git-manager-gui\installer\obj\Release\net48\
|
||||
GitManagerSetup
|
||||
none
|
||||
false
|
||||
TRACE;RELEASE;NETFRAMEWORK;NET48;NET30_OR_GREATER;NET35_OR_GREATER;NET40_OR_GREATER;NET45_OR_GREATER;NET451_OR_GREATER;NET452_OR_GREATER;NET46_OR_GREATER;NET461_OR_GREATER;NET462_OR_GREATER;NET47_OR_GREATER;NET471_OR_GREATER;NET472_OR_GREATER;NET48_OR_GREATER
|
||||
C:\Users\Viper\Desktop\git-manager-gui\installer\App.xaml
|
||||
41549299691
|
||||
|
||||
6-1705775420
|
||||
23-602304815
|
||||
MainWindow.xaml;Themes\Colors.Dark.xaml;Themes\Colors.Light.xaml;Themes\Controls.xaml;
|
||||
|
||||
False
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: System.Reflection.AssemblyCompanyAttribute("M_Viper")]
|
||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Release")]
|
||||
[assembly: System.Reflection.AssemblyCopyrightAttribute("Copyright (c) M_Viper")]
|
||||
[assembly: System.Reflection.AssemblyDescriptionAttribute("Installiert Git Manager GUI")]
|
||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("2.2.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("2.2.0")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("Git Manager GUI Setup")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("Git Manager GUI Setup")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("2.2.0.0")]
|
||||
[assembly: System.Resources.NeutralResourcesLanguageAttribute("de-DE")]
|
||||
|
||||
// Von der MSBuild WriteCodeFragment-Klasse generiert.
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
62f4b9ac7b1430e3a52ef9dfe2e8422ccd7cc5a7e41145550122532f56db4abe
|
||||
@@ -0,0 +1,14 @@
|
||||
is_global = true
|
||||
build_property.ApplicationManifest = app.manifest
|
||||
build_property.StartupObject =
|
||||
build_property.ApplicationDefaultFont =
|
||||
build_property.ApplicationHighDpiMode =
|
||||
build_property.ApplicationUseCompatibleTextRendering =
|
||||
build_property.ApplicationVisualStyles =
|
||||
build_property.RootNamespace = GitManagerSetup
|
||||
build_property.ProjectDir = C:\Users\Viper\Desktop\git-manager-gui\installer\
|
||||
build_property.EnableComHosting =
|
||||
build_property.EnableGeneratedComInterfaceComImportInterop =
|
||||
build_property.CsWinRTUseWindowsUIXamlProjections = false
|
||||
build_property.EffectiveAnalysisLevelStyle =
|
||||
build_property.EnableCodeStyleSeverity =
|
||||
@@ -0,0 +1,6 @@
|
||||
// <auto-generated/>
|
||||
global using System;
|
||||
global using System.Collections.Generic;
|
||||
global using System.Linq;
|
||||
global using System.Threading;
|
||||
global using System.Threading.Tasks;
|
||||
@@ -0,0 +1 @@
|
||||
4e41dc5e237f26559d3c4bd8208c813ba09bdba804615d78813773d6b0be3d1d
|
||||
@@ -0,0 +1,19 @@
|
||||
C:\Users\Viper\AppData\Local\Temp\claude\G--\bba0d154-b599-43ca-80e6-538e01f4f870\scratchpad\setupbuild\Git-Manager-GUI-Setup.exe.config
|
||||
C:\Users\Viper\AppData\Local\Temp\claude\G--\bba0d154-b599-43ca-80e6-538e01f4f870\scratchpad\setupbuild\Git-Manager-GUI-Setup.exe
|
||||
C:\Users\Viper\Desktop\git-manager-gui\installer\obj\Release\net48\GitManagerSetup.csproj.AssemblyReference.cache
|
||||
C:\Users\Viper\Desktop\git-manager-gui\installer\obj\Release\net48\MainWindow.baml
|
||||
C:\Users\Viper\Desktop\git-manager-gui\installer\obj\Release\net48\Themes\Colors.Dark.baml
|
||||
C:\Users\Viper\Desktop\git-manager-gui\installer\obj\Release\net48\Themes\Colors.Light.baml
|
||||
C:\Users\Viper\Desktop\git-manager-gui\installer\obj\Release\net48\Themes\Controls.baml
|
||||
C:\Users\Viper\Desktop\git-manager-gui\installer\obj\Release\net48\App.baml
|
||||
C:\Users\Viper\Desktop\git-manager-gui\installer\obj\Release\net48\MainWindow.g.cs
|
||||
C:\Users\Viper\Desktop\git-manager-gui\installer\obj\Release\net48\App.g.cs
|
||||
C:\Users\Viper\Desktop\git-manager-gui\installer\obj\Release\net48\Git-Manager-GUI-Setup_MarkupCompile.cache
|
||||
C:\Users\Viper\Desktop\git-manager-gui\installer\obj\Release\net48\Git-Manager-GUI-Setup.g.resources
|
||||
C:\Users\Viper\Desktop\git-manager-gui\installer\obj\Release\net48\GitManagerSetup.GeneratedMSBuildEditorConfig.editorconfig
|
||||
C:\Users\Viper\Desktop\git-manager-gui\installer\obj\Release\net48\GitManagerSetup.AssemblyInfoInputs.cache
|
||||
C:\Users\Viper\Desktop\git-manager-gui\installer\obj\Release\net48\GitManagerSetup.AssemblyInfo.cs
|
||||
C:\Users\Viper\Desktop\git-manager-gui\installer\obj\Release\net48\GitManagerSetup.csproj.CoreCompileInputs.cache
|
||||
C:\Users\Viper\Desktop\git-manager-gui\installer\obj\Release\net48\Git-Manager-GUI-Setup.exe
|
||||
C:\Users\Viper\Desktop\git-manager-gui\dist\.setup\stub\Git-Manager-GUI-Setup.exe.config
|
||||
C:\Users\Viper\Desktop\git-manager-gui\dist\.setup\stub\Git-Manager-GUI-Setup.exe
|
||||
@@ -0,0 +1,676 @@
|
||||
#pragma checksum "..\..\..\MainWindow.xaml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "248163D303017091F6C4A571FD0B5DF4D5DBF414"
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
using System.Windows.Automation;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Controls.Ribbon;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Forms.Integration;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Markup;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
using System.Windows.Media.Effects;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Media.Media3D;
|
||||
using System.Windows.Media.TextFormatting;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
using System.Windows.Shell;
|
||||
|
||||
|
||||
namespace GitManagerSetup {
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// MainWindow
|
||||
/// </summary>
|
||||
public partial class MainWindow : System.Windows.Window, System.Windows.Markup.IComponentConnector {
|
||||
|
||||
|
||||
#line 140 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.TextBlock VersionText;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 142 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.StackPanel Steps;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 144 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Border Dot0;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 144 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.TextBlock Num0;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 145 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.TextBlock Label0;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 147 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Border Line0;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 149 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Border Dot1;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 149 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.TextBlock Num1;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 150 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.TextBlock Label1;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 152 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Border Line1;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 154 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Border Dot2;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 154 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.TextBlock Num2;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 155 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.TextBlock Label2;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 168 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.TextBlock WindowTitle;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 174 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Button CloseButton;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 182 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.StackPanel PageWelcome;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 229 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Border UpdateHint;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 234 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.TextBlock UpdateHintText;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 237 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Border RunningHint;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 249 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.StackPanel PageOptions;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 258 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.TextBox DirBox;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 267 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.CheckBox DesktopLinkBox;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 273 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.StackPanel PageProgress;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 274 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Border SuccessIcon;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 276 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Media.ScaleTransform SuccessScale;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 281 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.TextBlock ProgressTitle;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 283 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.StackPanel ProgressBarPanel;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 284 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.TextBlock PercentText;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 286 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Border ProgressTrack;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 287 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Border ProgressFill;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 290 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.TextBlock ProgressText;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 292 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.StackPanel DonePanel;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 293 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.CheckBox LaunchBox;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 298 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.StackPanel PageUninstall;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 309 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.TextBlock DataInfo;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 310 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.CheckBox RemoveDataBox;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 312 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.TextBlock RemoveDataWarning;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 322 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Button BackButton;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 323 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Button NextButton;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 324 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Button CancelButton;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 328 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Grid DialogOverlay;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 329 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Border DialogCard;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 332 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Media.ScaleTransform DialogScale;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 336 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.TextBlock DialogIcon;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 339 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.TextBlock DialogTitle;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 342 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.TextBlock DialogText;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 345 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Button DialogSecondary;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 346 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Button DialogPrimary;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
private bool _contentLoaded;
|
||||
|
||||
/// <summary>
|
||||
/// InitializeComponent
|
||||
/// </summary>
|
||||
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "10.0.13.0")]
|
||||
public void InitializeComponent() {
|
||||
if (_contentLoaded) {
|
||||
return;
|
||||
}
|
||||
_contentLoaded = true;
|
||||
System.Uri resourceLocater = new System.Uri("/Git-Manager-GUI-Setup;component/mainwindow.xaml", System.UriKind.Relative);
|
||||
|
||||
#line 1 "..\..\..\MainWindow.xaml"
|
||||
System.Windows.Application.LoadComponent(this, resourceLocater);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
}
|
||||
|
||||
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "10.0.13.0")]
|
||||
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes")]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")]
|
||||
void System.Windows.Markup.IComponentConnector.Connect(int connectionId, object target) {
|
||||
switch (connectionId)
|
||||
{
|
||||
case 1:
|
||||
this.VersionText = ((System.Windows.Controls.TextBlock)(target));
|
||||
return;
|
||||
case 2:
|
||||
this.Steps = ((System.Windows.Controls.StackPanel)(target));
|
||||
return;
|
||||
case 3:
|
||||
this.Dot0 = ((System.Windows.Controls.Border)(target));
|
||||
return;
|
||||
case 4:
|
||||
this.Num0 = ((System.Windows.Controls.TextBlock)(target));
|
||||
return;
|
||||
case 5:
|
||||
this.Label0 = ((System.Windows.Controls.TextBlock)(target));
|
||||
return;
|
||||
case 6:
|
||||
this.Line0 = ((System.Windows.Controls.Border)(target));
|
||||
return;
|
||||
case 7:
|
||||
this.Dot1 = ((System.Windows.Controls.Border)(target));
|
||||
return;
|
||||
case 8:
|
||||
this.Num1 = ((System.Windows.Controls.TextBlock)(target));
|
||||
return;
|
||||
case 9:
|
||||
this.Label1 = ((System.Windows.Controls.TextBlock)(target));
|
||||
return;
|
||||
case 10:
|
||||
this.Line1 = ((System.Windows.Controls.Border)(target));
|
||||
return;
|
||||
case 11:
|
||||
this.Dot2 = ((System.Windows.Controls.Border)(target));
|
||||
return;
|
||||
case 12:
|
||||
this.Num2 = ((System.Windows.Controls.TextBlock)(target));
|
||||
return;
|
||||
case 13:
|
||||
this.Label2 = ((System.Windows.Controls.TextBlock)(target));
|
||||
return;
|
||||
case 14:
|
||||
this.WindowTitle = ((System.Windows.Controls.TextBlock)(target));
|
||||
return;
|
||||
case 15:
|
||||
|
||||
#line 172 "..\..\..\MainWindow.xaml"
|
||||
((System.Windows.Controls.Button)(target)).Click += new System.Windows.RoutedEventHandler(this.Minimize_Click);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
return;
|
||||
case 16:
|
||||
this.CloseButton = ((System.Windows.Controls.Button)(target));
|
||||
|
||||
#line 174 "..\..\..\MainWindow.xaml"
|
||||
this.CloseButton.Click += new System.Windows.RoutedEventHandler(this.Cancel_Click);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
return;
|
||||
case 17:
|
||||
this.PageWelcome = ((System.Windows.Controls.StackPanel)(target));
|
||||
return;
|
||||
case 18:
|
||||
this.UpdateHint = ((System.Windows.Controls.Border)(target));
|
||||
return;
|
||||
case 19:
|
||||
this.UpdateHintText = ((System.Windows.Controls.TextBlock)(target));
|
||||
return;
|
||||
case 20:
|
||||
this.RunningHint = ((System.Windows.Controls.Border)(target));
|
||||
return;
|
||||
case 21:
|
||||
this.PageOptions = ((System.Windows.Controls.StackPanel)(target));
|
||||
return;
|
||||
case 22:
|
||||
|
||||
#line 257 "..\..\..\MainWindow.xaml"
|
||||
((System.Windows.Controls.Button)(target)).Click += new System.Windows.RoutedEventHandler(this.ChangeDir_Click);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
return;
|
||||
case 23:
|
||||
this.DirBox = ((System.Windows.Controls.TextBox)(target));
|
||||
return;
|
||||
case 24:
|
||||
this.DesktopLinkBox = ((System.Windows.Controls.CheckBox)(target));
|
||||
return;
|
||||
case 25:
|
||||
this.PageProgress = ((System.Windows.Controls.StackPanel)(target));
|
||||
return;
|
||||
case 26:
|
||||
this.SuccessIcon = ((System.Windows.Controls.Border)(target));
|
||||
return;
|
||||
case 27:
|
||||
this.SuccessScale = ((System.Windows.Media.ScaleTransform)(target));
|
||||
return;
|
||||
case 28:
|
||||
this.ProgressTitle = ((System.Windows.Controls.TextBlock)(target));
|
||||
return;
|
||||
case 29:
|
||||
this.ProgressBarPanel = ((System.Windows.Controls.StackPanel)(target));
|
||||
return;
|
||||
case 30:
|
||||
this.PercentText = ((System.Windows.Controls.TextBlock)(target));
|
||||
return;
|
||||
case 31:
|
||||
this.ProgressTrack = ((System.Windows.Controls.Border)(target));
|
||||
|
||||
#line 286 "..\..\..\MainWindow.xaml"
|
||||
this.ProgressTrack.SizeChanged += new System.Windows.SizeChangedEventHandler(this.ProgressTrack_SizeChanged);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
return;
|
||||
case 32:
|
||||
this.ProgressFill = ((System.Windows.Controls.Border)(target));
|
||||
return;
|
||||
case 33:
|
||||
this.ProgressText = ((System.Windows.Controls.TextBlock)(target));
|
||||
return;
|
||||
case 34:
|
||||
this.DonePanel = ((System.Windows.Controls.StackPanel)(target));
|
||||
return;
|
||||
case 35:
|
||||
this.LaunchBox = ((System.Windows.Controls.CheckBox)(target));
|
||||
return;
|
||||
case 36:
|
||||
this.PageUninstall = ((System.Windows.Controls.StackPanel)(target));
|
||||
return;
|
||||
case 37:
|
||||
this.DataInfo = ((System.Windows.Controls.TextBlock)(target));
|
||||
return;
|
||||
case 38:
|
||||
this.RemoveDataBox = ((System.Windows.Controls.CheckBox)(target));
|
||||
|
||||
#line 311 "..\..\..\MainWindow.xaml"
|
||||
this.RemoveDataBox.Checked += new System.Windows.RoutedEventHandler(this.RemoveDataBox_Click);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
#line 311 "..\..\..\MainWindow.xaml"
|
||||
this.RemoveDataBox.Unchecked += new System.Windows.RoutedEventHandler(this.RemoveDataBox_Click);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
return;
|
||||
case 39:
|
||||
this.RemoveDataWarning = ((System.Windows.Controls.TextBlock)(target));
|
||||
return;
|
||||
case 40:
|
||||
this.BackButton = ((System.Windows.Controls.Button)(target));
|
||||
|
||||
#line 322 "..\..\..\MainWindow.xaml"
|
||||
this.BackButton.Click += new System.Windows.RoutedEventHandler(this.Back_Click);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
return;
|
||||
case 41:
|
||||
this.NextButton = ((System.Windows.Controls.Button)(target));
|
||||
|
||||
#line 323 "..\..\..\MainWindow.xaml"
|
||||
this.NextButton.Click += new System.Windows.RoutedEventHandler(this.Next_Click);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
return;
|
||||
case 42:
|
||||
this.CancelButton = ((System.Windows.Controls.Button)(target));
|
||||
|
||||
#line 324 "..\..\..\MainWindow.xaml"
|
||||
this.CancelButton.Click += new System.Windows.RoutedEventHandler(this.Cancel_Click);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
return;
|
||||
case 43:
|
||||
this.DialogOverlay = ((System.Windows.Controls.Grid)(target));
|
||||
return;
|
||||
case 44:
|
||||
this.DialogCard = ((System.Windows.Controls.Border)(target));
|
||||
return;
|
||||
case 45:
|
||||
this.DialogScale = ((System.Windows.Media.ScaleTransform)(target));
|
||||
return;
|
||||
case 46:
|
||||
this.DialogIcon = ((System.Windows.Controls.TextBlock)(target));
|
||||
return;
|
||||
case 47:
|
||||
this.DialogTitle = ((System.Windows.Controls.TextBlock)(target));
|
||||
return;
|
||||
case 48:
|
||||
this.DialogText = ((System.Windows.Controls.TextBlock)(target));
|
||||
return;
|
||||
case 49:
|
||||
this.DialogSecondary = ((System.Windows.Controls.Button)(target));
|
||||
|
||||
#line 345 "..\..\..\MainWindow.xaml"
|
||||
this.DialogSecondary.Click += new System.Windows.RoutedEventHandler(this.DialogSecondary_Click);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
return;
|
||||
case 50:
|
||||
this.DialogPrimary = ((System.Windows.Controls.Button)(target));
|
||||
|
||||
#line 346 "..\..\..\MainWindow.xaml"
|
||||
this.DialogPrimary.Click += new System.Windows.RoutedEventHandler(this.DialogPrimary_Click);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
return;
|
||||
}
|
||||
this._contentLoaded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,471 @@
|
||||
{
|
||||
"version": 3,
|
||||
"targets": {
|
||||
".NETFramework,Version=v4.8": {
|
||||
"Microsoft.NETFramework.ReferenceAssemblies/1.0.3": {
|
||||
"type": "package",
|
||||
"dependencies": {
|
||||
"Microsoft.NETFramework.ReferenceAssemblies.net48": "1.0.3"
|
||||
}
|
||||
},
|
||||
"Microsoft.NETFramework.ReferenceAssemblies.net48/1.0.3": {
|
||||
"type": "package",
|
||||
"build": {
|
||||
"build/Microsoft.NETFramework.ReferenceAssemblies.net48.targets": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"libraries": {
|
||||
"Microsoft.NETFramework.ReferenceAssemblies/1.0.3": {
|
||||
"sha512": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==",
|
||||
"type": "package",
|
||||
"path": "microsoft.netframework.referenceassemblies/1.0.3",
|
||||
"files": [
|
||||
".nupkg.metadata",
|
||||
".signature.p7s",
|
||||
"microsoft.netframework.referenceassemblies.1.0.3.nupkg.sha512",
|
||||
"microsoft.netframework.referenceassemblies.nuspec"
|
||||
]
|
||||
},
|
||||
"Microsoft.NETFramework.ReferenceAssemblies.net48/1.0.3": {
|
||||
"sha512": "zMk4D+9zyiEWByyQ7oPImPN/Jhpj166Ky0Nlla4eXlNL8hI/BtSJsgR8Inldd4NNpIAH3oh8yym0W2DrhXdSLQ==",
|
||||
"type": "package",
|
||||
"path": "microsoft.netframework.referenceassemblies.net48/1.0.3",
|
||||
"files": [
|
||||
".nupkg.metadata",
|
||||
".signature.p7s",
|
||||
"build/.NETFramework/v4.8/Accessibility.dll",
|
||||
"build/.NETFramework/v4.8/Accessibility.xml",
|
||||
"build/.NETFramework/v4.8/CustomMarshalers.dll",
|
||||
"build/.NETFramework/v4.8/CustomMarshalers.xml",
|
||||
"build/.NETFramework/v4.8/Facades/Microsoft.Win32.Primitives.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.AppContext.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Collections.Concurrent.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Collections.NonGeneric.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Collections.Specialized.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Collections.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.ComponentModel.Annotations.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.ComponentModel.EventBasedAsync.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.ComponentModel.Primitives.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.ComponentModel.TypeConverter.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.ComponentModel.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Console.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Data.Common.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Diagnostics.Contracts.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Diagnostics.Debug.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Diagnostics.FileVersionInfo.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Diagnostics.Process.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Diagnostics.StackTrace.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Diagnostics.TextWriterTraceListener.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Diagnostics.Tools.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Diagnostics.TraceSource.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Drawing.Primitives.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Dynamic.Runtime.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Globalization.Calendars.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Globalization.Extensions.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Globalization.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.IO.Compression.ZipFile.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.IO.FileSystem.DriveInfo.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.IO.FileSystem.Primitives.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.IO.FileSystem.Watcher.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.IO.FileSystem.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.IO.IsolatedStorage.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.IO.MemoryMappedFiles.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.IO.Pipes.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.IO.UnmanagedMemoryStream.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.IO.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Linq.Expressions.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Linq.Parallel.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Linq.Queryable.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Linq.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Net.Http.Rtc.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Net.NameResolution.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Net.NetworkInformation.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Net.Ping.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Net.Primitives.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Net.Requests.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Net.Security.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Net.Sockets.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Net.WebHeaderCollection.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Net.WebSockets.Client.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Net.WebSockets.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.ObjectModel.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Reflection.Emit.ILGeneration.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Reflection.Emit.Lightweight.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Reflection.Emit.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Reflection.Extensions.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Reflection.Primitives.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Reflection.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Resources.Reader.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Resources.ResourceManager.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Resources.Writer.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Runtime.CompilerServices.VisualC.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Runtime.Extensions.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Runtime.Handles.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Runtime.InteropServices.RuntimeInformation.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Runtime.InteropServices.WindowsRuntime.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Runtime.InteropServices.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Runtime.Numerics.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Runtime.Serialization.Formatters.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Runtime.Serialization.Json.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Runtime.Serialization.Primitives.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Runtime.Serialization.Xml.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Runtime.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Security.Claims.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Security.Cryptography.Algorithms.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Security.Cryptography.Csp.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Security.Cryptography.Encoding.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Security.Cryptography.Primitives.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Security.Cryptography.X509Certificates.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Security.Principal.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Security.SecureString.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.ServiceModel.Duplex.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.ServiceModel.Http.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.ServiceModel.NetTcp.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.ServiceModel.Primitives.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.ServiceModel.Security.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Text.Encoding.Extensions.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Text.Encoding.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Text.RegularExpressions.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Threading.Overlapped.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Threading.Tasks.Parallel.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Threading.Tasks.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Threading.Thread.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Threading.ThreadPool.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Threading.Timer.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Threading.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.ValueTuple.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Xml.ReaderWriter.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Xml.XDocument.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Xml.XPath.XDocument.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Xml.XPath.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Xml.XmlDocument.dll",
|
||||
"build/.NETFramework/v4.8/Facades/System.Xml.XmlSerializer.dll",
|
||||
"build/.NETFramework/v4.8/Facades/netstandard.dll",
|
||||
"build/.NETFramework/v4.8/ISymWrapper.dll",
|
||||
"build/.NETFramework/v4.8/ISymWrapper.xml",
|
||||
"build/.NETFramework/v4.8/Microsoft.Activities.Build.dll",
|
||||
"build/.NETFramework/v4.8/Microsoft.Activities.Build.xml",
|
||||
"build/.NETFramework/v4.8/Microsoft.Build.Conversion.v4.0.dll",
|
||||
"build/.NETFramework/v4.8/Microsoft.Build.Conversion.v4.0.xml",
|
||||
"build/.NETFramework/v4.8/Microsoft.Build.Engine.dll",
|
||||
"build/.NETFramework/v4.8/Microsoft.Build.Engine.xml",
|
||||
"build/.NETFramework/v4.8/Microsoft.Build.Framework.dll",
|
||||
"build/.NETFramework/v4.8/Microsoft.Build.Framework.xml",
|
||||
"build/.NETFramework/v4.8/Microsoft.Build.Tasks.v4.0.dll",
|
||||
"build/.NETFramework/v4.8/Microsoft.Build.Tasks.v4.0.xml",
|
||||
"build/.NETFramework/v4.8/Microsoft.Build.Utilities.v4.0.dll",
|
||||
"build/.NETFramework/v4.8/Microsoft.Build.Utilities.v4.0.xml",
|
||||
"build/.NETFramework/v4.8/Microsoft.Build.dll",
|
||||
"build/.NETFramework/v4.8/Microsoft.Build.xml",
|
||||
"build/.NETFramework/v4.8/Microsoft.CSharp.dll",
|
||||
"build/.NETFramework/v4.8/Microsoft.CSharp.xml",
|
||||
"build/.NETFramework/v4.8/Microsoft.JScript.dll",
|
||||
"build/.NETFramework/v4.8/Microsoft.JScript.xml",
|
||||
"build/.NETFramework/v4.8/Microsoft.VisualBasic.Compatibility.Data.dll",
|
||||
"build/.NETFramework/v4.8/Microsoft.VisualBasic.Compatibility.Data.xml",
|
||||
"build/.NETFramework/v4.8/Microsoft.VisualBasic.Compatibility.dll",
|
||||
"build/.NETFramework/v4.8/Microsoft.VisualBasic.Compatibility.xml",
|
||||
"build/.NETFramework/v4.8/Microsoft.VisualBasic.dll",
|
||||
"build/.NETFramework/v4.8/Microsoft.VisualBasic.xml",
|
||||
"build/.NETFramework/v4.8/Microsoft.VisualC.STLCLR.dll",
|
||||
"build/.NETFramework/v4.8/Microsoft.VisualC.STLCLR.xml",
|
||||
"build/.NETFramework/v4.8/Microsoft.VisualC.dll",
|
||||
"build/.NETFramework/v4.8/Microsoft.VisualC.xml",
|
||||
"build/.NETFramework/v4.8/PermissionSets/FullTrust.xml",
|
||||
"build/.NETFramework/v4.8/PermissionSets/Internet.xml",
|
||||
"build/.NETFramework/v4.8/PermissionSets/LocalIntranet.xml",
|
||||
"build/.NETFramework/v4.8/PresentationBuildTasks.dll",
|
||||
"build/.NETFramework/v4.8/PresentationBuildTasks.xml",
|
||||
"build/.NETFramework/v4.8/PresentationCore.dll",
|
||||
"build/.NETFramework/v4.8/PresentationCore.xml",
|
||||
"build/.NETFramework/v4.8/PresentationFramework.Aero.dll",
|
||||
"build/.NETFramework/v4.8/PresentationFramework.Aero.xml",
|
||||
"build/.NETFramework/v4.8/PresentationFramework.Aero2.dll",
|
||||
"build/.NETFramework/v4.8/PresentationFramework.Aero2.xml",
|
||||
"build/.NETFramework/v4.8/PresentationFramework.AeroLite.dll",
|
||||
"build/.NETFramework/v4.8/PresentationFramework.AeroLite.xml",
|
||||
"build/.NETFramework/v4.8/PresentationFramework.Classic.dll",
|
||||
"build/.NETFramework/v4.8/PresentationFramework.Classic.xml",
|
||||
"build/.NETFramework/v4.8/PresentationFramework.Luna.dll",
|
||||
"build/.NETFramework/v4.8/PresentationFramework.Luna.xml",
|
||||
"build/.NETFramework/v4.8/PresentationFramework.Royale.dll",
|
||||
"build/.NETFramework/v4.8/PresentationFramework.Royale.xml",
|
||||
"build/.NETFramework/v4.8/PresentationFramework.dll",
|
||||
"build/.NETFramework/v4.8/PresentationFramework.xml",
|
||||
"build/.NETFramework/v4.8/ReachFramework.dll",
|
||||
"build/.NETFramework/v4.8/ReachFramework.xml",
|
||||
"build/.NETFramework/v4.8/RedistList/FrameworkList.xml",
|
||||
"build/.NETFramework/v4.8/System.Activities.Core.Presentation.dll",
|
||||
"build/.NETFramework/v4.8/System.Activities.Core.Presentation.xml",
|
||||
"build/.NETFramework/v4.8/System.Activities.DurableInstancing.dll",
|
||||
"build/.NETFramework/v4.8/System.Activities.DurableInstancing.xml",
|
||||
"build/.NETFramework/v4.8/System.Activities.Presentation.dll",
|
||||
"build/.NETFramework/v4.8/System.Activities.Presentation.xml",
|
||||
"build/.NETFramework/v4.8/System.Activities.dll",
|
||||
"build/.NETFramework/v4.8/System.Activities.xml",
|
||||
"build/.NETFramework/v4.8/System.AddIn.Contract.dll",
|
||||
"build/.NETFramework/v4.8/System.AddIn.Contract.xml",
|
||||
"build/.NETFramework/v4.8/System.AddIn.dll",
|
||||
"build/.NETFramework/v4.8/System.AddIn.xml",
|
||||
"build/.NETFramework/v4.8/System.ComponentModel.Composition.Registration.dll",
|
||||
"build/.NETFramework/v4.8/System.ComponentModel.Composition.Registration.xml",
|
||||
"build/.NETFramework/v4.8/System.ComponentModel.Composition.dll",
|
||||
"build/.NETFramework/v4.8/System.ComponentModel.Composition.xml",
|
||||
"build/.NETFramework/v4.8/System.ComponentModel.DataAnnotations.dll",
|
||||
"build/.NETFramework/v4.8/System.ComponentModel.DataAnnotations.xml",
|
||||
"build/.NETFramework/v4.8/System.Configuration.Install.dll",
|
||||
"build/.NETFramework/v4.8/System.Configuration.Install.xml",
|
||||
"build/.NETFramework/v4.8/System.Configuration.dll",
|
||||
"build/.NETFramework/v4.8/System.Configuration.xml",
|
||||
"build/.NETFramework/v4.8/System.Core.dll",
|
||||
"build/.NETFramework/v4.8/System.Core.xml",
|
||||
"build/.NETFramework/v4.8/System.Data.DataSetExtensions.dll",
|
||||
"build/.NETFramework/v4.8/System.Data.DataSetExtensions.xml",
|
||||
"build/.NETFramework/v4.8/System.Data.Entity.Design.dll",
|
||||
"build/.NETFramework/v4.8/System.Data.Entity.Design.xml",
|
||||
"build/.NETFramework/v4.8/System.Data.Entity.dll",
|
||||
"build/.NETFramework/v4.8/System.Data.Entity.xml",
|
||||
"build/.NETFramework/v4.8/System.Data.Linq.dll",
|
||||
"build/.NETFramework/v4.8/System.Data.Linq.xml",
|
||||
"build/.NETFramework/v4.8/System.Data.OracleClient.dll",
|
||||
"build/.NETFramework/v4.8/System.Data.OracleClient.xml",
|
||||
"build/.NETFramework/v4.8/System.Data.Services.Client.dll",
|
||||
"build/.NETFramework/v4.8/System.Data.Services.Client.xml",
|
||||
"build/.NETFramework/v4.8/System.Data.Services.Design.dll",
|
||||
"build/.NETFramework/v4.8/System.Data.Services.Design.xml",
|
||||
"build/.NETFramework/v4.8/System.Data.Services.dll",
|
||||
"build/.NETFramework/v4.8/System.Data.Services.xml",
|
||||
"build/.NETFramework/v4.8/System.Data.SqlXml.dll",
|
||||
"build/.NETFramework/v4.8/System.Data.SqlXml.xml",
|
||||
"build/.NETFramework/v4.8/System.Data.dll",
|
||||
"build/.NETFramework/v4.8/System.Data.xml",
|
||||
"build/.NETFramework/v4.8/System.Deployment.dll",
|
||||
"build/.NETFramework/v4.8/System.Deployment.xml",
|
||||
"build/.NETFramework/v4.8/System.Design.dll",
|
||||
"build/.NETFramework/v4.8/System.Design.xml",
|
||||
"build/.NETFramework/v4.8/System.Device.dll",
|
||||
"build/.NETFramework/v4.8/System.Device.xml",
|
||||
"build/.NETFramework/v4.8/System.Diagnostics.Tracing.dll",
|
||||
"build/.NETFramework/v4.8/System.Diagnostics.Tracing.xml",
|
||||
"build/.NETFramework/v4.8/System.DirectoryServices.AccountManagement.dll",
|
||||
"build/.NETFramework/v4.8/System.DirectoryServices.AccountManagement.xml",
|
||||
"build/.NETFramework/v4.8/System.DirectoryServices.Protocols.dll",
|
||||
"build/.NETFramework/v4.8/System.DirectoryServices.Protocols.xml",
|
||||
"build/.NETFramework/v4.8/System.DirectoryServices.dll",
|
||||
"build/.NETFramework/v4.8/System.DirectoryServices.xml",
|
||||
"build/.NETFramework/v4.8/System.Drawing.Design.dll",
|
||||
"build/.NETFramework/v4.8/System.Drawing.Design.xml",
|
||||
"build/.NETFramework/v4.8/System.Drawing.dll",
|
||||
"build/.NETFramework/v4.8/System.Drawing.xml",
|
||||
"build/.NETFramework/v4.8/System.Dynamic.dll",
|
||||
"build/.NETFramework/v4.8/System.EnterpriseServices.Thunk.dll",
|
||||
"build/.NETFramework/v4.8/System.EnterpriseServices.Wrapper.dll",
|
||||
"build/.NETFramework/v4.8/System.EnterpriseServices.dll",
|
||||
"build/.NETFramework/v4.8/System.EnterpriseServices.xml",
|
||||
"build/.NETFramework/v4.8/System.IO.Compression.FileSystem.dll",
|
||||
"build/.NETFramework/v4.8/System.IO.Compression.FileSystem.xml",
|
||||
"build/.NETFramework/v4.8/System.IO.Compression.dll",
|
||||
"build/.NETFramework/v4.8/System.IO.Compression.xml",
|
||||
"build/.NETFramework/v4.8/System.IO.Log.dll",
|
||||
"build/.NETFramework/v4.8/System.IO.Log.xml",
|
||||
"build/.NETFramework/v4.8/System.IdentityModel.Selectors.dll",
|
||||
"build/.NETFramework/v4.8/System.IdentityModel.Selectors.xml",
|
||||
"build/.NETFramework/v4.8/System.IdentityModel.Services.dll",
|
||||
"build/.NETFramework/v4.8/System.IdentityModel.Services.xml",
|
||||
"build/.NETFramework/v4.8/System.IdentityModel.dll",
|
||||
"build/.NETFramework/v4.8/System.IdentityModel.xml",
|
||||
"build/.NETFramework/v4.8/System.Linq.xml",
|
||||
"build/.NETFramework/v4.8/System.Management.Instrumentation.dll",
|
||||
"build/.NETFramework/v4.8/System.Management.Instrumentation.xml",
|
||||
"build/.NETFramework/v4.8/System.Management.dll",
|
||||
"build/.NETFramework/v4.8/System.Management.xml",
|
||||
"build/.NETFramework/v4.8/System.Messaging.dll",
|
||||
"build/.NETFramework/v4.8/System.Messaging.xml",
|
||||
"build/.NETFramework/v4.8/System.Net.Http.WebRequest.dll",
|
||||
"build/.NETFramework/v4.8/System.Net.Http.WebRequest.xml",
|
||||
"build/.NETFramework/v4.8/System.Net.Http.dll",
|
||||
"build/.NETFramework/v4.8/System.Net.Http.xml",
|
||||
"build/.NETFramework/v4.8/System.Net.dll",
|
||||
"build/.NETFramework/v4.8/System.Net.xml",
|
||||
"build/.NETFramework/v4.8/System.Numerics.dll",
|
||||
"build/.NETFramework/v4.8/System.Numerics.xml",
|
||||
"build/.NETFramework/v4.8/System.Printing.dll",
|
||||
"build/.NETFramework/v4.8/System.Printing.xml",
|
||||
"build/.NETFramework/v4.8/System.Reflection.Context.dll",
|
||||
"build/.NETFramework/v4.8/System.Reflection.Context.xml",
|
||||
"build/.NETFramework/v4.8/System.Runtime.Caching.dll",
|
||||
"build/.NETFramework/v4.8/System.Runtime.Caching.xml",
|
||||
"build/.NETFramework/v4.8/System.Runtime.DurableInstancing.dll",
|
||||
"build/.NETFramework/v4.8/System.Runtime.DurableInstancing.xml",
|
||||
"build/.NETFramework/v4.8/System.Runtime.Remoting.dll",
|
||||
"build/.NETFramework/v4.8/System.Runtime.Remoting.xml",
|
||||
"build/.NETFramework/v4.8/System.Runtime.Serialization.Formatters.Soap.dll",
|
||||
"build/.NETFramework/v4.8/System.Runtime.Serialization.Formatters.Soap.xml",
|
||||
"build/.NETFramework/v4.8/System.Runtime.Serialization.dll",
|
||||
"build/.NETFramework/v4.8/System.Runtime.Serialization.xml",
|
||||
"build/.NETFramework/v4.8/System.Security.dll",
|
||||
"build/.NETFramework/v4.8/System.Security.xml",
|
||||
"build/.NETFramework/v4.8/System.ServiceModel.Activation.dll",
|
||||
"build/.NETFramework/v4.8/System.ServiceModel.Activation.xml",
|
||||
"build/.NETFramework/v4.8/System.ServiceModel.Activities.dll",
|
||||
"build/.NETFramework/v4.8/System.ServiceModel.Activities.xml",
|
||||
"build/.NETFramework/v4.8/System.ServiceModel.Channels.dll",
|
||||
"build/.NETFramework/v4.8/System.ServiceModel.Channels.xml",
|
||||
"build/.NETFramework/v4.8/System.ServiceModel.Discovery.dll",
|
||||
"build/.NETFramework/v4.8/System.ServiceModel.Discovery.xml",
|
||||
"build/.NETFramework/v4.8/System.ServiceModel.Routing.dll",
|
||||
"build/.NETFramework/v4.8/System.ServiceModel.Routing.xml",
|
||||
"build/.NETFramework/v4.8/System.ServiceModel.Web.dll",
|
||||
"build/.NETFramework/v4.8/System.ServiceModel.Web.xml",
|
||||
"build/.NETFramework/v4.8/System.ServiceModel.dll",
|
||||
"build/.NETFramework/v4.8/System.ServiceModel.xml",
|
||||
"build/.NETFramework/v4.8/System.ServiceProcess.dll",
|
||||
"build/.NETFramework/v4.8/System.ServiceProcess.xml",
|
||||
"build/.NETFramework/v4.8/System.Speech.dll",
|
||||
"build/.NETFramework/v4.8/System.Speech.xml",
|
||||
"build/.NETFramework/v4.8/System.Threading.Tasks.Dataflow.xml",
|
||||
"build/.NETFramework/v4.8/System.Transactions.dll",
|
||||
"build/.NETFramework/v4.8/System.Transactions.xml",
|
||||
"build/.NETFramework/v4.8/System.Web.Abstractions.dll",
|
||||
"build/.NETFramework/v4.8/System.Web.ApplicationServices.dll",
|
||||
"build/.NETFramework/v4.8/System.Web.ApplicationServices.xml",
|
||||
"build/.NETFramework/v4.8/System.Web.DataVisualization.Design.dll",
|
||||
"build/.NETFramework/v4.8/System.Web.DataVisualization.dll",
|
||||
"build/.NETFramework/v4.8/System.Web.DataVisualization.xml",
|
||||
"build/.NETFramework/v4.8/System.Web.DynamicData.Design.dll",
|
||||
"build/.NETFramework/v4.8/System.Web.DynamicData.Design.xml",
|
||||
"build/.NETFramework/v4.8/System.Web.DynamicData.dll",
|
||||
"build/.NETFramework/v4.8/System.Web.DynamicData.xml",
|
||||
"build/.NETFramework/v4.8/System.Web.Entity.Design.dll",
|
||||
"build/.NETFramework/v4.8/System.Web.Entity.Design.xml",
|
||||
"build/.NETFramework/v4.8/System.Web.Entity.dll",
|
||||
"build/.NETFramework/v4.8/System.Web.Entity.xml",
|
||||
"build/.NETFramework/v4.8/System.Web.Extensions.Design.dll",
|
||||
"build/.NETFramework/v4.8/System.Web.Extensions.Design.xml",
|
||||
"build/.NETFramework/v4.8/System.Web.Extensions.dll",
|
||||
"build/.NETFramework/v4.8/System.Web.Extensions.xml",
|
||||
"build/.NETFramework/v4.8/System.Web.Mobile.dll",
|
||||
"build/.NETFramework/v4.8/System.Web.Mobile.xml",
|
||||
"build/.NETFramework/v4.8/System.Web.RegularExpressions.dll",
|
||||
"build/.NETFramework/v4.8/System.Web.RegularExpressions.xml",
|
||||
"build/.NETFramework/v4.8/System.Web.Routing.dll",
|
||||
"build/.NETFramework/v4.8/System.Web.Services.dll",
|
||||
"build/.NETFramework/v4.8/System.Web.Services.xml",
|
||||
"build/.NETFramework/v4.8/System.Web.dll",
|
||||
"build/.NETFramework/v4.8/System.Web.xml",
|
||||
"build/.NETFramework/v4.8/System.Windows.Controls.Ribbon.dll",
|
||||
"build/.NETFramework/v4.8/System.Windows.Controls.Ribbon.xml",
|
||||
"build/.NETFramework/v4.8/System.Windows.Forms.DataVisualization.Design.dll",
|
||||
"build/.NETFramework/v4.8/System.Windows.Forms.DataVisualization.dll",
|
||||
"build/.NETFramework/v4.8/System.Windows.Forms.DataVisualization.xml",
|
||||
"build/.NETFramework/v4.8/System.Windows.Forms.dll",
|
||||
"build/.NETFramework/v4.8/System.Windows.Forms.xml",
|
||||
"build/.NETFramework/v4.8/System.Windows.Input.Manipulations.dll",
|
||||
"build/.NETFramework/v4.8/System.Windows.Input.Manipulations.xml",
|
||||
"build/.NETFramework/v4.8/System.Windows.Presentation.dll",
|
||||
"build/.NETFramework/v4.8/System.Windows.Presentation.xml",
|
||||
"build/.NETFramework/v4.8/System.Windows.dll",
|
||||
"build/.NETFramework/v4.8/System.Workflow.Activities.dll",
|
||||
"build/.NETFramework/v4.8/System.Workflow.Activities.xml",
|
||||
"build/.NETFramework/v4.8/System.Workflow.ComponentModel.dll",
|
||||
"build/.NETFramework/v4.8/System.Workflow.ComponentModel.xml",
|
||||
"build/.NETFramework/v4.8/System.Workflow.Runtime.dll",
|
||||
"build/.NETFramework/v4.8/System.Workflow.Runtime.xml",
|
||||
"build/.NETFramework/v4.8/System.WorkflowServices.dll",
|
||||
"build/.NETFramework/v4.8/System.WorkflowServices.xml",
|
||||
"build/.NETFramework/v4.8/System.Xaml.dll",
|
||||
"build/.NETFramework/v4.8/System.Xaml.xml",
|
||||
"build/.NETFramework/v4.8/System.Xml.Linq.dll",
|
||||
"build/.NETFramework/v4.8/System.Xml.Linq.xml",
|
||||
"build/.NETFramework/v4.8/System.Xml.Serialization.dll",
|
||||
"build/.NETFramework/v4.8/System.Xml.dll",
|
||||
"build/.NETFramework/v4.8/System.Xml.xml",
|
||||
"build/.NETFramework/v4.8/System.dll",
|
||||
"build/.NETFramework/v4.8/System.xml",
|
||||
"build/.NETFramework/v4.8/UIAutomationClient.dll",
|
||||
"build/.NETFramework/v4.8/UIAutomationClient.xml",
|
||||
"build/.NETFramework/v4.8/UIAutomationClientsideProviders.dll",
|
||||
"build/.NETFramework/v4.8/UIAutomationClientsideProviders.xml",
|
||||
"build/.NETFramework/v4.8/UIAutomationProvider.dll",
|
||||
"build/.NETFramework/v4.8/UIAutomationProvider.xml",
|
||||
"build/.NETFramework/v4.8/UIAutomationTypes.dll",
|
||||
"build/.NETFramework/v4.8/UIAutomationTypes.xml",
|
||||
"build/.NETFramework/v4.8/WindowsBase.dll",
|
||||
"build/.NETFramework/v4.8/WindowsBase.xml",
|
||||
"build/.NETFramework/v4.8/WindowsFormsIntegration.dll",
|
||||
"build/.NETFramework/v4.8/WindowsFormsIntegration.xml",
|
||||
"build/.NETFramework/v4.8/XamlBuildTask.dll",
|
||||
"build/.NETFramework/v4.8/XamlBuildTask.xml",
|
||||
"build/.NETFramework/v4.8/mscorlib.dll",
|
||||
"build/.NETFramework/v4.8/mscorlib.xml",
|
||||
"build/.NETFramework/v4.8/namespaces.xml",
|
||||
"build/.NETFramework/v4.8/sysglobl.dll",
|
||||
"build/.NETFramework/v4.8/sysglobl.xml",
|
||||
"build/Microsoft.NETFramework.ReferenceAssemblies.net48.targets",
|
||||
"microsoft.netframework.referenceassemblies.net48.1.0.3.nupkg.sha512",
|
||||
"microsoft.netframework.referenceassemblies.net48.nuspec"
|
||||
]
|
||||
}
|
||||
},
|
||||
"projectFileDependencyGroups": {
|
||||
".NETFramework,Version=v4.8": [
|
||||
"Microsoft.NETFramework.ReferenceAssemblies >= 1.0.3"
|
||||
]
|
||||
},
|
||||
"packageFolders": {
|
||||
"C:\\Users\\Viper\\.nuget\\packages\\": {}
|
||||
},
|
||||
"project": {
|
||||
"version": "0.0.0",
|
||||
"restore": {
|
||||
"projectUniqueName": "C:\\Users\\Viper\\Desktop\\git-manager-gui\\installer\\GitManagerSetup.csproj",
|
||||
"projectName": "Git-Manager-GUI-Setup",
|
||||
"projectPath": "C:\\Users\\Viper\\Desktop\\git-manager-gui\\installer\\GitManagerSetup.csproj",
|
||||
"packagesPath": "C:\\Users\\Viper\\.nuget\\packages\\",
|
||||
"outputPath": "C:\\Users\\Viper\\Desktop\\git-manager-gui\\installer\\obj\\",
|
||||
"projectStyle": "PackageReference",
|
||||
"configFilePaths": [
|
||||
"C:\\Users\\Viper\\AppData\\Roaming\\NuGet\\NuGet.Config"
|
||||
],
|
||||
"originalTargetFrameworks": [
|
||||
"net48"
|
||||
],
|
||||
"sources": {
|
||||
"https://api.nuget.org/v3/index.json": {}
|
||||
},
|
||||
"frameworks": {
|
||||
"net48": {
|
||||
"targetAlias": "net48",
|
||||
"projectReferences": {}
|
||||
}
|
||||
},
|
||||
"warningProperties": {
|
||||
"warnAsError": [
|
||||
"NU1605"
|
||||
]
|
||||
},
|
||||
"restoreAuditProperties": {
|
||||
"enableAudit": "true",
|
||||
"auditLevel": "low",
|
||||
"auditMode": "direct"
|
||||
},
|
||||
"SdkAnalysisLevel": "10.0.100"
|
||||
},
|
||||
"frameworks": {
|
||||
"net48": {
|
||||
"targetAlias": "net48",
|
||||
"dependencies": {
|
||||
"Microsoft.NETFramework.ReferenceAssemblies": {
|
||||
"suppressParent": "All",
|
||||
"target": "Package",
|
||||
"version": "[1.0.3, )",
|
||||
"autoReferenced": true
|
||||
}
|
||||
},
|
||||
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.113\\RuntimeIdentifierGraph.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "5on68f3wDDM=",
|
||||
"success": true,
|
||||
"projectFilePath": "C:\\Users\\Viper\\Desktop\\git-manager-gui\\installer\\GitManagerSetup.csproj",
|
||||
"expectedPackageFiles": [
|
||||
"C:\\Users\\Viper\\.nuget\\packages\\microsoft.netframework.referenceassemblies\\1.0.3\\microsoft.netframework.referenceassemblies.1.0.3.nupkg.sha512",
|
||||
"C:\\Users\\Viper\\.nuget\\packages\\microsoft.netframework.referenceassemblies.net48\\1.0.3\\microsoft.netframework.referenceassemblies.net48.1.0.3.nupkg.sha512"
|
||||
],
|
||||
"logs": []
|
||||
}
|
||||
@@ -490,13 +490,13 @@
|
||||
<!-- Features -->
|
||||
<section class="section" id="features">
|
||||
<h2 class="section-title">Warum Git Manager Pro?</h2>
|
||||
<p class="section-desc">Alles was du brauchst um deine Repositories effizient zu verwalten.</p>
|
||||
<p class="section-desc">Alles, was du brauchst, um deine Repositories effizient zu verwalten.</p>
|
||||
|
||||
<div class="features-grid">
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">🌐</div>
|
||||
<h3>Gitea & GitHub</h3>
|
||||
<p>Unterstützt beide Plattformen seamless — alle Repositories an einem Ort.</p>
|
||||
<p>Unterstützt beide Plattformen nahtlos — alle Repositories an einem Ort.</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">📂</div>
|
||||
@@ -535,16 +535,16 @@
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">📥</div>
|
||||
<h3>Ein-Klick Migration</h3>
|
||||
<h3>Ein-Klick-Migration</h3>
|
||||
<p>Migriere Repos zwischen Services — mit kompletter History.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<h3 style="color: var(--accent2); margin-bottom: 16px;">🎥 Demo (baldiger Update)</h3>
|
||||
<h3 style="color: var(--accent2); margin-bottom: 16px;">🎥 Demo (baldiges Update)</h3>
|
||||
<div class="demo-mockup">
|
||||
Screenshot-Bereich — Demo und UI-Preview kommen bald!<br><br>
|
||||
<em>Starte die App mit npm start um einen Live-Demo zu sehen</em>
|
||||
Screenshot-Bereich — Demo und UI-Vorschau kommen bald!<br><br>
|
||||
<em>Starte die App mit npm start, um eine Live-Demo zu sehen</em>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -603,7 +603,7 @@
|
||||
<div class="links-section">
|
||||
<div class="link-card">
|
||||
<h3>📖 Benutzerhandbuch</h3>
|
||||
<p>Vollständiges Handbuch mit Screenshots und Schritt-für-Schritt Anleitungen.</p>
|
||||
<p>Vollständiges Handbuch mit Screenshots und Schritt-für-Schritt-Anleitungen.</p>
|
||||
<a href="HANDBUCH.html" target="_blank">Zum Handbuch →</a>
|
||||
</div>
|
||||
<div class="link-card">
|
||||
|
||||