feat(grid): add go-to-column toolbar action
This commit is contained in:
1 parent
433907e96e
commit
9fc2e25fbf
3 files changed
+72
No files matched your search
@@ -63,6 +63,7 @@ import {
|
|||||||
PanelRight,
|
PanelRight,
|
||||||
TableProperties,
|
TableProperties,
|
||||||
Database,
|
Database,
|
||||||
|
Columns3,
|
||||||
} from "@lucide/vue";
|
} from "@lucide/vue";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import QueryLoadingState from "@/components/common/QueryLoadingState.vue";
|
import QueryLoadingState from "@/components/common/QueryLoadingState.vue";
|
||||||
@@ -1648,6 +1649,9 @@ const nullColumnsHidden = ref(false);
|
|||||||
const autoHiddenNullColumnIndexes = ref<Set<number>>(new Set());
|
const autoHiddenNullColumnIndexes = ref<Set<number>>(new Set());
|
||||||
const highlightedColumnIndex = ref<number | null>(null);
|
const highlightedColumnIndex = ref<number | null>(null);
|
||||||
let highlightedColumnTimer = 0;
|
let highlightedColumnTimer = 0;
|
||||||
|
|
||||||
|
const goToColumnOpen = ref(false);
|
||||||
|
const goToColumnSearch = ref("");
|
||||||
const columnOrderKeys = computed(() => uniqueDataGridColumnOrderKeys(props.result.columns, props.sourceColumns));
|
const columnOrderKeys = computed(() => uniqueDataGridColumnOrderKeys(props.result.columns, props.sourceColumns));
|
||||||
const columnLayoutScopeKey = computed(() =>
|
const columnLayoutScopeKey = computed(() =>
|
||||||
dataGridColumnLayoutScopeKey({
|
dataGridColumnLayoutScopeKey({
|
||||||
@@ -1669,6 +1673,18 @@ const displayableColumnIndexes = computed(() =>
|
|||||||
.filter(({ column }) => !isHiddenGridColumn(props.databaseType, column, props.tableMeta?.primaryKeys ?? []))
|
.filter(({ column }) => !isHiddenGridColumn(props.databaseType, column, props.tableMeta?.primaryKeys ?? []))
|
||||||
.map(({ index }) => index),
|
.map(({ index }) => index),
|
||||||
);
|
);
|
||||||
|
const goToColumnItems = computed(() =>
|
||||||
|
displayableColumnIndexes.value.map((index) => ({
|
||||||
|
index,
|
||||||
|
name: props.result.columns[index] ?? `#${index + 1}`,
|
||||||
|
sourceName: props.sourceColumns?.[index],
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
const filteredGoToColumns = computed(() => {
|
||||||
|
const query = goToColumnSearch.value.trim().toLocaleLowerCase();
|
||||||
|
if (!query) return goToColumnItems.value;
|
||||||
|
return goToColumnItems.value.filter((column) => column.name.toLocaleLowerCase().includes(query) || column.sourceName?.toLocaleLowerCase().includes(query));
|
||||||
|
});
|
||||||
const orderedDisplayableColumnIndexes = computed(() =>
|
const orderedDisplayableColumnIndexes = computed(() =>
|
||||||
orderedColumnIndexes({
|
orderedColumnIndexes({
|
||||||
availableIndexes: displayableColumnIndexes.value,
|
availableIndexes: displayableColumnIndexes.value,
|
||||||
@@ -1780,12 +1796,33 @@ const firstVisibleColumnIndex = computed(() => visibleColumnIndexes.value[0] ??
|
|||||||
function actualColumnIndex(visibleColumnIndex: number): number {
|
function actualColumnIndex(visibleColumnIndex: number): number {
|
||||||
return visibleColumnIndexes.value[visibleColumnIndex] ?? visibleColumnIndex;
|
return visibleColumnIndexes.value[visibleColumnIndex] ?? visibleColumnIndex;
|
||||||
}
|
}
|
||||||
|
function scrollToColumn(columnIndex: number) {
|
||||||
|
goToColumnOpen.value = false;
|
||||||
|
goToColumnSearch.value = "";
|
||||||
|
scrollToColumnIndex(columnIndex);
|
||||||
|
gridRef.value?.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onGoToColumnKeydown(event: KeyboardEvent) {
|
||||||
|
if (event.key === "Escape") {
|
||||||
|
goToColumnOpen.value = false;
|
||||||
|
goToColumnSearch.value = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(goToColumnOpen, (open) => {
|
||||||
|
if (!open) goToColumnSearch.value = "";
|
||||||
|
});
|
||||||
|
|
||||||
function matchesTableInfoColumn(resultColumn: string, sourceColumn: string | undefined, columnName: string): boolean {
|
function matchesTableInfoColumn(resultColumn: string, sourceColumn: string | undefined, columnName: string): boolean {
|
||||||
const target = columnName.toLocaleLowerCase();
|
const target = columnName.toLocaleLowerCase();
|
||||||
return resultColumn.toLocaleLowerCase() === target || sourceColumn?.toLocaleLowerCase() === target;
|
return resultColumn.toLocaleLowerCase() === target || sourceColumn?.toLocaleLowerCase() === target;
|
||||||
}
|
}
|
||||||
function scrollToTableInfoColumn(columnName: string) {
|
function scrollToTableInfoColumn(columnName: string) {
|
||||||
const columnIndex = props.result.columns.findIndex((column, index) => matchesTableInfoColumn(column, props.sourceColumns?.[index], columnName));
|
const columnIndex = props.result.columns.findIndex((column, index) => matchesTableInfoColumn(column, props.sourceColumns?.[index], columnName));
|
||||||
|
scrollToColumnIndex(columnIndex);
|
||||||
|
}
|
||||||
|
function scrollToColumnIndex(columnIndex: number) {
|
||||||
if (columnIndex < 0 || !displayableColumnIndexes.value.includes(columnIndex)) return;
|
if (columnIndex < 0 || !displayableColumnIndexes.value.includes(columnIndex)) return;
|
||||||
|
|
||||||
if (hiddenColumnIndexes.value.has(columnIndex)) {
|
if (hiddenColumnIndexes.value.has(columnIndex)) {
|
||||||
@@ -7165,6 +7202,35 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
|||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="bottom" class="max-w-sm"> {{ dataGridRenderMode === "canvas" ? t("grid.canvasRenderMode") : t("grid.domRenderMode") }} · {{ t("grid.renderModeHint") }} </TooltipContent>
|
<TooltipContent side="bottom" class="max-w-sm"> {{ dataGridRenderMode === "canvas" ? t("grid.canvasRenderMode") : t("grid.domRenderMode") }} · {{ t("grid.renderModeHint") }} </TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
<Tooltip v-if="props.result.columns.length">
|
||||||
|
<TooltipTrigger as-child>
|
||||||
|
<Popover v-model:open="goToColumnOpen">
|
||||||
|
<PopoverTrigger as-child>
|
||||||
|
<Button variant="ghost" size="sm" :class="['data-grid-topbar-action-button h-5 shrink-0 text-xs px-1.5', compactDataGridToolbar ? 'data-grid-topbar-action-button--compact' : '', goToColumnOpen ? 'text-primary bg-primary/10' : '']">
|
||||||
|
<Columns3 class="data-grid-topbar-action-icon w-3 h-3" />
|
||||||
|
<span class="data-grid-topbar-action-label" :class="{ 'data-grid-topbar-action-label--compact': compactDataGridToolbar }">{{ t("grid.goToColumn") }}</span>
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent align="end" class="w-56 p-2" @keydown="onGoToColumnKeydown">
|
||||||
|
<div class="relative mb-1">
|
||||||
|
<Search class="pointer-events-none absolute left-2 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground" />
|
||||||
|
<input v-model="goToColumnSearch" :placeholder="t('grid.searchColumn')" class="h-8 w-full rounded-md border bg-transparent pl-7 pr-6 text-xs outline-none focus-visible:border-ring/50 focus-visible:ring-1 focus-visible:ring-ring/25" />
|
||||||
|
<button v-if="goToColumnSearch" type="button" class="absolute right-1.5 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground" @click="goToColumnSearch = ''">
|
||||||
|
<X class="h-3.5 w-3.5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="max-h-56 overflow-auto rounded border">
|
||||||
|
<button v-for="column in filteredGoToColumns" :key="column.index" type="button" class="flex w-full items-center gap-1.5 px-2 py-1.5 text-left text-xs hover:bg-accent hover:text-accent-foreground" @click="scrollToColumn(column.index)">
|
||||||
|
<span class="min-w-0 truncate">{{ column.name }}</span>
|
||||||
|
<span class="ml-auto shrink-0 font-mono text-[10px] text-muted-foreground">#{{ column.index + 1 }}</span>
|
||||||
|
</button>
|
||||||
|
<div v-if="!filteredGoToColumns.length" class="px-2 py-3 text-center text-xs text-muted-foreground">{{ t("grid.noColumnsFound") }}</div>
|
||||||
|
</div>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="bottom">{{ t("grid.goToColumn") }}</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
<Tooltip v-if="editable && (tableMeta || customSaveHandler)">
|
<Tooltip v-if="editable && (tableMeta || customSaveHandler)">
|
||||||
<TooltipTrigger as-child>
|
<TooltipTrigger as-child>
|
||||||
<Button variant="ghost" size="sm" :class="['data-grid-topbar-action-button h-5 shrink-0 text-xs px-1.5', compactDataGridToolbar ? 'data-grid-topbar-action-button--compact' : '']" @click="addRow">
|
<Button variant="ghost" size="sm" :class="['data-grid-topbar-action-button h-5 shrink-0 text-xs px-1.5', compactDataGridToolbar ? 'data-grid-topbar-action-button--compact' : '']" @click="addRow">
|
||||||
|
|||||||
@@ -804,6 +804,9 @@ export default {
|
|||||||
tableInfoEmpty: "No metadata",
|
tableInfoEmpty: "No metadata",
|
||||||
tableInfoSearch: "Search...",
|
tableInfoSearch: "Search...",
|
||||||
tableInfoNoResults: "No results",
|
tableInfoNoResults: "No results",
|
||||||
|
goToColumn: "Go to column",
|
||||||
|
searchColumn: "Search column...",
|
||||||
|
noColumnsFound: "No columns found",
|
||||||
queryError: "Query Error",
|
queryError: "Query Error",
|
||||||
dataUnavailable: "Table data needs to be reloaded.",
|
dataUnavailable: "Table data needs to be reloaded.",
|
||||||
dataUnavailableHintPrefix: "Press ",
|
dataUnavailableHintPrefix: "Press ",
|
||||||
|
|||||||
@@ -807,6 +807,9 @@ export default withEnglishFallback({
|
|||||||
tableInfoEmpty: "暂无元数据",
|
tableInfoEmpty: "暂无元数据",
|
||||||
tableInfoSearch: "搜索...",
|
tableInfoSearch: "搜索...",
|
||||||
tableInfoNoResults: "无匹配结果",
|
tableInfoNoResults: "无匹配结果",
|
||||||
|
goToColumn: "跳转列",
|
||||||
|
searchColumn: "搜索列...",
|
||||||
|
noColumnsFound: "未找到列",
|
||||||
queryError: "查询出错",
|
queryError: "查询出错",
|
||||||
dataUnavailable: "表数据需要重新加载。",
|
dataUnavailable: "表数据需要重新加载。",
|
||||||
dataUnavailableHintPrefix: "按 ",
|
dataUnavailableHintPrefix: "按 ",
|
||||||
|
|||||||
Reference in new issue
Block a user