mirror of
https://github.com/zadam/trilium.git
synced 2025-10-09 15:08:14 +08:00
chore(react): port data part of server API
This commit is contained in:
parent
b41042fec4
commit
09811d23f6
7 changed files with 212 additions and 163 deletions
|
@ -30,16 +30,8 @@ const TPL = /*html*/`<div class="note-map-widget">
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
type WidgetMode = "type" | "ribbon";
|
type WidgetMode = "type" | "ribbon";
|
||||||
type MapType = "tree" | "link";
|
|
||||||
type Data = GraphData<NodeObject, LinkObject<NodeObject>>;
|
type Data = GraphData<NodeObject, LinkObject<NodeObject>>;
|
||||||
|
|
||||||
interface Node extends NodeObject {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
type: string;
|
|
||||||
color: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Link extends LinkObject<NodeObject> {
|
interface Link extends LinkObject<NodeObject> {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -50,43 +42,10 @@ interface Link extends LinkObject<NodeObject> {
|
||||||
target: Node;
|
target: Node;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface NotesAndRelationsData {
|
|
||||||
nodes: Node[];
|
|
||||||
links: {
|
|
||||||
id: string;
|
|
||||||
source: string;
|
|
||||||
target: string;
|
|
||||||
name: string;
|
|
||||||
}[];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace
|
|
||||||
interface ResponseLink {
|
|
||||||
key: string;
|
|
||||||
sourceNoteId: string;
|
|
||||||
targetNoteId: string;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface PostNotesMapResponse {
|
|
||||||
notes: string[];
|
|
||||||
links: ResponseLink[];
|
|
||||||
noteIdToDescendantCountMap: Record<string, number>;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface GroupedLink {
|
|
||||||
id: string;
|
|
||||||
sourceNoteId: string;
|
|
||||||
targetNoteId: string;
|
|
||||||
names: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class NoteMapWidget extends NoteContextAwareWidget {
|
export default class NoteMapWidget extends NoteContextAwareWidget {
|
||||||
|
|
||||||
private fixNodes: boolean;
|
private fixNodes: boolean;
|
||||||
private widgetMode: WidgetMode;
|
private widgetMode: WidgetMode;
|
||||||
private mapType?: MapType;
|
|
||||||
private cssData!: CssData;
|
|
||||||
|
|
||||||
private themeStyle!: string;
|
private themeStyle!: string;
|
||||||
private $container!: JQuery<HTMLElement>;
|
private $container!: JQuery<HTMLElement>;
|
||||||
|
@ -147,8 +106,6 @@ export default class NoteMapWidget extends NoteContextAwareWidget {
|
||||||
async refreshWithNote(note: FNote) {
|
async refreshWithNote(note: FNote) {
|
||||||
this.$widget.show();
|
this.$widget.show();
|
||||||
|
|
||||||
this.mapType = note.getLabelValue("mapType") === "tree" ? "tree" : "link";
|
|
||||||
|
|
||||||
//variables for the hover effekt. We have to save the neighbours of a hovered node in a set. Also we need to save the links as well as the hovered node itself
|
//variables for the hover effekt. We have to save the neighbours of a hovered node in a set. Also we need to save the links as well as the hovered node itself
|
||||||
|
|
||||||
let hoverNode: NodeObject | null = null;
|
let hoverNode: NodeObject | null = null;
|
||||||
|
@ -157,8 +114,6 @@ export default class NoteMapWidget extends NoteContextAwareWidget {
|
||||||
|
|
||||||
const ForceGraph = (await import("force-graph")).default;
|
const ForceGraph = (await import("force-graph")).default;
|
||||||
this.graph = new ForceGraph(this.$container[0])
|
this.graph = new ForceGraph(this.$container[0])
|
||||||
.width(this.$container.width() || 0)
|
|
||||||
.height(this.$container.height() || 0)
|
|
||||||
.onZoom((zoom) => this.setZoomLevel(zoom.k))
|
.onZoom((zoom) => this.setZoomLevel(zoom.k))
|
||||||
.d3AlphaDecay(0.01)
|
.d3AlphaDecay(0.01)
|
||||||
.d3VelocityDecay(0.08)
|
.d3VelocityDecay(0.08)
|
||||||
|
@ -244,15 +199,6 @@ export default class NoteMapWidget extends NoteContextAwareWidget {
|
||||||
.linkCanvasObjectMode(() => "after");
|
.linkCanvasObjectMode(() => "after");
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapRootNoteId = this.getMapRootNoteId();
|
|
||||||
|
|
||||||
const labelValues = (name: string) => this.note?.getLabels(name).map(l => l.value) ?? [];
|
|
||||||
|
|
||||||
const excludeRelations = labelValues("mapExcludeRelation");
|
|
||||||
const includeRelations = labelValues("mapIncludeRelation");
|
|
||||||
|
|
||||||
const data = await this.loadNotesAndRelations(mapRootNoteId, excludeRelations, includeRelations);
|
|
||||||
|
|
||||||
const nodeLinkRatio = data.nodes.length / data.links.length;
|
const nodeLinkRatio = data.nodes.length / data.links.length;
|
||||||
const magnifiedRatio = Math.pow(nodeLinkRatio, 1.5);
|
const magnifiedRatio = Math.pow(nodeLinkRatio, 1.5);
|
||||||
const charge = -20 / magnifiedRatio;
|
const charge = -20 / magnifiedRatio;
|
||||||
|
@ -273,22 +219,6 @@ export default class NoteMapWidget extends NoteContextAwareWidget {
|
||||||
this.renderData(data);
|
this.renderData(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
getMapRootNoteId(): string {
|
|
||||||
if (this.noteId && this.widgetMode === "ribbon") {
|
|
||||||
return this.noteId;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mapRootNoteId = this.note?.getLabelValue("mapRootNoteId");
|
|
||||||
|
|
||||||
if (mapRootNoteId === "hoisted") {
|
|
||||||
mapRootNoteId = hoistedNoteService.getHoistedNoteId();
|
|
||||||
} else if (!mapRootNoteId) {
|
|
||||||
mapRootNoteId = appContext.tabManager.getActiveContext()?.parentNoteId;
|
|
||||||
}
|
|
||||||
|
|
||||||
return mapRootNoteId ?? "";
|
|
||||||
}
|
|
||||||
|
|
||||||
getColorForNode(node: Node) {
|
getColorForNode(node: Node) {
|
||||||
if (node.color) {
|
if (node.color) {
|
||||||
return node.color;
|
return node.color;
|
||||||
|
@ -393,91 +323,7 @@ export default class NoteMapWidget extends NoteContextAwareWidget {
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadNotesAndRelations(mapRootNoteId: string, excludeRelations: string[], includeRelations: string[]): Promise<NotesAndRelationsData> {
|
|
||||||
const resp = await server.post<PostNotesMapResponse>(`note-map/${mapRootNoteId}/${this.mapType}`, {
|
|
||||||
excludeRelations, includeRelations
|
|
||||||
});
|
|
||||||
|
|
||||||
this.calculateNodeSizes(resp);
|
|
||||||
|
|
||||||
const links = this.getGroupedLinks(resp.links);
|
|
||||||
|
|
||||||
this.nodes = resp.notes.map(([noteId, title, type, color]) => ({
|
|
||||||
id: noteId,
|
|
||||||
name: title,
|
|
||||||
type: type,
|
|
||||||
color: color
|
|
||||||
}));
|
|
||||||
|
|
||||||
return {
|
|
||||||
nodes: this.nodes,
|
|
||||||
links: links.map((link) => ({
|
|
||||||
id: `${link.sourceNoteId}-${link.targetNoteId}`,
|
|
||||||
source: link.sourceNoteId,
|
|
||||||
target: link.targetNoteId,
|
|
||||||
name: link.names.join(", ")
|
|
||||||
}))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
getGroupedLinks(links: ResponseLink[]): 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
calculateNodeSizes(resp: PostNotesMapResponse) {
|
|
||||||
this.noteIdToSizeMap = {};
|
|
||||||
|
|
||||||
if (this.mapType === "tree") {
|
|
||||||
const { noteIdToDescendantCountMap } = resp;
|
|
||||||
|
|
||||||
for (const noteId in noteIdToDescendantCountMap) {
|
|
||||||
this.noteIdToSizeMap[noteId] = 4;
|
|
||||||
|
|
||||||
const count = noteIdToDescendantCountMap[noteId];
|
|
||||||
|
|
||||||
if (count > 0) {
|
|
||||||
this.noteIdToSizeMap[noteId] += 1 + Math.round(Math.log(count) / Math.log(1.5));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (this.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) {
|
|
||||||
this.noteIdToSizeMap[noteId] = 4;
|
|
||||||
|
|
||||||
if (noteId in noteIdToLinkCount) {
|
|
||||||
this.noteIdToSizeMap[noteId] += Math.min(Math.pow(noteIdToLinkCount[noteId], 0.5), 15);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
renderData(data: Data) {
|
renderData(data: Data) {
|
||||||
this.graph.graphData(data);
|
|
||||||
|
|
||||||
if (this.widgetMode === "ribbon" && this.note?.type !== "search") {
|
if (this.widgetMode === "ribbon" && this.note?.type !== "search") {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.setDimensions();
|
this.setDimensions();
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
import { useEffect, useRef, useState } from "preact/hooks";
|
import { useEffect, useRef, useState } from "preact/hooks";
|
||||||
import "./NoteMap.css";
|
import "./NoteMap.css";
|
||||||
import { rgb2hex } from "./utils";
|
import { getMapRootNoteId, NoteMapWidgetMode, rgb2hex } from "./utils";
|
||||||
|
import { RefObject } from "preact";
|
||||||
|
import FNote from "../../entities/fnote";
|
||||||
|
import { useNoteContext, useNoteLabel } from "../react/hooks";
|
||||||
|
import ForceGraph, { LinkObject, NodeObject } from "force-graph";
|
||||||
|
import { loadNotesAndRelations, NotesAndRelationsData } from "./data";
|
||||||
|
|
||||||
interface CssData {
|
interface CssData {
|
||||||
fontFamily: string;
|
fontFamily: string;
|
||||||
|
@ -8,11 +13,20 @@ interface CssData {
|
||||||
mutedTextColor: string;
|
mutedTextColor: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function NoteMap() {
|
interface NoteMapProps {
|
||||||
|
note: FNote;
|
||||||
|
widgetMode: NoteMapWidgetMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
type MapType = "tree" | "link";
|
||||||
|
|
||||||
|
export default function NoteMap({ note, widgetMode }: NoteMapProps) {
|
||||||
|
console.log("Got note", note);
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const styleResolverRef = useRef<HTMLDivElement>(null);
|
const styleResolverRef = useRef<HTMLDivElement>(null);
|
||||||
const [ cssData, setCssData ] = useState<CssData>();
|
const [ cssData, setCssData ] = useState<CssData>();
|
||||||
console.log("Got CSS ", cssData);
|
const [ mapTypeRaw ] = useNoteLabel(note, "mapType");
|
||||||
|
const mapType: MapType = mapTypeRaw === "tree" ? "tree" : "link";
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!containerRef.current || !styleResolverRef.current) return;
|
if (!containerRef.current || !styleResolverRef.current) return;
|
||||||
|
@ -22,14 +36,54 @@ export default function NoteMap() {
|
||||||
return (
|
return (
|
||||||
<div className="note-map-widget">
|
<div className="note-map-widget">
|
||||||
<div ref={styleResolverRef} class="style-resolver" />
|
<div ref={styleResolverRef} class="style-resolver" />
|
||||||
|
<NoteGraph containerRef={containerRef} note={note} widgetMode={widgetMode} mapType={mapType} />
|
||||||
<div ref={containerRef} className="note-map-container">
|
|
||||||
Container goes here.
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function NoteGraph({ containerRef, note, widgetMode, mapType }: {
|
||||||
|
containerRef: RefObject<HTMLDivElement>;
|
||||||
|
note: FNote;
|
||||||
|
widgetMode: NoteMapWidgetMode;
|
||||||
|
mapType: MapType;
|
||||||
|
}) {
|
||||||
|
const graphRef = useRef<ForceGraph<NodeObject, LinkObject<NodeObject>>>();
|
||||||
|
const [ data, setData ] = useState<NotesAndRelationsData>();
|
||||||
|
console.log("Got data ", data);
|
||||||
|
|
||||||
|
// Build the note graph instance.
|
||||||
|
useEffect(() => {
|
||||||
|
const container = containerRef.current;
|
||||||
|
if (!container) return;
|
||||||
|
const { width, height } = container.getBoundingClientRect();
|
||||||
|
const graph = new ForceGraph(container)
|
||||||
|
.width(width)
|
||||||
|
.height(height);
|
||||||
|
graphRef.current = graph;
|
||||||
|
|
||||||
|
const mapRootId = getMapRootNoteId(note.noteId, note, widgetMode);
|
||||||
|
if (!mapRootId) return;
|
||||||
|
|
||||||
|
const labelValues = (name: string) => note.getLabels(name).map(l => l.value) ?? [];
|
||||||
|
const excludeRelations = labelValues("mapExcludeRelation");
|
||||||
|
const includeRelations = labelValues("mapIncludeRelation");
|
||||||
|
loadNotesAndRelations(mapRootId, excludeRelations, includeRelations, mapType).then((data) => {
|
||||||
|
console.log("Got data ", data);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => container.replaceChildren();
|
||||||
|
}, [ note ]);
|
||||||
|
|
||||||
|
// Render the data.
|
||||||
|
useEffect(() => {
|
||||||
|
if (!graphRef.current || !data) return;
|
||||||
|
graphRef.current.graphData(data);
|
||||||
|
}, [ data ]);
|
||||||
|
|
||||||
|
|
||||||
|
return <div ref={containerRef} className="note-map-container" />;
|
||||||
|
}
|
||||||
|
|
||||||
function getCssData(container: HTMLElement, styleResolver: HTMLElement): CssData {
|
function getCssData(container: HTMLElement, styleResolver: HTMLElement): CssData {
|
||||||
const containerStyle = window.getComputedStyle(container);
|
const containerStyle = window.getComputedStyle(container);
|
||||||
const styleResolverStyle = window.getComputedStyle(styleResolver);
|
const styleResolverStyle = window.getComputedStyle(styleResolver);
|
||||||
|
|
113
apps/client/src/widgets/note_map/data.ts
Normal file
113
apps/client/src/widgets/note_map/data.ts
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
import { NoteMapLink, NoteMapPostResponse } from "@triliumnext/commons";
|
||||||
|
import server from "../../services/server";
|
||||||
|
import { NodeObject } from "force-graph";
|
||||||
|
|
||||||
|
type MapType = "tree" | "link";
|
||||||
|
|
||||||
|
interface GroupedLink {
|
||||||
|
id: string;
|
||||||
|
sourceNoteId: string;
|
||||||
|
targetNoteId: string;
|
||||||
|
names: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Node extends NodeObject {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
color: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
|
@ -1,6 +1,28 @@
|
||||||
|
import appContext from "../../components/app_context";
|
||||||
|
import FNote from "../../entities/fnote";
|
||||||
|
import hoisted_note from "../../services/hoisted_note";
|
||||||
|
|
||||||
|
export type NoteMapWidgetMode = "ribbon" | "hoisted";
|
||||||
|
|
||||||
export function rgb2hex(rgb: string) {
|
export function rgb2hex(rgb: string) {
|
||||||
return `#${(rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/) || [])
|
return `#${(rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/) || [])
|
||||||
.slice(1)
|
.slice(1)
|
||||||
.map((n) => parseInt(n, 10).toString(16).padStart(2, "0"))
|
.map((n) => parseInt(n, 10).toString(16).padStart(2, "0"))
|
||||||
.join("")}`;
|
.join("")}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getMapRootNoteId(noteId: string, note: FNote, widgetMode: NoteMapWidgetMode): string | null {
|
||||||
|
if (noteId && widgetMode === "ribbon") {
|
||||||
|
return noteId;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mapRootNoteId = note?.getLabelValue("mapRootNoteId");
|
||||||
|
|
||||||
|
if (mapRootNoteId === "hoisted") {
|
||||||
|
mapRootNoteId = hoisted_note.getHoistedNoteId();
|
||||||
|
} else if (!mapRootNoteId) {
|
||||||
|
mapRootNoteId = appContext.tabManager.getActiveContext()?.parentNoteId ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapRootNoteId;
|
||||||
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import NoteMap from "../note_map/NoteMap";
|
||||||
|
|
||||||
const SMALL_SIZE_HEIGHT = "300px";
|
const SMALL_SIZE_HEIGHT = "300px";
|
||||||
|
|
||||||
export default function NoteMapTab({ noteContext }: TabContext) {
|
export default function NoteMapTab({ note }: TabContext) {
|
||||||
const [ isExpanded, setExpanded ] = useState(false);
|
const [ isExpanded, setExpanded ] = useState(false);
|
||||||
const [ height, setHeight ] = useState(SMALL_SIZE_HEIGHT);
|
const [ height, setHeight ] = useState(SMALL_SIZE_HEIGHT);
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
@ -26,7 +26,7 @@ export default function NoteMapTab({ noteContext }: TabContext) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="note-map-ribbon-widget" style={{ height }} ref={containerRef}>
|
<div className="note-map-ribbon-widget" style={{ height }} ref={containerRef}>
|
||||||
<NoteMap />
|
{note && <NoteMap note={note} widgetMode="ribbon" />}
|
||||||
|
|
||||||
{!isExpanded ? (
|
{!isExpanded ? (
|
||||||
<ActionButton
|
<ActionButton
|
||||||
|
|
|
@ -42,6 +42,7 @@ type Labels = {
|
||||||
// Note-type specific
|
// Note-type specific
|
||||||
webViewSrc: string;
|
webViewSrc: string;
|
||||||
readOnly: boolean;
|
readOnly: boolean;
|
||||||
|
mapType: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -260,3 +260,16 @@ export interface RelationMapPostResponse {
|
||||||
relations: RelationMapRelation[];
|
relations: RelationMapRelation[];
|
||||||
inverseRelations: Record<string, string>;
|
inverseRelations: Record<string, string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface NoteMapLink {
|
||||||
|
key: string;
|
||||||
|
sourceNoteId: string;
|
||||||
|
targetNoteId: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NoteMapPostResponse {
|
||||||
|
notes: string[];
|
||||||
|
links: NoteMapLink[];
|
||||||
|
noteIdToDescendantCountMap: Record<string, number>;
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue