feat(desktop): copy table structure as documents
This commit is contained in:
parent
6b4a8df4eb
commit
ec5aa7a92b
|
|
@ -183,6 +183,27 @@ dependencies = [
|
|||
"derive_arbitrary",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arboard"
|
||||
version = "3.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf"
|
||||
dependencies = [
|
||||
"clipboard-win",
|
||||
"image",
|
||||
"log",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"objc2-core-foundation",
|
||||
"objc2-core-graphics",
|
||||
"objc2-foundation",
|
||||
"parking_lot",
|
||||
"percent-encoding",
|
||||
"windows-sys 0.60.2",
|
||||
"wl-clipboard-rs",
|
||||
"x11rb",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "argon2"
|
||||
version = "0.5.3"
|
||||
|
|
@ -963,6 +984,12 @@ version = "1.5.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder-lite"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.11.1"
|
||||
|
|
@ -1197,6 +1224,15 @@ dependencies = [
|
|||
"inout 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clipboard-win"
|
||||
version = "5.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4"
|
||||
dependencies = [
|
||||
"error-code",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cmake"
|
||||
version = "0.1.58"
|
||||
|
|
@ -1819,6 +1855,7 @@ dependencies = [
|
|||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-plugin-clipboard-manager",
|
||||
"tauri-plugin-deep-link",
|
||||
"tauri-plugin-dialog",
|
||||
"tauri-plugin-fs",
|
||||
|
|
@ -2187,6 +2224,12 @@ dependencies = [
|
|||
"tendril",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "downcast-rs"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
|
||||
|
||||
[[package]]
|
||||
name = "dpi"
|
||||
version = "0.1.2"
|
||||
|
|
@ -2443,6 +2486,12 @@ dependencies = [
|
|||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "error-code"
|
||||
version = "3.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59"
|
||||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "5.4.1"
|
||||
|
|
@ -2494,6 +2543,12 @@ version = "2.4.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6"
|
||||
|
||||
[[package]]
|
||||
name = "fax"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a"
|
||||
|
||||
[[package]]
|
||||
name = "fdeflate"
|
||||
version = "0.3.7"
|
||||
|
|
@ -2550,6 +2605,12 @@ version = "0.1.9"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||
|
||||
[[package]]
|
||||
name = "fixedbitset"
|
||||
version = "0.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
|
||||
|
||||
[[package]]
|
||||
name = "flagset"
|
||||
version = "0.4.7"
|
||||
|
|
@ -2911,6 +2972,16 @@ dependencies = [
|
|||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gethostname"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8"
|
||||
dependencies = [
|
||||
"rustix 1.1.4",
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.17"
|
||||
|
|
@ -3640,6 +3711,20 @@ dependencies = [
|
|||
"icu_properties",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "image"
|
||||
version = "0.25.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"byteorder-lite",
|
||||
"moxcms",
|
||||
"num-traits",
|
||||
"png 0.18.1",
|
||||
"tiff",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.3"
|
||||
|
|
@ -4487,6 +4572,16 @@ dependencies = [
|
|||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "moxcms"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"pxfm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "muda"
|
||||
version = "0.19.2"
|
||||
|
|
@ -4667,6 +4762,15 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "8.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.50.3"
|
||||
|
|
@ -4808,6 +4912,7 @@ dependencies = [
|
|||
"block2",
|
||||
"objc2",
|
||||
"objc2-core-foundation",
|
||||
"objc2-core-graphics",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
|
|
@ -5346,6 +5451,17 @@ version = "2.3.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||
|
||||
[[package]]
|
||||
name = "petgraph"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455"
|
||||
dependencies = [
|
||||
"fixedbitset",
|
||||
"hashbrown 0.15.5",
|
||||
"indexmap 2.14.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.13.1"
|
||||
|
|
@ -5796,6 +5912,18 @@ dependencies = [
|
|||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pxfm"
|
||||
version = "0.1.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f"
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.37.5"
|
||||
|
|
@ -7745,6 +7873,21 @@ dependencies = [
|
|||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-clipboard-manager"
|
||||
version = "2.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "206dc20af4ed210748ba945c2774e60fd0acd52b9a73a028402caf809e9b6ecf"
|
||||
dependencies = [
|
||||
"arboard",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"thiserror 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-deep-link"
|
||||
version = "2.4.9"
|
||||
|
|
@ -8135,6 +8278,20 @@ dependencies = [
|
|||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiff"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52"
|
||||
dependencies = [
|
||||
"fax",
|
||||
"flate2",
|
||||
"half",
|
||||
"quick-error",
|
||||
"weezl",
|
||||
"zune-jpeg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.47"
|
||||
|
|
@ -8597,6 +8754,17 @@ dependencies = [
|
|||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree_magic_mini"
|
||||
version = "3.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8765b90061cba6c22b5831f675da109ae5561588290f9fa2317adab2714d5a6"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"nom",
|
||||
"petgraph",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "try-lock"
|
||||
version = "0.2.5"
|
||||
|
|
@ -9058,6 +9226,76 @@ dependencies = [
|
|||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-backend"
|
||||
version = "0.3.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2857dd20b54e916ec7253b3d6b4d5c4d7d4ca2c33c2e11c6c76a99bd8744755d"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"downcast-rs",
|
||||
"rustix 1.1.4",
|
||||
"smallvec",
|
||||
"wayland-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-client"
|
||||
version = "0.31.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144"
|
||||
dependencies = [
|
||||
"bitflags 2.11.1",
|
||||
"rustix 1.1.4",
|
||||
"wayland-backend",
|
||||
"wayland-scanner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-protocols"
|
||||
version = "0.32.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "563a85523cade2429938e790815fd7319062103b9f4a2dc806e9b53b95982d8f"
|
||||
dependencies = [
|
||||
"bitflags 2.11.1",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-scanner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-protocols-wlr"
|
||||
version = "0.3.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234"
|
||||
dependencies = [
|
||||
"bitflags 2.11.1",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-protocols",
|
||||
"wayland-scanner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-scanner"
|
||||
version = "0.31.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c324a910fd86ebdc364a3e61ec1f11737d3b1d6c273c0239ee8ff4bc0d24b4a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quick-xml 0.39.4",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-sys"
|
||||
version = "0.31.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be"
|
||||
dependencies = [
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.99"
|
||||
|
|
@ -9197,6 +9435,12 @@ dependencies = [
|
|||
"windows-core 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "weezl"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
|
||||
|
||||
[[package]]
|
||||
name = "whoami"
|
||||
version = "2.1.2"
|
||||
|
|
@ -9860,6 +10104,24 @@ dependencies = [
|
|||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wl-clipboard-rs"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e9651471a32e87d96ef3a127715382b2d11cc7c8bb9822ded8a7cc94072eb0a3"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"os_pipe",
|
||||
"rustix 1.1.4",
|
||||
"thiserror 2.0.18",
|
||||
"tree_magic_mini",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-protocols",
|
||||
"wayland-protocols-wlr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "writeable"
|
||||
version = "0.6.3"
|
||||
|
|
@ -9940,6 +10202,23 @@ dependencies = [
|
|||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "x11rb"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414"
|
||||
dependencies = [
|
||||
"gethostname",
|
||||
"rustix 1.1.4",
|
||||
"x11rb-protocol",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "x11rb-protocol"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd"
|
||||
|
||||
[[package]]
|
||||
name = "x509-cert"
|
||||
version = "0.2.5"
|
||||
|
|
@ -10244,6 +10523,21 @@ dependencies = [
|
|||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zune-core"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9"
|
||||
|
||||
[[package]]
|
||||
name = "zune-jpeg"
|
||||
version = "0.5.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296"
|
||||
dependencies = [
|
||||
"zune-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zvariant"
|
||||
version = "5.11.0"
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ import { useSavedSqlStore } from "@/stores/savedSqlStore";
|
|||
import { useSettingsStore } from "@/stores/settingsStore";
|
||||
import { useToast } from "@/composables/useToast";
|
||||
import { useDatabaseOptions } from "@/composables/useDatabaseOptions";
|
||||
import type { DatabaseType, TreeNode, TreeNodeType } from "@/types/database";
|
||||
import type { ColumnInfo, DatabaseType, TreeNode, TreeNodeType } from "@/types/database";
|
||||
import * as api from "@/lib/api";
|
||||
import { uuid } from "@/lib/utils";
|
||||
import { resolveDefaultDatabase } from "@/lib/defaultDatabase";
|
||||
|
|
@ -150,6 +150,8 @@ const savedSqlStore = useSavedSqlStore();
|
|||
const settingsStore = useSettingsStore();
|
||||
const { toast } = useToast();
|
||||
const { highlight } = useSqlHighlighter();
|
||||
|
||||
type StructureCopyFormat = "tsv" | "markdown";
|
||||
const { getDatabaseOptions } = useDatabaseOptions();
|
||||
const showVisibleDatabasesDialog = ref(false);
|
||||
|
||||
|
|
@ -795,10 +797,13 @@ const showDropTableConfirm = ref(false);
|
|||
const showDropTableChildObjectConfirm = ref(false);
|
||||
const showBatchDropConfirm = ref(false);
|
||||
const showStructurePreviewDialog = ref(false);
|
||||
const showStructureDocCopyDialog = ref(false);
|
||||
const structurePreviewSql = ref("");
|
||||
const structurePreviewTitle = ref("");
|
||||
const structurePreviewDefaultFileName = ref("structure.sql");
|
||||
const structurePreviewError = ref("");
|
||||
const structureDocCopyText = ref("");
|
||||
const structureDocCopyTitle = ref("");
|
||||
const isLoadingStructurePreview = ref(false);
|
||||
const showEmptyTableConfirm = ref(false);
|
||||
const showTruncateTableConfirm = ref(false);
|
||||
|
|
@ -1741,6 +1746,124 @@ function structureExportTargets(): Array<TreeNode & { connectionId: string; data
|
|||
return selected.some((node) => node.id === props.node.id) ? selected : [props.node];
|
||||
}
|
||||
|
||||
function structureTargetName(target: TreeNode): string {
|
||||
return target.schema ? `${target.schema}.${target.label}` : target.label;
|
||||
}
|
||||
|
||||
function columnDocValue(value: unknown): string {
|
||||
return value === null || value === undefined ? "" : String(value);
|
||||
}
|
||||
|
||||
function tsvCell(value: unknown): string {
|
||||
return columnDocValue(value).replace(/\t/g, " ").replace(/\r?\n/g, " ").trim();
|
||||
}
|
||||
|
||||
function markdownCell(value: unknown): string {
|
||||
return columnDocValue(value).replace(/\|/g, "\\|").replace(/\r?\n/g, "<br>").trim();
|
||||
}
|
||||
|
||||
function columnDocHeaders(includeTable: boolean): string[] {
|
||||
const headers = [
|
||||
t("contextMenu.structureDocColumn"),
|
||||
t("contextMenu.structureDocType"),
|
||||
t("contextMenu.structureDocPrimaryKey"),
|
||||
t("contextMenu.structureDocNullable"),
|
||||
t("contextMenu.structureDocDefault"),
|
||||
t("contextMenu.structureDocComment"),
|
||||
];
|
||||
return includeTable ? [t("contextMenu.structureDocTable"), ...headers] : headers;
|
||||
}
|
||||
|
||||
function columnDocCells(target: TreeNode, column: ColumnInfo, includeTable: boolean): unknown[] {
|
||||
const cells = [
|
||||
column.name,
|
||||
column.data_type,
|
||||
column.is_primary_key ? t("contextMenu.structureDocYes") : t("contextMenu.structureDocNo"),
|
||||
column.is_nullable ? t("contextMenu.structureDocYes") : t("contextMenu.structureDocNo"),
|
||||
column.column_default,
|
||||
column.comment,
|
||||
];
|
||||
return includeTable ? [structureTargetName(target), ...cells] : cells;
|
||||
}
|
||||
|
||||
async function tableColumnsForStructureCopy(
|
||||
target: TreeNode & { connectionId: string; database: string },
|
||||
): Promise<ColumnInfo[]> {
|
||||
await connectionStore.ensureConnected(target.connectionId);
|
||||
return (await api.getColumns(
|
||||
target.connectionId,
|
||||
target.database,
|
||||
target.schema || target.database,
|
||||
target.label,
|
||||
)) as ColumnInfo[];
|
||||
}
|
||||
|
||||
async function buildStructureCopyText(format: StructureCopyFormat): Promise<string> {
|
||||
const targets = structureExportTargets();
|
||||
if (!targets.length) return "";
|
||||
const includeTable = targets.length > 1;
|
||||
const headers = columnDocHeaders(includeTable);
|
||||
|
||||
if (format === "tsv") {
|
||||
const lines = [headers.map(tsvCell).join("\t")];
|
||||
for (const target of targets) {
|
||||
const columns = await tableColumnsForStructureCopy(target);
|
||||
for (const column of columns) {
|
||||
lines.push(columnDocCells(target, column, includeTable).map(tsvCell).join("\t"));
|
||||
}
|
||||
}
|
||||
return `${lines.join("\n")}\n`;
|
||||
}
|
||||
|
||||
const tables: string[] = [];
|
||||
const markdownHeaders = columnDocHeaders(false);
|
||||
for (const target of targets) {
|
||||
const columns = await tableColumnsForStructureCopy(target);
|
||||
const tableLines = [
|
||||
`### ${markdownCell(structureTargetName(target))}`,
|
||||
"",
|
||||
`| ${markdownHeaders.map(markdownCell).join(" | ")} |`,
|
||||
`| ${markdownHeaders.map(() => "---").join(" | ")} |`,
|
||||
...columns.map((column) => `| ${columnDocCells(target, column, false).map(markdownCell).join(" | ")} |`),
|
||||
];
|
||||
tables.push(tableLines.join("\n"));
|
||||
}
|
||||
return `${tables.join("\n\n")}\n`;
|
||||
}
|
||||
|
||||
async function copyStructureAs(format: StructureCopyFormat) {
|
||||
let text = "";
|
||||
try {
|
||||
text = await buildStructureCopyText(format);
|
||||
if (!text) return;
|
||||
await copyToClipboard(text);
|
||||
toast(t("contextMenu.structureDocCopied"), 2000);
|
||||
} catch (e: any) {
|
||||
if (text) {
|
||||
structureDocCopyText.value = text;
|
||||
structureDocCopyTitle.value =
|
||||
format === "tsv" ? t("contextMenu.copyStructureAsTsv") : t("contextMenu.copyStructureAsMarkdown");
|
||||
showStructureDocCopyDialog.value = true;
|
||||
return;
|
||||
}
|
||||
toast(t("grid.copyFailed", { message: e?.message || String(e) }), 5000);
|
||||
}
|
||||
}
|
||||
|
||||
async function copyStructureDocText() {
|
||||
if (!structureDocCopyText.value) return;
|
||||
try {
|
||||
await copyToClipboard(structureDocCopyText.value);
|
||||
toast(t("contextMenu.structureDocCopied"), 2000);
|
||||
} catch (e: any) {
|
||||
toast(t("grid.copyFailed", { message: e?.message || String(e) }), 5000);
|
||||
}
|
||||
}
|
||||
|
||||
function selectTextareaContent(event: FocusEvent) {
|
||||
if (event.target instanceof HTMLTextAreaElement) event.target.select();
|
||||
}
|
||||
|
||||
async function copyStructurePreview() {
|
||||
if (!structurePreviewSql.value) return;
|
||||
try {
|
||||
|
|
@ -2428,6 +2551,17 @@ function exportDataSubmenu(): ContextMenuItem {
|
|||
};
|
||||
}
|
||||
|
||||
function copyStructureAsSubmenu(): ContextMenuItem {
|
||||
return {
|
||||
label: t("contextMenu.copyStructureAs"),
|
||||
icon: Clipboard,
|
||||
children: [
|
||||
{ label: t("contextMenu.copyStructureAsTsv"), action: () => copyStructureAs("tsv") },
|
||||
{ label: t("contextMenu.copyStructureAsMarkdown"), action: () => copyStructureAs("markdown") },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function treeItemMenuItems(): ContextMenuItem[] {
|
||||
const node = props.node;
|
||||
const items: ContextMenuItem[] = [];
|
||||
|
|
@ -2629,6 +2763,7 @@ function treeItemMenuItems(): ContextMenuItem[] {
|
|||
items.push(exportDataSubmenu());
|
||||
items.push({ label: t("contextMenu.exportDatabase"), action: openDatabaseExport, icon: Download });
|
||||
items.push({ label: t("contextMenu.exportStructure"), action: exportStructure, icon: FileCode });
|
||||
items.push(copyStructureAsSubmenu());
|
||||
if (isTableNotView.value) {
|
||||
items.push({ label: "", separator: true });
|
||||
items.push({ label: t("contextMenu.duplicateStructure"), action: duplicateStructure, icon: CopyPlus });
|
||||
|
|
@ -3037,6 +3172,30 @@ function treeItemMenuItems(): ContextMenuItem[] {
|
|||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<Dialog v-model:open="showStructureDocCopyDialog">
|
||||
<DialogContent class="sm:max-w-[760px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ structureDocCopyTitle || t("contextMenu.copyStructureAs") }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div class="grid gap-3">
|
||||
<p class="text-sm text-muted-foreground">{{ t("contextMenu.structureDocCopyFallbackHint") }}</p>
|
||||
<textarea
|
||||
readonly
|
||||
class="max-h-[56vh] min-h-64 resize-y overflow-auto rounded bg-muted p-3 font-mono text-xs whitespace-pre"
|
||||
:value="structureDocCopyText"
|
||||
@focus="selectTextareaContent"
|
||||
></textarea>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" @click="showStructureDocCopyDialog = false">{{ t("dangerDialog.cancel") }}</Button>
|
||||
<Button :disabled="!structureDocCopyText" @click="copyStructureDocText">
|
||||
<Clipboard class="h-4 w-4" />
|
||||
{{ t("contextMenu.copyStructure") }}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<Dialog v-model:open="showDeleteSavedSqlFileConfirm">
|
||||
<DialogContent class="sm:max-w-[400px]">
|
||||
<DialogHeader>
|
||||
|
|
|
|||
|
|
@ -922,6 +922,20 @@ export default {
|
|||
exportStructurePreviewTitleMultiple: "Structure - {count} objects",
|
||||
exportStructureLoading: "Loading structure...",
|
||||
copyStructure: "Copy Structure",
|
||||
copyStructureAs: "Copy Structure As",
|
||||
copyStructureAsTsv: "TSV (for Excel)",
|
||||
copyStructureAsMarkdown: "Markdown Table",
|
||||
structureDocTable: "Table",
|
||||
structureDocColumn: "Column",
|
||||
structureDocType: "Type",
|
||||
structureDocPrimaryKey: "Primary Key",
|
||||
structureDocNullable: "Nullable",
|
||||
structureDocDefault: "Default",
|
||||
structureDocComment: "Comment",
|
||||
structureDocYes: "Yes",
|
||||
structureDocNo: "No",
|
||||
structureDocCopied: "Structure copied",
|
||||
structureDocCopyFallbackHint: "Automatic copy was blocked. Click Copy again, or select the content below.",
|
||||
saveStructure: "Save SQL",
|
||||
exportStructureCopied: "Structure copied",
|
||||
createTable: "Create Table",
|
||||
|
|
|
|||
|
|
@ -818,6 +818,21 @@ export default {
|
|||
exportStructurePreviewTitleMultiple: "Estructura - {count} objetos",
|
||||
exportStructureLoading: "Cargando estructura...",
|
||||
copyStructure: "Copiar estructura",
|
||||
copyStructureAs: "Copiar estructura como",
|
||||
copyStructureAsTsv: "TSV (para Excel)",
|
||||
copyStructureAsMarkdown: "Tabla Markdown",
|
||||
structureDocTable: "Tabla",
|
||||
structureDocColumn: "Columna",
|
||||
structureDocType: "Tipo",
|
||||
structureDocPrimaryKey: "Clave primaria",
|
||||
structureDocNullable: "Permite NULL",
|
||||
structureDocDefault: "Valor predeterminado",
|
||||
structureDocComment: "Comentario",
|
||||
structureDocYes: "Sí",
|
||||
structureDocNo: "No",
|
||||
structureDocCopied: "Estructura copiada",
|
||||
structureDocCopyFallbackHint:
|
||||
"La copia automática fue bloqueada. Haz clic en Copiar de nuevo o selecciona el contenido inferior.",
|
||||
saveStructure: "Guardar SQL",
|
||||
exportStructureCopied: "Estructura copiada",
|
||||
createTable: "Crear tabla",
|
||||
|
|
|
|||
|
|
@ -903,6 +903,20 @@ export default {
|
|||
exportStructurePreviewTitleMultiple: "表结构 - {count} 个对象",
|
||||
exportStructureLoading: "正在读取表结构...",
|
||||
copyStructure: "复制表结构",
|
||||
copyStructureAs: "复制表结构为",
|
||||
copyStructureAsTsv: "TSV(粘贴到 Excel)",
|
||||
copyStructureAsMarkdown: "Markdown 表格",
|
||||
structureDocTable: "表",
|
||||
structureDocColumn: "字段名称",
|
||||
structureDocType: "类型",
|
||||
structureDocPrimaryKey: "主键",
|
||||
structureDocNullable: "允许为空",
|
||||
structureDocDefault: "默认值",
|
||||
structureDocComment: "注释",
|
||||
structureDocYes: "是",
|
||||
structureDocNo: "否",
|
||||
structureDocCopied: "表结构已复制",
|
||||
structureDocCopyFallbackHint: "自动复制被系统拦截。请再次点击复制,或选中下方内容手动复制。",
|
||||
saveStructure: "保存 SQL",
|
||||
exportStructureCopied: "表结构已复制",
|
||||
createTable: "新建表",
|
||||
|
|
|
|||
|
|
@ -903,6 +903,20 @@ export default {
|
|||
exportStructurePreviewTitleMultiple: "資料表結構 - {count} 個物件",
|
||||
exportStructureLoading: "正在讀取資料表結構...",
|
||||
copyStructure: "複製資料表結構",
|
||||
copyStructureAs: "複製資料表結構為",
|
||||
copyStructureAsTsv: "TSV(貼到 Excel)",
|
||||
copyStructureAsMarkdown: "Markdown 表格",
|
||||
structureDocTable: "資料表",
|
||||
structureDocColumn: "欄位名稱",
|
||||
structureDocType: "類型",
|
||||
structureDocPrimaryKey: "主鍵",
|
||||
structureDocNullable: "允許空值",
|
||||
structureDocDefault: "預設值",
|
||||
structureDocComment: "註解",
|
||||
structureDocYes: "是",
|
||||
structureDocNo: "否",
|
||||
structureDocCopied: "資料表結構已複製",
|
||||
structureDocCopyFallbackHint: "自動複製被系統阻擋。請再次點擊複製,或選取下方內容手動複製。",
|
||||
saveStructure: "儲存 SQL",
|
||||
exportStructureCopied: "資料表結構已複製",
|
||||
createTable: "建立資料表",
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { isTauriRuntime } from "./tauriRuntime";
|
||||
|
||||
interface ClipboardApi {
|
||||
writeText?: (text: string) => Promise<void> | void;
|
||||
}
|
||||
|
|
@ -15,6 +17,7 @@ interface ClipboardTextarea {
|
|||
opacity?: string;
|
||||
};
|
||||
setAttribute(name: string, value: string): void;
|
||||
focus?(): void;
|
||||
select(): void;
|
||||
setSelectionRange?(start: number, end: number): void;
|
||||
}
|
||||
|
|
@ -37,6 +40,16 @@ export async function copyToClipboard(
|
|||
text: string,
|
||||
env: ClipboardEnvironment = globalThis as unknown as ClipboardEnvironment,
|
||||
): Promise<void> {
|
||||
if (isTauriRuntime(env as unknown as Record<string, unknown>)) {
|
||||
try {
|
||||
const { writeText } = await import("@tauri-apps/plugin-clipboard-manager");
|
||||
await writeText(text);
|
||||
return;
|
||||
} catch {
|
||||
// Fall through to Web Clipboard / legacy copy when the native plugin is unavailable.
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (env.navigator?.clipboard?.writeText) {
|
||||
await env.navigator.clipboard.writeText(text);
|
||||
|
|
@ -61,6 +74,7 @@ export async function copyToClipboard(
|
|||
|
||||
document.body.appendChild(textarea);
|
||||
try {
|
||||
textarea.focus?.();
|
||||
textarea.select();
|
||||
textarea.setSelectionRange?.(0, text.length);
|
||||
if (!document.execCommand("copy")) {
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@
|
|||
"@codemirror/theme-one-dark": "^6.1.3",
|
||||
"@codemirror/view": "^6.41.1",
|
||||
"@tauri-apps/api": "^2.11.0",
|
||||
"@tauri-apps/plugin-clipboard-manager": "^2.3.2",
|
||||
"@tauri-apps/plugin-dialog": "^2.7.1",
|
||||
"@tauri-apps/plugin-fs": "^2.5.1",
|
||||
"@tauri-apps/plugin-process": "^2.3.1",
|
||||
|
|
|
|||
|
|
@ -41,6 +41,9 @@ importers:
|
|||
'@tauri-apps/api':
|
||||
specifier: ^2.11.0
|
||||
version: 2.11.0
|
||||
'@tauri-apps/plugin-clipboard-manager':
|
||||
specifier: ^2.3.2
|
||||
version: 2.3.2
|
||||
'@tauri-apps/plugin-dialog':
|
||||
specifier: ^2.7.1
|
||||
version: 2.7.1
|
||||
|
|
@ -1460,6 +1463,9 @@ packages:
|
|||
engines: {node: '>= 10'}
|
||||
hasBin: true
|
||||
|
||||
'@tauri-apps/plugin-clipboard-manager@2.3.2':
|
||||
resolution: {integrity: sha512-CUlb5Hqi2oZbcZf4VUyUH53XWPPdtpw43EUpCza5HWZJwxEoDowFzNUDt1tRUXA8Uq+XPn17Ysfptip33sG4eQ==}
|
||||
|
||||
'@tauri-apps/plugin-dialog@2.7.1':
|
||||
resolution: {integrity: sha512-OK1UBXYt+ojcmxMktzzuyonYIFta8CmAASpX+CA+DTGK24KlHjhYI6x2iOJ/TjZF4N7/ACK1oFmEOjIY9IhzOQ==}
|
||||
|
||||
|
|
@ -4696,6 +4702,10 @@ snapshots:
|
|||
'@tauri-apps/cli-win32-ia32-msvc': 2.11.0
|
||||
'@tauri-apps/cli-win32-x64-msvc': 2.11.0
|
||||
|
||||
'@tauri-apps/plugin-clipboard-manager@2.3.2':
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.11.0
|
||||
|
||||
'@tauri-apps/plugin-dialog@2.7.1':
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.11.0
|
||||
|
|
|
|||
|
|
@ -52,3 +52,4 @@ calamine = "0.30.1"
|
|||
zip = "4.6.1"
|
||||
dbx-core = { path = "../crates/dbx-core" }
|
||||
font-kit = "0.14.3"
|
||||
tauri-plugin-clipboard-manager = "2.3.2"
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
"core:window:allow-close",
|
||||
"core:window:allow-start-dragging",
|
||||
"core:webview:allow-set-webview-zoom",
|
||||
"clipboard-manager:allow-write-text",
|
||||
"dialog:default",
|
||||
"dialog:allow-save",
|
||||
"dialog:allow-open",
|
||||
|
|
|
|||
|
|
@ -233,6 +233,7 @@ pub fn run() {
|
|||
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_deep_link::init())
|
||||
.plugin(tauri_plugin_clipboard_manager::init())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_fs::init())
|
||||
.plugin(tauri_plugin_single_instance::init(|app, args, cwd| {
|
||||
|
|
|
|||
Loading…
Reference in New Issue