feat(editor): drag result grid columns into the SQL editor

This commit is contained in:
Yang Fei authored and GitHub committed 2026-08-27 17:16:57 +08:00
1 parent 9e4fe8ce56
commit ab4e747bf9
17 files changed
+911 -25

No files matched your search

@@ -97,14 +97,18 @@ import { analyzeMysqlRoutineSyntax, supportsMysqlRoutineSyntaxDiagnostics } from
import {
DBX_TABLE_REFERENCE_MIME,
DBX_TABLE_REFERENCE_DROP_EVENT,
DBX_TABLE_REFERENCE_HOVER_EVENT,
DBX_TABLE_REFERENCE_DRAG_END_EVENT,
activeTableReferencePayloadValue,
clearActiveTableReferencePayload,
hasTableReferencePayloadType,
parseTableReferencePayload,
tableReferenceInsertText,
type QueryEditorTableReferenceDropDetail,
type QueryEditorTableReferenceHoverDetail,
type QueryEditorTableReferencePayload,
} from "@/lib/editor/queryEditorTableDrop";
import { isPointOverElementRoot } from "@/lib/editor/tableReferenceDragFeedback";
import type { SqlHighlighter } from "@/lib/sql/sqlHighlighter";
import { EDITOR_FONT_FAMILY_CSS_VAR, EDITOR_FONT_SIZE_CSS_VAR, editorDiagnosticColors, editorThemeAppearanceFor, loadEditorTheme, editorFontTheme, shellLineCommentTheme, sqlCompletionTheme, sqlSemanticHighlightTheme } from "@/lib/editor/editorThemes";
import { createStatementGutterMarkerDom, shouldShowStatementGutter } from "@/lib/editor/codemirrorStatementGutter";
@@ -3295,6 +3299,7 @@ function insertTableReferencePayload(currentView: EditorViewType, payload: Query
userEvent: "input.drop",
});
clearActiveTableReferencePayload(payload);
hideQueryEditorDropCaret();
currentView.focus();
return true;
}
@@ -3316,21 +3321,77 @@ function onTableReferenceDropEvent(event: Event) {
if (!currentView || props.readOnly || !(event instanceof CustomEvent)) return;
const detail = event.detail as QueryEditorTableReferenceDropDetail | undefined;
if (!detail?.payload) return;
const target = document.elementFromPoint(detail.clientX, detail.clientY);
if (target instanceof Element && editorRef.value?.contains(target)) {
// elementFromPoint 被透明覆盖层拦截时回退为编辑器根节点包围盒判定(见 isPointOverElementRoot)。
if (isPointOverElementRoot(detail.clientX, detail.clientY, editorRef.value)) {
insertTableReferencePayload(currentView, detail.payload, detail);
}
}
// --- 表引用拖拽悬停时的插入光标线(指针模拟拖拽经 window 事件驱动) ---
const queryEditorDropCaret = ref<{ left: number; top: number; height: number } | null>(null);
const queryEditorDropCaretStyle = computed(() => {
const caret = queryEditorDropCaret.value;
return caret ? { left: `${caret.left}px`, top: `${caret.top}px`, height: `${caret.height}px` } : {};
});
function showQueryEditorDropCaretAt(clientX: number, clientY: number) {
const currentView = view.value;
if (!currentView || props.readOnly || !editorRef.value) {
hideQueryEditorDropCaret();
return;
}
let dropPos: number | null = null;
try {
dropPos = currentView.posAtCoords({ x: clientX, y: clientY });
} catch {
dropPos = null;
}
if (dropPos == null) {
hideQueryEditorDropCaret();
return;
}
const coords = currentView.coordsAtPos(dropPos);
if (!coords) {
hideQueryEditorDropCaret();
return;
}
const rect = editorRef.value.getBoundingClientRect();
queryEditorDropCaret.value = { left: coords.left - rect.left, top: coords.top - rect.top, height: Math.max(coords.bottom - coords.top, 0) };
}
function hideQueryEditorDropCaret() {
queryEditorDropCaret.value = null;
}
function onTableReferenceHoverEvent(event: Event) {
if (!(event instanceof CustomEvent)) return;
const detail = event.detail as QueryEditorTableReferenceHoverDetail | undefined;
if (!detail) return;
// elementFromPoint 被透明覆盖层拦截时回退为编辑器根节点包围盒判定(见 isPointOverElementRoot)。
if (!isPointOverElementRoot(detail.clientX, detail.clientY, editorRef.value)) {
hideQueryEditorDropCaret();
return;
}
showQueryEditorDropCaretAt(detail.clientX, detail.clientY);
}
function onTableReferenceDragEndEvent() {
hideQueryEditorDropCaret();
}
function registerTableReferenceDropListener() {
if (tableReferenceDropListenerRegistered) return;
window.addEventListener(DBX_TABLE_REFERENCE_DROP_EVENT, onTableReferenceDropEvent);
window.addEventListener(DBX_TABLE_REFERENCE_HOVER_EVENT, onTableReferenceHoverEvent);
window.addEventListener(DBX_TABLE_REFERENCE_DRAG_END_EVENT, onTableReferenceDragEndEvent);
tableReferenceDropListenerRegistered = true;
}
function unregisterTableReferenceDropListener() {
if (!tableReferenceDropListenerRegistered) return;
window.removeEventListener(DBX_TABLE_REFERENCE_DROP_EVENT, onTableReferenceDropEvent);
window.removeEventListener(DBX_TABLE_REFERENCE_HOVER_EVENT, onTableReferenceHoverEvent);
window.removeEventListener(DBX_TABLE_REFERENCE_DRAG_END_EVENT, onTableReferenceDragEndEvent);
tableReferenceDropListenerRegistered = false;
}
@@ -5374,12 +5435,21 @@ onMounted(async () => {
return recoverLargeTauriPaste(event, currentView);
},
dragover(event) {
if (props.readOnly || !hasDroppedTableReference(event)) return false;
if (props.readOnly || !hasDroppedTableReference(event)) {
hideQueryEditorDropCaret();
return false;
}
event.preventDefault();
if (event.dataTransfer) event.dataTransfer.dropEffect = "copy";
showQueryEditorDropCaretAt(event.clientX, event.clientY);
return true;
},
dragleave() {
hideQueryEditorDropCaret();
return false;
},
drop(event, currentView) {
hideQueryEditorDropCaret();
return insertDroppedTableReference(currentView, event);
},
blur(_event, currentView) {
@@ -6187,6 +6257,7 @@ defineExpose({
"
/>
</CustomContextMenu>
<div v-show="queryEditorDropCaret" data-query-editor-drop-caret class="pointer-events-none absolute z-20 w-0.5 rounded-full bg-primary/70" :style="queryEditorDropCaretStyle" />
<EditorSearchPanel ref="searchPanelRef" :view="view" />
<SqlExecutionTargetPicker v-if="pickerVisible" :candidates="pickerCandidates" :active-index="pickerActiveIndex" :anchor="pickerAnchor" @update:active-index="onPickerActiveIndexChange" @confirm="onPickerConfirm" @cancel="closePicker" />
<DelimitedListDialog v-model:open="delimitedListOpen" :selected-text="delimitedListSelectedText" @confirm="applyDelimitedListResult" />