feat: add column sort dropdown
This commit is contained in:
1 file changed
+73
-25
@@ -136,7 +136,7 @@ import { useSqlHighlighter } from "@/composables/useSqlHighlighter";
|
||||
import { useCellDetailEditor, type UseCellDetailEditorReturn } from "@/composables/useCellDetailEditor";
|
||||
import { useTheme } from "@/composables/useTheme";
|
||||
import { useSettingsStore } from "@/stores/settingsStore";
|
||||
import { nextDataGridSortState, type DataGridSortDirection } from "@/lib/dataGridSort";
|
||||
import type { DataGridSortDirection } from "@/lib/dataGridSort";
|
||||
import { getTableMetadataCapabilities } from "@/lib/tableMetadataCapabilities";
|
||||
import { forgetDataGridConditionHistory, loadDataGridConditionHistory, rememberDataGridConditionHistory } from "@/lib/dataGridConditionHistory";
|
||||
|
||||
@@ -167,6 +167,8 @@ type ConditionSuggestion = {
|
||||
kind: "column" | "history";
|
||||
};
|
||||
|
||||
type SortMenuValue = "asc" | "desc" | "clear";
|
||||
|
||||
const props = defineProps<{
|
||||
result: QueryResult;
|
||||
sql?: string;
|
||||
@@ -349,6 +351,38 @@ function headerColumnSortable(actualColIdx: number): boolean {
|
||||
return resolved !== undefined ? resolved : true;
|
||||
}
|
||||
|
||||
function columnIsSorted(column: string, columnIndex: number): boolean {
|
||||
return sortCol.value === column && sortColIndex.value === columnIndex;
|
||||
}
|
||||
|
||||
function sortMenuItems(column: string, columnIndex: number) {
|
||||
return [
|
||||
{
|
||||
label: t("grid.sortAscending"),
|
||||
value: "asc",
|
||||
icon: ArrowUp,
|
||||
checked: columnIsSorted(column, columnIndex) && sortDir.value === "asc",
|
||||
},
|
||||
{
|
||||
label: t("grid.sortDescending"),
|
||||
value: "desc",
|
||||
icon: ArrowDown,
|
||||
checked: columnIsSorted(column, columnIndex) && sortDir.value === "desc",
|
||||
},
|
||||
{
|
||||
label: t("grid.clearSort"),
|
||||
value: "clear",
|
||||
icon: ArrowUpDown,
|
||||
disabled: !columnIsSorted(column, columnIndex),
|
||||
separatorBefore: true,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function selectedSortMenuValue(column: string, columnIndex: number): SortMenuValue | undefined {
|
||||
return columnIsSorted(column, columnIndex) ? sortDir.value : undefined;
|
||||
}
|
||||
|
||||
function typeColorClass(t: string): string {
|
||||
// Strip precision/scale suffix like (20,6)
|
||||
const base = t.replace(/\(.*\)$/, "").toLowerCase();
|
||||
@@ -476,6 +510,7 @@ type StructuredFilterRule = {
|
||||
const localColumnFilters = ref<Record<number, Set<string>>>({});
|
||||
const localFilterOpenColumn = ref<number | null>(null);
|
||||
const headerActionMenuOpenColumn = ref<number | null>(null);
|
||||
const headerSortMenuOpenColumn = ref<number | null>(null);
|
||||
const headerPanelDismissGuardUntil = ref(0);
|
||||
const localFilterSearch = ref("");
|
||||
const localFilterDraft = ref<LocalColumnFilterDraft | null>(null);
|
||||
@@ -3485,23 +3520,10 @@ function setDetailNull() {
|
||||
detailCell.value = { ...detailCell.value! };
|
||||
}
|
||||
|
||||
function toggleSort(colName: string, colIdx: number) {
|
||||
function applyColumnSort(column: string, columnIndex: number, direction: "asc" | "desc" | null) {
|
||||
if (getIsResizing()) return;
|
||||
currentPage.value = 1;
|
||||
resetGridVerticalScroll(true);
|
||||
const next = nextDataGridSortState({ column: sortCol.value, columnIndex: sortColIndex.value, direction: sortDir.value }, colName, colIdx);
|
||||
sortCol.value = next.column;
|
||||
sortColIndex.value = next.columnIndex;
|
||||
sortDir.value = next.direction;
|
||||
syncOrderByInputWithSort(next.column, next.column ? next.direction : null);
|
||||
emit("sort", colName, colIdx, next.column ? next.direction : null, currentWhereInput());
|
||||
}
|
||||
|
||||
function applyContextSort(direction: "asc" | "desc" | null) {
|
||||
if (!contextColumn.value || !contextCell.value) return;
|
||||
const column = contextColumn.value;
|
||||
const columnIndex = contextCell.value.col;
|
||||
currentPage.value = 1;
|
||||
if (direction) {
|
||||
sortCol.value = column;
|
||||
sortColIndex.value = columnIndex;
|
||||
@@ -3516,6 +3538,15 @@ function applyContextSort(direction: "asc" | "desc" | null) {
|
||||
emit("sort", column, columnIndex, direction, currentWhereInput());
|
||||
}
|
||||
|
||||
function selectHeaderSort(value: string, column: string, columnIndex: number) {
|
||||
applyColumnSort(column, columnIndex, value === "clear" ? null : (value as DataGridSortDirection));
|
||||
}
|
||||
|
||||
function applyContextSort(direction: "asc" | "desc" | null) {
|
||||
if (!contextColumn.value || !contextCell.value) return;
|
||||
applyColumnSort(contextColumn.value, contextCell.value.col, direction);
|
||||
}
|
||||
|
||||
async function contextFilterCondition(mode: FilterMode): Promise<string | null> {
|
||||
if (!contextColumn.value) return null;
|
||||
return (
|
||||
@@ -6808,18 +6839,35 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
||||
{{ headerColumnComment(col.name) }}
|
||||
</span>
|
||||
</span>
|
||||
<button
|
||||
<LightDropdownMenu
|
||||
v-if="headerColumnSortable(col.actualColIdx)"
|
||||
type="button"
|
||||
class="flex h-4 w-4 shrink-0 items-center justify-center rounded text-muted-foreground hover:bg-gray-200 dark:hover:bg-gray-800 hover:text-foreground"
|
||||
:class="sortCol === col.name && sortColIndex === col.actualColIdx ? 'text-primary opacity-100' : 'opacity-80'"
|
||||
:title="t('grid.sort')"
|
||||
@click.stop="toggleSort(col.name, col.actualColIdx)"
|
||||
:items="sortMenuItems(col.name, col.actualColIdx)"
|
||||
:open="headerSortMenuOpenColumn === col.actualColIdx"
|
||||
:selected-value="selectedSortMenuValue(col.name, col.actualColIdx)"
|
||||
align="end"
|
||||
content-class="w-max min-w-28 p-0.5"
|
||||
item-class="gap-1 px-1.5 py-0.5 text-xs"
|
||||
item-icon-class="h-3 w-3"
|
||||
:match-trigger-width="false"
|
||||
@update:open="(value: boolean) => (headerSortMenuOpenColumn = value ? col.actualColIdx : null)"
|
||||
@select="(value: string) => selectHeaderSort(value, col.name, col.actualColIdx)"
|
||||
>
|
||||
<ArrowUp v-if="sortCol === col.name && sortColIndex === col.actualColIdx && sortDir === 'asc'" class="h-3 w-3 shrink-0" />
|
||||
<ArrowDown v-else-if="sortCol === col.name && sortColIndex === col.actualColIdx && sortDir === 'desc'" class="h-3 w-3 shrink-0" />
|
||||
<ArrowUpDown v-else class="h-3 w-3 shrink-0" />
|
||||
</button>
|
||||
<template #trigger="{ open, toggle }">
|
||||
<button
|
||||
type="button"
|
||||
class="flex h-4 w-4 shrink-0 items-center justify-center rounded text-muted-foreground hover:bg-gray-200 dark:hover:bg-gray-800 hover:text-foreground"
|
||||
:class="columnIsSorted(col.name, col.actualColIdx) ? 'text-primary opacity-100' : 'opacity-80'"
|
||||
:title="t('grid.sort')"
|
||||
:aria-expanded="open"
|
||||
@mousedown.stop
|
||||
@click.stop="toggle"
|
||||
>
|
||||
<ArrowUp v-if="columnIsSorted(col.name, col.actualColIdx) && sortDir === 'asc'" class="h-3 w-3 shrink-0" />
|
||||
<ArrowDown v-else-if="columnIsSorted(col.name, col.actualColIdx) && sortDir === 'desc'" class="h-3 w-3 shrink-0" />
|
||||
<ArrowUpDown v-else class="h-3 w-3 shrink-0" />
|
||||
</button>
|
||||
</template>
|
||||
</LightDropdownMenu>
|
||||
<LightDropdownMenu
|
||||
v-if="compactColumnHeaderActions"
|
||||
:items="compactColumnActionMenuItems(col.name)"
|
||||
|
||||
Reference in new issue
Block a user