mirror of
https://github.com/chartdb/chartdb.git
synced 2025-10-23 07:11:56 +00:00
Compare commits
1 Commits
release-pl
...
jf/add_dup
Author | SHA1 | Date | |
---|---|---|---|
|
76f9662b80 |
@@ -764,6 +764,7 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
||||
db.db_dependencies.where('diagramId').equals(id).delete(),
|
||||
db.areas.where('diagramId').equals(id).delete(),
|
||||
db.db_custom_types.where('diagramId').equals(id).delete(),
|
||||
db.diagram_filters.where('diagramId').equals(id).delete(),
|
||||
]);
|
||||
},
|
||||
[db]
|
||||
|
@@ -0,0 +1,216 @@
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/dropdown-menu/dropdown-menu';
|
||||
import { Button } from '@/components/button/button';
|
||||
import type { Diagram } from '@/lib/domain/diagram';
|
||||
import {
|
||||
Copy,
|
||||
MoreHorizontal,
|
||||
SquareArrowOutUpRight,
|
||||
Trash2,
|
||||
Loader2,
|
||||
} from 'lucide-react';
|
||||
import { useStorage } from '@/hooks/use-storage';
|
||||
import { useAlert } from '@/context/alert-context/alert-context';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { cloneDiagram } from '@/lib/clone';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { useConfig } from '@/hooks/use-config';
|
||||
|
||||
interface DiagramRowActionsMenuProps {
|
||||
diagram: Diagram;
|
||||
onOpen: () => void;
|
||||
refetch: () => void;
|
||||
onSelectDiagram?: (diagramId: string | undefined) => void;
|
||||
}
|
||||
|
||||
export const DiagramRowActionsMenu: React.FC<DiagramRowActionsMenuProps> = ({
|
||||
diagram,
|
||||
onOpen,
|
||||
refetch,
|
||||
onSelectDiagram,
|
||||
}) => {
|
||||
const { addDiagram, deleteDiagram, listDiagrams, getDiagram } =
|
||||
useStorage();
|
||||
const { showAlert } = useAlert();
|
||||
const { t } = useTranslation();
|
||||
const { diagramId: currentDiagramId } = useParams<{ diagramId: string }>();
|
||||
const navigate = useNavigate();
|
||||
const { updateConfig } = useConfig();
|
||||
const [isDuplicating, setIsDuplicating] = useState(false);
|
||||
|
||||
const handleDuplicateDiagram = useCallback(async () => {
|
||||
setIsDuplicating(true);
|
||||
|
||||
try {
|
||||
// Load the full diagram with all components
|
||||
const fullDiagram = await getDiagram(diagram.id, {
|
||||
includeTables: true,
|
||||
includeRelationships: true,
|
||||
includeAreas: true,
|
||||
includeDependencies: true,
|
||||
includeCustomTypes: true,
|
||||
});
|
||||
|
||||
if (!fullDiagram) {
|
||||
console.error('Failed to load diagram for duplication');
|
||||
setIsDuplicating(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const { diagram: clonedDiagram } = cloneDiagram(fullDiagram);
|
||||
|
||||
// Generate a unique name for the duplicated diagram
|
||||
const diagrams = await listDiagrams();
|
||||
const existingNames = diagrams.map((d) => d.name);
|
||||
let duplicatedName = `${diagram.name} - Copy`;
|
||||
let counter = 1;
|
||||
|
||||
while (existingNames.includes(duplicatedName)) {
|
||||
duplicatedName = `${diagram.name} - Copy ${counter}`;
|
||||
counter++;
|
||||
}
|
||||
|
||||
const diagramToAdd = {
|
||||
...clonedDiagram,
|
||||
name: duplicatedName,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
// Add 2 second delay for better UX
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||
|
||||
await addDiagram({ diagram: diagramToAdd });
|
||||
|
||||
// Clear current selection first, then select the new diagram
|
||||
if (onSelectDiagram) {
|
||||
onSelectDiagram(undefined); // Clear selection
|
||||
await refetch(); // Refresh the list
|
||||
// Use setTimeout to ensure the DOM has updated with the new row
|
||||
setTimeout(() => {
|
||||
onSelectDiagram(diagramToAdd.id);
|
||||
}, 100);
|
||||
} else {
|
||||
await refetch(); // Refresh the list
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error duplicating diagram:', error);
|
||||
} finally {
|
||||
setIsDuplicating(false);
|
||||
}
|
||||
}, [
|
||||
diagram,
|
||||
addDiagram,
|
||||
listDiagrams,
|
||||
getDiagram,
|
||||
refetch,
|
||||
onSelectDiagram,
|
||||
]);
|
||||
|
||||
const handleDeleteDiagram = useCallback(() => {
|
||||
showAlert({
|
||||
title: t('delete_diagram_alert.title'),
|
||||
description: t('delete_diagram_alert.description'),
|
||||
actionLabel: t('delete_diagram_alert.delete'),
|
||||
closeLabel: t('delete_diagram_alert.cancel'),
|
||||
onAction: async () => {
|
||||
await deleteDiagram(diagram.id);
|
||||
|
||||
// If we deleted the currently open diagram, navigate to another one
|
||||
if (currentDiagramId === diagram.id) {
|
||||
// Get updated list of diagrams after deletion
|
||||
const remainingDiagrams = await listDiagrams();
|
||||
|
||||
if (remainingDiagrams.length > 0) {
|
||||
// Sort by last modified date (most recent first)
|
||||
const sortedDiagrams = remainingDiagrams.sort(
|
||||
(a, b) =>
|
||||
b.updatedAt.getTime() - a.updatedAt.getTime()
|
||||
);
|
||||
|
||||
// Navigate to the most recently modified diagram
|
||||
const firstDiagram = sortedDiagrams[0];
|
||||
updateConfig({
|
||||
config: { defaultDiagramId: firstDiagram.id },
|
||||
});
|
||||
navigate(`/diagrams/${firstDiagram.id}`);
|
||||
} else {
|
||||
// No diagrams left, navigate to home
|
||||
navigate('/');
|
||||
}
|
||||
}
|
||||
|
||||
refetch(); // Refresh the list
|
||||
},
|
||||
});
|
||||
}, [
|
||||
diagram.id,
|
||||
currentDiagramId,
|
||||
deleteDiagram,
|
||||
refetch,
|
||||
showAlert,
|
||||
t,
|
||||
listDiagrams,
|
||||
updateConfig,
|
||||
navigate,
|
||||
]);
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="size-8 p-0"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
disabled={isDuplicating}
|
||||
>
|
||||
{isDuplicating ? (
|
||||
<Loader2 className="size-3.5 animate-spin" />
|
||||
) : (
|
||||
<MoreHorizontal className="size-3.5" />
|
||||
)}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onOpen();
|
||||
}}
|
||||
className="flex justify-between gap-4"
|
||||
>
|
||||
Open
|
||||
<SquareArrowOutUpRight className="size-3.5" />
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleDuplicateDiagram();
|
||||
}}
|
||||
className="flex justify-between gap-4"
|
||||
>
|
||||
{t('menu.databases.duplicate')}
|
||||
<Copy className="size-3.5" />
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleDeleteDiagram();
|
||||
}}
|
||||
className="flex items-center justify-between text-red-600 focus:text-red-600"
|
||||
>
|
||||
{t('menu.databases.delete_diagram')}
|
||||
<Trash2 className="size-3.5" />
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
};
|
@@ -27,6 +27,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import type { BaseDialogProps } from '../common/base-dialog-props';
|
||||
import { useDebounce } from '@/hooks/use-debounce';
|
||||
import { DiagramRowActionsMenu } from './diagram-row-actions-menu/diagram-row-actions-menu';
|
||||
|
||||
export interface OpenDiagramDialogProps extends BaseDialogProps {
|
||||
canClose?: boolean;
|
||||
@@ -50,17 +51,18 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
|
||||
setSelectedDiagramId(undefined);
|
||||
}, [dialog.open]);
|
||||
|
||||
const fetchDiagrams = useCallback(async () => {
|
||||
const diagrams = await listDiagrams({ includeTables: true });
|
||||
setDiagrams(
|
||||
diagrams.sort(
|
||||
(a, b) => b.updatedAt.getTime() - a.updatedAt.getTime()
|
||||
)
|
||||
);
|
||||
}, [listDiagrams]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchDiagrams = async () => {
|
||||
const diagrams = await listDiagrams({ includeTables: true });
|
||||
setDiagrams(
|
||||
diagrams.sort(
|
||||
(a, b) => b.updatedAt.getTime() - a.updatedAt.getTime()
|
||||
)
|
||||
);
|
||||
};
|
||||
fetchDiagrams();
|
||||
}, [listDiagrams, setDiagrams, dialog.open]);
|
||||
}, [fetchDiagrams, dialog.open]);
|
||||
|
||||
const openDiagram = useCallback(
|
||||
(diagramId: string) => {
|
||||
@@ -221,6 +223,19 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
|
||||
<TableCell className="text-center">
|
||||
{diagram.tables?.length}
|
||||
</TableCell>
|
||||
<TableCell className="items-center p-0 pr-1 text-right">
|
||||
<DiagramRowActionsMenu
|
||||
diagram={diagram}
|
||||
onOpen={() => {
|
||||
openDiagram(diagram.id);
|
||||
closeOpenDiagramDialog();
|
||||
}}
|
||||
refetch={fetchDiagrams}
|
||||
onSelectDiagram={
|
||||
setSelectedDiagramId
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
|
@@ -17,6 +17,7 @@ export const ar: LanguageTranslation = {
|
||||
new: 'مخطط جديد',
|
||||
browse: 'تصفح...',
|
||||
save: 'حفظ',
|
||||
duplicate: 'تكرار',
|
||||
import: 'استيراد قاعدة بيانات',
|
||||
export_sql: 'SQL تصدير',
|
||||
export_as: 'تصدير كـ',
|
||||
|
@@ -17,6 +17,7 @@ export const bn: LanguageTranslation = {
|
||||
new: 'নতুন ডায়াগ্রাম',
|
||||
browse: 'ব্রাউজ করুন...',
|
||||
save: 'সংরক্ষণ করুন',
|
||||
duplicate: 'ডুপ্লিকেট করুন',
|
||||
import: 'ডাটাবেস আমদানি করুন',
|
||||
export_sql: 'SQL রপ্তানি করুন',
|
||||
export_as: 'রূপে রপ্তানি করুন',
|
||||
|
@@ -17,6 +17,7 @@ export const de: LanguageTranslation = {
|
||||
new: 'Neues Diagramm',
|
||||
browse: 'Durchsuchen...',
|
||||
save: 'Speichern',
|
||||
duplicate: 'Diagramm duplizieren',
|
||||
import: 'Datenbank importieren',
|
||||
export_sql: 'SQL exportieren',
|
||||
export_as: 'Exportieren als',
|
||||
@@ -304,7 +305,7 @@ export const de: LanguageTranslation = {
|
||||
step_1: 'Gehen Sie zu Tools > Optionen > Abfrageergebnisse > SQL Server.',
|
||||
step_2: 'Wenn Sie "Ergebnisse in Raster" verwenden, ändern Sie die maximale Zeichenanzahl für Nicht-XML-Daten (auf 9999999 setzen).',
|
||||
},
|
||||
instructions_link: 'Brauchen Sie Hilfe? So geht’s',
|
||||
instructions_link: "Brauchen Sie Hilfe? So geht's",
|
||||
check_script_result: 'Skriptergebnis überprüfen',
|
||||
},
|
||||
|
||||
|
@@ -17,6 +17,7 @@ export const en = {
|
||||
new: 'New Diagram',
|
||||
browse: 'Browse...',
|
||||
save: 'Save',
|
||||
duplicate: 'Duplicate Diagram',
|
||||
import: 'Import',
|
||||
export_sql: 'Export SQL',
|
||||
export_as: 'Export as',
|
||||
|
@@ -17,6 +17,7 @@ export const es: LanguageTranslation = {
|
||||
new: 'Nuevo Diagrama',
|
||||
browse: 'Examinar...',
|
||||
save: 'Guardar',
|
||||
duplicate: 'Duplicar',
|
||||
import: 'Importar Base de Datos',
|
||||
export_sql: 'Exportar SQL',
|
||||
export_as: 'Exportar como',
|
||||
|
@@ -17,6 +17,7 @@ export const fr: LanguageTranslation = {
|
||||
new: 'Nouveau Diagramme',
|
||||
browse: 'Parcourir...',
|
||||
save: 'Enregistrer',
|
||||
duplicate: 'Dupliquer',
|
||||
import: 'Importer Base de Données',
|
||||
export_sql: 'Exporter SQL',
|
||||
export_as: 'Exporter en tant que',
|
||||
|
@@ -17,6 +17,7 @@ export const gu: LanguageTranslation = {
|
||||
new: 'નવું ડાયાગ્રામ',
|
||||
browse: 'બ્રાઉજ કરો...',
|
||||
save: 'સાચવો',
|
||||
duplicate: 'ડુપ્લિકેટ',
|
||||
import: 'ડેટાબેસ આયાત કરો',
|
||||
export_sql: 'SQL નિકાસ કરો',
|
||||
export_as: 'રૂપે નિકાસ કરો',
|
||||
|
@@ -17,6 +17,7 @@ export const hi: LanguageTranslation = {
|
||||
new: 'नया आरेख',
|
||||
browse: 'ब्राउज़ करें...',
|
||||
save: 'सहेजें',
|
||||
duplicate: 'डुप्लिकेट',
|
||||
import: 'डेटाबेस आयात करें',
|
||||
export_sql: 'SQL निर्यात करें',
|
||||
export_as: 'के रूप में निर्यात करें',
|
||||
|
@@ -17,6 +17,7 @@ export const hr: LanguageTranslation = {
|
||||
new: 'Novi Dijagram',
|
||||
browse: 'Pregledaj...',
|
||||
save: 'Spremi',
|
||||
duplicate: 'Dupliciraj dijagram',
|
||||
import: 'Uvezi',
|
||||
export_sql: 'Izvezi SQL',
|
||||
export_as: 'Izvezi kao',
|
||||
|
@@ -17,6 +17,7 @@ export const id_ID: LanguageTranslation = {
|
||||
new: 'Diagram Baru',
|
||||
browse: 'Jelajahi...',
|
||||
save: 'Simpan',
|
||||
duplicate: 'Duplikat',
|
||||
import: 'Impor Database',
|
||||
export_sql: 'Ekspor SQL',
|
||||
export_as: 'Ekspor Sebagai',
|
||||
|
@@ -17,6 +17,7 @@ export const ja: LanguageTranslation = {
|
||||
new: '新しいダイアグラム',
|
||||
browse: '参照...',
|
||||
save: '保存',
|
||||
duplicate: '複製',
|
||||
import: 'データベースをインポート',
|
||||
export_sql: 'SQLをエクスポート',
|
||||
export_as: '形式を指定してエクスポート',
|
||||
|
@@ -17,6 +17,7 @@ export const ko_KR: LanguageTranslation = {
|
||||
new: '새 다이어그램',
|
||||
browse: '찾아보기...',
|
||||
save: '저장',
|
||||
duplicate: '복사',
|
||||
import: '데이터베이스 가져오기',
|
||||
export_sql: 'SQL로 저장',
|
||||
export_as: '다른 형식으로 저장',
|
||||
|
@@ -17,6 +17,7 @@ export const mr: LanguageTranslation = {
|
||||
new: 'नवीन आरेख',
|
||||
browse: 'ब्राउज करा...',
|
||||
save: 'जतन करा',
|
||||
duplicate: 'डुप्लिकेट',
|
||||
import: 'डेटाबेस इम्पोर्ट करा',
|
||||
export_sql: 'SQL एक्स्पोर्ट करा',
|
||||
export_as: 'म्हणून एक्स्पोर्ट करा',
|
||||
|
@@ -17,6 +17,7 @@ export const ne: LanguageTranslation = {
|
||||
new: 'नयाँ डायाग्राम',
|
||||
browse: 'ब्राउज गर्नुहोस्...',
|
||||
save: 'सुरक्षित गर्नुहोस्',
|
||||
duplicate: 'डुप्लिकेट',
|
||||
import: 'डाटाबेस आयात गर्नुहोस्',
|
||||
export_sql: 'SQL निर्यात गर्नुहोस्',
|
||||
export_as: 'निर्यात गर्नुहोस्',
|
||||
|
@@ -17,6 +17,7 @@ export const pt_BR: LanguageTranslation = {
|
||||
new: 'Novo Diagrama',
|
||||
browse: 'Navegar...',
|
||||
save: 'Salvar',
|
||||
duplicate: 'Duplicar',
|
||||
import: 'Importar Banco de Dados',
|
||||
export_sql: 'Exportar SQL',
|
||||
export_as: 'Exportar como',
|
||||
|
@@ -17,6 +17,7 @@ export const ru: LanguageTranslation = {
|
||||
new: 'Новая диаграмма',
|
||||
browse: 'Обзор...',
|
||||
save: 'Сохранить',
|
||||
duplicate: 'Дублировать',
|
||||
import: 'Импортировать базу данных',
|
||||
export_sql: 'Экспорт SQL',
|
||||
export_as: 'Экспортировать как',
|
||||
|
@@ -17,6 +17,7 @@ export const te: LanguageTranslation = {
|
||||
new: 'కొత్త డైగ్రాం',
|
||||
browse: 'బ్రాఉజ్ చేయండి...',
|
||||
save: 'సేవ్',
|
||||
duplicate: 'డుప్లికేట్',
|
||||
import: 'డేటాబేస్ను దిగుమతి చేసుకోండి',
|
||||
export_sql: 'SQL ఎగుమతి',
|
||||
export_as: 'వగా ఎగుమతి చేయండి',
|
||||
|
@@ -17,6 +17,7 @@ export const tr: LanguageTranslation = {
|
||||
new: 'Yeni Diyagram',
|
||||
browse: 'Gözat...',
|
||||
save: 'Kaydet',
|
||||
duplicate: 'Kopyala',
|
||||
import: 'Veritabanı İçe Aktar',
|
||||
export_sql: 'SQL Olarak Dışa Aktar',
|
||||
export_as: 'Olarak Dışa Aktar',
|
||||
|
@@ -17,6 +17,7 @@ export const uk: LanguageTranslation = {
|
||||
new: 'Нова діаграма',
|
||||
browse: 'Огляд...',
|
||||
save: 'Зберегти',
|
||||
duplicate: 'Дублювати',
|
||||
import: 'Імпорт бази даних',
|
||||
export_sql: 'Експорт SQL',
|
||||
export_as: 'Експортувати як',
|
||||
|
@@ -17,6 +17,7 @@ export const vi: LanguageTranslation = {
|
||||
new: 'Sơ đồ mới',
|
||||
browse: 'Duyệt...',
|
||||
save: 'Lưu',
|
||||
duplicate: 'Nhân đôi',
|
||||
import: 'Nhập cơ sở dữ liệu',
|
||||
export_sql: 'Xuất SQL',
|
||||
export_as: 'Xuất thành',
|
||||
|
@@ -17,6 +17,7 @@ export const zh_CN: LanguageTranslation = {
|
||||
new: '新建关系图',
|
||||
browse: '浏览...',
|
||||
save: '保存',
|
||||
duplicate: '复制',
|
||||
import: '导入数据库',
|
||||
export_sql: '导出 SQL 语句',
|
||||
export_as: '导出为',
|
||||
|
@@ -17,6 +17,7 @@ export const zh_TW: LanguageTranslation = {
|
||||
new: '新增圖表',
|
||||
browse: '瀏覽...',
|
||||
save: '儲存',
|
||||
duplicate: '複製',
|
||||
import: '匯入資料庫',
|
||||
export_sql: '匯出 SQL',
|
||||
export_as: '匯出為特定格式',
|
||||
|
Reference in New Issue
Block a user