mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-28 16:09:17 +00:00
feat(grid): optimize transpose view toggle
This commit is contained in:
@@ -647,7 +647,7 @@ watch(
|
||||
</nav>
|
||||
|
||||
<div class="min-w-0 flex-1 overflow-hidden px-1 flex flex-col">
|
||||
<div class="min-h-0 flex-1 overflow-y-auto overflow-x-hidden pr-1">
|
||||
<div class="min-h-0 flex-1 overflow-y-auto overflow-x-hidden px-1 pr-2">
|
||||
<section v-if="activeSettingsTab === 'editor'" class="flex flex-col gap-5 py-2">
|
||||
<div class="grid gap-4 md:grid-cols-[minmax(0,1fr)_220px]">
|
||||
<!-- Font Family -->
|
||||
@@ -847,7 +847,7 @@ watch(
|
||||
<textarea
|
||||
id="sidebar-hidden-table-prefixes"
|
||||
v-model="editSidebarHiddenTablePrefixes"
|
||||
class="min-h-24 w-full rounded-md border border-input bg-background px-3 py-2 text-sm outline-none transition-colors placeholder:text-muted-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
||||
class="min-h-24 w-full rounded-md border border-input bg-background px-3 py-2 text-sm outline-none transition-colors placeholder:text-muted-foreground focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring"
|
||||
:placeholder="t('settings.sidebarHiddenTablePrefixesPlaceholder')"
|
||||
/>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
|
||||
@@ -90,8 +90,9 @@ import {
|
||||
} from "@/lib/tableEditing";
|
||||
import { formatGridSqlLiteral } from "@/lib/dataGridSql";
|
||||
import {
|
||||
buildTransposeRows,
|
||||
buildVisibleTransposeRows,
|
||||
nextContextTransposeState,
|
||||
nextKeyboardTransposeState,
|
||||
nextTransposeState,
|
||||
transposeFieldWidth,
|
||||
transposeScrollLeftForRecord,
|
||||
@@ -118,7 +119,7 @@ import {
|
||||
type DateTimeFormatterUnit,
|
||||
} from "@/lib/columnFormatter";
|
||||
import { temporalCellEditorKind, type TemporalCellEditorKind } from "@/lib/dataGridTemporalEditor";
|
||||
import { isCancelSearchShortcut, isFocusSearchShortcut } from "@/lib/keyboardShortcuts";
|
||||
import { isCancelSearchShortcut, isFocusSearchShortcut, isToggleTransposeShortcut } from "@/lib/keyboardShortcuts";
|
||||
import { dataGridHeaderContentWidth, scrollbarGutterWidth } from "@/lib/dataGridScrollGutter";
|
||||
import { dataGridSaveActionMode, dataGridSaveToolbarState } from "@/lib/dataGridSaveUi";
|
||||
import { appendColumnValueFilterCondition, buildColumnValueFilterCondition } from "@/lib/dataGridColumnFilter";
|
||||
@@ -2258,6 +2259,33 @@ function scrollCellIntoView(rowIndex: number, colIndex: number) {
|
||||
});
|
||||
}
|
||||
|
||||
function currentTransposeRequestedRowIndex(): number {
|
||||
const position = currentSelectedCellPosition();
|
||||
if (position) return position.rowIndex;
|
||||
if (transposeRowIndex.value !== null) return transposeRowIndex.value;
|
||||
return 0;
|
||||
}
|
||||
|
||||
function toggleKeyboardTranspose(): boolean {
|
||||
if (displayItems.value.length === 0) return false;
|
||||
const next = nextKeyboardTransposeState({
|
||||
showTranspose: showTranspose.value,
|
||||
transposeRowIndex: transposeRowIndex.value,
|
||||
requestedRowIndex: currentTransposeRequestedRowIndex(),
|
||||
rowIds: displayItems.value.map((item) => item.id),
|
||||
selectedRowIds: selectedRowIds.value,
|
||||
selectedRange: selectedRange.value,
|
||||
});
|
||||
showTranspose.value = next.showTranspose;
|
||||
transposeRowIndex.value = next.transposeRowIndex;
|
||||
if (next.showTranspose) {
|
||||
closeCellDetails();
|
||||
nextTick(updateTransposeViewport);
|
||||
if (next.transposeRowIndex !== null) scrollTransposeRecordIntoView(next.transposeRowIndex);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function moveSelectedCell(rowDelta: number, colDelta: number): boolean {
|
||||
const position = currentSelectedCellPosition();
|
||||
if (!position || editingCell.value || displayItems.value.length === 0 || visibleColumnIndexes.value.length === 0)
|
||||
@@ -2293,6 +2321,10 @@ async function onGridKeydown(event: KeyboardEvent) {
|
||||
return;
|
||||
}
|
||||
if (eventTargetAllowsNativeClipboard(event)) return;
|
||||
if (isToggleTransposeShortcut(event, settingsStore.editorSettings.shortcuts) && toggleKeyboardTranspose()) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
if (event.key === "ArrowLeft" && moveTransposeRecordSelection(-1)) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
@@ -2375,15 +2407,6 @@ const transposePinnedWidth = computed(
|
||||
() => transposePinnedWidthOverride.value ?? transposeFieldWidth(visibleColumns.value),
|
||||
);
|
||||
|
||||
const transposeRows = computed(() => {
|
||||
return buildTransposeRows({
|
||||
columns: visibleColumns.value,
|
||||
records: displayItems.value.map((item) => visibleColumnIndexes.value.map((columnIndex) => item.data[columnIndex])),
|
||||
typeByColumn: columnTypeMap.value,
|
||||
displayValue: (value, _column, index) => formatCell(value, visibleColumnIndexes.value[index]),
|
||||
});
|
||||
});
|
||||
const isTransposeMode = computed(() => showTranspose.value && transposeRows.value.length > 0);
|
||||
const transposeRecordWindow = computed(() =>
|
||||
visibleTransposeRecordWindow({
|
||||
totalRecords: displayItems.value.length,
|
||||
@@ -2398,6 +2421,17 @@ const visibleTransposeRecordIndexes = computed(() => {
|
||||
const window = transposeRecordWindow.value;
|
||||
return Array.from({ length: window.end - window.start }, (_, offset) => window.start + offset);
|
||||
});
|
||||
const transposeRows = computed(() => {
|
||||
return buildVisibleTransposeRows({
|
||||
columns: visibleColumns.value,
|
||||
records: displayItems.value.map((item) => item.data),
|
||||
recordIndexes: visibleTransposeRecordIndexes.value,
|
||||
valueIndexes: visibleColumnIndexes.value,
|
||||
typeByColumn: columnTypeMap.value,
|
||||
displayValue: (value, _column, index) => formatCell(value, visibleColumnIndexes.value[index]),
|
||||
});
|
||||
});
|
||||
const isTransposeMode = computed(() => showTranspose.value && transposeRows.value.length > 0);
|
||||
const transposeTotalWidth = computed(
|
||||
() => transposePinnedWidth.value + displayItems.value.length * transposeRecordWidth.value,
|
||||
);
|
||||
@@ -3344,18 +3378,18 @@ defineExpose({
|
||||
</div>
|
||||
<div class="shrink-0" :style="{ width: `${transposeRecordWindow.beforeWidth}px` }" />
|
||||
<div
|
||||
v-for="recordIndex in visibleTransposeRecordIndexes"
|
||||
:key="`${item.id}:${recordIndex}`"
|
||||
v-for="cell in item.values"
|
||||
:key="`${item.id}:${cell.recordIndex}`"
|
||||
class="shrink-0 border-r border-border/70 px-2 py-1.5 font-mono truncate"
|
||||
:class="{
|
||||
'text-muted-foreground italic': item.values[recordIndex]?.isNull,
|
||||
'bg-primary/10': recordIndex === transposeRowIndex,
|
||||
'text-muted-foreground italic': cell.isNull,
|
||||
'bg-primary/10': cell.recordIndex === transposeRowIndex,
|
||||
}"
|
||||
:style="{ width: `${transposeRecordWidth}px` }"
|
||||
:title="item.values[recordIndex]?.display"
|
||||
@click="selectTransposeRecord(recordIndex)"
|
||||
:title="cell.display"
|
||||
@click="selectTransposeRecord(cell.recordIndex)"
|
||||
>
|
||||
{{ item.values[recordIndex]?.display }}
|
||||
{{ cell.display }}
|
||||
</div>
|
||||
<div class="shrink-0" :style="{ width: `${transposeRecordWindow.afterWidth}px` }" />
|
||||
</div>
|
||||
|
||||
@@ -1219,9 +1219,11 @@ export default {
|
||||
shortcutCloseTab: "Close tab",
|
||||
shortcutFocusSearch: "Focus search",
|
||||
shortcutRefreshData: "Refresh data",
|
||||
shortcutToggleTranspose: "Toggle transpose view",
|
||||
shortcutCancelSearch: "Cancel search",
|
||||
shortcutScopeGlobal: "Global",
|
||||
shortcutScopeEditor: "SQL editor",
|
||||
shortcutScopeGrid: "Data grid",
|
||||
shortcutScopeSearch: "Search fields",
|
||||
shortcutPressShortcut: "Press shortcut",
|
||||
shortcutConflict: "This shortcut conflicts with another action in the same scope.",
|
||||
|
||||
@@ -1117,9 +1117,11 @@ export default {
|
||||
shortcutCloseTab: "Cerrar pestaña",
|
||||
shortcutFocusSearch: "Enfocar búsqueda",
|
||||
shortcutRefreshData: "Actualizar datos",
|
||||
shortcutToggleTranspose: "Alternar vista transpuesta",
|
||||
shortcutCancelSearch: "Cancelar búsqueda",
|
||||
shortcutScopeGlobal: "Global",
|
||||
shortcutScopeEditor: "Editor SQL",
|
||||
shortcutScopeGrid: "Cuadrícula de datos",
|
||||
shortcutScopeSearch: "Campos de búsqueda",
|
||||
shortcutPressShortcut: "Presiona un atajo",
|
||||
shortcutConflict: "Este atajo entra en conflicto con otra acción del mismo ámbito.",
|
||||
|
||||
@@ -1197,9 +1197,11 @@ export default {
|
||||
shortcutCloseTab: "关闭标签页",
|
||||
shortcutFocusSearch: "聚焦搜索",
|
||||
shortcutRefreshData: "刷新数据",
|
||||
shortcutToggleTranspose: "切换转置视图",
|
||||
shortcutCancelSearch: "取消搜索",
|
||||
shortcutScopeGlobal: "全局",
|
||||
shortcutScopeEditor: "SQL 编辑器",
|
||||
shortcutScopeGrid: "数据表格",
|
||||
shortcutScopeSearch: "搜索框",
|
||||
shortcutPressShortcut: "按下快捷键",
|
||||
shortcutConflict: "这个快捷键与同一作用域内的其他操作冲突。",
|
||||
|
||||
@@ -16,6 +16,10 @@ export interface DataGridTransposeCell<T> {
|
||||
isNull: boolean;
|
||||
}
|
||||
|
||||
export interface DataGridVisibleTransposeCell<T> extends DataGridTransposeCell<T> {
|
||||
recordIndex: number;
|
||||
}
|
||||
|
||||
export interface DataGridTransposeRow<T> {
|
||||
id: string;
|
||||
column: string;
|
||||
@@ -23,6 +27,18 @@ export interface DataGridTransposeRow<T> {
|
||||
values: Array<DataGridTransposeCell<T>>;
|
||||
}
|
||||
|
||||
export interface DataGridVisibleTransposeRow<T> {
|
||||
id: string;
|
||||
column: string;
|
||||
type: string;
|
||||
values: Array<DataGridVisibleTransposeCell<T>>;
|
||||
}
|
||||
|
||||
export interface BuildVisibleTransposeRowsOptions<T> extends BuildTransposeRowsOptions<T> {
|
||||
recordIndexes: number[];
|
||||
valueIndexes?: number[];
|
||||
}
|
||||
|
||||
export interface TransposeRecordWindowOptions {
|
||||
totalRecords: number;
|
||||
scrollLeft: number;
|
||||
@@ -58,6 +74,8 @@ export interface ContextTransposeStateOptions extends TransposeAnchorOptions {
|
||||
transposeRowIndex: number | null;
|
||||
}
|
||||
|
||||
export type KeyboardTransposeStateOptions = ContextTransposeStateOptions;
|
||||
|
||||
export interface TransposeFieldWidthOptions {
|
||||
minWidth?: number;
|
||||
maxWidth?: number;
|
||||
@@ -89,6 +107,14 @@ export function nextContextTransposeState(options: ContextTransposeStateOptions)
|
||||
return nextTransposeState(options.showTranspose, options.transposeRowIndex, anchorRowIndex);
|
||||
}
|
||||
|
||||
export function nextKeyboardTransposeState(options: KeyboardTransposeStateOptions): DataGridTransposeState {
|
||||
if (options.showTranspose) return { showTranspose: false, transposeRowIndex: null };
|
||||
if (options.rowIds.length === 0) return { showTranspose: false, transposeRowIndex: null };
|
||||
const requestedRowIndex = Math.max(0, Math.min(options.rowIds.length - 1, options.requestedRowIndex));
|
||||
const anchorRowIndex = transposeAnchorRowIndex({ ...options, requestedRowIndex });
|
||||
return { showTranspose: true, transposeRowIndex: anchorRowIndex };
|
||||
}
|
||||
|
||||
export function buildTransposeRows<T>(options: BuildTransposeRowsOptions<T>): Array<DataGridTransposeRow<T>> {
|
||||
return options.columns.map((column, columnIndex) => {
|
||||
return {
|
||||
@@ -107,6 +133,32 @@ export function buildTransposeRows<T>(options: BuildTransposeRowsOptions<T>): Ar
|
||||
});
|
||||
}
|
||||
|
||||
export function buildVisibleTransposeRows<T>(
|
||||
options: BuildVisibleTransposeRowsOptions<T>,
|
||||
): Array<DataGridVisibleTransposeRow<T>> {
|
||||
return options.columns.map((column, columnIndex) => {
|
||||
return {
|
||||
id: `${columnIndex}:${column}`,
|
||||
column,
|
||||
type: options.typeByColumn?.get(column) || "",
|
||||
values: options.recordIndexes.flatMap((recordIndex) => {
|
||||
const record = options.records[recordIndex];
|
||||
if (!record) return [];
|
||||
const valueIndex = options.valueIndexes?.[columnIndex] ?? columnIndex;
|
||||
const value = record[valueIndex] as T;
|
||||
return [
|
||||
{
|
||||
recordIndex,
|
||||
value,
|
||||
display: options.displayValue(value, column, columnIndex, recordIndex),
|
||||
isNull: value === null,
|
||||
},
|
||||
];
|
||||
}),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function visibleTransposeRecordWindow(options: TransposeRecordWindowOptions): TransposeRecordWindow {
|
||||
if (options.totalRecords <= 0 || options.recordWidth <= 0) {
|
||||
return { start: 0, end: 0, beforeWidth: 0, afterWidth: 0 };
|
||||
|
||||
@@ -88,6 +88,10 @@ export function isRefreshDataShortcut(event: ShortcutLikeEvent, shortcuts?: Part
|
||||
return matchesShortcut(event, actionShortcut("refreshData", shortcuts));
|
||||
}
|
||||
|
||||
export function isToggleTransposeShortcut(event: ShortcutLikeEvent, shortcuts?: Partial<ShortcutSettings>): boolean {
|
||||
return matchesShortcut(event, actionShortcut("toggleTranspose", shortcuts));
|
||||
}
|
||||
|
||||
export function isSaveShortcut(event: ShortcutLikeEvent, shortcuts?: Partial<ShortcutSettings>): boolean {
|
||||
return matchesShortcut(event, actionShortcut("saveSql", shortcuts));
|
||||
}
|
||||
|
||||
@@ -5,9 +5,10 @@ export type ShortcutActionId =
|
||||
| "closeTab"
|
||||
| "focusSearch"
|
||||
| "refreshData"
|
||||
| "toggleTranspose"
|
||||
| "cancelSearch";
|
||||
|
||||
export type ShortcutScope = "global" | "editor" | "search";
|
||||
export type ShortcutScope = "global" | "editor" | "grid" | "search";
|
||||
|
||||
export interface ShortcutDefinition {
|
||||
id: ShortcutActionId;
|
||||
@@ -55,6 +56,12 @@ export const SHORTCUT_DEFINITIONS: ShortcutDefinition[] = [
|
||||
scope: "global",
|
||||
defaultShortcut: "F5",
|
||||
},
|
||||
{
|
||||
id: "toggleTranspose",
|
||||
labelKey: "settings.shortcutToggleTranspose",
|
||||
scope: "grid",
|
||||
defaultShortcut: "Tab",
|
||||
},
|
||||
{
|
||||
id: "cancelSearch",
|
||||
labelKey: "settings.shortcutCancelSearch",
|
||||
|
||||
Reference in New Issue
Block a user