Update from Git Manager GUI

This commit is contained in:
2026-03-24 19:18:26 +01:00
parent 2ef1e0df61
commit b2ba2a09e2
5 changed files with 1804 additions and 122 deletions

View File

@@ -4,39 +4,42 @@ import Settings from './Settings.jsx';
export default function App() {
const [folder, setFolder] = useState('');
const [repoName, setRepoName] = useState('');
const [platform, setPlatform] = useState('github');
const [status, setStatus] = useState('');
const [platform, setPlatform] = useState('gitea');
const [status, setStatus] = useState('Bereit');
const [showSettings, setShowSettings] = useState(false);
const [branches, setBranches] = useState([]);
const [selectedBranch, setSelectedBranch] = useState('master');
const [selectedBranch, setSelectedBranch] = useState('main');
const [logs, setLogs] = useState([]);
const [progress, setProgress] = useState(0);
async function selectFolder() {
const selected = await window.electronAPI.selectFolder();
if (selected) setFolder(selected);
// Branches laden
if (selected) {
const branchList = await window.electronAPI.getBranches({ folder: selected });
setBranches(branchList);
if (branchList.includes('master')) setSelectedBranch('master');
}
if (!selected) return;
setFolder(selected);
const branchList = await window.electronAPI.getBranches({ folder: selected });
setBranches(branchList);
if (branchList.includes('main')) setSelectedBranch('main');
else if (branchList.includes('master')) setSelectedBranch('master');
else if (branchList.length > 0) setSelectedBranch(branchList[0]);
}
async function createRepoHandler() {
if (!repoName) return alert('Repo Name required!');
setStatus('Creating repository...');
if (!repoName) return alert('Repo-Name erforderlich!');
setStatus('Repository wird erstellt…');
const result = await window.electronAPI.createRepo({ name: repoName, platform });
setStatus(result ? 'Repository created!' : 'Failed to create repository.');
setStatus(result ? 'Repository erstellt!' : 'Fehler beim Erstellen des Repositories.');
}
async function pushProjectHandler() {
if (!folder) return alert('Select a project folder first!');
setStatus('Pushing project...');
if (!folder) return alert('Bitte zuerst einen Projektordner auswählen!');
setStatus('Projekt wird gepusht…');
setProgress(0);
const onProgress = (p) => setProgress(p); // Callback für Fortschritt
const result = await window.electronAPI.pushProject({ folder, branch: selectedBranch, onProgress });
setStatus(result ? 'Project pushed!' : 'Failed to push project.');
const result = await window.electronAPI.pushProject({
folder,
branch: selectedBranch,
onProgress: (p) => setProgress(p),
});
setStatus(result ? 'Projekt gepusht!' : 'Push fehlgeschlagen.');
if (result) {
const logList = await window.electronAPI.getCommitLogs({ folder });
setLogs(logList);
@@ -44,61 +47,168 @@ export default function App() {
}
return (
<div style={{ padding: 20 }}>
<h1>Git Manager GUI - High-End</h1>
<button onClick={() => setShowSettings(!showSettings)}>Settings</button>
{showSettings && <Settings />}
<div id="app" style={{ display: 'flex', flexDirection: 'column', height: '100vh' }}>
<div style={{ marginTop: 20 }}>
<label>Platform:</label>
<select value={platform} onChange={e => setPlatform(e.target.value)}>
<option value="github">GitHub</option>
<option value="gitea">Gitea</option>
</select>
{/* ── Toolbar ── */}
<div id="toolbar">
<div className="toolbar-row toolbar-row--top">
<div className="toolbar-brand">
<div className="toolbar-brand-mark">
<img src="./icon.png" alt="Git Manager Logo" className="toolbar-brand-logo" />
</div>
<div className="toolbar-brand-copy">
<span className="toolbar-kicker">Workspace Control</span>
<strong>Git Manager Explorer Pro</strong>
</div>
</div>
<div className="toolbar-top-actions">
<div className="tool-group tool-group--quick-actions">
<button onClick={createRepoHandler} title="Neues Repository erstellen">🚀 New Repo</button>
<button onClick={pushProjectHandler} title="Projekt pushen"> Push</button>
</div>
<div className="tool-group tool-group--utility">
<button onClick={() => setShowSettings(true)} title="Einstellungen"> Settings</button>
</div>
<div className="toolbar-status-wrap">
<span className="status-dot" aria-hidden="true" />
<span className="status">{status}</span>
</div>
</div>
</div>
<div className="toolbar-row toolbar-row--bottom">
<div className="tool-group tool-group--workspace">
<button className="accent-btn" onClick={selectFolder} title="Lokalen Ordner öffnen">
📂 Open Local
</button>
</div>
<div className="tool-group tool-group--repo">
<div className="platform-switch" role="tablist" aria-label="Plattform auswählen">
{['gitea', 'github'].map(p => (
<button
key={p}
type="button"
className={`platform-option${platform === p ? ' active' : ''}`}
data-platform={p}
aria-pressed={platform === p}
onClick={() => setPlatform(p)}
>
{p === 'gitea' ? 'Gitea' : 'GitHub'}
</button>
))}
</div>
{branches.length > 0 && (
<select
value={selectedBranch}
onChange={e => setSelectedBranch(e.target.value)}
title="Branch auswählen"
>
{branches.map(b => <option key={b} value={b}>{b}</option>)}
</select>
)}
</div>
</div>
</div>
<div style={{ marginTop: 20 }}>
<button onClick={selectFolder}>Select Project Folder</button>
<span style={{ marginLeft: 10 }}>{folder}</span>
</div>
{/* ── Main ── */}
<main id="main">
<div style={{ marginTop: 20 }}>
<label>Branch:</label>
<select value={selectedBranch} onChange={e => setSelectedBranch(e.target.value)}>
{branches.map(b => <option key={b} value={b}>{b}</option>)}
</select>
</div>
{/* Fortschrittsbalken */}
{progress > 0 && progress < 100 && (
<div className="input-group" style={{ marginBottom: 20 }}>
<label>Fortschritt</label>
<progress value={progress} max="100" style={{ width: '100%', height: 8, borderRadius: 4 }} />
</div>
)}
<div style={{ marginTop: 20 }}>
<input
type="text"
placeholder="Repository Name"
value={repoName}
onChange={e => setRepoName(e.target.value)} />
<button onClick={createRepoHandler} style={{ marginLeft: 10 }}>Create Repo</button>
</div>
{/* Repo erstellen / Ordner */}
<div className="card" style={{ marginBottom: 20 }}>
<h2>📁 Projekt & Repository</h2>
<div style={{ marginTop: 20 }}>
<button onClick={pushProjectHandler}>Push / Update Project</button>
</div>
<div className="input-group">
<label>Lokaler Projektordner</label>
<div style={{ display: 'flex', gap: 10, alignItems: 'center' }}>
<input
type="text"
readOnly
value={folder}
placeholder="Noch kein Ordner ausgewählt…"
style={{ flex: 1 }}
/>
<button onClick={selectFolder} style={{ flex: '0 0 auto' }}>📂 Auswählen</button>
</div>
</div>
<div style={{ marginTop: 10 }}>
<label>Progress:</label>
<progress value={progress} max="100" style={{ width: '100%' }} />
</div>
<div className="input-group">
<label>Repository Name</label>
<input
type="text"
placeholder="mein-projekt"
value={repoName}
onChange={e => setRepoName(e.target.value)}
/>
</div>
<div style={{ marginTop: 20 }}>
<strong>Status: </strong>{status}
</div>
<div style={{ display: 'flex', gap: 10, marginTop: 8 }}>
<button className="accent-btn" onClick={createRepoHandler}>🚀 Repo erstellen</button>
<button onClick={pushProjectHandler}> Push / Aktualisieren</button>
</div>
</div>
<div style={{ marginTop: 20 }}>
<h3>Commit Logs:</h3>
<ul>
{logs.map((log, i) => (
<li key={i}>{log}</li>
))}
</ul>
</div>
{/* Commit-Logs */}
{logs.length > 0 && (
<div className="card">
<h2>📊 Commit-Verlauf</h2>
<ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'flex', flexDirection: 'column', gap: 6 }}>
{logs.map((log, i) => (
<li
key={i}
style={{
padding: '8px 12px',
borderRadius: 8,
background: 'rgba(255,255,255,0.04)',
border: '1px solid rgba(140,173,255,0.08)',
fontSize: 13,
color: 'var(--text-secondary)',
fontFamily: 'monospace',
}}
>
{log}
</li>
))}
</ul>
</div>
)}
{logs.length === 0 && !folder && (
<div style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
paddingTop: 60,
gap: 16,
opacity: 0.55,
}}>
<div style={{ fontSize: 64, filter: 'drop-shadow(0 8px 16px rgba(88,213,255,0.2))' }}>📂</div>
<p style={{ color: 'var(--text-muted)', fontSize: 15, textAlign: 'center' }}>
Öffne einen lokalen Ordner oder lade Repos über die Toolbar.
</p>
</div>
)}
</main>
{/* ── Settings-Modal ── */}
{showSettings && (
<div id="settingsModal" className="modal" onClick={e => { if (e.target === e.currentTarget) setShowSettings(false); }}>
<Settings onClose={() => setShowSettings(false)} />
</div>
)}
</div>
);
}

