fix(sidebar): preserve table reference drag while filtering
This commit is contained in:
parent
c78f451177
commit
af16c82568
|
|
@ -1788,7 +1788,7 @@ defineExpose({ focusSearch, createNewGroup, collapseAllTreeNodes });
|
|||
<TreeItem
|
||||
:node="item.node"
|
||||
:depth="item.depth"
|
||||
:drag-disabled="isRootListPartial || isConnectionListAlphabeticallySorted"
|
||||
:reorder-disabled="isRootListPartial || isConnectionListAlphabeticallySorted"
|
||||
:pending-rename="pendingRenameGroupId === item.node.id"
|
||||
:highlighted="highlightedNodeId === item.node.id"
|
||||
:comment-label-width="sidebarCommentLabelWidths.get(item.node.id)"
|
||||
|
|
@ -1799,7 +1799,14 @@ defineExpose({ focusSearch, createNewGroup, collapseAllTreeNodes });
|
|||
</template>
|
||||
</RecycleScroller>
|
||||
<div v-if="stickyNode" class="sticky-database-header pointer-events-auto absolute inset-x-0 top-0 z-[5] border-b border-border/60" :style="stickyHeaderStyle">
|
||||
<TreeItem :node="stickyNode.node" :depth="stickyNode.depth" :drag-disabled="true" :comment-label-width="sidebarCommentLabelWidths.get(stickyNode.node.id)" @context-menu="(event, node) => openSidebarContextMenu(event, node, contextMenuSlot.onContextMenu)" />
|
||||
<TreeItem
|
||||
:node="stickyNode.node"
|
||||
:depth="stickyNode.depth"
|
||||
:reorder-disabled="true"
|
||||
:reference-drag-disabled="true"
|
||||
:comment-label-width="sidebarCommentLabelWidths.get(stickyNode.node.id)"
|
||||
@context-menu="(event, node) => openSidebarContextMenu(event, node, contextMenuSlot.onContextMenu)"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="hasSidebarVerticalOverflow"
|
||||
|
|
@ -1828,7 +1835,7 @@ defineExpose({ focusSearch, createNewGroup, collapseAllTreeNodes });
|
|||
:key="item.id"
|
||||
:node="item.node"
|
||||
:depth="item.depth"
|
||||
:drag-disabled="isRootListPartial || isConnectionListAlphabeticallySorted"
|
||||
:reorder-disabled="isRootListPartial || isConnectionListAlphabeticallySorted"
|
||||
:pending-rename="pendingRenameGroupId === item.node.id"
|
||||
:highlighted="highlightedNodeId === item.id"
|
||||
:comment-label-width="sidebarCommentLabelWidths.get(item.node.id)"
|
||||
|
|
|
|||
|
|
@ -155,7 +155,8 @@ const useWindowsSidebarCommentFont = isWindows();
|
|||
const props = defineProps<{
|
||||
node: TreeNode;
|
||||
depth: number;
|
||||
dragDisabled?: boolean;
|
||||
reorderDisabled?: boolean;
|
||||
referenceDragDisabled?: boolean;
|
||||
pendingRename?: boolean;
|
||||
highlighted?: boolean;
|
||||
commentLabelWidth?: number;
|
||||
|
|
@ -797,7 +798,7 @@ function pinnedSortKey(): string {
|
|||
}
|
||||
|
||||
function canDragPinnedOrder(): boolean {
|
||||
return isPinned.value && !isNodeDefaultDatabase.value && !props.dragDisabled;
|
||||
return isPinned.value && !isNodeDefaultDatabase.value && !props.reorderDisabled;
|
||||
}
|
||||
|
||||
const {
|
||||
|
|
@ -818,8 +819,8 @@ const {
|
|||
connectionStore.reorderSidebarEntries(draggedIds, targetId, position);
|
||||
});
|
||||
|
||||
const isDraggable = computed(() => {
|
||||
if (props.dragDisabled) return false;
|
||||
const canReorderTreeNode = computed(() => {
|
||||
if (props.reorderDisabled) return false;
|
||||
return activeNode.value.type === "connection" || activeNode.value.type === "connection-group";
|
||||
});
|
||||
|
||||
|
|
@ -869,7 +870,7 @@ const TABLE_REFERENCE_DRAG_THRESHOLD = 5;
|
|||
const TABLE_REFERENCE_DRAGGING_CLASS = "dbx-table-reference-dragging";
|
||||
|
||||
const canDragTableReference = computed(() => {
|
||||
if (props.dragDisabled || !activeNode.value.connectionId) return false;
|
||||
if (props.referenceDragDisabled || !activeNode.value.connectionId) return false;
|
||||
if (activeNode.value.type === "database") return typeof activeNode.value.database === "string" && activeNode.value.database.trim().length > 0;
|
||||
if (activeNode.value.database == null) return false;
|
||||
if (activeNode.value.type === "table" || activeNode.value.type === "view" || activeNode.value.type === "materialized_view") return true;
|
||||
|
|
@ -986,7 +987,7 @@ function startTableReferenceMouseDrag(event: MouseEvent) {
|
|||
}
|
||||
|
||||
function onRowMouseDown(event: MouseEvent) {
|
||||
if (isDraggable.value) {
|
||||
if (canReorderTreeNode.value) {
|
||||
startDrag(event, activeNode.value.id, activeNode.value.type);
|
||||
} else if (canDragTableReference.value) {
|
||||
startTableReferenceMouseDrag(event);
|
||||
|
|
|
|||
149
apps/desktop/src/components/sidebar/__tests__/TreeItem.tableReferenceDrag.spec.ts
vendored
Normal file
149
apps/desktop/src/components/sidebar/__tests__/TreeItem.tableReferenceDrag.spec.ts
vendored
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
// @vitest-environment happy-dom
|
||||
|
||||
import { createApp, defineComponent, h, nextTick, type App } from "vue";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import i18n from "@/i18n";
|
||||
import TreeItem from "@/components/sidebar/TreeItem.vue";
|
||||
import { DBX_TABLE_REFERENCE_DROP_EVENT, type QueryEditorTableReferenceDropDetail } from "@/lib/editor/queryEditorTableDrop";
|
||||
import { createSidebarTreeRuntime, sidebarTreeRuntimeKey } from "@/lib/sidebar/sidebarTreeRuntime";
|
||||
import type { TreeNode } from "@/types/database";
|
||||
|
||||
const connectionStore = {
|
||||
activeConnectionId: "connection-1",
|
||||
connectedIds: new Set(["connection-1"]),
|
||||
connectingIds: new Set<string>(),
|
||||
connectionMultiSelectActive: false,
|
||||
connections: [],
|
||||
getConfig: () => ({ id: "connection-1", db_type: "sqlite" }),
|
||||
isDefaultDatabase: () => false,
|
||||
isPinnedTreeNodeReorderTarget: () => false,
|
||||
isTreeNodeChildrenLoaded: () => false,
|
||||
isTreeNodePinned: () => false,
|
||||
selectedTreeNodeId: null,
|
||||
selectedTreeNodeIds: [],
|
||||
selectedTreeNodeIdsSet: new Set<string>(),
|
||||
sidebarTableSearchQueries: {},
|
||||
tableNameFilterForScope: () => undefined,
|
||||
treeNodes: [],
|
||||
treeSelectionAnchorId: null,
|
||||
};
|
||||
|
||||
vi.mock("@/stores/connectionStore", () => ({
|
||||
useConnectionStore: () => connectionStore,
|
||||
}));
|
||||
|
||||
vi.mock("@/stores/queryStore", () => ({
|
||||
useQueryStore: () => ({ openDatabaseKeys: new Set<string>() }),
|
||||
}));
|
||||
|
||||
vi.mock("@/stores/settingsStore", () => ({
|
||||
useSettingsStore: () => ({
|
||||
editorSettings: {
|
||||
shortcuts: { openDataInNewTab: "" },
|
||||
sidebarActivation: "single",
|
||||
sidebarAllowHorizontalScroll: false,
|
||||
sidebarHiddenTablePrefixes: [],
|
||||
sidebarObjectInfoMode: "none",
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("@/composables/useToast", () => ({
|
||||
useToast: () => ({ toast: vi.fn() }),
|
||||
}));
|
||||
|
||||
const tableNode: TreeNode = {
|
||||
id: "table-orders",
|
||||
label: "orders",
|
||||
type: "table",
|
||||
connectionId: "connection-1",
|
||||
database: "main",
|
||||
};
|
||||
|
||||
const mountedApps: App[] = [];
|
||||
const dropListeners: EventListener[] = [];
|
||||
|
||||
async function mountTreeItem(props: { reorderDisabled?: boolean; referenceDragDisabled?: boolean }) {
|
||||
const container = document.createElement("div");
|
||||
document.body.append(container);
|
||||
const app = createApp(
|
||||
defineComponent({
|
||||
setup: () => () =>
|
||||
h(TreeItem, {
|
||||
node: tableNode,
|
||||
depth: 2,
|
||||
...props,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
mountedApps.push(app);
|
||||
app.use(i18n);
|
||||
app.provide(sidebarTreeRuntimeKey, createSidebarTreeRuntime());
|
||||
app.mount(container);
|
||||
await nextTick();
|
||||
|
||||
const row = container.querySelector<HTMLElement>("[tabindex]");
|
||||
if (!row) throw new Error(`Tree item row was not rendered: ${container.innerHTML}`);
|
||||
return row;
|
||||
}
|
||||
|
||||
function listenForTableReferenceDrop() {
|
||||
const listener = vi.fn<(event: Event) => void>();
|
||||
dropListeners.push(listener);
|
||||
window.addEventListener(DBX_TABLE_REFERENCE_DROP_EVENT, listener);
|
||||
return listener;
|
||||
}
|
||||
|
||||
function mockEditorDropTarget() {
|
||||
const editor = document.createElement("div");
|
||||
editor.dataset.queryEditorRoot = "";
|
||||
document.body.append(editor);
|
||||
vi.spyOn(document, "elementFromPoint").mockReturnValue(editor);
|
||||
}
|
||||
|
||||
function dragToEditor(row: HTMLElement) {
|
||||
row.dispatchEvent(new MouseEvent("mousedown", { bubbles: true, button: 0, clientX: 10, clientY: 10 }));
|
||||
document.dispatchEvent(new MouseEvent("mousemove", { bubbles: true, buttons: 1, clientX: 20, clientY: 20 }));
|
||||
document.dispatchEvent(new MouseEvent("mouseup", { bubbles: true, button: 0, clientX: 30, clientY: 30 }));
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
for (const listener of dropListeners.splice(0)) window.removeEventListener(DBX_TABLE_REFERENCE_DROP_EVENT, listener);
|
||||
for (const app of mountedApps.splice(0)) app.unmount();
|
||||
document.body.innerHTML = "";
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe("TreeItem table reference dragging", () => {
|
||||
it("keeps table reference dragging enabled when tree reordering is disabled", async () => {
|
||||
mockEditorDropTarget();
|
||||
const onDrop = listenForTableReferenceDrop();
|
||||
|
||||
const row = await mountTreeItem({ reorderDisabled: true });
|
||||
dragToEditor(row);
|
||||
|
||||
expect(onDrop).toHaveBeenCalledOnce();
|
||||
const detail = (onDrop.mock.calls[0][0] as CustomEvent<QueryEditorTableReferenceDropDetail>).detail;
|
||||
expect(detail).toEqual({
|
||||
payload: {
|
||||
kind: "dbx-table-reference",
|
||||
connectionId: "connection-1",
|
||||
database: "main",
|
||||
tableName: "orders",
|
||||
databaseType: "sqlite",
|
||||
},
|
||||
clientX: 30,
|
||||
clientY: 30,
|
||||
});
|
||||
});
|
||||
|
||||
it("does not start table reference dragging when it is explicitly disabled", async () => {
|
||||
mockEditorDropTarget();
|
||||
const onDrop = listenForTableReferenceDrop();
|
||||
|
||||
const row = await mountTreeItem({ referenceDragDisabled: true });
|
||||
dragToEditor(row);
|
||||
|
||||
expect(onDrop).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
@ -25,7 +25,7 @@ describe("sidebar filter guards", () => {
|
|||
expect(source).toContain("sidebarTableSearchEnabled && !isTreeSearchFiltering.value");
|
||||
expect(source).toContain("!useVirtualTree.value || isTreeSearchFiltering.value");
|
||||
expect(source.match(/if \(isRootListPartial\.value\)/g)).toHaveLength(2);
|
||||
expect(source.match(/:drag-disabled="isRootListPartial \|\| isConnectionListAlphabeticallySorted"/g)).toHaveLength(2);
|
||||
expect(source.match(/:reorder-disabled="isRootListPartial \|\| isConnectionListAlphabeticallySorted"/g)).toHaveLength(2);
|
||||
expect(source).not.toContain("isFiltering");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue