fix(canvas): add right-click option to create relationships (#568)

* feat(create-relationship): add right-click option to easy create relationships

* add missing translations

* fix

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
This commit is contained in:
Jonathan Fishner
2025-02-10 10:06:31 +02:00
committed by GitHub
parent 0db67ea42a
commit e993f1549c
25 changed files with 96 additions and 15 deletions
@@ -6,6 +6,7 @@ import type { ExportSQLDialogProps } from '@/dialogs/export-sql-dialog/export-sq
import type { ExportImageDialogProps } from '@/dialogs/export-image-dialog/export-image-dialog';
import type { ExportDiagramDialogProps } from '@/dialogs/export-diagram-dialog/export-diagram-dialog';
import type { ImportDiagramDialogProps } from '@/dialogs/import-diagram-dialog/import-diagram-dialog';
import type { CreateRelationshipDialogProps } from '@/dialogs/create-relationship-dialog/create-relationship-dialog';
export interface DialogContext {
// Create diagram dialog
@@ -21,7 +22,9 @@ export interface DialogContext {
closeExportSQLDialog: () => void;
// Create relationship dialog
openCreateRelationshipDialog: () => void;
openCreateRelationshipDialog: (
params?: Omit<CreateRelationshipDialogProps, 'dialog'>
) => void;
closeCreateRelationshipDialog: () => void;
// Import database dialog
+15 -2
View File
@@ -6,6 +6,7 @@ import { OpenDiagramDialog } from '@/dialogs/open-diagram-dialog/open-diagram-di
import type { ExportSQLDialogProps } from '@/dialogs/export-sql-dialog/export-sql-dialog';
import { ExportSQLDialog } from '@/dialogs/export-sql-dialog/export-sql-dialog';
import { DatabaseType } from '@/lib/domain/database-type';
import type { CreateRelationshipDialogProps } from '@/dialogs/create-relationship-dialog/create-relationship-dialog';
import { CreateRelationshipDialog } from '@/dialogs/create-relationship-dialog/create-relationship-dialog';
import type { ImportDatabaseDialogProps } from '@/dialogs/import-database-dialog/import-database-dialog';
import { ImportDatabaseDialog } from '@/dialogs/import-database-dialog/import-database-dialog';
@@ -28,6 +29,17 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
const [openCreateRelationshipDialog, setOpenCreateRelationshipDialog] =
useState(false);
const [createRelationshipDialogParams, setCreateRelationshipDialogParams] =
useState<Omit<CreateRelationshipDialogProps, 'dialog'>>();
const openCreateRelationshipDialogHandler: DialogContext['openCreateRelationshipDialog'] =
useCallback(
(params) => {
setCreateRelationshipDialogParams(params);
setOpenCreateRelationshipDialog(true);
},
[setOpenCreateRelationshipDialog]
);
const [openStarUsDialog, setOpenStarUsDialog] = useState(false);
const [openBuckleDialog, setOpenBuckleDialog] = useState(false);
@@ -109,8 +121,8 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
closeOpenDiagramDialog: () => setOpenOpenDiagramDialog(false),
openExportSQLDialog: openExportSQLDialogHandler,
closeExportSQLDialog: () => setOpenExportSQLDialog(false),
openCreateRelationshipDialog: () =>
setOpenCreateRelationshipDialog(true),
openCreateRelationshipDialog:
openCreateRelationshipDialogHandler,
closeCreateRelationshipDialog: () =>
setOpenCreateRelationshipDialog(false),
openImportDatabaseDialog: openImportDatabaseDialogHandler,
@@ -143,6 +155,7 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
/>
<CreateRelationshipDialog
dialog={{ open: openCreateRelationshipDialog }}
{...createRelationshipDialogParams}
/>
<ImportDatabaseDialog
dialog={{ open: openImportDatabaseDialog }}
@@ -22,13 +22,17 @@ import { areFieldTypesCompatible } from '@/lib/data/data-types/data-types';
const ErrorMessageRelationshipFieldsNotSameType =
'Relationships can only be created between fields of the same type';
export interface CreateRelationshipDialogProps extends BaseDialogProps {}
export interface CreateRelationshipDialogProps extends BaseDialogProps {
sourceTableId?: string;
}
export const CreateRelationshipDialog: React.FC<
CreateRelationshipDialogProps
> = ({ dialog }) => {
> = ({ dialog, sourceTableId: preSelectedSourceTableId }) => {
const { closeCreateRelationshipDialog } = useDialog();
const [primaryTableId, setPrimaryTableId] = useState<string | undefined>();
const [primaryTableId, setPrimaryTableId] = useState<string | undefined>(
preSelectedSourceTableId
);
const [primaryFieldId, setPrimaryFieldId] = useState<string | undefined>();
const [referencedTableId, setReferencedTableId] = useState<
string | undefined
@@ -43,6 +47,9 @@ export const CreateRelationshipDialog: React.FC<
const [canCreateRelationship, setCanCreateRelationship] = useState(false);
const { fitView, setEdges } = useReactFlow();
const { databaseType } = useChartDB();
const [primaryFieldSelectOpen, setPrimaryFieldSelectOpen] = useState(false);
const [referencedTableSelectOpen, setReferencedTableSelectOpen] =
useState(false);
const tableOptions = useMemo(() => {
return tables.map(
@@ -89,8 +96,23 @@ export const CreateRelationshipDialog: React.FC<
setReferencedTableId(undefined);
setReferencedFieldId(undefined);
setErrorMessage('');
setPrimaryFieldSelectOpen(false);
setReferencedTableSelectOpen(false);
}, [dialog.open]);
useEffect(() => {
if (preSelectedSourceTableId) {
const table = getTable(preSelectedSourceTableId);
if (table) {
setPrimaryTableId(preSelectedSourceTableId);
}
setTimeout(() => {
setPrimaryFieldSelectOpen(true);
}, 100);
}
}, [preSelectedSourceTableId, getTable]);
useEffect(() => {
setCanCreateRelationship(false);
setErrorMessage('');
@@ -223,8 +245,14 @@ export const CreateRelationshipDialog: React.FC<
)}
value={primaryTableId}
onChange={(value) => {
setPrimaryTableId(value as string);
setPrimaryFieldId(undefined);
const newTableId = value as string;
setPrimaryTableId(newTableId);
if (
newTableId !==
preSelectedSourceTableId
) {
setPrimaryFieldId(undefined);
}
}}
emptyPlaceholder={t(
'create_relationship_dialog.no_tables_found'
@@ -253,6 +281,8 @@ export const CreateRelationshipDialog: React.FC<
'create_relationship_dialog.primary_field_placeholder'
)}
value={primaryFieldId}
open={primaryFieldSelectOpen}
onOpenChange={setPrimaryFieldSelectOpen}
onChange={(value) =>
setPrimaryFieldId(value as string)
}
@@ -283,6 +313,8 @@ export const CreateRelationshipDialog: React.FC<
'create_relationship_dialog.referenced_table_placeholder'
)}
value={referencedTableId}
open={referencedTableSelectOpen}
onOpenChange={setReferencedTableSelectOpen}
onChange={(value) => {
setReferencedTableId(value as string);
setReferencedFieldId(undefined);
+1
View File
@@ -402,6 +402,7 @@ export const ar: LanguageTranslation = {
edit_table: 'تعديل الجدول',
duplicate_table: 'نسخ الجدول',
delete_table: 'حذف الجدول',
add_relationship: 'Add Relationship', // TODO: Translate
},
snap_to_grid_tooltip: '({{key}} مغنظة الشبكة (اضغط مع الاستمرار على',
+1
View File
@@ -406,6 +406,7 @@ export const bn: LanguageTranslation = {
edit_table: 'টেবিল সম্পাদনা করুন',
duplicate_table: 'টেবিল নকল করুন',
delete_table: 'টেবিল মুছে ফেলুন',
add_relationship: 'Add Relationship', // TODO: Translate
},
snap_to_grid_tooltip: 'গ্রিডে স্ন্যাপ করুন (অবস্থান {{key}})',
+1
View File
@@ -409,6 +409,7 @@ export const de: LanguageTranslation = {
edit_table: 'Tabelle bearbeiten',
duplicate_table: 'Duplicate Table', // TODO: Translate
delete_table: 'Tabelle löschen',
add_relationship: 'Add Relationship', // TODO: Translate
},
// TODO: Add translations
+1
View File
@@ -401,6 +401,7 @@ export const en = {
edit_table: 'Edit Table',
duplicate_table: 'Duplicate Table',
delete_table: 'Delete Table',
add_relationship: 'Add Relationship',
},
snap_to_grid_tooltip: 'Snap to Grid (Hold {{key}})',
+1
View File
@@ -408,6 +408,7 @@ export const es: LanguageTranslation = {
edit_table: 'Editar Tabla',
duplicate_table: 'Duplicate Table', // TODO: Translate
delete_table: 'Eliminar Tabla',
add_relationship: 'Add Relationship', // TODO: Translate
},
// TODO: Add translations
+1
View File
@@ -410,6 +410,7 @@ export const fr: LanguageTranslation = {
edit_table: 'Éditer la Table',
duplicate_table: 'Duplicate Table', // TODO: Translate
delete_table: 'Supprimer la Table',
add_relationship: 'Add Relationship', // TODO: Translate
},
// TODO: Add translations
+1
View File
@@ -406,6 +406,7 @@ export const gu: LanguageTranslation = {
edit_table: 'ટેબલ સંપાદિત કરો',
duplicate_table: 'ટેબલ નકલ કરો',
delete_table: 'ટેબલ કાઢી નાખો',
add_relationship: 'Add Relationship', // TODO: Translate
},
snap_to_grid_tooltip: 'ગ્રિડ પર સ્નેપ કરો (જમાવટ {{key}})',
+1
View File
@@ -410,6 +410,7 @@ export const hi: LanguageTranslation = {
edit_table: 'तालिका संपादित करें',
duplicate_table: 'Duplicate Table', // TODO: Translate
delete_table: 'तालिका हटाएँ',
add_relationship: 'Add Relationship', // TODO: Translate
},
// TODO: Add translations
+1
View File
@@ -405,6 +405,7 @@ export const id_ID: LanguageTranslation = {
edit_table: 'Ubah Tabel',
delete_table: 'Hapus Tabel',
duplicate_table: 'Duplikat Tabel',
add_relationship: 'Add Relationship', // TODO: Translate
},
snap_to_grid_tooltip: 'Snap ke Kisi (Tahan {{key}})',
+1
View File
@@ -413,6 +413,7 @@ export const ja: LanguageTranslation = {
edit_table: 'テーブルを編集',
duplicate_table: 'Duplicate Table', // TODO: Translate
delete_table: 'テーブルを削除',
add_relationship: 'Add Relationship', // TODO: Translate
},
// TODO: Add translations
+1
View File
@@ -402,6 +402,7 @@ export const ko_KR: LanguageTranslation = {
edit_table: '테이블 수정',
duplicate_table: '테이블 복제',
delete_table: '테이블 삭제',
add_relationship: 'Add Relationship', // TODO: Translate
},
snap_to_grid_tooltip: '그리드에 맞추기 ({{key}}를 누른채 유지)',
+2 -2
View File
@@ -414,8 +414,8 @@ export const mr: LanguageTranslation = {
table_node_context_menu: {
edit_table: 'टेबल संपादित करा',
delete_table: 'टेबल हटवा',
// TODO: Add translations
duplicate_table: 'Duplicate Table',
duplicate_table: 'Duplicate Table', // TODO: Translate
add_relationship: 'Add Relationship', // TODO: Translate
},
// TODO: Add translations
+1
View File
@@ -408,6 +408,7 @@ export const ne: LanguageTranslation = {
edit_table: 'तालिका सम्पादन गर्नुहोस्',
duplicate_table: 'तालिका नक्कली गर्नुहोस्',
delete_table: 'तालिका हटाउनुहोस्',
add_relationship: 'Add Relationship', // TODO: Translate
},
snap_to_grid_tooltip: 'ग्रिडमा स्न्याप गर्नुहोस् ({{key}} थिच्नुहोस)',
+1
View File
@@ -407,6 +407,7 @@ export const pt_BR: LanguageTranslation = {
edit_table: 'Editar Tabela',
duplicate_table: 'Duplicate Table', // TODO: Translate
delete_table: 'Excluir Tabela',
add_relationship: 'Add Relationship', // TODO: Translate
},
// TODO: Add translations
+1
View File
@@ -403,6 +403,7 @@ export const ru: LanguageTranslation = {
edit_table: 'Изменить таблицу',
duplicate_table: 'Duplicate Table', // TODO: Translate
delete_table: 'Удалить таблицу',
add_relationship: 'Add Relationship', // TODO: Translate
},
copy_to_clipboard: 'Скопировать в буфер обмена',
+2 -2
View File
@@ -409,9 +409,9 @@ export const te: LanguageTranslation = {
table_node_context_menu: {
edit_table: 'పట్టికను సవరించు',
// TODO: Translate
duplicate_table: 'Duplicate Table',
duplicate_table: 'Duplicate Table', // TODO: Translate
delete_table: 'పట్టికను తొలగించు',
add_relationship: 'Add Relationship', // TODO: Translate
},
// TODO: Translate
+2 -2
View File
@@ -394,8 +394,8 @@ export const tr: LanguageTranslation = {
table_node_context_menu: {
edit_table: 'Tabloyu Düzenle',
delete_table: 'Tabloyu Sil',
// TODO: Translate
duplicate_table: 'Duplicate Table',
duplicate_table: 'Duplicate Table', // TODO: Translate
add_relationship: 'Add Relationship', // TODO: Translate
},
// TODO: Translate
+1
View File
@@ -402,6 +402,7 @@ export const uk: LanguageTranslation = {
edit_table: 'Редагувати таблицю',
duplicate_table: 'Дублювати таблицю',
delete_table: 'Видалити таблицю',
add_relationship: 'Add Relationship', // TODO: Translate
},
snap_to_grid_tooltip: 'Вирівнювати за сіткою (Отримуйте {{key}})',
+1
View File
@@ -403,6 +403,7 @@ export const vi: LanguageTranslation = {
edit_table: 'Sửa bảng',
duplicate_table: 'Nhân đôi bảng',
delete_table: 'Xóa bảng',
add_relationship: 'Add Relationship', // TODO: Translate
},
snap_to_grid_tooltip: 'Căn lưới (Giữ phím {{key}})',
+1
View File
@@ -399,6 +399,7 @@ export const zh_CN: LanguageTranslation = {
edit_table: '编辑表',
duplicate_table: 'Duplicate Table', // TODO: Translate
delete_table: '删除表',
add_relationship: 'Add Relationship', // TODO: Translate
},
snap_to_grid_tooltip: '对齐到网格(按住 {{key}}',
+1
View File
@@ -398,6 +398,7 @@ export const zh_TW: LanguageTranslation = {
edit_table: '編輯表格',
duplicate_table: 'Duplicate Table', // TODO: Translate
delete_table: '刪除表格',
add_relationship: 'Add Relationship', // TODO: Translate
},
snap_to_grid_tooltip: '對齊網格(按住 {{key}}',
@@ -9,9 +9,10 @@ import { useChartDB } from '@/hooks/use-chartdb';
import { useLayout } from '@/hooks/use-layout';
import { cloneTable } from '@/lib/clone';
import type { DBTable } from '@/lib/domain/db-table';
import { Copy, Pencil, Trash2 } from 'lucide-react';
import { Copy, Pencil, Trash2, Workflow } from 'lucide-react';
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useDialog } from '@/hooks/use-dialog';
export interface TableNodeContextMenuProps {
table: DBTable;
@@ -24,6 +25,7 @@ export const TableNodeContextMenu: React.FC<
const { openTableFromSidebar } = useLayout();
const { t } = useTranslation();
const { isMd: isDesktop } = useBreakpoint('md');
const { openCreateRelationshipDialog } = useDialog();
const duplicateTableHandler = useCallback(() => {
const clonedTable = cloneTable(table);
@@ -43,6 +45,12 @@ export const TableNodeContextMenu: React.FC<
removeTable(table.id);
}, [removeTable, table.id]);
const addRelationshipHandler = useCallback(() => {
openCreateRelationshipDialog({
sourceTableId: table.id,
});
}, [openCreateRelationshipDialog, table.id]);
if (!isDesktop || readonly) {
return <>{children}</>;
}
@@ -64,6 +72,13 @@ export const TableNodeContextMenu: React.FC<
<span>{t('table_node_context_menu.duplicate_table')}</span>
<Copy className="size-3.5" />
</ContextMenuItem>
<ContextMenuItem
onClick={addRelationshipHandler}
className="flex justify-between gap-3"
>
<span>{t('table_node_context_menu.add_relationship')}</span>
<Workflow className="size-3.5" />
</ContextMenuItem>
<ContextMenuItem
onClick={removeTableHandler}
className="flex justify-between gap-3"