feat(web): refonte visuelle « Emerald » (thème clair/sombre, polices, tokens)
Adopte le langage visuel du design system Emerald dans l'IDE web : neutres zinc + accent emerald, polices auto-hébergées Inter + JetBrains Mono, radii/pills mono, points de statut, motif terminal, halo/grain. Refonte purement visuelle et additive (layout, routing, stores, protocole inchangés). - style.css : défauts sombres dans @theme + override clair sous html[data-theme=light] ; accent scindé (bright #34d399 / solid #059669 / hover) ; tokens border-soft/info/warn/danger/coffee ; idiomes globaux (focus/selection accent, scrollbars fines, jl-pulse/jl-blink, classes .chip/.status/.eyebrow/.label-mono/.caret/.halo/.grain, .btn*/.badge pill). - lib/theme.ts : singleton themeMode/resolvedTheme (dark/light/system, persistedRef arb.theme) + application dataset.theme/metas ; script anti-FOUC inline dans index.html ; toggle ActivityBar + Réglages. - Thèmes Monaco + xterm dark/light réactifs (palette ANSI + coloration syntaxique) ; fonts.ready + remeasureFonts pour l'alignement des glyphes. - status-tokens : tons sémantiques tokenisés ; balayage des ~340 couleurs Tailwind brutes vers les tokens sur tout components/* et views/* (exceptions assumées commentées). - GroupSummary.color exploité : icône de groupe teintée + ColorSwatchPicker (lib/group-colors.ts) dans les modals de groupe. - Polices via @fontsource-variable/{inter,jetbrains-mono} (offline).
This commit is contained in:
@@ -4,16 +4,22 @@
|
||||
v-for="item in items"
|
||||
:key="item.view"
|
||||
type="button"
|
||||
class="relative flex h-10 w-10 items-center justify-center rounded-lg transition-colors"
|
||||
:class="isActive(item.view) ? 'bg-surface-3 text-fg' : 'text-fg-subtle hover:bg-surface-2 hover:text-fg'"
|
||||
class="relative flex h-10 w-10 items-center justify-center rounded-[9px] transition-colors"
|
||||
:class="isActive(item.view) ? 'bg-surface-2 text-accent' : 'text-fg-subtle hover:bg-surface-2 hover:text-fg'"
|
||||
:title="item.label"
|
||||
:aria-label="item.label"
|
||||
@click="ide.toggleActivity(item.view)"
|
||||
>
|
||||
<span
|
||||
v-if="isActive(item.view)"
|
||||
class="absolute top-1/2 left-0 h-5 w-0.5 -translate-y-1/2 rounded-full bg-accent"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<component :is="item.icon" :size="20" :stroke-width="1.75" />
|
||||
<!-- Exception assumee : texte quasi-noir fixe (text-zinc-950), lisible sur bg-warn en clair ET sombre. -->
|
||||
<span
|
||||
v-if="item.badge"
|
||||
class="absolute top-1 right-1 flex h-3.5 min-w-3.5 items-center justify-center rounded-full bg-amber-500 px-1 text-[9px] font-semibold text-amber-950"
|
||||
class="absolute top-1 right-1 flex h-3.5 min-w-3.5 items-center justify-center rounded-full bg-warn px-1 text-[9px] font-semibold text-zinc-950"
|
||||
>
|
||||
{{ item.badge }}
|
||||
</span>
|
||||
@@ -26,7 +32,7 @@
|
||||
type="button"
|
||||
:title="l.label"
|
||||
:aria-label="l.label"
|
||||
class="flex h-10 w-10 items-center justify-center rounded-lg text-fg-subtle transition-colors hover:bg-surface-2 hover:text-fg"
|
||||
class="flex h-10 w-10 items-center justify-center rounded-[9px] text-fg-subtle transition-colors hover:bg-surface-2 hover:text-fg"
|
||||
@click="l.onSelect"
|
||||
>
|
||||
<component :is="l.icon" :size="20" :stroke-width="1.75" />
|
||||
@@ -38,11 +44,23 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, defineAsyncComponent } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { Boxes, FolderTree, GitCompare, LifeBuoy, LogOut, Settings, SquareTerminal } from '@lucide/vue';
|
||||
import {
|
||||
Boxes,
|
||||
FolderTree,
|
||||
GitCompare,
|
||||
LifeBuoy,
|
||||
LogOut,
|
||||
Monitor,
|
||||
Moon,
|
||||
Settings,
|
||||
SquareTerminal,
|
||||
Sun,
|
||||
} from '@lucide/vue';
|
||||
import { useIdeStore, type ActivityView } from '../../stores/ide';
|
||||
import { useModalsStore } from '../../stores/modals';
|
||||
import { useNav } from '../../composables/useNav';
|
||||
import { useSession } from '../../composables/useSession';
|
||||
import { themeMode, cycleTheme } from '../../lib/theme';
|
||||
|
||||
const { t } = useI18n();
|
||||
const ide = useIdeStore();
|
||||
@@ -63,7 +81,16 @@ const items = computed(() => [
|
||||
{ view: 'groups' as ActivityView, icon: Boxes, label: t('ide.activity.groups'), badge: 0 },
|
||||
]);
|
||||
|
||||
// Icône du toggle selon le mode courant (dark → lune, light → soleil, system → écran).
|
||||
const themeIcon = computed(() => (themeMode.value === 'light' ? Sun : themeMode.value === 'system' ? Monitor : Moon));
|
||||
|
||||
const links = computed(() => [
|
||||
{
|
||||
key: 'theme',
|
||||
icon: themeIcon.value,
|
||||
label: `${t('theme.label')} : ${t(`theme.${themeMode.value}`)}`,
|
||||
onSelect: () => cycleTheme(),
|
||||
},
|
||||
{ key: 'settings', icon: Settings, label: t('nav.settings'), onSelect: () => void modals.open(SettingsOverlay) },
|
||||
{ key: 'help', icon: LifeBuoy, label: t('nav.help'), onSelect: () => void modals.open(HelpOverlay) },
|
||||
{ key: 'logout', icon: LogOut, label: t('common.logout'), onSelect: () => void logout() },
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<template>
|
||||
<div v-if="waiting.length" class="flex flex-col gap-2 border-b border-border px-2 py-2">
|
||||
<div class="flex items-center gap-1.5 px-1 text-[11px] font-semibold text-amber-400">
|
||||
<div class="flex items-center gap-1.5 px-1 text-[11px] font-semibold text-warn">
|
||||
<AlertTriangle :size="13" />
|
||||
{{ t('attention.title') }}
|
||||
<span class="ml-auto rounded-full bg-amber-500/20 px-1.5 text-[10px] text-amber-300">{{ waiting.length }}</span>
|
||||
<span class="ml-auto rounded-full bg-warn/20 px-1.5 text-[10px] text-warn">{{ waiting.length }}</span>
|
||||
</div>
|
||||
<div v-for="s in waiting" :key="s.id" class="flex flex-col gap-1">
|
||||
<button
|
||||
type="button"
|
||||
class="truncate text-left font-mono text-[11px] text-amber-300/80 transition-colors hover:text-amber-200"
|
||||
class="truncate text-left font-mono text-[11px] text-warn/80 transition-colors hover:text-warn"
|
||||
:title="s.cwd"
|
||||
@click="ide.openTerminal(s.id)"
|
||||
>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<Teleport to="body">
|
||||
<div v-if="isOpen" class="fixed inset-0 z-[60]" @pointerdown="close" @contextmenu.prevent="close">
|
||||
<div
|
||||
class="absolute min-w-44 rounded-md border border-border bg-surface-1 py-1 shadow-lg"
|
||||
class="absolute min-w-44 rounded-lg border border-border bg-surface-1 py-1 shadow-pop"
|
||||
:style="menuStyle"
|
||||
@pointerdown.stop
|
||||
>
|
||||
@@ -11,7 +11,7 @@
|
||||
:key="i"
|
||||
type="button"
|
||||
class="flex w-full items-center gap-2 px-3 py-1.5 text-left text-sm transition-colors disabled:opacity-40"
|
||||
:class="item.danger ? 'text-red-400 hover:bg-red-500/10' : 'text-fg-muted hover:bg-surface-2 hover:text-fg'"
|
||||
:class="item.danger ? 'text-danger hover:bg-danger/10' : 'text-fg-muted hover:bg-surface-2 hover:text-fg'"
|
||||
:disabled="item.disabled"
|
||||
@click="select(item)"
|
||||
>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<!-- bannière de conflit (le fichier a changé sur le disque depuis le chargement) -->
|
||||
<div
|
||||
v-if="conflict && activeTab.view === 'editor'"
|
||||
class="flex flex-wrap items-center gap-2 border-b border-amber-700/40 bg-amber-950/30 px-3 py-1.5 text-xs text-amber-200"
|
||||
class="flex flex-wrap items-center gap-2 border-b border-warn/40 bg-warn/10 px-3 py-1.5 text-xs text-warn"
|
||||
>
|
||||
<TriangleAlert :size="14" /> {{ t('editor.conflict') }}
|
||||
<div class="ml-auto flex items-center gap-2">
|
||||
@@ -31,7 +31,7 @@
|
||||
<BaseButton size="sm" variant="danger" :loading="saving" @click="overwrite">{{ t('editor.overwrite') }}</BaseButton>
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="loadError && activeTab.view === 'editor'" class="px-3 py-1 text-xs text-rose-400">{{ loadError }}</p>
|
||||
<p v-if="loadError && activeTab.view === 'editor'" class="px-3 py-1 text-xs text-danger">{{ loadError }}</p>
|
||||
|
||||
<div class="relative min-h-0 flex-1">
|
||||
<div v-show="activeTab.view === 'editor'" ref="host" class="h-full min-h-0" />
|
||||
@@ -64,6 +64,7 @@ import { useIdeStore, type EditorTab } from '../../stores/ide';
|
||||
import { gitApi } from '../../lib/git-api';
|
||||
import { ApiError } from '../../lib/api';
|
||||
import { loadMonaco } from '../../composables/useMonaco';
|
||||
import { resolvedTheme } from '../../lib/theme';
|
||||
import EditorTabs from './EditorTabs.vue';
|
||||
import DiffViewer from '../workspace/DiffViewer.vue';
|
||||
import SegmentedControl from '../ui/SegmentedControl.vue';
|
||||
@@ -109,14 +110,19 @@ onMounted(async () => {
|
||||
if (disposed || !host.value) return;
|
||||
editor.value = monaco.editor.create(host.value, {
|
||||
value: '',
|
||||
theme: 'arboretum-dark',
|
||||
theme: resolvedTheme.value === 'light' ? 'arboretum-light' : 'arboretum-dark',
|
||||
automaticLayout: true,
|
||||
minimap: { enabled: false },
|
||||
wordWrap: 'on',
|
||||
fontSize: 13,
|
||||
fontFamily: "'JetBrains Mono Variable', ui-monospace, 'Fira Code', Menlo, Consolas, monospace",
|
||||
scrollBeyondLastLine: false,
|
||||
tabSize: 2,
|
||||
});
|
||||
// Re-mesure après chargement de la fonte web (sinon curseur/colonnes décalés).
|
||||
void document.fonts?.ready.then(() => {
|
||||
if (!disposed) monaco?.editor.remeasureFonts();
|
||||
});
|
||||
editor.value.onDidChangeModelContent(() => {
|
||||
if (!shownTabId) return;
|
||||
const entry = entries.get(shownTabId);
|
||||
@@ -227,6 +233,11 @@ watch(
|
||||
},
|
||||
);
|
||||
|
||||
// bascule de thème -> réapplique le thème Monaco (setTheme est global et idempotent).
|
||||
watch(resolvedTheme, (r) => {
|
||||
monaco?.editor.setTheme(r === 'light' ? 'arboretum-light' : 'arboretum-dark');
|
||||
});
|
||||
|
||||
// onglets fermés -> dispose leurs modèles et view states (évite les fuites mémoire).
|
||||
watch(
|
||||
() => ide.editorTabs.map((tb) => tb.id),
|
||||
|
||||
@@ -40,9 +40,9 @@ const pendingCloseId = ref<string | null>(null);
|
||||
const basename = (path: string): string => path.split('/').pop() ?? path;
|
||||
|
||||
function closeButtonClass(id: string): string {
|
||||
if (pendingCloseId.value === id) return 'text-amber-400 opacity-100';
|
||||
if (pendingCloseId.value === id) return 'text-warn opacity-100';
|
||||
// dirty : point visible en permanence ; propre : croix visible au survol de l'onglet.
|
||||
return ide.isDirty(id) ? 'text-amber-400 opacity-100 group-hover:text-fg' : 'opacity-0 group-hover:opacity-100';
|
||||
return ide.isDirty(id) ? 'text-warn opacity-100 group-hover:text-fg' : 'opacity-0 group-hover:opacity-100';
|
||||
}
|
||||
|
||||
function requestClose(id: string): void {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="flex h-full min-h-0 flex-col">
|
||||
<div class="flex items-center gap-1 px-3 py-2 text-[11px] font-semibold tracking-wide text-fg-subtle uppercase">
|
||||
<div class="label-mono flex items-center gap-1 px-3 py-2">
|
||||
{{ t('ide.activity.groups') }}
|
||||
<button type="button" class="ml-auto rounded p-0.5 hover:bg-surface-2 hover:text-fg" :title="t('ide.menu.newGroup')" @click="openNewGroup">
|
||||
<Plus :size="14" />
|
||||
@@ -17,7 +17,12 @@
|
||||
>
|
||||
<button type="button" class="flex min-w-0 flex-1 items-center gap-1.5 px-2 py-1 text-left" @click="toggleExpand(g.id)">
|
||||
<component :is="isExpanded(g.id) ? ChevronDown : ChevronRight" :size="14" class="shrink-0 text-fg-subtle" />
|
||||
<Boxes :size="13" class="shrink-0 text-fg-subtle" />
|
||||
<Boxes
|
||||
:size="13"
|
||||
class="shrink-0"
|
||||
:class="g.color ? '' : 'text-fg-subtle'"
|
||||
:style="g.color ? { color: g.color } : undefined"
|
||||
/>
|
||||
<span class="truncate">{{ g.label }}</span>
|
||||
<span class="ml-auto shrink-0 pl-1 text-[10px] text-fg-subtle">{{ t('groups.repoCount', groups.reposInGroup(g.id).length) }}</span>
|
||||
</button>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="flex h-full min-h-0 flex-col">
|
||||
<div class="flex items-center gap-1 px-3 py-2 text-[11px] font-semibold tracking-wide text-fg-subtle uppercase">
|
||||
<div class="label-mono flex items-center gap-1 px-3 py-2">
|
||||
{{ t('ide.projects') }}
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
<component :is="isWtExpanded(wt) ? ChevronDown : ChevronRight" :size="14" class="shrink-0 text-fg-subtle" />
|
||||
<component :is="wt.isMain ? Home : GitBranch" :size="12" class="shrink-0 text-fg-subtle" />
|
||||
<span class="truncate font-mono">{{ wt.branch ?? wt.head.slice(0, 7) }}</span>
|
||||
<span v-if="isDirty(wt)" class="ml-auto shrink-0 pl-1 text-amber-400" :title="t('worktrees.dirty', { n: wt.git.dirtyCount })">●</span>
|
||||
<span v-if="isDirty(wt)" class="ml-auto shrink-0 pl-1 text-warn" :title="t('worktrees.dirty', { n: wt.git.dirtyCount })">●</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="flex h-full min-h-0 flex-col">
|
||||
<div class="flex items-center gap-1 px-3 py-2 text-[11px] font-semibold tracking-wide text-fg-subtle uppercase">
|
||||
<div class="label-mono flex items-center gap-1 px-3 py-2">
|
||||
{{ t('ide.activity.sessions') }}
|
||||
<button type="button" class="ml-auto rounded p-0.5 hover:bg-surface-2 hover:text-fg" :title="t('sessions.newSession')" @click="openAddMenu">
|
||||
<Plus :size="14" />
|
||||
@@ -24,8 +24,8 @@
|
||||
<button type="button" class="flex min-w-0 flex-1 items-center gap-1.5 text-left" @click="onRowClick($event, s)">
|
||||
<SessionStateBadge :session="s" />
|
||||
<span class="truncate font-mono" :class="s.live ? '' : 'text-fg-subtle'">{{ sessionLabel(s, worktrees) }}</span>
|
||||
<BaseBadge v-if="s.source === 'discovered'" tone="sky" class="shrink-0">{{ t('sessions.sourceDiscovered') }}</BaseBadge>
|
||||
<BaseBadge v-if="s.archived" tone="amber" class="shrink-0">{{ t('sessions.archivedBadge') }}</BaseBadge>
|
||||
<BaseBadge v-if="s.source === 'discovered'" tone="info" class="shrink-0">{{ t('sessions.sourceDiscovered') }}</BaseBadge>
|
||||
<BaseBadge v-if="s.archived" tone="warn" class="shrink-0">{{ t('sessions.archivedBadge') }}</BaseBadge>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -42,10 +42,10 @@ const activeWt = computed(() => {
|
||||
const wsStatus = computed(() => wsClient.status.value);
|
||||
const wsDot = computed(() =>
|
||||
wsStatus.value === 'open'
|
||||
? 'bg-emerald-500'
|
||||
? 'bg-accent'
|
||||
: wsStatus.value === 'reconnecting' || wsStatus.value === 'connecting'
|
||||
? 'bg-amber-500'
|
||||
: 'bg-zinc-600',
|
||||
? 'bg-warn'
|
||||
: 'bg-fg-subtle',
|
||||
);
|
||||
const dockCount = computed(() => ide.dockSessionIds.length);
|
||||
</script>
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
<template>
|
||||
<div class="flex h-[var(--ide-tab-h)] shrink-0 items-stretch overflow-x-auto border-b border-border bg-surface-1">
|
||||
<div class="flex shrink-0 items-center gap-1 px-2 text-[11px] font-semibold tracking-wide text-fg-subtle uppercase">
|
||||
<SquareTerminal :size="12" /> {{ t('ide.terminals') }}
|
||||
<div class="flex shrink-0 items-center gap-1.5 px-2 label-mono">
|
||||
<span class="flex items-center gap-1" aria-hidden="true">
|
||||
<span class="h-2 w-2 rounded-full bg-danger" />
|
||||
<span class="h-2 w-2 rounded-full bg-warn" />
|
||||
<span class="h-2 w-2 rounded-full bg-accent" />
|
||||
</span>
|
||||
{{ t('ide.terminals') }}
|
||||
</div>
|
||||
|
||||
<div
|
||||
@@ -38,7 +43,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { ChevronDown, SquareTerminal, X } from '@lucide/vue';
|
||||
import { ChevronDown, X } from '@lucide/vue';
|
||||
import type { SessionSummary } from '@arboretum/shared';
|
||||
import { useIdeStore } from '../../stores/ide';
|
||||
import { useSessionsStore } from '../../stores/sessions';
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="flex w-full max-w-sm flex-col gap-3 rounded-lg border border-border bg-surface-1 p-4">
|
||||
<h2 class="font-semibold text-fg">{{ title }}</h2>
|
||||
<p v-if="message" class="whitespace-pre-line text-sm text-fg-muted">{{ message }}</p>
|
||||
<p v-if="errorMsg" class="text-xs text-red-400">{{ errorMsg }}</p>
|
||||
<p v-if="errorMsg" class="text-xs text-danger">{{ errorMsg }}</p>
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<button type="button" class="btn text-xs" :disabled="busy" @click="emit('close')">
|
||||
{{ cancelLabel ?? t('common.cancel') }}
|
||||
|
||||
@@ -12,6 +12,11 @@
|
||||
<input v-model="label" class="input" :placeholder="t('groups.namePlaceholder')" required :disabled="busy" />
|
||||
</label>
|
||||
|
||||
<div class="flex flex-col gap-1.5 text-xs text-fg-muted">
|
||||
{{ t('groups.colorLabel') }}
|
||||
<ColorSwatchPicker v-model="color" :none-label="t('groups.colorNone')" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1 text-xs text-fg-muted">
|
||||
{{ t('groups.reposLabel') }}
|
||||
<p v-if="worktrees.repos.length === 0" class="text-fg-subtle">{{ t('groups.noReposRegistered') }}</p>
|
||||
@@ -27,7 +32,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p v-if="errorMsg" class="text-xs text-red-400">{{ errorMsg }}</p>
|
||||
<p v-if="errorMsg" class="text-xs text-danger">{{ errorMsg }}</p>
|
||||
<div>
|
||||
<button type="submit" class="btn-primary" :disabled="busy || label.trim() === ''">
|
||||
{{ busy ? t('common.working') : t('groups.new') }}
|
||||
@@ -46,6 +51,7 @@ import { useI18n } from 'vue-i18n';
|
||||
import { useGroupsStore } from '../../../stores/groups';
|
||||
import { useWorktreesStore } from '../../../stores/worktrees';
|
||||
import { useToastsStore } from '../../../stores/toasts';
|
||||
import ColorSwatchPicker from '../../ui/ColorSwatchPicker.vue';
|
||||
|
||||
const emit = defineEmits<{ close: [] }>();
|
||||
|
||||
@@ -55,6 +61,7 @@ const worktrees = useWorktreesStore();
|
||||
const toasts = useToastsStore();
|
||||
|
||||
const label = ref('');
|
||||
const color = ref<string | null>(null);
|
||||
const selectedRepoIds = ref<string[]>([]);
|
||||
const busy = ref(false);
|
||||
const errorMsg = ref<string | null>(null);
|
||||
@@ -64,7 +71,11 @@ async function onSubmit(): Promise<void> {
|
||||
busy.value = true;
|
||||
errorMsg.value = null;
|
||||
try {
|
||||
await groups.createGroup({ label: label.value.trim(), repoIds: [...selectedRepoIds.value] });
|
||||
await groups.createGroup({
|
||||
label: label.value.trim(),
|
||||
repoIds: [...selectedRepoIds.value],
|
||||
...(color.value ? { color: color.value } : {}),
|
||||
});
|
||||
toasts.success(t('toast.groupCreated'));
|
||||
emit('close');
|
||||
} catch (err) {
|
||||
|
||||
@@ -6,6 +6,11 @@
|
||||
<button class="btn ml-auto text-xs" @click="emit('close')">{{ t('common.close') }}</button>
|
||||
</header>
|
||||
|
||||
<div class="flex flex-col gap-1.5 text-xs text-fg-muted">
|
||||
{{ t('groups.colorLabel') }}
|
||||
<ColorSwatchPicker :model-value="group?.color ?? null" :none-label="t('groups.colorNone')" @update:model-value="setColor" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1 text-xs text-fg-muted">
|
||||
{{ t('groups.reposLabel') }}
|
||||
<p v-if="worktrees.repos.length === 0" class="text-fg-subtle">{{ t('groups.noReposRegistered') }}</p>
|
||||
@@ -20,7 +25,7 @@
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="errorMsg" class="text-xs text-red-400">{{ errorMsg }}</p>
|
||||
<p v-if="errorMsg" class="text-xs text-danger">{{ errorMsg }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -33,6 +38,7 @@ import { useI18n } from 'vue-i18n';
|
||||
import { useGroupsStore } from '../../../stores/groups';
|
||||
import { useWorktreesStore } from '../../../stores/worktrees';
|
||||
import { useToastsStore } from '../../../stores/toasts';
|
||||
import ColorSwatchPicker from '../../ui/ColorSwatchPicker.vue';
|
||||
|
||||
const props = defineProps<{ groupId: string }>();
|
||||
const emit = defineEmits<{ close: [] }>();
|
||||
@@ -48,6 +54,20 @@ const errorMsg = ref<string | null>(null);
|
||||
const group = computed(() => groupsStore.byId(props.groupId));
|
||||
const repoIds = computed<string[]>(() => group.value?.repoIds ?? []);
|
||||
|
||||
async function setColor(c: string | null): Promise<void> {
|
||||
if (busy.value) return;
|
||||
busy.value = true;
|
||||
errorMsg.value = null;
|
||||
try {
|
||||
await groupsStore.updateGroup(props.groupId, { color: c });
|
||||
toasts.success(t('toast.groupUpdated'));
|
||||
} catch (err) {
|
||||
errorMsg.value = err instanceof Error ? err.message : String(err);
|
||||
} finally {
|
||||
busy.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function toggle(repoId: string): Promise<void> {
|
||||
if (busy.value) return;
|
||||
busy.value = true;
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<p v-if="errorMsg" class="text-xs text-red-400">{{ errorMsg }}</p>
|
||||
<p v-if="errorMsg" class="text-xs text-danger">{{ errorMsg }}</p>
|
||||
<div>
|
||||
<button type="submit" class="btn-primary" :disabled="creating || cwd.trim() === ''">
|
||||
{{ creating ? t('common.working') : t('sessions.newSession') }}
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<p v-if="error" class="text-xs text-red-400">{{ error }}</p>
|
||||
<p v-if="error" class="text-xs text-danger">{{ error }}</p>
|
||||
<div>
|
||||
<button type="submit" class="btn-primary" :disabled="busy || !canSubmit">
|
||||
{{ busy ? t('common.working') : mode === 'worktree' ? t('worktrees.create') : t('worktrees.start') }}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="fixed inset-0 z-50 flex items-start justify-center overflow-y-auto bg-black/60 p-4 sm:p-8" @click.self="emit('close')">
|
||||
<div class="relative w-full max-w-3xl rounded-lg border border-border bg-surface-0 p-4 shadow-xl">
|
||||
<div class="relative w-full max-w-3xl rounded-lg border border-border bg-surface-0 p-4 shadow-pop">
|
||||
<button
|
||||
type="button"
|
||||
class="absolute top-3 right-3 z-10 rounded p-1 text-fg-subtle hover:bg-surface-2 hover:text-fg"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="fixed inset-0 z-50 flex items-start justify-center overflow-y-auto bg-black/60 p-4 sm:p-8" @click.self="emit('close')">
|
||||
<div class="relative w-full max-w-3xl rounded-lg border border-border bg-surface-0 p-4 shadow-xl">
|
||||
<div class="relative w-full max-w-3xl rounded-lg border border-border bg-surface-0 p-4 shadow-pop">
|
||||
<button
|
||||
type="button"
|
||||
class="absolute top-3 right-3 z-10 rounded p-1 text-fg-subtle hover:bg-surface-2 hover:text-fg"
|
||||
|
||||
Reference in New Issue
Block a user