Compare commits
12
Commits
2026.7.19
...
ff00925b13
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff00925b13 | ||
|
|
982dbf136d | ||
|
|
646dcc341e | ||
|
|
5f3e55dcd7 | ||
|
|
fdbff8175e | ||
|
|
a6e7e991dd | ||
|
|
3491b8efb3 | ||
|
|
59a8f34e0e | ||
|
|
286b106baa | ||
|
|
670e7e24fe | ||
|
|
6729d61dca | ||
|
|
b181b384e7 |
@@ -104,7 +104,19 @@
|
|||||||
"Read(//home/christoph/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tauri-2.11.5/src/**)",
|
"Read(//home/christoph/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tauri-2.11.5/src/**)",
|
||||||
"Read(//home/christoph/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tauri-runtime-wry-2.11.4/src/**)",
|
"Read(//home/christoph/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tauri-runtime-wry-2.11.4/src/**)",
|
||||||
"Read(//home/christoph/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tauri-2.11.5/src/window/**)",
|
"Read(//home/christoph/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tauri-2.11.5/src/window/**)",
|
||||||
"WebSearch"
|
"WebSearch",
|
||||||
|
"Bash(node -p \"require\\('./package.json'\\).version\")",
|
||||||
|
"Bash(bash -n PKGBUILD)",
|
||||||
|
"Bash(makepkg --printsrcinfo)",
|
||||||
|
"Bash(npx tauri *)",
|
||||||
|
"Bash(echo \"---exit code:$?---\")",
|
||||||
|
"Bash(git -C /mnt/data/Development/GitLite status --short)",
|
||||||
|
"Bash(makepkg -f --noconfirm)",
|
||||||
|
"Bash(tar -tf gitty-2026.7.19-1-x86_64.pkg.tar.zst)",
|
||||||
|
"Bash(cp /mnt/data/Development/GitLite/PKGBUILD .)",
|
||||||
|
"Bash(sed -i 's/^pkgrel=1/pkgrel=3/' PKGBUILD)",
|
||||||
|
"Bash(node -e \"const fs=require\\('fs'\\); const version='2026.8.1'; const pkgbuild=fs.readFileSync\\('PKGBUILD','utf8'\\).replace\\(/^pkgver=.*\\\\$/m, 'pkgver='+version\\).replace\\(/^pkgrel=.*\\\\$/m, 'pkgrel=1'\\); fs.writeFileSync\\('PKGBUILD', pkgbuild\\);\")",
|
||||||
|
"Bash(rm PKGBUILD)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,6 +87,10 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
node -e "const fs=require('fs'); const path='src-tauri/tauri.conf.json'; const config=JSON.parse(fs.readFileSync(path,'utf8')); config.version=process.env.RELEASE_VERSION; fs.writeFileSync(path, JSON.stringify(config,null,2)+'\n');"
|
node -e "const fs=require('fs'); const path='src-tauri/tauri.conf.json'; const config=JSON.parse(fs.readFileSync(path,'utf8')); config.version=process.env.RELEASE_VERSION; fs.writeFileSync(path, JSON.stringify(config,null,2)+'\n');"
|
||||||
|
|
||||||
|
- name: Update PKGBUILD Version
|
||||||
|
run: |
|
||||||
|
node -e "const fs=require('fs'); const version=require('./package.json').version; const pkgbuild=fs.readFileSync('PKGBUILD','utf8').replace(/^pkgver=.*$/m, 'pkgver='+version).replace(/^pkgrel=.*$/m, 'pkgrel=1'); fs.writeFileSync('PKGBUILD', pkgbuild);"
|
||||||
|
|
||||||
- name: Commit and Push Changes
|
- name: Commit and Push Changes
|
||||||
if: matrix.platform == 'windows-latest'
|
if: matrix.platform == 'windows-latest'
|
||||||
shell: powershell
|
shell: powershell
|
||||||
@@ -95,7 +99,7 @@ jobs:
|
|||||||
git config user.email '${{ vars.EMAIL_GIT }}'
|
git config user.email '${{ vars.EMAIL_GIT }}'
|
||||||
# npm version also bumps the version inside package-lock.json, so stage it
|
# npm version also bumps the version inside package-lock.json, so stage it
|
||||||
# too -- otherwise it stays as an unstaged change and blocks the rebase.
|
# too -- otherwise it stays as an unstaged change and blocks the rebase.
|
||||||
git add package.json package-lock.json src-tauri/tauri.conf.json
|
git add package.json package-lock.json src-tauri/tauri.conf.json PKGBUILD
|
||||||
|
|
||||||
git commit -m 'Update version to ${{ github.ref_name }}'
|
git commit -m 'Update version to ${{ github.ref_name }}'
|
||||||
if ($LASTEXITCODE -ne 0) {
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
|||||||
@@ -7,3 +7,6 @@
|
|||||||
~
|
~
|
||||||
.codex*
|
.codex*
|
||||||
target
|
target
|
||||||
|
|
||||||
|
*.pkg.tar.zst
|
||||||
|
pkg
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
# Maintainer: Christoph Brandau <c.brandau91@googlemail.com>
|
||||||
|
#
|
||||||
|
# Local/in-tree PKGBUILD: builds straight from this working directory (no
|
||||||
|
# source download, no tauri-bundler/AppImage step) and installs the raw
|
||||||
|
# binary + desktop entry. Run `makepkg -si` from the repo root.
|
||||||
|
|
||||||
|
pkgname=gitty
|
||||||
|
pkgver=2026.7.19
|
||||||
|
pkgrel=1
|
||||||
|
pkgdesc="A lightweight, modern Git client built with Tauri"
|
||||||
|
arch=('x86_64')
|
||||||
|
url="https://git.cbsk-tech.de/Christoph/GitLite"
|
||||||
|
license=('MIT')
|
||||||
|
depends=('webkit2gtk-4.1' 'gtk3' 'git' 'hicolor-icon-theme')
|
||||||
|
makedepends=('rust' 'nodejs' 'npm')
|
||||||
|
options=('!lto')
|
||||||
|
|
||||||
|
source=()
|
||||||
|
sha256sums=()
|
||||||
|
|
||||||
|
# Keeps pkgver in sync with package.json (the release CI bumps that file).
|
||||||
|
pkgver() {
|
||||||
|
cd "$startdir"
|
||||||
|
node -p "require('./package.json').version"
|
||||||
|
}
|
||||||
|
|
||||||
|
build() {
|
||||||
|
cd "$startdir"
|
||||||
|
npm ci
|
||||||
|
# Build through the Tauri CLI (not a raw `cargo build --release`): it enables
|
||||||
|
# the `custom-protocol` cargo feature that makes the binary load the embedded
|
||||||
|
# `dist/` assets instead of the Vite dev server URL (127.0.0.1:1420), and runs
|
||||||
|
# `beforeBuildCommand` (`npm run build`) for us. `--no-bundle` skips deb/rpm/
|
||||||
|
# appimage packaging (and with it linuxdeploy/FUSE) since pacman owns that here.
|
||||||
|
npm run tauri -- build --no-bundle
|
||||||
|
}
|
||||||
|
|
||||||
|
package() {
|
||||||
|
cd "$startdir"
|
||||||
|
|
||||||
|
install -Dm755 "src-tauri/target/release/gitty" "$pkgdir/usr/bin/gitty"
|
||||||
|
|
||||||
|
install -Dm644 "src-tauri/icons/32x32.png" \
|
||||||
|
"$pkgdir/usr/share/icons/hicolor/32x32/apps/gitty.png"
|
||||||
|
install -Dm644 "src-tauri/icons/128x128.png" \
|
||||||
|
"$pkgdir/usr/share/icons/hicolor/128x128/apps/gitty.png"
|
||||||
|
install -Dm644 "src-tauri/icons/128x128@2x.png" \
|
||||||
|
"$pkgdir/usr/share/icons/hicolor/256x256@2/apps/gitty.png"
|
||||||
|
install -Dm644 "src-tauri/icons/icon.png" \
|
||||||
|
"$pkgdir/usr/share/icons/hicolor/512x512/apps/gitty.png"
|
||||||
|
|
||||||
|
install -d "$pkgdir/usr/share/applications"
|
||||||
|
cat > "$pkgdir/usr/share/applications/gitty.desktop" <<-EOF
|
||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=Gitty
|
||||||
|
Comment=$pkgdesc
|
||||||
|
Exec=gitty
|
||||||
|
Icon=gitty
|
||||||
|
Terminal=false
|
||||||
|
Categories=Development;RevisionControl;
|
||||||
|
StartupWMClass=gitty
|
||||||
|
EOF
|
||||||
|
|
||||||
|
install -Dm644 "README.md" "$pkgdir/usr/share/doc/$pkgname/README.md"
|
||||||
|
}
|
||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "gitty",
|
"name": "gitty",
|
||||||
"version": "0.200.10",
|
"version": "2026.7.19",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "gitty",
|
"name": "gitty",
|
||||||
"version": "0.200.10",
|
"version": "2026.7.19",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@lucide/svelte": "^1.21.0",
|
"@lucide/svelte": "^1.21.0",
|
||||||
"@tailwindcss/vite": "^4.3.1",
|
"@tailwindcss/vite": "^4.3.1",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "gitty",
|
"name": "gitty",
|
||||||
"version": "0.200.10",
|
"version": "2026.7.19",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -27,6 +27,12 @@ tauri-build = { version = "2", features = [] }
|
|||||||
|
|
||||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||||
tauri-plugin-single-instance = "2"
|
tauri-plugin-single-instance = "2"
|
||||||
|
|
||||||
|
# Self-updating only makes sense for install methods this app ships itself
|
||||||
|
# (NSIS on Windows, the .app bundle on macOS). On Linux the app is meant to be
|
||||||
|
# installed via the system package manager (e.g. the PKGBUILD), which owns
|
||||||
|
# updates instead — so the updater plugin isn't even compiled in there.
|
||||||
|
[target.'cfg(not(any(target_os = "android", target_os = "ios", target_os = "linux")))'.dependencies]
|
||||||
tauri-plugin-updater = "2"
|
tauri-plugin-updater = "2"
|
||||||
|
|
||||||
# ── Build profiles ───────────────────────────────────────────────────────────
|
# ── Build profiles ───────────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -2,8 +2,7 @@
|
|||||||
"identifier": "desktop-capability",
|
"identifier": "desktop-capability",
|
||||||
"platforms": [
|
"platforms": [
|
||||||
"macOS",
|
"macOS",
|
||||||
"windows",
|
"windows"
|
||||||
"linux"
|
|
||||||
],
|
],
|
||||||
"windows": [
|
"windows": [
|
||||||
"main"
|
"main"
|
||||||
|
|||||||
+11
-3
@@ -33,6 +33,7 @@ fn close_splashscreen(app: tauri::AppHandle) -> Result<(), String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(window) = app.get_webview_window("main") {
|
if let Some(window) = app.get_webview_window("main") {
|
||||||
|
let _ = window.maximize();
|
||||||
window
|
window
|
||||||
.show()
|
.show()
|
||||||
.map_err(|error| format!("failed to show main window: {error}"))?;
|
.map_err(|error| format!("failed to show main window: {error}"))?;
|
||||||
@@ -52,7 +53,7 @@ async fn main() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
tauri::Builder::default()
|
let builder =tauri::Builder::default()
|
||||||
.plugin(tauri_plugin_single_instance::init(|app, _, _| {
|
.plugin(tauri_plugin_single_instance::init(|app, _, _| {
|
||||||
#[cfg(desktop)]
|
#[cfg(desktop)]
|
||||||
let _ = app
|
let _ = app
|
||||||
@@ -60,7 +61,6 @@ async fn main() {
|
|||||||
.expect("no main window")
|
.expect("no main window")
|
||||||
.set_focus();
|
.set_focus();
|
||||||
}))
|
}))
|
||||||
.plugin(tauri_plugin_updater::Builder::new().build())
|
|
||||||
.plugin(
|
.plugin(
|
||||||
tauri_plugin_aptabase::Builder::new("A-SH-1344793789")
|
tauri_plugin_aptabase::Builder::new("A-SH-1344793789")
|
||||||
.with_options(tauri_plugin_aptabase::InitOptions {
|
.with_options(tauri_plugin_aptabase::InitOptions {
|
||||||
@@ -71,7 +71,15 @@ async fn main() {
|
|||||||
)
|
)
|
||||||
.manage(SearchCancellationState::default())
|
.manage(SearchCancellationState::default())
|
||||||
.manage(commit_ai::CommitAiEngine::new())
|
.manage(commit_ai::CommitAiEngine::new())
|
||||||
.plugin(tauri_plugin_dialog::init())
|
.plugin(tauri_plugin_dialog::init());
|
||||||
|
|
||||||
|
// Linux installs are expected to come from the system package manager (see the
|
||||||
|
// PKGBUILD), which owns updates itself — the self-updater is only wired up for
|
||||||
|
// the platforms whose install method this app ships (NSIS/Windows, .app/macOS).
|
||||||
|
#[cfg(not(target_os = "linux"))]
|
||||||
|
let builder = builder.plugin(tauri_plugin_updater::Builder::new().build());
|
||||||
|
|
||||||
|
builder
|
||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
open_repository,
|
open_repository,
|
||||||
clone_repository,
|
clone_repository,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "Gitty",
|
"productName": "Gitty",
|
||||||
"version": "0.200.10",
|
"version": "2026.7.19",
|
||||||
"identifier": "com.gitty",
|
"identifier": "com.gitty",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "npm run dev",
|
"beforeDevCommand": "npm run dev",
|
||||||
|
|||||||
+81
-17
@@ -6,6 +6,7 @@
|
|||||||
import { AlertCircle, BookOpen, Cherry, Download, FolderOpen, GitBranch, GitMerge, LoaderCircle, Plus, Search, Star, X } from "@lucide/svelte";
|
import { AlertCircle, BookOpen, Cherry, Download, FolderOpen, GitBranch, GitMerge, LoaderCircle, Plus, Search, Star, X } from "@lucide/svelte";
|
||||||
|
|
||||||
import TitleBar from "./lib/TitleBar.svelte";
|
import TitleBar from "./lib/TitleBar.svelte";
|
||||||
|
import RepoToolbar from "./lib/RepoToolbar.svelte";
|
||||||
import AiSettingsDialog from "./lib/components/AiSettingsDialog.svelte";
|
import AiSettingsDialog from "./lib/components/AiSettingsDialog.svelte";
|
||||||
import AnalyticsNoticeDialog from "./lib/components/AnalyticsNoticeDialog.svelte";
|
import AnalyticsNoticeDialog from "./lib/components/AnalyticsNoticeDialog.svelte";
|
||||||
import AppSettingsDialog from "./lib/components/AppSettingsDialog.svelte";
|
import AppSettingsDialog from "./lib/components/AppSettingsDialog.svelte";
|
||||||
@@ -21,6 +22,7 @@
|
|||||||
import ExplorerPanel from "./lib/components/ExplorerPanel.svelte";
|
import ExplorerPanel from "./lib/components/ExplorerPanel.svelte";
|
||||||
import FileHistoryPanel from "./lib/components/FileHistoryPanel.svelte";
|
import FileHistoryPanel from "./lib/components/FileHistoryPanel.svelte";
|
||||||
import GlobalSearchDialog from "./lib/components/GlobalSearchDialog.svelte";
|
import GlobalSearchDialog from "./lib/components/GlobalSearchDialog.svelte";
|
||||||
|
import HelpOverlay from "./lib/components/HelpOverlay.svelte";
|
||||||
import HistoryPanel from "./lib/components/HistoryPanel.svelte";
|
import HistoryPanel from "./lib/components/HistoryPanel.svelte";
|
||||||
import InteractiveRebaseDialog from "./lib/components/InteractiveRebaseDialog.svelte";
|
import InteractiveRebaseDialog from "./lib/components/InteractiveRebaseDialog.svelte";
|
||||||
import LinePatchDialog from "./lib/components/LinePatchDialog.svelte";
|
import LinePatchDialog from "./lib/components/LinePatchDialog.svelte";
|
||||||
@@ -104,6 +106,7 @@
|
|||||||
|
|
||||||
import type {
|
import type {
|
||||||
AiSettings,
|
AiSettings,
|
||||||
|
AppLanguage,
|
||||||
AppTheme,
|
AppTheme,
|
||||||
AnalyticsSettings,
|
AnalyticsSettings,
|
||||||
CommitAiPhase,
|
CommitAiPhase,
|
||||||
@@ -176,6 +179,7 @@
|
|||||||
const AI_SETTINGS_KEY = "gitlite.aiSettings.v1";
|
const AI_SETTINGS_KEY = "gitlite.aiSettings.v1";
|
||||||
const ANALYTICS_SETTINGS_KEY = "gitlite.analyticsSettings.v1";
|
const ANALYTICS_SETTINGS_KEY = "gitlite.analyticsSettings.v1";
|
||||||
const APP_THEME_KEY = "gitlite.theme.v1";
|
const APP_THEME_KEY = "gitlite.theme.v1";
|
||||||
|
const APP_LANGUAGE_KEY = "gitlite.language.v1";
|
||||||
const COMMIT_PANEL_HEIGHT_KEY = "gitlite.commitPanelHeight.v1";
|
const COMMIT_PANEL_HEIGHT_KEY = "gitlite.commitPanelHeight.v1";
|
||||||
const LEFT_SIDEBAR_WIDTH_KEY = "gitlite.leftSidebarWidth.v1";
|
const LEFT_SIDEBAR_WIDTH_KEY = "gitlite.leftSidebarWidth.v1";
|
||||||
const LEFT_BRANCH_PANEL_HEIGHT_KEY = "gitlite.leftBranchPanelHeight.v1";
|
const LEFT_BRANCH_PANEL_HEIGHT_KEY = "gitlite.leftBranchPanelHeight.v1";
|
||||||
@@ -248,9 +252,11 @@
|
|||||||
let aiSettings: AiSettings = defaultAiSettings();
|
let aiSettings: AiSettings = defaultAiSettings();
|
||||||
let aiSettingsOpen = false;
|
let aiSettingsOpen = false;
|
||||||
let appSettingsOpen = false;
|
let appSettingsOpen = false;
|
||||||
|
let helpOpen = false;
|
||||||
let analyticsNoticeOpen = false;
|
let analyticsNoticeOpen = false;
|
||||||
let analyticsSettings: AnalyticsSettings = defaultAnalyticsSettings();
|
let analyticsSettings: AnalyticsSettings = defaultAnalyticsSettings();
|
||||||
let appTheme: AppTheme = loadThemePreference();
|
let appTheme: AppTheme = loadThemePreference();
|
||||||
|
let appLanguage: AppLanguage = loadLanguagePreference();
|
||||||
let localModelOptions: LocalModelOption[] = [];
|
let localModelOptions: LocalModelOption[] = [];
|
||||||
let errorMessage = "";
|
let errorMessage = "";
|
||||||
let operation = "";
|
let operation = "";
|
||||||
@@ -412,6 +418,7 @@
|
|||||||
$: allLeftPanelsCollapsed = branchPanelCollapsed && stashPanelCollapsed && explorerPanelCollapsed;
|
$: allLeftPanelsCollapsed = branchPanelCollapsed && stashPanelCollapsed && explorerPanelCollapsed;
|
||||||
|
|
||||||
$: applyThemePreference(appTheme);
|
$: applyThemePreference(appTheme);
|
||||||
|
$: applyLanguagePreference(appLanguage);
|
||||||
|
|
||||||
// ── Lifecycle ──────────────────────────────────────────────────────────────
|
// ── Lifecycle ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -672,7 +679,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function autoRefreshTick() {
|
async function autoRefreshTick() {
|
||||||
if (!autoRefreshEnabled || activeView !== "repository" || !activeRepoPath || isBusy || autoRefreshInFlight || resolveDialogOpen || compareDialogOpen || compareSelectOpen || interactiveRebaseOpen || reflogOpen || newBranchCommit || globalSearchOpen) return;
|
if (!autoRefreshEnabled || activeView !== "repository" || !activeRepoPath || isBusy || autoRefreshInFlight || resolveDialogOpen || compareDialogOpen || compareSelectOpen || interactiveRebaseOpen || reflogOpen || newBranchCommit || globalSearchOpen || helpOpen) return;
|
||||||
const path = activeRepoPath;
|
const path = activeRepoPath;
|
||||||
autoRefreshInFlight = true;
|
autoRefreshInFlight = true;
|
||||||
try {
|
try {
|
||||||
@@ -813,6 +820,29 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function loadLanguagePreference(): AppLanguage {
|
||||||
|
try {
|
||||||
|
const stored = localStorage.getItem(APP_LANGUAGE_KEY);
|
||||||
|
if (stored === "en" || stored === "de") return stored;
|
||||||
|
} catch {
|
||||||
|
// Local storage is optional; English is the first-start default.
|
||||||
|
}
|
||||||
|
return "en";
|
||||||
|
}
|
||||||
|
|
||||||
|
function persistLanguagePreference(next: AppLanguage) {
|
||||||
|
try {
|
||||||
|
localStorage.setItem(APP_LANGUAGE_KEY, next);
|
||||||
|
} catch {
|
||||||
|
// Ignore storage quota/private-mode errors.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyLanguagePreference(next: AppLanguage) {
|
||||||
|
document.documentElement.lang = next;
|
||||||
|
document.documentElement.dataset.language = next;
|
||||||
|
}
|
||||||
|
|
||||||
function applyThemePreference(next: AppTheme) {
|
function applyThemePreference(next: AppTheme) {
|
||||||
const prefersLight = themeMediaQuery?.matches ?? window.matchMedia("(prefers-color-scheme: light)").matches;
|
const prefersLight = themeMediaQuery?.matches ?? window.matchMedia("(prefers-color-scheme: light)").matches;
|
||||||
const resolved = next === "system"
|
const resolved = next === "system"
|
||||||
@@ -828,13 +858,15 @@
|
|||||||
if (appTheme === "system") applyThemePreference(appTheme);
|
if (appTheme === "system") applyThemePreference(appTheme);
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveAppSettings(next: AnalyticsSettings, nextTheme: AppTheme) {
|
function saveAppSettings(next: AnalyticsSettings, nextTheme: AppTheme, nextLanguage: AppLanguage) {
|
||||||
analyticsSettings = next;
|
analyticsSettings = next;
|
||||||
appTheme = nextTheme;
|
appTheme = nextTheme;
|
||||||
|
appLanguage = nextLanguage;
|
||||||
persistAnalyticsSettings(next);
|
persistAnalyticsSettings(next);
|
||||||
persistThemePreference(nextTheme);
|
persistThemePreference(nextTheme);
|
||||||
|
persistLanguagePreference(nextLanguage);
|
||||||
appSettingsOpen = false;
|
appSettingsOpen = false;
|
||||||
if (next.enabled) trackEvent("settings_saved", { analytics_enabled: 1, theme: nextTheme });
|
if (next.enabled) trackEvent("settings_saved", { analytics_enabled: 1, theme: nextTheme, language: nextLanguage });
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateCommitMessage(message: string) {
|
function updateCommitMessage(message: string) {
|
||||||
@@ -3259,6 +3291,11 @@
|
|||||||
trackEvent("global_search_opened");
|
trackEvent("global_search_opened");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openHelp() {
|
||||||
|
helpOpen = true;
|
||||||
|
trackEvent("help_opened");
|
||||||
|
}
|
||||||
|
|
||||||
async function compareSelectedCommits() {
|
async function compareSelectedCommits() {
|
||||||
if (!canCompare) return;
|
if (!canCompare) return;
|
||||||
await runOperation("Comparing commits", async () => {
|
await runOperation("Comparing commits", async () => {
|
||||||
@@ -3449,6 +3486,15 @@
|
|||||||
// ── Event handlers ─────────────────────────────────────────────────────────
|
// ── Event handlers ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
function handleWindowKeydown(event: KeyboardEvent) {
|
function handleWindowKeydown(event: KeyboardEvent) {
|
||||||
|
if ((event.ctrlKey || event.metaKey) && event.key === "/") {
|
||||||
|
event.preventDefault();
|
||||||
|
openHelp();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (event.key === "Escape" && helpOpen) {
|
||||||
|
helpOpen = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (event.key === "Escape" && repoTabContextMenu) closeRepoTabContextMenu();
|
if (event.key === "Escape" && repoTabContextMenu) closeRepoTabContextMenu();
|
||||||
else if (event.key === "Escape" && pendingDiscard && !isBusy) closeDiscardConfirm();
|
else if (event.key === "Escape" && pendingDiscard && !isBusy) closeDiscardConfirm();
|
||||||
else if (event.key === "Escape" && compareDialogOpen) closeCompareDialog();
|
else if (event.key === "Escape" && compareDialogOpen) closeCompareDialog();
|
||||||
@@ -3484,21 +3530,9 @@
|
|||||||
behind={status?.behind ?? 0}
|
behind={status?.behind ?? 0}
|
||||||
repoName={activeRepoPath ? (activeRepoPath.split(/[\\/]/).filter(Boolean).slice(-1)[0] ?? "") : ""}
|
repoName={activeRepoPath ? (activeRepoPath.split(/[\\/]/).filter(Boolean).slice(-1)[0] ?? "") : ""}
|
||||||
hasRepository={workspaceActive}
|
hasRepository={workspaceActive}
|
||||||
{isBusy}
|
|
||||||
{operation}
|
|
||||||
{autoRefreshEnabled}
|
|
||||||
{autoRefreshInFlight}
|
|
||||||
onFetch={fetchRepo}
|
|
||||||
onPull={pullRepo}
|
|
||||||
onPush={pushRepo}
|
|
||||||
onRefresh={refreshRepo}
|
|
||||||
onSearch={openGlobalSearchDialog}
|
|
||||||
onCompare={openCompareSelect}
|
|
||||||
onInteractiveRebase={openInteractiveRebase}
|
|
||||||
onReflog={openReflog}
|
|
||||||
onOpenInExplorer={openActiveRepoInExplorer}
|
|
||||||
onToggleAutoRefresh={toggleAutoRefresh}
|
|
||||||
onOpenSettings={() => { appSettingsOpen = true; }}
|
onOpenSettings={() => { appSettingsOpen = true; }}
|
||||||
|
onOpenHelp={openHelp}
|
||||||
|
language={appLanguage}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="shell-body">
|
<div class="shell-body">
|
||||||
@@ -3563,6 +3597,31 @@
|
|||||||
</button>
|
</button>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
<!-- Repo actions live under the tab bar and disappear in Repository
|
||||||
|
Management, where none of them are applicable. -->
|
||||||
|
{#if workspaceActive}
|
||||||
|
<RepoToolbar
|
||||||
|
hasRepository={workspaceActive}
|
||||||
|
{isBusy}
|
||||||
|
{operation}
|
||||||
|
ahead={status?.ahead ?? 0}
|
||||||
|
behind={status?.behind ?? 0}
|
||||||
|
{autoRefreshEnabled}
|
||||||
|
{autoRefreshInFlight}
|
||||||
|
language={appLanguage}
|
||||||
|
onFetch={fetchRepo}
|
||||||
|
onPull={pullRepo}
|
||||||
|
onPush={pushRepo}
|
||||||
|
onRefresh={refreshRepo}
|
||||||
|
onSearch={openGlobalSearchDialog}
|
||||||
|
onCompare={openCompareSelect}
|
||||||
|
onInteractiveRebase={openInteractiveRebase}
|
||||||
|
onReflog={openReflog}
|
||||||
|
onOpenInExplorer={openActiveRepoInExplorer}
|
||||||
|
onToggleAutoRefresh={toggleAutoRefresh}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if repoTabContextMenu}
|
{#if repoTabContextMenu}
|
||||||
{@const menu = repoTabContextMenu}
|
{@const menu = repoTabContextMenu}
|
||||||
{@const contextTab = repoTabs.find((tab) => sameRepoPath(tab.path, menu.path))}
|
{@const contextTab = repoTabs.find((tab) => sameRepoPath(tab.path, menu.path))}
|
||||||
@@ -4113,11 +4172,16 @@
|
|||||||
<AppSettingsDialog
|
<AppSettingsDialog
|
||||||
analytics={analyticsSettings}
|
analytics={analyticsSettings}
|
||||||
theme={appTheme}
|
theme={appTheme}
|
||||||
|
language={appLanguage}
|
||||||
onSave={saveAppSettings}
|
onSave={saveAppSettings}
|
||||||
onClose={() => { appSettingsOpen = false; }}
|
onClose={() => { appSettingsOpen = false; }}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if helpOpen}
|
||||||
|
<HelpOverlay language={appLanguage} onClose={() => { helpOpen = false; }} />
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if linePatchOpen && linePatchFile}
|
{#if linePatchOpen && linePatchFile}
|
||||||
<LinePatchDialog
|
<LinePatchDialog
|
||||||
file={linePatchFile}
|
file={linePatchFile}
|
||||||
|
|||||||
+203
-8
@@ -541,6 +541,7 @@
|
|||||||
|
|
||||||
.shell {
|
.shell {
|
||||||
--app-titlebar-height: 42px;
|
--app-titlebar-height: 42px;
|
||||||
|
--app-toolbar-height: 58px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -562,7 +563,7 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
z-index: 500;
|
z-index: 500;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto 1fr auto;
|
grid-template-columns: auto 1fr auto auto;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: var(--app-titlebar-height);
|
height: var(--app-titlebar-height);
|
||||||
background:
|
background:
|
||||||
@@ -639,8 +640,187 @@
|
|||||||
|
|
||||||
.tb-no-repo { color: var(--color-bar-muted); font-size: 12px; font-style: italic; }
|
.tb-no-repo { color: var(--color-bar-muted); font-size: 12px; font-style: italic; }
|
||||||
|
|
||||||
.titlebar-right { display: flex; align-items: stretch; height: 100%; border-left: 1px solid rgba(255,255,255,0.06); }
|
/* App-global actions (help, settings) that must stay reachable in every view,
|
||||||
.titlebar-actions { display: flex; align-items: stretch; }
|
including Repository Management where the repo toolbar is hidden. */
|
||||||
|
.titlebar-globals {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Repository actions are ordered by workflow: sync, inspect/history, utilities. */
|
||||||
|
.repo-toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
gap: 8px;
|
||||||
|
height: var(--app-toolbar-height);
|
||||||
|
padding: 5px 8px;
|
||||||
|
border: 1px solid var(--color-border-subtle);
|
||||||
|
border-radius: 10px;
|
||||||
|
background: rgba(16, 17, 29, 0.9);
|
||||||
|
flex-shrink: 0;
|
||||||
|
overflow: visible;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repo-toolbar-sync {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: 11px 34px;
|
||||||
|
gap: 2px;
|
||||||
|
align-self: stretch;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.repo-toolbar-group-label {
|
||||||
|
padding-left: 6px;
|
||||||
|
color: var(--color-ink-faint);
|
||||||
|
font-size: 8px;
|
||||||
|
font-weight: 850;
|
||||||
|
line-height: 11px;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.repo-action-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
height: 34px;
|
||||||
|
min-width: 0;
|
||||||
|
border: 1px solid var(--color-border-subtle);
|
||||||
|
border-radius: 7px;
|
||||||
|
background: rgba(255,255,255,0.018);
|
||||||
|
}
|
||||||
|
.repo-action-group > .repo-action,
|
||||||
|
.repo-action-group > .repo-history-wrap > .repo-action {
|
||||||
|
border-right: 1px solid var(--color-border-subtle);
|
||||||
|
}
|
||||||
|
.repo-action-group > .repo-action:last-child,
|
||||||
|
.repo-action-group > .repo-history-wrap:last-child > .repo-action {
|
||||||
|
border-right: 0;
|
||||||
|
}
|
||||||
|
.repo-action {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 6px;
|
||||||
|
min-width: 38px;
|
||||||
|
height: 32px;
|
||||||
|
min-height: 32px;
|
||||||
|
padding: 0 12px;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
color: var(--color-ink-muted);
|
||||||
|
background: transparent;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
white-space: nowrap;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 120ms ease, background 120ms ease;
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
}
|
||||||
|
.repo-action:first-child { border-radius: 6px 0 0 6px; }
|
||||||
|
.repo-action:last-child { border-radius: 0 6px 6px 0; }
|
||||||
|
.repo-action:only-child { border-radius: 6px; }
|
||||||
|
.repo-action:hover:not(:disabled),
|
||||||
|
.repo-action.active {
|
||||||
|
color: var(--color-ink);
|
||||||
|
background: var(--color-surface-hover);
|
||||||
|
}
|
||||||
|
.repo-action:disabled { opacity: 0.34; cursor: not-allowed; }
|
||||||
|
.repo-action.sync-primary { color: var(--color-accent); }
|
||||||
|
.repo-action.sync-primary:hover:not(:disabled) { background: rgba(90,140,248,0.1); }
|
||||||
|
.repo-action-label { font-size: 12px; font-weight: 750; }
|
||||||
|
.repo-action-count {
|
||||||
|
color: currentColor;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 9.5px;
|
||||||
|
font-weight: 850;
|
||||||
|
}
|
||||||
|
.repo-action-count.behind { color: #7aacff; }
|
||||||
|
.repo-action-count.ahead { color: #e0a040; }
|
||||||
|
.repo-toolbar-divider {
|
||||||
|
width: 1px;
|
||||||
|
height: 30px;
|
||||||
|
margin: 0 1px 2px;
|
||||||
|
background: var(--color-border-subtle);
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
.repo-toolbar-spacer { flex: 1 1 auto; min-width: 6px; }
|
||||||
|
.repo-inspect-actions,
|
||||||
|
.repo-utility-actions { margin-bottom: 1px; }
|
||||||
|
.repo-history-wrap { position: relative; height: 32px; }
|
||||||
|
.history-trigger { border-radius: 0 6px 6px 0; }
|
||||||
|
.history-trigger .history-chevron { transition: transform 120ms ease; }
|
||||||
|
.history-trigger.active .history-chevron { transform: rotate(180deg); }
|
||||||
|
.repo-history-menu {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 90;
|
||||||
|
top: calc(100% + 7px);
|
||||||
|
right: -1px;
|
||||||
|
display: grid;
|
||||||
|
width: 240px;
|
||||||
|
padding: 5px;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 9px;
|
||||||
|
background: var(--color-surface-raised);
|
||||||
|
box-shadow: 0 16px 42px rgba(0,0,0,0.36);
|
||||||
|
}
|
||||||
|
.repo-history-menu > button {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 24px minmax(0, 1fr);
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
min-height: 48px;
|
||||||
|
padding: 7px 8px;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 6px;
|
||||||
|
color: var(--color-ink-muted);
|
||||||
|
background: transparent;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.repo-history-menu > button:hover { color: var(--color-ink); background: var(--color-surface-hover); }
|
||||||
|
.repo-history-menu > button > svg { color: var(--color-accent); }
|
||||||
|
.repo-history-menu > button > span { display: grid; gap: 2px; min-width: 0; }
|
||||||
|
.repo-history-menu strong { color: inherit; font-size: 11px; }
|
||||||
|
.repo-history-menu small { overflow: hidden; color: var(--color-ink-faint); font-size: 9.5px; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
|
.repo-utility-actions { flex: 0 0 auto; }
|
||||||
|
.repo-action.icon-action { width: 36px; padding: 0; }
|
||||||
|
.repo-auto-toggle {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
height: 32px;
|
||||||
|
min-height: 32px;
|
||||||
|
padding: 0 8px 0 10px;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 0 6px 6px 0;
|
||||||
|
color: var(--color-ink-muted);
|
||||||
|
background: transparent;
|
||||||
|
font-size: 11.5px;
|
||||||
|
font-weight: 750;
|
||||||
|
}
|
||||||
|
.repo-auto-toggle:hover { color: var(--color-ink); background: var(--color-surface-hover); }
|
||||||
|
.repo-switch {
|
||||||
|
position: relative;
|
||||||
|
width: 27px;
|
||||||
|
height: 15px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: var(--color-border-input);
|
||||||
|
transition: background 140ms ease;
|
||||||
|
}
|
||||||
|
.repo-switch > span {
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
left: 2px;
|
||||||
|
width: 11px;
|
||||||
|
height: 11px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #ffffff;
|
||||||
|
box-shadow: 0 1px 3px rgba(0,0,0,0.28);
|
||||||
|
transition: transform 140ms ease;
|
||||||
|
}
|
||||||
|
.repo-auto-toggle.active { color: #4eca76; }
|
||||||
|
.repo-auto-toggle.active .repo-switch { background: #26924f; }
|
||||||
|
.repo-auto-toggle.active .repo-switch > span { transform: translateX(12px); }
|
||||||
|
.repo-switch.busy { box-shadow: 0 0 0 3px rgba(78,202,118,0.12); }
|
||||||
|
|
||||||
.tb-action {
|
.tb-action {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -655,6 +835,7 @@
|
|||||||
color: var(--color-bar-muted);
|
color: var(--color-bar-muted);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
flex-shrink: 0;
|
||||||
transition: background 120ms ease, color 120ms ease;
|
transition: background 120ms ease, color 120ms ease;
|
||||||
-webkit-app-region: no-drag;
|
-webkit-app-region: no-drag;
|
||||||
}
|
}
|
||||||
@@ -664,9 +845,9 @@
|
|||||||
.tb-action.auto-toggle.active:hover:not(:disabled) { color: #6ee090; background: rgba(78,202,118,0.1); }
|
.tb-action.auto-toggle.active:hover:not(:disabled) { color: #6ee090; background: rgba(78,202,118,0.1); }
|
||||||
.tb-action-label { font-size: 12px; font-weight: 600; }
|
.tb-action-label { font-size: 12px; font-weight: 600; }
|
||||||
|
|
||||||
.titlebar-divider { width: 1px; margin: 8px 0; background: rgba(255,255,255,0.08); flex-shrink: 0; }
|
.titlebar-divider { width: 1px; margin: 8px 6px; background: rgba(255,255,255,0.08); flex-shrink: 0; }
|
||||||
|
|
||||||
.titlebar-controls { display: flex; align-items: stretch; height: 100%; }
|
.titlebar-controls { display: flex; align-items: stretch; height: 100%; border-left: 1px solid rgba(255,255,255,0.06); }
|
||||||
|
|
||||||
.tb-btn {
|
.tb-btn {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -3083,6 +3264,10 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.settings-segmented.settings-language {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
.settings-toggle-row {
|
.settings-toggle-row {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto minmax(0, 1fr);
|
grid-template-columns: auto minmax(0, 1fr);
|
||||||
@@ -4716,10 +4901,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
:root[data-theme="light"] .titlebar-brand,
|
:root[data-theme="light"] .titlebar-brand,
|
||||||
:root[data-theme="light"] .titlebar-right {
|
:root[data-theme="light"] .titlebar-controls {
|
||||||
border-color: rgba(34,49,78,0.1);
|
border-color: rgba(34,49,78,0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:root[data-theme="light"] .repo-toolbar {
|
||||||
|
border-color: var(--color-border);
|
||||||
|
background: rgba(255,255,255,0.78);
|
||||||
|
}
|
||||||
|
|
||||||
:root[data-theme="light"] .titlebar-brand-icon img {
|
:root[data-theme="light"] .titlebar-brand-icon img {
|
||||||
filter: drop-shadow(0 1px 2px rgba(28,44,74,0.18));
|
filter: drop-shadow(0 1px 2px rgba(28,44,74,0.18));
|
||||||
}
|
}
|
||||||
@@ -5187,6 +5377,7 @@
|
|||||||
.branch-actions { flex-direction: column; align-items: flex-end; gap: 4px; }
|
.branch-actions { flex-direction: column; align-items: flex-end; gap: 4px; }
|
||||||
.branch-actions button { width: 100%; }
|
.branch-actions button { width: 100%; }
|
||||||
.tb-action-label { display: none; }
|
.tb-action-label { display: none; }
|
||||||
|
.repo-action-label.utility-label { display: none; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Narrow: single column */
|
/* Narrow: single column */
|
||||||
@@ -5194,8 +5385,7 @@
|
|||||||
body { overflow: auto; }
|
body { overflow: auto; }
|
||||||
.shell-body { min-height: 100%; gap: 6px; }
|
.shell-body { min-height: 100%; gap: 6px; }
|
||||||
.titlebar-info { padding: 0 8px; }
|
.titlebar-info { padding: 0 8px; }
|
||||||
.titlebar-actions .tb-action:not(.auto-toggle):not(:last-child) { display: none; }
|
.tb-action { padding: 0 9px; }
|
||||||
.tb-action { width: 36px; padding: 0 8px; }
|
|
||||||
.tb-btn { width: 34px; }
|
.tb-btn { width: 34px; }
|
||||||
.workspace { grid-template-columns: 1fr; gap: 6px; }
|
.workspace { grid-template-columns: 1fr; gap: 6px; }
|
||||||
.left-sidebar-resize-handle { display: none; }
|
.left-sidebar-resize-handle { display: none; }
|
||||||
@@ -5261,4 +5451,9 @@
|
|||||||
.dialog-files { border-right: none; border-bottom: 1px solid var(--color-border-subtle); }
|
.dialog-files { border-right: none; border-bottom: 1px solid var(--color-border-subtle); }
|
||||||
.branch-actions { flex-direction: row; justify-content: flex-start; }
|
.branch-actions { flex-direction: row; justify-content: flex-start; }
|
||||||
.tb-action-label { display: none; }
|
.tb-action-label { display: none; }
|
||||||
|
.repo-action-label { display: none; }
|
||||||
|
.repo-action { padding-inline: 8px; }
|
||||||
|
.repo-toolbar { gap: 5px; padding-inline: 6px; }
|
||||||
|
.repo-toolbar-divider { margin-inline: 0; }
|
||||||
|
.repo-auto-toggle { padding-left: 7px; gap: 6px; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,218 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {
|
||||||
|
ChevronDown,
|
||||||
|
CloudDownload,
|
||||||
|
Download,
|
||||||
|
FolderOpen,
|
||||||
|
GitCompare,
|
||||||
|
History,
|
||||||
|
ListRestart,
|
||||||
|
LoaderCircle,
|
||||||
|
RefreshCw,
|
||||||
|
Search,
|
||||||
|
Upload,
|
||||||
|
} from "@lucide/svelte";
|
||||||
|
|
||||||
|
export let hasRepository: boolean = false;
|
||||||
|
export let isBusy: boolean = false;
|
||||||
|
export let operation: string = "";
|
||||||
|
export let ahead: number = 0;
|
||||||
|
export let behind: number = 0;
|
||||||
|
export let autoRefreshEnabled: boolean = true;
|
||||||
|
export let autoRefreshInFlight: boolean = false;
|
||||||
|
export let language: "en" | "de" = "en";
|
||||||
|
export let onFetch: () => void = () => {};
|
||||||
|
export let onPull: () => void = () => {};
|
||||||
|
export let onPush: () => void = () => {};
|
||||||
|
export let onRefresh: () => void = () => {};
|
||||||
|
export let onSearch: () => void = () => {};
|
||||||
|
export let onCompare: () => void = () => {};
|
||||||
|
export let onInteractiveRebase: () => void = () => {};
|
||||||
|
export let onReflog: () => void = () => {};
|
||||||
|
export let onOpenInExplorer: () => void = () => {};
|
||||||
|
export let onToggleAutoRefresh: () => void = () => {};
|
||||||
|
|
||||||
|
let historyOpen = false;
|
||||||
|
let toolbarElement: HTMLDivElement;
|
||||||
|
|
||||||
|
$: isGerman = language === "de";
|
||||||
|
|
||||||
|
function runHistoryAction(action: () => void) {
|
||||||
|
historyOpen = false;
|
||||||
|
action();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleWindowClick(event: MouseEvent) {
|
||||||
|
if (historyOpen && toolbarElement && !toolbarElement.contains(event.target as Node)) {
|
||||||
|
historyOpen = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleWindowKeydown(event: KeyboardEvent) {
|
||||||
|
if (event.key === "Escape" && historyOpen) {
|
||||||
|
event.stopPropagation();
|
||||||
|
historyOpen = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:window onclick={handleWindowClick} onkeydown={handleWindowKeydown} />
|
||||||
|
|
||||||
|
<div bind:this={toolbarElement} class="repo-toolbar" role="toolbar" aria-label={isGerman ? "Repository-Aktionen" : "Repository actions"}>
|
||||||
|
<div class="repo-toolbar-sync">
|
||||||
|
<span class="repo-toolbar-group-label">Sync</span>
|
||||||
|
<div class="repo-action-group repo-sync-actions">
|
||||||
|
<button
|
||||||
|
class="repo-action fetch"
|
||||||
|
onclick={onFetch}
|
||||||
|
disabled={!hasRepository || isBusy}
|
||||||
|
title="Fetch"
|
||||||
|
aria-label="Fetch"
|
||||||
|
>
|
||||||
|
{#if operation === "Fetching"}
|
||||||
|
<LoaderCircle class="spin" size={15} aria-hidden="true" />
|
||||||
|
{:else}
|
||||||
|
<CloudDownload size={15} aria-hidden="true" />
|
||||||
|
{/if}
|
||||||
|
<span class="repo-action-label">Fetch</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="repo-action sync-primary"
|
||||||
|
onclick={onPull}
|
||||||
|
disabled={!hasRepository || isBusy}
|
||||||
|
title="Pull"
|
||||||
|
aria-label={behind > 0
|
||||||
|
? `Pull, ${behind} ${isGerman ? (behind === 1 ? "Remote-Commit voraus" : "Remote-Commits voraus") : (behind === 1 ? "commit behind" : "commits behind")}`
|
||||||
|
: "Pull"}
|
||||||
|
>
|
||||||
|
{#if operation === "Pulling"}
|
||||||
|
<LoaderCircle class="spin" size={15} aria-hidden="true" />
|
||||||
|
{:else}
|
||||||
|
<Download size={15} aria-hidden="true" />
|
||||||
|
{/if}
|
||||||
|
<span class="repo-action-label">Pull</span>
|
||||||
|
{#if behind > 0}<span class="repo-action-count behind">↓{behind}</span>{/if}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="repo-action sync-primary"
|
||||||
|
onclick={onPush}
|
||||||
|
disabled={!hasRepository || isBusy}
|
||||||
|
title="Push"
|
||||||
|
aria-label={ahead > 0
|
||||||
|
? `Push, ${ahead} ${isGerman ? (ahead === 1 ? "lokaler Commit voraus" : "lokale Commits voraus") : (ahead === 1 ? "commit ahead" : "commits ahead")}`
|
||||||
|
: "Push"}
|
||||||
|
>
|
||||||
|
{#if operation === "Pushing"}
|
||||||
|
<LoaderCircle class="spin" size={15} aria-hidden="true" />
|
||||||
|
{:else}
|
||||||
|
<Upload size={15} aria-hidden="true" />
|
||||||
|
{/if}
|
||||||
|
<span class="repo-action-label">Push</span>
|
||||||
|
{#if ahead > 0}<span class="repo-action-count ahead">↑{ahead}</span>{/if}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="repo-toolbar-divider" aria-hidden="true"></div>
|
||||||
|
|
||||||
|
<div class="repo-action-group repo-inspect-actions">
|
||||||
|
<button
|
||||||
|
class="repo-action"
|
||||||
|
onclick={onSearch}
|
||||||
|
disabled={!hasRepository || isBusy}
|
||||||
|
title={isGerman ? "Globale Suche" : "Global search"}
|
||||||
|
aria-label={isGerman ? "Globale Suche" : "Global search"}
|
||||||
|
>
|
||||||
|
<Search size={15} aria-hidden="true" />
|
||||||
|
<span class="repo-action-label">{isGerman ? "Suchen" : "Search"}</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="repo-action"
|
||||||
|
onclick={onCompare}
|
||||||
|
disabled={!hasRepository || isBusy}
|
||||||
|
title={isGerman ? "Commits vergleichen" : "Compare commits"}
|
||||||
|
aria-label={isGerman ? "Commits vergleichen" : "Compare commits"}
|
||||||
|
>
|
||||||
|
<GitCompare size={15} aria-hidden="true" />
|
||||||
|
<span class="repo-action-label">{isGerman ? "Vergleichen" : "Compare"}</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="repo-history-wrap">
|
||||||
|
<button
|
||||||
|
class="repo-action history-trigger"
|
||||||
|
class:active={historyOpen}
|
||||||
|
type="button"
|
||||||
|
onclick={() => { historyOpen = !historyOpen; }}
|
||||||
|
disabled={!hasRepository || isBusy}
|
||||||
|
aria-haspopup="menu"
|
||||||
|
aria-expanded={historyOpen}
|
||||||
|
title={isGerman ? "Verlauf und Rebase" : "History and rebase"}
|
||||||
|
>
|
||||||
|
<History size={15} aria-hidden="true" />
|
||||||
|
<span class="repo-action-label">{isGerman ? "Verlauf" : "History"}</span>
|
||||||
|
<ChevronDown class="history-chevron" size={13} aria-hidden="true" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{#if historyOpen}
|
||||||
|
<div class="repo-history-menu" role="menu">
|
||||||
|
<button type="button" role="menuitem" onclick={() => runHistoryAction(onReflog)}>
|
||||||
|
<History size={15} aria-hidden="true" />
|
||||||
|
<span>
|
||||||
|
<strong>Reflog</strong>
|
||||||
|
<small>{isGerman ? "Lokale Referenzbewegungen" : "Local reference movements"}</small>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<button type="button" role="menuitem" onclick={() => runHistoryAction(onInteractiveRebase)}>
|
||||||
|
<ListRestart size={15} aria-hidden="true" />
|
||||||
|
<span>
|
||||||
|
<strong>{isGerman ? "Interaktiver Rebase" : "Interactive rebase"}</strong>
|
||||||
|
<small>{isGerman ? "Commits ordnen und zusammenfassen" : "Reorder and combine commits"}</small>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="repo-toolbar-spacer"></div>
|
||||||
|
<div class="repo-toolbar-divider utility" aria-hidden="true"></div>
|
||||||
|
|
||||||
|
<div class="repo-action-group repo-utility-actions">
|
||||||
|
<button
|
||||||
|
class="repo-action"
|
||||||
|
onclick={onOpenInExplorer}
|
||||||
|
disabled={!hasRepository || isBusy}
|
||||||
|
title={isGerman ? "Repository im Explorer öffnen" : "Open repository in Explorer"}
|
||||||
|
aria-label={isGerman ? "Repository im Explorer öffnen" : "Open repository in Explorer"}
|
||||||
|
>
|
||||||
|
<FolderOpen size={15} aria-hidden="true" />
|
||||||
|
<span class="repo-action-label utility-label">Explorer</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="repo-action icon-action"
|
||||||
|
onclick={onRefresh}
|
||||||
|
disabled={isBusy || !hasRepository}
|
||||||
|
title={isGerman ? "Manuell aktualisieren" : "Refresh now"}
|
||||||
|
aria-label={isGerman ? "Manuell aktualisieren" : "Refresh now"}
|
||||||
|
>
|
||||||
|
<RefreshCw class={operation === "Refreshing" ? "spin" : ""} size={15} aria-hidden="true" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="repo-auto-toggle"
|
||||||
|
class:active={autoRefreshEnabled}
|
||||||
|
onclick={onToggleAutoRefresh}
|
||||||
|
aria-pressed={autoRefreshEnabled}
|
||||||
|
title={autoRefreshEnabled
|
||||||
|
? (isGerman ? "Auto-Aktualisierung ausschalten" : "Turn auto-refresh off")
|
||||||
|
: (isGerman ? "Auto-Aktualisierung einschalten" : "Turn auto-refresh on")}
|
||||||
|
>
|
||||||
|
<span>{isGerman ? "Auto" : "Auto"}</span>
|
||||||
|
<span class="repo-switch" class:busy={autoRefreshInFlight} aria-hidden="true"><span></span></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
+11
-151
@@ -2,7 +2,7 @@
|
|||||||
import { onDestroy, onMount } from "svelte";
|
import { onDestroy, onMount } from "svelte";
|
||||||
import { getVersion } from "@tauri-apps/api/app";
|
import { getVersion } from "@tauri-apps/api/app";
|
||||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||||
import { CloudDownload, Download, FolderOpen, GitBranch, GitCompare, History, ListRestart, LoaderCircle, Minus, RefreshCw, Search, Settings, Upload, X } from "@lucide/svelte";
|
import { CircleHelp, GitBranch, Minus, Settings, X } from "@lucide/svelte";
|
||||||
import iconUrl from "../../src-tauri/icons/icon.png";
|
import iconUrl from "../../src-tauri/icons/icon.png";
|
||||||
|
|
||||||
export let branch: string = "";
|
export let branch: string = "";
|
||||||
@@ -10,21 +10,9 @@
|
|||||||
export let behind: number = 0;
|
export let behind: number = 0;
|
||||||
export let repoName: string = "";
|
export let repoName: string = "";
|
||||||
export let hasRepository: boolean = false;
|
export let hasRepository: boolean = false;
|
||||||
export let isBusy: boolean = false;
|
export let onOpenHelp: () => void = () => {};
|
||||||
export let operation: string = "";
|
|
||||||
export let autoRefreshEnabled: boolean = true;
|
|
||||||
export let autoRefreshInFlight: boolean = false;
|
|
||||||
export let onFetch: () => void = () => {};
|
|
||||||
export let onPull: () => void = () => {};
|
|
||||||
export let onPush: () => void = () => {};
|
|
||||||
export let onRefresh: () => void = () => {};
|
|
||||||
export let onSearch: () => void = () => {};
|
|
||||||
export let onCompare: () => void = () => {};
|
|
||||||
export let onInteractiveRebase: () => void = () => {};
|
|
||||||
export let onReflog: () => void = () => {};
|
|
||||||
export let onOpenInExplorer: () => void = () => {};
|
|
||||||
export let onToggleAutoRefresh: () => void = () => {};
|
|
||||||
export let onOpenSettings: () => void = () => {};
|
export let onOpenSettings: () => void = () => {};
|
||||||
|
export let language: "en" | "de" = "en";
|
||||||
|
|
||||||
let win: ReturnType<typeof getCurrentWindow> | null = null;
|
let win: ReturnType<typeof getCurrentWindow> | null = null;
|
||||||
let isMaximized = false;
|
let isMaximized = false;
|
||||||
@@ -104,153 +92,26 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Right: actions + window controls -->
|
<!-- Right: app-global actions + window controls -->
|
||||||
<div class="titlebar-right">
|
<div class="titlebar-globals">
|
||||||
<div class="titlebar-actions" role="toolbar" aria-label="Repository actions">
|
|
||||||
<button
|
<button
|
||||||
class="tb-action"
|
class="tb-action"
|
||||||
onclick={onOpenInExplorer}
|
onclick={onOpenHelp}
|
||||||
disabled={!hasRepository || isBusy}
|
title={language === "de" ? "Hilfe (Ctrl+/)" : "Help (Ctrl+/)"}
|
||||||
title="Open repository in Explorer"
|
aria-label={language === "de" ? "Hilfe öffnen" : "Open help"}
|
||||||
aria-label="Open repository in Explorer"
|
|
||||||
>
|
>
|
||||||
<FolderOpen size={14} aria-hidden="true" />
|
<CircleHelp size={14} aria-hidden="true" />
|
||||||
<span class="tb-action-label">Explorer</span>
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
|
||||||
class="tb-action"
|
|
||||||
onclick={onSearch}
|
|
||||||
disabled={!hasRepository || isBusy}
|
|
||||||
title="Global search"
|
|
||||||
aria-label="Global search"
|
|
||||||
>
|
|
||||||
<Search size={14} aria-hidden="true" />
|
|
||||||
<span class="tb-action-label">Search</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
class="tb-action"
|
|
||||||
onclick={onCompare}
|
|
||||||
disabled={!hasRepository || isBusy}
|
|
||||||
title="Compare commits"
|
|
||||||
aria-label="Compare commits"
|
|
||||||
>
|
|
||||||
<GitCompare size={14} aria-hidden="true" />
|
|
||||||
<span class="tb-action-label">Compare</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
class="tb-action"
|
|
||||||
onclick={onInteractiveRebase}
|
|
||||||
disabled={!hasRepository || isBusy}
|
|
||||||
title="Interactive rebase"
|
|
||||||
aria-label="Interactive rebase"
|
|
||||||
>
|
|
||||||
<ListRestart size={14} aria-hidden="true" />
|
|
||||||
<span class="tb-action-label">Rebase</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
class="tb-action"
|
|
||||||
onclick={onReflog}
|
|
||||||
disabled={!hasRepository || isBusy}
|
|
||||||
title="Reflog"
|
|
||||||
aria-label="Reflog"
|
|
||||||
>
|
|
||||||
<History size={14} aria-hidden="true" />
|
|
||||||
<span class="tb-action-label">Reflog</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
class="tb-action"
|
|
||||||
onclick={onFetch}
|
|
||||||
disabled={!hasRepository || isBusy}
|
|
||||||
title="Fetch"
|
|
||||||
aria-label="Fetch"
|
|
||||||
>
|
|
||||||
{#if operation === "Fetching"}
|
|
||||||
<LoaderCircle class="spin" size={14} aria-hidden="true" />
|
|
||||||
{:else}
|
|
||||||
<CloudDownload size={14} aria-hidden="true" />
|
|
||||||
{/if}
|
|
||||||
<span class="tb-action-label">Fetch</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
class="tb-action"
|
|
||||||
onclick={onPull}
|
|
||||||
disabled={!hasRepository || isBusy}
|
|
||||||
title="Pull"
|
|
||||||
aria-label="Pull"
|
|
||||||
>
|
|
||||||
{#if operation === "Pulling"}
|
|
||||||
<LoaderCircle class="spin" size={14} aria-hidden="true" />
|
|
||||||
{:else}
|
|
||||||
<Download size={14} aria-hidden="true" />
|
|
||||||
{/if}
|
|
||||||
<span class="tb-action-label">Pull</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
class="tb-action"
|
|
||||||
onclick={onPush}
|
|
||||||
disabled={!hasRepository || isBusy}
|
|
||||||
title="Push"
|
|
||||||
aria-label="Push"
|
|
||||||
>
|
|
||||||
{#if operation === "Pushing"}
|
|
||||||
<LoaderCircle class="spin" size={14} aria-hidden="true" />
|
|
||||||
{:else}
|
|
||||||
<Upload size={14} aria-hidden="true" />
|
|
||||||
{/if}
|
|
||||||
<span class="tb-action-label">Push</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
class="tb-action"
|
|
||||||
onclick={onRefresh}
|
|
||||||
disabled={isBusy || !hasRepository}
|
|
||||||
title="Refresh"
|
|
||||||
aria-label="Refresh"
|
|
||||||
>
|
|
||||||
<RefreshCw
|
|
||||||
class={operation === "Refreshing" ? "spin" : ""}
|
|
||||||
size={14}
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
<span class="tb-action-label">Refresh</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
class="tb-action auto-toggle"
|
|
||||||
class:active={autoRefreshEnabled}
|
|
||||||
onclick={onToggleAutoRefresh}
|
|
||||||
aria-pressed={autoRefreshEnabled}
|
|
||||||
title={autoRefreshEnabled ? "Auto-refresh on — click to disable" : "Auto-refresh off — click to enable"}
|
|
||||||
aria-label="Toggle auto-refresh"
|
|
||||||
>
|
|
||||||
<RefreshCw
|
|
||||||
class={autoRefreshInFlight ? "spin" : ""}
|
|
||||||
size={14}
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
<span class="tb-action-label">Auto</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="tb-action"
|
class="tb-action"
|
||||||
onclick={onOpenSettings}
|
onclick={onOpenSettings}
|
||||||
title="Settings"
|
title={language === "de" ? "Einstellungen" : "Settings"}
|
||||||
aria-label="Settings"
|
aria-label={language === "de" ? "Einstellungen" : "Settings"}
|
||||||
>
|
>
|
||||||
<Settings size={14} aria-hidden="true" />
|
<Settings size={14} aria-hidden="true" />
|
||||||
<span class="tb-action-label">Settings</span>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="titlebar-divider" aria-hidden="true"></div>
|
|
||||||
|
|
||||||
<div class="titlebar-controls">
|
<div class="titlebar-controls">
|
||||||
<button class="tb-btn" onclick={minimizeWindow} title="Minimize" aria-label="Minimize">
|
<button class="tb-btn" onclick={minimizeWindow} title="Minimize" aria-label="Minimize">
|
||||||
<Minus size={12} aria-hidden="true" />
|
<Minus size={12} aria-hidden="true" />
|
||||||
@@ -276,5 +137,4 @@
|
|||||||
<X size={12} aria-hidden="true" />
|
<X size={12} aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@@ -1,22 +1,26 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Check, Settings, X } from "@lucide/svelte";
|
import { Check, Languages, Settings, X } from "@lucide/svelte";
|
||||||
import type { AnalyticsSettings, AppTheme } from "../types";
|
import type { AnalyticsSettings, AppLanguage, AppTheme } from "../types";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
analytics: AnalyticsSettings;
|
analytics: AnalyticsSettings;
|
||||||
theme: AppTheme;
|
theme: AppTheme;
|
||||||
onSave: (settings: AnalyticsSettings, theme: AppTheme) => void;
|
language: AppLanguage;
|
||||||
|
onSave: (settings: AnalyticsSettings, theme: AppTheme, language: AppLanguage) => void;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { analytics, theme = "system", onSave = () => {}, onClose = () => {} }: Props = $props();
|
let { analytics, theme = "system", language = "en", onSave = () => {}, onClose = () => {} }: Props = $props();
|
||||||
|
|
||||||
let analyticsEnabled = $state(true);
|
let analyticsEnabled = $state(true);
|
||||||
let selectedTheme = $state<AppTheme>("system");
|
let selectedTheme = $state<AppTheme>("system");
|
||||||
|
let selectedLanguage = $state<AppLanguage>("en");
|
||||||
|
const isGerman = $derived(selectedLanguage === "de");
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
analyticsEnabled = analytics.enabled;
|
analyticsEnabled = analytics.enabled;
|
||||||
selectedTheme = theme;
|
selectedTheme = theme;
|
||||||
|
selectedLanguage = language;
|
||||||
});
|
});
|
||||||
|
|
||||||
function save() {
|
function save() {
|
||||||
@@ -24,18 +28,18 @@
|
|||||||
...analytics,
|
...analytics,
|
||||||
enabled: analyticsEnabled,
|
enabled: analyticsEnabled,
|
||||||
noticeSeen: true,
|
noticeSeen: true,
|
||||||
}, selectedTheme);
|
}, selectedTheme, selectedLanguage);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="dialog-backdrop" role="presentation">
|
<div class="dialog-backdrop" role="presentation">
|
||||||
<div class="dialog app-settings-dialog" role="dialog" aria-modal="true" aria-label="Settings" tabindex="-1">
|
<div class="dialog app-settings-dialog" role="dialog" aria-modal="true" aria-label={isGerman ? "Einstellungen" : "Settings"} tabindex="-1">
|
||||||
<header class="dialog-header">
|
<header class="dialog-header">
|
||||||
<div>
|
<div>
|
||||||
<span class="eyebrow">Gitty</span>
|
<span class="eyebrow">Gitty</span>
|
||||||
<h2 class="dialog-title">Settings</h2>
|
<h2 class="dialog-title">{isGerman ? "Einstellungen" : "Settings"}</h2>
|
||||||
</div>
|
</div>
|
||||||
<button class="dialog-close" type="button" onclick={onClose} title="Close">
|
<button class="dialog-close" type="button" onclick={onClose} title={isGerman ? "Schließen" : "Close"}>
|
||||||
<X size={18} aria-hidden="true" />
|
<X size={18} aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
</header>
|
</header>
|
||||||
@@ -45,23 +49,44 @@
|
|||||||
<header>
|
<header>
|
||||||
<Settings size={16} aria-hidden="true" />
|
<Settings size={16} aria-hidden="true" />
|
||||||
<div>
|
<div>
|
||||||
<span class="eyebrow">Appearance</span>
|
<span class="eyebrow">{isGerman ? "Darstellung" : "Appearance"}</span>
|
||||||
<h3>Theme</h3>
|
<h3>{isGerman ? "Farbschema" : "Theme"}</h3>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="settings-segmented" role="radiogroup" aria-label="Theme">
|
<div class="settings-segmented" role="radiogroup" aria-label={isGerman ? "Farbschema" : "Theme"}>
|
||||||
<label class:active={selectedTheme === "system"}>
|
<label class:active={selectedTheme === "system"}>
|
||||||
<input type="radio" bind:group={selectedTheme} value="system" />
|
<input type="radio" bind:group={selectedTheme} value="system" />
|
||||||
<span>System</span>
|
<span>System</span>
|
||||||
</label>
|
</label>
|
||||||
<label class:active={selectedTheme === "light"}>
|
<label class:active={selectedTheme === "light"}>
|
||||||
<input type="radio" bind:group={selectedTheme} value="light" />
|
<input type="radio" bind:group={selectedTheme} value="light" />
|
||||||
<span>Light</span>
|
<span>{isGerman ? "Hell" : "Light"}</span>
|
||||||
</label>
|
</label>
|
||||||
<label class:active={selectedTheme === "dark"}>
|
<label class:active={selectedTheme === "dark"}>
|
||||||
<input type="radio" bind:group={selectedTheme} value="dark" />
|
<input type="radio" bind:group={selectedTheme} value="dark" />
|
||||||
<span>Dark</span>
|
<span>{isGerman ? "Dunkel" : "Dark"}</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="settings-section">
|
||||||
|
<header>
|
||||||
|
<Languages size={16} aria-hidden="true" />
|
||||||
|
<div>
|
||||||
|
<span class="eyebrow">{isGerman ? "Sprache" : "Language"}</span>
|
||||||
|
<h3>{isGerman ? "App-Sprache" : "App language"}</h3>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="settings-segmented settings-language" role="radiogroup" aria-label={isGerman ? "App-Sprache" : "App language"}>
|
||||||
|
<label class:active={selectedLanguage === "en"}>
|
||||||
|
<input type="radio" bind:group={selectedLanguage} value="en" />
|
||||||
|
<span>EN · English</span>
|
||||||
|
</label>
|
||||||
|
<label class:active={selectedLanguage === "de"}>
|
||||||
|
<input type="radio" bind:group={selectedLanguage} value="de" />
|
||||||
|
<span>DE · Deutsch</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -71,24 +96,24 @@
|
|||||||
<Settings size={16} aria-hidden="true" />
|
<Settings size={16} aria-hidden="true" />
|
||||||
<div>
|
<div>
|
||||||
<span class="eyebrow">Analytics</span>
|
<span class="eyebrow">Analytics</span>
|
||||||
<h3>Anonymous usage analytics</h3>
|
<h3>{isGerman ? "Anonyme Nutzungsanalyse" : "Anonymous usage analytics"}</h3>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<label class="settings-toggle-row">
|
<label class="settings-toggle-row">
|
||||||
<input type="checkbox" bind:checked={analyticsEnabled} />
|
<input type="checkbox" bind:checked={analyticsEnabled} />
|
||||||
<span>
|
<span>
|
||||||
<strong>Allow anonymous Aptabase events</strong>
|
<strong>{isGerman ? "Anonyme Aptabase-Ereignisse erlauben" : "Allow anonymous Aptabase events"}</strong>
|
||||||
<small>No repository paths, remotes, branches, commit messages, file names, diffs, credentials, or code are sent.</small>
|
<small>{isGerman ? "Es werden keine Repository-Pfade, Remotes, Branches, Commit-Nachrichten, Dateinamen, Diffs, Zugangsdaten oder Code übertragen." : "No repository paths, remotes, branches, commit messages, file names, diffs, credentials, or code are sent."}</small>
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div class="new-branch-actions">
|
<div class="new-branch-actions">
|
||||||
<button class="btn-secondary" type="button" onclick={onClose}>Cancel</button>
|
<button class="btn-secondary" type="button" onclick={onClose}>{isGerman ? "Abbrechen" : "Cancel"}</button>
|
||||||
<button class="btn-primary" type="submit">
|
<button class="btn-primary" type="submit">
|
||||||
<Check size={16} aria-hidden="true" />
|
<Check size={16} aria-hidden="true" />
|
||||||
Save
|
{isGerman ? "Speichern" : "Save"}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -11,6 +11,7 @@ export type CommitAiPhase = "idle" | "loading" | "ready" | "error";
|
|||||||
export type CommitAiProvider = "local" | "openai" | "anthropic" | "custom";
|
export type CommitAiProvider = "local" | "openai" | "anthropic" | "custom";
|
||||||
export type CommitAiLocalProfile = "fast" | "balanced" | "detailed";
|
export type CommitAiLocalProfile = "fast" | "balanced" | "detailed";
|
||||||
export type AppTheme = "system" | "light" | "dark";
|
export type AppTheme = "system" | "light" | "dark";
|
||||||
|
export type AppLanguage = "en" | "de";
|
||||||
|
|
||||||
export interface CommitAiStatus {
|
export interface CommitAiStatus {
|
||||||
phase: CommitAiPhase;
|
phase: CommitAiPhase;
|
||||||
|
|||||||
Reference in New Issue
Block a user