feat(grid): add split toolbar layout option
This commit is contained in:
1 parent
6dcd31c0d2
commit
645adff972
19 files changed
+182
-16
No files matched your search
@@ -88,6 +88,7 @@ import {
|
||||
type DeleteConnectionTabHandlingMode,
|
||||
type DataTabReuseMode,
|
||||
type DataGridFilterEditorView,
|
||||
type DataGridToolbarLayout,
|
||||
type MultiStatementDefaultView,
|
||||
type OpenTabsRestoreMode,
|
||||
type AppCloseUnsavedTabsMode,
|
||||
@@ -665,6 +666,7 @@ const editShowIndexIndicatorsInHeader = ref(settingsStore.editorSettings.showInd
|
||||
const editCompactColumnHeaderActions = ref(settingsStore.editorSettings.compactColumnHeaderActions);
|
||||
const editDataGridQuickEntry = ref(settingsStore.editorSettings.dataGridQuickEntry);
|
||||
const editDataGridFilterEditorView = ref<DataGridFilterEditorView>(settingsStore.editorSettings.dataGridFilterEditorView);
|
||||
const editDataGridToolbarLayout = ref<DataGridToolbarLayout>(settingsStore.editorSettings.dataGridToolbarLayout);
|
||||
const editDataGridKeepFilterEditorExpanded = ref(settingsStore.editorSettings.dataGridKeepFilterEditorExpanded);
|
||||
const dataGridFilterViewPreviewExpanded = ref(true);
|
||||
const editDataGridTextFilterPanelHeight = ref(settingsStore.editorSettings.dataGridTextFilterPanelHeight);
|
||||
@@ -1006,6 +1008,7 @@ function currentEditorSettingsDraft(): EditorSettingsDraft {
|
||||
compactColumnHeaderActions: editCompactColumnHeaderActions.value,
|
||||
dataGridQuickEntry: editDataGridQuickEntry.value,
|
||||
dataGridFilterEditorView: editDataGridFilterEditorView.value,
|
||||
dataGridToolbarLayout: editDataGridToolbarLayout.value,
|
||||
dataGridKeepFilterEditorExpanded: editDataGridKeepFilterEditorExpanded.value,
|
||||
dataGridTextFilterPanelHeight: editDataGridTextFilterPanelHeight.value,
|
||||
defaultAutoKeepResults: editDefaultAutoKeepResults.value,
|
||||
@@ -1649,6 +1652,7 @@ function syncEditorSettingsDraftFromStore() {
|
||||
editCompactColumnHeaderActions.value = settingsStore.editorSettings.compactColumnHeaderActions;
|
||||
editDataGridQuickEntry.value = settingsStore.editorSettings.dataGridQuickEntry;
|
||||
editDataGridFilterEditorView.value = settingsStore.editorSettings.dataGridFilterEditorView;
|
||||
editDataGridToolbarLayout.value = settingsStore.editorSettings.dataGridToolbarLayout;
|
||||
editDataGridKeepFilterEditorExpanded.value = settingsStore.editorSettings.dataGridKeepFilterEditorExpanded;
|
||||
editDataGridTextFilterPanelHeight.value = settingsStore.editorSettings.dataGridTextFilterPanelHeight;
|
||||
editDefaultAutoKeepResults.value = settingsStore.editorSettings.defaultAutoKeepResults;
|
||||
@@ -1780,6 +1784,7 @@ const editorSettingsDraftRefs: EditorSettingsDraftRefMap = {
|
||||
compactColumnHeaderActions: editCompactColumnHeaderActions,
|
||||
dataGridQuickEntry: editDataGridQuickEntry,
|
||||
dataGridFilterEditorView: editDataGridFilterEditorView,
|
||||
dataGridToolbarLayout: editDataGridToolbarLayout,
|
||||
dataGridKeepFilterEditorExpanded: editDataGridKeepFilterEditorExpanded,
|
||||
dataGridTextFilterPanelHeight: editDataGridTextFilterPanelHeight,
|
||||
defaultAutoKeepResults: editDefaultAutoKeepResults,
|
||||
@@ -2302,6 +2307,7 @@ function resetDefaultsForTab(tab: SettingsCategory) {
|
||||
editCompactColumnHeaderActions.value = DEFAULT_EDITOR_SETTINGS.compactColumnHeaderActions;
|
||||
editDataGridQuickEntry.value = DEFAULT_EDITOR_SETTINGS.dataGridQuickEntry;
|
||||
editDataGridFilterEditorView.value = DEFAULT_EDITOR_SETTINGS.dataGridFilterEditorView;
|
||||
editDataGridToolbarLayout.value = DEFAULT_EDITOR_SETTINGS.dataGridToolbarLayout;
|
||||
editDataGridKeepFilterEditorExpanded.value = DEFAULT_EDITOR_SETTINGS.dataGridKeepFilterEditorExpanded;
|
||||
editDataGridTextFilterPanelHeight.value = DEFAULT_EDITOR_SETTINGS.dataGridTextFilterPanelHeight;
|
||||
editDefaultAutoKeepResults.value = DEFAULT_EDITOR_SETTINGS.defaultAutoKeepResults;
|
||||
@@ -2407,6 +2413,7 @@ function resetAllDefaults() {
|
||||
editCompactColumnHeaderActions.value = DEFAULT_EDITOR_SETTINGS.compactColumnHeaderActions;
|
||||
editDataGridQuickEntry.value = DEFAULT_EDITOR_SETTINGS.dataGridQuickEntry;
|
||||
editDataGridFilterEditorView.value = DEFAULT_EDITOR_SETTINGS.dataGridFilterEditorView;
|
||||
editDataGridToolbarLayout.value = DEFAULT_EDITOR_SETTINGS.dataGridToolbarLayout;
|
||||
editDataGridKeepFilterEditorExpanded.value = DEFAULT_EDITOR_SETTINGS.dataGridKeepFilterEditorExpanded;
|
||||
editDataGridTextFilterPanelHeight.value = DEFAULT_EDITOR_SETTINGS.dataGridTextFilterPanelHeight;
|
||||
editDefaultAutoKeepResults.value = DEFAULT_EDITOR_SETTINGS.defaultAutoKeepResults;
|
||||
@@ -7537,6 +7544,21 @@ onUnmounted(() => {
|
||||
<Button type="button" variant="outline" size="sm" @click="historyRetention.load">{{ t("common.retry") }}</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="data-grid-toolbar-layout" data-settings-search-id="data-grid-toolbar-layout" :class="['settings-item flex items-center justify-between gap-4 rounded-md border bg-muted/20 px-3 py-2', settingsSearchTargetClass('data-grid-toolbar-layout')]">
|
||||
<div class="min-w-0 space-y-1">
|
||||
<Label for="data-grid-toolbar-layout-select">{{ t("settings.dataGridToolbarLayout") }}</Label>
|
||||
<p class="text-xs text-muted-foreground">{{ t("settings.dataGridToolbarLayoutDescription") }}</p>
|
||||
</div>
|
||||
<Select v-model="editDataGridToolbarLayout">
|
||||
<SelectTrigger id="data-grid-toolbar-layout-select" class="w-48 shrink-0">
|
||||
<SelectValue :placeholder="t('settings.dataGridToolbarLayout')" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="split">{{ t("settings.dataGridToolbarLayoutSplit") }}</SelectItem>
|
||||
<SelectItem value="single">{{ t("settings.dataGridToolbarLayoutSingle") }}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div data-settings-search-id="data-grid-filter-view" :class="['overflow-hidden rounded-md border bg-muted/20', settingsSearchTargetClass('data-grid-filter-view')]">
|
||||
<div class="space-y-3 p-3">
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
|
||||
@@ -758,6 +758,7 @@ const compactColumnHeaderActions = computed(() => settingsStore.editorSettings.c
|
||||
const dataGridRenderMode = computed(() => settingsStore.editorSettings.dataGridRenderMode);
|
||||
const dataGridSearchMode = computed(() => settingsStore.editorSettings.dataGridSearchMode);
|
||||
const compactDataGridToolbar = computed(() => dataGridTopbarOverflowCompact.value || isDataGridToolbarCompact(dataGridTopbarWidth.value, dataGridViewportWidth.value, DATA_GRID_CONDITION_TOOLBAR_MIN_WIDTH));
|
||||
const splitDataGridToolbar = computed(() => settingsStore.editorSettings.dataGridToolbarLayout === "split");
|
||||
const responsiveDataGridToolbarActionCount = computed(() => dataGridToolbarActionCollapseCount(dataGridTopbarWidth.value, dataGridViewportWidth.value, DATA_GRID_CONDITION_TOOLBAR_MIN_WIDTH));
|
||||
const compactDataGridToolbarActionCount = computed(() => Math.max(responsiveDataGridToolbarActionCount.value, dataGridTopbarOverflowActionCount.value));
|
||||
const infiniteScrollEnabled = computed(() => props.paginationEnabled && settingsStore.editorSettings.infiniteScroll);
|
||||
@@ -11974,12 +11975,18 @@ useUpdateBlocker(() => (hasPendingChanges.value || hasPendingDataEditorDraft.val
|
||||
<div v-if="hasData || canShowWhereSearch" class="flex-1 flex flex-col overflow-hidden" @contextmenu="onContextMenu">
|
||||
<!-- Search bar -->
|
||||
<!-- Leave real vertical space around the 28px controls instead of fitting them against the border. -->
|
||||
<div ref="dataGridTopbarRef" v-if="showDataGridTopbar" class="data-grid-topbar-shell flex h-8 min-w-0 shrink-0 items-center border-b bg-muted/20">
|
||||
<div v-if="hasResultToolbarLeadingSlot" class="flex shrink-0 items-center border-r">
|
||||
<div
|
||||
ref="dataGridTopbarRef"
|
||||
v-if="showDataGridTopbar"
|
||||
:data-grid-toolbar-layout="settingsStore.editorSettings.dataGridToolbarLayout"
|
||||
class="data-grid-topbar-shell min-w-0 shrink-0 border-b bg-muted/20"
|
||||
:class="splitDataGridToolbar ? 'grid h-16 grid-cols-[auto_minmax(0,1fr)] grid-rows-2' : 'flex h-8 items-center'"
|
||||
>
|
||||
<div v-if="hasResultToolbarLeadingSlot" data-grid-topbar-row="actions" class="flex shrink-0 items-center border-r" :class="splitDataGridToolbar ? 'col-start-1 row-start-1' : ''">
|
||||
<slot name="result-toolbar-leading" :compact="compactDataGridToolbar" />
|
||||
</div>
|
||||
<!-- Clip both axes instead of creating a hidden scroll container around the toolbar controls. -->
|
||||
<div class="data-grid-topbar-scroll min-w-0 flex-1 overflow-clip">
|
||||
<div data-grid-topbar-row="filters" class="data-grid-topbar-scroll min-w-0 overflow-clip" :class="splitDataGridToolbar ? 'data-grid-topbar-scroll--row-divider col-span-2 row-start-2' : 'flex-1'">
|
||||
<div class="data-grid-topbar flex items-stretch relative" :class="{ 'data-grid-topbar--compact': compactDataGridToolbar }">
|
||||
<div v-if="useTransaction && editable && hasDataGridSaveTarget" class="flex items-center px-2 py-0.5 border-r shrink-0">
|
||||
<Select :model-value="rowStatusFilter" @update:model-value="(value: any) => setRowStatusFilter(String(value))">
|
||||
@@ -12049,7 +12056,8 @@ useUpdateBlocker(() => (hasPendingChanges.value || hasPendingDataEditorDraft.val
|
||||
</div>
|
||||
|
||||
<DataGridToolbar
|
||||
class="ml-auto"
|
||||
data-grid-topbar-row="actions"
|
||||
:class="splitDataGridToolbar ? 'col-start-2 row-start-1' : 'ml-auto'"
|
||||
:compact-action-count="compactDataGridToolbarActionCount"
|
||||
:navigation-visible="props.result.columns.length > 0"
|
||||
:refresh="refreshToolbarCapability"
|
||||
@@ -14343,6 +14351,20 @@ useUpdateBlocker(() => (hasPendingChanges.value || hasPendingDataEditorDraft.val
|
||||
scrollbar-gutter: auto;
|
||||
}
|
||||
|
||||
.data-grid-topbar-scroll--row-divider {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.data-grid-topbar-scroll--row-divider::before {
|
||||
position: absolute;
|
||||
inset: 0 0 auto;
|
||||
z-index: 1;
|
||||
height: 1px;
|
||||
background-color: var(--border);
|
||||
content: "";
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.data-grid-topbar-scroll::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
+71
-7
@@ -50,7 +50,7 @@ const RecycleScroller = defineComponent({
|
||||
},
|
||||
});
|
||||
|
||||
function mountGrid(options: { displayableColumns?: boolean; columns?: string[] } = {}) {
|
||||
function mountGrid(options: { displayableColumns?: boolean; columns?: string[]; slots?: Record<string, () => ReturnType<typeof h>>; withTableMetadata?: boolean } = {}) {
|
||||
const pinia = createPinia();
|
||||
setActivePinia(pinia);
|
||||
const settingsStore = useSettingsStore();
|
||||
@@ -79,11 +79,22 @@ function mountGrid(options: { displayableColumns?: boolean; columns?: string[] }
|
||||
{ delayDuration: 0 },
|
||||
{
|
||||
default: () =>
|
||||
h(DataGrid, {
|
||||
result,
|
||||
databaseType: "mysql",
|
||||
context: "table-data",
|
||||
}),
|
||||
h(
|
||||
DataGrid,
|
||||
{
|
||||
result,
|
||||
databaseType: "mysql",
|
||||
context: "table-data",
|
||||
...(options.withTableMetadata
|
||||
? {
|
||||
connectionId: "test-connection",
|
||||
database: "test-database",
|
||||
tableMeta: { tableName: "users", schema: "test-database", columns: [], primaryKeys: [] },
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
options.slots,
|
||||
),
|
||||
},
|
||||
);
|
||||
},
|
||||
@@ -95,7 +106,7 @@ function mountGrid(options: { displayableColumns?: boolean; columns?: string[] }
|
||||
app.mount(host);
|
||||
const mounted = { app, host };
|
||||
mountedApps.push(mounted);
|
||||
return mounted;
|
||||
return { ...mounted, settingsStore };
|
||||
}
|
||||
|
||||
async function settle() {
|
||||
@@ -129,6 +140,7 @@ function goToColumnEvent(target: HTMLElement): KeyboardEvent {
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
for (const { app, host } of mountedApps.splice(0)) {
|
||||
app.unmount();
|
||||
host.remove();
|
||||
@@ -142,6 +154,58 @@ function functionBody(name: string, nextName: string): string {
|
||||
}
|
||||
|
||||
describe("DataGrid go-to-column shortcut", () => {
|
||||
it("switches between the split and original single-row toolbar layouts", async () => {
|
||||
const { host, settingsStore } = mountGrid();
|
||||
await settle();
|
||||
|
||||
const topbar = host.querySelector<HTMLElement>("[data-grid-toolbar-layout]");
|
||||
expect(topbar?.dataset.gridToolbarLayout).toBe("single");
|
||||
|
||||
settingsStore.updateEditorSettings({ dataGridToolbarLayout: "split" });
|
||||
await settle();
|
||||
|
||||
expect(topbar?.dataset.gridToolbarLayout).toBe("split");
|
||||
expect(topbar?.classList.contains("grid")).toBe(true);
|
||||
|
||||
settingsStore.updateEditorSettings({ dataGridToolbarLayout: "single" });
|
||||
await settle();
|
||||
|
||||
expect(topbar?.dataset.gridToolbarLayout).toBe("single");
|
||||
expect(topbar?.classList.contains("flex")).toBe(true);
|
||||
expect(host.querySelector('[data-grid-topbar-row="actions"].ml-auto')).not.toBeNull();
|
||||
|
||||
settingsStore.updateEditorSettings({ dataGridToolbarLayout: "split" });
|
||||
await settle();
|
||||
|
||||
expect(topbar?.dataset.gridToolbarLayout).toBe("split");
|
||||
expect(topbar?.classList.contains("grid")).toBe(true);
|
||||
expect(host.querySelector('[data-grid-topbar-row="filters"].data-grid-topbar-scroll--row-divider')).not.toBeNull();
|
||||
});
|
||||
|
||||
it("does not show a DDL action in the result action toolbar", async () => {
|
||||
const { host } = mountGrid({ withTableMetadata: true });
|
||||
await settle();
|
||||
|
||||
expect(host.querySelector('[data-toolbar-action="tableInfo"]')).toBeNull();
|
||||
});
|
||||
|
||||
it("keeps result actions and query filters in their separate toolbar rows", async () => {
|
||||
const { host } = mountGrid({
|
||||
slots: {
|
||||
"result-toolbar-leading": () => h("span", { "data-testid": "result-view" }, "结果视图"),
|
||||
"result-toolbar-actions": () => h("span", { "data-testid": "result-actions" }, "结果操作"),
|
||||
"search-bar": () => h("span", { "data-testid": "search-controls" }, "文档筛选"),
|
||||
},
|
||||
});
|
||||
await settle();
|
||||
|
||||
const actionRowSelector = '[data-grid-topbar-row="actions"]';
|
||||
const filterRowSelector = '[data-grid-topbar-row="filters"]';
|
||||
expect(host.querySelector('[data-testid="result-view"]')?.closest(actionRowSelector)).not.toBeNull();
|
||||
expect(host.querySelector('[data-testid="result-actions"]')?.closest(actionRowSelector)).not.toBeNull();
|
||||
expect(host.querySelector('[data-testid="search-controls"]')?.closest(filterRowSelector)).not.toBeNull();
|
||||
});
|
||||
|
||||
it("opens and consumes the configured shortcut when a column is displayable", async () => {
|
||||
const { host } = mountGrid();
|
||||
await settle();
|
||||
|
||||
Reference in new issue
Block a user