Skip to content

Commit 3c99409

Browse files
committed
Refactors a lot of code in order to make link management much better.
1 parent 7e8776c commit 3c99409

File tree

5 files changed

+240
-442
lines changed

5 files changed

+240
-442
lines changed

linkManager.ts

Lines changed: 202 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,237 @@
11

2-
import { CustomLink, LinkStatus } from 'types';
2+
import { ObsidianRenderer, ObsidianLink, LinkPair, GltLink, DataviewLinkType } from 'types';
3+
import { Page, getAPI } from 'obsidian-dataview';
4+
import { Text, TextStyle } from 'pixi.js';
5+
import extractLinks from 'markdown-link-extractor';
6+
37

48
export class LinkManager {
5-
linksMap: Map<string, CustomLink>;
6-
linkStatus: Map<string, LinkStatus.First | LinkStatus.Second>;
9+
linksMap: Map<string, GltLink>;
10+
api = getAPI();
711

812
constructor() {
9-
this.linksMap = new Map<string, CustomLink>();
10-
this.linkStatus = new Map<string, LinkStatus.First | LinkStatus.Second>();
13+
this.linksMap = new Map<string, GltLink>();
1114
}
1215

1316
generateKey(sourceId: string, targetId: string): string {
1417
return `${sourceId}-${targetId}`;
1518
}
1619

17-
addLink(link: CustomLink): void {
18-
const key = this.generateKey(link.source.id, link.target.id);
19-
const reverseKey = this.generateKey(link.target.id, link.source.id);
20+
addLink(renderer: ObsidianRenderer, obLink: ObsidianLink): void {
21+
const key = this.generateKey(obLink.source.id, obLink.target.id);
22+
const reverseKey = this.generateKey(obLink.target.id, obLink.source.id);
23+
const pairStatus = (obLink.source.id !== obLink.target.id) && this.linksMap.has(reverseKey) ? LinkPair.Second : LinkPair.None;
24+
const newLink: GltLink = {
25+
obsidianLink: obLink,
26+
pairStatus: pairStatus,
27+
pixiText: this.createTextForLink(renderer, obLink, pairStatus)
28+
};
2029

21-
// Add the new link
22-
this.linksMap.set(key, link);
30+
this.linksMap.set(key, newLink);
2331

24-
// Manage the pair statuses
25-
if (this.linksMap.has(reverseKey)) {
26-
// If the reverse link is already present, set the statuses accordingly
27-
this.linkStatus.set(key, LinkStatus.Second);
28-
this.linkStatus.set(reverseKey, LinkStatus.First);
29-
} else {
30-
// If it's a standalone link (no pair yet), do not assign a pair status
31-
// This will be managed when the reverse link is added (if it happens)
32+
if ((obLink.source.id !== obLink.target.id) && this.linksMap.has(reverseKey)) {
33+
const reverseLink = this.linksMap.get(reverseKey);
34+
if (reverseLink) {
35+
reverseLink.pairStatus = LinkPair.First;
36+
}
37+
console.log("New Pair: ", newLink.obsidianLink, reverseLink?.obsidianLink);
3238
}
3339
}
3440

35-
removeLink(link: CustomLink): void {
41+
removeLink(renderer: ObsidianRenderer, link: ObsidianLink): void {
3642
const key = this.generateKey(link.source.id, link.target.id);
3743
const reverseKey = this.generateKey(link.target.id, link.source.id);
3844

39-
this.linksMap.delete(key);
40-
if (this.linkStatus.has(key)) {
41-
this.linkStatus.delete(key);
45+
const gltLink = this.linksMap.get(key);
46+
47+
if (gltLink && gltLink.pixiText && renderer.px && renderer.px.stage && renderer.px.stage.children && renderer.px.stage.children.includes(gltLink.pixiText)) {
48+
renderer.px.stage.removeChild(gltLink.pixiText);
49+
gltLink.pixiText.destroy();
4250
}
43-
if (this.linkStatus.get(reverseKey) === LinkStatus.Second) {
44-
this.linkStatus.delete(reverseKey);
51+
52+
this.linksMap.delete(key);
53+
54+
const reverseLink = this.linksMap.get(reverseKey);
55+
if (reverseLink && reverseLink.pairStatus !== LinkPair.None) {
56+
reverseLink.pairStatus = LinkPair.None;
4557
}
4658
}
4759

48-
updateLinks(currentLinks: CustomLink[]): void {
60+
removeLinks(renderer: ObsidianRenderer, currentLinks: ObsidianLink[]): void {
4961
const currentKeys = new Set(currentLinks.map(link => this.generateKey(link.source.id, link.target.id)));
50-
for (const key of this.linksMap.keys()) {
62+
// remove any links in our map that aren't in this list
63+
this.linksMap.forEach((_, key) => {
5164
if (!currentKeys.has(key)) {
5265
const link = this.linksMap.get(key);
5366
if (link) {
54-
this.removeLink(link);
67+
this.removeLink(renderer, link.obsidianLink);
5568
}
5669
}
70+
});
71+
}
72+
73+
getLinkPairStatus(key: string): LinkPair {
74+
const link = this.linksMap.get(key);
75+
return link ? link.pairStatus : LinkPair.None;
76+
}
77+
78+
// Update the position of the text on the graph
79+
updateTextPosition(renderer: ObsidianRenderer, link: ObsidianLink): void {
80+
if (!renderer || !link || !link.source || !link.target) {
81+
// If any of these are null, exit the function
82+
return;
83+
}
84+
const linkKey = this.generateKey(link.source.id, link.target.id);
85+
const obsLink = this.linksMap.get(linkKey);
86+
let text;
87+
if (obsLink) {
88+
text = obsLink.pixiText;
89+
} else {
90+
return
91+
};
92+
93+
// Calculate the mid-point of the link
94+
const midX: number = (link.source.x + link.target.x) / 2;
95+
const midY: number = (link.source.y + link.target.y) / 2;
96+
97+
// Transform the mid-point coordinates based on the renderer's pan and scale
98+
const { x, y } = this.getLinkToTextCoordinates(midX, midY, renderer.panX, renderer.panY, renderer.scale);
99+
if (text && renderer.px && renderer.px.stage && renderer.px.stage.children && renderer.px.stage.children.includes(text)) {
100+
// Set the position and scale of the text
101+
text.x = x;
102+
text.y = y;
103+
text.scale.set(1 / (3 * renderer.nodeScale));
104+
}
105+
}
106+
107+
// Create or update text for a given link
108+
private createTextForLink(renderer: ObsidianRenderer, link: ObsidianLink, pairStatus : LinkPair): Text | null{
109+
110+
// Get the text to display for the link
111+
let linkString: string | null = this.getMetadataKeyForLink(link.source.id, link.target.id);
112+
if (linkString === null) {
113+
return null;
114+
} //doesn't add if link is null
115+
if (link.source.id === link.target.id) {
116+
linkString = "";
117+
}
118+
119+
if (pairStatus === LinkPair.None) {
120+
121+
} else if (pairStatus === LinkPair.First) {
122+
linkString = linkString + "\n\n";
123+
} else if (pairStatus === LinkPair.Second) {
124+
linkString = "\n\n" + linkString;
125+
} else {
126+
127+
}
128+
// Define the style for the text
129+
const textStyle: TextStyle = new TextStyle({
130+
fontFamily: 'Arial',
131+
fontSize: 36,
132+
fill: this.determineTextColor()
133+
});
134+
// Create new text node
135+
const text: Text = new Text(linkString, textStyle);
136+
text.alpha = 0.7;
137+
text.anchor.set(0.5, 0.5);
138+
139+
140+
this.updateTextPosition(renderer, link);
141+
renderer.px.stage.addChild(text);
142+
return text
143+
}
144+
145+
// Utility function to extract the file path from a Markdown link
146+
private extractPathFromMarkdownLink(markdownLink: string | unknown): string {
147+
const links = extractLinks(markdownLink).links;
148+
// The package returns an array of links. Assuming you want the first link.
149+
return links.length > 0 ? links[0] : '';
150+
}
151+
152+
private determineTextColor(): string {
153+
// Get the computed style of the document body
154+
const style = getComputedStyle(document.body);
155+
156+
// This is a basic check. You might need to adjust the logic based on the themes you support.
157+
// Here, we assume that dark themes have a background color with a low brightness value.
158+
let textColor = '#FF0000';
159+
if (style && style.backgroundColor && style.backgroundColor) {
160+
const isDarkTheme = style.backgroundColor.match(/\d+/g)?.map(Number).slice(0, 3).reduce((a, b) => a + b, 0) < 382.5;
161+
isDarkTheme ? textColor = '#FFFFFF' : textColor = '#000000'; // White text for dark themes, black for light themes)
162+
}
163+
164+
return textColor
165+
}
166+
167+
// Method to determine the type of a value, now a class method
168+
private determineDataviewLinkType(value: any): DataviewLinkType {
169+
if (typeof value === 'object' && value !== null && 'path' in value) {
170+
return DataviewLinkType.WikiLink;
171+
} else if (typeof value === 'string' && value.includes('](')) {
172+
return DataviewLinkType.MarkdownLink;
173+
} else if (typeof value === 'string') {
174+
return DataviewLinkType.String;
175+
} else if (Array.isArray(value)) {
176+
return DataviewLinkType.Array;
177+
} else {
178+
return DataviewLinkType.Other;
179+
}
180+
}
181+
182+
// Remove all text nodes from the graph
183+
destroyMap(renderer: ObsidianRenderer): void {
184+
if (this.linksMap.size > 0) {
185+
this.linksMap.forEach((gltLink, linkKey) => {
186+
if (gltLink.pixiText && renderer.px && renderer.px.stage && renderer.px.stage.children && renderer.px.stage.children.includes(gltLink.pixiText)) {
187+
renderer.px.stage.removeChild(gltLink.pixiText);
188+
gltLink.pixiText.destroy();
189+
}
190+
this.linksMap.delete(linkKey);
191+
});
57192
}
58193
}
59194

60-
getLinkStatus(key: string): LinkStatus {
61-
const status = this.linkStatus.get(key)
62-
if (status !== undefined) {
63-
return status
64-
} else{
65-
return LinkStatus.None
195+
// Get the metadata key for a link between two pages
196+
private getMetadataKeyForLink(sourceId: string, targetId: string): string | null {
197+
const sourcePage: Page | undefined = this.api.page(sourceId);
198+
if (!sourcePage) return null;
199+
200+
for (const [key, value] of Object.entries(sourcePage)) {
201+
const valueType = this.determineDataviewLinkType(value);
202+
203+
switch (valueType) {
204+
case DataviewLinkType.WikiLink:
205+
if (value.path === targetId) {
206+
return key;
207+
}
208+
break;
209+
case DataviewLinkType.MarkdownLink:
210+
if (this.extractPathFromMarkdownLink(value) === targetId) {
211+
return key;
212+
}
213+
break;
214+
case DataviewLinkType.Array:
215+
for (const item of value) {
216+
if (this.determineDataviewLinkType(item) === DataviewLinkType.WikiLink && item.path === targetId) {
217+
return key;
218+
}
219+
if (this.determineDataviewLinkType(item) === DataviewLinkType.MarkdownLink && this.extractPathFromMarkdownLink(item) === targetId) {
220+
return key;
221+
}
222+
}
223+
break;
224+
// Handle other cases as needed
225+
}
66226
}
227+
return null;
228+
}
229+
230+
// Function to calculate the coordinates for placing the link text.
231+
private getLinkToTextCoordinates(linkX: number, linkY: number, panX: number, panY: number, scale: number): { x: number, y: number } {
232+
// Apply scaling and panning to calculate the actual position.
233+
return { x: linkX * scale + panX, y: linkY * scale + panY };
67234
}
235+
236+
68237
}

0 commit comments

Comments
 (0)