mirror of
https://github.com/t8y2/dbx.git
synced 2026-11-02 00:29:30 +00:00
chore: add oxfmt + husky + lint-staged for code formatting and CI format checks
This commit is contained in:
@@ -2,18 +2,12 @@
|
||||
import { computed, ref, watch } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { isTauriRuntime } from "@/lib/tauriRuntime";
|
||||
import {
|
||||
Dialog, DialogHeader, DialogTitle, DialogFooter, DialogScrollContent,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Dialog, DialogHeader, DialogTitle, DialogFooter, DialogScrollContent } from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import {
|
||||
Select, SelectContent, SelectItem, SelectTrigger, SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import {
|
||||
Check, FileUp, Loader2, Square, Upload, X,
|
||||
} from "lucide-vue-next";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Check, FileUp, Loader2, Square, Upload, X } from "lucide-vue-next";
|
||||
import { useConnectionStore } from "@/stores/connectionStore";
|
||||
import { useToast } from "@/composables/useToast";
|
||||
import { autoMapImportColumns } from "@/lib/tableImport";
|
||||
@@ -47,7 +41,7 @@ const progress = ref<api.TableImportProgress | null>(null);
|
||||
const errorMessage = ref("");
|
||||
|
||||
const selectedConnection = computed(() =>
|
||||
props.prefillConnectionId ? store.getConfig(props.prefillConnectionId) : undefined
|
||||
props.prefillConnectionId ? store.getConfig(props.prefillConnectionId) : undefined,
|
||||
);
|
||||
const targetColumnNames = computed(() => targetColumns.value.map((column) => column.name));
|
||||
const mappedColumns = computed<api.TableImportColumnMapping[]>(() => {
|
||||
@@ -61,12 +55,13 @@ const mappedColumns = computed<api.TableImportColumnMapping[]>(() => {
|
||||
.filter((mapping) => mapping.targetColumn);
|
||||
});
|
||||
const mappedCount = computed(() => mappedColumns.value.length);
|
||||
const canImport = computed(() =>
|
||||
!!preview.value &&
|
||||
!!props.prefillConnectionId &&
|
||||
!!props.prefillTable &&
|
||||
mappedColumns.value.length > 0 &&
|
||||
!running.value
|
||||
const canImport = computed(
|
||||
() =>
|
||||
!!preview.value &&
|
||||
!!props.prefillConnectionId &&
|
||||
!!props.prefillTable &&
|
||||
mappedColumns.value.length > 0 &&
|
||||
!running.value,
|
||||
);
|
||||
const progressPercent = computed(() => {
|
||||
const p = progress.value;
|
||||
@@ -74,8 +69,12 @@ const progressPercent = computed(() => {
|
||||
return Math.min(100, Math.round((p.rowsImported / p.totalRows) * 100));
|
||||
});
|
||||
const targetLabel = computed(() => {
|
||||
const pieces = [selectedConnection.value?.name, props.prefillDatabase, props.prefillSchema, props.prefillTable]
|
||||
.filter(Boolean);
|
||||
const pieces = [
|
||||
selectedConnection.value?.name,
|
||||
props.prefillDatabase,
|
||||
props.prefillSchema,
|
||||
props.prefillTable,
|
||||
].filter(Boolean);
|
||||
return pieces.join(" / ");
|
||||
});
|
||||
|
||||
@@ -175,19 +174,22 @@ async function startImport() {
|
||||
};
|
||||
|
||||
try {
|
||||
const summary = await api.importTableFile({
|
||||
importId: importId.value,
|
||||
connectionId: props.prefillConnectionId,
|
||||
database: props.prefillDatabase || "",
|
||||
schema: props.prefillSchema || "",
|
||||
table: props.prefillTable,
|
||||
filePath: currentPreview.filePath,
|
||||
mappings: mappedColumns.value,
|
||||
mode: importMode.value,
|
||||
batchSize: Math.max(1, Number(batchSize.value) || 500),
|
||||
}, (nextProgress) => {
|
||||
progress.value = nextProgress;
|
||||
});
|
||||
const summary = await api.importTableFile(
|
||||
{
|
||||
importId: importId.value,
|
||||
connectionId: props.prefillConnectionId,
|
||||
database: props.prefillDatabase || "",
|
||||
schema: props.prefillSchema || "",
|
||||
table: props.prefillTable,
|
||||
filePath: currentPreview.filePath,
|
||||
mappings: mappedColumns.value,
|
||||
mode: importMode.value,
|
||||
batchSize: Math.max(1, Number(batchSize.value) || 500),
|
||||
},
|
||||
(nextProgress) => {
|
||||
progress.value = nextProgress;
|
||||
},
|
||||
);
|
||||
toast(t("tableImport.success", { count: summary.rowsImported }), 2500);
|
||||
open.value = false;
|
||||
} catch (e: any) {
|
||||
@@ -218,48 +220,52 @@ watch(open, (value) => {
|
||||
<DialogHeader>
|
||||
<DialogTitle class="flex items-center gap-2">
|
||||
<FileUp class="h-4 w-4" />
|
||||
{{ t('tableImport.title') }}
|
||||
{{ t("tableImport.title") }}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div class="space-y-4 py-2">
|
||||
<div class="grid grid-cols-[1fr_auto] gap-2">
|
||||
<div class="min-w-0 rounded-md border bg-muted/20 px-3 py-2">
|
||||
<div class="truncate text-xs text-muted-foreground">{{ t('tableImport.target') }}</div>
|
||||
<div class="truncate text-sm font-medium">{{ targetLabel || t('editor.noDatabase') }}</div>
|
||||
<div class="truncate text-xs text-muted-foreground">{{ t("tableImport.target") }}</div>
|
||||
<div class="truncate text-sm font-medium">
|
||||
{{ targetLabel || t("editor.noDatabase") }}
|
||||
</div>
|
||||
</div>
|
||||
<Button variant="outline" size="sm" :disabled="running || loadingPreview" @click="selectFile">
|
||||
<Loader2 v-if="loadingPreview" class="mr-1.5 h-3.5 w-3.5 animate-spin" />
|
||||
<Upload v-else class="mr-1.5 h-3.5 w-3.5" />
|
||||
{{ t('tableImport.selectFile') }}
|
||||
{{ t("tableImport.selectFile") }}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div v-if="preview" class="grid grid-cols-3 gap-2 text-xs">
|
||||
<div class="rounded-md border px-3 py-2">
|
||||
<div class="text-muted-foreground">{{ t('tableImport.file') }}</div>
|
||||
<div class="text-muted-foreground">{{ t("tableImport.file") }}</div>
|
||||
<div class="truncate font-medium">{{ preview.fileName }}</div>
|
||||
</div>
|
||||
<div class="rounded-md border px-3 py-2">
|
||||
<div class="text-muted-foreground">{{ t('tableImport.rows') }}</div>
|
||||
<div class="text-muted-foreground">{{ t("tableImport.rows") }}</div>
|
||||
<div class="font-medium">{{ preview.totalRows.toLocaleString() }}</div>
|
||||
</div>
|
||||
<div class="rounded-md border px-3 py-2">
|
||||
<div class="text-muted-foreground">{{ t('tableImport.mapped') }}</div>
|
||||
<div class="text-muted-foreground">{{ t("tableImport.mapped") }}</div>
|
||||
<div class="font-medium">{{ mappedCount }} / {{ preview.columns.length }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="preview" class="grid grid-cols-[minmax(220px,280px)_1fr] gap-3">
|
||||
<div class="rounded-md border">
|
||||
<div class="border-b px-3 py-2 text-xs font-medium">{{ t('tableImport.mapping') }}</div>
|
||||
<div class="border-b px-3 py-2 text-xs font-medium">{{ t("tableImport.mapping") }}</div>
|
||||
<div class="max-h-[280px] overflow-auto p-2">
|
||||
<div
|
||||
v-for="sourceColumn in preview.columns"
|
||||
:key="sourceColumn"
|
||||
class="grid grid-cols-[1fr_1fr] items-center gap-2 py-1"
|
||||
>
|
||||
<div class="truncate font-mono text-xs" :title="sourceColumn">{{ sourceColumn }}</div>
|
||||
<div class="truncate font-mono text-xs" :title="sourceColumn">
|
||||
{{ sourceColumn }}
|
||||
</div>
|
||||
<Select
|
||||
:model-value="columnMapping[sourceColumn] || SKIP_VALUE"
|
||||
@update:model-value="(value: any) => updateMapping(sourceColumn, value)"
|
||||
@@ -268,7 +274,7 @@ watch(open, (value) => {
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem :value="SKIP_VALUE">{{ t('tableImport.skipColumn') }}</SelectItem>
|
||||
<SelectItem :value="SKIP_VALUE">{{ t("tableImport.skipColumn") }}</SelectItem>
|
||||
<SelectItem v-for="column in targetColumns" :key="column.name" :value="column.name">
|
||||
{{ column.name }}
|
||||
</SelectItem>
|
||||
@@ -279,7 +285,7 @@ watch(open, (value) => {
|
||||
</div>
|
||||
|
||||
<div class="min-w-0 rounded-md border">
|
||||
<div class="border-b px-3 py-2 text-xs font-medium">{{ t('tableImport.preview') }}</div>
|
||||
<div class="border-b px-3 py-2 text-xs font-medium">{{ t("tableImport.preview") }}</div>
|
||||
<div class="max-h-[280px] overflow-auto">
|
||||
<table class="min-w-full border-separate border-spacing-0 text-xs">
|
||||
<thead class="sticky top-0 bg-background">
|
||||
@@ -312,29 +318,30 @@ watch(open, (value) => {
|
||||
|
||||
<div v-if="preview" class="grid grid-cols-3 gap-3">
|
||||
<div class="space-y-1.5">
|
||||
<Label class="text-xs">{{ t('tableImport.mode') }}</Label>
|
||||
<Select :model-value="importMode" @update:model-value="(value: any) => importMode = value">
|
||||
<Label class="text-xs">{{ t("tableImport.mode") }}</Label>
|
||||
<Select :model-value="importMode" @update:model-value="(value: any) => (importMode = value)">
|
||||
<SelectTrigger class="h-8 text-xs">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="append">{{ t('tableImport.append') }}</SelectItem>
|
||||
<SelectItem value="truncate">{{ t('tableImport.truncate') }}</SelectItem>
|
||||
<SelectItem value="append">{{ t("tableImport.append") }}</SelectItem>
|
||||
<SelectItem value="truncate">{{ t("tableImport.truncate") }}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div class="space-y-1.5">
|
||||
<Label class="text-xs">{{ t('transfer.batchSize') }}</Label>
|
||||
<Label class="text-xs">{{ t("transfer.batchSize") }}</Label>
|
||||
<Input v-model.number="batchSize" type="number" min="1" class="h-8 text-xs" />
|
||||
</div>
|
||||
<div v-if="running || progress" class="space-y-1.5">
|
||||
<Label class="text-xs">{{ t('tableImport.progress') }}</Label>
|
||||
<Label class="text-xs">{{ t("tableImport.progress") }}</Label>
|
||||
<div class="h-8 rounded-md border px-2 text-xs flex items-center gap-2">
|
||||
<Loader2 v-if="running && !cancelling" class="h-3.5 w-3.5 animate-spin text-primary" />
|
||||
<Square v-else-if="cancelling" class="h-3.5 w-3.5 fill-current text-destructive" />
|
||||
<Check v-else class="h-3.5 w-3.5 text-emerald-600" />
|
||||
<span class="truncate">
|
||||
{{ progress?.rowsImported ?? 0 }} / {{ progress?.totalRows ?? preview.totalRows }} · {{ progressPercent }}%
|
||||
{{ progress?.rowsImported ?? 0 }} / {{ progress?.totalRows ?? preview.totalRows }} ·
|
||||
{{ progressPercent }}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -342,9 +349,12 @@ watch(open, (value) => {
|
||||
|
||||
<div v-if="loadingTarget" class="flex items-center gap-2 text-xs text-muted-foreground">
|
||||
<Loader2 class="h-3.5 w-3.5 animate-spin" />
|
||||
{{ t('common.loading') }}
|
||||
{{ t("common.loading") }}
|
||||
</div>
|
||||
<div v-if="errorMessage" class="rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-xs text-destructive">
|
||||
<div
|
||||
v-if="errorMessage"
|
||||
class="rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-xs text-destructive"
|
||||
>
|
||||
{{ errorMessage }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -352,16 +362,16 @@ watch(open, (value) => {
|
||||
<DialogFooter>
|
||||
<Button variant="outline" :disabled="running" @click="open = false">
|
||||
<X class="mr-1.5 h-3.5 w-3.5" />
|
||||
{{ t('dangerDialog.cancel') }}
|
||||
{{ t("dangerDialog.cancel") }}
|
||||
</Button>
|
||||
<Button v-if="running" variant="destructive" :disabled="cancelling" @click="cancelImport">
|
||||
<Loader2 v-if="cancelling" class="mr-1.5 h-3.5 w-3.5 animate-spin" />
|
||||
<Square v-else class="mr-1.5 h-3.5 w-3.5 fill-current" />
|
||||
{{ t('sqlFile.cancel') }}
|
||||
{{ t("sqlFile.cancel") }}
|
||||
</Button>
|
||||
<Button v-else :disabled="!canImport" @click="startImport">
|
||||
<Upload class="mr-1.5 h-3.5 w-3.5" />
|
||||
{{ t('tableImport.start') }}
|
||||
{{ t("tableImport.start") }}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogScrollContent>
|
||||
|
||||
Reference in New Issue
Block a user