feat(grid): improve data copy format controls

This commit is contained in:
t8y2 committed 2026-07-30 23:08:15 +08:00
1 parent 0234470466
commit 38e1c66f26
18 files changed
+235 -35

No files matched your search

+44 -23
View File
@@ -39,6 +39,7 @@ import {
Eraser,
Columns3,
PencilRuler,
Settings2,
WandSparkles,
} from "@lucide/vue";
import { Button } from "@/components/ui/button";
@@ -206,7 +207,7 @@ import { useConnectionStore } from "@/stores/connectionStore";
import { useQueryStore } from "@/stores/queryStore";
import { useSettingsStore } from "@/stores/settingsStore";
import { simpleDataGridOrderByMatchesSort, simpleDataGridOrderByReferencesMissingColumn, type DataGridSortDirection, type DataGridSortMode } from "@/lib/dataGrid/dataGridSort";
import { DATA_GRID_COMPACT_TOPBAR_WIDTH, type DataGridReloadIntent, type DataGridToolbarActionCapability, type DataGridToolbarAutoRefreshCapability, type DataGridToolbarCopyCapability, type DataGridToolbarSaveCapability } from "@/lib/dataGrid/dataGridToolbar";
import { DATA_GRID_COMPACT_TOPBAR_WIDTH, type DataGridReloadIntent, type DataGridToolbarActionCapability, type DataGridToolbarAutoRefreshCapability, type DataGridToolbarSaveCapability } from "@/lib/dataGrid/dataGridToolbar";
import { getTableMetadataCapabilities } from "@/lib/table/tableMetadataCapabilities";
import { getTableStructureCapabilities } from "@/lib/table/tableStructureCapabilities";
import { filterObjectBrowserTableColumns } from "@/lib/table/objectBrowserTableInfo";
@@ -5269,6 +5270,10 @@ function copyExtractorLabel(extractor: DataGridCopyExtractorId): string {
}
const selectedCopyExtractor = computed(() => settingsStore.editorSettings.dataGridCopyExtractor);
const defaultCopyExtractorLabel = computed(() => {
if (selectedCopyExtractor.value !== "sql-inserts") return copyExtractorLabel(selectedCopyExtractor.value);
return t(settingsStore.editorSettings.dataGridExtractorOptions.sql.excludePrimaryKeysFromInsert ? "grid.copyExtractorSqlInsertsWithoutPrimaryKeys" : "grid.copyExtractorSqlInsertsWithPrimaryKeys");
});
const extractorConfigOpen = ref(false);
const extractorMenuItems = computed(() =>
DATA_GRID_COPY_EXTRACTOR_IDS.map((extractor) => ({
@@ -5278,23 +5283,23 @@ const extractorMenuItems = computed(() =>
separatorBefore: DATA_GRID_COPY_EXTRACTOR_DESCRIPTORS[extractor].separatorBefore,
})),
);
const copyToolbarCapability = computed<DataGridToolbarCopyCapability>(() => ({
label: copyExtractorLabel(selectedCopyExtractor.value),
tooltip: `${t("grid.copy")} (${shortcutMod}+C)`,
currentValue: selectedCopyExtractor.value,
disabled: !canCopyWithExtractor(selectedCopyExtractor.value),
items: [...extractorMenuItems.value, { value: "__configure__", label: t("grid.copyExtractorConfigure"), separatorBefore: true }],
onCopy: async () => {
await copyWithExtractor(selectedCopyExtractor.value);
},
onSelect: (value) => {
if (value === "__configure__") {
extractorConfigOpen.value = true;
return;
}
settingsStore.updateEditorSettings({ dataGridCopyExtractor: value as DataGridCopyExtractorId });
},
}));
function openExtractorConfiguration() {
extractorConfigOpen.value = true;
}
function setDefaultCopyExtractor(value: string) {
const item = extractorMenuItems.value.find((candidate) => candidate.value === value);
if (!item || item.disabled) return;
settingsStore.updateEditorSettings({ dataGridCopyExtractor: item.value });
}
function sqlInsertExtractorOptions(excludePrimaryKeysFromInsert: boolean) {
const options = settingsStore.editorSettings.dataGridExtractorOptions;
return {
...options,
sql: { ...options.sql, excludePrimaryKeysFromInsert },
};
}
function saveExtractorConfiguration(value: { extractor: DataGridCopyExtractorId; options: typeof settingsStore.editorSettings.dataGridExtractorOptions }) {
settingsStore.updateEditorSettings({ dataGridCopyExtractor: value.extractor, dataGridExtractorOptions: value.options });
@@ -7597,6 +7602,11 @@ defineExpose({
exportSql,
exportXlsx,
exportTxt,
defaultCopyExtractor: selectedCopyExtractor,
defaultCopyExtractorLabel,
copyExtractorMenuItems: extractorMenuItems,
setDefaultCopyExtractor,
openExtractorConfiguration,
});
// ---- CustomContextMenu ----
@@ -7637,22 +7647,34 @@ function buildExtractorContextItems(): ContextMenuItem[] {
items.push({ label: "", separator: true });
}
const selected = extractor === selectedCopyExtractor.value;
if (extractor === "sql-inserts") {
for (const excludePrimaryKeysFromInsert of [false, true]) {
const options = sqlInsertExtractorOptions(excludePrimaryKeysFromInsert);
items.push({
label: t(excludePrimaryKeysFromInsert ? "grid.copyExtractorSqlInsertsWithoutPrimaryKeys" : "grid.copyExtractorSqlInsertsWithPrimaryKeys"),
action: () => void copyWithExtractor(extractor, options),
disabled: !canCopyWithExtractor(extractor, options),
checked: selected && settingsStore.editorSettings.dataGridExtractorOptions.sql.excludePrimaryKeysFromInsert === excludePrimaryKeysFromInsert,
});
}
continue;
}
items.push({
label: `${selected ? "✓ " : ""}${copyExtractorLabel(extractor)}`,
label: copyExtractorLabel(extractor),
action: () => {
// One-off copy as the chosen format; do NOT persist it as the default —
// the saved default stays controlled by the toolbar/settings dialog.
void copyWithExtractor(extractor);
},
disabled: !canCopyWithExtractor(extractor),
checked: selected,
});
}
items.push({ label: "", separator: true });
items.push({
label: t("grid.copyExtractorConfigure"),
action: () => {
extractorConfigOpen.value = true;
},
icon: Settings2,
action: openExtractorConfiguration,
});
return items;
}
@@ -7899,7 +7921,6 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
:compact="compactDataGridToolbar"
:refresh="refreshToolbarCapability"
:auto-refresh="autoRefreshToolbarCapability"
:copy-data="copyToolbarCapability"
:add-row="addRowToolbarCapability"
:preview="previewToolbarCapability"
:save="saveToolbarCapability"
@@ -0,0 +1,51 @@
<script setup lang="ts">
import { Check, ChevronDown, Copy, Settings2 } from "@lucide/vue";
import { useI18n } from "vue-i18n";
import { Button } from "@/components/ui/button";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
import type { DataGridToolbarMenuItem } from "@/lib/dataGrid/dataGridToolbar";
defineProps<{
currentLabel: string;
currentValue: string;
items: readonly DataGridToolbarMenuItem[];
}>();
const emit = defineEmits<{
configure: [];
select: [value: string];
}>();
const { t } = useI18n();
</script>
<template>
<div class="flex items-center justify-between gap-3 px-3 py-1.5 text-xs">
<span class="flex min-w-0 items-center gap-2 font-medium">
<Copy class="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
<span>{{ t("grid.copyExtractorDefaultFormat") }}</span>
</span>
<div class="flex min-w-0 items-center gap-1">
<DropdownMenu>
<DropdownMenuTrigger as-child>
<Button variant="outline" size="sm" class="h-6 max-w-52 min-w-0 gap-1 px-2 text-xs">
<span class="truncate">{{ currentLabel }}</span>
<ChevronDown class="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" class="min-w-52">
<template v-for="item in items" :key="item.value">
<DropdownMenuSeparator v-if="item.separatorBefore" />
<DropdownMenuItem class="gap-2" :disabled="item.disabled" @select="emit('select', item.value)">
<span class="flex-1">{{ item.label }}</span>
<Check v-if="currentValue === item.value" class="h-3.5 w-3.5 shrink-0 text-primary" />
</DropdownMenuItem>
</template>
</DropdownMenuContent>
</DropdownMenu>
<Button variant="ghost" size="icon" class="h-6 w-6 shrink-0" :title="t('grid.copyExtractorConfigure')" :aria-label="t('grid.copyExtractorConfigure')" @click="emit('configure')">
<Settings2 class="h-3.5 w-3.5" />
</Button>
</div>
</div>
</template>
@@ -109,9 +109,8 @@ function actionLabelClass() {
<template v-for="item in copyData?.items ?? []" :key="item.value">
<DropdownMenuSeparator v-if="item.separatorBefore" />
<DropdownMenuItem class="gap-2" :disabled="item.disabled" @select="void selectDataGridToolbarCopyItem(copyData, item.value)">
<Check v-if="copyData?.currentValue === item.value" class="h-3.5 w-3.5" />
<span v-else class="h-3.5 w-3.5" />
{{ item.label }}
<span class="flex-1">{{ item.label }}</span>
<Check v-if="copyData?.currentValue === item.value" class="h-3.5 w-3.5 shrink-0 text-primary" />
</DropdownMenuItem>
</template>
</DropdownMenuContent>