Introduce an appearance preference with modern, classic, and custom modes and a persisted custom theme palette. Add helpers to load, persist, and apply appearance and custom colors, and wire them into the app settings lifecycle. Implement a comprehensive light theme and appearance presets using CSS variables so custom palettes are applied consistently across the UI, and include appearance in analytics when settings are saved. - Persist and apply appearance and custom color palette to :root - Add muted light theme plus classic/modern presets and custom mapping - Include appearance and customTheme in settings save and analytics payload
676 lines
45 KiB
Svelte
676 lines
45 KiB
Svelte
<script lang="ts">
|
||
import { open } from "@tauri-apps/plugin-dialog";
|
||
import {
|
||
Check,
|
||
CheckCircle2,
|
||
ChevronDown,
|
||
ChevronRight,
|
||
CircleDashed,
|
||
Code2,
|
||
FolderOpen,
|
||
GitCompare,
|
||
GitMerge,
|
||
Languages,
|
||
Palette,
|
||
RefreshCw,
|
||
RotateCw,
|
||
Settings2,
|
||
ShieldCheck,
|
||
SlidersHorizontal,
|
||
Terminal,
|
||
Wrench,
|
||
X,
|
||
} from "@lucide/svelte";
|
||
import {
|
||
applyExternalToolPreset,
|
||
defaultExternalToolsSettings,
|
||
externalToolPresets,
|
||
isExternalToolPresetAvailable,
|
||
type ExternalToolKind,
|
||
type ExternalToolPreset,
|
||
} from "../externalTools";
|
||
import type {
|
||
AnalyticsSettings,
|
||
AppAppearance,
|
||
AppLanguage,
|
||
AppTheme,
|
||
CustomThemeColors,
|
||
DetectedExternalTool,
|
||
ExternalToolsSettings,
|
||
ToolOpenMode,
|
||
} from "../types";
|
||
import SelectMenu from "./SelectMenu.svelte";
|
||
|
||
type SettingsPage = "general" | "tools";
|
||
|
||
interface Props {
|
||
analytics: AnalyticsSettings;
|
||
theme: AppTheme;
|
||
appearance: AppAppearance;
|
||
customTheme: CustomThemeColors;
|
||
language: AppLanguage;
|
||
autoRefresh: boolean;
|
||
externalTools: ExternalToolsSettings;
|
||
detectedTools: DetectedExternalTool[];
|
||
detectionPending: boolean;
|
||
detectionUnavailable: boolean;
|
||
onRefreshDetectedTools: () => void | Promise<void>;
|
||
onSave: (settings: AnalyticsSettings, theme: AppTheme, appearance: AppAppearance, customTheme: CustomThemeColors, language: AppLanguage, autoRefresh: boolean, externalTools: ExternalToolsSettings) => void;
|
||
onClose: () => void;
|
||
}
|
||
|
||
let {
|
||
analytics,
|
||
theme = "system",
|
||
appearance = "modern",
|
||
customTheme = { background: "#cfd5dc", surface: "#e2e6ea", accent: "#2eb5d1", text: "#222328" },
|
||
language = "en",
|
||
autoRefresh = true,
|
||
externalTools,
|
||
detectedTools = [],
|
||
detectionPending = false,
|
||
detectionUnavailable = false,
|
||
onRefreshDetectedTools = () => {},
|
||
onSave = () => {},
|
||
onClose = () => {},
|
||
}: Props = $props();
|
||
|
||
const toolKinds: ExternalToolKind[] = ["editor", "diff", "merge", "terminal", "fileManager"];
|
||
|
||
let activePage = $state<SettingsPage>("tools");
|
||
let activeToolKind = $state<ExternalToolKind>("editor");
|
||
let advancedOpen = $state(false);
|
||
let analyticsEnabled = $state(true);
|
||
let selectedTheme = $state<AppTheme>("system");
|
||
let selectedAppearance = $state<AppAppearance>("modern");
|
||
let customColors = $state<CustomThemeColors>({ background: "#cfd5dc", surface: "#e2e6ea", accent: "#2eb5d1", text: "#222328" });
|
||
let selectedLanguage = $state<AppLanguage>("en");
|
||
let autoRefreshEnabled = $state(true);
|
||
let tools = $state<ExternalToolsSettings>(defaultExternalToolsSettings());
|
||
const isGerman = $derived(selectedLanguage === "de");
|
||
|
||
$effect(() => {
|
||
analyticsEnabled = analytics.enabled;
|
||
selectedTheme = theme;
|
||
selectedAppearance = appearance;
|
||
customColors = structuredClone(customTheme);
|
||
selectedLanguage = language;
|
||
autoRefreshEnabled = autoRefresh;
|
||
tools = structuredClone(externalTools);
|
||
});
|
||
|
||
function save() {
|
||
onSave({
|
||
...analytics,
|
||
enabled: analyticsEnabled,
|
||
noticeSeen: true,
|
||
}, selectedTheme, selectedAppearance, $state.snapshot(customColors), selectedLanguage, autoRefreshEnabled, $state.snapshot(tools));
|
||
}
|
||
|
||
function resetCustomColors() {
|
||
customColors = selectedTheme === "dark"
|
||
? { background: "#222328", surface: "#2b2e34", accent: "#2eb5d1", text: "#f0f1f2" }
|
||
: { background: "#cfd5dc", surface: "#e2e6ea", accent: "#2eb5d1", text: "#222328" };
|
||
}
|
||
|
||
function cssColorToHex(value: string, fallback: string): string {
|
||
const color = value.trim();
|
||
if (/^#[0-9a-f]{6}$/i.test(color)) return color.toLowerCase();
|
||
if (!color || typeof document === "undefined") return fallback;
|
||
const probe = document.createElement("span");
|
||
probe.style.color = color;
|
||
if (!probe.style.color) return fallback;
|
||
probe.style.display = "none";
|
||
document.body.appendChild(probe);
|
||
const match = getComputedStyle(probe).color.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)/i);
|
||
probe.remove();
|
||
if (!match) return fallback;
|
||
return `#${match.slice(1, 4).map((part) => Number(part).toString(16).padStart(2, "0")).join("")}`;
|
||
}
|
||
|
||
function currentThemeColors(): CustomThemeColors {
|
||
const styles = getComputedStyle(document.documentElement);
|
||
const fallback = selectedTheme === "dark"
|
||
? { background: "#222328", surface: "#2b2e34", accent: "#2eb5d1", text: "#f0f1f2" }
|
||
: { background: "#cfd5dc", surface: "#e2e6ea", accent: "#2eb5d1", text: "#222328" };
|
||
return {
|
||
background: cssColorToHex(styles.getPropertyValue("--app-bg"), fallback.background),
|
||
surface: cssColorToHex(styles.getPropertyValue("--color-surface"), fallback.surface),
|
||
accent: cssColorToHex(styles.getPropertyValue("--color-accent"), fallback.accent),
|
||
text: cssColorToHex(styles.getPropertyValue("--color-ink"), fallback.text),
|
||
};
|
||
}
|
||
|
||
function selectAppearance(next: AppAppearance) {
|
||
if (next === "custom" && selectedAppearance !== "custom") customColors = currentThemeColors();
|
||
selectedAppearance = next;
|
||
}
|
||
|
||
function toolLabel(kind: ExternalToolKind): string {
|
||
const labels = {
|
||
editor: "Editor",
|
||
diff: isGerman ? "Diff-Tool" : "Diff tool",
|
||
merge: isGerman ? "Merge-Tool" : "Merge tool",
|
||
terminal: "Terminal",
|
||
fileManager: isGerman ? "Dateimanager" : "File manager",
|
||
};
|
||
return labels[kind];
|
||
}
|
||
|
||
function toolDescription(kind: ExternalToolKind): string {
|
||
const descriptions = isGerman
|
||
? {
|
||
editor: "Öffnet Repositories und einzelne Dateien zum Bearbeiten.",
|
||
diff: "Vergleicht eine Arbeitsdatei mit ihrer Version aus HEAD.",
|
||
merge: "Übergibt Base, Current, Incoming und Ergebnis an einen 3-Wege-Merger.",
|
||
terminal: "Startet eine Shell direkt im Repository-Verzeichnis.",
|
||
fileManager: "Öffnet das Repository im bevorzugten Dateimanager.",
|
||
}
|
||
: {
|
||
editor: "Opens repositories and individual files for editing.",
|
||
diff: "Compares a working file with its version from HEAD.",
|
||
merge: "Passes base, current, incoming, and result to a three-way merger.",
|
||
terminal: "Starts a shell directly in the repository directory.",
|
||
fileManager: "Opens the repository in your preferred file manager.",
|
||
};
|
||
return descriptions[kind];
|
||
}
|
||
|
||
function toolUsage(kind: ExternalToolKind): string {
|
||
const usage = isGerman
|
||
? {
|
||
editor: "Oben in der Repository-Leiste oder über das Code-Symbol im Datei-Explorer.",
|
||
diff: "Datei im Explorer markieren und das Vergleichs-Symbol anklicken – alternativ Rechtsklick auf die Datei.",
|
||
merge: "Bei einem Konflikt „Konflikte lösen“ öffnen und anschließend dieses Merge-Tool starten.",
|
||
terminal: "Oben in der Repository-Leiste über den Terminal-Button.",
|
||
fileManager: "Oben in der Repository-Leiste über den Ordner-Button.",
|
||
}
|
||
: {
|
||
editor: "Use the repository toolbar or the code button in the file explorer.",
|
||
diff: "Select a file in Explorer and click the compare button, or right-click the file.",
|
||
merge: "Open Resolve conflicts and start this merge tool from the conflict view.",
|
||
terminal: "Use the terminal button in the repository toolbar.",
|
||
fileManager: "Use the folder button in the repository toolbar.",
|
||
};
|
||
return usage[kind];
|
||
}
|
||
|
||
function presetAvailable(kind: ExternalToolKind, preset: ExternalToolPreset): boolean {
|
||
return isExternalToolPresetAvailable(kind, preset, detectedTools);
|
||
}
|
||
|
||
function availablePresets(kind: ExternalToolKind): ExternalToolPreset[] {
|
||
return externalToolPresets[kind].filter((preset) => presetAvailable(kind, preset));
|
||
}
|
||
|
||
function otherPresets(kind: ExternalToolKind): ExternalToolPreset[] {
|
||
return externalToolPresets[kind].filter((preset) => !presetAvailable(kind, preset));
|
||
}
|
||
|
||
function selectedPreset(kind: ExternalToolKind): ExternalToolPreset | undefined {
|
||
return externalToolPresets[kind].find((preset) => preset.id === tools[kind].preset);
|
||
}
|
||
|
||
function selectedToolName(kind: ExternalToolKind): string {
|
||
return tools[kind].preset === "custom"
|
||
? tools[kind].program.split(/[\\/]/).pop() || (isGerman ? "Eigenes Programm" : "Custom application")
|
||
: selectedPreset(kind)?.label ?? tools[kind].program;
|
||
}
|
||
|
||
function openMode(kind: "diff" | "merge"): ToolOpenMode {
|
||
return kind === "diff" ? tools.diffOpenMode : tools.mergeOpenMode;
|
||
}
|
||
|
||
function setOpenMode(kind: "diff" | "merge", mode: ToolOpenMode) {
|
||
if (kind === "diff") tools.diffOpenMode = mode;
|
||
else tools.mergeOpenMode = mode;
|
||
}
|
||
|
||
function selectionAvailable(kind: ExternalToolKind): boolean {
|
||
if (tools[kind].preset === "custom") return tools[kind].program.trim().length > 0;
|
||
const preset = selectedPreset(kind);
|
||
return preset ? presetAvailable(kind, preset) : false;
|
||
}
|
||
|
||
function selectionStatus(kind: ExternalToolKind): string {
|
||
if (tools[kind].preset === "custom") {
|
||
return tools[kind].program.trim()
|
||
? (isGerman ? "Manuell konfiguriert" : "Manually configured")
|
||
: (isGerman ? "Programmpfad fehlt" : "Application path missing");
|
||
}
|
||
return selectionAvailable(kind)
|
||
? (isGerman ? "Installiert und verfügbar" : "Installed and available")
|
||
: (isGerman ? "Nicht automatisch erkannt" : "Not automatically detected");
|
||
}
|
||
|
||
function changePreset(kind: ExternalToolKind, id: string) {
|
||
if (id === "custom") {
|
||
tools[kind] = { ...tools[kind], preset: "custom" };
|
||
advancedOpen = true;
|
||
return;
|
||
}
|
||
tools[kind] = applyExternalToolPreset(kind, id, detectedTools);
|
||
}
|
||
|
||
function selectToolKind(kind: ExternalToolKind) {
|
||
activeToolKind = kind;
|
||
advancedOpen = tools[kind].preset === "custom";
|
||
}
|
||
|
||
function updateProgram(kind: ExternalToolKind, program: string) {
|
||
tools[kind] = { ...tools[kind], preset: "custom", program };
|
||
}
|
||
|
||
function updateArgs(kind: ExternalToolKind, value: string) {
|
||
tools[kind] = {
|
||
...tools[kind],
|
||
preset: "custom",
|
||
args: value.split("\n").map((arg) => arg.trim()).filter(Boolean),
|
||
};
|
||
}
|
||
|
||
async function browseProgram(kind: ExternalToolKind) {
|
||
const selected = await open({
|
||
title: isGerman ? `${toolLabel(kind)} auswählen` : `Choose ${toolLabel(kind)}`,
|
||
multiple: false,
|
||
directory: false,
|
||
});
|
||
if (typeof selected === "string") {
|
||
updateProgram(kind, selected);
|
||
advancedOpen = true;
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<div class="dialog-backdrop" role="presentation">
|
||
<div class="dialog app-settings-dialog" role="dialog" aria-modal="true" aria-label={isGerman ? "Einstellungen" : "Settings"} tabindex="-1">
|
||
<header class="app-settings-head">
|
||
<div class="app-settings-title">
|
||
<span class="app-settings-mark"><Settings2 size={18} aria-hidden="true" /></span>
|
||
<div>
|
||
<h2>{isGerman ? "Einstellungen" : "Settings"}</h2>
|
||
<p>{isGerman ? "Gitty an deinen Workflow anpassen" : "Make Gitty fit your workflow"}</p>
|
||
</div>
|
||
</div>
|
||
<button class="dialog-close" type="button" onclick={onClose} title={isGerman ? "Schließen" : "Close"} aria-label={isGerman ? "Einstellungen schließen" : "Close settings"}>
|
||
<X size={18} aria-hidden="true" />
|
||
</button>
|
||
</header>
|
||
|
||
<form class="app-settings-shell" onsubmit={(event) => { event.preventDefault(); save(); }}>
|
||
<div class="app-settings-body">
|
||
<nav class="settings-nav" aria-label={isGerman ? "Einstellungsbereiche" : "Settings sections"}>
|
||
<button type="button" class:active={activePage === "general"} onclick={() => { activePage = "general"; }}>
|
||
<SlidersHorizontal size={16} aria-hidden="true" />
|
||
<span>
|
||
<strong>{isGerman ? "Allgemein" : "General"}</strong>
|
||
<small>{isGerman ? "Darstellung & Verhalten" : "Appearance & behavior"}</small>
|
||
</span>
|
||
</button>
|
||
<button type="button" class:active={activePage === "tools"} onclick={() => { activePage = "tools"; }}>
|
||
<Wrench size={16} aria-hidden="true" />
|
||
<span>
|
||
<strong>{isGerman ? "Externe Tools" : "External tools"}</strong>
|
||
<small>{isGerman ? "Editor, Diff & Terminal" : "Editor, diff & terminal"}</small>
|
||
</span>
|
||
{#if !detectionUnavailable}<em>{detectedTools.length}</em>{/if}
|
||
</button>
|
||
|
||
<div class="settings-nav-note">
|
||
<ShieldCheck size={15} aria-hidden="true" />
|
||
<p>{isGerman ? "Tool-Argumente werden direkt und ohne zusätzliche Shell übergeben." : "Tool arguments are passed directly without an extra shell."}</p>
|
||
</div>
|
||
</nav>
|
||
|
||
<div class="settings-content">
|
||
{#if activePage === "general"}
|
||
<div class="settings-page-head">
|
||
<div>
|
||
<h3>{isGerman ? "Allgemein" : "General"}</h3>
|
||
<p>{isGerman ? "Darstellung, Sprache und Hintergrundverhalten." : "Appearance, language, and background behavior."}</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="general-settings-grid">
|
||
<section class="general-setting-panel">
|
||
<header><Palette size={16} /><div><h4>{isGerman ? "Farbschema" : "Theme"}</h4><p>{isGerman ? "Passend zu deiner Umgebung." : "Match your environment."}</p></div></header>
|
||
<div class="settings-segmented" role="radiogroup" aria-label={isGerman ? "Farbschema" : "Theme"}>
|
||
<label class:active={selectedTheme === "system"}><input type="radio" bind:group={selectedTheme} value="system" /><span>System</span></label>
|
||
<label class:active={selectedTheme === "light"}><input type="radio" bind:group={selectedTheme} value="light" /><span>{isGerman ? "Hell" : "Light"}</span></label>
|
||
<label class:active={selectedTheme === "dark"}><input type="radio" bind:group={selectedTheme} value="dark" /><span>{isGerman ? "Dunkel" : "Dark"}</span></label>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="general-setting-panel">
|
||
<header><SlidersHorizontal size={16} /><div><h4>{isGerman ? "Darstellungsstil" : "Design style"}</h4><p>{isGerman ? "Aktuell, klassisch oder selbst gestaltet." : "Current, classic, or designed by you."}</p></div></header>
|
||
<div class="settings-segmented" role="radiogroup" aria-label={isGerman ? "Darstellungsstil" : "Design style"}>
|
||
<label class:active={selectedAppearance === "modern"}><input type="radio" name="appearance" value="modern" checked={selectedAppearance === "modern"} onchange={() => selectAppearance("modern")} /><span>{isGerman ? "Aktuell" : "Modern"}</span></label>
|
||
<label class:active={selectedAppearance === "classic"}><input type="radio" name="appearance" value="classic" checked={selectedAppearance === "classic"} onchange={() => selectAppearance("classic")} /><span>{isGerman ? "Klassisch" : "Classic"}</span></label>
|
||
<label class:active={selectedAppearance === "custom"}><input type="radio" name="appearance" value="custom" checked={selectedAppearance === "custom"} onchange={() => selectAppearance("custom")} /><span>{isGerman ? "Eigene" : "Custom"}</span></label>
|
||
</div>
|
||
</section>
|
||
|
||
{#if selectedAppearance === "custom"}
|
||
<section class="general-setting-panel general-setting-wide custom-theme-panel">
|
||
<header>
|
||
<Palette size={16} />
|
||
<div><h4>{isGerman ? "Theme-Generator" : "Theme generator"}</h4><p>{isGerman ? "Erstelle dein eigenes Farbprofil." : "Create your own color profile."}</p></div>
|
||
<button class="theme-reset-button" type="button" onclick={resetCustomColors}><RotateCw size={13} />{isGerman ? "Zurücksetzen" : "Reset"}</button>
|
||
</header>
|
||
<div
|
||
class="theme-preview"
|
||
style={`--preview-bg:${customColors.background};--preview-surface:${customColors.surface};--preview-accent:${customColors.accent};--preview-text:${customColors.text};`}
|
||
aria-label={isGerman ? "Vorschau des eigenen Themes" : "Custom theme preview"}
|
||
>
|
||
<span class="theme-preview-sidebar"></span>
|
||
<span class="theme-preview-content"><i></i><b></b><em></em></span>
|
||
</div>
|
||
<div class="theme-color-grid">
|
||
<label><span>{isGerman ? "Hintergrund" : "Background"}</span><input type="color" bind:value={customColors.background} aria-label={isGerman ? "Hintergrundfarbe" : "Background color"} /><code>{customColors.background}</code></label>
|
||
<label><span>{isGerman ? "Fläche" : "Surface"}</span><input type="color" bind:value={customColors.surface} aria-label={isGerman ? "Flächenfarbe" : "Surface color"} /><code>{customColors.surface}</code></label>
|
||
<label><span>{isGerman ? "Akzent" : "Accent"}</span><input type="color" bind:value={customColors.accent} aria-label={isGerman ? "Akzentfarbe" : "Accent color"} /><code>{customColors.accent}</code></label>
|
||
<label><span>{isGerman ? "Schrift" : "Text"}</span><input type="color" bind:value={customColors.text} aria-label={isGerman ? "Schriftfarbe" : "Text color"} /><code>{customColors.text}</code></label>
|
||
</div>
|
||
<p class="theme-generator-note">{isGerman ? "Die Farben werden beim Speichern auf die gesamte Oberfläche angewendet." : "The colors are applied across the interface when you save."}</p>
|
||
</section>
|
||
{/if}
|
||
|
||
<section class="general-setting-panel">
|
||
<header><Languages size={16} /><div><h4>{isGerman ? "Sprache" : "Language"}</h4><p>{isGerman ? "Sprache der Oberfläche." : "Language used by the interface."}</p></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>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="general-setting-panel general-setting-wide">
|
||
<header><RefreshCw size={16} /><div><h4>{isGerman ? "Repository-Aktualisierung" : "Repository refresh"}</h4><p>{isGerman ? "Arbeitsbereich und Remotes aktuell halten." : "Keep the working tree and remotes current."}</p></div></header>
|
||
<label class="settings-switch-row">
|
||
<span><strong>{isGerman ? "Automatisch aktualisieren" : "Refresh automatically"}</strong><small>{isGerman ? "Branch-Status und Änderungen regelmäßig im Hintergrund prüfen." : "Periodically check branch state and working-tree changes."}</small></span>
|
||
<input type="checkbox" bind:checked={autoRefreshEnabled} />
|
||
</label>
|
||
</section>
|
||
|
||
<section class="general-setting-panel general-setting-wide">
|
||
<header><ShieldCheck size={16} /><div><h4>{isGerman ? "Datenschutz" : "Privacy"}</h4><p>{isGerman ? "Anonyme Produkt- und Fehlerdiagnose." : "Anonymous product and error diagnostics."}</p></div></header>
|
||
<label class="settings-switch-row">
|
||
<span><strong>{isGerman ? "Anonyme Analytics erlauben" : "Allow anonymous analytics"}</strong><small>{isGerman ? "Keine Pfade, Remotes, Branches, Diffs, Zugangsdaten oder Quelltexte." : "No paths, remotes, branches, diffs, credentials, or source code."}</small></span>
|
||
<input type="checkbox" bind:checked={analyticsEnabled} />
|
||
</label>
|
||
</section>
|
||
</div>
|
||
{:else}
|
||
<div class="settings-page-head tools-page-head">
|
||
<div>
|
||
<h3>{isGerman ? "Externe Tools" : "External tools"}</h3>
|
||
<p>
|
||
{detectionUnavailable
|
||
? (isGerman ? "Automatische Erkennung ist in dieser Umgebung nicht verfügbar." : "Automatic detection is unavailable in this environment.")
|
||
: (isGerman ? `${detectedTools.length} installierte Programme erkannt.` : `${detectedTools.length} installed applications detected.`)}
|
||
</p>
|
||
</div>
|
||
<button class="tool-rescan-button" type="button" onclick={onRefreshDetectedTools} disabled={detectionPending}>
|
||
<RotateCw class={detectionPending ? "spin" : ""} size={14} aria-hidden="true" />
|
||
{detectionPending ? (isGerman ? "Erkennung läuft…" : "Detecting…") : (isGerman ? "Neu erkennen" : "Detect again")}
|
||
</button>
|
||
</div>
|
||
|
||
<div class="tool-kind-tabs" role="tablist" aria-label={isGerman ? "Tool-Kategorie" : "Tool category"}>
|
||
{#each toolKinds as kind}
|
||
<button type="button" role="tab" aria-selected={activeToolKind === kind} class:active={activeToolKind === kind} onclick={() => selectToolKind(kind)}>
|
||
{#if kind === "editor"}<Code2 size={16} />
|
||
{:else if kind === "diff"}<GitCompare size={16} />
|
||
{:else if kind === "merge"}<GitMerge size={16} />
|
||
{:else if kind === "terminal"}<Terminal size={16} />
|
||
{:else}<FolderOpen size={16} />{/if}
|
||
<span>{toolLabel(kind)}</span>
|
||
<small class:available={selectionAvailable(kind)}></small>
|
||
</button>
|
||
{/each}
|
||
</div>
|
||
|
||
<section class="tool-config-panel" aria-label={`${toolLabel(activeToolKind)} ${isGerman ? "konfigurieren" : "configuration"}`}>
|
||
<div class="tool-config-summary">
|
||
<span class="tool-config-icon">
|
||
{#if activeToolKind === "editor"}<Code2 size={22} />
|
||
{:else if activeToolKind === "diff"}<GitCompare size={22} />
|
||
{:else if activeToolKind === "merge"}<GitMerge size={22} />
|
||
{:else if activeToolKind === "terminal"}<Terminal size={22} />
|
||
{:else}<FolderOpen size={22} />{/if}
|
||
</span>
|
||
<div>
|
||
<h4>{toolLabel(activeToolKind)}</h4>
|
||
<p>{toolDescription(activeToolKind)}</p>
|
||
</div>
|
||
<span class="tool-status" class:available={selectionAvailable(activeToolKind)}>
|
||
{#if selectionAvailable(activeToolKind)}<CheckCircle2 size={13} />{:else}<CircleDashed size={13} />{/if}
|
||
{selectionStatus(activeToolKind)}
|
||
</span>
|
||
</div>
|
||
|
||
{#if activeToolKind === "diff" || activeToolKind === "merge"}
|
||
{@const openModeKind = activeToolKind as "diff" | "merge"}
|
||
<div class="tool-route" aria-label={isGerman ? "Aktuelle Standardansicht" : "Current default view"}>
|
||
<span>{isGerman ? "Standard" : "Default"}</span><ChevronRight size={15} aria-hidden="true" />
|
||
<strong>{openMode(openModeKind) === "gitty" ? (isGerman ? "Gitty · integriert" : "Gitty · built in") : selectedToolName(activeToolKind)}</strong>
|
||
</div>
|
||
|
||
<fieldset class="tool-open-mode">
|
||
<legend>{isGerman ? "Beim Öffnen verwenden" : "Use when opening"}</legend>
|
||
<div>
|
||
<button type="button" class:active={openMode(openModeKind) === "gitty"} aria-pressed={openMode(openModeKind) === "gitty"} onclick={() => setOpenMode(openModeKind, "gitty")}>
|
||
<span>Gitty</span><small>{isGerman ? "Integrierte Ansicht" : "Built-in view"}</small>
|
||
</button>
|
||
<button type="button" class:active={openMode(openModeKind) === "external"} aria-pressed={openMode(openModeKind) === "external"} onclick={() => setOpenMode(openModeKind, "external")}>
|
||
<span>{selectedToolName(activeToolKind)}</span><small>{isGerman ? "Externes Programm" : "External application"}</small>
|
||
</button>
|
||
</div>
|
||
</fieldset>
|
||
{:else}
|
||
<div class="tool-route" aria-label={isGerman ? "Aktuelle Standardzuordnung" : "Current default mapping"}>
|
||
<span>Gitty</span><ChevronRight size={15} aria-hidden="true" /><strong>{selectedToolName(activeToolKind)}</strong>
|
||
</div>
|
||
{/if}
|
||
|
||
<label class="tool-default-field">
|
||
<span>{isGerman ? `Standardprogramm für ${toolLabel(activeToolKind)}` : `Default application for ${toolLabel(activeToolKind)}`}</span>
|
||
<SelectMenu
|
||
class="tool-preset-select"
|
||
value={tools[activeToolKind].preset}
|
||
options={[
|
||
...availablePresets(activeToolKind).map((preset) => ({ value: preset.id, label: `Installed - ${preset.label}`, group: isGerman ? "Installiert" : "Installed" })),
|
||
...otherPresets(activeToolKind).map((preset) => ({ value: preset.id, label: preset.label, group: isGerman ? "Weitere unterstützte Programme" : "Other supported applications" })),
|
||
{ value: "custom", label: isGerman ? "Eigenes Programm auswählen..." : "Choose a custom application..." },
|
||
]}
|
||
ariaLabel={isGerman ? `Standardprogramm für ${toolLabel(activeToolKind)}` : `Default application for ${toolLabel(activeToolKind)}`}
|
||
onChange={(value) => changePreset(activeToolKind, value)}
|
||
/>
|
||
<small>{isGerman ? "Diese Auswahl wird gespeichert und für alle passenden Aktionen verwendet." : "This selection is saved and used for every matching action."}</small>
|
||
</label>
|
||
|
||
<div class="tool-usage-callout">
|
||
<span>{isGerman ? "So öffnest du es" : "How to open it"}</span>
|
||
<p>{toolUsage(activeToolKind)}</p>
|
||
</div>
|
||
|
||
<button class="tool-advanced-toggle" type="button" aria-expanded={advancedOpen} onclick={() => { advancedOpen = !advancedOpen; }}>
|
||
<span>{isGerman ? "Programmpfad und Argumente" : "Application path and arguments"}</span>
|
||
{#if advancedOpen}<ChevronDown size={15} />{:else}<ChevronRight size={15} />{/if}
|
||
</button>
|
||
|
||
{#if advancedOpen}
|
||
<div class="tool-advanced-panel">
|
||
<label>
|
||
<span>{isGerman ? "Programmpfad" : "Application path"}</span>
|
||
<div class="tool-program-row">
|
||
<input value={tools[activeToolKind].program} oninput={(event) => updateProgram(activeToolKind, event.currentTarget.value)} spellcheck="false" />
|
||
<button type="button" onclick={() => browseProgram(activeToolKind)} title={isGerman ? "Programm auswählen" : "Choose application"} aria-label={isGerman ? "Programm auswählen" : "Choose application"}><FolderOpen size={15} /></button>
|
||
</div>
|
||
</label>
|
||
<label>
|
||
<span>{isGerman ? "Argumente · eine Zeile pro Argument" : "Arguments · one per line"}</span>
|
||
<textarea value={tools[activeToolKind].args.join("\n")} oninput={(event) => updateArgs(activeToolKind, event.currentTarget.value)} spellcheck="false"></textarea>
|
||
</label>
|
||
<p class="tool-placeholders">
|
||
<span>{isGerman ? "Verfügbare Platzhalter" : "Available placeholders"}</span>
|
||
<code>{"{repo}"}</code><code>{"{file}"}</code><code>{"{parent}"}</code><code>{"{left}"}</code><code>{"{right}"}</code><code>{"{base}"}</code><code>{"{ours}"}</code><code>{"{theirs}"}</code><code>{"{result}"}</code>
|
||
</p>
|
||
</div>
|
||
{/if}
|
||
</section>
|
||
{/if}
|
||
</div>
|
||
</div>
|
||
|
||
<footer class="app-settings-footer">
|
||
<span>{isGerman ? "Änderungen werden erst beim Speichern übernommen." : "Changes are applied only after saving."}</span>
|
||
<div>
|
||
<button class="btn-secondary" type="button" onclick={onClose}>{isGerman ? "Abbrechen" : "Cancel"}</button>
|
||
<button class="btn-primary" type="submit"><Check size={16} aria-hidden="true" />{isGerman ? "Änderungen speichern" : "Save changes"}</button>
|
||
</div>
|
||
</footer>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<style>
|
||
.app-settings-dialog { display: grid; grid-template-rows: auto minmax(0, 1fr); width: min(920px, calc(100vw - 32px)); height: min(720px, calc(100vh - 32px)); overflow: hidden; }
|
||
.app-settings-head { display: flex; align-items: center; justify-content: space-between; min-height: 70px; padding: 14px 18px; border-bottom: 1px solid var(--color-border-subtle); background: var(--app-dialog-chrome); }
|
||
.app-settings-title { display: flex; align-items: center; gap: 12px; min-width: 0; }
|
||
.app-settings-mark { display: grid; place-items: center; width: 38px; height: 38px; border: 1px solid color-mix(in srgb, var(--color-accent) 28%, var(--color-border)); border-radius: 10px; color: var(--color-accent); background: color-mix(in srgb, var(--color-accent) 9%, transparent); }
|
||
.app-settings-title h2, .settings-page-head h3, .tool-config-summary h4, .general-setting-panel h4 { margin: 0; color: var(--color-ink); }
|
||
.app-settings-title h2 { font-size: 18px; line-height: 1.2; }
|
||
.app-settings-title p, .settings-page-head p, .tool-config-summary p, .general-setting-panel p { margin: 0; color: var(--color-ink-dim); }
|
||
.app-settings-title p { margin-top: 3px; font-size: 11px; }
|
||
.app-settings-shell { display: grid; min-height: 0; grid-template-rows: minmax(0, 1fr) auto; }
|
||
.app-settings-body { display: grid; min-height: 0; grid-template-columns: 205px minmax(0, 1fr); }
|
||
.settings-nav { display: flex; flex-direction: column; gap: 6px; min-width: 0; padding: 14px 12px; border-right: 1px solid var(--color-border-subtle); background: color-mix(in srgb, var(--app-dialog-chrome) 72%, var(--app-dialog-bg)); }
|
||
.settings-nav > button { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: 10px; width: 100%; min-height: 50px; padding: 8px 10px; border: 1px solid transparent; border-radius: 8px; color: var(--color-ink-dim); background: transparent; text-align: left; }
|
||
.settings-nav > button:hover { color: var(--color-ink); background: var(--color-surface-hover); }
|
||
.settings-nav > button.active { border-color: color-mix(in srgb, var(--color-accent) 28%, var(--color-border)); color: var(--color-ink); background: color-mix(in srgb, var(--color-accent) 9%, var(--color-surface-raised)); box-shadow: inset 2px 0 0 var(--color-accent); }
|
||
.settings-nav button > :global(svg) { color: var(--color-ink-muted); }
|
||
.settings-nav button.active > :global(svg) { color: var(--color-accent); }
|
||
.settings-nav button span { display: grid; min-width: 0; gap: 2px; }
|
||
.settings-nav button strong { font-size: 12px; }
|
||
.settings-nav button small { overflow: hidden; color: var(--color-ink-faint); font-size: 9.5px; text-overflow: ellipsis; white-space: nowrap; }
|
||
.settings-nav button em { display: grid; place-items: center; min-width: 21px; height: 20px; padding-inline: 5px; border-radius: 10px; color: var(--color-accent); background: color-mix(in srgb, var(--color-accent) 12%, transparent); font-size: 9px; font-style: normal; font-weight: 800; }
|
||
.settings-nav-note { display: flex; align-items: flex-start; gap: 8px; margin-top: auto; padding: 10px; border-top: 1px solid var(--color-border-subtle); color: var(--color-ink-faint); }
|
||
.settings-nav-note :global(svg) { flex: 0 0 auto; margin-top: 1px; color: var(--color-success); }
|
||
.settings-nav-note p { margin: 0; font-size: 9.5px; line-height: 1.45; }
|
||
.settings-content { min-width: 0; overflow: auto; padding: 18px 20px 22px; }
|
||
.settings-page-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; margin-bottom: 16px; }
|
||
.settings-page-head h3 { font-size: 18px; }
|
||
.settings-page-head p { margin-top: 4px; font-size: 11px; line-height: 1.45; }
|
||
.tool-rescan-button { display: inline-flex; align-items: center; gap: 7px; flex: 0 0 auto; min-height: 30px; padding: 0 10px; border: 1px solid var(--color-border); border-radius: 7px; color: var(--color-ink-muted); background: var(--color-surface-raised); font-size: 10px; font-weight: 750; }
|
||
.tool-rescan-button:hover:not(:disabled) { color: var(--color-ink); border-color: var(--color-border-input); background: var(--color-surface-hover); }
|
||
.tool-kind-tabs { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 6px; margin-bottom: 14px; padding: 4px; border: 1px solid var(--color-border-subtle); border-radius: 10px; background: var(--app-settings-row-bg); }
|
||
.tool-kind-tabs button { position: relative; display: flex; align-items: center; justify-content: center; gap: 7px; min-width: 0; height: 38px; padding: 0 8px; border: 1px solid transparent; border-radius: 7px; color: var(--color-ink-dim); background: transparent; font-size: 10.5px; font-weight: 750; }
|
||
.tool-kind-tabs button:hover { color: var(--color-ink); background: var(--color-surface-hover); }
|
||
.tool-kind-tabs button.active { border-color: var(--color-border-input); color: var(--color-ink); background: var(--color-surface-raised); box-shadow: 0 2px 8px rgba(0,0,0,.12); }
|
||
.tool-kind-tabs button.active :global(svg) { color: var(--color-accent); }
|
||
.tool-kind-tabs button small { position: absolute; top: 5px; right: 6px; width: 5px; height: 5px; border-radius: 50%; background: var(--color-ink-faint); }
|
||
.tool-kind-tabs button small.available { background: var(--color-success); box-shadow: 0 0 0 2px color-mix(in srgb, var(--color-success) 15%, transparent); }
|
||
.tool-config-panel { display: grid; gap: 14px; padding: 16px; border: 1px solid var(--color-border-subtle); border-radius: 12px; background: color-mix(in srgb, var(--app-settings-row-bg) 72%, transparent); }
|
||
.tool-config-summary { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: 11px; }
|
||
.tool-config-icon { display: grid; place-items: center; width: 42px; height: 42px; border: 1px solid color-mix(in srgb, var(--color-accent) 25%, var(--color-border)); border-radius: 10px; color: var(--color-accent); background: color-mix(in srgb, var(--color-accent) 8%, transparent); }
|
||
.tool-config-summary h4 { font-size: 14px; }
|
||
.tool-config-summary p { margin-top: 3px; font-size: 10.5px; line-height: 1.4; }
|
||
.tool-status { display: inline-flex; align-items: center; gap: 5px; padding: 5px 7px; border: 1px solid var(--color-border-subtle); border-radius: 6px; color: var(--color-ink-faint); font-size: 9px; font-weight: 750; }
|
||
.tool-status.available { border-color: color-mix(in srgb, var(--color-success) 24%, var(--color-border)); color: var(--color-success); background: color-mix(in srgb, var(--color-success) 6%, transparent); }
|
||
.tool-route { display: flex; align-items: center; gap: 8px; min-height: 34px; padding: 7px 10px; border: 1px solid var(--color-border-subtle); border-radius: 8px; color: var(--color-ink-faint); background: var(--color-surface-raised); font-size: 10.5px; }
|
||
.tool-route strong { color: var(--color-ink); }
|
||
.tool-open-mode { display: grid; gap: 6px; min-width: 0; margin: 0; padding: 0; border: 0; }
|
||
.tool-open-mode legend { margin-bottom: 6px; color: var(--color-ink-muted); font-size: 10.5px; font-weight: 750; }
|
||
.tool-open-mode > div { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 7px; padding: 4px; border: 1px solid var(--color-border-subtle); border-radius: 9px; background: var(--color-surface-raised); }
|
||
.tool-open-mode button { display: grid; justify-items: start; gap: 2px; min-width: 0; min-height: 46px; padding: 7px 10px; border: 1px solid transparent; border-radius: 7px; color: var(--color-ink-dim); background: transparent; text-align: left; }
|
||
.tool-open-mode button:hover { color: var(--color-ink); background: var(--color-surface-hover); }
|
||
.tool-open-mode button.active { border-color: color-mix(in srgb, var(--color-accent) 38%, var(--color-border)); color: var(--color-ink); background: color-mix(in srgb, var(--color-accent) 9%, var(--color-surface-hover)); box-shadow: inset 2px 0 0 var(--color-accent); }
|
||
.tool-open-mode button span { max-width: 100%; overflow: hidden; font-size: 11px; font-weight: 800; text-overflow: ellipsis; white-space: nowrap; }
|
||
.tool-open-mode button small { color: var(--color-ink-faint); font-size: 9px; font-weight: 550; }
|
||
.tool-default-field, .tool-advanced-panel label { display: grid; gap: 6px; min-width: 0; color: var(--color-ink-muted); font-size: 10.5px; font-weight: 750; }
|
||
.tool-advanced-panel input, .tool-advanced-panel textarea { width: 100%; min-width: 0; border: 1px solid var(--color-border); border-radius: 7px; color: var(--color-ink); background: var(--color-surface-raised); font: inherit; }
|
||
:global(.tool-preset-select .select-menu-trigger) { height: 38px; min-height: 38px; padding: 0 11px; border-color: var(--color-border); font-size: 12px; font-weight: 700; }
|
||
.tool-default-field small { color: var(--color-ink-faint); font-size: 9.5px; font-weight: 500; }
|
||
.tool-usage-callout { display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: start; gap: 10px; padding: 10px 11px; border-left: 2px solid var(--color-accent); border-radius: 0 7px 7px 0; background: color-mix(in srgb, var(--color-accent) 6%, transparent); }
|
||
.tool-usage-callout span { color: var(--color-accent); font-size: 9.5px; font-weight: 850; text-transform: uppercase; letter-spacing: .04em; }
|
||
.tool-usage-callout p { margin: 0; color: var(--color-ink-muted); font-size: 10.5px; line-height: 1.45; }
|
||
.tool-advanced-toggle { display: flex; align-items: center; justify-content: space-between; min-height: 32px; padding: 0; border: 0; border-top: 1px solid var(--color-border-subtle); color: var(--color-ink-dim); background: transparent; font-size: 10.5px; font-weight: 750; }
|
||
.tool-advanced-toggle:hover { color: var(--color-ink); }
|
||
.tool-advanced-panel { display: grid; gap: 11px; padding-top: 2px; }
|
||
.tool-program-row { display: grid; grid-template-columns: minmax(0, 1fr) 34px; gap: 6px; }
|
||
.tool-advanced-panel input { height: 34px; padding: 0 9px; font-family: var(--font-mono); font-size: 10.5px; }
|
||
.tool-advanced-panel textarea { min-height: 80px; padding: 8px 9px; resize: vertical; font-family: var(--font-mono); font-size: 10.5px; line-height: 1.45; }
|
||
.tool-program-row button { display: grid; place-items: center; border: 1px solid var(--color-border); border-radius: 7px; color: var(--color-ink-muted); background: var(--color-surface-raised); }
|
||
.tool-program-row button:hover { color: var(--color-ink); border-color: var(--color-border-input); background: var(--color-surface-hover); }
|
||
.tool-placeholders { display: flex; flex-wrap: wrap; align-items: center; gap: 5px; margin: 0; color: var(--color-ink-faint); font-size: 9px; }
|
||
.tool-placeholders span { margin-right: 3px; }
|
||
.tool-placeholders code { padding: 2px 4px; border-radius: 4px; color: var(--color-primary); background: color-mix(in srgb, var(--color-primary) 9%, transparent); }
|
||
.general-settings-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
|
||
.general-setting-panel { display: grid; align-content: start; gap: 14px; min-width: 0; padding: 14px; border: 1px solid var(--color-border-subtle); border-radius: 10px; background: var(--app-settings-row-bg); }
|
||
.general-setting-panel.general-setting-wide { grid-column: 1 / -1; }
|
||
.general-setting-panel > header { display: flex; align-items: flex-start; gap: 9px; }
|
||
.general-setting-panel > header > :global(svg) { flex: 0 0 auto; margin-top: 1px; color: var(--color-accent); }
|
||
.general-setting-panel h4 { font-size: 12.5px; }
|
||
.general-setting-panel p { margin-top: 3px; font-size: 9.5px; }
|
||
.settings-segmented { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 4px; padding: 3px; border: 1px solid var(--color-border-subtle); border-radius: 8px; background: var(--color-surface-raised); }
|
||
.settings-segmented.settings-language { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||
.settings-segmented label { display: flex; align-items: center; justify-content: center; min-height: 31px; border: 1px solid transparent; border-radius: 6px; color: var(--color-ink-dim); font-size: 10.5px; font-weight: 750; }
|
||
.settings-segmented label.active { border-color: var(--color-border-input); color: var(--color-ink); background: var(--color-surface-hover); }
|
||
.settings-segmented input { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
|
||
.custom-theme-panel > header { align-items: center; }
|
||
.custom-theme-panel > header > div { min-width: 0; }
|
||
.theme-reset-button { display: inline-flex; align-items: center; gap: 5px; min-height: 27px; margin-left: auto; padding: 0 8px; border: 1px solid var(--color-border); color: var(--color-ink-muted); background: var(--color-surface-raised); font-size: 9.5px; font-weight: 750; }
|
||
.theme-reset-button:hover:not(:disabled) { border-color: var(--color-border-input); color: var(--color-ink); background: var(--color-surface-hover); }
|
||
.theme-preview { display: grid; grid-template-columns: 28% 1fr; min-height: 78px; overflow: hidden; border: 1px solid color-mix(in srgb, var(--preview-text) 30%, var(--preview-surface)); background: var(--preview-bg); }
|
||
.theme-preview-sidebar { border-right: 1px solid color-mix(in srgb, var(--preview-text) 24%, var(--preview-surface)); background: color-mix(in srgb, var(--preview-surface) 86%, var(--preview-bg)); }
|
||
.theme-preview-content { display: grid; grid-template-columns: 1fr auto; align-content: start; gap: 8px; margin: 10px; padding: 10px; border: 1px solid color-mix(in srgb, var(--preview-text) 22%, var(--preview-surface)); color: var(--preview-text); background: var(--preview-surface); }
|
||
.theme-preview-content i { display: block; width: 54%; height: 7px; background: var(--preview-text); opacity: .82; }
|
||
.theme-preview-content b { display: block; width: 34px; height: 18px; grid-row: 1 / 3; grid-column: 2; background: var(--preview-accent); }
|
||
.theme-preview-content em { display: block; width: 76%; height: 5px; background: var(--preview-text); opacity: .32; }
|
||
.theme-color-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 7px; }
|
||
.theme-color-grid label { display: grid; grid-template-columns: minmax(0, 1fr) 32px auto; align-items: center; gap: 8px; min-width: 0; min-height: 38px; padding: 5px 7px; border: 1px solid var(--color-border-subtle); background: var(--color-surface-raised); }
|
||
.theme-color-grid label > span { color: var(--color-ink-muted); font-size: 10px; font-weight: 750; }
|
||
.theme-color-grid input[type="color"] { width: 32px; height: 25px; padding: 2px; border: 1px solid var(--color-border-input); background: transparent; cursor: pointer; }
|
||
.theme-color-grid code { color: var(--color-ink-faint); font: 9px var(--font-mono); text-transform: uppercase; }
|
||
.theme-generator-note { margin: -5px 0 0 !important; color: var(--color-ink-faint) !important; }
|
||
.settings-switch-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 14px; }
|
||
.settings-switch-row span { display: grid; gap: 3px; }
|
||
.settings-switch-row strong { color: var(--color-ink); font-size: 11px; }
|
||
.settings-switch-row small { color: var(--color-ink-dim); font-size: 9.5px; line-height: 1.4; }
|
||
.settings-switch-row input { width: 32px; height: 18px; accent-color: var(--color-accent); }
|
||
.app-settings-footer { display: flex; align-items: center; justify-content: space-between; gap: 16px; min-height: 62px; padding: 11px 16px; border-top: 1px solid var(--color-border-subtle); background: var(--app-dialog-chrome); }
|
||
.app-settings-footer > span { color: var(--color-ink-faint); font-size: 9.5px; }
|
||
.app-settings-footer > div { display: flex; gap: 8px; }
|
||
.app-settings-footer button { min-height: 32px; }
|
||
|
||
@media (max-width: 760px) {
|
||
.app-settings-dialog { height: min(760px, calc(100vh - 20px)); width: min(660px, calc(100vw - 20px)); }
|
||
.app-settings-body { grid-template-columns: 1fr; grid-template-rows: auto minmax(0, 1fr); }
|
||
.settings-nav { flex-direction: row; padding: 8px 10px; border-right: 0; border-bottom: 1px solid var(--color-border-subtle); }
|
||
.settings-nav > button { width: auto; min-width: 0; flex: 1 1 0; min-height: 42px; }
|
||
.settings-nav-note { display: none; }
|
||
.settings-content { padding: 14px; }
|
||
.tool-kind-tabs { grid-template-columns: repeat(5, minmax(42px, 1fr)); overflow-x: auto; }
|
||
.tool-kind-tabs button { height: 40px; }
|
||
.tool-kind-tabs button span { display: none; }
|
||
.tool-config-summary { grid-template-columns: auto minmax(0, 1fr); }
|
||
.tool-status { grid-column: 1 / -1; justify-self: start; }
|
||
.app-settings-footer > span { display: none; }
|
||
.app-settings-footer { justify-content: flex-end; }
|
||
}
|
||
|
||
@media (max-width: 520px) {
|
||
.app-settings-head { min-height: 58px; padding: 10px 12px; }
|
||
.app-settings-mark { width: 34px; height: 34px; }
|
||
.settings-nav button small, .settings-nav button em { display: none; }
|
||
.settings-nav > button { grid-template-columns: auto minmax(0, 1fr); }
|
||
.settings-page-head { align-items: stretch; flex-direction: column; }
|
||
.tool-rescan-button { align-self: flex-start; }
|
||
.general-settings-grid { grid-template-columns: 1fr; }
|
||
.general-setting-panel.general-setting-wide { grid-column: auto; }
|
||
.theme-color-grid { grid-template-columns: 1fr; }
|
||
.tool-config-panel { padding: 13px; }
|
||
.tool-usage-callout { grid-template-columns: 1fr; gap: 4px; }
|
||
}
|
||
</style>
|