Settings-Dialog neu: drei Gruppen, festes Zeilen-Grid, Sofort-Schreiber abgesetzt

Darstellung (Titel/Theme/Dock-Icon), Ordner (Projekt/Arbeitsordner/Archiv)
und Session (Todoliste) als Gruppen mit Eyebrow-Überschriften; jede Zeile
im Grid Label | Wert | Aktionen. Pfade ~-kontrahiert mit vollem Pfad im
Hover-Pop. Die Ordner-Gruppe schreibt sofort und ist als eigene Fläche
abgesetzt (eigener Hinweis); „gilt ab nächstem Start" hängt an der
Darstellungs-Gruppe statt global. Nur ProjectList.vue + i18n-Schlüssel.
This commit is contained in:
marcus hinz
2026-07-20 11:06:40 +02:00
parent a1c95dae4a
commit 23f6562f50
2 changed files with 199 additions and 54 deletions
+191 -54
View File
@@ -152,6 +152,11 @@ const THEME_NAMES: [string, string][] = [
["one-light", "One Light"],
];
// Anzeige: Home-Anteil ~-kontrahiert; der volle Pfad steht im Hover-Pop.
function contractHome(p: string): string {
return p.replace(/^\/(?:home|Users)\/[^/]+/, "~");
}
interface TerminalSettings {
id: string;
name: string;
@@ -644,66 +649,114 @@ onUnmounted(() => window.clearInterval(timer));
</div>
<div v-if="settings" class="overlay">
<div class="dialog">
<div class="dialog settings-dialog">
<h3>{{ $t("projects.terminal", { name: settings.name }) }}</h3>
<label class="field">
{{ $t("projects.title") }}
<input v-model="settings.title" :placeholder="settings.name" />
</label>
<label class="field">
{{ $t("projects.theme") }}
<select v-model="settings.theme">
<option v-for="[value, label] in THEME_NAMES" :key="value" :value="value">
{{ label }}
</option>
</select>
</label>
<label class="field">
{{ $t("projects.dockIcon") }}
<span class="icon-row">
<button @click="pickIcon">{{ $t("projects.chooseFile") }}</button>
<button v-if="settings.icon" @click="settings.icon = null">
{{ $t("projects.remove") }}
</button>
<span v-if="settings.icon" class="icon-path hover-pop">
{{ settings.icon.split("/").pop() }}
<span class="pop pop-path">{{ settings.icon }}</span>
<section class="sgroup">
<h4 class="eyebrow">{{ $t("projects.groupAppearance") }}</h4>
<label class="srow">
<span class="slbl">{{ $t("projects.title") }}</span>
<span class="sval">
<input v-model="settings.title" :placeholder="settings.name" />
</span>
<span v-else class="icon-path">{{ $t("projects.defaultIcon") }}</span>
</span>
</label>
<label class="field field-top">
{{ $t("projects.workDir") }}
<span class="workdirs">
<span class="icon-row">
<span class="icon-path">{{ settings.path }}</span>
<span class="sacts"></span>
</label>
<label class="srow">
<span class="slbl">{{ $t("projects.theme") }}</span>
<span class="sval">
<select v-model="settings.theme">
<option v-for="[value, label] in THEME_NAMES" :key="value" :value="value">
{{ label }}
</option>
</select>
</span>
<span class="sacts"></span>
</label>
<div class="srow">
<span class="slbl">{{ $t("projects.dockIcon") }}</span>
<span class="sval">
<span v-if="settings.icon" class="spath hover-pop">
{{ settings.icon.split("/").pop() }}
<span class="pop pop-path">{{ settings.icon }}</span>
</span>
<span v-else class="spath muted">{{ $t("projects.defaultIcon") }}</span>
</span>
<span class="sacts">
<button @click="pickIcon">{{ $t("projects.chooseFile") }}</button>
<button v-if="settings.icon" @click="settings.icon = null">
{{ $t("projects.remove") }}
</button>
</span>
</div>
<p class="ghint">{{ $t("projects.appliesNextStart") }}</p>
</section>
<section class="sgroup instant">
<h4 class="eyebrow">{{ $t("projects.groupFolders") }}</h4>
<div class="srow">
<span class="slbl">{{ $t("projects.projectDir") }}</span>
<span class="sval">
<span class="spath hover-pop">
{{ contractHome(settings.path) }}
<span class="pop pop-path">{{ settings.path }}</span>
</span>
</span>
<span class="sacts">
<button @click="changeProjectDir">{{ $t("projects.changeDir") }}</button>
</span>
<span v-for="wd in settings.workDirs" :key="wd" class="icon-row">
<span class="icon-path">{{ wd }}</span>
</div>
<div v-for="(wd, i) in settings.workDirs" :key="wd" class="srow">
<span class="slbl">{{ i === 0 ? $t("projects.workDir") : "" }}</span>
<span class="sval">
<span class="spath hover-pop">
{{ contractHome(wd) }}
<span class="pop pop-path">{{ wd }}</span>
</span>
</span>
<span class="sacts">
<button @click="removeWorkDir(wd)">{{ $t("projects.remove") }}</button>
</span>
<button @click="addWorkDir">{{ $t("projects.addWorkDir") }}</button>
</span>
</label>
<label class="field">
{{ $t("projects.archive") }}
<span class="icon-row">
<span class="icon-path">{{ settings.archiveHome ?? $t("projects.archiveNone") }}</span>
<button @click="chooseArchive">{{ $t("projects.changeDir") }}</button>
<button v-if="settings.archiveHome" @click="clearArchive">
{{ $t("projects.remove") }}
</button>
</span>
</label>
<label class="field">
{{ $t("projects.todo") }}
<span class="checkline">
<input v-model="settings.todo" type="checkbox" />
{{ $t("projects.todoDesc") }}
</span>
</label>
<p class="hint">{{ $t("projects.appliesNextStart") }}</p>
</div>
<div class="srow">
<span class="slbl">{{ settings.workDirs.length ? "" : $t("projects.workDir") }}</span>
<span class="sval"></span>
<span class="sacts">
<button @click="addWorkDir">{{ $t("projects.addWorkDir") }}</button>
</span>
</div>
<div class="srow">
<span class="slbl">{{ $t("projects.archive") }}</span>
<span class="sval">
<span v-if="settings.archiveHome" class="spath hover-pop">
{{ contractHome(settings.archiveHome) }}
<span class="pop pop-path">{{ settings.archiveHome }}</span>
</span>
<span v-else class="spath muted">{{ $t("projects.archiveNone") }}</span>
</span>
<span class="sacts">
<button @click="chooseArchive">{{ $t("projects.changeDir") }}</button>
<button v-if="settings.archiveHome" @click="clearArchive">
{{ $t("projects.remove") }}
</button>
</span>
</div>
<p class="ghint">{{ $t("projects.writesImmediately") }}</p>
</section>
<section class="sgroup">
<h4 class="eyebrow">{{ $t("projects.groupSession") }}</h4>
<div class="srow">
<span class="slbl">{{ $t("projects.todo") }}</span>
<span class="sval">
<label class="checkline">
<input v-model="settings.todo" type="checkbox" />
{{ $t("projects.todoDesc") }}
</label>
</span>
<span class="sacts"></span>
</div>
</section>
<div class="actions">
<button @click="settings = null">{{ $t("projects.cancel") }}</button>
<button class="primary" @click="saveSettings">
@@ -732,3 +785,87 @@ onUnmounted(() => window.clearInterval(timer));
</div>
</div>
</template>
<style scoped>
/* Settings-Dialog: festes Grid Label | Wert | Aktionen in drei Gruppen. */
.settings-dialog {
width: 36rem;
max-width: 36rem;
gap: 0.8rem;
}
.sgroup {
display: flex;
flex-direction: column;
gap: 0.4rem;
padding: 0.7rem 0.9rem;
}
/* Sofort-Schreiber (Ordner/Archiv) als abgesetzte Fläche — hier gibt es
keinen Speichern-Schritt, der Rest des Dialogs speichert erst am Ende. */
.sgroup.instant {
background: var(--crust);
border: 1px solid var(--surface0);
border-radius: 9px;
}
.eyebrow {
margin: 0 0 0.15rem;
font-size: 0.68rem;
font-weight: 600;
letter-spacing: 0.09em;
text-transform: uppercase;
color: var(--overlay);
}
.srow {
display: grid;
grid-template-columns: 7.5rem 1fr auto;
align-items: center;
gap: 0.75rem;
min-height: 1.9rem;
color: var(--subtext);
font-size: 0.85rem;
}
.sval {
display: flex;
align-items: center;
min-width: 0;
}
.sval input,
.sval select {
width: 100%;
}
.sacts {
display: flex;
gap: 0.4rem;
justify-content: flex-end;
}
.spath {
font-family: var(--mono);
font-size: 0.75rem;
color: var(--subtext);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-width: 0;
}
.spath.muted {
color: var(--overlay);
}
.ghint {
margin: 0.1rem 0 0;
font-size: 0.75rem;
color: var(--overlay);
}
.settings-dialog .actions {
margin-top: 0.25rem;
}
</style>
+8
View File
@@ -80,6 +80,10 @@ const de = {
"Nur aus Liste nehmen entfernt den Eintrag; der Ordner bleibt unangetastet.",
todo: "Todoliste",
todoDesc: "OFFENE-PUNKTE.md bei jedem Sessionstart einspielen",
groupAppearance: "Darstellung",
groupFolders: "Ordner",
groupSession: "Session",
writesImmediately: "Änderungen hier schreiben sofort — ohne Speichern.",
},
pools: {
pool: "Pool",
@@ -206,6 +210,10 @@ const en: typeof de = {
"Remove from list only drops the entry; the folder stays untouched.",
todo: "Todo list",
todoDesc: "Inject OFFENE-PUNKTE.md at every session start",
groupAppearance: "Appearance",
groupFolders: "Folders",
groupSession: "Session",
writesImmediately: "Changes here are written immediately — no save step.",
},
pools: {
pool: "Pool",