feat(v3): add global settings section to SettingsTab
Add Global section with theme flavor dropdown, default shell input, and default CWD input. All settings persisted via settings-bridge. Fix a11y by using wrapping <label> elements for project fields. Clean up unused CSS selectors.
This commit is contained in:
parent
6ea1ed1dfd
commit
3b2c1353fa
1 changed files with 120 additions and 15 deletions
|
|
@ -1,4 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { onMount } from 'svelte';
|
||||||
import {
|
import {
|
||||||
getActiveProjectId,
|
getActiveProjectId,
|
||||||
getActiveGroup,
|
getActiveGroup,
|
||||||
|
|
@ -12,6 +13,9 @@
|
||||||
switchGroup,
|
switchGroup,
|
||||||
} from '../../stores/workspace.svelte';
|
} from '../../stores/workspace.svelte';
|
||||||
import { deriveIdentifier } from '../../types/groups';
|
import { deriveIdentifier } from '../../types/groups';
|
||||||
|
import { getSetting, setSetting } from '../../adapters/settings-bridge';
|
||||||
|
import { getCurrentFlavor, setFlavor } from '../../stores/theme.svelte';
|
||||||
|
import type { CatppuccinFlavor } from '../../styles/themes';
|
||||||
|
|
||||||
let activeGroupId = $derived(getActiveGroupId());
|
let activeGroupId = $derived(getActiveGroupId());
|
||||||
let activeGroup = $derived(getActiveGroup());
|
let activeGroup = $derived(getActiveGroup());
|
||||||
|
|
@ -22,6 +26,35 @@
|
||||||
activeGroup?.projects.find(p => p.id === activeProjectId),
|
activeGroup?.projects.find(p => p.id === activeProjectId),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Global settings
|
||||||
|
let defaultShell = $state('');
|
||||||
|
let defaultCwd = $state('');
|
||||||
|
let themeFlavor = $state<CatppuccinFlavor>(getCurrentFlavor());
|
||||||
|
const flavors: CatppuccinFlavor[] = ['latte', 'frappe', 'macchiato', 'mocha'];
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
const [shell, cwd] = await Promise.all([
|
||||||
|
getSetting('default_shell'),
|
||||||
|
getSetting('default_cwd'),
|
||||||
|
]);
|
||||||
|
defaultShell = shell ?? '';
|
||||||
|
defaultCwd = cwd ?? '';
|
||||||
|
themeFlavor = getCurrentFlavor();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function saveGlobalSetting(key: string, value: string) {
|
||||||
|
try {
|
||||||
|
await setSetting(key, value);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Failed to save setting ${key}:`, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleThemeChange(flavor: CatppuccinFlavor) {
|
||||||
|
themeFlavor = flavor;
|
||||||
|
await setFlavor(flavor);
|
||||||
|
}
|
||||||
|
|
||||||
// New project form
|
// New project form
|
||||||
let newName = $state('');
|
let newName = $state('');
|
||||||
let newCwd = $state('');
|
let newCwd = $state('');
|
||||||
|
|
@ -55,6 +88,42 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="settings-tab">
|
<div class="settings-tab">
|
||||||
|
<section class="settings-section">
|
||||||
|
<h2>Global</h2>
|
||||||
|
<div class="global-settings">
|
||||||
|
<div class="setting-row">
|
||||||
|
<label for="theme-flavor">Theme</label>
|
||||||
|
<select
|
||||||
|
id="theme-flavor"
|
||||||
|
value={themeFlavor}
|
||||||
|
onchange={e => handleThemeChange((e.target as HTMLSelectElement).value as CatppuccinFlavor)}
|
||||||
|
>
|
||||||
|
{#each flavors as f}
|
||||||
|
<option value={f}>{f.charAt(0).toUpperCase() + f.slice(1)}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="setting-row">
|
||||||
|
<label for="default-shell">Default shell</label>
|
||||||
|
<input
|
||||||
|
id="default-shell"
|
||||||
|
value={defaultShell}
|
||||||
|
placeholder="/bin/bash"
|
||||||
|
onchange={e => { defaultShell = (e.target as HTMLInputElement).value; saveGlobalSetting('default_shell', defaultShell); }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="setting-row">
|
||||||
|
<label for="default-cwd">Default CWD</label>
|
||||||
|
<input
|
||||||
|
id="default-cwd"
|
||||||
|
value={defaultCwd}
|
||||||
|
placeholder="~"
|
||||||
|
onchange={e => { defaultCwd = (e.target as HTMLInputElement).value; saveGlobalSetting('default_cwd', defaultCwd); }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section class="settings-section">
|
<section class="settings-section">
|
||||||
<h2>Groups</h2>
|
<h2>Groups</h2>
|
||||||
<div class="group-list">
|
<div class="group-list">
|
||||||
|
|
@ -85,36 +154,36 @@
|
||||||
|
|
||||||
{#each activeGroup.projects as project}
|
{#each activeGroup.projects as project}
|
||||||
<div class="project-settings-row">
|
<div class="project-settings-row">
|
||||||
<div class="project-field">
|
<label class="project-field">
|
||||||
<label>Name</label>
|
<span class="field-label">Name</span>
|
||||||
<input
|
<input
|
||||||
value={project.name}
|
value={project.name}
|
||||||
onchange={e => updateProject(activeGroupId, project.id, { name: (e.target as HTMLInputElement).value })}
|
onchange={e => updateProject(activeGroupId, project.id, { name: (e.target as HTMLInputElement).value })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</label>
|
||||||
<div class="project-field">
|
<label class="project-field">
|
||||||
<label>CWD</label>
|
<span class="field-label">CWD</span>
|
||||||
<input
|
<input
|
||||||
value={project.cwd}
|
value={project.cwd}
|
||||||
onchange={e => updateProject(activeGroupId, project.id, { cwd: (e.target as HTMLInputElement).value })}
|
onchange={e => updateProject(activeGroupId, project.id, { cwd: (e.target as HTMLInputElement).value })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</label>
|
||||||
<div class="project-field">
|
<label class="project-field">
|
||||||
<label>Icon</label>
|
<span class="field-label">Icon</span>
|
||||||
<input
|
<input
|
||||||
value={project.icon}
|
value={project.icon}
|
||||||
onchange={e => updateProject(activeGroupId, project.id, { icon: (e.target as HTMLInputElement).value })}
|
onchange={e => updateProject(activeGroupId, project.id, { icon: (e.target as HTMLInputElement).value })}
|
||||||
style="width: 60px"
|
style="width: 60px"
|
||||||
/>
|
/>
|
||||||
</div>
|
</label>
|
||||||
<div class="project-field">
|
<label class="project-field">
|
||||||
<label>Enabled</label>
|
<span class="field-label">Enabled</span>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={project.enabled}
|
checked={project.enabled}
|
||||||
onchange={e => updateProject(activeGroupId, project.id, { enabled: (e.target as HTMLInputElement).checked })}
|
onchange={e => updateProject(activeGroupId, project.id, { enabled: (e.target as HTMLInputElement).checked })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</label>
|
||||||
<button class="btn-danger" onclick={() => removeProject(activeGroupId, project.id)}>
|
<button class="btn-danger" onclick={() => removeProject(activeGroupId, project.id)}>
|
||||||
Remove
|
Remove
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -155,6 +224,43 @@
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.global-settings {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
background: var(--ctp-surface0);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-row label {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--ctp-subtext0);
|
||||||
|
min-width: 100px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-row input,
|
||||||
|
.setting-row select {
|
||||||
|
padding: 4px 8px;
|
||||||
|
background: var(--ctp-base);
|
||||||
|
border: 1px solid var(--ctp-surface1);
|
||||||
|
border-radius: 3px;
|
||||||
|
color: var(--ctp-text);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-row select {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.group-list {
|
.group-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
@ -208,14 +314,13 @@
|
||||||
gap: 2px;
|
gap: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-field label {
|
.field-label {
|
||||||
font-size: 0.7rem;
|
font-size: 0.7rem;
|
||||||
color: var(--ctp-overlay0);
|
color: var(--ctp-overlay0);
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-field input[type="text"],
|
.project-field input:not([type="checkbox"]) {
|
||||||
.project-field input:not([type]) {
|
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
background: var(--ctp-base);
|
background: var(--ctp-base);
|
||||||
border: 1px solid var(--ctp-surface1);
|
border: 1px solid var(--ctp-surface1);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue