mirror of
https://github.com/zadam/trilium.git
synced 2025-11-11 00:49:00 +01:00
chore(react/type_widgets): reintroduce relation context menu
This commit is contained in:
parent
c469fffb6e
commit
1eca9f6541
@ -11,26 +11,20 @@ import dialog from "../../../services/dialog";
|
||||
import server from "../../../services/server";
|
||||
import toast from "../../../services/toast";
|
||||
import { CreateChildrenResponse, RelationMapPostResponse, RelationMapRelation } from "@triliumnext/commons";
|
||||
import RelationMapApi, { MapData, MapDataNoteEntry } from "./api";
|
||||
import RelationMapApi, { ClientRelation, MapData, MapDataNoteEntry } from "./api";
|
||||
import setupOverlays, { uniDirectionalOverlays } from "./overlays";
|
||||
import { JsPlumb } from "./jsplumb";
|
||||
import { getMousePosition, getZoom, idToNoteId, noteIdToId } from "./utils";
|
||||
import { NoteBox } from "./NoteBox";
|
||||
import utils from "../../../services/utils";
|
||||
import attribute_autocomplete from "../../../services/attribute_autocomplete";
|
||||
import { buildRelationContextMenuHandler } from "./context_menu";
|
||||
|
||||
interface Clipboard {
|
||||
noteId: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
type RelationType = "uniDirectional" | "biDirectional" | "inverse";
|
||||
|
||||
interface ClientRelation extends RelationMapRelation {
|
||||
type: RelationType;
|
||||
render: boolean;
|
||||
}
|
||||
|
||||
export default function RelationMap({ note, ntxId }: TypeWidgetProps) {
|
||||
const [ data, setData ] = useState<MapData>();
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
@ -222,6 +216,7 @@ async function useRelationData(noteId: string, mapData: MapData | undefined, map
|
||||
}
|
||||
|
||||
setRelations(relations);
|
||||
mapApiRef.current?.loadRelations(relations);
|
||||
mapApiRef.current?.cleanupOtherNotes(Object.keys(data.noteTitles));
|
||||
}
|
||||
|
||||
@ -312,10 +307,15 @@ function useNoteCreation({ ntxId, note, containerRef, mapApiRef }: {
|
||||
|
||||
function useRelationCreation({ mapApiRef, jsPlumbApiRef }: { mapApiRef: RefObject<RelationMapApi>, jsPlumbApiRef: RefObject<jsPlumbInstance> }) {
|
||||
const connectionCallback = useCallback(async (info: OnConnectionBindInfo, originalEvent: Event) => {
|
||||
const connection = info.connection;
|
||||
|
||||
// Called whenever a connection is created, either initially or manually when added by the user.
|
||||
const handler = buildRelationContextMenuHandler(connection, mapApiRef);
|
||||
connection.bind("contextmenu", handler);
|
||||
|
||||
// if there's no event, then this has been triggered programmatically
|
||||
if (!originalEvent || !mapApiRef.current) return;
|
||||
|
||||
const connection = info.connection;
|
||||
let name = await dialog.prompt({
|
||||
message: t("relation_map.specify_new_relation_name"),
|
||||
shown: ({ $answer }) => {
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import { Connection } from "jsplumb";
|
||||
import FNote from "../../../entities/fnote";
|
||||
import { t } from "../../../services/i18n";
|
||||
import server from "../../../services/server";
|
||||
import utils from "../../../services/utils";
|
||||
import { RelationMapRelation } from "@triliumnext/commons";
|
||||
|
||||
export interface MapDataNoteEntry {
|
||||
noteId: string;
|
||||
@ -14,17 +16,29 @@ export interface MapData {
|
||||
transform: PanZoomTransform;
|
||||
}
|
||||
|
||||
export type RelationType = "uniDirectional" | "biDirectional" | "inverse";
|
||||
|
||||
export interface ClientRelation extends RelationMapRelation {
|
||||
type: RelationType;
|
||||
render: boolean;
|
||||
}
|
||||
|
||||
const DELTA = 0.0001;
|
||||
|
||||
export default class RelationMapApi {
|
||||
|
||||
private data: MapData;
|
||||
private relations: any[];
|
||||
private relations: ClientRelation[];
|
||||
private onDataChange: (refreshUi: boolean) => void;
|
||||
|
||||
constructor(note: FNote, initialMapData: MapData, onDataChange: (newData: MapData, refreshUi: boolean) => void) {
|
||||
this.data = initialMapData;
|
||||
this.onDataChange = (refreshUi) => onDataChange({ ...this.data }, refreshUi);
|
||||
this.relations = [];
|
||||
}
|
||||
|
||||
loadRelations(relations: ClientRelation[]) {
|
||||
this.relations = relations;
|
||||
}
|
||||
|
||||
createItem(newNote: MapDataNoteEntry) {
|
||||
@ -50,6 +64,16 @@ export default class RelationMapApi {
|
||||
this.onDataChange(true);
|
||||
}
|
||||
|
||||
async removeRelation(connection: Connection) {
|
||||
const relation = this.relations.find((rel) => rel.attributeId === connection.id);
|
||||
|
||||
if (relation) {
|
||||
await server.remove(`notes/${relation.sourceNoteId}/relations/${relation.name}/to/${relation.targetNoteId}`);
|
||||
}
|
||||
|
||||
this.onDataChange(true);
|
||||
}
|
||||
|
||||
cleanupOtherNotes(noteIds: string[]) {
|
||||
const filteredNotes = this.data.notes.filter((note) => noteIds.includes(note.noteId));
|
||||
if (filteredNotes.length === this.data.notes.length) return;
|
||||
|
||||
@ -6,6 +6,7 @@ import dialog from "../../../services/dialog";
|
||||
import { t } from "../../../services/i18n";
|
||||
import server from "../../../services/server";
|
||||
import RelationMapApi from "./api";
|
||||
import { Connection } from "jsplumb";
|
||||
|
||||
export function buildNoteContextMenuHandler(note: FNote | null | undefined, mapApiRef: RefObject<RelationMapApi>) {
|
||||
return (e: MouseEvent) => {
|
||||
@ -54,3 +55,31 @@ export function buildNoteContextMenuHandler(note: FNote | null | undefined, mapA
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
export function buildRelationContextMenuHandler(connection: Connection, mapApiRef: RefObject<RelationMapApi>) {
|
||||
return (_, event: MouseEvent) => {
|
||||
if (connection.getType().includes("link")) {
|
||||
// don't create context menu if it's a link since there's nothing to do with link from relation map
|
||||
// (don't open browser menu either)
|
||||
event.preventDefault();
|
||||
} else {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
contextMenu.show({
|
||||
x: event.pageX,
|
||||
y: event.pageY,
|
||||
items: [{ title: t("relation_map.remove_relation"), command: "remove", uiIcon: "bx bx-trash" }],
|
||||
selectMenuItemHandler: async ({ command }) => {
|
||||
if (command === "remove") {
|
||||
if (!(await dialog.confirm(t("relation_map.confirm_remove_relation")))) {
|
||||
return;
|
||||
}
|
||||
|
||||
mapApiRef.current?.removeRelation(connection);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -118,43 +118,6 @@ export default class RelationMapTypeWidget extends TypeWidget {
|
||||
}
|
||||
}
|
||||
|
||||
async connectionCreatedHandler(info: ConnectionMadeEventInfo, originalEvent: Event) {
|
||||
|
||||
connection.bind("contextmenu", (obj: unknown, event: MouseEvent) => {
|
||||
if (connection.getType().includes("link")) {
|
||||
// don't create context menu if it's a link since there's nothing to do with link from relation map
|
||||
// (don't open browser menu either)
|
||||
event.preventDefault();
|
||||
} else {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
contextMenu.show({
|
||||
x: event.pageX,
|
||||
y: event.pageY,
|
||||
items: [{ title: t("relation_map.remove_relation"), command: "remove", uiIcon: "bx bx-trash" }],
|
||||
selectMenuItemHandler: async ({ command }) => {
|
||||
if (command === "remove") {
|
||||
if (!(await dialogService.confirm(t("relation_map.confirm_remove_relation"))) || !this.relations) {
|
||||
return;
|
||||
}
|
||||
|
||||
const relation = this.relations.find((rel) => rel.attributeId === connection.id);
|
||||
|
||||
if (relation) {
|
||||
await server.remove(`notes/${relation.sourceNoteId}/relations/${relation.name}/to/${relation.targetNoteId}`);
|
||||
}
|
||||
|
||||
this.jsPlumbInstance?.deleteConnection(connection);
|
||||
|
||||
this.relations = this.relations.filter((relation) => relation.attributeId !== connection.id);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
saveData() {
|
||||
this.spacedUpdate.scheduleUpdate();
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user