Setzt Issue #7 um und schliesst damit die Workspace-Map ab. Die files-bar zeigt nicht mehr zwei Platzhalter-Routen, sondern den rekursiv eingelesenen Inhalt des offenen Workspace. TreeNode ist ein verschachteltes Enum mit einer einzigen File-Variante fuer alle Dateitypen - wer wissen will, ob es eine Notiz ist, fragt path.extension(). Aufgebaut wird ueber std::fs::read_dir mit eigener Rekursion; walkdir liegt zwar im Graph, iteriert aber flach, woraus einen Baum zu bauen mehr Code waere. Eingesammelt wird alles ausser Namen mit fuehrendem Punkt - Dateien wie Ordner, womit .dionotes und .git ohne Ausnahmeliste wegfallen - und ausser Symlinks, deren Verfolgung einen Zyklusschutz erfordern wuerde. Leere Ordner werden angezeigt, sortiert wird Ordner-vor-Dateien und alphabetisch ohne Ruecksicht auf Gross-/Kleinschreibung. Der Aufklapp-Zustand liegt als HashSet<PathBuf> neben dem Baum, nicht darin: Es gibt keinen Filewatcher, der Nutzer liest bei Bedarf neu ein - steckte expanded im Knoten, klappte jeder Refresh saemtliche Ordner zu. Persistiert wird er nicht. Mit dem Baum entfaellt die Tab-Leiste im Editor und damit EDITOR_TABS, der letzte Rest des in #5 dokumentierten Pflegepunkts. strum wird dadurch nirgends mehr gebraucht und faellt samt strum_macros aus dem Graph. Ausserdem entfernt: der Dioxus-Vorlagentext Hero samt header.svg, der nach dem Oeffnen eines Workspace die rechte Haelfte mit ungestylten Werbelinks fuellte. Home ist jetzt ein Empty-State.
347 lines
8.7 KiB
CSS
347 lines
8.7 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: flex;
|
|
align-items: center;
|
|
gap: var(--dn-space-1);
|
|
/* Einrückung pro Baumebene — --depth setzt die Komponente je Knoten. */
|
|
padding: var(--dn-space-1) var(--dn-space-2) var(--dn-space-1)
|
|
calc(var(--dn-space-3) + var(--depth, 0) * var(--dn-space-4));
|
|
/* Als <button> im Baum: Browser-Vorgaben zurücknehmen. */
|
|
width: 100%;
|
|
border: 0;
|
|
background: transparent;
|
|
text-align: left;
|
|
cursor: pointer;
|
|
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);
|
|
}
|
|
|
|
/* Das Dreieck hält seinen Platz auch bei Dateien, damit Ordner- und
|
|
Dateinamen derselben Ebene bündig stehen. */
|
|
.tree-caret {
|
|
flex: none;
|
|
width: 0.9em;
|
|
color: var(--dn-fg-faint);
|
|
font-size: 0.8em;
|
|
}
|
|
|
|
.file-selector.is-folder {
|
|
color: var(--dn-fg);
|
|
}
|
|
|
|
.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;
|
|
}
|
|
|
|
/* Quadratischer Knopf für ein einzelnes Zeichen — Refresh, Schließen. */
|
|
.btn-icon {
|
|
padding: var(--dn-space-1) var(--dn-space-2);
|
|
line-height: 1;
|
|
}
|
|
|
|
.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;
|
|
}
|