mirror of
https://github.com/zadam/trilium.git
synced 2025-11-11 08:58:58 +01:00
chore(type_widgets): get relations to render
This commit is contained in:
parent
082ea7b5c1
commit
7a2d91e7de
@ -12,7 +12,7 @@ import panzoom, { PanZoomOptions } from "panzoom";
|
||||
import dialog from "../../../services/dialog";
|
||||
import server from "../../../services/server";
|
||||
import toast from "../../../services/toast";
|
||||
import { CreateChildrenResponse } from "@triliumnext/commons";
|
||||
import { CreateChildrenResponse, RelationMapPostResponse, RelationMapRelation } from "@triliumnext/commons";
|
||||
import contextMenu from "../../../menus/context_menu";
|
||||
import appContext from "../../../components/app_context";
|
||||
import RelationMapApi, { MapData, MapDataNoteEntry } from "./api";
|
||||
@ -23,6 +23,13 @@ interface Clipboard {
|
||||
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);
|
||||
@ -103,6 +110,8 @@ export default function RelationMap({ note, ntxId }: TypeWidgetProps) {
|
||||
onTransform
|
||||
});
|
||||
|
||||
useRelationData(note.noteId, data, mapApiRef, pbApiRef);
|
||||
|
||||
return (
|
||||
<div className="note-detail-relation-map note-detail-printable">
|
||||
<div className="relation-map-wrapper" onClick={clickCallback}>
|
||||
@ -172,6 +181,84 @@ function usePanZoom({ ntxId, containerRef, options, transformData, onTransform }
|
||||
});
|
||||
}
|
||||
|
||||
async function useRelationData(noteId: string, mapData: MapData | undefined, mapApiRef: RefObject<RelationMapApi>, jsPlumbRef: RefObject<jsPlumbInstance>) {
|
||||
const noteIds = mapData?.notes.map((note) => note.noteId);
|
||||
const [ relations, setRelations ] = useState<ClientRelation[]>();
|
||||
const [ inverseRelations, setInverseRelations ] = useState<RelationMapPostResponse["inverseRelations"]>();
|
||||
|
||||
async function refresh() {
|
||||
const data = await server.post<RelationMapPostResponse>("relation-map", { noteIds, relationMapNoteId: noteId });
|
||||
const relations: ClientRelation[] = [];
|
||||
|
||||
for (const _relation of data.relations) {
|
||||
const relation = _relation as ClientRelation; // we inject a few variables.
|
||||
const match = relations.find(
|
||||
(rel) =>
|
||||
rel.name === data.inverseRelations[relation.name] &&
|
||||
((rel.sourceNoteId === relation.sourceNoteId && rel.targetNoteId === relation.targetNoteId) ||
|
||||
(rel.sourceNoteId === relation.targetNoteId && rel.targetNoteId === relation.sourceNoteId))
|
||||
);
|
||||
|
||||
if (match) {
|
||||
match.type = relation.type = relation.name === data.inverseRelations[relation.name] ? "biDirectional" : "inverse";
|
||||
relation.render = false; // don't render second relation
|
||||
} else {
|
||||
relation.type = "uniDirectional";
|
||||
relation.render = true;
|
||||
}
|
||||
|
||||
relations.push(relation);
|
||||
setInverseRelations(data.inverseRelations);
|
||||
}
|
||||
|
||||
setRelations(relations);
|
||||
mapApiRef.current?.cleanupOtherNotes(Object.keys(data.noteTitles));
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
refresh();
|
||||
}, [ noteId, mapData, jsPlumbInstance ]);
|
||||
|
||||
// Refresh on the canvas.
|
||||
useEffect(() => {
|
||||
const jsPlumbInstance = jsPlumbRef.current;
|
||||
if (!jsPlumbInstance) return;
|
||||
|
||||
jsPlumbInstance.batch(async () => {
|
||||
if (!mapData || !relations) {
|
||||
return;
|
||||
}
|
||||
|
||||
jsPlumbInstance.deleteEveryEndpoint();
|
||||
|
||||
for (const relation of relations) {
|
||||
if (!relation.render) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const connection = jsPlumbInstance.connect({
|
||||
source: noteIdToId(relation.sourceNoteId),
|
||||
target: noteIdToId(relation.targetNoteId),
|
||||
type: relation.type
|
||||
});
|
||||
|
||||
// TODO: Does this actually do anything.
|
||||
//@ts-expect-error
|
||||
connection.id = relation.attributeId;
|
||||
|
||||
if (relation.type === "inverse") {
|
||||
connection.getOverlay("label-source").setLabel(relation.name);
|
||||
connection.getOverlay("label-target").setLabel(inverseRelations?.[relation.name] ?? "");
|
||||
} else {
|
||||
connection.getOverlay("label").setLabel(relation.name);
|
||||
}
|
||||
|
||||
connection.canvas.setAttribute("data-connection-id", connection.id);
|
||||
}
|
||||
});
|
||||
}, [ relations, mapData ]);
|
||||
}
|
||||
|
||||
function useNoteCreation({ ntxId, note, containerRef, mapApiRef }: {
|
||||
ntxId: string | null | undefined;
|
||||
note: FNote;
|
||||
@ -238,7 +325,10 @@ function JsPlumb({ className, props, children, containerRef: externalContainerRe
|
||||
}
|
||||
|
||||
onInstanceCreated?.(jsPlumbInstance);
|
||||
return () => jsPlumbInstance.cleanupListeners();
|
||||
return () => {
|
||||
jsPlumbInstance.deleteEveryEndpoint();
|
||||
jsPlumbInstance.cleanupListeners()
|
||||
};
|
||||
}, [ apiRef ]);
|
||||
|
||||
return (
|
||||
|
||||
@ -49,6 +49,13 @@ export default class RelationMapApi {
|
||||
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;
|
||||
this.data.notes = filteredNotes;
|
||||
this.onDataChange(true);
|
||||
}
|
||||
|
||||
setTransform(transform: PanZoomTransform) {
|
||||
if (this.data.transform.scale - transform.scale > DELTA
|
||||
|| this.data.transform.x - transform.x > DELTA
|
||||
|
||||
@ -31,24 +31,6 @@ declare module "jsplumb" {
|
||||
|
||||
let containerCounter = 1;
|
||||
|
||||
export type RelationType = "uniDirectional" | "biDirectional" | "inverse";
|
||||
|
||||
interface Relation {
|
||||
name: string;
|
||||
attributeId: string;
|
||||
sourceNoteId: string;
|
||||
targetNoteId: string;
|
||||
type: RelationType;
|
||||
render: boolean;
|
||||
}
|
||||
|
||||
// TODO: Deduplicate.
|
||||
interface RelationMapPostResponse {
|
||||
relations: Relation[];
|
||||
inverseRelations: Record<string, string>;
|
||||
noteTitles: Record<string, string>;
|
||||
}
|
||||
|
||||
type MenuCommands = "openInNewTab" | "remove" | "editTitle";
|
||||
|
||||
export default class RelationMapTypeWidget extends TypeWidget {
|
||||
@ -113,77 +95,6 @@ export default class RelationMapTypeWidget extends TypeWidget {
|
||||
this.$relationMapContainer.empty();
|
||||
}
|
||||
|
||||
async loadNotesAndRelations() {
|
||||
if (!this.mapData || !this.jsPlumbInstance) {
|
||||
return;
|
||||
}
|
||||
|
||||
const noteIds = this.mapData.notes.map((note) => note.noteId);
|
||||
const data = await server.post<RelationMapPostResponse>("relation-map", { noteIds, relationMapNoteId: this.noteId });
|
||||
|
||||
this.relations = [];
|
||||
|
||||
for (const relation of data.relations) {
|
||||
const match = this.relations.find(
|
||||
(rel) =>
|
||||
rel.name === data.inverseRelations[relation.name] &&
|
||||
((rel.sourceNoteId === relation.sourceNoteId && rel.targetNoteId === relation.targetNoteId) ||
|
||||
(rel.sourceNoteId === relation.targetNoteId && rel.targetNoteId === relation.sourceNoteId))
|
||||
);
|
||||
|
||||
if (match) {
|
||||
match.type = relation.type = relation.name === data.inverseRelations[relation.name] ? "biDirectional" : "inverse";
|
||||
relation.render = false; // don't render second relation
|
||||
} else {
|
||||
relation.type = "uniDirectional";
|
||||
relation.render = true;
|
||||
}
|
||||
|
||||
this.relations.push(relation);
|
||||
}
|
||||
|
||||
this.mapData.notes = this.mapData.notes.filter((note) => note.noteId in data.noteTitles);
|
||||
|
||||
this.jsPlumbInstance.batch(async () => {
|
||||
if (!this.jsPlumbInstance || !this.mapData || !this.relations) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.clearMap();
|
||||
|
||||
for (const note of this.mapData.notes) {
|
||||
const title = data.noteTitles[note.noteId];
|
||||
|
||||
await this.createNoteBox(note.noteId, title, note.x, note.y);
|
||||
}
|
||||
|
||||
for (const relation of this.relations) {
|
||||
if (!relation.render) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const connection = this.jsPlumbInstance.connect({
|
||||
source: this.noteIdToId(relation.sourceNoteId),
|
||||
target: this.noteIdToId(relation.targetNoteId),
|
||||
type: relation.type
|
||||
});
|
||||
|
||||
// TODO: Does this actually do anything.
|
||||
//@ts-expect-error
|
||||
connection.id = relation.attributeId;
|
||||
|
||||
if (relation.type === "inverse") {
|
||||
connection.getOverlay("label-source").setLabel(relation.name);
|
||||
connection.getOverlay("label-target").setLabel(data.inverseRelations[relation.name]);
|
||||
} else {
|
||||
connection.getOverlay("label").setLabel(relation.name);
|
||||
}
|
||||
|
||||
connection.canvas.setAttribute("data-connection-id", connection.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
if (this.jsPlumbInstance) {
|
||||
this.clearMap();
|
||||
|
||||
@ -1,22 +1,12 @@
|
||||
import type { Request } from "express";
|
||||
import becca from "../../becca/becca.js";
|
||||
import sql from "../../services/sql.js";
|
||||
|
||||
interface ResponseData {
|
||||
noteTitles: Record<string, string>;
|
||||
relations: {
|
||||
attributeId: string;
|
||||
sourceNoteId: string;
|
||||
targetNoteId: string;
|
||||
name: string;
|
||||
}[];
|
||||
inverseRelations: Record<string, string>;
|
||||
}
|
||||
import { RelationMapPostResponse } from "@triliumnext/commons";
|
||||
|
||||
function getRelationMap(req: Request) {
|
||||
const { relationMapNoteId, noteIds } = req.body;
|
||||
|
||||
const resp: ResponseData = {
|
||||
const resp: RelationMapPostResponse = {
|
||||
// noteId => title
|
||||
noteTitles: {},
|
||||
relations: [],
|
||||
|
||||
@ -247,3 +247,16 @@ export interface SchemaResponse {
|
||||
type: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface RelationMapRelation {
|
||||
name: string;
|
||||
attributeId: string;
|
||||
sourceNoteId: string;
|
||||
targetNoteId: string;
|
||||
}
|
||||
|
||||
export interface RelationMapPostResponse {
|
||||
noteTitles: Record<string, string>;
|
||||
relations: RelationMapRelation[];
|
||||
inverseRelations: Record<string, string>;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user