fix: add actions menu to diagram list + add duplicate diagram (#876)

This commit is contained in:
Guy Ben-Aharon
2025-08-26 17:10:25 +03:00
committed by GitHub
parent 459c5f1ce3
commit abd2a6ccbe
25 changed files with 310 additions and 64 deletions

View File

@@ -0,0 +1,98 @@
import React, { useCallback } from 'react';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/dropdown-menu/dropdown-menu';
import { Button } from '@/components/button/button';
import { Ellipsis, Layers2, SquareArrowOutUpRight, Trash2 } from 'lucide-react';
import { useChartDB } from '@/hooks/use-chartdb';
import type { Diagram } from '@/lib/domain';
import { useStorage } from '@/hooks/use-storage';
import { cloneDiagram } from '@/lib/clone';
import { useTranslation } from 'react-i18next';
interface DiagramRowActionsMenuProps {
diagram: Diagram;
onOpen: () => void;
refetch: () => void;
numberOfDiagrams: number;
}
export const DiagramRowActionsMenu: React.FC<DiagramRowActionsMenuProps> = ({
diagram,
onOpen,
refetch,
numberOfDiagrams,
}) => {
const { diagramId } = useChartDB();
const { deleteDiagram, addDiagram } = useStorage();
const { t } = useTranslation();
const onDelete = useCallback(async () => {
deleteDiagram(diagram.id);
refetch();
if (diagram.id === diagramId || numberOfDiagrams <= 1) {
window.location.href = '/';
}
}, [deleteDiagram, diagram.id, diagramId, refetch, numberOfDiagrams]);
const onDuplicate = useCallback(async () => {
const duplicatedDiagram = cloneDiagram(diagram);
const diagramToAdd = duplicatedDiagram.diagram;
if (!diagramToAdd) {
return;
}
diagramToAdd.name = `${diagram.name} (Copy)`;
addDiagram({ diagram: diagramToAdd });
refetch();
}, [addDiagram, refetch, diagram]);
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
className="size-8 p-0"
onClick={(e) => e.stopPropagation()}
>
<Ellipsis className="size-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem
onClick={onOpen}
className="flex justify-between gap-4"
>
{t('open_diagram_dialog.diagram_actions.open')}
<SquareArrowOutUpRight className="size-3.5" />
</DropdownMenuItem>
<DropdownMenuItem
onClick={onDuplicate}
className="flex justify-between gap-4"
>
{t('open_diagram_dialog.diagram_actions.duplicate')}
<Layers2 className="size-3.5" />
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
onClick={onDelete}
className="flex justify-between gap-4 text-red-700"
>
{t('open_diagram_dialog.diagram_actions.delete')}
<Trash2 className="size-3.5 text-red-700" />
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
};

View File

@@ -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;
@@ -46,21 +47,22 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
string | undefined
>();
useEffect(() => {
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()
)
);
};
if (!dialog.open) {
return;
}
setSelectedDiagramId(undefined);
fetchDiagrams();
}, [listDiagrams, setDiagrams, dialog.open]);
}, [dialog.open, fetchDiagrams]);
const openDiagram = useCallback(
(diagramId: string) => {
@@ -166,6 +168,7 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
'open_diagram_dialog.table_columns.tables_count'
)}
</TableHead>
<TableHead />
</TableRow>
</TableHeader>
<TableBody>
@@ -221,6 +224,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();
}}
numberOfDiagrams={
diagrams.length
}
refetch={fetchDiagrams}
/>
</TableCell>
</TableRow>
))}
</TableBody>

View File

