fix(app): harden clipboard copy flows

This commit is contained in:
t8y2 2026-05-23 10:52:19 +08:00
parent ccfc3cf92c
commit 2fea0c38c5
12 changed files with 427 additions and 81 deletions

View File

@ -19,6 +19,7 @@ import { applyParsedConnectionUrl, parseConnectionUrl } from "@/lib/connectionUr
import type { ConnectionDeepLinkDraft } from "@/lib/connectionDeepLink";
import { connectionUrlPlaceholder as getUrlPlaceholder } from "@/lib/connectionPresentation";
import { mongodbAuthFailureHint, mongoUrlParam, setMongoUrlParam } from "@/lib/mongoConnectionOptions";
import { copyToClipboard } from "@/lib/clipboard";
import { showAgentDriverInstallHint, type AgentDriverInstallState } from "@/lib/agentDriverInstallHint";
import {
ArrowLeft,
@ -688,8 +689,12 @@ function applyConnectionUrl() {
async function copyTestResult() {
if (!testResultMessage.value) return;
await navigator.clipboard.writeText(testResultMessage.value);
toast(t("grid.copied"));
try {
await copyToClipboard(testResultMessage.value);
toast(t("grid.copied"));
} catch (e: any) {
toast(t("grid.copyFailed", { message: e?.message || String(e) }), 5000);
}
}
function resetForm() {

View File

@ -8,6 +8,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
import { useConnectionStore } from "@/stores/connectionStore";
import { useToast } from "@/composables/useToast";
import { isSchemaAware } from "@/lib/databaseCapabilities";
import { copyToClipboard } from "@/lib/clipboard";
import type { DataCompareResult } from "@/lib/dataCompare";
import * as api from "@/lib/api";
import DatabaseIcon from "@/components/icons/DatabaseIcon.vue";
@ -288,8 +289,12 @@ async function startCompare() {
}
async function copySql() {
await navigator.clipboard.writeText(syncSql.value);
toast(t("grid.copied"));
try {
await copyToClipboard(syncSql.value);
toast(t("grid.copied"));
} catch (e: any) {
toast(t("grid.copyFailed", { message: e?.message || String(e) }), 5000);
}
}
async function executeSql() {

View File

@ -10,6 +10,7 @@ import { useConnectionStore } from "@/stores/connectionStore";
import DatabaseIcon from "@/components/icons/DatabaseIcon.vue";
import * as api from "@/lib/api";
import { isSchemaAware } from "@/lib/databaseCapabilities";
import { copyToClipboard } from "@/lib/clipboard";
import type { TableDiff, TableSchemaDetail } from "@/lib/schemaDiff";
import type { TableInfo } from "@/types/database";
import { sqlMetadataRefreshTarget } from "@/lib/sqlMetadataRefresh";
@ -286,9 +287,13 @@ async function executeSql() {
}
}
function copySql() {
navigator.clipboard.writeText(syncSql.value);
toast(t("grid.copied"));
async function copySql() {
try {
await copyToClipboard(syncSql.value);
toast(t("grid.copied"));
} catch (e: any) {
toast(t("grid.copyFailed", { message: e?.message || String(e) }), 5000);
}
}
function diffBadgeVariant(type: string) {

View File

@ -68,6 +68,7 @@ import type { ConnectionConfig, QueryTab, TableInfo } from "@/types/database";
import { useDatabaseOptions } from "@/composables/useDatabaseOptions";
import { resolveDefaultDatabase } from "@/lib/defaultDatabase";
import { isSchemaAware } from "@/lib/databaseCapabilities";
import { copyToClipboard } from "@/lib/clipboard";
import { formatAiTableMention, parseAiTableMentions, type AiTableMention } from "@/lib/aiTableMentions";
import { isAiPromptImeCompositionEvent, shouldSubmitAiPromptOnKeydown } from "@/lib/aiPromptKeyboard";
@ -561,11 +562,15 @@ function executeSql(code: string) {
const copiedIndex = ref("");
async function copyCode(code: string, key: string) {
await navigator.clipboard.writeText(code);
copiedIndex.value = key;
setTimeout(() => {
if (copiedIndex.value === key) copiedIndex.value = "";
}, 2000);
try {
await copyToClipboard(code);
copiedIndex.value = key;
setTimeout(() => {
if (copiedIndex.value === key) copiedIndex.value = "";
}, 2000);
} catch (e: any) {
toast(t("grid.copyFailed", { message: e?.message || String(e) }), 5000);
}
}
function clearMessages() {

View File

@ -3,6 +3,8 @@ import { X, Copy } from "lucide-vue-next";
import { useI18n } from "vue-i18n";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { useToast } from "@/composables/useToast";
import { copyToClipboard } from "@/lib/clipboard";
export interface ColumnInfo {
name: string;
@ -26,9 +28,15 @@ const emit = defineEmits<{
}>();
const { t } = useI18n();
const { toast } = useToast();
function copyText(text: string) {
navigator.clipboard.writeText(text);
async function copyText(text: string) {
try {
await copyToClipboard(text);
toast(t("grid.copied"));
} catch (e: any) {
toast(t("grid.copyFailed", { message: e?.message || String(e) }), 5000);
}
}
</script>

View File

@ -13,6 +13,7 @@ import { resolveHistoryActivityKind } from "@/lib/historyActivityKind";
import { canRollbackHistoryEntry } from "@/lib/historyAiAnalysis";
import { HISTORY_ROW_HEIGHT, HISTORY_SCROLL_BUFFER, shouldVirtualizeHistory } from "@/lib/historyVirtualList";
import type { HistoryEntry } from "@/lib/api";
import { copyToClipboard } from "@/lib/clipboard";
import * as api from "@/lib/api";
const { t } = useI18n();
@ -58,8 +59,12 @@ function restore(entry: HistoryEntry) {
}
async function copyText(text: string) {
await navigator.clipboard.writeText(text);
toast(t("grid.copied"));
try {
await copyToClipboard(text);
toast(t("grid.copied"));
} catch (e: any) {
toast(t("grid.copyFailed", { message: e?.message || String(e) }), 5000);
}
}
function confirmDeleteEntry(id: string) {

View File

@ -150,6 +150,13 @@ const { t } = useI18n();
const settingsStore = useSettingsStore();
const { toast } = useToast();
interface PreparedCopyValue {
key: string;
text: string;
loading: boolean;
ready: boolean;
}
const props = defineProps<{
result: QueryResult;
sql?: string;
@ -1892,6 +1899,82 @@ const activeValueEditorActions = computed(() => {
});
});
const detailSqlConditionCopy = ref<PreparedCopyValue>({
key: "",
text: "",
loading: false,
ready: false,
});
const detailSqlConditionKey = computed(() => {
const detail = activeCellDetail.value;
if (!detail) return "";
return JSON.stringify({
databaseType: props.databaseType ?? null,
column: detail.column,
value: detail.value,
type: detail.type,
schema: props.tableMeta?.schema ?? null,
tableName: props.tableMeta?.tableName ?? null,
});
});
function canCopyPreparedDetailSqlCondition(): boolean {
return detailSqlConditionCopy.value.ready && detailSqlConditionCopy.value.key === detailSqlConditionKey.value;
}
async function prefetchDetailSqlCondition() {
const detail = activeCellDetail.value;
const key = detailSqlConditionKey.value;
if (!detail || !key) {
detailSqlConditionCopy.value = {
key: "",
text: "",
loading: false,
ready: false,
};
return;
}
const current = detailSqlConditionCopy.value;
if ((current.loading || current.ready) && current.key === key) return;
detailSqlConditionCopy.value = {
key,
text: "",
loading: true,
ready: false,
};
try {
const condition = await buildDataGridContextFilterCondition({
databaseType: props.databaseType,
columnName: detail.column,
columnInfo: props.tableMeta?.columns.find((column) => column.name === detail.column),
mode: "equals",
value: detail.value,
});
if (detailSqlConditionCopy.value.key !== key) return;
detailSqlConditionCopy.value = {
key,
text: condition ?? "",
loading: false,
ready: !!condition,
};
} catch {
if (detailSqlConditionCopy.value.key !== key) return;
detailSqlConditionCopy.value = {
key,
text: "",
loading: false,
ready: false,
};
}
}
watch(activeCellDetail, () => {
void prefetchDetailSqlCondition();
});
const detailEditValue = ref("");
const isEditingDetail = ref(false);
const detailTemporalEditorKind = computed(() => {
@ -2204,6 +2287,10 @@ const {
copyRow,
copyRowAsInsert,
copyRowAsInsertWithoutPrimaryKeys,
prefetchRowAsInsertStatement,
canCopyPreparedInsert,
prefetchRowAsUpdateStatement,
canCopyPreparedUpdate,
copyRowAsUpdate,
canCopyRowAsInsertWithoutPrimaryKeys,
canCopyRowAsUpdate,
@ -2458,16 +2545,8 @@ function copyDetailColumnName() {
}
async function copyDetailSqlCondition() {
const detail = activeCellDetail.value;
if (!detail) return;
const condition = await buildDataGridContextFilterCondition({
databaseType: props.databaseType,
columnName: detail.column,
columnInfo: props.tableMeta?.columns.find((column) => column.name === detail.column),
mode: "equals",
value: detail.value,
});
if (condition) copyText(condition);
if (!canCopyPreparedDetailSqlCondition()) return;
copyText(detailSqlConditionCopy.value.text);
}
const TRANSPOSE_RECORD_DEFAULT_WIDTH = 168;
@ -2677,12 +2756,14 @@ watch(
function onCellContext(rowId: number, rowIndex: number, colIdx: number, visibleColIdx: number) {
contextCell.value = { rowId, rowIndex, col: colIdx };
if (hasRowSelection.value && isRowSelected(rowId)) {
void prefetchCopyStatements();
return;
}
clearRowSelection();
if (!cellIsSelected(rowIndex, visibleColIdx)) {
selectSingleCell(rowIndex, visibleColIdx);
}
void prefetchCopyStatements();
}
function onRowContext(rowId: number, rowIndex: number) {
@ -2692,6 +2773,17 @@ function onRowContext(rowId: number, rowIndex: number) {
selectedRowIds.value = new Set([rowId]);
selection.lastClickedRowIndex.value = rowIndex;
}
void prefetchCopyStatements();
}
async function prefetchCopyStatements() {
await prefetchRowAsInsertStatement(false);
if (canCopyRowAsInsertWithoutPrimaryKeys.value) {
await prefetchRowAsInsertStatement(true);
}
if (canCopyRowAsUpdate.value) {
await prefetchRowAsUpdateStatement();
}
}
const sqlOneLiner = computed(() => props.sql?.replace(/\s+/g, " ").trim() || "");
@ -2865,8 +2957,7 @@ if (showTableInfo.value && props.tableMeta && props.connectionId) {
}
function copyDdl() {
navigator.clipboard.writeText(ddlContent.value);
toast(t("grid.copied"));
copyText(ddlContent.value);
}
function toggleDdlWrap() {
@ -4487,7 +4578,13 @@ defineExpose({
<Button variant="ghost" size="sm" class="h-7 justify-start text-xs" @click="copyDetailColumnName">
<Copy class="w-3 h-3 mr-2" /> {{ t("grid.copyColumnName") }}
</Button>
<Button variant="ghost" size="sm" class="h-7 justify-start text-xs" @click="copyDetailSqlCondition">
<Button
variant="ghost"
size="sm"
class="h-7 justify-start text-xs"
:disabled="!canCopyPreparedDetailSqlCondition()"
@click="copyDetailSqlCondition"
>
<Code2 class="w-3 h-3 mr-2" /> {{ t("grid.copySqlCondition") }}
</Button>
</div>
@ -4598,17 +4695,21 @@ defineExpose({
<ContextMenuItem @click="copyRow">
{{ isMultiRow ? t("grid.copyRows", { count: multiRowCount }) : t("grid.copyRow") }}
</ContextMenuItem>
<ContextMenuItem @click="copyRowAsInsert">
<ContextMenuItem :disabled="!canCopyPreparedInsert(false)" @click="copyRowAsInsert">
{{ isMultiRow ? t("grid.copyRowsInsert", { count: multiRowCount }) : t("grid.copyRowInsert") }}
</ContextMenuItem>
<ContextMenuItem v-if="canCopyRowAsInsertWithoutPrimaryKeys" @click="copyRowAsInsertWithoutPrimaryKeys">
<ContextMenuItem
v-if="canCopyRowAsInsertWithoutPrimaryKeys"
:disabled="!canCopyPreparedInsert(true)"
@click="copyRowAsInsertWithoutPrimaryKeys"
>
{{
isMultiRow
? t("grid.copyRowsInsertWithoutPrimaryKeys", { count: multiRowCount })
: t("grid.copyRowInsertWithoutPrimaryKeys")
}}
</ContextMenuItem>
<ContextMenuItem v-if="canCopyRowAsUpdate" @click="copyRowAsUpdate">
<ContextMenuItem v-if="canCopyRowAsUpdate" :disabled="!canCopyPreparedUpdate()" @click="copyRowAsUpdate">
{{ isMultiRow ? t("grid.copyRowsUpdate", { count: multiRowCount }) : t("grid.copyRowUpdate") }}
</ContextMenuItem>
<ContextMenuItem @click="copyAll">{{ t("grid.copyAll") }}</ContextMenuItem>

View File

@ -32,6 +32,7 @@ import {
type FieldLineageTable,
type FieldLineageView,
} from "@/lib/fieldLineage";
import { copyToClipboard } from "@/lib/clipboard";
const props = defineProps<{
open: boolean;
@ -291,14 +292,18 @@ function itemDescription(item: FieldLineageItem) {
return t("lineage.description.sameName");
}
function copyItem(item: FieldLineageItem) {
async function copyItem(item: FieldLineageItem) {
const text = itemPrimaryLabel(item);
navigator.clipboard.writeText(text);
copiedId.value = item.id;
toast(t("lineage.copied"));
setTimeout(() => {
if (copiedId.value === item.id) copiedId.value = "";
}, 1400);
try {
await copyToClipboard(text);
copiedId.value = item.id;
toast(t("lineage.copied"));
setTimeout(() => {
if (copiedId.value === item.id) copiedId.value = "";
}, 1400);
} catch (e: any) {
toast(t("grid.copyFailed", { message: e?.message || String(e) }), 5000);
}
}
function openItemTarget(item: FieldLineageItem) {

View File

@ -70,6 +70,7 @@ import {
import { buildRenameObjectSql, supportsObjectRename } from "@/lib/objectRenameSql";
import { buildViewDdl } from "@/lib/viewDdl";
import { isTauriRuntime } from "@/lib/tauriRuntime";
import { copyToClipboard } from "@/lib/clipboard";
import { formatSqlInsert } from "@/lib/exportFormats";
import { fetchTableDataForExport } from "@/lib/tableDataExport";
import { useConnectionStore } from "@/stores/connectionStore";
@ -746,15 +747,23 @@ async function confirmEmptyTable() {
emptyTarget.value = null;
}
function copyName(row: ObjectBrowserRow) {
navigator.clipboard.writeText(row.name);
toast(t("connection.copied"), 2000);
async function copyName(row: ObjectBrowserRow) {
try {
await copyToClipboard(row.name);
toast(t("connection.copied"), 2000);
} catch (e: any) {
toast(t("grid.copyFailed", { message: e?.message || String(e) }), 5000);
}
}
function copySource() {
async function copySource() {
if (!sourceContent.value) return;
navigator.clipboard.writeText(sourceContent.value);
toast(t("grid.copied"));
try {
await copyToClipboard(sourceContent.value);
toast(t("grid.copied"));
} catch (e: any) {
toast(t("grid.copyFailed", { message: e?.message || String(e) }), 5000);
}
}
function editSource() {

View File

@ -15,6 +15,7 @@ import type { RedisKeyInfo, RedisValue } from "@/lib/api";
import { useToast } from "@/composables/useToast";
import { useTheme } from "@/composables/useTheme";
import { createRedisShikiJsonHighlighter, type RedisJsonHighlighter } from "@/lib/redisJsonHighlighter";
import { copyToClipboard } from "@/lib/clipboard";
import {
canEditRedisMemberDetail,
clampRedisMemberDetailSheetWidth,
@ -275,20 +276,28 @@ function requestDeleteKey() {
showDeleteConfirm.value = true;
}
function copyValue() {
async function copyValue() {
if (!data.value) return;
const text = typeof data.value.value === "string" ? data.value.value : JSON.stringify(data.value.value, null, 2);
navigator.clipboard.writeText(text);
toast(t("redis.copied"), 2000);
try {
await copyToClipboard(text);
toast(t("redis.copied"), 2000);
} catch (e: any) {
toast(t("grid.copyFailed", { message: e?.message || String(e) }), 5000);
}
}
function copyText(text: string) {
navigator.clipboard.writeText(text);
toast(t("redis.copied"), 2000);
async function copyText(text: string) {
try {
await copyToClipboard(text);
toast(t("redis.copied"), 2000);
} catch (e: any) {
toast(t("grid.copyFailed", { message: e?.message || String(e) }), 5000);
}
}
function copyMember(value: unknown) {
copyText(formatRedisMemberDetail(value).text);
void copyText(formatRedisMemberDetail(value).text);
}
function selectMember(title: string, value: unknown, context: RedisMemberContext) {

View File

@ -115,6 +115,7 @@ import { hasTreeNodeDatabaseContext } from "@/lib/treeNodeContext";
import { sidebarDisplayTableName } from "@/lib/sidebarTableNameDisplay";
import DangerConfirmDialog from "@/components/editor/DangerConfirmDialog.vue";
import { isTauriRuntime } from "@/lib/tauriRuntime";
import { copyToClipboard } from "@/lib/clipboard";
import DatabaseIcon from "@/components/icons/DatabaseIcon.vue";
import ConnectionErrorIndicator from "@/components/connection/ConnectionErrorIndicator.vue";
import VisibleDatabasesDialog from "@/components/sidebar/VisibleDatabasesDialog.vue";
@ -631,9 +632,13 @@ async function confirmDelete() {
}
}
function copyName() {
navigator.clipboard.writeText(props.node.label);
toast(t("connection.copied"), 2000);
async function copyName() {
try {
await copyToClipboard(props.node.label);
toast(t("connection.copied"), 2000);
} catch (e: any) {
toast(t("grid.copyFailed", { message: e?.message || String(e) }), 5000);
}
}
async function duplicateConnection() {

View File

@ -1,4 +1,4 @@
import { computed, type ComputedRef, type Ref } from "vue";
import { computed, ref, type ComputedRef, type Ref } from "vue";
import { useI18n } from "vue-i18n";
import { isTauriRuntime } from "@/lib/tauriRuntime";
import * as api from "@/lib/api";
@ -46,10 +46,35 @@ export interface UseDataGridExportOptions {
hasRowSelection: ComputedRef<boolean>;
}
interface CopyStatementCache {
key: string;
text: string;
loading: boolean;
ready: boolean;
}
export function useDataGridExport(options: UseDataGridExportOptions) {
const { t } = useI18n();
const { toast } = useToast();
const exportGuard: ActionActivationGuard = {};
const copyRowInsertCache = ref<CopyStatementCache>({
key: "",
text: "",
loading: false,
ready: false,
});
const copyRowInsertWithoutPrimaryKeysCache = ref<CopyStatementCache>({
key: "",
text: "",
loading: false,
ready: false,
});
const copyRowUpdateCache = ref<CopyStatementCache>({
key: "",
text: "",
loading: false,
ready: false,
});
const {
columns,
@ -99,6 +124,181 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
return targetedRows().filter((item) => !item.isNew && !item.isDeleted);
}
function updateCopyKey(): string {
const rows = updateEligibleRows().map((item) => item.id);
return JSON.stringify({
databaseType: databaseType.value ?? null,
schema: tableMeta.value?.schema ?? null,
tableName: tableMeta.value?.tableName ?? null,
primaryKeys: tableMeta.value?.primaryKeys ?? [],
columns: columns.value,
sourceColumns: sourceColumns.value ?? null,
rows,
});
}
function insertCopyKey(excludePrimaryKeys: boolean): string {
const rows = insertEligibleRows().map((item) => item.id);
return JSON.stringify({
databaseType: databaseType.value ?? null,
schema: tableMeta.value?.schema ?? null,
tableName: tableMeta.value?.tableName ?? null,
columns: columns.value,
sourceColumns: sourceColumns.value ?? null,
excludePrimaryKeys,
rows,
});
}
function insertCopyCache(excludePrimaryKeys: boolean): CopyStatementCache {
return excludePrimaryKeys ? copyRowInsertWithoutPrimaryKeysCache.value : copyRowInsertCache.value;
}
function setInsertCopyCache(excludePrimaryKeys: boolean, cache: CopyStatementCache) {
if (excludePrimaryKeys) {
copyRowInsertWithoutPrimaryKeysCache.value = cache;
} else {
copyRowInsertCache.value = cache;
}
}
function setUpdateCopyCache(cache: CopyStatementCache) {
copyRowUpdateCache.value = cache;
}
async function prefetchRowAsInsertStatement(excludePrimaryKeys: boolean) {
const rows = insertEligibleRows();
if (!rows.length) {
setInsertCopyCache(excludePrimaryKeys, {
key: "",
text: "",
loading: false,
ready: false,
});
return;
}
const key = insertCopyKey(excludePrimaryKeys);
const current = insertCopyCache(excludePrimaryKeys);
if ((current.loading || current.ready) && current.key === key) return;
setInsertCopyCache(excludePrimaryKeys, {
key,
text: "",
loading: true,
ready: false,
});
try {
const statement = await buildDataGridCopyInsertStatement({
databaseType: databaseType.value,
tableMeta: tableMeta.value,
columns: columns.value,
sourceColumns: sourceColumns.value,
rows: rows.map((item) => item.data),
excludePrimaryKeys,
});
const latest = insertCopyCache(excludePrimaryKeys);
if (latest.key !== key) return;
setInsertCopyCache(excludePrimaryKeys, {
key,
text: statement ?? "",
loading: false,
ready: !!statement,
});
} catch {
const latest = insertCopyCache(excludePrimaryKeys);
if (latest.key !== key) return;
setInsertCopyCache(excludePrimaryKeys, {
key,
text: "",
loading: false,
ready: false,
});
}
}
function canCopyPreparedInsert(excludePrimaryKeys: boolean): boolean {
const cache = insertCopyCache(excludePrimaryKeys);
return cache.ready && cache.key === insertCopyKey(excludePrimaryKeys);
}
function copyPreparedRowAsInsert(excludePrimaryKeys: boolean): boolean {
if (!canCopyPreparedInsert(excludePrimaryKeys)) return false;
void copyText(insertCopyCache(excludePrimaryKeys).text);
return true;
}
async function prefetchRowAsUpdateStatement() {
if (!tableMeta.value?.primaryKeys.length) {
setUpdateCopyCache({
key: "",
text: "",
loading: false,
ready: false,
});
return;
}
const rows = updateEligibleRows();
if (!rows.length) {
setUpdateCopyCache({
key: "",
text: "",
loading: false,
ready: false,
});
return;
}
const key = updateCopyKey();
const current = copyRowUpdateCache.value;
if ((current.loading || current.ready) && current.key === key) return;
setUpdateCopyCache({
key,
text: "",
loading: true,
ready: false,
});
try {
const statements = await buildDataGridCopyUpdateStatements({
databaseType: databaseType.value,
tableMeta: tableMeta.value,
columns: columns.value,
sourceColumns: sourceColumns.value,
rows: rows.map((item) => item.data),
});
const latest = copyRowUpdateCache.value;
if (latest.key !== key) return;
const text = statements.join("\n");
setUpdateCopyCache({
key,
text,
loading: false,
ready: statements.length > 0,
});
} catch {
const latest = copyRowUpdateCache.value;
if (latest.key !== key) return;
setUpdateCopyCache({
key,
text: "",
loading: false,
ready: false,
});
}
}
function canCopyPreparedUpdate(): boolean {
const cache = copyRowUpdateCache.value;
return cache.ready && cache.key === updateCopyKey();
}
function copyPreparedRowAsUpdate(): boolean {
if (!canCopyPreparedUpdate()) return false;
void copyText(copyRowUpdateCache.value.text);
return true;
}
// --- Selection copy functions ---
async function copySelectionTsv() {
if (!hasCellSelection.value) return;
@ -168,38 +368,16 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
return targetedRows();
}
async function copyRowAsInsertStatement(excludePrimaryKeys: boolean) {
const statement = await buildDataGridCopyInsertStatement({
databaseType: databaseType.value,
tableMeta: tableMeta.value,
columns: columns.value,
sourceColumns: sourceColumns.value,
rows: insertEligibleRows().map((item) => item.data),
excludePrimaryKeys,
});
if (!statement) return;
await copyText(statement);
}
async function copyRowAsInsert() {
await copyRowAsInsertStatement(false);
copyPreparedRowAsInsert(false);
}
async function copyRowAsInsertWithoutPrimaryKeys() {
await copyRowAsInsertStatement(true);
copyPreparedRowAsInsert(true);
}
async function copyRowAsUpdate() {
if (!tableMeta.value?.primaryKeys.length) return;
const statements = await buildDataGridCopyUpdateStatements({
databaseType: databaseType.value,
tableMeta: tableMeta.value,
columns: columns.value,
sourceColumns: sourceColumns.value,
rows: updateEligibleRows().map((item) => item.data),
});
if (!statements.length) return;
await copyText(statements.join("\n"));
copyPreparedRowAsUpdate();
}
const canCopyRowAsUpdate = computed(() => {
@ -353,6 +531,12 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
copyRow,
copyRowAsInsert,
copyRowAsInsertWithoutPrimaryKeys,
prefetchRowAsInsertStatement,
canCopyPreparedInsert,
copyPreparedRowAsInsert,
prefetchRowAsUpdateStatement,
canCopyPreparedUpdate,
copyPreparedRowAsUpdate,
copyRowAsUpdate,
canCopyRowAsInsertWithoutPrimaryKeys,
canCopyRowAsUpdate,