Setzt Issue #8 um: eine Ansicht statt Reiter. Die Liste der bekannten Workspaces fuehrt, das Hinzufuegen sitzt als Nebenaktion in der Fusszeile und wird nur beim leeren Erststart zur Primaeraktion. Oeffnen ist der Alltagsfall, Hinzufuegen passiert pro Workspace genau einmal - zwei gleichrangige Reiter haetten bei jedem Start eine Entscheidung verlangt, die fast immer dieselbe ist. Ein Eintrag zeigt den Ordnernamen, darunter den Pfad in der Code-Schrift. Der Pfad wird bei Ueberlaenge vorne gekuerzt, weil sein Ende mehr aussagt als sein Anfang. Die Zustandspruefung laeuft nebenlaeufig ueber use_resource und spawn_blocking. Ohne das wuerde ein nicht eingehaengtes Netzlaufwerk den Selector einfrieren: dort laeuft schon is_dir() in ein Timeout von Sekunden, und zwar auf dem Bildschirm, den jeder Start zuerst zeigt. Solange die Pruefung laeuft, bleiben die Eintraege unmarkiert. Die drei Zustaende aus #6 bekommen eigene Farben und Formulierungen - nicht erreichbar, kein Workspace mehr, beschaedigt -, weil sie zu verschiedenen Handlungen fuehren. Ein defekter Eintrag wird gedaempft, bleibt aber anklickbar und verschwindet nie automatisch: Meist fehlt nur gerade das Laufwerk, und ein Klick versucht es erneut. OpenError ist dafuer Clone und PartialEq geworden; Io traegt jetzt die Meldung als String statt io::Error, der beides nicht kann.
318 lines
7.9 KiB
CSS
318 lines
7.9 KiB
CSS
/* ==========================================================================
|
|
Components — the things you click. Each rule owns one class.
|
|
========================================================================== */
|
|
|
|
/* --- File rail entry -------------------------------------------------------
|
|
An active file is marked by a rule at the left edge, the way an editor marks
|
|
the current line — not by a filled pill.
|
|
-------------------------------------------------------------------------- */
|
|
|
|
.file-selector {
|
|
position: relative;
|
|
display: block;
|
|
padding: var(--dn-space-1) var(--dn-space-2) var(--dn-space-1) var(--dn-space-3);
|
|
border-radius: var(--dn-radius-sm);
|
|
color: var(--dn-fg-muted);
|
|
font-family: var(--dn-font-code);
|
|
font-size: var(--dn-text-sm);
|
|
text-decoration: none;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
transition: color var(--dn-dur-fast) var(--dn-ease),
|
|
background-color var(--dn-dur-fast) var(--dn-ease);
|
|
}
|
|
|
|
.file-selector::before {
|
|
content: "";
|
|
position: absolute;
|
|
inset-block: 3px;
|
|
left: 0;
|
|
width: 2px;
|
|
border-radius: 999px;
|
|
background: var(--dn-accent);
|
|
transform: scaleY(0);
|
|
transition: transform var(--dn-dur) var(--dn-ease);
|
|
}
|
|
|
|
.file-selector:hover {
|
|
background: var(--dn-bg-raised);
|
|
color: var(--dn-fg);
|
|
}
|
|
|
|
.file-selector.active {
|
|
color: var(--dn-fg);
|
|
background: var(--dn-bg-raised);
|
|
}
|
|
|
|
.file-selector.active::before {
|
|
transform: scaleY(1);
|
|
}
|
|
|
|
/* --- Buttons ---------------------------------------------------------------- */
|
|
|
|
.btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: var(--dn-space-2);
|
|
padding: var(--dn-space-2) var(--dn-space-4);
|
|
border: 1px solid var(--dn-border-strong);
|
|
border-radius: var(--dn-radius);
|
|
background: var(--dn-bg-raised);
|
|
color: var(--dn-fg);
|
|
font-size: var(--dn-text-base);
|
|
font-weight: 500;
|
|
transition: background-color var(--dn-dur-fast) var(--dn-ease),
|
|
border-color var(--dn-dur-fast) var(--dn-ease);
|
|
}
|
|
|
|
.btn:hover:not(:disabled) {
|
|
background: var(--dn-bg-overlay);
|
|
border-color: var(--ctp-surface-2);
|
|
}
|
|
|
|
.btn-primary {
|
|
background: var(--dn-accent);
|
|
border-color: var(--dn-accent);
|
|
color: var(--dn-fg-on-accent);
|
|
}
|
|
|
|
.btn-primary:hover:not(:disabled) {
|
|
background: var(--dn-accent-hover);
|
|
border-color: var(--dn-accent-hover);
|
|
}
|
|
|
|
.btn-ghost {
|
|
background: transparent;
|
|
border-color: transparent;
|
|
}
|
|
|
|
.btn-ghost:hover:not(:disabled) {
|
|
background: var(--dn-bg-raised);
|
|
border-color: transparent;
|
|
}
|
|
|
|
/* --- Text input --------------------------------------------------------------
|
|
Workspace names, file names, search.
|
|
-------------------------------------------------------------------------- */
|
|
|
|
.input {
|
|
width: 100%;
|
|
padding: var(--dn-space-2) var(--dn-space-3);
|
|
border: 1px solid var(--dn-border-strong);
|
|
border-radius: var(--dn-radius);
|
|
background: var(--dn-bg-well);
|
|
color: var(--dn-fg);
|
|
font-family: var(--dn-font-code);
|
|
font-size: var(--dn-text-base);
|
|
transition: border-color var(--dn-dur-fast) var(--dn-ease);
|
|
}
|
|
|
|
.input:focus {
|
|
border-color: var(--dn-accent);
|
|
outline: none;
|
|
}
|
|
|
|
.input:focus-visible {
|
|
outline: 2px solid var(--dn-accent-muted);
|
|
outline-offset: 0;
|
|
}
|
|
|
|
/* --- Field ------------------------------------------------------------------- */
|
|
|
|
.field {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--dn-space-2);
|
|
}
|
|
|
|
.field-label {
|
|
color: var(--dn-fg-muted);
|
|
font-family: var(--dn-font-code);
|
|
font-size: var(--dn-text-xs);
|
|
letter-spacing: var(--dn-tracking-caps);
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.field-hint {
|
|
color: var(--dn-fg-faint);
|
|
font-size: var(--dn-text-xs);
|
|
}
|
|
|
|
.field-error {
|
|
color: var(--dn-danger);
|
|
font-size: var(--dn-text-xs);
|
|
}
|
|
|
|
/* --- Empty state --------------------------------------------------------------
|
|
An empty screen is an invitation to act, not a shrug.
|
|
-------------------------------------------------------------------------- */
|
|
|
|
.empty-state {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: var(--dn-space-3);
|
|
height: 100%;
|
|
padding: var(--dn-space-7) var(--dn-space-4);
|
|
color: var(--dn-fg-muted);
|
|
text-align: center;
|
|
}
|
|
|
|
.empty-state-title {
|
|
color: var(--dn-fg);
|
|
font-size: var(--dn-text-md);
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* --- Workspace-Listeneintrag ---------------------------------------------------
|
|
Ordnername, darunter der Pfad, rechts bei Bedarf ein Statusabzeichen. Ein
|
|
defekter Eintrag wird gedämpft, bleibt aber anklickbar: Ein Klick versucht
|
|
es erneut, womit „Stick wieder eingesteckt" ohne eigenen Knopf abgedeckt ist.
|
|
-------------------------------------------------------------------------- */
|
|
|
|
.ws-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 1px;
|
|
}
|
|
|
|
.ws-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--dn-space-3);
|
|
width: 100%;
|
|
padding: var(--dn-space-3);
|
|
border: 0;
|
|
border-radius: var(--dn-radius);
|
|
background: transparent;
|
|
color: inherit;
|
|
font: inherit;
|
|
text-align: left;
|
|
cursor: pointer;
|
|
transition: background-color var(--dn-dur-fast) var(--dn-ease);
|
|
}
|
|
|
|
.ws-item:hover {
|
|
background: var(--dn-bg-raised);
|
|
}
|
|
|
|
.ws-item-body {
|
|
flex: 1;
|
|
min-width: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
}
|
|
|
|
.ws-item-name {
|
|
color: var(--dn-fg);
|
|
font-weight: 500;
|
|
}
|
|
|
|
/* Bei Überlänge vorne kürzen: Das Ende eines Pfades sagt mehr als sein Anfang. */
|
|
.ws-item-path {
|
|
direction: rtl;
|
|
text-align: left;
|
|
color: var(--dn-fg-faint);
|
|
font-family: var(--dn-font-code);
|
|
font-size: var(--dn-text-xs);
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.ws-item.is-broken .ws-item-name {
|
|
color: var(--dn-fg-muted);
|
|
}
|
|
|
|
.ws-badge {
|
|
flex: none;
|
|
padding: 2px var(--dn-space-2);
|
|
border-radius: 999px;
|
|
font-size: var(--dn-text-xs);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* Drei Zustände, drei Farben — sie führen zu verschiedenen Handlungen:
|
|
Laufwerk einhängen, Ordner wiederherstellen, Datei reparieren. */
|
|
.ws-badge.unreachable {
|
|
background: color-mix(in oklab, var(--dn-warning) 18%, transparent);
|
|
color: var(--dn-warning);
|
|
}
|
|
|
|
.ws-badge.no-marker {
|
|
background: color-mix(in oklab, var(--dn-fg-faint) 22%, transparent);
|
|
color: var(--dn-fg-muted);
|
|
}
|
|
|
|
.ws-badge.damaged {
|
|
background: color-mix(in oklab, var(--dn-danger) 18%, transparent);
|
|
color: var(--dn-danger);
|
|
}
|
|
|
|
/* --- Dialog --------------------------------------------------------------------
|
|
Für Unterbrechungen einer laufenden Aktion. Zustände gehören dagegen inline
|
|
an ihren Ort, nicht in ein zweites Fenster.
|
|
-------------------------------------------------------------------------- */
|
|
|
|
.dialog-scrim {
|
|
position: fixed;
|
|
inset: 0;
|
|
display: grid;
|
|
place-items: center;
|
|
padding: var(--dn-space-4);
|
|
background: rgb(0 0 0 / 0.55);
|
|
z-index: 50;
|
|
}
|
|
|
|
.dialog {
|
|
width: min(28rem, 100%);
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--dn-space-4);
|
|
padding: var(--dn-space-5);
|
|
border: 1px solid var(--dn-border-strong);
|
|
border-radius: var(--dn-radius-lg);
|
|
background: var(--dn-bg-sunken);
|
|
box-shadow: var(--dn-shadow);
|
|
}
|
|
|
|
.dialog h2 {
|
|
font-size: var(--dn-text-md);
|
|
font-weight: 600;
|
|
}
|
|
|
|
.dialog p {
|
|
color: var(--dn-fg-muted);
|
|
line-height: var(--dn-leading-normal);
|
|
}
|
|
|
|
.dialog code {
|
|
font-family: var(--dn-font-code);
|
|
font-size: var(--dn-text-sm);
|
|
color: var(--dn-fg);
|
|
overflow-wrap: anywhere;
|
|
}
|
|
|
|
.dialog-actions {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
gap: var(--dn-space-2);
|
|
}
|
|
|
|
/* --- Utilities ---------------------------------------------------------------- */
|
|
|
|
.visually-hidden {
|
|
position: absolute;
|
|
width: 1px;
|
|
height: 1px;
|
|
margin: -1px;
|
|
padding: 0;
|
|
overflow: hidden;
|
|
clip-path: inset(50%);
|
|
white-space: nowrap;
|
|
}
|