feat(sidebar): add connection copy paste shortcuts
This commit is contained in:
19 files changed
+509
-20
No files matched your search
@@ -1019,7 +1019,7 @@ function copySingleTableToClipboard(row: ObjectBrowserRow) {
|
||||
|
||||
function openPasteTableDialog() {
|
||||
const clipboard = connectionStore.treeClipboard;
|
||||
if (!canPasteTableClipboard() || !clipboard) {
|
||||
if (!canPasteTableClipboard() || clipboard?.kind !== "table-copy") {
|
||||
toast(t("contextMenu.noTableToPaste"), 2000);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -8,9 +8,10 @@ import { useSettingsStore } from "@/stores/settingsStore";
|
||||
import { useToast } from "@/composables/useToast";
|
||||
import type { TreeNode, TreeNodeType } from "@/types/database";
|
||||
import { filterSidebarSearchRootsByConnectionState, filterSidebarTree } from "@/lib/sidebarSearchTree";
|
||||
import { isCancelSearchShortcut } from "@/lib/keyboardShortcuts";
|
||||
import { isCancelSearchShortcut, isCopySidebarSelectionShortcut, isEditSidebarConnectionShortcut, isPasteSidebarSelectionShortcut } from "@/lib/keyboardShortcuts";
|
||||
import { copyNameForTreeNode } from "@/lib/treeNodeClick";
|
||||
import { copyToClipboard, eventTargetAllowsAppClipboardShortcut } from "@/lib/clipboard";
|
||||
import { copyToClipboard } from "@/lib/clipboard";
|
||||
import { connectionPasteTargetGroupId, selectedConnectionClipboardNodes, selectedConnectionEditTarget } from "@/lib/sidebarConnectionSelection";
|
||||
import { isEditableSidebarTypeSearchTarget, sidebarTypeSearchNextQuery } from "@/lib/sidebarTypeSearch";
|
||||
import { usesTreeSchemaMode } from "@/lib/databaseFeatureSupport";
|
||||
import { connectionUsesDatabaseObjectTreeMode, effectiveDatabaseTypeForConnection } from "@/lib/jdbcDialect";
|
||||
@@ -781,15 +782,22 @@ function focusSearchAtEnd() {
|
||||
function onWindowKeydown(event: KeyboardEvent) {
|
||||
if (event.defaultPrevented) return;
|
||||
if (sidebarShortcutTargetIsActive(event.target)) {
|
||||
if (eventTargetAllowsAppClipboardShortcut(event, "c")) {
|
||||
if (sidebarShortcutTargetAllowsAppShortcut(event.target) && isEditConnectionShortcut(event)) {
|
||||
if (requestSelectedConnectionEdit()) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (sidebarShortcutTargetAllowsAppShortcut(event.target) && isCopySidebarSelectionShortcut(event, settingsStore.editorSettings.shortcuts)) {
|
||||
if (copySelectedSidebarNames()) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (eventTargetAllowsAppClipboardShortcut(event, "v")) {
|
||||
if (requestSelectedSidebarPasteTable()) {
|
||||
if (sidebarShortcutTargetAllowsAppShortcut(event.target) && isPasteSidebarSelectionShortcut(event, settingsStore.editorSettings.shortcuts)) {
|
||||
if (requestSelectedSidebarPaste()) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
@@ -820,14 +828,39 @@ function sidebarShortcutTargetIsActive(target: EventTarget | null): boolean {
|
||||
return pointerInsideTree.value && (!active || active === document.body || root.contains(active));
|
||||
}
|
||||
|
||||
function sidebarShortcutTargetAllowsAppShortcut(target: EventTarget | null): boolean {
|
||||
if (!(target instanceof HTMLElement)) return true;
|
||||
return !(target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target.isContentEditable || !!target.closest("[contenteditable='true'], [role='textbox']"));
|
||||
}
|
||||
|
||||
function selectedSidebarNodesInVisibleOrder(): TreeNode[] {
|
||||
const selectedIds = new Set(store.selectedTreeNodeIds);
|
||||
return visibleNodes.value.filter((node) => selectedIds.has(node.id));
|
||||
}
|
||||
|
||||
function isEditConnectionShortcut(event: KeyboardEvent): boolean {
|
||||
return isEditSidebarConnectionShortcut(event, settingsStore.editorSettings.shortcuts);
|
||||
}
|
||||
|
||||
function requestSelectedConnectionEdit(): boolean {
|
||||
const selectedNodeId = store.selectedTreeNodeId;
|
||||
const currentNode = selectedNodeId ? visibleNodes.value.find((node) => node.id === selectedNodeId) : null;
|
||||
if (!currentNode) return false;
|
||||
const editTarget = selectedConnectionEditTarget(currentNode, selectedSidebarNodesInVisibleOrder());
|
||||
if (!editTarget) return false;
|
||||
store.startEditing(editTarget.connectionId);
|
||||
return true;
|
||||
}
|
||||
|
||||
function copySelectedSidebarNames(): boolean {
|
||||
const nodes = selectedSidebarNodesInVisibleOrder();
|
||||
if (nodes.length === 0) return false;
|
||||
const connectionNodes = selectedConnectionClipboardNodes(nodes);
|
||||
if (connectionNodes.length > 0) {
|
||||
const copiedCount = store.copyConnectionsToTreeClipboard(connectionNodes.map((node) => node.connectionId));
|
||||
if (copiedCount > 0) toast(t("connection.copied"), 2000);
|
||||
return copiedCount > 0;
|
||||
}
|
||||
const tableNodes = nodes.filter((node) => node.type === "table" && !!node.connectionId && !!node.database);
|
||||
store.treeClipboard =
|
||||
tableNodes.length > 0
|
||||
@@ -847,9 +880,20 @@ function copySelectedSidebarNames(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
function requestSelectedSidebarPasteTable(): boolean {
|
||||
function requestSelectedSidebarPaste(): boolean {
|
||||
const clipboard = store.treeClipboard;
|
||||
const selectedNodeId = store.selectedTreeNodeId;
|
||||
if (clipboard?.kind === "connection-copy") {
|
||||
const selectedNode = selectedNodeId ? visibleNodes.value.find((node) => node.id === selectedNodeId) : null;
|
||||
const targetGroupId = connectionPasteTargetGroupId(selectedNode, (connectionId) => store.groupIdForConnection(connectionId));
|
||||
void store
|
||||
.pasteConnectionClipboard(targetGroupId)
|
||||
.then((count) => {
|
||||
if (count > 0) toast(count > 1 ? t("connection.duplicatedSelected", { count }) : t("connection.duplicated"), 2000);
|
||||
})
|
||||
.catch((e: any) => toast(t("connection.saveFailed", { message: e?.message || String(e) }), 5000));
|
||||
return true;
|
||||
}
|
||||
if (clipboard?.kind !== "table-copy" || clipboard.tables.length === 0 || !selectedNodeId) return false;
|
||||
window.dispatchEvent(new CustomEvent("dbx:sidebar-request-paste-table", { detail: { nodeId: selectedNodeId } }));
|
||||
return true;
|
||||
|
||||
@@ -79,7 +79,8 @@ import { clearActiveTableReferencePayload, createTableReferencePayload, createTa
|
||||
import { editableRowIdentifierColumns, usesSyntheticRowIdKey } from "@/lib/tableEditing";
|
||||
import { tableOpenPageLimit } from "@/lib/tableOpenPageLimit";
|
||||
import { supportsDatabaseCreation, supportsDatabaseSearch, supportsFieldLineage, supportsObjectBrowserTreeNode, supportsSchemaDiagram, supportsSqlFileExecution, supportsTableImport, supportsTableTruncate, supportsTableStructureEditing, usesTreeSchemaMode } from "@/lib/databaseCapabilities";
|
||||
import { copyNameForTreeNode, isDocumentBrowserTreeNode, objectSourceKindForTreeNode, shouldRunTreeNodeRowAction, sidebarSelectionCopyAction, treeNodeRowAction, treeNodeRowDoubleClickAction } from "@/lib/treeNodeClick";
|
||||
import { copyNameForTreeNode, isDocumentBrowserTreeNode, objectSourceKindForTreeNode, shouldRunTreeNodeRowAction, treeNodeRowAction, treeNodeRowDoubleClickAction } from "@/lib/treeNodeClick";
|
||||
import { isCopySidebarSelectionShortcut, isEditSidebarConnectionShortcut, isPasteSidebarSelectionShortcut } from "@/lib/keyboardShortcuts";
|
||||
import { formatSqlInsert } from "@/lib/exportFormats";
|
||||
import { joinExportedDdls } from "@/lib/ddlExport";
|
||||
import { fetchTableDataForExport } from "@/lib/tableDataExport";
|
||||
@@ -118,7 +119,7 @@ import { defaultPasteTableMode, pasteTableModeCopiesData, supportsWholeRowTableD
|
||||
import { sidebarDisplayTableName } from "@/lib/sidebarTableNameDisplay";
|
||||
import { shouldMeasureSidebarLabelOverflow } from "@/lib/sidebarLabelTooltip";
|
||||
import { selectedTreeNodesInVisibleOrder as orderSelectedTreeNodes, treeSelectionRangeIdsByIndex, treeSelectionRangeIds } from "@/lib/sidebarTreeSelection";
|
||||
import { selectedConnectionDeleteTargets, selectedConnectionDuplicateTargets } from "@/lib/sidebarConnectionSelection";
|
||||
import { connectionPasteTargetGroupId, selectedConnectionClipboardTargets, selectedConnectionDeleteTargets, selectedConnectionDuplicateTargets, selectedConnectionEditTarget } from "@/lib/sidebarConnectionSelection";
|
||||
import { supportsDatabaseUserAdmin } from "@/lib/databaseUserAdmin";
|
||||
import { canCloseSidebarDatabaseConnection, isSidebarDatabaseOpened } from "@/lib/sidebarDatabaseOpenState";
|
||||
import { sidebarTreeContextKey } from "@/lib/sidebarTreeContext";
|
||||
@@ -842,6 +843,12 @@ function onKeydown(event: KeyboardEvent) {
|
||||
event.stopPropagation();
|
||||
return;
|
||||
}
|
||||
if (isEditConnectionShortcut(event)) {
|
||||
if (!requestEditSelectedConnection()) return;
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return;
|
||||
}
|
||||
if (!event.metaKey && !event.ctrlKey && !event.altKey && !event.shiftKey && event.key === "F2") {
|
||||
if (!requestRenameSelectedNode()) return;
|
||||
event.preventDefault();
|
||||
@@ -860,8 +867,7 @@ function onKeydown(event: KeyboardEvent) {
|
||||
event.stopPropagation();
|
||||
return;
|
||||
}
|
||||
const action = sidebarSelectionCopyAction(event);
|
||||
if (action !== "copy-name") return;
|
||||
if (!isCopyTreeSelectionShortcut(event)) return;
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
copySelectedNames();
|
||||
@@ -872,7 +878,15 @@ function isDeleteTreeNodeShortcut(event: KeyboardEvent): boolean {
|
||||
}
|
||||
|
||||
function isPasteTreeClipboardShortcut(event: KeyboardEvent): boolean {
|
||||
return (event.metaKey || event.ctrlKey) && !event.altKey && !event.shiftKey && event.key.toLowerCase() === "v";
|
||||
return isPasteSidebarSelectionShortcut(event, settingsStore.editorSettings.shortcuts);
|
||||
}
|
||||
|
||||
function isEditConnectionShortcut(event: KeyboardEvent): boolean {
|
||||
return isEditSidebarConnectionShortcut(event, settingsStore.editorSettings.shortcuts);
|
||||
}
|
||||
|
||||
function isCopyTreeSelectionShortcut(event: KeyboardEvent): boolean {
|
||||
return isCopySidebarSelectionShortcut(event, settingsStore.editorSettings.shortcuts);
|
||||
}
|
||||
|
||||
function pasteTableTargetContext(): TableClipboardContext | null {
|
||||
@@ -891,6 +905,16 @@ function canPasteTreeClipboardToCurrentNode(): boolean {
|
||||
|
||||
function requestPasteTreeClipboard(): boolean {
|
||||
const clipboard = connectionStore.treeClipboard;
|
||||
if (clipboard?.kind === "connection-copy") {
|
||||
const targetGroupId = connectionPasteTargetGroupId(props.node, (connectionId) => connectionStore.groupIdForConnection(connectionId));
|
||||
void connectionStore
|
||||
.pasteConnectionClipboard(targetGroupId)
|
||||
.then((count) => {
|
||||
if (count > 0) toast(count > 1 ? t("connection.duplicatedSelected", { count }) : t("connection.duplicated"), 2000);
|
||||
})
|
||||
.catch((e: any) => toast(t("connection.saveFailed", { message: e?.message || String(e) }), 5000));
|
||||
return true;
|
||||
}
|
||||
if (clipboard?.kind !== "table-copy" || !canPasteTreeClipboardToCurrentNode()) return false;
|
||||
pasteTableMode.value = defaultPasteTableMode(currentDatabaseType());
|
||||
pasteTableEntries.value = clipboard.tables.map((entry) => ({
|
||||
@@ -927,6 +951,11 @@ function canRefreshTreeNodeShortcut(): boolean {
|
||||
function requestRenameSelectedNode(): boolean {
|
||||
const selected = selectedTreeNodesInVisibleOrder();
|
||||
if (selected.length > 1 && selected.some((node) => node.id === props.node.id)) return false;
|
||||
const editTarget = selectedConnectionEditTarget(props.node, selected);
|
||||
if (editTarget) {
|
||||
connectionStore.startEditing(editTarget.connectionId);
|
||||
return true;
|
||||
}
|
||||
if (canRenameObject.value) {
|
||||
openRenameObjectDialog();
|
||||
return true;
|
||||
@@ -938,6 +967,13 @@ function requestRenameSelectedNode(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
function requestEditSelectedConnection(): boolean {
|
||||
const editTarget = selectedConnectionEditTarget(props.node, selectedTreeNodesInVisibleOrder());
|
||||
if (!editTarget) return false;
|
||||
connectionStore.startEditing(editTarget.connectionId);
|
||||
return true;
|
||||
}
|
||||
|
||||
function requestDeleteSelectedNode(): boolean {
|
||||
if (requestDropSelectedNodes()) return true;
|
||||
if (props.node.type === "connection") {
|
||||
@@ -1568,6 +1604,12 @@ async function copyFinalProxyPort() {
|
||||
async function copySelectedNames() {
|
||||
const selectedNodes = selectedTreeNodesInVisibleOrder();
|
||||
const nodes = selectedNodes.length > 1 && selectedNodes.some((node) => node.id === props.node.id) ? selectedNodes : [props.node];
|
||||
const connectionTargets = selectedConnectionClipboardTargets(props.node, nodes);
|
||||
if (connectionTargets.length > 0) {
|
||||
const copiedCount = connectionStore.copyConnectionsToTreeClipboard(connectionTargets.map((node) => node.connectionId));
|
||||
if (copiedCount > 0) toast(t("connection.copied"), 2000);
|
||||
return;
|
||||
}
|
||||
updateTreeClipboardForNodes(nodes);
|
||||
try {
|
||||
await copyToClipboard(nodes.map(copyNameForTreeNode).join("\n"));
|
||||
@@ -3917,7 +3959,8 @@ onBeforeUnmount(() => {
|
||||
|
||||
// ---- CustomContextMenu ----
|
||||
|
||||
const shortcutCopyName = computed(() => formatShortcut("Mod+C"));
|
||||
const shortcutCopyName = computed(() => formatShortcut(settingsStore.editorSettings.shortcuts.copySidebarSelection));
|
||||
const shortcutEditConnection = computed(() => formatShortcut(settingsStore.editorSettings.shortcuts.editSidebarConnection));
|
||||
const shortcutRename = "F2";
|
||||
const shortcutRefresh = "F5";
|
||||
const shortcutDelete = "Delete";
|
||||
@@ -4108,7 +4151,7 @@ function treeItemMenuItems(): ContextMenuItem[] {
|
||||
icon: ListFilter,
|
||||
});
|
||||
}
|
||||
items.push({ label: t("contextMenu.editConnection"), action: editConnection, icon: Pencil });
|
||||
items.push({ label: t("contextMenu.editConnection"), action: editConnection, icon: Pencil, shortcut: shortcutEditConnection.value });
|
||||
if (revealConnectionFilePath.value) {
|
||||
items.push({
|
||||
label: t("contextMenu.revealDatabaseFile"),
|
||||
|
||||
Reference in new issue
Block a user