@@ -12,8 +12,8 @@ export const ar: LanguageTranslation = {
custom_types: 'الأنواع المخصصة',
},
menu: {
databases: {
databases: 'قواعد البيانات',
actions: {
actions: 'الإجراءات',
new: 'مخطط جديد',
browse: 'تصفح...',
save: 'حفظ',
@@ -323,6 +323,12 @@ export const ar: LanguageTranslation = {
},
cancel: 'إلغاء',
open: 'فتح',
diagram_actions: {
open: 'فتح',
duplicate: 'تكرار',
delete: 'حذف الرسم التخطيطي',
},
},
export_sql_dialog: {

View File

@@ -12,8 +12,8 @@ export const bn: LanguageTranslation = {
custom_types: 'কাস্টম টাইপ',
},
menu: {
databases: {
databases: 'ডাটাবেস',
actions: {
actions: 'কার্য',
new: 'নতুন ডায়াগ্রাম',
browse: 'ব্রাউজ করুন...',
save: 'সংরক্ষণ করুন',
@@ -325,6 +325,12 @@ export const bn: LanguageTranslation = {
},
cancel: 'বাতিল করুন',
open: 'খুলুন',
diagram_actions: {
open: 'খুলুন',
duplicate: 'ডুপ্লিকেট',
delete: 'ডায়াগ্রাম মুছুন',
},
},
export_sql_dialog: {

View File

@@ -12,8 +12,8 @@ export const de: LanguageTranslation = {
custom_types: 'Benutzerdefinierte Typen',
},
menu: {
databases: {
databases: 'Datenbanken',
actions: {
actions: 'Aktionen',
new: 'Neues Diagramm',
browse: 'Durchsuchen...',
save: 'Speichern',
@@ -328,6 +328,12 @@ export const de: LanguageTranslation = {
},
cancel: 'Abbrechen',
open: 'Öffnen',
diagram_actions: {
open: 'Öffnen',
duplicate: 'Duplizieren',
delete: 'Diagramm löschen',
},
},
export_sql_dialog: {

View File

@@ -12,8 +12,8 @@ export const en = {
custom_types: 'Custom Types',
},
menu: {
databases: {
databases: 'Databases',
actions: {
actions: 'Actions',
new: 'New Diagram',
browse: 'Browse...',
save: 'Save',
@@ -316,6 +316,12 @@ export const en = {
},
cancel: 'Cancel',
open: 'Open',
diagram_actions: {
open: 'Open',
duplicate: 'Duplicate',
delete: 'Delete Diagram',
},
},
export_sql_dialog: {

View File

@@ -12,8 +12,8 @@ export const es: LanguageTranslation = {
custom_types: 'Tipos Personalizados',
},
menu: {
databases: {
databases: 'Bases de Datos',
actions: {
actions: 'Acciones',
new: 'Nuevo Diagrama',
browse: 'Examinar...',
save: 'Guardar',
@@ -326,6 +326,12 @@ export const es: LanguageTranslation = {
},
cancel: 'Cancelar',
open: 'Abrir',
diagram_actions: {
open: 'Abrir',
duplicate: 'Duplicar',
delete: 'Eliminar Diagrama',
},
},
export_sql_dialog: {

View File

@@ -12,8 +12,8 @@ export const fr: LanguageTranslation = {
custom_types: 'Types Personnalisés',
},
menu: {
databases: {
databases: 'Bases de Données',
actions: {
actions: 'Actions',
new: 'Nouveau Diagramme',
browse: 'Parcourir...',
save: 'Enregistrer',
@@ -323,6 +323,12 @@ export const fr: LanguageTranslation = {
},
cancel: 'Annuler',
open: 'Ouvrir',
diagram_actions: {
open: 'Ouvrir',
duplicate: 'Dupliquer',
delete: 'Supprimer le diagramme',
},
},
export_sql_dialog: {

View File

@@ -12,8 +12,8 @@ export const gu: LanguageTranslation = {
custom_types: 'કસ્ટમ ટાઇપ',
},
menu: {
databases: {
databases: 'ડેટાબેસેસ',
actions: {
actions: 'ક્રિયાઓ',
new: 'નવું ડાયાગ્રામ',
browse: 'બ્રાઉજ કરો...',
save: 'સાચવો',
@@ -325,6 +325,12 @@ export const gu: LanguageTranslation = {
},
cancel: 'રદ કરો',
open: 'ખોલો',
diagram_actions: {
open: 'ખોલો',
duplicate: 'ડુપ્લિકેટ',
delete: 'ડાયાગ્રામ કાઢી નાખો',
},
},
export_sql_dialog: {

View File

@@ -12,8 +12,8 @@ export const hi: LanguageTranslation = {
custom_types: 'कस्टम टाइप',
},
menu: {
databases: {
databases: 'डेटाबेस',
actions: {
actions: 'कार्य',
new: 'नया आरेख',
browse: 'ब्राउज़ करें...',
save: 'सहेजें',
@@ -327,6 +327,12 @@ export const hi: LanguageTranslation = {
},
cancel: 'रद्द करें',
open: 'खोलें',
diagram_actions: {
open: 'खोलें',
duplicate: 'डुप्लिकेट',
delete: 'डायग्राम हटाएं',
},
},
export_sql_dialog: {

View File

@@ -12,8 +12,8 @@ export const hr: LanguageTranslation = {
custom_types: 'Prilagođeni Tipovi',
},
menu: {
databases: {
databases: 'Baze Podataka',
actions: {
actions: 'Akcije',
new: 'Novi Dijagram',
browse: 'Pregledaj...',
save: 'Spremi',
@@ -320,6 +320,12 @@ export const hr: LanguageTranslation = {
},
cancel: 'Odustani',
open: 'Otvori',
diagram_actions: {
open: 'Otvori',
duplicate: 'Dupliciraj',
delete: 'Obriši dijagram',
},
},
export_sql_dialog: {

View File

@@ -12,8 +12,8 @@ export const id_ID: LanguageTranslation = {
custom_types: 'Tipe Kustom',
},
menu: {
databases: {
databases: 'Basis Data',
actions: {
actions: 'Aksi',
new: 'Diagram Baru',
browse: 'Jelajahi...',
save: 'Simpan',
@@ -324,6 +324,12 @@ export const id_ID: LanguageTranslation = {
},
cancel: 'Batal',
open: 'Buka',
diagram_actions: {
open: 'Buka',
duplicate: 'Duplikat',
delete: 'Hapus Diagram',
},
},
export_sql_dialog: {

View File

@@ -12,8 +12,8 @@ export const ja: LanguageTranslation = {
custom_types: 'カスタムタイプ',
},
menu: {
databases: {
databases: 'データベース',
actions: {
actions: 'アクション',
new: '新しいダイアグラム',
browse: '参照...',
save: '保存',
@@ -329,6 +329,12 @@ export const ja: LanguageTranslation = {
},
cancel: 'キャンセル',
open: '開く',
diagram_actions: {
open: '開く',
duplicate: '複製',
delete: 'ダイアグラムを削除',
},
},
export_sql_dialog: {

View File

@@ -12,8 +12,8 @@ export const ko_KR: LanguageTranslation = {
custom_types: '사용자 지정 타입',
},
menu: {
databases: {
databases: '데이터베이스',
actions: {
actions: '작업',
new: '새 다이어그램',
browse: '찾아보기...',
save: '저장',
@@ -324,6 +324,12 @@ export const ko_KR: LanguageTranslation = {
},
cancel: '취소',
open: '열기',
diagram_actions: {
open: '열기',
duplicate: '복제',
delete: '다이어그램 삭제',
},
},
export_sql_dialog: {

View File

@@ -12,8 +12,8 @@ export const mr: LanguageTranslation = {
custom_types: 'कस्टम प्रकार',
},
menu: {
databases: {
databases: 'डेटाबेस',
actions: {
actions: 'क्रिया',
new: 'नवीन आरेख',
browse: 'ब्राउज करा...',
save: 'जतन करा',
@@ -330,6 +330,12 @@ export const mr: LanguageTranslation = {
},
cancel: 'रद्द करा',
open: 'उघडा',
diagram_actions: {
open: 'उघडा',
duplicate: 'डुप्लिकेट',
delete: 'आरेख हटवा',
},
},
export_sql_dialog: {

View File

@@ -12,8 +12,8 @@ export const ne: LanguageTranslation = {
custom_types: 'कस्टम प्रकारहरू',
},
menu: {
databases: {
databases: 'डाटाबेसहरू',
actions: {
actions: 'कार्यहरू',
new: 'नयाँ डायाग्राम',
browse: 'ब्राउज गर्नुहोस्...',
save: 'सुरक्षित गर्नुहोस्',
@@ -327,6 +327,12 @@ export const ne: LanguageTranslation = {
},
cancel: 'रद्द गर्नुहोस्',
open: 'खोल्नुहोस्',
diagram_actions: {
open: 'खोल्नुहोस्',
duplicate: 'डुप्लिकेट',
delete: 'डायग्राम मेटाउनुहोस्',
},
},
export_sql_dialog: {

View File

@@ -12,8 +12,8 @@ export const pt_BR: LanguageTranslation = {
custom_types: 'Tipos Personalizados',
},
menu: {
databases: {
databases: 'Bancos de Dados',
actions: {
actions: 'Ações',
new: 'Novo Diagrama',
browse: 'Navegar...',
save: 'Salvar',
@@ -326,6 +326,12 @@ export const pt_BR: LanguageTranslation = {
},
cancel: 'Cancelar',
open: 'Abrir',
diagram_actions: {
open: 'Abrir',
duplicate: 'Duplicar',
delete: 'Excluir Diagrama',
},
},
export_sql_dialog: {

View File

@@ -12,8 +12,8 @@ export const ru: LanguageTranslation = {
custom_types: 'Пользовательские типы',
},
menu: {
databases: {
databases: 'Базы данных',
actions: {
actions: 'Действия',
new: 'Новая диаграмма',
browse: 'Обзор...',
save: 'Сохранить',
@@ -323,6 +323,12 @@ export const ru: LanguageTranslation = {
},
cancel: 'Отмена',
open: 'Открыть',
diagram_actions: {
open: 'Открыть',
duplicate: 'Дублировать',
delete: 'Удалить диаграмму',
},
},
export_sql_dialog: {

View File

@@ -12,8 +12,8 @@ export const te: LanguageTranslation = {
custom_types: 'కస్టమ్ టైప్స్',
},
menu: {
databases: {
databases: 'డేటాబేస్లు',
actions: {
actions: 'చర్యలు',
new: 'కొత్త డైగ్రాం',
browse: 'బ్రాఉజ్ చేయండి...',
save: 'సేవ్',
@@ -327,6 +327,12 @@ export const te: LanguageTranslation = {
},
cancel: 'రద్దు',
open: 'తెరవు',
diagram_actions: {
open: 'తెరవు',
duplicate: 'నకలు',
delete: 'డైగ్రామ్ తొలగించు',
},
},
export_sql_dialog: {

View File

@@ -12,8 +12,8 @@ export const tr: LanguageTranslation = {
custom_types: 'Özel Tipler',
},
menu: {
databases: {
databases: 'Veritabanları',
actions: {
actions: 'Eylemler',
new: 'Yeni Diyagram',
browse: 'Gözat...',
save: 'Kaydet',
@@ -322,6 +322,12 @@ export const tr: LanguageTranslation = {
},
cancel: 'İptal',
open: 'Aç',
diagram_actions: {
open: 'Aç',
duplicate: 'Kopyala',
delete: 'Diyagramı Sil',
},
},
export_sql_dialog: {

View File

@@ -12,8 +12,8 @@ export const uk: LanguageTranslation = {
custom_types: 'Користувацькі типи',
},
menu: {
databases: {
databases: 'Бази даних',
actions: {
actions: 'Дії',
new: 'Нова діаграма',
browse: 'Огляд...',
save: 'Зберегти',
@@ -324,6 +324,12 @@ export const uk: LanguageTranslation = {
},
cancel: 'Скасувати',
open: 'Відкрити',
diagram_actions: {
open: 'Відкрити',
duplicate: 'Дублювати',
delete: 'Видалити діаграму',
},
},
export_sql_dialog: {

View File

@@ -12,8 +12,8 @@ export const vi: LanguageTranslation = {
custom_types: 'Kiểu tùy chỉnh',
},
menu: {
databases: {
databases: 'Cơ sở dữ liệu',
actions: {
actions: 'Hành động',
new: 'Sơ đồ mới',
browse: 'Duyệt...',
save: 'Lưu',
@@ -324,6 +324,12 @@ export const vi: LanguageTranslation = {
},
cancel: 'Hủy',
open: 'Mở',
diagram_actions: {
open: 'Mở',
duplicate: 'Nhân bản',
delete: 'Xóa sơ đồ',
},
},
export_sql_dialog: {

View File

@@ -12,8 +12,8 @@ export const zh_CN: LanguageTranslation = {
custom_types: '自定义类型',
},
menu: {
databases: {
databases: '数据库',
actions: {
actions: '操作',
new: '新建关系图',
browse: '浏览...',
save: '保存',
@@ -321,6 +321,12 @@ export const zh_CN: LanguageTranslation = {
},
cancel: '取消',
open: '打开',
diagram_actions: {
open: '打开',
duplicate: '复制',
delete: '删除图表',
},
},
export_sql_dialog: {

View File

@@ -12,8 +12,8 @@ export const zh_TW: LanguageTranslation = {
custom_types: '自定義類型',
},
menu: {
databases: {
databases: '資料庫',
actions: {
actions: '操作',
new: '新增圖表',
browse: '瀏覽...',
save: '儲存',
@@ -320,6 +320,12 @@ export const zh_TW: LanguageTranslation = {
},
cancel: '取消',
open: '開啟',
diagram_actions: {
open: '開啟',
duplicate: '複製',
delete: '刪除圖表',
},
},
export_sql_dialog: {

View File

@@ -151,13 +151,13 @@ export const Menu: React.FC<MenuProps> = () => {
return (
<Menubar className="h-8 border-none py-2 shadow-none md:h-10 md:py-0">
<MenubarMenu>
<MenubarTrigger>{t('menu.databases.databases')}</MenubarTrigger>
<MenubarTrigger>{t('menu.actions.actions')}</MenubarTrigger>
<MenubarContent>
<MenubarItem onClick={createNewDiagram}>
{t('menu.databases.new')}
{t('menu.actions.new')}
</MenubarItem>
<MenubarItem onClick={openDiagram}>
{t('menu.databases.browse')}
{t('menu.actions.browse')}
<MenubarShortcut>
{
keyboardShortcutsForOS[
@@ -167,7 +167,7 @@ export const Menu: React.FC<MenuProps> = () => {
</MenubarShortcut>
</MenubarItem>
<MenubarItem onClick={updateDiagramUpdatedAt}>
{t('menu.databases.save')}
{t('menu.actions.save')}
<MenubarShortcut>
{
keyboardShortcutsForOS[
@@ -179,7 +179,7 @@ export const Menu: React.FC<MenuProps> = () => {
<MenubarSeparator />
<MenubarSub>
<MenubarSubTrigger>
{t('menu.databases.import')}
{t('menu.actions.import')}
</MenubarSubTrigger>
<MenubarSubContent>
<MenubarItem onClick={openImportDiagramDialog}>
@@ -248,7 +248,7 @@ export const Menu: React.FC<MenuProps> = () => {
<MenubarSeparator />
<MenubarSub>
<MenubarSubTrigger>
{t('menu.databases.export_sql')}
{t('menu.actions.export_sql')}
</MenubarSubTrigger>
<MenubarSubContent>
{databaseType === DatabaseType.GENERIC ? (
@@ -331,7 +331,7 @@ export const Menu: React.FC<MenuProps> = () => {
</MenubarSub>
<MenubarSub>
<MenubarSubTrigger>
{t('menu.databases.export_as')}
{t('menu.actions.export_as')}
</MenubarSubTrigger>
<MenubarSubContent>
<MenubarItem onClick={exportPNG}>PNG</MenubarItem>
@@ -357,7 +357,7 @@ export const Menu: React.FC<MenuProps> = () => {
})
}
>
{t('menu.databases.delete_diagram')}
{t('menu.actions.delete_diagram')}
</MenubarItem>
</MenubarContent>
</MenubarMenu>