update
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, watch, type Ref } from 'vue'; // Removed reactive as it wasn't used after style removal
|
import { ref, computed, watch, type Ref } from 'vue'; // Re-added computed
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useLayoutStore, type LayoutNode, type PaneName } from '../stores/layout.store';
|
import { useLayoutStore, type LayoutNode, type PaneName } from '../stores/layout.store';
|
||||||
import draggable from 'vuedraggable';
|
import draggable from 'vuedraggable';
|
||||||
@@ -24,6 +24,7 @@ const layoutStore = useLayoutStore();
|
|||||||
const localLayoutTree: Ref<LayoutNode | null> = ref(null);
|
const localLayoutTree: Ref<LayoutNode | null> = ref(null);
|
||||||
const hasChanges = ref(false);
|
const hasChanges = ref(false);
|
||||||
const localSidebarPanes: Ref<{ left: PaneName[], right: PaneName[] }> = ref({ left: [], right: [] });
|
const localSidebarPanes: Ref<{ left: PaneName[], right: PaneName[] }> = ref({ left: [], right: [] });
|
||||||
|
const localAvailablePanes: Ref<PaneName[]> = ref([]); // New state for available panes
|
||||||
|
|
||||||
// --- Dialog State ---
|
// --- Dialog State ---
|
||||||
const dialogRef = ref<HTMLElement | null>(null);
|
const dialogRef = ref<HTMLElement | null>(null);
|
||||||
@@ -43,11 +44,18 @@ watch(() => props.isVisible, (newValue) => {
|
|||||||
} else {
|
} else {
|
||||||
localSidebarPanes.value = { left: [], right: [] }; // Default
|
localSidebarPanes.value = { left: [], right: [] }; // Default
|
||||||
}
|
}
|
||||||
|
// Initialize available panes based on store's possible panes
|
||||||
|
localAvailablePanes.value = [...layoutStore.allPossiblePanes];
|
||||||
|
// Remove panes already in use (initial setup)
|
||||||
|
const initialUsed = getAllLocalUsedPaneNames(localLayoutTree.value, localSidebarPanes.value);
|
||||||
|
localAvailablePanes.value = localAvailablePanes.value.filter(pane => !initialUsed.has(pane));
|
||||||
|
|
||||||
hasChanges.value = false; // Reset changes flag on open
|
hasChanges.value = false; // Reset changes flag on open
|
||||||
console.log('[LayoutConfigurator] Dialog opened, loaded layout and sidebar config.');
|
console.log('[LayoutConfigurator] Dialog opened, loaded layout, sidebar config, and available panes.');
|
||||||
} else {
|
} else {
|
||||||
localLayoutTree.value = null;
|
localLayoutTree.value = null; // Clear main layout
|
||||||
localSidebarPanes.value = { left: [], right: [] };
|
localSidebarPanes.value = { left: [], right: [] }; // Clear sidebars
|
||||||
|
localAvailablePanes.value = []; // Clear available panes
|
||||||
console.log('[LayoutConfigurator] Dialog closed.');
|
console.log('[LayoutConfigurator] Dialog closed.');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -102,16 +110,33 @@ function getAllLocalUsedPaneNames(mainNode: LayoutNode | null, sidebars: { left:
|
|||||||
return usedNames;
|
return usedNames;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper to add pane back to available list if not present
|
||||||
|
function addPaneToAvailableList(paneName: PaneName) {
|
||||||
|
if (!localAvailablePanes.value.includes(paneName) && layoutStore.allPossiblePanes.includes(paneName)) {
|
||||||
|
// Maintain original order if possible, otherwise just add
|
||||||
|
// Find the original index in allPossiblePanes
|
||||||
|
const originalIndex = layoutStore.allPossiblePanes.indexOf(paneName);
|
||||||
|
let inserted = false;
|
||||||
|
// Try to insert based on original order relative to existing available panes
|
||||||
|
for (let i = 0; i < localAvailablePanes.value.length; i++) {
|
||||||
|
const currentAvailablePane = localAvailablePanes.value[i];
|
||||||
|
const currentOriginalIndex = layoutStore.allPossiblePanes.indexOf(currentAvailablePane);
|
||||||
|
if (originalIndex < currentOriginalIndex) {
|
||||||
|
localAvailablePanes.value.splice(i, 0, paneName);
|
||||||
|
inserted = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!inserted) {
|
||||||
|
localAvailablePanes.value.push(paneName); // Add to end if no suitable spot found
|
||||||
|
}
|
||||||
|
console.log(`[LayoutConfigurator] Added '${paneName}' back to available panes.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- Computed ---
|
// --- Computed ---
|
||||||
const allPossiblePanes = computed(() => layoutStore.allPossiblePanes);
|
|
||||||
|
|
||||||
const configuratorAvailablePanes = computed(() => {
|
|
||||||
const localUsed = getAllLocalUsedPaneNames(localLayoutTree.value, localSidebarPanes.value);
|
|
||||||
return allPossiblePanes.value.filter(pane => !localUsed.has(pane));
|
|
||||||
});
|
|
||||||
|
|
||||||
// Panel Labels for display
|
// Panel Labels for display
|
||||||
const paneLabels = computed(() => ({
|
const paneLabels = computed(() => ({ // Assuming labels might depend on i18n
|
||||||
connections: t('layout.pane.connections', '连接列表'),
|
connections: t('layout.pane.connections', '连接列表'),
|
||||||
terminal: t('layout.pane.terminal', '终端'),
|
terminal: t('layout.pane.terminal', '终端'),
|
||||||
commandBar: t('layout.pane.commandBar', '命令栏'),
|
commandBar: t('layout.pane.commandBar', '命令栏'),
|
||||||
@@ -161,11 +186,15 @@ const resetToDefault = () => {
|
|||||||
const defaultLayout = layoutStore.getSystemDefaultLayout();
|
const defaultLayout = layoutStore.getSystemDefaultLayout();
|
||||||
localLayoutTree.value = JSON.parse(JSON.stringify(defaultLayout));
|
localLayoutTree.value = JSON.parse(JSON.stringify(defaultLayout));
|
||||||
|
|
||||||
// Reset sidebar config (assuming store provides a default or empty)
|
// Reset sidebar config
|
||||||
const defaultSidebarPanes = layoutStore.getSystemDefaultSidebarPanes(); // Get default from store
|
const defaultSidebarPanes = layoutStore.getSystemDefaultSidebarPanes();
|
||||||
localSidebarPanes.value = JSON.parse(JSON.stringify(defaultSidebarPanes));
|
localSidebarPanes.value = JSON.parse(JSON.stringify(defaultSidebarPanes));
|
||||||
console.log('[LayoutConfigurator] Reset to default layout and sidebar panes.');
|
|
||||||
|
|
||||||
|
// Reset available panes
|
||||||
|
const defaultUsed = getAllLocalUsedPaneNames(localLayoutTree.value, localSidebarPanes.value);
|
||||||
|
localAvailablePanes.value = [...layoutStore.allPossiblePanes].filter(pane => !defaultUsed.has(pane));
|
||||||
|
|
||||||
|
console.log('[LayoutConfigurator] Reset to default layout, sidebar panes, and available panes.');
|
||||||
hasChanges.value = true; // Mark as changed after reset
|
hasChanges.value = true; // Mark as changed after reset
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -190,34 +219,64 @@ const handleNodeUpdate = (updatedNode: LayoutNode) => {
|
|||||||
// No need to set hasChanges here, the watcher on localLayoutTree handles it
|
// No need to set hasChanges here, the watcher on localLayoutTree handles it
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle remove requests from LayoutNodeEditor (for main layout)
|
// Handle remove requests from LayoutNodeEditor (for main layout) - CORRECTED VERSION
|
||||||
function findAndRemoveNode(node: LayoutNode | null, parentNodeId: string | undefined, nodeIndex: number): LayoutNode | null {
|
function findAndRemoveNode(node: LayoutNode | null, parentNodeId: string | undefined, nodeIndex: number): LayoutNode | null {
|
||||||
if (!node) return null;
|
if (!node) return null;
|
||||||
if (node.id === parentNodeId && node.type === 'container' && node.children && node.children[nodeIndex]) {
|
|
||||||
const updatedChildren = [...node.children];
|
// Case 1: Found the parent container
|
||||||
updatedChildren.splice(nodeIndex, 1);
|
if (node.id === parentNodeId && node.type === 'container' && node.children && node.children[nodeIndex]) {
|
||||||
console.log(`[LayoutConfigurator] Removed node at index ${nodeIndex} from parent ${parentNodeId}`);
|
const updatedChildren = [...node.children];
|
||||||
return { ...node, children: updatedChildren };
|
const removedNode = updatedChildren.splice(nodeIndex, 1)[0]; // Remove and get the node
|
||||||
}
|
console.log(`[LayoutConfigurator] Removing node at index ${nodeIndex} from parent ${parentNodeId}`);
|
||||||
if (node.type === 'container' && node.children) {
|
|
||||||
const updatedChildren = node.children.map(child => findAndRemoveNode(child, parentNodeId, nodeIndex));
|
// Add the pane back to available list if it was a pane node
|
||||||
if (updatedChildren.some((child, index) => child !== node.children![index])) {
|
if (removedNode.type === 'pane' && removedNode.component) {
|
||||||
return { ...node, children: updatedChildren.filter(Boolean) as LayoutNode[] };
|
addPaneToAvailableList(removedNode.component);
|
||||||
|
}
|
||||||
|
// If the removed node was a container, recursively add its children back
|
||||||
|
else if (removedNode.type === 'container') { // Check type directly
|
||||||
|
function addPanesFromContainer(containerNode: LayoutNode | null) { // Accept null
|
||||||
|
if (!containerNode || !containerNode.children) return; // Guard against null/undefined children
|
||||||
|
containerNode.children.forEach(child => {
|
||||||
|
if (child.type === 'pane' && child.component) {
|
||||||
|
addPaneToAvailableList(child.component);
|
||||||
|
} else if (child.type === 'container') {
|
||||||
|
addPanesFromContainer(child); // Recurse into nested containers
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
addPanesFromContainer(removedNode);
|
||||||
|
}
|
||||||
|
return { ...node, children: updatedChildren };
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return node;
|
// Case 2: Traverse deeper
|
||||||
|
if (node.type === 'container' && node.children) {
|
||||||
|
const updatedChildren = node.children.map(child => findAndRemoveNode(child, parentNodeId, nodeIndex));
|
||||||
|
// Check if any child subtree was modified
|
||||||
|
if (updatedChildren.some((child, index) => child !== node.children![index])) {
|
||||||
|
return { ...node, children: updatedChildren.filter(Boolean) as LayoutNode[] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case 3: No match or not a container
|
||||||
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CORRECTED handleNodeRemove
|
||||||
const handleNodeRemove = (payload: { parentNodeId: string | undefined; nodeIndex: number }) => {
|
const handleNodeRemove = (payload: { parentNodeId: string | undefined; nodeIndex: number }) => {
|
||||||
console.log('[LayoutConfigurator] Received node remove request:', payload);
|
console.log('[LayoutConfigurator] Received node remove request:', payload);
|
||||||
if (payload.parentNodeId === undefined && payload.nodeIndex === 0) {
|
if (payload.parentNodeId === undefined && payload.nodeIndex === 0) {
|
||||||
if (confirm('确定要清空整个布局吗?')) {
|
if (confirm(t('layoutConfigurator.confirmClearLayout', '确定要清空整个布局吗?所有面板将返回可用列表。'))) {
|
||||||
|
// Add all panes from the tree back to available list before clearing
|
||||||
|
const usedInTree = getMainLayoutUsedPaneNames(localLayoutTree.value); // Single declaration
|
||||||
|
usedInTree.forEach(paneName => addPaneToAvailableList(paneName)); // Correctly call the helper
|
||||||
|
// Clear the tree
|
||||||
localLayoutTree.value = null;
|
localLayoutTree.value = null;
|
||||||
// No need to set hasChanges here, the watcher on localLayoutTree handles it
|
|
||||||
}
|
}
|
||||||
} else if (payload.parentNodeId) {
|
} else if (payload.parentNodeId) {
|
||||||
localLayoutTree.value = findAndRemoveNode(localLayoutTree.value, payload.parentNodeId, payload.nodeIndex);
|
localLayoutTree.value = findAndRemoveNode(localLayoutTree.value, payload.parentNodeId, payload.nodeIndex);
|
||||||
// No need to set hasChanges here, the watcher on localLayoutTree handles it
|
// Watcher on localLayoutTree handles hasChanges
|
||||||
} else {
|
} else {
|
||||||
console.warn('[LayoutConfigurator] Invalid remove payload:', payload);
|
console.warn('[LayoutConfigurator] Invalid remove payload:', payload);
|
||||||
}
|
}
|
||||||
@@ -225,10 +284,13 @@ const handleNodeRemove = (payload: { parentNodeId: string | undefined; nodeIndex
|
|||||||
|
|
||||||
// Remove pane from sidebar list
|
// Remove pane from sidebar list
|
||||||
const removeSidebarPane = (side: 'left' | 'right', index: number) => {
|
const removeSidebarPane = (side: 'left' | 'right', index: number) => {
|
||||||
localSidebarPanes.value[side].splice(index, 1);
|
const removedPane = localSidebarPanes.value[side].splice(index, 1)[0]; // Remove and get pane name
|
||||||
console.log(`[LayoutConfigurator] Removed pane from ${side} sidebar at index ${index}.`);
|
if (removedPane) {
|
||||||
// Explicitly set hasChanges flag
|
console.log(`[LayoutConfigurator] Removed pane '${removedPane}' from ${side} sidebar at index ${index}.`);
|
||||||
hasChanges.value = true;
|
addPaneToAvailableList(removedPane); // Correctly call the helper
|
||||||
|
}
|
||||||
|
// Explicitly set hasChanges flag (watcher might not catch splice reliably?)
|
||||||
|
hasChanges.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handler for vuedraggable end event to ensure changes flag is set and handle added items
|
// Handler for vuedraggable end event to ensure changes flag is set and handle added items
|
||||||
@@ -257,6 +319,48 @@ const onDraggableChange = (event: any, side: 'left' | 'right') => { // Add side
|
|||||||
hasChanges.value = true;
|
hasChanges.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle drag end from the available panes list
|
||||||
|
const handleAvailablePaneDragEnd = (event: any) => {
|
||||||
|
// Check if the item was dropped into a different list (main layout or sidebars)
|
||||||
|
if (event.to !== event.from) {
|
||||||
|
// Find the component (Draggable) associated with the source item element
|
||||||
|
// This might rely on internal structure, adjust if needed or find a better way
|
||||||
|
const draggedItemElement = event.item; // The original element in the source list
|
||||||
|
let paneName: PaneName | null = null;
|
||||||
|
|
||||||
|
// Attempt to get data via Vue's internal context (might be unstable)
|
||||||
|
// Note: __draggable_component__ might not be reliable across versions. Consider data attributes if this fails.
|
||||||
|
if ((draggedItemElement as any)?.__draggable_component__?.context?.element) {
|
||||||
|
paneName = (draggedItemElement as any).__draggable_component__.context.element as PaneName;
|
||||||
|
} else {
|
||||||
|
// Fallback: Try getting from the data array using oldIndex if context fails
|
||||||
|
if (event.oldIndex !== undefined && localAvailablePanes.value[event.oldIndex]) {
|
||||||
|
paneName = localAvailablePanes.value[event.oldIndex];
|
||||||
|
console.warn("[LayoutConfigurator] Using index fallback to get pane name in drag end.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (paneName === 'terminal') {
|
||||||
|
console.log('[LayoutConfigurator] "terminal" pane dropped elsewhere. Removing from available list.');
|
||||||
|
// Find the precise index in the *current* state of localAvailablePanes, as it might have shifted
|
||||||
|
const currentIndex = localAvailablePanes.value.indexOf('terminal');
|
||||||
|
if (currentIndex > -1) {
|
||||||
|
localAvailablePanes.value.splice(currentIndex, 1);
|
||||||
|
} else {
|
||||||
|
console.warn('[LayoutConfigurator] Could not find "terminal" in available list to remove after drag.');
|
||||||
|
}
|
||||||
|
} else if (paneName) {
|
||||||
|
console.log(`[LayoutConfigurator] Non-terminal pane "${paneName}" dropped elsewhere. Kept in available list (clone).`);
|
||||||
|
// Do nothing, item remains in localAvailablePanes
|
||||||
|
} else {
|
||||||
|
console.error('[LayoutConfigurator] Could not determine dragged pane name in handleAvailablePaneDragEnd.');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('[LayoutConfigurator] Item dropped back into available list or drag cancelled.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -275,9 +379,10 @@ const onDraggableChange = (event: any, side: 'left' | 'right') => { // Add side
|
|||||||
<section class="available-panes-section">
|
<section class="available-panes-section">
|
||||||
<h3>{{ t('layoutConfigurator.availablePanes', '可用面板') }}</h3>
|
<h3>{{ t('layoutConfigurator.availablePanes', '可用面板') }}</h3>
|
||||||
<draggable
|
<draggable
|
||||||
:list="configuratorAvailablePanes"
|
:list="localAvailablePanes"
|
||||||
tag="ul"
|
tag="ul"
|
||||||
class="available-panes-list"
|
class="available-panes-list"
|
||||||
|
@end="handleAvailablePaneDragEnd"
|
||||||
:item-key="(element: PaneName) => element"
|
:item-key="(element: PaneName) => element"
|
||||||
:group="{ name: 'layout-items', pull: 'clone', put: false }"
|
:group="{ name: 'layout-items', pull: 'clone', put: false }"
|
||||||
:sort="false"
|
:sort="false"
|
||||||
@@ -289,11 +394,11 @@ const onDraggableChange = (event: any, side: 'left' | 'right') => { // Add side
|
|||||||
{{ paneLabels[element] || element }}
|
{{ paneLabels[element] || element }}
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<li v-if="configuratorAvailablePanes.length === 0" class="no-available-panes">
|
<li v-if="localAvailablePanes.length === 0" class="no-available-panes">
|
||||||
{{ t('layoutConfigurator.noAvailablePanes', '所有面板都已在布局中') }}
|
{{ t('layoutConfigurator.noAvailablePanes', '所有面板都已在布局中') }}
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
</draggable>
|
</draggable>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -362,7 +467,7 @@ const onDraggableChange = (event: any, side: 'left' | 'right') => { // Add side
|
|||||||
:item-key="(element: PaneName) => `right-${element}`"
|
:item-key="(element: PaneName) => `right-${element}`"
|
||||||
group="layout-items"
|
group="layout-items"
|
||||||
:sort="true"
|
:sort="true"
|
||||||
@change="(event) => onDraggableChange(event, 'right')"
|
@change="(event) => onDraggableChange(event, 'right')"
|
||||||
>
|
>
|
||||||
<template #item="{ element, index }: { element: PaneName, index: number }">
|
<template #item="{ element, index }: { element: PaneName, index: number }">
|
||||||
<li class="sidebar-pane-item">
|
<li class="sidebar-pane-item">
|
||||||
|
|||||||
Reference in New Issue
Block a user