diff --git a/src/context/storage-context/storage-provider.tsx b/src/context/storage-context/storage-provider.tsx index b5ff2622..954ce20d 100644 --- a/src/context/storage-context/storage-provider.tsx +++ b/src/context/storage-context/storage-provider.tsx @@ -764,6 +764,7 @@ export const StorageProvider: React.FC = ({ 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] diff --git a/src/dialogs/open-diagram-dialog/diagram-row-actions-menu/diagram-row-actions-menu.tsx b/src/dialogs/open-diagram-dialog/diagram-row-actions-menu/diagram-row-actions-menu.tsx new file mode 100644 index 00000000..d4d85427 --- /dev/null +++ b/src/dialogs/open-diagram-dialog/diagram-row-actions-menu/diagram-row-actions-menu.tsx @@ -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 = ({ + 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 ( + + + + + + { + e.stopPropagation(); + onOpen(); + }} + className="flex justify-between gap-4" + > + Open + + + { + e.stopPropagation(); + handleDuplicateDiagram(); + }} + className="flex justify-between gap-4" + > + {t('menu.databases.duplicate')} + + + + { + e.stopPropagation(); + handleDeleteDiagram(); + }} + className="flex items-center justify-between text-red-600 focus:text-red-600" + > + {t('menu.databases.delete_diagram')} + + + + + ); +}; diff --git a/src/dialogs/open-diagram-dialog/open-diagram-dialog.tsx b/src/dialogs/open-diagram-dialog/open-diagram-dialog.tsx index 553fc5cc..afdfb22d 100644 --- a/src/dialogs/open-diagram-dialog/open-diagram-dialog.tsx +++ b/src/dialogs/open-diagram-dialog/open-diagram-dialog.tsx @@ -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 = ({ 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 = ({ {diagram.tables?.length} + + { + openDiagram(diagram.id); + closeOpenDiagramDialog(); + }} + refetch={fetchDiagrams} + onSelectDiagram={ + setSelectedDiagramId + } + /> + ))} diff --git a/src/i18n/locales/ar.ts b/src/i18n/locales/ar.ts index 28a64b00..9b0160d8 100644 --- a/src/i18n/locales/ar.ts +++ b/src/i18n/locales/ar.ts @@ -17,6 +17,7 @@ export const ar: LanguageTranslation = { new: 'مخطط جديد', browse: 'تصفح...', save: 'حفظ', + duplicate: 'تكرار', import: 'استيراد قاعدة بيانات', export_sql: 'SQL تصدير', export_as: 'تصدير كـ', diff --git a/src/i18n/locales/bn.ts b/src/i18n/locales/bn.ts index fee723ea..8d68a620 100644 --- a/src/i18n/locales/bn.ts +++ b/src/i18n/locales/bn.ts @@ -17,6 +17,7 @@ export const bn: LanguageTranslation = { new: 'নতুন ডায়াগ্রাম', browse: 'ব্রাউজ করুন...', save: 'সংরক্ষণ করুন', + duplicate: 'ডুপ্লিকেট করুন', import: 'ডাটাবেস আমদানি করুন', export_sql: 'SQL রপ্তানি করুন', export_as: 'রূপে রপ্তানি করুন', diff --git a/src/i18n/locales/de.ts b/src/i18n/locales/de.ts index aa854bda..94273773 100644 --- a/src/i18n/locales/de.ts +++ b/src/i18n/locales/de.ts @@ -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', }, diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts index 709ea5dc..fd9f50b0 100644 --- a/src/i18n/locales/en.ts +++ b/src/i18n/locales/en.ts @@ -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', diff --git a/src/i18n/locales/es.ts b/src/i18n/locales/es.ts index e08cd3b5..59c0246d 100644 --- a/src/i18n/locales/es.ts +++ b/src/i18n/locales/es.ts @@ -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', diff --git a/src/i18n/locales/fr.ts b/src/i18n/locales/fr.ts index 0c4cf939..99c5eaf5 100644 --- a/src/i18n/locales/fr.ts +++ b/src/i18n/locales/fr.ts @@ -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', diff --git a/src/i18n/locales/gu.ts b/src/i18n/locales/gu.ts index 00075dad..943dcb1f 100644 --- a/src/i18n/locales/gu.ts +++ b/src/i18n/locales/gu.ts @@ -17,6 +17,7 @@ export const gu: LanguageTranslation = { new: 'નવું ડાયાગ્રામ', browse: 'બ્રાઉજ કરો...', save: 'સાચવો', + duplicate: 'ડુપ્લિકેટ', import: 'ડેટાબેસ આયાત કરો', export_sql: 'SQL નિકાસ કરો', export_as: 'રૂપે નિકાસ કરો', diff --git a/src/i18n/locales/hi.ts b/src/i18n/locales/hi.ts index 459e8c86..ca7383a4 100644 --- a/src/i18n/locales/hi.ts +++ b/src/i18n/locales/hi.ts @@ -17,6 +17,7 @@ export const hi: LanguageTranslation = { new: 'नया आरेख', browse: 'ब्राउज़ करें...', save: 'सहेजें', + duplicate: 'डुप्लिकेट', import: 'डेटाबेस आयात करें', export_sql: 'SQL निर्यात करें', export_as: 'के रूप में निर्यात करें', diff --git a/src/i18n/locales/hr.ts b/src/i18n/locales/hr.ts index 499acd3a..3f88e6b2 100644 --- a/src/i18n/locales/hr.ts +++ b/src/i18n/locales/hr.ts @@ -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', diff --git a/src/i18n/locales/id_ID.ts b/src/i18n/locales/id_ID.ts index 3179be01..80a84c19 100644 --- a/src/i18n/locales/id_ID.ts +++ b/src/i18n/locales/id_ID.ts @@ -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', diff --git a/src/i18n/locales/ja.ts b/src/i18n/locales/ja.ts index 8f9fa3f0..aef92093 100644 --- a/src/i18n/locales/ja.ts +++ b/src/i18n/locales/ja.ts @@ -17,6 +17,7 @@ export const ja: LanguageTranslation = { new: '新しいダイアグラム', browse: '参照...', save: '保存', + duplicate: '複製', import: 'データベースをインポート', export_sql: 'SQLをエクスポート', export_as: '形式を指定してエクスポート', diff --git a/src/i18n/locales/ko_KR.ts b/src/i18n/locales/ko_KR.ts index aa3865c2..fc258b20 100644 --- a/src/i18n/locales/ko_KR.ts +++ b/src/i18n/locales/ko_KR.ts @@ -17,6 +17,7 @@ export const ko_KR: LanguageTranslation = { new: '새 다이어그램', browse: '찾아보기...', save: '저장', + duplicate: '복사', import: '데이터베이스 가져오기', export_sql: 'SQL로 저장', export_as: '다른 형식으로 저장', diff --git a/src/i18n/locales/mr.ts b/src/i18n/locales/mr.ts index d7fe7875..febbca7b 100644 --- a/src/i18n/locales/mr.ts +++ b/src/i18n/locales/mr.ts @@ -17,6 +17,7 @@ export const mr: LanguageTranslation = { new: 'नवीन आरेख', browse: 'ब्राउज करा...', save: 'जतन करा', + duplicate: 'डुप्लिकेट', import: 'डेटाबेस इम्पोर्ट करा', export_sql: 'SQL एक्स्पोर्ट करा', export_as: 'म्हणून एक्स्पोर्ट करा', diff --git a/src/i18n/locales/ne.ts b/src/i18n/locales/ne.ts index 7d2ecd50..290e8b77 100644 --- a/src/i18n/locales/ne.ts +++ b/src/i18n/locales/ne.ts @@ -17,6 +17,7 @@ export const ne: LanguageTranslation = { new: 'नयाँ डायाग्राम', browse: 'ब्राउज गर्नुहोस्...', save: 'सुरक्षित गर्नुहोस्', + duplicate: 'डुप्लिकेट', import: 'डाटाबेस आयात गर्नुहोस्', export_sql: 'SQL निर्यात गर्नुहोस्', export_as: 'निर्यात गर्नुहोस्', diff --git a/src/i18n/locales/pt_BR.ts b/src/i18n/locales/pt_BR.ts index 011fdf20..28adbeb1 100644 --- a/src/i18n/locales/pt_BR.ts +++ b/src/i18n/locales/pt_BR.ts @@ -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', diff --git a/src/i18n/locales/ru.ts b/src/i18n/locales/ru.ts index 6aea01ec..4ea1e137 100644 --- a/src/i18n/locales/ru.ts +++ b/src/i18n/locales/ru.ts @@ -17,6 +17,7 @@ export const ru: LanguageTranslation = { new: 'Новая диаграмма', browse: 'Обзор...', save: 'Сохранить', + duplicate: 'Дублировать', import: 'Импортировать базу данных', export_sql: 'Экспорт SQL', export_as: 'Экспортировать как', diff --git a/src/i18n/locales/te.ts b/src/i18n/locales/te.ts index ac8c8ccc..9364e640 100644 --- a/src/i18n/locales/te.ts +++ b/src/i18n/locales/te.ts @@ -17,6 +17,7 @@ export const te: LanguageTranslation = { new: 'కొత్త డైగ్రాం', browse: 'బ్రాఉజ్ చేయండి...', save: 'సేవ్', + duplicate: 'డుప్లికేట్', import: 'డేటాబేస్‌ను దిగుమతి చేసుకోండి', export_sql: 'SQL ఎగుమతి', export_as: 'వగా ఎగుమతి చేయండి', diff --git a/src/i18n/locales/tr.ts b/src/i18n/locales/tr.ts index 177287da..d5d04201 100644 --- a/src/i18n/locales/tr.ts +++ b/src/i18n/locales/tr.ts @@ -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', diff --git a/src/i18n/locales/uk.ts b/src/i18n/locales/uk.ts index c8b42b91..88dc492c 100644 --- a/src/i18n/locales/uk.ts +++ b/src/i18n/locales/uk.ts @@ -17,6 +17,7 @@ export const uk: LanguageTranslation = { new: 'Нова діаграма', browse: 'Огляд...', save: 'Зберегти', + duplicate: 'Дублювати', import: 'Імпорт бази даних', export_sql: 'Експорт SQL', export_as: 'Експортувати як', diff --git a/src/i18n/locales/vi.ts b/src/i18n/locales/vi.ts index c0980b57..4bb5da7d 100644 --- a/src/i18n/locales/vi.ts +++ b/src/i18n/locales/vi.ts @@ -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', diff --git a/src/i18n/locales/zh_CN.ts b/src/i18n/locales/zh_CN.ts index 20acefd5..cefd22a0 100644 --- a/src/i18n/locales/zh_CN.ts +++ b/src/i18n/locales/zh_CN.ts @@ -17,6 +17,7 @@ export const zh_CN: LanguageTranslation = { new: '新建关系图', browse: '浏览...', save: '保存', + duplicate: '复制', import: '导入数据库', export_sql: '导出 SQL 语句', export_as: '导出为', diff --git a/src/i18n/locales/zh_TW.ts b/src/i18n/locales/zh_TW.ts index 055694c9..bf31a4e1 100644 --- a/src/i18n/locales/zh_TW.ts +++ b/src/i18n/locales/zh_TW.ts @@ -17,6 +17,7 @@ export const zh_TW: LanguageTranslation = { new: '新增圖表', browse: '瀏覽...', save: '儲存', + duplicate: '複製', import: '匯入資料庫', export_sql: '匯出 SQL', export_as: '匯出為特定格式',