View File

@@ -1,9 +1,10 @@
import React, { useState, useEffect } from 'react';
export default function Settings() {
export default function Settings({ onClose }) {
const [githubToken, setGithubToken] = useState('');
const [giteaToken, setGiteaToken] = useState('');
const [giteaURL, setGiteaURL] = useState('');
const [savedOk, setSavedOk] = useState(false);
function normalizeAndValidateGiteaUrl(rawUrl) {
const value = (rawUrl || '').trim();
@@ -15,14 +16,14 @@ export default function Settings() {
} catch (_) {
return {
ok: false,
error: 'Ungültige Gitea-URL. Beispiel für IPv6: http://[2001:db8::1]:3000'
error: 'Ungültige Gitea-URL. Beispiel für IPv6: http://[2001:db8::1]:3000',
};
}
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
return {
ok: false,
error: 'Die Gitea-URL muss mit http:// oder https:// beginnen.'
error: 'Die Gitea-URL muss mit http:// oder https:// beginnen.',
};
}
@@ -39,33 +40,95 @@ export default function Settings() {
});
}, []);
const save = () => {
function save() {
const checkedUrl = normalizeAndValidateGiteaUrl(giteaURL);
if (!checkedUrl.ok) {
alert(checkedUrl.error);
return;
}
window.electronAPI.saveCredentials({ githubToken, giteaToken, giteaURL: checkedUrl.value });
alert('Settings saved securely!');
setSavedOk(true);
setTimeout(() => setSavedOk(false), 2500);
}
return (
<div style={{ padding: 20 }}>
<h2>Settings</h2>
<div>
<label>GitHub Token:</label>
<input type="password" value={githubToken} onChange={e => setGithubToken(e.target.value)} />
<div className="modalContent card settings-modal-content">
<div className="settings-header">
<div>
<div className="settings-eyebrow">Konfiguration</div>
<h2> Einstellungen</h2>
<p className="settings-subtitle">
Zugangsdaten für GitHub und Gitea hinterlegen.
</p>
</div>
</div>
<div>
<label>Gitea Token:</label>
<input type="password" value={giteaToken} onChange={e => setGiteaToken(e.target.value)} />
<div className="settings-layout">
<div className="settings-column settings-column--left">
<section className="settings-panel settings-panel--credentials">
<div className="settings-panel-header">
<div>
<h3>Zugangsdaten</h3>
<p>API-Zugriffe für GitHub und Gitea konfigurieren.</p>
</div>
</div>
<div className="settings-fields-grid">
<div className="input-group">
<label htmlFor="react-githubToken">GitHub Token</label>
<input
id="react-githubToken"
type="password"
placeholder="ghp_…"
value={githubToken}
onChange={e => setGithubToken(e.target.value)}
/>
</div>
<div className="input-group">
<label htmlFor="react-giteaToken">Gitea Token</label>
<input
id="react-giteaToken"
type="password"
placeholder="Token hier einfügen"
value={giteaToken}
onChange={e => setGiteaToken(e.target.value)}
/>
</div>
</div>
<div className="input-group input-group--wide">
<label htmlFor="react-giteaURL">Gitea URL</label>
<input
id="react-giteaURL"
type="text"
placeholder="https://gitea.example.com"
value={giteaURL}
onChange={e => setGiteaURL(e.target.value)}
/>
<div className="settings-connection-tools">
<div className="settings-inline-hint">
Hinweis: IPv6 mit Klammern eingeben, z.B. http://[2001:db8::1]:3000
</div>
</div>
</div>
</section>
</div>
</div>
<div>
<label>Gitea URL:</label>
<input type="text" value={giteaURL} onChange={e => setGiteaURL(e.target.value)} />
<div className="modal-buttons settings-modal-actions">
<button
className="accent-btn"
onClick={save}
style={savedOk ? { background: 'var(--success)', borderColor: 'var(--success)' } : {}}
>
{savedOk ? '✅ Gespeichert' : 'Speichern'}
</button>
{onClose && (
<button className="secondary" onClick={onClose}>Abbrechen</button>
)}
</div>
<button onClick={save} style={{ marginTop: 10 }}>Save</button>
</div>
);
}

