feat(web): zone éditeur à onglets (Monaco multi-modèles + diff)
components/ide/EditorArea.vue : UNE instance Monaco partagée avec cache de modèles par onglet (préserve contenu, undo et curseur au changement d'onglet ; view states restaurés), thème arboretum-dark, save + conflit STALE_FILE + reload/overwrite, dispose des modèles à la fermeture d'onglet et au démontage. Onglets diff via DiffViewer. components/ide/EditorTabs.vue : bandeau d'onglets multi-projets, point de modification, fermeture immédiate si propre et garde à deux clics si modifié. Bascule éditeur/diff par onglet. Branché dans IdeShell (centre). 427 tests.
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<div class="flex h-[var(--ide-tab-h)] shrink-0 items-stretch overflow-x-auto border-b border-border bg-surface-1">
|
||||
<div
|
||||
v-for="tab in ide.editorTabs"
|
||||
:key="tab.id"
|
||||
class="group flex cursor-pointer items-center gap-1.5 border-r border-border px-3 text-xs select-none"
|
||||
:class="tab.id === ide.activeTabId ? 'bg-surface-0 text-fg' : 'text-fg-muted hover:bg-surface-2/50'"
|
||||
:title="tab.file"
|
||||
@click="ide.setActiveTab(tab.id)"
|
||||
@mousedown.middle.prevent="requestClose(tab.id)"
|
||||
>
|
||||
<FileCode :size="12" class="shrink-0 text-fg-subtle" />
|
||||
<span class="truncate font-mono">{{ basename(tab.file) }}</span>
|
||||
<span v-if="tab.view === 'diff'" class="text-[9px] tracking-wide text-fg-subtle uppercase">diff</span>
|
||||
<button
|
||||
type="button"
|
||||
class="ml-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded hover:bg-surface-3"
|
||||
:class="closeButtonClass(tab.id)"
|
||||
:title="pendingCloseId === tab.id ? t('editor.unsaved') : t('common.close')"
|
||||
@click.stop="requestClose(tab.id)"
|
||||
>
|
||||
<component :is="pendingCloseId === tab.id ? AlertTriangle : ide.isDirty(tab.id) ? Dot : X" :size="pendingCloseId === tab.id ? 12 : 14" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// Bandeau d'onglets de l'éditeur. Fermeture immédiate si propre ; garde à deux clics si modifié
|
||||
// (cohérent avec les autres confirmations de l'app), pour ne pas perdre des changements non sauvés.
|
||||
import { ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { AlertTriangle, Dot, FileCode, X } from '@lucide/vue';
|
||||
import { useIdeStore } from '../../stores/ide';
|
||||
|
||||
const { t } = useI18n();
|
||||
const ide = useIdeStore();
|
||||
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';
|
||||
// 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';
|
||||
}
|
||||
|
||||
function requestClose(id: string): void {
|
||||
if (ide.isDirty(id) && pendingCloseId.value !== id) {
|
||||
pendingCloseId.value = id; // premier clic sur un onglet modifié : demande confirmation
|
||||
return;
|
||||
}
|
||||
pendingCloseId.value = null;
|
||||
ide.closeTab(id);
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user