feat(grid): improve data copy format controls
This commit is contained in:
18 files changed
+235
-35
No files matched your search
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user