View File

@@ -9,6 +9,25 @@
</head>
<body>
<div id="app">
<!-- Titelbalken-Streifen -->
<div id="titlebar-strip">
<div class="titlebar-strip-brand">
<img src="./icon.png" alt="" class="titlebar-strip-icon">
<span class="titlebar-strip-title">Git Manager Explorer Pro</span>
</div>
<div class="win-controls" aria-label="Fenster-Steuerung">
<button class="win-btn win-btn--minimize" id="btnWinMinimize" title="Minimieren">
<svg width="10" height="1" viewBox="0 0 10 1"><rect width="10" height="1" fill="currentColor"/></svg>
</button>
<button class="win-btn win-btn--maximize" id="btnWinMaximize" title="Maximieren">
<svg width="10" height="10" viewBox="0 0 10 10"><rect x="0.5" y="0.5" width="9" height="9" rx="1" fill="none" stroke="currentColor"/></svg>
</button>
<button class="win-btn win-btn--close" id="btnWinClose" title="Schließen">
<svg width="10" height="10" viewBox="0 0 10 10"><line x1="0" y1="0" x2="10" y2="10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><line x1="10" y1="0" x2="0" y2="10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
</button>
</div>
</div>
<div id="toolbar">
<div class="toolbar-row toolbar-row--top">
<div class="toolbar-brand" aria-label="App Kopfbereich">
@@ -63,10 +82,12 @@
</div>
</div>
<main id="main">
<div id="explorerGrid" class="explorer-grid">
</div>
</main>
<div id="contentArea" class="content-area">
<aside id="favHistorySidebar" class="fav-history-sidebar" aria-label="Favoriten und Verlauf"></aside>
<main id="main">
<div id="explorerGrid" class="explorer-grid"></div>
</main>
</div>
<div id="settingsModal" class="modal hidden">
<div class="modalContent card settings-modal-content">
@@ -176,6 +197,27 @@
</label>
</div>
</section>
<section class="settings-panel settings-panel--system">
<div class="settings-panel-header">
<div>
<h3>System</h3>
<p>Verhalten beim Windows-Start steuern.</p>
</div>
</div>
<div class="settings-toggle-list">
<label class="settings-toggle-row" for="settingAutostart">
<span class="settings-toggle-info">
<span class="settings-toggle-title">🚀 Mit Windows starten</span>
<span class="settings-toggle-desc">Startet die App automatisch beim Anmelden, minimiert im System-Tray.</span>
</span>
<span class="toggle-switch">
<input type="checkbox" id="settingAutostart">
<span class="toggle-track"></span>
</span>
</label>
</div>
</section>
</div>
<div class="settings-column settings-column--right">
@@ -230,6 +272,7 @@
<div class="input-group">
<label>Repository Name</label>
<input id="repoName" type="text" placeholder="mein-projekt">
<div id="repoNameValidationHint" class="settings-inline-hint">Name prüfen: Duplikate, ähnliche Namen und ungültige Zeichen werden erkannt.</div>
</div>
<div class="input-group">
@@ -295,6 +338,7 @@
<input id="batchCloneTarget" type="text" readonly placeholder="Bitte Zielordner auswählen">
<button id="btnSelectBatchCloneTarget" class="secondary">📁 Wählen</button>
</div>
<div id="batchCloneValidationHint" class="settings-inline-hint">Kollisionsprüfung aktiv: vorhandene Zielordner und Namenskonflikte werden angezeigt.</div>
</div>
<div id="batchTagGroup" class="input-group hidden">

