mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-21 03:59:03 +00:00
fix(grid): preserve viewport during gutter execution
This commit is contained in:
@@ -406,6 +406,7 @@ const {
|
||||
blockDangerousRedisCommands,
|
||||
onMissingDatabase: promptActiveDatabaseSelection,
|
||||
requestDangerConfirmation: (request) => sqlExecutionDangerStore.requestConfirmation(request),
|
||||
onExecutionStarted: (editorViewportRequestId) => contentAreaRef.value?.acceptQueryEditorExecutionViewport(editorViewportRequestId),
|
||||
});
|
||||
|
||||
function requestActiveEditorExecute() {
|
||||
|
||||
@@ -22,6 +22,7 @@ import { canFormatSqlForDatabaseType, formatSqlForEditing, compressSqlText, type
|
||||
import { detectAndFormatStructured } from "@/lib/sql/autoFormat";
|
||||
import { enabledSqlParameterSyntaxes, resolveSqlVariableSyntaxToggles } from "@/lib/sql/sqlVariableSyntax";
|
||||
import { blankLineDeletionChanges, replaceSelectedEditorText } from "@/lib/editor/queryEditorTextEdits";
|
||||
import { createQueryEditorExecutionViewportOwnership } from "@/lib/editor/queryEditorExecutionViewport";
|
||||
import { joinQueryEditorLines } from "@/lib/editor/queryEditorJoinLines";
|
||||
import { insertQueryEditorNewline } from "@/lib/editor/queryEditorNewline";
|
||||
import { createSqlSignatureTooltipDom } from "@/lib/editor/sqlSignatureTooltip";
|
||||
@@ -218,6 +219,7 @@ let viewportEmitFrame: number | null = null;
|
||||
let viewportRestoreFrame: number | null = null;
|
||||
let latestViewport: { scrollTop: number; scrollLeft: number } | undefined = props.initialViewport;
|
||||
let lastEmittedViewport: { scrollTop: number; scrollLeft: number } | undefined = props.initialViewport;
|
||||
const gutterExecutionViewport = createQueryEditorExecutionViewportOwnership();
|
||||
let latestSelection: { anchor: number; head: number } | undefined = props.initialSelection;
|
||||
const connectionStore = useConnectionStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
@@ -805,6 +807,9 @@ interface RequestExecuteOptions {
|
||||
}
|
||||
|
||||
function emitExecutionRequest(source: SqlExecutionOverride, openInNewResultTab = false) {
|
||||
if (typeof source === "string" || source.editorViewportRequestId === undefined) {
|
||||
gutterExecutionViewport.cancelPendingRequest();
|
||||
}
|
||||
if (openInNewResultTab) {
|
||||
emit("executeInNewResultTab", source);
|
||||
} else {
|
||||
@@ -813,6 +818,7 @@ function emitExecutionRequest(source: SqlExecutionOverride, openInNewResultTab =
|
||||
}
|
||||
|
||||
function requestExecute(options: RequestExecuteOptions = {}) {
|
||||
gutterExecutionViewport.cancelPendingRequest();
|
||||
const currentView = view.value;
|
||||
if (!currentView) return false;
|
||||
currentView.focus();
|
||||
@@ -1661,8 +1667,10 @@ function executeSqlStatementFromGutter(currentView: EditorViewType, line: { from
|
||||
event.stopPropagation();
|
||||
// Gutter play is always scoped to the statement/command for that line, even
|
||||
// when the main editor execute action would run the full document.
|
||||
emitExecutionRequest(sqlExecutionSnapshotForRange(currentView, statementRange));
|
||||
currentView.focus();
|
||||
const editorViewportRequestId = gutterExecutionViewport.beginRequest();
|
||||
emitExecutionRequest({ ...sqlExecutionSnapshotForRange(currentView, statementRange), editorViewportRequestId });
|
||||
// 不主动聚焦编辑器,否则 CodeMirror 会把屏幕滚回之前的光标位置。
|
||||
// currentView.focus();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -5565,6 +5573,7 @@ function pauseQueryEditorBackgroundWork() {
|
||||
flushEditorSelection();
|
||||
clearTableNavigationHover();
|
||||
clearPendingCompletionTab();
|
||||
gutterExecutionViewport.reset();
|
||||
editorIsActive = false;
|
||||
clearScheduledSemanticDiagnostics();
|
||||
completionEpoch++;
|
||||
@@ -5723,7 +5732,8 @@ function openReplace(): boolean {
|
||||
}
|
||||
|
||||
function scrollCursorIntoView() {
|
||||
if (!view.value || !editorViewModule || !editorIsActive) return;
|
||||
const preserveViewport = gutterExecutionViewport.consumeAcceptedRequest();
|
||||
if (!view.value || !editorViewModule || !editorIsActive || preserveViewport) return;
|
||||
const pos = view.value.state.selection.main.head;
|
||||
// Use "center" rather than "nearest": by the time this runs, the results pane has already
|
||||
// opened/resized and shrunk the editor viewport, so the cursor's old position is often no
|
||||
@@ -5741,10 +5751,15 @@ function dismissHoverTooltip() {
|
||||
view.value.dispatch({ effects: hoverCloseEffect });
|
||||
}
|
||||
|
||||
function acceptGutterExecutionViewport(requestId: number) {
|
||||
return gutterExecutionViewport.acceptRequest(requestId);
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
openSearch,
|
||||
openReplace,
|
||||
scrollCursorIntoView,
|
||||
acceptGutterExecutionViewport,
|
||||
requestExecute,
|
||||
requestExecuteInNewResultTab,
|
||||
pasteClipboardAsSqlInCondition,
|
||||
|
||||
@@ -986,6 +986,10 @@ function requestQueryEditorExecuteInNewResultTab() {
|
||||
return queryEditorRef.value?.requestExecuteInNewResultTab();
|
||||
}
|
||||
|
||||
function acceptQueryEditorExecutionViewport(requestId: number) {
|
||||
return queryEditorRef.value?.acceptGutterExecutionViewport(requestId) ?? false;
|
||||
}
|
||||
|
||||
async function handleExportQuery(payload: { sql: string; format: "csv" | "xlsx" | "txt"; columnComments?: (string | null)[] }) {
|
||||
const tab = props.activeTab;
|
||||
if (!tab || tab.mode !== "query") return;
|
||||
@@ -1018,7 +1022,7 @@ async function executeRedisCommand(command: string): Promise<boolean> {
|
||||
return (await redisKeyBrowserRef.value?.executeCommand?.(command)) ?? false;
|
||||
}
|
||||
|
||||
defineExpose({ focusSearch, refreshData, refreshQueryEditorCompletionCache, handleModRTarget, requestQueryEditorExecute, requestQueryEditorExecuteInNewResultTab, pasteClipboardAsSqlInCondition, applyTableStructureChanges, insertRedisCommand, executeRedisCommand });
|
||||
defineExpose({ focusSearch, refreshData, refreshQueryEditorCompletionCache, handleModRTarget, requestQueryEditorExecute, requestQueryEditorExecuteInNewResultTab, acceptQueryEditorExecutionViewport, pasteClipboardAsSqlInCondition, applyTableStructureChanges, insertRedisCommand, executeRedisCommand });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -30,6 +30,7 @@ const DANGER_RE = /^\s*(DROP|DELETE|TRUNCATE|ALTER|UPDATE|MERGE|REPLACE)\b/i;
|
||||
|
||||
interface SqlExecutionOptions {
|
||||
openInNewResultTab?: boolean;
|
||||
editorViewportRequestId?: number;
|
||||
}
|
||||
|
||||
interface TargetSqlExecutionInput {
|
||||
@@ -113,6 +114,7 @@ export function useSqlExecution(deps: {
|
||||
blockDangerousRedisCommands?: Ref<boolean>;
|
||||
onMissingDatabase?: () => void;
|
||||
requestDangerConfirmation?: (request: SqlExecutionDangerRequest) => Promise<boolean>;
|
||||
onExecutionStarted?: (editorViewportRequestId: number) => void;
|
||||
}) {
|
||||
const { t } = useI18n();
|
||||
const queryStore = useQueryStore();
|
||||
@@ -136,31 +138,35 @@ export function useSqlExecution(deps: {
|
||||
const pendingDangerKind = ref<"sql" | "redis">("sql");
|
||||
const pendingDangerSourceOffset = ref<number | undefined>();
|
||||
const pendingOpenInNewResultTab = ref(false);
|
||||
const pendingSqlParameterEditorViewportRequestId = ref<number | undefined>();
|
||||
const pendingDangerEditorViewportRequestId = ref<number | undefined>();
|
||||
let pendingSqlParameterContinuation: ((sql: string, sourceOffset?: number) => Promise<void> | void) | undefined;
|
||||
|
||||
async function resolvedExecutableSql(source?: SqlExecutionOverride): Promise<{ sql: string; sourceOffset?: number }> {
|
||||
async function resolvedExecutableSql(source?: SqlExecutionOverride): Promise<{ sql: string; sourceOffset?: number; editorViewportRequestId?: number }> {
|
||||
const atSetEnabled = resolveSqlVariableSyntaxToggles(settingsStore.editorSettings.sqlVariableSyntaxOverrides, deps.activeConnection.value?.db_type, settingsStore.editorSettings.sqlVariableSubstitutionEnabled).atSet;
|
||||
const expand = (sql: string) => (atSetEnabled ? expandSqlVariables(sql).sql : sql);
|
||||
if (typeof source === "string") return { sql: expand(source) };
|
||||
|
||||
const resolved = deps.resolveExecutableSql ? await deps.resolveExecutableSql(source) : isSqlExecutionSnapshot(source) ? resolveExecutableSql(source.fullSql, source.selectedSql, { cursorPos: source.cursorPos }) : deps.executableSql.value;
|
||||
const sql = expand(resolved);
|
||||
if (!isSqlExecutionSnapshot(source) || !source.selectedSql.trim() || sql !== resolved) return { sql };
|
||||
const editorViewportRequestId = isSqlExecutionSnapshot(source) ? source.editorViewportRequestId : undefined;
|
||||
if (!isSqlExecutionSnapshot(source) || !source.selectedSql.trim() || sql !== resolved) return { sql, editorViewportRequestId };
|
||||
|
||||
const leadingWhitespace = source.selectedSql.length - source.selectedSql.trimStart().length;
|
||||
return { sql, sourceOffset: source.selectionFrom + leadingWhitespace };
|
||||
return { sql, sourceOffset: source.selectionFrom + leadingWhitespace, editorViewportRequestId };
|
||||
}
|
||||
|
||||
async function tryExecute(sqlOverride?: SqlExecutionOverride, options: SqlExecutionOptions = {}) {
|
||||
const tab = deps.activeTab.value;
|
||||
const { sql, sourceOffset } = await resolvedExecutableSql(sqlOverride);
|
||||
const { sql, sourceOffset, editorViewportRequestId } = await resolvedExecutableSql(sqlOverride);
|
||||
const executionOptions = { ...options, editorViewportRequestId };
|
||||
if (!tab || !sql.trim()) return;
|
||||
if (requiresDatabaseSelection(tab, deps.activeConnection.value, sql)) {
|
||||
deps.onMissingDatabase?.();
|
||||
return;
|
||||
}
|
||||
if (supportsSqlTemplateParameters(deps.activeConnection.value, sql) && prepareSqlParameterDialog(sql, sourceOffset, options)) return;
|
||||
await continueExecute(sql, sourceOffset, options);
|
||||
if (supportsSqlTemplateParameters(deps.activeConnection.value, sql) && prepareSqlParameterDialog(sql, sourceOffset, executionOptions)) return;
|
||||
await continueExecute(sql, sourceOffset, executionOptions);
|
||||
}
|
||||
|
||||
function tryExecuteInNewResultTab(sqlOverride?: SqlExecutionOverride) {
|
||||
@@ -195,6 +201,7 @@ export function useSqlExecution(deps: {
|
||||
pendingDangerKind.value = "redis";
|
||||
pendingDangerSourceOffset.value = sourceOffset;
|
||||
pendingOpenInNewResultTab.value = options.openInNewResultTab === true;
|
||||
pendingDangerEditorViewportRequestId.value = options.editorViewportRequestId;
|
||||
suppressDangerConfirm.value = false;
|
||||
showDangerDialog.value = true;
|
||||
return;
|
||||
@@ -219,6 +226,7 @@ export function useSqlExecution(deps: {
|
||||
pendingDangerKind.value = "sql";
|
||||
pendingDangerSourceOffset.value = sourceOffset;
|
||||
pendingOpenInNewResultTab.value = options.openInNewResultTab === true;
|
||||
pendingDangerEditorViewportRequestId.value = options.editorViewportRequestId;
|
||||
suppressDangerConfirm.value = false;
|
||||
showDangerDialog.value = true;
|
||||
} else {
|
||||
@@ -238,6 +246,7 @@ export function useSqlExecution(deps: {
|
||||
sqlParameterEnabledSyntaxes.value = enabledSyntaxes;
|
||||
pendingSourceOffset.value = sourceOffset;
|
||||
pendingOpenInNewResultTab.value = options.openInNewResultTab === true;
|
||||
pendingSqlParameterEditorViewportRequestId.value = options.editorViewportRequestId;
|
||||
pendingSqlParameterContinuation = continuation;
|
||||
showSqlParameterDialog.value = true;
|
||||
return true;
|
||||
@@ -287,6 +296,7 @@ export function useSqlExecution(deps: {
|
||||
...(isRedis ? { skipRedisSafetyCheck: deps.blockDangerousRedisCommands?.value === false } : {}),
|
||||
...(sourceOffset !== undefined ? { sourceOffset } : {}),
|
||||
...(options.openInNewResultTab ? { openInNewResultTab: true } : {}),
|
||||
...(options.editorViewportRequestId !== undefined ? { onExecutionStarted: () => deps.onExecutionStarted?.(options.editorViewportRequestId!) } : {}),
|
||||
});
|
||||
if (producedResult === false) return;
|
||||
const sqlServerMessageResultIndex = executionDatabaseType === "sqlserver" ? tab.results?.findIndex((result) => result.server_message === true) : undefined;
|
||||
@@ -518,19 +528,22 @@ export function useSqlExecution(deps: {
|
||||
const sourceOffset = pendingDangerSourceOffset.value;
|
||||
const kind = pendingDangerKind.value;
|
||||
const openInNewResultTab = pendingOpenInNewResultTab.value;
|
||||
const editorViewportRequestId = pendingDangerEditorViewportRequestId.value;
|
||||
pendingDangerSql.value = "";
|
||||
pendingDangerSourceOffset.value = undefined;
|
||||
pendingDangerKind.value = "sql";
|
||||
pendingOpenInNewResultTab.value = false;
|
||||
pendingDangerEditorViewportRequestId.value = undefined;
|
||||
if (suppressDangerConfirm.value && kind === "sql") {
|
||||
settingsStore.updateEditorSettings({ confirmDangerousSqlExecution: false });
|
||||
}
|
||||
suppressDangerConfirm.value = false;
|
||||
await doExecute(sql, sourceOffset, { openInNewResultTab });
|
||||
await doExecute(sql, sourceOffset, { openInNewResultTab, editorViewportRequestId });
|
||||
}
|
||||
|
||||
async function onSqlParametersConfirm(sql: string) {
|
||||
const openInNewResultTab = pendingOpenInNewResultTab.value;
|
||||
const editorViewportRequestId = pendingSqlParameterEditorViewportRequestId.value;
|
||||
showSqlParameterDialog.value = false;
|
||||
sqlParameterSourceSql.value = "";
|
||||
sqlParameterNames.value = [];
|
||||
@@ -539,10 +552,11 @@ export function useSqlExecution(deps: {
|
||||
const sourceOffset = pendingSourceOffset.value;
|
||||
pendingSourceOffset.value = undefined;
|
||||
pendingOpenInNewResultTab.value = false;
|
||||
pendingSqlParameterEditorViewportRequestId.value = undefined;
|
||||
const continuation = pendingSqlParameterContinuation;
|
||||
pendingSqlParameterContinuation = undefined;
|
||||
if (continuation) await continuation(sql, sourceOffset);
|
||||
else await continueExecute(sql, sourceOffset, { openInNewResultTab });
|
||||
else await continueExecute(sql, sourceOffset, { openInNewResultTab, editorViewportRequestId });
|
||||
}
|
||||
|
||||
watch(showSqlParameterDialog, (open) => {
|
||||
@@ -553,6 +567,7 @@ export function useSqlExecution(deps: {
|
||||
sqlParameterEnabledSyntaxes.value = [];
|
||||
pendingSourceOffset.value = undefined;
|
||||
pendingOpenInNewResultTab.value = false;
|
||||
pendingSqlParameterEditorViewportRequestId.value = undefined;
|
||||
pendingSqlParameterContinuation = undefined;
|
||||
});
|
||||
|
||||
@@ -562,6 +577,7 @@ export function useSqlExecution(deps: {
|
||||
pendingDangerSourceOffset.value = undefined;
|
||||
pendingDangerKind.value = "sql";
|
||||
pendingOpenInNewResultTab.value = false;
|
||||
pendingDangerEditorViewportRequestId.value = undefined;
|
||||
suppressDangerConfirm.value = false;
|
||||
});
|
||||
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { createQueryEditorExecutionViewportOwnership } from "@/lib/editor/queryEditorExecutionViewport";
|
||||
|
||||
const queryEditorSource = readFileSync(new URL("../../../components/editor/QueryEditor.vue", import.meta.url), "utf8");
|
||||
const contentAreaSource = readFileSync(new URL("../../../components/layout/ContentArea.vue", import.meta.url), "utf8");
|
||||
const appSource = readFileSync(new URL("../../../App.vue", import.meta.url), "utf8");
|
||||
const sqlExecutionSource = readFileSync(new URL("../../../composables/useSqlExecution.ts", import.meta.url), "utf8");
|
||||
const queryStoreSource = readFileSync(new URL("../../../stores/queryStore.ts", import.meta.url), "utf8");
|
||||
|
||||
describe("QueryEditor execution routing", () => {
|
||||
it("routes the execution shortcut through the shared execution-mode contract while bypassing the picker", () => {
|
||||
@@ -46,10 +50,18 @@ describe("QueryEditor execution routing", () => {
|
||||
});
|
||||
|
||||
it("preserves the source range when executing from the statement gutter", () => {
|
||||
expect(queryEditorSource).toContain("emitExecutionRequest(sqlExecutionSnapshotForRange(currentView, statementRange))");
|
||||
expect(queryEditorSource).toContain("const editorViewportRequestId = gutterExecutionViewport.beginRequest()");
|
||||
expect(queryEditorSource).toContain("emitExecutionRequest({ ...sqlExecutionSnapshotForRange(currentView, statementRange), editorViewportRequestId })");
|
||||
expect(queryEditorSource).not.toContain('emit("execute", statementRange.sql)');
|
||||
});
|
||||
|
||||
it("claims gutter viewport ownership only after the matching execution starts", () => {
|
||||
expect(appSource).toContain("acceptQueryEditorExecutionViewport(editorViewportRequestId)");
|
||||
expect(contentAreaSource).toContain("acceptGutterExecutionViewport(requestId)");
|
||||
expect(sqlExecutionSource).toContain("onExecutionStarted: () => deps.onExecutionStarted?.(options.editorViewportRequestId!)");
|
||||
expect(queryStoreSource.indexOf("tab.isExecuting = true")).toBeLessThan(queryStoreSource.indexOf("options?.onExecutionStarted?.()"));
|
||||
});
|
||||
|
||||
it("lets the shortcut skip the picker without affecting other execution entry points", () => {
|
||||
// The picker guard must also honor the shortcut's bypass flag, otherwise Ctrl+Enter would keep popping the dialog.
|
||||
expect(queryEditorSource).toContain("if (options.bypassPicker || !settingsStore.editorSettings.showExecutionTargetPicker");
|
||||
@@ -63,6 +75,41 @@ describe("QueryEditor execution routing", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("QueryEditor gutter execution viewport ownership", () => {
|
||||
it("does not let a cancelled or early-returned gutter request affect the next ordinary execution", () => {
|
||||
const ownership = createQueryEditorExecutionViewportOwnership();
|
||||
const cancelledRequestId = ownership.beginRequest();
|
||||
|
||||
ownership.cancelPendingRequest();
|
||||
|
||||
expect(ownership.acceptRequest(cancelledRequestId)).toBe(false);
|
||||
expect(ownership.consumeAcceptedRequest()).toBe(false);
|
||||
});
|
||||
|
||||
it("preserves the viewport once for the matching accepted execution", () => {
|
||||
const ownership = createQueryEditorExecutionViewportOwnership();
|
||||
const requestId = ownership.beginRequest();
|
||||
|
||||
expect(ownership.acceptRequest(requestId)).toBe(true);
|
||||
expect(ownership.consumeAcceptedRequest()).toBe(true);
|
||||
expect(ownership.consumeAcceptedRequest()).toBe(false);
|
||||
});
|
||||
|
||||
it("clears pending and accepted ownership when the editor becomes inactive", () => {
|
||||
const ownership = createQueryEditorExecutionViewportOwnership();
|
||||
const pendingRequestId = ownership.beginRequest();
|
||||
ownership.reset();
|
||||
|
||||
expect(ownership.acceptRequest(pendingRequestId)).toBe(false);
|
||||
|
||||
const acceptedRequestId = ownership.beginRequest();
|
||||
expect(ownership.acceptRequest(acceptedRequestId)).toBe(true);
|
||||
ownership.reset();
|
||||
|
||||
expect(ownership.consumeAcceptedRequest()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("ContentArea execution summary errors", () => {
|
||||
it("keeps batch errors selectable and copyable without triggering statement navigation", () => {
|
||||
expect(contentAreaSource).toContain('class="absolute inset-0 z-0 cursor-pointer');
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
export function createQueryEditorExecutionViewportOwnership() {
|
||||
let nextRequestId = 0;
|
||||
let pendingRequestId: number | undefined;
|
||||
let acceptedRequestId: number | undefined;
|
||||
|
||||
return {
|
||||
beginRequest(): number {
|
||||
nextRequestId += 1;
|
||||
pendingRequestId = nextRequestId;
|
||||
return nextRequestId;
|
||||
},
|
||||
cancelPendingRequest() {
|
||||
pendingRequestId = undefined;
|
||||
},
|
||||
acceptRequest(requestId: number): boolean {
|
||||
if (pendingRequestId !== requestId) return false;
|
||||
pendingRequestId = undefined;
|
||||
acceptedRequestId = requestId;
|
||||
return true;
|
||||
},
|
||||
consumeAcceptedRequest(): boolean {
|
||||
if (acceptedRequestId === undefined) return false;
|
||||
acceptedRequestId = undefined;
|
||||
return true;
|
||||
},
|
||||
reset() {
|
||||
pendingRequestId = undefined;
|
||||
acceptedRequestId = undefined;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -10,6 +10,7 @@ export interface SqlExecutionSnapshot {
|
||||
cursorPos: number;
|
||||
selectionFrom: number;
|
||||
selectionTo: number;
|
||||
editorViewportRequestId?: number;
|
||||
}
|
||||
|
||||
export type SqlExecutionOverride = string | SqlExecutionSnapshot;
|
||||
|
||||
@@ -3369,7 +3369,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
await executeCurrentSql(tab.sql);
|
||||
}
|
||||
|
||||
async function executeCurrentSql(sql: string, options?: { skipRedisSafetyCheck?: boolean; sourceOffset?: number; openInNewResultTab?: boolean }) {
|
||||
async function executeCurrentSql(sql: string, options?: { skipRedisSafetyCheck?: boolean; sourceOffset?: number; openInNewResultTab?: boolean; onExecutionStarted?: () => void }) {
|
||||
const executionTabId = activeTabId.value;
|
||||
if (!executionTabId) return;
|
||||
const tab = tabs.value.find((item) => item.id === executionTabId);
|
||||
@@ -3976,6 +3976,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
openInNewResultTab?: boolean;
|
||||
targetContext?: SqlExecutionTargetContext;
|
||||
executionTarget?: MultiDbExecutionTarget;
|
||||
onExecutionStarted?: () => void;
|
||||
},
|
||||
) {
|
||||
const tab = findExecutionTab(id);
|
||||
@@ -3993,6 +3994,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
const startedAt = performance.now();
|
||||
const elapsed = () => `${Math.round(performance.now() - startedAt)}ms`;
|
||||
tab.isExecuting = true;
|
||||
options?.onExecutionStarted?.();
|
||||
tab.isCancelling = false;
|
||||
if (!tab.queryExecutionStartedAt) {
|
||||
tab.queryExecutionStartedAt = Date.now();
|
||||
|
||||
Reference in New Issue
Block a user