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:
67
packages/web/src/components/ide/IdeShell.vue
Normal file
67
packages/web/src/components/ide/IdeShell.vue
Normal file
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<div class="flex h-dvh min-h-0 flex-col bg-surface-0 text-fg">
|
||||
<div class="flex min-h-0 flex-1">
|
||||
<ActivityBar />
|
||||
<template v-if="ide.leftVisible">
|
||||
<PrimarySidebar />
|
||||
<PanelSplitter v-model="ide.leftWidth" axis="x" :min="200" :max="560" />
|
||||
</template>
|
||||
|
||||
<div class="flex min-w-0 flex-1 flex-col">
|
||||
<!-- centre : éditeur à onglets (livré en B4). Placeholder pour l'instant. -->
|
||||
<main class="min-h-0 flex-1 overflow-hidden">
|
||||
<EmptyState :icon="FileCode" :title="t('ide.noFileOpen')" :hint="t('ide.noFileHint')" class="m-6" />
|
||||
</main>
|
||||
|
||||
<!-- dock terminaux (livré en B5). Placeholder pour l'instant. -->
|
||||
<template v-if="ide.bottomVisible">
|
||||
<PanelSplitter v-model="ide.bottomHeight" axis="y" :min="120" :max="640" invert />
|
||||
<section class="shrink-0 overflow-hidden border-t border-border" :style="{ height: `${ide.bottomHeight}px` }">
|
||||
<EmptyState :icon="SquareTerminal" :title="t('ide.noTerminal')" :hint="t('ide.noTerminalHint')" class="m-6" />
|
||||
</section>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<StatusBar />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// Coquille de la vue IDE (layout 'ide', plein écran). Assemble barre d'activité, panneau gauche,
|
||||
// zone centrale (éditeur), dock terminaux et barre de statut. Propriétaire de la réconciliation
|
||||
// des ressources ouvertes contre les données live (worktrees / sessions).
|
||||
import { watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { FileCode, SquareTerminal } from '@lucide/vue';
|
||||
import { useIdeStore, wtKey } from '../../stores/ide';
|
||||
import { useWorktreesStore } from '../../stores/worktrees';
|
||||
import { useSessionsStore } from '../../stores/sessions';
|
||||
import ActivityBar from './ActivityBar.vue';
|
||||
import PrimarySidebar from './PrimarySidebar.vue';
|
||||
import PanelSplitter from './PanelSplitter.vue';
|
||||
import StatusBar from './StatusBar.vue';
|
||||
import EmptyState from '../ui/EmptyState.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const ide = useIdeStore();
|
||||
const worktrees = useWorktreesStore();
|
||||
const sessions = useSessionsStore();
|
||||
|
||||
function prune(): void {
|
||||
const live = new Set(sessions.sessions.filter((s) => s.live).map((s) => s.id));
|
||||
const known = new Set(worktrees.worktrees.map((w) => wtKey(w.repoId, w.path)));
|
||||
ide.pruneDeadResources(live, known);
|
||||
}
|
||||
|
||||
// On ne réconcilie qu'une fois les données prêtes (fetch initial terminé) pour ne pas fermer les
|
||||
// onglets/terminaux persistés avant leur chargement. Puis à chaque ajout/retrait de worktree ou
|
||||
// de session.
|
||||
watch(
|
||||
[() => worktrees.loading, () => sessions.sessions.length, () => worktrees.worktrees.length],
|
||||
() => {
|
||||
if (!worktrees.loading) prune();
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
Reference in New Issue
Block a user