fix(editor): make table drag payload reliable
This commit is contained in:
parent
548692f073
commit
879da4b86f
|
|
@ -26,6 +26,8 @@ import { extractIdentifierAt, isSqlKeyword, matchTable } from "@/lib/sqlNavigati
|
|||
import { lineColumnToOffset, parseSqlErrorLocation } from "@/lib/sqlDiagnostics";
|
||||
import {
|
||||
DBX_TABLE_REFERENCE_MIME,
|
||||
activeTableReferencePayloadValue,
|
||||
clearActiveTableReferencePayload,
|
||||
hasTableReferencePayloadType,
|
||||
parseTableReferencePayload,
|
||||
tableReferenceInsertText,
|
||||
|
|
@ -723,11 +725,14 @@ async function formatCurrentSql() {
|
|||
}
|
||||
|
||||
function droppedTableReference(event: DragEvent) {
|
||||
return parseTableReferencePayload(event.dataTransfer?.getData(DBX_TABLE_REFERENCE_MIME));
|
||||
return (
|
||||
activeTableReferencePayloadValue() ??
|
||||
parseTableReferencePayload(event.dataTransfer?.getData(DBX_TABLE_REFERENCE_MIME))
|
||||
);
|
||||
}
|
||||
|
||||
function hasDroppedTableReference(event: DragEvent) {
|
||||
return hasTableReferencePayloadType(event.dataTransfer?.types);
|
||||
return !!activeTableReferencePayloadValue() || hasTableReferencePayloadType(event.dataTransfer?.types);
|
||||
}
|
||||
|
||||
function insertDroppedTableReference(currentView: EditorViewType, event: DragEvent): boolean {
|
||||
|
|
@ -749,6 +754,7 @@ function insertDroppedTableReference(currentView: EditorViewType, event: DragEve
|
|||
scrollIntoView: true,
|
||||
userEvent: "input.drop",
|
||||
});
|
||||
clearActiveTableReferencePayload(payload);
|
||||
currentView.focus();
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,8 +60,10 @@ import { canTreeNodeShowExpander, treeItemPaddingLeft } from "@/lib/sidebarTreeI
|
|||
import { buildTableSelectSql } from "@/lib/tableSelectSql";
|
||||
import {
|
||||
DBX_TABLE_REFERENCE_MIME,
|
||||
clearActiveTableReferencePayload,
|
||||
createTableReferencePayload,
|
||||
serializeTableReferencePayload,
|
||||
setActiveTableReferencePayload,
|
||||
tableReferenceInsertText,
|
||||
} from "@/lib/queryEditorTableDrop";
|
||||
import { editablePrimaryKeys, usesSyntheticRowIdKey } from "@/lib/tableEditing";
|
||||
|
|
@ -1803,9 +1805,11 @@ const canDragTableReference = computed(
|
|||
!props.dragDisabled &&
|
||||
(props.node.type === "table" || props.node.type === "view") &&
|
||||
!!props.node.connectionId &&
|
||||
!!props.node.database,
|
||||
props.node.database != null,
|
||||
);
|
||||
|
||||
let draggingTableReferencePayload: ReturnType<typeof createTableReferencePayload> = null;
|
||||
|
||||
function onTableReferenceDragStart(event: DragEvent) {
|
||||
if (!canDragTableReference.value) return;
|
||||
const payload = createTableReferencePayload({
|
||||
|
|
@ -1815,11 +1819,20 @@ function onTableReferenceDragStart(event: DragEvent) {
|
|||
tableName: props.node.label,
|
||||
databaseType: currentDatabaseType(),
|
||||
});
|
||||
if (!payload || !event.dataTransfer) return;
|
||||
if (!payload) return;
|
||||
draggingTableReferencePayload = payload;
|
||||
setActiveTableReferencePayload(payload);
|
||||
const insertText = tableReferenceInsertText(payload, currentDatabaseType());
|
||||
event.dataTransfer.effectAllowed = "copy";
|
||||
event.dataTransfer.setData(DBX_TABLE_REFERENCE_MIME, serializeTableReferencePayload(payload));
|
||||
event.dataTransfer.setData("text/plain", insertText);
|
||||
if (event.dataTransfer) {
|
||||
event.dataTransfer.effectAllowed = "copy";
|
||||
event.dataTransfer.setData(DBX_TABLE_REFERENCE_MIME, serializeTableReferencePayload(payload));
|
||||
event.dataTransfer.setData("text/plain", insertText);
|
||||
}
|
||||
}
|
||||
|
||||
function onTableReferenceDragEnd() {
|
||||
clearActiveTableReferencePayload(draggingTableReferencePayload);
|
||||
draggingTableReferencePayload = null;
|
||||
}
|
||||
|
||||
// ---- CustomContextMenu ----
|
||||
|
|
@ -2147,6 +2160,7 @@ function treeItemMenuItems(): ContextMenuItem[] {
|
|||
@mousemove="isDropTarget ? updateTarget($event, node.id, node.type) : undefined"
|
||||
@mouseleave="clearTarget(node.id)"
|
||||
@dragstart="onTableReferenceDragStart"
|
||||
@dragend="onTableReferenceDragEnd"
|
||||
>
|
||||
<div
|
||||
v-if="showDropBefore"
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ export interface QueryEditorTableReferencePayload {
|
|||
databaseType?: DatabaseType;
|
||||
}
|
||||
|
||||
let activeTableReferencePayload: QueryEditorTableReferencePayload | null = null;
|
||||
|
||||
export function createTableReferencePayload(options: {
|
||||
connectionId?: string;
|
||||
database?: string;
|
||||
|
|
@ -19,15 +21,16 @@ export function createTableReferencePayload(options: {
|
|||
tableName?: string;
|
||||
databaseType?: DatabaseType;
|
||||
}): QueryEditorTableReferencePayload | null {
|
||||
if (!options.connectionId || !options.database || !options.tableName) return null;
|
||||
return {
|
||||
if (!options.connectionId || options.database == null || !options.tableName) return null;
|
||||
const payload: QueryEditorTableReferencePayload = {
|
||||
kind: "dbx-table-reference",
|
||||
connectionId: options.connectionId,
|
||||
database: options.database,
|
||||
schema: options.schema,
|
||||
tableName: options.tableName,
|
||||
databaseType: options.databaseType,
|
||||
};
|
||||
if (options.schema) payload.schema = options.schema;
|
||||
if (options.databaseType) payload.databaseType = options.databaseType;
|
||||
return payload;
|
||||
}
|
||||
|
||||
export function serializeTableReferencePayload(payload: QueryEditorTableReferencePayload): string {
|
||||
|
|
@ -44,19 +47,19 @@ export function parseTableReferencePayload(value: string | undefined | null): Qu
|
|||
typeof parsed.database !== "string" ||
|
||||
typeof parsed.tableName !== "string" ||
|
||||
!parsed.connectionId ||
|
||||
!parsed.database ||
|
||||
!parsed.tableName
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
const payload: QueryEditorTableReferencePayload = {
|
||||
kind: "dbx-table-reference",
|
||||
connectionId: parsed.connectionId,
|
||||
database: parsed.database,
|
||||
schema: typeof parsed.schema === "string" && parsed.schema ? parsed.schema : undefined,
|
||||
tableName: parsed.tableName,
|
||||
databaseType: parsed.databaseType,
|
||||
};
|
||||
if (typeof parsed.schema === "string" && parsed.schema) payload.schema = parsed.schema;
|
||||
if (parsed.databaseType) payload.databaseType = parsed.databaseType;
|
||||
return payload;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -70,6 +73,20 @@ export function hasTableReferencePayloadType(types: Iterable<string> | undefined
|
|||
return false;
|
||||
}
|
||||
|
||||
export function setActiveTableReferencePayload(payload: QueryEditorTableReferencePayload | null) {
|
||||
activeTableReferencePayload = payload;
|
||||
}
|
||||
|
||||
export function activeTableReferencePayloadValue(): QueryEditorTableReferencePayload | null {
|
||||
return activeTableReferencePayload;
|
||||
}
|
||||
|
||||
export function clearActiveTableReferencePayload(payload?: QueryEditorTableReferencePayload | null) {
|
||||
if (!payload || activeTableReferencePayload === payload) {
|
||||
activeTableReferencePayload = null;
|
||||
}
|
||||
}
|
||||
|
||||
export function tableReferenceInsertText(
|
||||
payload: QueryEditorTableReferencePayload,
|
||||
fallbackDatabaseType?: DatabaseType,
|
||||
|
|
|
|||
|
|
@ -2,15 +2,33 @@ import { strict as assert } from "node:assert";
|
|||
import test from "node:test";
|
||||
import {
|
||||
DBX_TABLE_REFERENCE_MIME,
|
||||
activeTableReferencePayloadValue,
|
||||
clearActiveTableReferencePayload,
|
||||
createTableReferencePayload,
|
||||
hasTableReferencePayloadType,
|
||||
parseTableReferencePayload,
|
||||
serializeTableReferencePayload,
|
||||
setActiveTableReferencePayload,
|
||||
tableReferenceInsertText,
|
||||
} from "../../apps/desktop/src/lib/queryEditorTableDrop.ts";
|
||||
|
||||
test("creates table drag payload only when table context is complete", () => {
|
||||
assert.equal(createTableReferencePayload({ connectionId: "c1", database: "db" }), null);
|
||||
assert.deepEqual(
|
||||
createTableReferencePayload({
|
||||
connectionId: "c1",
|
||||
database: "",
|
||||
tableName: "catalogless_table",
|
||||
databaseType: "sqlite",
|
||||
}),
|
||||
{
|
||||
kind: "dbx-table-reference",
|
||||
connectionId: "c1",
|
||||
database: "",
|
||||
tableName: "catalogless_table",
|
||||
databaseType: "sqlite",
|
||||
},
|
||||
);
|
||||
assert.deepEqual(
|
||||
createTableReferencePayload({
|
||||
connectionId: "c1",
|
||||
|
|
@ -39,10 +57,40 @@ test("round trips table drag payload and rejects unrelated data", () => {
|
|||
});
|
||||
assert.ok(payload);
|
||||
assert.deepEqual(parseTableReferencePayload(serializeTableReferencePayload(payload)), payload);
|
||||
assert.deepEqual(
|
||||
parseTableReferencePayload(
|
||||
JSON.stringify({
|
||||
kind: "dbx-table-reference",
|
||||
connectionId: "c1",
|
||||
database: "",
|
||||
tableName: "orders",
|
||||
}),
|
||||
),
|
||||
{
|
||||
kind: "dbx-table-reference",
|
||||
connectionId: "c1",
|
||||
database: "",
|
||||
tableName: "orders",
|
||||
},
|
||||
);
|
||||
assert.equal(parseTableReferencePayload("not json"), null);
|
||||
assert.equal(parseTableReferencePayload(JSON.stringify({ kind: "dbx-table-reference", tableName: "orders" })), null);
|
||||
});
|
||||
|
||||
test("tracks the active in-app table drag payload without dataTransfer reads", () => {
|
||||
const payload = createTableReferencePayload({
|
||||
connectionId: "c1",
|
||||
database: "",
|
||||
tableName: "orders",
|
||||
databaseType: "sqlite",
|
||||
});
|
||||
assert.ok(payload);
|
||||
setActiveTableReferencePayload(payload);
|
||||
assert.equal(activeTableReferencePayloadValue(), payload);
|
||||
clearActiveTableReferencePayload(payload);
|
||||
assert.equal(activeTableReferencePayloadValue(), null);
|
||||
});
|
||||
|
||||
test("detects table drag payload type without reading drag data", () => {
|
||||
assert.equal(hasTableReferencePayloadType(undefined), false);
|
||||
assert.equal(hasTableReferencePayloadType(["text/plain"]), false);
|
||||
|
|
|
|||
Loading…
Reference in New Issue