Files
Trilium/apps/client/src/widgets/note_map/data.ts
2025-10-04 12:29:48 +03:00

123 lines
3.5 KiB
TypeScript

import { NoteMapLink, NoteMapPostResponse } from "@triliumnext/commons";
import server from "../../services/server";
import { LinkObject, NodeObject } from "force-graph";
type MapType = "tree" | "link";
interface GroupedLink {
id: string;
sourceNoteId: string;
targetNoteId: string;
names: string[];
}
export interface Node extends NodeObject {
id: string;
name: string;
type: string;
color: string;
}
export interface Link extends LinkObject<NodeObject> {
id: string;
name: string;
x: number;
y: number;
source: Node;
target: Node;
}
export interface NotesAndRelationsData {
nodes: Node[];
links: {
id: string;
source: string;
target: string;
name: string;
}[];
noteIdToSizeMap: Record<string, number>;
}
export async function loadNotesAndRelations(mapRootNoteId: string, excludeRelations: string[], includeRelations: string[], mapType: MapType): Promise<NotesAndRelationsData> {
const resp = await server.post<NoteMapPostResponse>(`note-map/${mapRootNoteId}/${mapType}`, {
excludeRelations, includeRelations
});
const noteIdToSizeMap = calculateNodeSizes(resp, mapType);
const links = getGroupedLinks(resp.links);
const nodes = resp.notes.map(([noteId, title, type, color]) => ({
id: noteId,
name: title,
type: type,
color: color
}));
return {
noteIdToSizeMap,
nodes,
links: links.map((link) => ({
id: `${link.sourceNoteId}-${link.targetNoteId}`,
source: link.sourceNoteId,
target: link.targetNoteId,
name: link.names.join(", ")
}))
};
}
function calculateNodeSizes(resp: NoteMapPostResponse, mapType: MapType) {
const noteIdToSizeMap: Record<string, number> = {};
if (mapType === "tree") {
const { noteIdToDescendantCountMap } = resp;
for (const noteId in noteIdToDescendantCountMap) {
noteIdToSizeMap[noteId] = 4;
const count = noteIdToDescendantCountMap[noteId];
if (count > 0) {
noteIdToSizeMap[noteId] += 1 + Math.round(Math.log(count) / Math.log(1.5));
}
}
} else if (mapType === "link") {
const noteIdToLinkCount: Record<string, number> = {};
for (const link of resp.links) {
noteIdToLinkCount[link.targetNoteId] = 1 + (noteIdToLinkCount[link.targetNoteId] || 0);
}
for (const [noteId] of resp.notes) {
noteIdToSizeMap[noteId] = 4;
if (noteId in noteIdToLinkCount) {
noteIdToSizeMap[noteId] += Math.min(Math.pow(noteIdToLinkCount[noteId], 0.5), 15);
}
}
}
return noteIdToSizeMap;
}
function getGroupedLinks(links: NoteMapLink[]): GroupedLink[] {
const linksGroupedBySourceTarget: Record<string, GroupedLink> = {};
for (const link of links) {
const key = `${link.sourceNoteId}-${link.targetNoteId}`;
if (key in linksGroupedBySourceTarget) {
if (!linksGroupedBySourceTarget[key].names.includes(link.name)) {
linksGroupedBySourceTarget[key].names.push(link.name);
}
} else {
linksGroupedBySourceTarget[key] = {
id: key,
sourceNoteId: link.sourceNoteId,
targetNoteId: link.targetNoteId,
names: [link.name]
};
}
}
return Object.values(linksGroupedBySourceTarget);
}