File diff suppressed because it is too large Load Diff

View File

@@ -59,6 +59,145 @@
padding: 0;
}
/* ===========================
TITELBALKEN-STREIFEN
=========================== */
#titlebar-strip {
height: 32px;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 4px 0 12px;
background: linear-gradient(135deg, rgba(8, 14, 26, 0.98) 0%, rgba(10, 19, 34, 0.98) 100%);
border-bottom: 1px solid rgba(88, 213, 255, 0.14);
-webkit-app-region: drag;
user-select: none;
position: relative;
z-index: 200;
box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.24);
}
.titlebar-strip-brand {
display: flex;
align-items: center;
gap: 7px;
min-width: 0;
}
.titlebar-strip-icon {
width: 13px;
height: 13px;
object-fit: contain;
opacity: 0.80;
filter: drop-shadow(0 1px 3px rgba(88, 213, 255, 0.25));
flex-shrink: 0;
}
.titlebar-strip-title {
font-size: 11px;
font-weight: 700;
color: rgba(174, 189, 216, 0.68);
letter-spacing: 0.045em;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
#titlebar-strip .win-controls {
-webkit-app-region: no-drag;
margin-left: auto;
border-radius: 10px;
background: linear-gradient(180deg, rgba(16, 30, 52, 0.96) 0%, rgba(12, 22, 40, 0.98) 100%);
border-color: rgba(151, 181, 255, 0.18);
box-shadow: 0 6px 14px rgba(2, 8, 24, 0.30), inset 0 1px 0 rgba(255, 255, 255, 0.05);
}
#titlebar-strip .win-btn {
width: 18px;
height: 18px;
border-radius: 5px;
}
/* ===========================
WINDOW CONTROLS
=========================== */
.win-controls {
display: inline-flex;
align-items: center;
gap: 1px;
padding: 2px;
border-radius: 8px;
background: linear-gradient(180deg, #0f1b30 0%, #0c1628 100%);
border: 1px solid rgba(151, 181, 255, 0.12);
box-shadow: 0 4px 10px rgba(2, 8, 24, 0.22), inset 0 1px 0 rgba(255, 255, 255, 0.04);
flex-shrink: 0;
-webkit-app-region: no-drag;
user-select: none;
}
.win-btn {
width: 20px;
height: 20px;
border-radius: 5px;
border: 1px solid transparent;
background: transparent;
color: #aebdd8;
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
transition: background 120ms ease, border-color 120ms ease, color 120ms ease, transform 80ms ease;
flex-shrink: 0;
}
.win-btn svg {
display: block;
flex-shrink: 0;
}
.win-btn:hover {
color: #f5f8ff;
border-color: rgba(151, 181, 255, 0.18);
background: rgba(255, 255, 255, 0.08);
}
.win-btn--minimize:hover {
background: rgba(88, 213, 255, 0.14);
border-color: rgba(88, 213, 255, 0.28);
color: #58d5ff;
}
.win-btn--maximize:hover {
background: rgba(92, 135, 255, 0.14);
border-color: rgba(92, 135, 255, 0.28);
color: #5c87ff;
}
.win-btn--close:hover {
background: rgba(239, 68, 68, 0.18);
border-color: rgba(239, 68, 68, 0.36);
color: #ef4444;
}
.win-btn:active {
transform: scale(0.88);
}
#toolbar {
-webkit-app-region: no-drag;
}
#toolbar button,
#toolbar select,
#toolbar input,
#toolbar a,
#toolbar .platform-switch,
#toolbar .toolbar-status-wrap,
#toolbar .win-controls {
-webkit-app-region: no-drag;
}
body {
margin: 0;
background: var(--bg-primary);
@@ -80,6 +219,9 @@ body {
radial-gradient(circle at bottom center, rgba(18, 127, 255, 0.10), transparent 36%),
var(--bg-primary);
position: relative;
border: 1px solid rgba(88, 213, 255, 0.14);
border-radius: 0;
overflow: hidden;
}
#app::before {
@@ -503,6 +645,10 @@ body {
min-height: unset;
}
.titlebar-strip-title {
max-width: 240px;
}
.toolbar-row {
flex-direction: column;
align-items: stretch;
@@ -529,12 +675,23 @@ body {
}
}
@media (max-width: 760px) {
#titlebar-strip {
padding-left: 8px;
}
.titlebar-strip-title {
display: none;
}
}
/* ===========================
MAIN CONTENT
=========================== */
#main {
flex: 1;
min-height: 0;
min-width: 0;
padding: var(--spacing-xl);
overflow-y: auto;
overflow-x: hidden;
@@ -556,6 +713,156 @@ body {
background: radial-gradient(circle at top right, rgba(88, 213, 255, 0.08), transparent 26%);
}
.content-area {
display: flex;
flex-direction: row;
flex: 1;
min-height: 0;
overflow: hidden;
}
.fav-history-sidebar {
width: 0;
flex-shrink: 0;
overflow: hidden;
opacity: 0;
transition: width 220ms cubic-bezier(0.4, 0, 0.2, 1),
opacity 180ms cubic-bezier(0.4, 0, 0.2, 1),
margin 220ms cubic-bezier(0.4, 0, 0.2, 1);
margin: 0;
align-self: stretch;
display: flex;
flex-direction: column;
}
.fav-history-sidebar.visible {
width: 220px;
opacity: 1;
margin: 16px 0 16px 16px;
}
.fav-history-sidebar-inner {
padding: 10px 8px;
border-radius: 14px;
border: 1px solid rgba(88, 213, 255, 0.18);
background: linear-gradient(180deg, rgba(8, 18, 35, 0.9) 0%, rgba(7, 15, 30, 0.94) 100%);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04), 0 14px 24px rgba(0, 0, 0, 0.22);
overflow: hidden;
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
}
.fav-history-switch {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 6px;
margin-bottom: 10px;
}
.fav-history-tab {
height: 28px;
border-radius: 8px;
border: 1px solid rgba(151, 181, 255, 0.14);
background: rgba(255, 255, 255, 0.03);
color: rgba(174, 189, 216, 0.88);
font-size: 11px;
font-weight: 700;
letter-spacing: 0.03em;
cursor: pointer;
transition: background var(--transition-fast), color var(--transition-fast), border-color var(--transition-fast);
}
.fav-history-tab:hover {
border-color: rgba(88, 213, 255, 0.34);
color: var(--text-primary);
}
.fav-history-tab.active {
color: #03131a;
border-color: rgba(103, 232, 249, 0.62);
background: linear-gradient(135deg, #67e8f9 0%, #58a6ff 100%);
}
.fav-history-list {
display: flex;
flex-direction: column;
gap: 6px;
max-height: calc(100vh - 270px);
overflow: auto;
padding-right: 2px;
}
.fav-history-list::-webkit-scrollbar {
width: 7px;
}
.fav-history-list::-webkit-scrollbar-thumb {
background: rgba(112, 131, 164, 0.4);
border-radius: 8px;
}
.fav-history-item {
display: flex;
flex-direction: column;
gap: 2px;
width: 100%;
text-align: left;
border: 1px solid rgba(151, 181, 255, 0.08);
border-radius: 9px;
background: rgba(16, 28, 48, 0.72);
color: var(--text-primary);
padding: 8px 9px;
cursor: pointer;
transition: border-color var(--transition-fast), transform var(--transition-fast), background var(--transition-fast);
}
.fav-history-item:hover {
border-color: rgba(88, 213, 255, 0.34);
background: rgba(19, 34, 58, 0.88);
transform: translateY(-1px);
}
.fav-history-item-name {
font-size: 12px;
font-weight: 700;
line-height: 1.3;
color: var(--text-primary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.fav-history-item-meta {
font-size: 10px;
color: var(--text-muted);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.fav-history-empty {
margin-top: 8px;
padding: 10px;
border-radius: 9px;
border: 1px dashed rgba(151, 181, 255, 0.2);
color: var(--text-muted);
font-size: 11px;
text-align: center;
}
@media (max-width: 1180px) {
.fav-history-sidebar.visible {
width: 180px;
margin: 12px 0 12px 12px;
}
.fav-history-list {
max-height: 180px;
}
}
/* Global Drop Zone Indicator */
#main.drop-active {
background: rgba(88, 213, 255, 0.06);
@@ -786,6 +1093,26 @@ body.compact-mode .item-card:hover {
padding: 20px;
}
/* Custom Scrollbar Modals */
.modal::-webkit-scrollbar {
width: 8px;
}
.modal::-webkit-scrollbar-track {
background: rgba(7, 17, 31, 0.6);
border-radius: 8px;
}
.modal::-webkit-scrollbar-thumb {
background: linear-gradient(180deg, rgba(88, 213, 255, 0.28) 0%, rgba(92, 135, 255, 0.22) 100%);
border-radius: 8px;
border: 2px solid rgba(7, 17, 31, 0.6);
}
.modal::-webkit-scrollbar-thumb:hover {
background: linear-gradient(180deg, rgba(88, 213, 255, 0.52) 0%, rgba(92, 135, 255, 0.46) 100%);
}
.modal.hidden {
display: none !important;
pointer-events: none;
@@ -898,6 +1225,28 @@ input::placeholder {
color: var(--text-muted);
}
/* Native Dropdown-Listen in Modals im Dark-Theme halten (Windows/Electron) */
#repoActionModal select,
#batchActionModal select,
#activityLogModal select {
appearance: auto;
color-scheme: dark;
}
#repoActionModal select option,
#batchActionModal select option,
#activityLogModal select option {
background: #1a2233;
color: var(--text-primary);
}
#repoActionModal select option:checked,
#batchActionModal select option:checked,
#activityLogModal select option:checked {
background: #2d4ea4;
color: #ffffff;
}
input[type="checkbox"] {
width: auto;
margin-right: var(--spacing-sm);
@@ -1261,6 +1610,52 @@ input[type="checkbox"] {
.activity-toolbar select {
width: 140px;
height: 36px;
padding: 0 10px;
border-radius: var(--radius-md);
border: 1px solid rgba(255, 255, 255, 0.18);
background: rgba(255, 255, 255, 0.06);
color: var(--text-primary);
font-size: 13px;
font-weight: 500;
cursor: pointer;
appearance: auto;
color-scheme: dark;
transition: background 0.15s, border-color 0.15s;
}
.activity-toolbar select:hover {
background: rgba(255, 255, 255, 0.10);
border-color: rgba(255, 255, 255, 0.28);
}
.activity-toolbar select:focus {
outline: none;
border-color: rgba(88, 213, 255, 0.5);
}
.activity-toolbar select option {
background: #1a1f2e;
color: var(--text-primary);
}
.activity-toolbar button {
height: 36px;
padding: 0 14px;
border-radius: var(--radius-md);
border: 1px solid rgba(255, 255, 255, 0.18);
background: rgba(255, 255, 255, 0.06);
color: var(--text-primary);
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: background 0.15s, border-color 0.15s;
white-space: nowrap;
}
.activity-toolbar button:hover {
background: rgba(255, 255, 255, 0.12);
border-color: rgba(255, 255, 255, 0.3);
}
.activity-queue-info {
@@ -1352,6 +1747,286 @@ input[type="checkbox"] {
transform: translateX(4px);
}
.repo-search-top {
display: flex;
gap: 10px;
align-items: center;
}
.repo-search-input {
flex: 1;
}
.repo-search-clear {
min-width: 34px;
height: 34px;
padding: 0;
appearance: none;
border: 1px solid rgba(255, 255, 255, 0.16);
background: linear-gradient(180deg, rgba(255, 255, 255, 0.08) 0%, rgba(255, 255, 255, 0.04) 100%);
color: var(--text-secondary);
font-size: 16px;
font-weight: 500;
line-height: 1;
cursor: pointer;
border-radius: var(--radius-md);
display: inline-flex;
align-items: center;
justify-content: center;
transition: border-color 140ms ease, background 140ms ease, color 140ms ease, transform 140ms ease;
}
.repo-search-clear:hover {
border-color: rgba(88, 213, 255, 0.45);
background: linear-gradient(180deg, rgba(88, 213, 255, 0.18) 0%, rgba(88, 213, 255, 0.10) 100%);
color: #d9f7ff;
transform: translateY(-1px);
}
.repo-search-clear:active {
transform: translateY(0);
}
.repo-search-clear:focus {
outline: none;
}
.repo-search-clear:focus-visible {
border-color: rgba(88, 213, 255, 0.55);
box-shadow: 0 0 0 3px rgba(88, 213, 255, 0.2);
}
.repo-search-meta {
margin-top: 8px;
font-size: 12px;
color: var(--text-muted);
}
.activity-heatmap-card {
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: var(--radius-lg);
background:
radial-gradient(120% 180% at 90% -40%, rgba(88, 213, 255, 0.12), transparent 48%),
linear-gradient(180deg, rgba(20, 28, 42, 0.92), rgba(13, 18, 30, 0.94));
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.22);
overflow: hidden;
}
.activity-heatmap-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 12px 14px;
border-bottom: 1px solid rgba(255, 255, 255, 0.07);
}
.activity-heatmap-header strong {
font-size: 13px;
color: var(--text-primary);
letter-spacing: 0.02em;
}
.activity-heatmap-controls {
display: inline-flex;
align-items: center;
gap: 8px;
}
.activity-heatmap-range-chip {
min-height: 24px;
padding: 0 8px;
border-radius: 999px;
border: 1px solid rgba(130, 162, 218, 0.2);
background: rgba(17, 29, 49, 0.78);
color: var(--text-secondary);
font-size: 11px;
cursor: pointer;
transition: border-color 140ms ease, background 140ms ease, color 140ms ease;
}
.activity-heatmap-range-chip:hover {
border-color: rgba(88, 213, 255, 0.38);
color: #d9ecff;
}
.activity-heatmap-range-chip.active {
border-color: rgba(88, 213, 255, 0.55);
background: linear-gradient(180deg, rgba(38, 62, 97, 0.92), rgba(23, 39, 66, 0.98));
color: #f2f8ff;
}
.activity-heatmap-range-chip.static {
cursor: default;
pointer-events: none;
}
.activity-heatmap-toggle {
min-height: 28px;
padding: 0 10px;
font-size: 12px;
border-radius: 8px;
border: 1px solid rgba(130, 162, 218, 0.28);
background: linear-gradient(180deg, rgba(28, 41, 66, 0.96), rgba(18, 28, 48, 0.98));
color: #d2ddf2;
cursor: pointer;
transition: border-color 140ms ease, background 140ms ease, color 140ms ease, transform 120ms ease;
appearance: none;
}
.activity-heatmap-toggle:hover {
border-color: rgba(88, 213, 255, 0.42);
background: linear-gradient(180deg, rgba(34, 52, 82, 0.96), rgba(20, 33, 57, 0.98));
color: #f2f7ff;
transform: translateY(-1px);
}
.activity-heatmap-toggle:focus {
outline: none;
}
.activity-heatmap-toggle:focus-visible {
border-color: rgba(88, 213, 255, 0.6);
box-shadow: 0 0 0 3px rgba(88, 213, 255, 0.2);
}
.activity-heatmap-body {
--hm-weeks: 53;
--hm-cell-size: 10px;
--hm-week-gap: 2px;
--hm-weekday-col: 22px;
--hm-grid-gap: 8px;
padding: 10px 12px 12px;
display: grid;
gap: 8px;
}
.activity-heatmap-card.collapsed .activity-heatmap-body {
display: none;
}
.activity-heatmap-months {
margin-left: calc(var(--hm-weekday-col) + var(--hm-grid-gap));
width: max-content;
display: grid;
grid-template-columns: repeat(var(--hm-weeks), var(--hm-cell-size));
column-gap: var(--hm-week-gap);
color: var(--text-muted);
font-size: 10px;
line-height: 1;
user-select: none;
}
.activity-heatmap-grid-wrap {
display: flex;
gap: var(--hm-grid-gap);
align-items: flex-start;
overflow-x: auto;
padding-bottom: 2px;
}
.activity-heatmap-weekdays {
width: var(--hm-weekday-col);
display: grid;
grid-template-rows: repeat(7, var(--hm-cell-size));
gap: var(--hm-week-gap);
color: var(--text-muted);
font-size: 9px;
line-height: 1;
user-select: none;
}
.activity-heatmap-grid {
display: grid;
grid-template-columns: repeat(var(--hm-weeks), var(--hm-cell-size));
column-gap: var(--hm-week-gap);
flex: 0 0 auto;
min-width: auto;
}
.activity-heatmap-week {
display: grid;
grid-template-rows: repeat(7, var(--hm-cell-size));
gap: var(--hm-week-gap);
}
.activity-heatmap-cell {
width: var(--hm-cell-size);
height: var(--hm-cell-size);
border-radius: 2px;
display: inline-block;
border: 1px solid rgba(255, 255, 255, 0.06);
box-sizing: border-box;
}
.activity-heatmap-cell.lv0 {
background: rgba(255, 255, 255, 0.06);
}
.activity-heatmap-cell.lv1 {
background: rgba(66, 192, 122, 0.35);
}
.activity-heatmap-cell.lv2 {
background: rgba(66, 192, 122, 0.58);
}
.activity-heatmap-cell.lv3 {
background: rgba(66, 192, 122, 0.78);
}
.activity-heatmap-cell.lv4 {
background: rgba(66, 192, 122, 0.98);
}
.activity-heatmap-cell.out {
opacity: 0.24;
}
.activity-heatmap-footer {
margin-top: 0;
display: flex;
justify-content: flex-start;
gap: 12px;
align-items: center;
flex-wrap: wrap;
color: var(--text-muted);
font-size: 10px;
}
.activity-heatmap-legend {
display: inline-flex;
align-items: center;
gap: 5px;
}
.activity-heatmap-legend .activity-heatmap-cell {
width: 10px;
height: 10px;
}
@media (max-width: 768px) {
.activity-heatmap-body {
--hm-cell-size: 9px;
--hm-week-gap: 2px;
--hm-grid-gap: 6px;
--hm-weekday-col: 20px;
padding: 8px;
}
.activity-heatmap-weekdays {
font-size: 9px;
}
.activity-heatmap-grid {
min-width: auto;
}
.activity-heatmap-cell {
border-radius: 1px;
}
}
/* ===========================
PROGRESS BAR (falls verwendet)
=========================== */
@@ -2375,6 +3050,10 @@ progress::-moz-progress-bar {
color: #86efac;
}
.settings-inline-hint.warn {
color: #fcd34d;
}
.settings-health-box {
height: auto;
margin-top: 0;