mirror of
https://github.com/chartdb/chartdb.git
synced 2025-11-01 04:23:57 +00:00
add import database dialog
This commit is contained in:
committed by
Guy Ben-Aharon
parent
970bd4d801
commit
45f4bf88a8
@@ -44,11 +44,19 @@ export interface ChartDBContext {
|
||||
table: DBTable,
|
||||
options?: { updateHistory: boolean }
|
||||
) => Promise<void>;
|
||||
addTables: (
|
||||
tables: DBTable[],
|
||||
options?: { updateHistory: boolean }
|
||||
) => Promise<void>;
|
||||
getTable: (id: string) => DBTable | null;
|
||||
removeTable: (
|
||||
id: string,
|
||||
options?: { updateHistory: boolean }
|
||||
) => Promise<void>;
|
||||
removeTables: (
|
||||
ids: string[],
|
||||
options?: { updateHistory: boolean }
|
||||
) => Promise<void>;
|
||||
updateTable: (
|
||||
id: string,
|
||||
table: Partial<DBTable>,
|
||||
@@ -163,7 +171,9 @@ export const chartDBContext = createContext<ChartDBContext>({
|
||||
createTable: emptyFn,
|
||||
getTable: emptyFn,
|
||||
addTable: emptyFn,
|
||||
addTables: emptyFn,
|
||||
removeTable: emptyFn,
|
||||
removeTables: emptyFn,
|
||||
updateTable: emptyFn,
|
||||
updateTablesState: emptyFn,
|
||||
|
||||
|
||||
@@ -253,21 +253,21 @@ export const ChartDBProvider: React.FC<React.PropsWithChildren> = ({
|
||||
]
|
||||
);
|
||||
|
||||
const addTable: ChartDBContext['addTable'] = useCallback(
|
||||
async (table: DBTable, options = { updateHistory: true }) => {
|
||||
setTables((tables) => [...tables, table]);
|
||||
const addTables: ChartDBContext['addTables'] = useCallback(
|
||||
async (tables: DBTable[], options = { updateHistory: true }) => {
|
||||
setTables((currentTables) => [...currentTables, ...tables]);
|
||||
const updatedAt = new Date();
|
||||
setDiagramUpdatedAt(updatedAt);
|
||||
await Promise.all([
|
||||
db.updateDiagram({ id: diagramId, attributes: { updatedAt } }),
|
||||
db.addTable({ diagramId, table }),
|
||||
...tables.map((table) => db.addTable({ diagramId, table })),
|
||||
]);
|
||||
|
||||
if (options.updateHistory) {
|
||||
addUndoAction({
|
||||
action: 'addTable',
|
||||
redoData: { table },
|
||||
undoData: { tableId: table.id },
|
||||
action: 'addTables',
|
||||
redoData: { tables },
|
||||
undoData: { tableIds: tables.map((t) => t.id) },
|
||||
});
|
||||
resetRedoStack();
|
||||
}
|
||||
@@ -275,6 +275,13 @@ export const ChartDBProvider: React.FC<React.PropsWithChildren> = ({
|
||||
[db, diagramId, setTables, addUndoAction, resetRedoStack]
|
||||
);
|
||||
|
||||
const addTable: ChartDBContext['addTable'] = useCallback(
|
||||
async (table: DBTable, options = { updateHistory: true }) => {
|
||||
return addTables([table], options);
|
||||
},
|
||||
[addTables]
|
||||
);
|
||||
|
||||
const createTable: ChartDBContext['createTable'] = useCallback(
|
||||
async (attributes) => {
|
||||
const table: DBTable = {
|
||||
@@ -314,22 +321,27 @@ export const ChartDBProvider: React.FC<React.PropsWithChildren> = ({
|
||||
[tables]
|
||||
);
|
||||
|
||||
const removeTable: ChartDBContext['removeTable'] = useCallback(
|
||||
async (id: string, options = { updateHistory: true }) => {
|
||||
const table = getTable(id);
|
||||
const removeTables: ChartDBContext['removeTables'] = useCallback(
|
||||
async (ids, options) => {
|
||||
const tables = ids.map((id) => getTable(id)).filter((t) => !!t);
|
||||
const relationshipsToRemove = relationships.filter(
|
||||
(relationship) =>
|
||||
relationship.sourceTableId === id ||
|
||||
relationship.targetTableId === id
|
||||
ids.includes(relationship.sourceTableId) ||
|
||||
ids.includes(relationship.targetTableId)
|
||||
);
|
||||
|
||||
setRelationships((relationships) =>
|
||||
relationships.filter(
|
||||
(relationship) =>
|
||||
relationship.sourceTableId !== id &&
|
||||
relationship.targetTableId !== id
|
||||
!relationshipsToRemove.some(
|
||||
(r) => r.id === relationship.id
|
||||
)
|
||||
)
|
||||
);
|
||||
setTables((tables) => tables.filter((table) => table.id !== id));
|
||||
|
||||
setTables((tables) =>
|
||||
tables.filter((table) => !ids.includes(table.id))
|
||||
);
|
||||
|
||||
const updatedAt = new Date();
|
||||
setDiagramUpdatedAt(updatedAt);
|
||||
@@ -338,14 +350,16 @@ export const ChartDBProvider: React.FC<React.PropsWithChildren> = ({
|
||||
...relationshipsToRemove.map((relationship) =>
|
||||
db.deleteRelationship({ diagramId, id: relationship.id })
|
||||
),
|
||||
db.deleteTable({ diagramId, id }),
|
||||
...ids.map((id) => db.deleteTable({ diagramId, id })),
|
||||
]);
|
||||
|
||||
if (!!table && options.updateHistory) {
|
||||
if (tables.length > 0 && options?.updateHistory) {
|
||||
addUndoAction({
|
||||
action: 'removeTable',
|
||||
redoData: { tableId: id },
|
||||
undoData: { table, relationships: relationshipsToRemove },
|
||||
action: 'removeTables',
|
||||
redoData: {
|
||||
tableIds: ids,
|
||||
},
|
||||
undoData: { tables, relationships: relationshipsToRemove },
|
||||
});
|
||||
resetRedoStack();
|
||||
}
|
||||
@@ -361,6 +375,13 @@ export const ChartDBProvider: React.FC<React.PropsWithChildren> = ({
|
||||
]
|
||||
);
|
||||
|
||||
const removeTable: ChartDBContext['removeTable'] = useCallback(
|
||||
async (id: string, options = { updateHistory: true }) => {
|
||||
return removeTables([id], options);
|
||||
},
|
||||
[removeTables]
|
||||
);
|
||||
|
||||
const updateTable: ChartDBContext['updateTable'] = useCallback(
|
||||
async (
|
||||
id: string,
|
||||
@@ -1147,8 +1168,10 @@ export const ChartDBProvider: React.FC<React.PropsWithChildren> = ({
|
||||
updateDiagramUpdatedAt,
|
||||
createTable,
|
||||
addTable,
|
||||
addTables,
|
||||
getTable,
|
||||
removeTable,
|
||||
removeTables,
|
||||
updateTable,
|
||||
updateTablesState,
|
||||
updateField,
|
||||
|
||||
@@ -23,6 +23,10 @@ export interface DialogContext {
|
||||
// Create relationship dialog
|
||||
openCreateRelationshipDialog: () => void;
|
||||
closeCreateRelationshipDialog: () => void;
|
||||
|
||||
// Import database dialog
|
||||
openImportDatabaseDialog: (params: { databaseType: DatabaseType }) => void;
|
||||
closeImportDatabaseDialog: () => void;
|
||||
}
|
||||
|
||||
export const dialogContext = createContext<DialogContext>({
|
||||
@@ -36,4 +40,6 @@ export const dialogContext = createContext<DialogContext>({
|
||||
showAlert: emptyFn,
|
||||
closeCreateRelationshipDialog: emptyFn,
|
||||
openCreateRelationshipDialog: emptyFn,
|
||||
openImportDatabaseDialog: emptyFn,
|
||||
closeImportDatabaseDialog: emptyFn,
|
||||
});
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
BaseAlertDialogProps,
|
||||
} from '@/dialogs/base-alert-dialog/base-alert-dialog';
|
||||
import { CreateRelationshipDialog } from '@/dialogs/create-relationship-dialog/create-relationship-dialog';
|
||||
import { ImportDatabaseDialog } from '@/dialogs/import-database-dialog/import-database-dialog';
|
||||
|
||||
export const DialogProvider: React.FC<React.PropsWithChildren> = ({
|
||||
children,
|
||||
@@ -21,6 +22,12 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
|
||||
}>({ targetDatabaseType: DatabaseType.GENERIC });
|
||||
const [openCreateRelationshipDialog, setOpenCreateRelationshipDialog] =
|
||||
useState(false);
|
||||
const [openImportDatabaseDialog, setOpenImportDatabaseDialog] =
|
||||
useState(false);
|
||||
const [openImportDatabaseDialogParams, setOpenImportDatabaseDialogParams] =
|
||||
useState<{ databaseType: DatabaseType }>({
|
||||
databaseType: DatabaseType.GENERIC,
|
||||
});
|
||||
const [showAlert, setShowAlert] = useState(false);
|
||||
const [alertParams, setAlertParams] = useState<BaseAlertDialogProps>({
|
||||
title: '',
|
||||
@@ -35,6 +42,15 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
|
||||
[setOpenExportSQLDialog]
|
||||
);
|
||||
|
||||
const openImportDatabaseDialogHandler: DialogContext['openImportDatabaseDialog'] =
|
||||
useCallback(
|
||||
({ databaseType }) => {
|
||||
setOpenImportDatabaseDialog(true);
|
||||
setOpenImportDatabaseDialogParams({ databaseType });
|
||||
},
|
||||
[setOpenImportDatabaseDialog]
|
||||
);
|
||||
|
||||
const showAlertHandler: DialogContext['showAlert'] = useCallback(
|
||||
(params) => {
|
||||
setAlertParams(params);
|
||||
@@ -62,6 +78,9 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
|
||||
setOpenCreateRelationshipDialog(true),
|
||||
closeCreateRelationshipDialog: () =>
|
||||
setOpenCreateRelationshipDialog(false),
|
||||
openImportDatabaseDialog: openImportDatabaseDialogHandler,
|
||||
closeImportDatabaseDialog: () =>
|
||||
setOpenImportDatabaseDialog(false),
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
@@ -75,6 +94,10 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
|
||||
<CreateRelationshipDialog
|
||||
dialog={{ open: openCreateRelationshipDialog }}
|
||||
/>
|
||||
<ImportDatabaseDialog
|
||||
dialog={{ open: openImportDatabaseDialog }}
|
||||
{...openImportDatabaseDialogParams}
|
||||
/>
|
||||
</dialogContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -17,7 +17,9 @@ export const HistoryProvider: React.FC<React.PropsWithChildren> = ({
|
||||
} = useRedoUndoStack();
|
||||
const {
|
||||
addTable,
|
||||
addTables,
|
||||
removeTable,
|
||||
removeTables,
|
||||
updateTable,
|
||||
updateDiagramName,
|
||||
removeField,
|
||||
@@ -42,9 +44,15 @@ export const HistoryProvider: React.FC<React.PropsWithChildren> = ({
|
||||
addTable: ({ redoData: { table } }) => {
|
||||
return addTable(table, { updateHistory: false });
|
||||
},
|
||||
addTables: ({ redoData: { tables } }) => {
|
||||
return addTables(tables, { updateHistory: false });
|
||||
},
|
||||
removeTable: ({ redoData: { tableId } }) => {
|
||||
return removeTable(tableId, { updateHistory: false });
|
||||
},
|
||||
removeTables: ({ redoData: { tableIds } }) => {
|
||||
return removeTables(tableIds, { updateHistory: false });
|
||||
},
|
||||
updateTable: ({ redoData: { tableId, table } }) => {
|
||||
return updateTable(tableId, table, { updateHistory: false });
|
||||
},
|
||||
@@ -104,7 +112,9 @@ export const HistoryProvider: React.FC<React.PropsWithChildren> = ({
|
||||
}),
|
||||
[
|
||||
addTable,
|
||||
addTables,
|
||||
removeTable,
|
||||
removeTables,
|
||||
updateTable,
|
||||
updateDiagramName,
|
||||
removeField,
|
||||
@@ -130,12 +140,21 @@ export const HistoryProvider: React.FC<React.PropsWithChildren> = ({
|
||||
addTable: ({ undoData: { tableId } }) => {
|
||||
return removeTable(tableId, { updateHistory: false });
|
||||
},
|
||||
addTables: ({ undoData: { tableIds } }) => {
|
||||
return removeTables(tableIds, { updateHistory: false });
|
||||
},
|
||||
removeTable: async ({ undoData: { table, relationships } }) => {
|
||||
await Promise.all([
|
||||
addTable(table, { updateHistory: false }),
|
||||
addRelationships(relationships, { updateHistory: false }),
|
||||
]);
|
||||
},
|
||||
removeTables: async ({ undoData: { tables, relationships } }) => {
|
||||
await Promise.all([
|
||||
addTables(tables, { updateHistory: false }),
|
||||
addRelationships(relationships, { updateHistory: false }),
|
||||
]);
|
||||
},
|
||||
updateTable: ({ undoData: { tableId, table } }) => {
|
||||
return updateTable(tableId, table, { updateHistory: false });
|
||||
},
|
||||
@@ -200,7 +219,9 @@ export const HistoryProvider: React.FC<React.PropsWithChildren> = ({
|
||||
}),
|
||||
[
|
||||
addTable,
|
||||
addTables,
|
||||
removeTable,
|
||||
removeTables,
|
||||
updateTable,
|
||||
updateDiagramName,
|
||||
removeField,
|
||||
|
||||
@@ -30,12 +30,24 @@ type RedoUndoActionAddTable = RedoUndoActionBase<
|
||||
{ tableId: string }
|
||||
>;
|
||||
|
||||
type RedoUndoActionAddTables = RedoUndoActionBase<
|
||||
'addTables',
|
||||
{ tables: DBTable[] },
|
||||
{ tableIds: string[] }
|
||||
>;
|
||||
|
||||
type RedoUndoActionRemoveTable = RedoUndoActionBase<
|
||||
'removeTable',
|
||||
{ tableId: string },
|
||||
{ table: DBTable; relationships: DBRelationship[] }
|
||||
>;
|
||||
|
||||
type RedoUndoActionRemoveTables = RedoUndoActionBase<
|
||||
'removeTables',
|
||||
{ tableIds: string[] },
|
||||
{ tables: DBTable[]; relationships: DBRelationship[] }
|
||||
>;
|
||||
|
||||
type RedoUndoActionUpdateTablesState = RedoUndoActionBase<
|
||||
'updateTablesState',
|
||||
{ tables: DBTable[] },
|
||||
@@ -110,7 +122,9 @@ type RedoUndoActionRemoveRelationships = RedoUndoActionBase<
|
||||
|
||||
export type RedoUndoAction =
|
||||
| RedoUndoActionAddTable
|
||||
| RedoUndoActionAddTables
|
||||
| RedoUndoActionRemoveTable
|
||||
| RedoUndoActionRemoveTables
|
||||
| RedoUndoActionUpdateTable
|
||||
| RedoUndoActionUpdateDiagramName
|
||||
| RedoUndoActionUpdateTablesState
|
||||
|
||||
@@ -19,6 +19,7 @@ export interface BaseAlertDialogProps {
|
||||
closeLabel?: string;
|
||||
onAction?: () => void;
|
||||
dialog?: AlertDialogProps;
|
||||
onClose?: () => void;
|
||||
content?: React.ReactNode;
|
||||
}
|
||||
|
||||
@@ -30,8 +31,15 @@ export const BaseAlertDialog: React.FC<BaseAlertDialogProps> = ({
|
||||
onAction,
|
||||
dialog,
|
||||
content,
|
||||
onClose,
|
||||
}) => {
|
||||
const { closeAlert } = useDialog();
|
||||
|
||||
const closeAlertHandler = useCallback(() => {
|
||||
onClose?.();
|
||||
closeAlert();
|
||||
}, [onClose, closeAlert]);
|
||||
|
||||
const alertHandler = useCallback(() => {
|
||||
onAction?.();
|
||||
closeAlert();
|
||||
@@ -57,7 +65,7 @@ export const BaseAlertDialog: React.FC<BaseAlertDialogProps> = ({
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
{closeLabel && (
|
||||
<AlertDialogCancel onClick={closeAlert}>
|
||||
<AlertDialogCancel onClick={closeAlertHandler}>
|
||||
{closeLabel}
|
||||
</AlertDialogCancel>
|
||||
)}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { Button } from '@/components/button/button';
|
||||
import {
|
||||
DialogClose,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
@@ -23,7 +24,6 @@ import {
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from '@/components/avatar/avatar';
|
||||
import { CreateDiagramDialogStep } from '../create-diagram-dialog-step';
|
||||
import { SSMSInfo } from './ssms-info/ssms-info';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Tabs, TabsList, TabsTrigger } from '@/components/tabs/tabs';
|
||||
@@ -32,10 +32,15 @@ import {
|
||||
databaseClientToLabelMap,
|
||||
databaseTypeToClientsMap,
|
||||
} from '@/lib/domain/database-clients';
|
||||
import { isDatabaseMetadata } from '@/lib/data/import-metadata/metadata-types/database-metadata';
|
||||
|
||||
export interface ImportDatabaseStepProps {
|
||||
setStep: React.Dispatch<React.SetStateAction<CreateDiagramDialogStep>>;
|
||||
createNewDiagram: () => void;
|
||||
const errorScriptOutputMessage =
|
||||
'Invalid JSON. Please correct it or contact us at chartdb.io@gmail.com for help.';
|
||||
|
||||
export interface ImportDatabaseProps {
|
||||
goBack?: () => void;
|
||||
onImport: () => void;
|
||||
onCreateEmptyDiagram?: () => void;
|
||||
scriptResult: string;
|
||||
setScriptResult: React.Dispatch<React.SetStateAction<string>>;
|
||||
databaseType: DatabaseType;
|
||||
@@ -43,24 +48,54 @@ export interface ImportDatabaseStepProps {
|
||||
setDatabaseEdition: React.Dispatch<
|
||||
React.SetStateAction<DatabaseEdition | undefined>
|
||||
>;
|
||||
errorMessage: string;
|
||||
keepDialogAfterImport?: boolean;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export const ImportDatabaseStep: React.FC<ImportDatabaseStepProps> = ({
|
||||
export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
|
||||
setScriptResult,
|
||||
setStep,
|
||||
goBack,
|
||||
scriptResult,
|
||||
createNewDiagram,
|
||||
onImport,
|
||||
onCreateEmptyDiagram,
|
||||
databaseType,
|
||||
databaseEdition,
|
||||
setDatabaseEdition,
|
||||
errorMessage,
|
||||
keepDialogAfterImport,
|
||||
title,
|
||||
}) => {
|
||||
const databaseClients = databaseTypeToClientsMap[databaseType];
|
||||
const [errorMessage, setErrorMessage] = useState('');
|
||||
const [databaseClient, setDatabaseClient] = useState<
|
||||
DatabaseClient | undefined
|
||||
>();
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
if (scriptResult.trim().length === 0) {
|
||||
setErrorMessage('');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const parsedResult = JSON.parse(scriptResult);
|
||||
|
||||
if (isDatabaseMetadata(parsedResult)) {
|
||||
setErrorMessage('');
|
||||
} else {
|
||||
setErrorMessage(errorScriptOutputMessage);
|
||||
}
|
||||
} catch (error) {
|
||||
setErrorMessage(errorScriptOutputMessage);
|
||||
}
|
||||
}, [scriptResult]);
|
||||
|
||||
const handleImport = useCallback(() => {
|
||||
if (errorMessage.length === 0 && scriptResult.trim().length !== 0) {
|
||||
onImport();
|
||||
}
|
||||
}, [errorMessage.length, onImport, scriptResult]);
|
||||
|
||||
const handleInputChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
const inputValue = e.target.value;
|
||||
@@ -72,12 +107,11 @@ export const ImportDatabaseStep: React.FC<ImportDatabaseStepProps> = ({
|
||||
const renderHeader = useCallback(() => {
|
||||
return (
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{t('new_diagram_dialog.import_database.title')}
|
||||
</DialogTitle>
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
<DialogDescription className="hidden" />
|
||||
</DialogHeader>
|
||||
);
|
||||
}, [t]);
|
||||
}, [title]);
|
||||
|
||||
const renderContent = useCallback(() => {
|
||||
return (
|
||||
@@ -253,27 +287,30 @@ export const ImportDatabaseStep: React.FC<ImportDatabaseStepProps> = ({
|
||||
return (
|
||||
<DialogFooter className="mt-4 flex !justify-between gap-2">
|
||||
<div className="flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2">
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
onClick={() =>
|
||||
setStep(CreateDiagramDialogStep.SELECT_DATABASE)
|
||||
}
|
||||
>
|
||||
{t('new_diagram_dialog.back')}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end sm:space-x-2">
|
||||
<DialogClose asChild>
|
||||
{goBack && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={createNewDiagram}
|
||||
variant="secondary"
|
||||
onClick={goBack}
|
||||
>
|
||||
{t('new_diagram_dialog.empty_diagram')}
|
||||
{t('new_diagram_dialog.back')}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<DialogClose asChild>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end sm:space-x-2">
|
||||
{onCreateEmptyDiagram && (
|
||||
<DialogClose asChild>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={onCreateEmptyDiagram}
|
||||
>
|
||||
{t('new_diagram_dialog.empty_diagram')}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
)}
|
||||
|
||||
{keepDialogAfterImport ? (
|
||||
<Button
|
||||
type="button"
|
||||
variant="default"
|
||||
@@ -281,15 +318,37 @@ export const ImportDatabaseStep: React.FC<ImportDatabaseStepProps> = ({
|
||||
scriptResult.trim().length === 0 ||
|
||||
errorMessage.length > 0
|
||||
}
|
||||
onClick={createNewDiagram}
|
||||
onClick={handleImport}
|
||||
>
|
||||
{t('new_diagram_dialog.import')}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
) : (
|
||||
<DialogClose asChild>
|
||||
<Button
|
||||
type="button"
|
||||
variant="default"
|
||||
disabled={
|
||||
scriptResult.trim().length === 0 ||
|
||||
errorMessage.length > 0
|
||||
}
|
||||
onClick={handleImport}
|
||||
>
|
||||
{t('new_diagram_dialog.import')}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
)}
|
||||
</div>
|
||||
</DialogFooter>
|
||||
);
|
||||
}, [createNewDiagram, errorMessage.length, scriptResult, setStep, t]);
|
||||
}, [
|
||||
handleImport,
|
||||
keepDialogAfterImport,
|
||||
onCreateEmptyDiagram,
|
||||
errorMessage.length,
|
||||
scriptResult,
|
||||
goBack,
|
||||
t,
|
||||
]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -8,19 +8,16 @@ import { useNavigate } from 'react-router-dom';
|
||||
import { useConfig } from '@/hooks/use-config';
|
||||
import {
|
||||
DatabaseMetadata,
|
||||
isDatabaseMetadata,
|
||||
loadDatabaseMetadata,
|
||||
} from '@/lib/data/import-metadata/metadata-types/database-metadata';
|
||||
import { generateDiagramId } from '@/lib/utils';
|
||||
import { useChartDB } from '@/hooks/use-chartdb';
|
||||
import { useDialog } from '@/hooks/use-dialog';
|
||||
import { DatabaseEdition } from '@/lib/domain/database-edition';
|
||||
import { SelectDatabaseStep } from './select-database-step/select-database-step';
|
||||
import { SelectDatabase } from './select-database/select-database';
|
||||
import { CreateDiagramDialogStep } from './create-diagram-dialog-step';
|
||||
import { ImportDatabaseStep } from './import-database-step/import-database-step';
|
||||
|
||||
const errorScriptOutputMessage =
|
||||
'Invalid JSON. Please correct it or contact us at chartdb.io@gmail.com for help.';
|
||||
import { ImportDatabase } from '../common/import-database/import-database';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export interface CreateDiagramDialogProps {
|
||||
dialog: DialogProps;
|
||||
@@ -30,13 +27,13 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
|
||||
dialog,
|
||||
}) => {
|
||||
const { diagramId } = useChartDB();
|
||||
const { t } = useTranslation();
|
||||
const [databaseType, setDatabaseType] = useState<DatabaseType>(
|
||||
DatabaseType.GENERIC
|
||||
);
|
||||
const { closeCreateDiagramDialog } = useDialog();
|
||||
const { updateConfig } = useConfig();
|
||||
const [scriptResult, setScriptResult] = useState('');
|
||||
const [errorMessage, setErrorMessage] = useState('');
|
||||
const [databaseEdition, setDatabaseEdition] = useState<
|
||||
DatabaseEdition | undefined
|
||||
>();
|
||||
@@ -60,57 +57,23 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
|
||||
setDatabaseType(DatabaseType.GENERIC);
|
||||
setDatabaseEdition(undefined);
|
||||
setScriptResult('');
|
||||
setErrorMessage('');
|
||||
}, [dialog.open]);
|
||||
|
||||
useEffect(() => {
|
||||
if (scriptResult.trim().length === 0) {
|
||||
setErrorMessage('');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const parsedResult = JSON.parse(scriptResult);
|
||||
|
||||
if (isDatabaseMetadata(parsedResult)) {
|
||||
setErrorMessage('');
|
||||
} else {
|
||||
setErrorMessage(errorScriptOutputMessage);
|
||||
}
|
||||
} catch (error) {
|
||||
setErrorMessage(errorScriptOutputMessage);
|
||||
}
|
||||
}, [scriptResult]);
|
||||
|
||||
const hasExistingDiagram = (diagramId ?? '').trim().length !== 0;
|
||||
|
||||
const createNewDiagram = useCallback(async () => {
|
||||
let diagram: Diagram = {
|
||||
id: generateDiagramId(),
|
||||
name: `Diagram ${diagramNumber}`,
|
||||
databaseType: databaseType ?? DatabaseType.GENERIC,
|
||||
const importNewDiagram = useCallback(async () => {
|
||||
const databaseMetadata: DatabaseMetadata =
|
||||
loadDatabaseMetadata(scriptResult);
|
||||
|
||||
const diagram = loadFromDatabaseMetadata({
|
||||
databaseType,
|
||||
databaseMetadata,
|
||||
diagramNumber,
|
||||
databaseEdition:
|
||||
databaseEdition?.trim().length === 0
|
||||
? undefined
|
||||
: databaseEdition,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
if (errorMessage.length === 0 && scriptResult.trim().length !== 0) {
|
||||
const databaseMetadata: DatabaseMetadata =
|
||||
loadDatabaseMetadata(scriptResult);
|
||||
|
||||
diagram = loadFromDatabaseMetadata({
|
||||
databaseType,
|
||||
databaseMetadata,
|
||||
diagramNumber,
|
||||
databaseEdition:
|
||||
databaseEdition?.trim().length === 0
|
||||
? undefined
|
||||
: databaseEdition,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
await addDiagram({ diagram });
|
||||
await updateConfig({ defaultDiagramId: diagram.id });
|
||||
@@ -125,7 +88,33 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
|
||||
updateConfig,
|
||||
scriptResult,
|
||||
diagramNumber,
|
||||
errorMessage,
|
||||
]);
|
||||
|
||||
const createEmptyDiagram = useCallback(async () => {
|
||||
const diagram: Diagram = {
|
||||
id: generateDiagramId(),
|
||||
name: `Diagram ${diagramNumber}`,
|
||||
databaseType: databaseType ?? DatabaseType.GENERIC,
|
||||
databaseEdition:
|
||||
databaseEdition?.trim().length === 0
|
||||
? undefined
|
||||
: databaseEdition,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
await addDiagram({ diagram });
|
||||
await updateConfig({ defaultDiagramId: diagram.id });
|
||||
closeCreateDiagramDialog();
|
||||
navigate(`/diagrams/${diagram.id}`);
|
||||
}, [
|
||||
databaseType,
|
||||
addDiagram,
|
||||
databaseEdition,
|
||||
closeCreateDiagramDialog,
|
||||
navigate,
|
||||
updateConfig,
|
||||
diagramNumber,
|
||||
]);
|
||||
|
||||
return (
|
||||
@@ -146,23 +135,28 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
|
||||
showClose={hasExistingDiagram}
|
||||
>
|
||||
{step === CreateDiagramDialogStep.SELECT_DATABASE ? (
|
||||
<SelectDatabaseStep
|
||||
createNewDiagram={createNewDiagram}
|
||||
<SelectDatabase
|
||||
createNewDiagram={createEmptyDiagram}
|
||||
databaseType={databaseType}
|
||||
hasExistingDiagram={hasExistingDiagram}
|
||||
setDatabaseType={setDatabaseType}
|
||||
setStep={setStep}
|
||||
onContinue={() =>
|
||||
setStep(CreateDiagramDialogStep.IMPORT_DATABASE)
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<ImportDatabaseStep
|
||||
createNewDiagram={createNewDiagram}
|
||||
<ImportDatabase
|
||||
onImport={importNewDiagram}
|
||||
onCreateEmptyDiagram={createEmptyDiagram}
|
||||
databaseEdition={databaseEdition}
|
||||
databaseType={databaseType}
|
||||
errorMessage={errorMessage}
|
||||
scriptResult={scriptResult}
|
||||
setDatabaseEdition={setDatabaseEdition}
|
||||
setStep={setStep}
|
||||
goBack={() =>
|
||||
setStep(CreateDiagramDialogStep.SELECT_DATABASE)
|
||||
}
|
||||
setScriptResult={setScriptResult}
|
||||
title={t('new_diagram_dialog.import_database.title')}
|
||||
/>
|
||||
)}
|
||||
</DialogContent>
|
||||
|
||||
@@ -13,20 +13,19 @@ import { DatabaseType } from '@/lib/domain/database-type';
|
||||
import { databaseTypeToLabelMap, getDatabaseLogo } from '@/lib/databases';
|
||||
import { Link } from '@/components/link/link';
|
||||
import { LayoutGrid } from 'lucide-react';
|
||||
import { CreateDiagramDialogStep } from '../create-diagram-dialog-step';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useTheme } from '@/hooks/use-theme';
|
||||
|
||||
export interface SelectDatabaseStepProps {
|
||||
setStep: React.Dispatch<React.SetStateAction<CreateDiagramDialogStep>>;
|
||||
export interface SelectDatabaseProps {
|
||||
onContinue: () => void;
|
||||
databaseType: DatabaseType;
|
||||
setDatabaseType: React.Dispatch<React.SetStateAction<DatabaseType>>;
|
||||
hasExistingDiagram: boolean;
|
||||
createNewDiagram: () => void;
|
||||
}
|
||||
|
||||
export const SelectDatabaseStep: React.FC<SelectDatabaseStepProps> = ({
|
||||
setStep,
|
||||
export const SelectDatabase: React.FC<SelectDatabaseProps> = ({
|
||||
onContinue,
|
||||
databaseType,
|
||||
setDatabaseType,
|
||||
hasExistingDiagram,
|
||||
@@ -98,7 +97,7 @@ export const SelectDatabaseStep: React.FC<SelectDatabaseStepProps> = ({
|
||||
setDatabaseType(DatabaseType.GENERIC);
|
||||
} else {
|
||||
setDatabaseType(value);
|
||||
setStep(CreateDiagramDialogStep.IMPORT_DATABASE);
|
||||
onContinue();
|
||||
}
|
||||
}}
|
||||
type="single"
|
||||
@@ -118,7 +117,7 @@ export const SelectDatabaseStep: React.FC<SelectDatabaseStepProps> = ({
|
||||
renderDatabaseOption,
|
||||
renderExamplesOption,
|
||||
setDatabaseType,
|
||||
setStep,
|
||||
onContinue,
|
||||
]);
|
||||
|
||||
const renderFooter = useCallback(() => {
|
||||
@@ -145,16 +144,14 @@ export const SelectDatabaseStep: React.FC<SelectDatabaseStepProps> = ({
|
||||
type="button"
|
||||
variant="default"
|
||||
disabled={databaseType === DatabaseType.GENERIC}
|
||||
onClick={() =>
|
||||
setStep(CreateDiagramDialogStep.IMPORT_DATABASE)
|
||||
}
|
||||
onClick={onContinue}
|
||||
>
|
||||
{t('new_diagram_dialog.continue')}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogFooter>
|
||||
);
|
||||
}, [createNewDiagram, databaseType, hasExistingDiagram, setStep, t]);
|
||||
}, [createNewDiagram, databaseType, hasExistingDiagram, onContinue, t]);
|
||||
|
||||
return (
|
||||
<>
|
||||
346
src/dialogs/import-database-dialog/import-database-dialog.tsx
Normal file
346
src/dialogs/import-database-dialog/import-database-dialog.tsx
Normal file
@@ -0,0 +1,346 @@
|
||||
import { Dialog, DialogContent } from '@/components/dialog/dialog';
|
||||
import { useDialog } from '@/hooks/use-dialog';
|
||||
import { DatabaseType } from '@/lib/domain/database-type';
|
||||
import { DialogProps } from '@radix-ui/react-dialog';
|
||||
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { ImportDatabase } from '../common/import-database/import-database';
|
||||
import { DatabaseEdition } from '@/lib/domain/database-edition';
|
||||
import {
|
||||
DatabaseMetadata,
|
||||
loadDatabaseMetadata,
|
||||
} from '@/lib/data/import-metadata/metadata-types/database-metadata';
|
||||
import { loadFromDatabaseMetadata } from '@/lib/domain/diagram';
|
||||
import { useChartDB } from '@/hooks/use-chartdb';
|
||||
import { useRedoUndoStack } from '@/hooks/use-redo-undo-stack';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { useReactFlow } from '@xyflow/react';
|
||||
|
||||
export interface ImportDatabaseDialogProps {
|
||||
dialog: DialogProps;
|
||||
databaseType: DatabaseType;
|
||||
}
|
||||
|
||||
export const ImportDatabaseDialog: React.FC<ImportDatabaseDialogProps> = ({
|
||||
dialog,
|
||||
databaseType,
|
||||
}) => {
|
||||
const { closeImportDatabaseDialog, showAlert } = useDialog();
|
||||
const {
|
||||
tables,
|
||||
relationships,
|
||||
removeTables,
|
||||
removeRelationships,
|
||||
addTables,
|
||||
addRelationships,
|
||||
diagramName,
|
||||
} = useChartDB();
|
||||
const [scriptResult, setScriptResult] = useState('');
|
||||
const { resetRedoStack, resetUndoStack } = useRedoUndoStack();
|
||||
const { setNodes } = useReactFlow();
|
||||
const { t } = useTranslation();
|
||||
const [databaseEdition, setDatabaseEdition] = useState<
|
||||
DatabaseEdition | undefined
|
||||
>();
|
||||
|
||||
useEffect(() => {
|
||||
if (!dialog.open) return;
|
||||
setDatabaseEdition(undefined);
|
||||
setScriptResult('');
|
||||
}, [dialog.open]);
|
||||
|
||||
const importDatabase = useCallback(async () => {
|
||||
const databaseMetadata: DatabaseMetadata =
|
||||
loadDatabaseMetadata(scriptResult);
|
||||
|
||||
const diagram = loadFromDatabaseMetadata({
|
||||
databaseType,
|
||||
databaseMetadata,
|
||||
databaseEdition:
|
||||
databaseEdition?.trim().length === 0
|
||||
? undefined
|
||||
: databaseEdition,
|
||||
});
|
||||
|
||||
const tableIdsToRemove = tables
|
||||
.filter((table) =>
|
||||
diagram.tables?.some(
|
||||
(t) => t.name === table.name && t.schema === table.schema
|
||||
)
|
||||
)
|
||||
.map((table) => table.id);
|
||||
|
||||
const relationshipIdsToRemove = relationships
|
||||
.filter((relationship) => {
|
||||
const sourceTable = tables.find(
|
||||
(table) => table.id === relationship.sourceTableId
|
||||
);
|
||||
|
||||
const targetTable = tables.find(
|
||||
(table) => table.id === relationship.targetTableId
|
||||
);
|
||||
|
||||
if (!sourceTable || !targetTable) return true; // should not happen
|
||||
|
||||
const sourceField = sourceTable.fields.find(
|
||||
(field) => field.id === relationship.sourceFieldId
|
||||
);
|
||||
|
||||
const targetField = targetTable.fields.find(
|
||||
(field) => field.id === relationship.targetFieldId
|
||||
);
|
||||
|
||||
if (!sourceField || !targetField) return true; // should not happen
|
||||
|
||||
const replacementSourceTable = diagram.tables?.find(
|
||||
(table) =>
|
||||
table.name === sourceTable.name &&
|
||||
table.schema === sourceTable.schema
|
||||
);
|
||||
|
||||
const replacementTargetTable = diagram.tables?.find(
|
||||
(table) =>
|
||||
table.name === targetTable.name &&
|
||||
table.schema === targetTable.schema
|
||||
);
|
||||
|
||||
// if the source or target field of the relationship is not in the new table, remove the relationship
|
||||
if (
|
||||
(replacementSourceTable &&
|
||||
!replacementSourceTable.fields.some(
|
||||
(field) => field.name === sourceField.name
|
||||
)) ||
|
||||
(replacementTargetTable &&
|
||||
!replacementTargetTable.fields.some(
|
||||
(field) => field.name === targetField.name
|
||||
))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return diagram.relationships?.some((r) => {
|
||||
const sourceNewTable = diagram.tables?.find(
|
||||
(table) => table.id === r.sourceTableId
|
||||
);
|
||||
|
||||
const targetNewTable = diagram.tables?.find(
|
||||
(table) => table.id === r.targetTableId
|
||||
);
|
||||
|
||||
const sourceNewField = sourceNewTable?.fields.find(
|
||||
(field) => field.id === r.sourceFieldId
|
||||
);
|
||||
|
||||
const targetNewField = targetNewTable?.fields.find(
|
||||
(field) => field.id === r.targetFieldId
|
||||
);
|
||||
|
||||
return (
|
||||
sourceField.name === sourceNewField?.name &&
|
||||
sourceTable.name === sourceNewTable?.name &&
|
||||
sourceTable.schema === sourceNewTable?.schema &&
|
||||
targetField.name === targetNewField?.name &&
|
||||
targetTable.name === targetNewTable?.name &&
|
||||
targetTable.schema === targetNewTable?.schema
|
||||
);
|
||||
});
|
||||
})
|
||||
.map((relationship) => relationship.id);
|
||||
|
||||
const newRelationshipsNumber = diagram.relationships?.filter(
|
||||
(relationship) => {
|
||||
const newSourceTable = diagram.tables?.find(
|
||||
(table) => table.id === relationship.sourceTableId
|
||||
);
|
||||
const newTargetTable = diagram.tables?.find(
|
||||
(table) => table.id === relationship.targetTableId
|
||||
);
|
||||
const newSourceField = newSourceTable?.fields.find(
|
||||
(field) => field.id === relationship.sourceFieldId
|
||||
);
|
||||
const newTargetField = newTargetTable?.fields.find(
|
||||
(field) => field.id === relationship.targetFieldId
|
||||
);
|
||||
|
||||
return !relationships.some((r) => {
|
||||
const sourceTable = tables.find(
|
||||
(table) => table.id === r.sourceTableId
|
||||
);
|
||||
const targetTable = tables.find(
|
||||
(table) => table.id === r.targetTableId
|
||||
);
|
||||
const sourceField = sourceTable?.fields.find(
|
||||
(field) => field.id === r.sourceFieldId
|
||||
);
|
||||
const targetField = targetTable?.fields.find(
|
||||
(field) => field.id === r.targetFieldId
|
||||
);
|
||||
return (
|
||||
sourceField?.name === newSourceField?.name &&
|
||||
sourceTable?.name === newSourceTable?.name &&
|
||||
sourceTable?.schema === newSourceTable?.schema &&
|
||||
targetField?.name === newTargetField?.name &&
|
||||
targetTable?.name === newTargetTable?.name &&
|
||||
targetTable?.schema === newTargetTable?.schema
|
||||
);
|
||||
});
|
||||
}
|
||||
).length;
|
||||
|
||||
const newTablesNumber = diagram.tables?.filter(
|
||||
(table) =>
|
||||
!tables.some(
|
||||
(t) => t.name === table.name && t.schema === table.schema
|
||||
)
|
||||
).length;
|
||||
|
||||
const shouldRemove = new Promise<boolean>((resolve) => {
|
||||
if (
|
||||
tableIdsToRemove.length === 0 &&
|
||||
relationshipIdsToRemove.length === 0 &&
|
||||
newTablesNumber === 0 &&
|
||||
newRelationshipsNumber === 0
|
||||
) {
|
||||
resolve(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const content = (
|
||||
<>
|
||||
<div className="!mb-2">
|
||||
{t(
|
||||
'import_database_dialog.override_alert.content.alert'
|
||||
)}
|
||||
</div>
|
||||
{(newTablesNumber ?? 0 > 0) ? (
|
||||
<div className="!m-0 text-blue-500">
|
||||
<Trans
|
||||
i18nKey="import_database_dialog.override_alert.content.new_tables"
|
||||
values={{
|
||||
newTablesNumber,
|
||||
}}
|
||||
components={{
|
||||
bold: <span className="font-bold" />,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
{(newRelationshipsNumber ?? 0 > 0) ? (
|
||||
<div className="!m-0 text-blue-500">
|
||||
<Trans
|
||||
i18nKey="import_database_dialog.override_alert.content.new_relationships"
|
||||
values={{
|
||||
newRelationshipsNumber,
|
||||
}}
|
||||
components={{
|
||||
bold: <span className="font-bold" />,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
{tableIdsToRemove.length > 0 && (
|
||||
<div className="!m-0 text-red-500">
|
||||
<Trans
|
||||
i18nKey="import_database_dialog.override_alert.content.tables_override"
|
||||
values={{
|
||||
tablesOverrideNumber:
|
||||
tableIdsToRemove.length,
|
||||
}}
|
||||
components={{
|
||||
bold: <span className="font-bold" />,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="!mt-2">
|
||||
{t(
|
||||
'import_database_dialog.override_alert.content.proceed'
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
showAlert({
|
||||
title: t('import_database_dialog.override_alert.title'),
|
||||
content,
|
||||
actionLabel: t('import_database_dialog.override_alert.import'),
|
||||
closeLabel: t('import_database_dialog.override_alert.cancel'),
|
||||
onAction: () => resolve(true),
|
||||
onClose: () => resolve(false),
|
||||
});
|
||||
});
|
||||
|
||||
if (!(await shouldRemove)) return;
|
||||
|
||||
await Promise.all([
|
||||
removeTables(tableIdsToRemove, { updateHistory: false }),
|
||||
removeRelationships(relationshipIdsToRemove, {
|
||||
updateHistory: false,
|
||||
}),
|
||||
]);
|
||||
|
||||
await Promise.all([
|
||||
addTables(diagram.tables ?? [], { updateHistory: false }),
|
||||
addRelationships(diagram.relationships ?? [], {
|
||||
updateHistory: false,
|
||||
}),
|
||||
]);
|
||||
|
||||
setNodes((nodes) =>
|
||||
nodes.map((node) => ({
|
||||
...node,
|
||||
selected:
|
||||
diagram.tables?.some((table) => table.id === node.id) ??
|
||||
false,
|
||||
}))
|
||||
);
|
||||
|
||||
resetRedoStack();
|
||||
resetUndoStack();
|
||||
|
||||
closeImportDatabaseDialog();
|
||||
}, [
|
||||
databaseEdition,
|
||||
databaseType,
|
||||
scriptResult,
|
||||
tables,
|
||||
addRelationships,
|
||||
addTables,
|
||||
closeImportDatabaseDialog,
|
||||
relationships,
|
||||
removeRelationships,
|
||||
removeTables,
|
||||
resetRedoStack,
|
||||
resetUndoStack,
|
||||
showAlert,
|
||||
setNodes,
|
||||
t,
|
||||
]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
{...dialog}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) {
|
||||
closeImportDatabaseDialog();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogContent
|
||||
className="flex max-h-[90vh] w-[90vw] flex-col overflow-y-auto md:overflow-visible xl:min-w-[45vw]"
|
||||
showClose
|
||||
>
|
||||
<ImportDatabase
|
||||
databaseType={databaseType}
|
||||
databaseEdition={databaseEdition}
|
||||
setDatabaseEdition={setDatabaseEdition}
|
||||
onImport={importDatabase}
|
||||
scriptResult={scriptResult}
|
||||
setScriptResult={setScriptResult}
|
||||
keepDialogAfterImport
|
||||
title={t('import_database_dialog.title', { diagramName })}
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -8,6 +8,7 @@ export const en = {
|
||||
new: 'New',
|
||||
open: 'Open',
|
||||
save: 'Save',
|
||||
import_database: 'Import Database',
|
||||
export_sql: 'Export SQL',
|
||||
export_as: 'Export as',
|
||||
delete_diagram: 'Delete Diagram',
|
||||
@@ -240,6 +241,25 @@ export const en = {
|
||||
cancel: 'Cancel',
|
||||
},
|
||||
|
||||
import_database_dialog: {
|
||||
title: 'Import to Current Diagram',
|
||||
override_alert: {
|
||||
title: 'Import Database',
|
||||
content: {
|
||||
alert: 'Importing this diagram will affect existing tables and relationships.',
|
||||
new_tables:
|
||||
'<bold>{{newTablesNumber}}</bold> new tables will be added.',
|
||||
new_relationships:
|
||||
'<bold>{{newRelationshipsNumber}}</bold> new relationships will be created.',
|
||||
tables_override:
|
||||
'<bold>{{tablesOverrideNumber}}</bold> tables will be overwritten.',
|
||||
proceed: 'Do you want to proceed?',
|
||||
},
|
||||
import: 'Import',
|
||||
cancel: 'Cancel',
|
||||
},
|
||||
},
|
||||
|
||||
relationship_type: {
|
||||
one_to_one: 'One to One',
|
||||
one_to_many: 'One to Many',
|
||||
|
||||
@@ -8,6 +8,7 @@ export const es: LanguageTranslation = {
|
||||
new: 'Nuevo',
|
||||
open: 'Abrir',
|
||||
save: 'Guardar',
|
||||
import_database: 'Importar Base de Datos',
|
||||
export_sql: 'Exportar SQL',
|
||||
export_as: 'Exportar como',
|
||||
delete_diagram: 'Eliminar Diagrama',
|
||||
@@ -241,6 +242,25 @@ export const es: LanguageTranslation = {
|
||||
title: 'Crear Relación',
|
||||
},
|
||||
|
||||
import_database_dialog: {
|
||||
title: 'Importar a Diagrama Actual',
|
||||
override_alert: {
|
||||
title: 'Importar Base de Datos',
|
||||
content: {
|
||||
alert: 'Importar este diagrama afectará las tablas y relaciones existentes.',
|
||||
new_tables:
|
||||
'<bold>{{newTablesNumber}}</bold> nuevas tablas se agregarán.',
|
||||
new_relationships:
|
||||
'<bold>{{newRelationshipsNumber}}</bold> nuevas relaciones se crearán.',
|
||||
tables_override:
|
||||
'<bold>{{tablesOverrideNumber}}</bold> tablas se sobrescribirán.',
|
||||
proceed: '¿Deseas continuar?',
|
||||
},
|
||||
import: 'Importar',
|
||||
cancel: 'Cancelar',
|
||||
},
|
||||
},
|
||||
|
||||
relationship_type: {
|
||||
one_to_one: 'Uno a Uno',
|
||||
one_to_many: 'Uno a Muchos',
|
||||
|
||||
@@ -30,7 +30,7 @@ export const loadFromDatabaseMetadata = ({
|
||||
}: {
|
||||
databaseType: DatabaseType;
|
||||
databaseMetadata: DatabaseMetadata;
|
||||
diagramNumber: number;
|
||||
diagramNumber?: number;
|
||||
databaseEdition?: DatabaseEdition;
|
||||
}): Diagram => {
|
||||
const {
|
||||
@@ -71,9 +71,11 @@ export const loadFromDatabaseMetadata = ({
|
||||
|
||||
return {
|
||||
id: generateDiagramId(),
|
||||
name:
|
||||
`${databaseMetadata.database_name}-db` ||
|
||||
`Diagram ${diagramNumber}`,
|
||||
name: databaseMetadata.database_name
|
||||
? `${databaseMetadata.database_name}-db`
|
||||
: diagramNumber
|
||||
? `Diagram ${diagramNumber}`
|
||||
: 'New Diagram',
|
||||
databaseType: databaseType ?? DatabaseType.GENERIC,
|
||||
databaseEdition,
|
||||
tables: sortedTables,
|
||||
|
||||
@@ -62,6 +62,7 @@ export const TopNavbar: React.FC<TopNavbarProps> = () => {
|
||||
openCreateDiagramDialog,
|
||||
openOpenDiagramDialog,
|
||||
openExportSQLDialog,
|
||||
openImportDatabaseDialog,
|
||||
showAlert,
|
||||
} = useDialog();
|
||||
const { setTheme, theme } = useTheme();
|
||||
@@ -352,6 +353,72 @@ export const TopNavbar: React.FC<TopNavbarProps> = () => {
|
||||
</MenubarShortcut>
|
||||
</MenubarItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarSub>
|
||||
<MenubarSubTrigger>
|
||||
{t('menu.file.import_database')}
|
||||
</MenubarSubTrigger>
|
||||
<MenubarSubContent>
|
||||
<MenubarItem
|
||||
onClick={() =>
|
||||
openImportDatabaseDialog({
|
||||
databaseType:
|
||||
DatabaseType.POSTGRESQL,
|
||||
})
|
||||
}
|
||||
>
|
||||
{
|
||||
databaseTypeToLabelMap[
|
||||
'postgresql'
|
||||
]
|
||||
}
|
||||
</MenubarItem>
|
||||
<MenubarItem
|
||||
onClick={() =>
|
||||
openImportDatabaseDialog({
|
||||
databaseType:
|
||||
DatabaseType.MYSQL,
|
||||
})
|
||||
}
|
||||
>
|
||||
{databaseTypeToLabelMap['mysql']}
|
||||
</MenubarItem>
|
||||
<MenubarItem
|
||||
onClick={() =>
|
||||
openImportDatabaseDialog({
|
||||
databaseType:
|
||||
DatabaseType.SQL_SERVER,
|
||||
})
|
||||
}
|
||||
>
|
||||
{
|
||||
databaseTypeToLabelMap[
|
||||
'sql_server'
|
||||
]
|
||||
}
|
||||
</MenubarItem>
|
||||
<MenubarItem
|
||||
onClick={() =>
|
||||
openImportDatabaseDialog({
|
||||
databaseType:
|
||||
DatabaseType.MARIADB,
|
||||
})
|
||||
}
|
||||
>
|
||||
{databaseTypeToLabelMap['mariadb']}
|
||||
</MenubarItem>
|
||||
<MenubarItem
|
||||
onClick={() =>
|
||||
openImportDatabaseDialog({
|
||||
databaseType:
|
||||
DatabaseType.SQLITE,
|
||||
})
|
||||
}
|
||||
>
|
||||
{databaseTypeToLabelMap['sqlite']}
|
||||
</MenubarItem>
|
||||
</MenubarSubContent>
|
||||
</MenubarSub>
|
||||
<MenubarSeparator />
|
||||
<MenubarSub>
|
||||
<MenubarSubTrigger>
|
||||
{t('menu.file.export_sql')}
|
||||
|
||||
Reference in New Issue
Block a user