feat(web): coquille IDE + arbre de projets unifié (route /ide)
components/ide/ : IdeShell (grille barre d'activité + panneau gauche + zone centrale + dock + barre de statut, splitters persistés, réconciliation des ressources ouvertes contre les données live), ActivityBar (Explorer/Git/Sessions/Groupes + Réglages/Aide), PrimarySidebar, ProjectTree + ProjectTreeNode (arbre unique repos -> worktrees -> sessions, fichiers embarqués via FileTree en mode sans chrome, dépliage paresseux), StatusBar. Route /ide (layout 'ide'), entrée de nav « IDE », namespace i18n ide (EN/FR). Clics de l'arbre pilotent le store (contexte worktree, ouverture de fichier/terminal). Centre et dock en placeholders (remplis en B4/B5). FileTree gagne un mode embedded. 427 tests.
This commit is contained in:
69
packages/web/src/components/ide/ProjectTree.vue
Normal file
69
packages/web/src/components/ide/ProjectTree.vue
Normal file
@@ -0,0 +1,69 @@
|
||||
<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">
|
||||
{{ t('ide.projects') }}
|
||||
<button
|
||||
type="button"
|
||||
class="ml-auto rounded p-0.5 hover:bg-surface-2 hover:text-fg"
|
||||
:title="t('repos.scan')"
|
||||
:disabled="scanning"
|
||||
@click="onScan"
|
||||
>
|
||||
<ScanSearch :size="14" />
|
||||
</button>
|
||||
<button type="button" class="rounded p-0.5 hover:bg-surface-2 hover:text-fg" :title="t('ide.addProject')" @click="showPicker = true">
|
||||
<Plus :size="14" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="min-h-0 flex-1 overflow-auto pb-2">
|
||||
<p v-if="repos.length === 0" class="px-3 py-2 text-xs text-fg-subtle">{{ t('ide.noProjects') }}</p>
|
||||
<ProjectTreeNode v-for="repo in repos" :key="repo.id" :repo="repo" />
|
||||
</div>
|
||||
|
||||
<DirectoryPicker v-if="showPicker" mode="repo" :initial-path="''" @select="onPick" @close="showPicker = false" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { Plus, ScanSearch } from '@lucide/vue';
|
||||
import { useWorktreesStore } from '../../stores/worktrees';
|
||||
import { useToastsStore } from '../../stores/toasts';
|
||||
import ProjectTreeNode from './ProjectTreeNode.vue';
|
||||
import DirectoryPicker from '../DirectoryPicker.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const worktrees = useWorktreesStore();
|
||||
const toasts = useToastsStore();
|
||||
|
||||
const showPicker = ref(false);
|
||||
const scanning = ref(false);
|
||||
|
||||
const repos = computed(() =>
|
||||
[...worktrees.repos].filter((r) => !r.hidden).sort((a, b) => a.label.localeCompare(b.label)),
|
||||
);
|
||||
|
||||
async function onScan(): Promise<void> {
|
||||
scanning.value = true;
|
||||
try {
|
||||
const res = await worktrees.discover();
|
||||
toasts.success(t('repos.scanResult', res.added));
|
||||
} catch (err) {
|
||||
toasts.error(err);
|
||||
} finally {
|
||||
scanning.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function onPick(path: string): Promise<void> {
|
||||
showPicker.value = false;
|
||||
try {
|
||||
await worktrees.addRepo(path);
|
||||
toasts.success(t('toast.repoAdded'));
|
||||
} catch (err) {
|
||||
toasts.error(err);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user