fix(import): apply layouts when connections already exist

This commit is contained in:
Melody Ruch authored and GitHub committed 2026-08-16 14:10:30 +08:00
1 parent 71136377e5
commit 2f3a32955c
4 files changed
+264 -3

No files matched your search

@@ -2,6 +2,7 @@ import { ref, watch } from "vue";
import { useI18n } from "vue-i18n";
import { useConnectionStore } from "@/stores/connectionStore";
import { useToast } from "@/composables/useToast";
import { hasSidebarLayoutEntries } from "@/lib/sidebar/sidebarLayout";
import type { SidebarLayout } from "@/types/database";
const showTransferDialog = ref(false);
@@ -310,7 +311,7 @@ export function useDialogSources() {
: t("configExport.importNone"),
4000,
);
if (layout && count > 0) {
if (hasSidebarLayoutEntries(layout)) {
pendingImportLayout.value = layout;
showImportLayoutConfirm.value = true;
}
@@ -325,7 +326,7 @@ export function useDialogSources() {
const { count, layout } = await connectionStore.importConnectionsFromFile(pendingImportContent.value, passphrase);
showConfigPassphraseDialog.value = false;
toast(count > 0 ? t("configExport.importSuccess", { count }) : t("configExport.importNone"), 2000);
if (layout && count > 0) {
if (hasSidebarLayoutEntries(layout)) {
pendingImportLayout.value = layout;
showImportLayoutConfirm.value = true;
}
@@ -6,6 +6,10 @@ export function emptyLayout(): SidebarLayout {
return { groups: [], order: [] };
}
export function hasSidebarLayoutEntries(layout: SidebarLayout | null | undefined): layout is SidebarLayout {
return !!layout && (layout.groups.length > 0 || layout.order.length > 0);
}
function folderPathSegments(path: string | undefined): string[] {
return (path ?? "").split("/").filter((segment) => segment.length > 0);
}
@@ -121,6 +125,58 @@ export function remapSidebarLayoutConnectionIds(layout: SidebarLayout, connectio
};
}
/**
* Merge an imported layout into the current one instead of replacing it.
* Folders are matched by name within the same level so repeated imports reuse
* them, and connections outside the imported set keep their current placement.
*/
export function mergeSidebarLayout(current: SidebarLayout, imported: SidebarLayout): SidebarLayout {
const importedGroups = new Map(imported.groups.map((group) => [group.id, group]));
const groups = current.groups.map((group) => ({ ...group }));
const groupNameById = new Map(groups.map((group) => [group.id, group.name]));
const order = cloneEntries(current.order);
// The imported layout owns the placement of the connections it lists, so drop
// their current entries first — otherwise they would end up listed twice.
const importedConnectionIds: string[] = [];
const collectConnections = (entries: SidebarOrderEntry[]) => {
for (const entry of entries) {
if (entry.type === "connection") importedConnectionIds.push(entry.id);
else collectConnections(entryChildren(entry));
}
};
collectConnections(imported.order);
for (const id of importedConnectionIds) removeEntry(order, id);
const merge = (target: SidebarOrderEntry[], source: SidebarOrderEntry[]) => {
for (const entry of source) {
if (entry.type === "connection") {
target.push({ type: "connection", id: entry.id });
continue;
}
const importedGroup = importedGroups.get(entry.id);
if (!importedGroup) continue;
let destination = target.find((candidate): candidate is Extract<SidebarOrderEntry, { type: "group" }> => candidate.type === "group" && groupNameById.get(candidate.id) === importedGroup.name);
if (!destination) {
// A fresh id keeps the merged tree free of collisions with the group ids
// the current layout already uses.
const id = uuid();
destination = { type: "group", id, children: [] };
groups.push({ ...importedGroup, id });
groupNameById.set(id, importedGroup.name);
target.push(destination);
}
destination.children ??= [];
merge(destination.children, entryChildren(entry));
}
};
merge(order, imported.order);
return { groups, order };
}
export function connectionSidebarSearchAliases(config: Pick<ConnectionConfig, "host" | "username">): string[] {
return [config.host, config.username].filter((value) => value.trim().length > 0);
}
+2 -1
View File
@@ -53,6 +53,7 @@ import {
collapseAllGroups as collapseAllGroupsOp,
moveConnectionToGroup as moveConnectionToGroupOp,
remapSidebarLayoutConnectionIds,
mergeSidebarLayout,
reorderEntry as reorderEntryOp,
buildConnectionGroupPathMap,
connectionSidebarSearchAliases,
@@ -7857,7 +7858,7 @@ export const useConnectionStore = defineStore("connection", () => {
function applySidebarLayout(layout: SidebarLayout) {
const reconciledLayout = reconcileLayout(
connections.value.map((c) => c.id),
layout,
mergeSidebarLayout(sidebarLayout.value, layout),
);
updateLayoutAndRebuild(reconciledLayout);
}