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
+168 -31
View File
@@ -152,6 +152,11 @@ const THEME_NAMES: [string, string][] = [
["one-light", "One Light"], ["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 { interface TerminalSettings {
id: string; id: string;
name: string; name: string;
@@ -644,66 +649,114 @@ onUnmounted(() => window.clearInterval(timer));
</div> </div>
<div v-if="settings" class="overlay"> <div v-if="settings" class="overlay">
<div class="dialog"> <div class="dialog settings-dialog">
<h3>{{ $t("projects.terminal", { name: settings.name }) }}</h3> <h3>{{ $t("projects.terminal", { name: settings.name }) }}</h3>
<label class="field">
{{ $t("projects.title") }} <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" /> <input v-model="settings.title" :placeholder="settings.name" />
</span>
<span class="sacts"></span>
</label> </label>
<label class="field"> <label class="srow">
{{ $t("projects.theme") }} <span class="slbl">{{ $t("projects.theme") }}</span>
<span class="sval">
<select v-model="settings.theme"> <select v-model="settings.theme">
<option v-for="[value, label] in THEME_NAMES" :key="value" :value="value"> <option v-for="[value, label] in THEME_NAMES" :key="value" :value="value">
{{ label }} {{ label }}
</option> </option>
</select> </select>
</span>
<span class="sacts"></span>
</label> </label>
<label class="field"> <div class="srow">
{{ $t("projects.dockIcon") }} <span class="slbl">{{ $t("projects.dockIcon") }}</span>
<span class="icon-row"> <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 @click="pickIcon">{{ $t("projects.chooseFile") }}</button>
<button v-if="settings.icon" @click="settings.icon = null"> <button v-if="settings.icon" @click="settings.icon = null">
{{ $t("projects.remove") }} {{ $t("projects.remove") }}
</button> </button>
<span v-if="settings.icon" class="icon-path hover-pop">
{{ settings.icon.split("/").pop() }}
<span class="pop pop-path">{{ settings.icon }}</span>
</span> </span>
<span v-else class="icon-path">{{ $t("projects.defaultIcon") }}</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>
</label> </span>
<label class="field field-top"> <span class="sacts">
{{ $t("projects.workDir") }}
<span class="workdirs">
<span class="icon-row">
<span class="icon-path">{{ settings.path }}</span>
<button @click="changeProjectDir">{{ $t("projects.changeDir") }}</button> <button @click="changeProjectDir">{{ $t("projects.changeDir") }}</button>
</span> </span>
<span v-for="wd in settings.workDirs" :key="wd" class="icon-row"> </div>
<span class="icon-path">{{ wd }}</span> <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> <button @click="removeWorkDir(wd)">{{ $t("projects.remove") }}</button>
</span> </span>
</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> <button @click="addWorkDir">{{ $t("projects.addWorkDir") }}</button>
</span> </span>
</label> </div>
<label class="field"> <div class="srow">
{{ $t("projects.archive") }} <span class="slbl">{{ $t("projects.archive") }}</span>
<span class="icon-row"> <span class="sval">
<span class="icon-path">{{ settings.archiveHome ?? $t("projects.archiveNone") }}</span> <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 @click="chooseArchive">{{ $t("projects.changeDir") }}</button>
<button v-if="settings.archiveHome" @click="clearArchive"> <button v-if="settings.archiveHome" @click="clearArchive">
{{ $t("projects.remove") }} {{ $t("projects.remove") }}
</button> </button>
</span> </span>
</label> </div>
<label class="field"> <p class="ghint">{{ $t("projects.writesImmediately") }}</p>
{{ $t("projects.todo") }} </section>
<span class="checkline">
<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" /> <input v-model="settings.todo" type="checkbox" />
{{ $t("projects.todoDesc") }} {{ $t("projects.todoDesc") }}
</span>
</label> </label>
<p class="hint">{{ $t("projects.appliesNextStart") }}</p> </span>
<span class="sacts"></span>
</div>
</section>
<div class="actions"> <div class="actions">
<button @click="settings = null">{{ $t("projects.cancel") }}</button> <button @click="settings = null">{{ $t("projects.cancel") }}</button>
<button class="primary" @click="saveSettings"> <button class="primary" @click="saveSettings">
@@ -732,3 +785,87 @@ onUnmounted(() => window.clearInterval(timer));
</div> </div>
</div> </div>
</template> </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.", "Nur aus Liste nehmen entfernt den Eintrag; der Ordner bleibt unangetastet.",
todo: "Todoliste", todo: "Todoliste",
todoDesc: "OFFENE-PUNKTE.md bei jedem Sessionstart einspielen", todoDesc: "OFFENE-PUNKTE.md bei jedem Sessionstart einspielen",
groupAppearance: "Darstellung",
groupFolders: "Ordner",
groupSession: "Session",
writesImmediately: "Änderungen hier schreiben sofort — ohne Speichern.",
}, },
pools: { pools: {
pool: "Pool", pool: "Pool",
@@ -206,6 +210,10 @@ const en: typeof de = {
"Remove from list only drops the entry; the folder stays untouched.", "Remove from list only drops the entry; the folder stays untouched.",
todo: "Todo list", todo: "Todo list",
todoDesc: "Inject OFFENE-PUNKTE.md at every session start", 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: { pools: {
pool: "Pool", pool: "Pool",