mirror of
https://github.com/nn-ninja/n-brace.git
synced 2026-07-22 06:42:36 +00:00
ignore gh-pages files
This commit is contained in:
parent
a961cfbfd7
commit
dd4e2f5469
21 changed files with 100 additions and 350 deletions
5
.github/workflows/release.yml
vendored
5
.github/workflows/release.yml
vendored
|
|
@ -8,11 +8,12 @@ on:
|
|||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: oven-sh/setup-bun@v1
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
|
|
|
|||
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -20,3 +20,7 @@ main.js
|
|||
|
||||
.obsidian
|
||||
dist
|
||||
|
||||
# gh-pages
|
||||
_site
|
||||
.tweet-cache
|
||||
|
|
|
|||
29
README.md
29
README.md
|
|
@ -1,6 +1,6 @@
|
|||
# Obsidian N-brace Plugin
|
||||
|
||||

|
||||
[Obsidian N-brace Plugin](https://nn-ninja.github.io/n-brace/) Homepage
|
||||
|
||||
A more intuitive Graph navigation and visualization for Obsidian.
|
||||
|
||||
|
|
@ -13,7 +13,7 @@ https://buymeacoffee.com/nbrace
|
|||
|
||||
### Through community plugin store
|
||||
|
||||
You can install this on community plugin store (not yet).
|
||||
You can install this on community plugin store.
|
||||
|
||||
### Manual installation
|
||||
|
||||
|
|
@ -22,11 +22,34 @@ You can install this on community plugin store (not yet).
|
|||
3. `cd obsidian-n-brace && bun install && bun run build`
|
||||
4. there you go 🎉
|
||||
|
||||
## Usage
|
||||
|
||||

|
||||
|
||||
In a note tab context menu (right click), click on `use N-brace` to open a local graph of viewed note.
|
||||

|
||||
|
||||
The graph appears in the right-hand view and is designed to be used alongside the note on the left. Its purpose is to help you quickly understand how the current note connects to others and to visualize its overall structure.
|
||||
|
||||
G-span defines how large a portion of the graph is displayed. When the number of visible nodes reaches this limit, the view automatically shrinks by hiding the most distant nodes.
|
||||
|
||||
### Navigation
|
||||
|
||||
You can navigate using the keyboard arrow keys. Notes that haven’t been expanded yet in the local graph are shown with a dotted border.
|
||||
|
||||
If you need to explore the parent notes, hold Ctrl to reverse the navigation direction — the link directions will swap accordingly.
|
||||
|
||||
## Notes
|
||||
|
||||
At present, the n-brace graph model is initialized during application startup, which may result in longer loading times.
|
||||
|
||||
I have in my plans to optimize this process.
|
||||
|
||||
## Features
|
||||
|
||||
### Local Graph
|
||||
|
||||
In a note tab context menu (right click), click on `N-brace` to open a local graph of viewed note.
|
||||
It is designed to rather operate on a local graph, not on huge and unclear global graph.
|
||||
|
||||
### Save Setting
|
||||
|
||||
|
|
|
|||
|
|
@ -1,65 +0,0 @@
|
|||
## Plugin architecture
|
||||
|
||||
The plugin consist of following component:
|
||||
|
||||
1. plugin setting manager
|
||||
1. it controls the settings of the plugin
|
||||
2. responsible for loading and saving setting
|
||||
2. global graph view, a wrapper of graph setting manager and force graph
|
||||
3. local graph view, a wrapper of graph setting manager and force graph
|
||||
4. graph setting manager
|
||||
1. each graph view will have a graph setting manager
|
||||
2. it resposible for the control of the whole graph, then tell the force graph how to respond for setting changes
|
||||
3. the setting manager can be isolated from force graph because it follow separation of concern principle
|
||||
5. force graph
|
||||
1. the graph itself render the the nodes and links
|
||||
2. interaction manager
|
||||
1. it is responsible to handle all the interaction of the user and the force graph
|
||||
6. Graph Command
|
||||
1. has very simple API to extends commands
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class pluginSettingManager {
|
||||
+ controls the settings of the plugin
|
||||
+ loads and saves setting
|
||||
}
|
||||
|
||||
class globalGraphView {
|
||||
}
|
||||
|
||||
class localGraphView {
|
||||
}
|
||||
|
||||
class graphSettingManager {
|
||||
+ controls the whole graph
|
||||
+ communicates setting changes to force graph
|
||||
}
|
||||
|
||||
class forceGraph {
|
||||
+ renders nodes and links
|
||||
}
|
||||
|
||||
class interactionManager {
|
||||
+ handles user interaction with force graph
|
||||
}
|
||||
|
||||
class GraphCommand {
|
||||
+ extends commands through simple API
|
||||
}
|
||||
|
||||
pluginSettingManager --|> globalGraphView: contains
|
||||
pluginSettingManager --|> localGraphView: contains
|
||||
graphSettingManager --|> globalGraphView: contains
|
||||
graphSettingManager --|> localGraphView: contains
|
||||
forceGraph --|> globalGraphView: contains
|
||||
forceGraph --|> localGraphView: contains
|
||||
interactionManager --|> forceGraph: is part of
|
||||
GraphCommand --|> globalGraphView: can be used by
|
||||
GraphCommand --|> localGraphView: can be used by
|
||||
|
||||
```
|
||||
|
||||
## Key guideline for plugin development
|
||||
|
||||
1. Each component here follow separation of concern principle. They can be isolated easily and still functions properly.
|
||||
BIN
bun.lockb
BIN
bun.lockb
Binary file not shown.
|
|
@ -21,8 +21,7 @@
|
|||
"showExtension": false,
|
||||
"showFullPath": false,
|
||||
"showLinkArrow": true,
|
||||
"dontMoveWhenDrag": false,
|
||||
"dagOrientation": "null"
|
||||
"dontMoveWhenDrag": false
|
||||
}
|
||||
},
|
||||
"pluginSetting": {
|
||||
|
|
|
|||
BIN
docs/graph.png
Normal file
BIN
docs/graph.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 255 KiB |
BIN
docs/menu.png
Normal file
BIN
docs/menu.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
|
|
@ -1,11 +1,11 @@
|
|||
{
|
||||
"id": "n-brace-plugin",
|
||||
"id": "n-brace",
|
||||
"name": "N-brace",
|
||||
"version": "1.0.0",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "The N-brace plugin enhances navigation and clarity in the local graph view.",
|
||||
"minAppVersion": "1.9.14",
|
||||
"description": "Altered local graph's visual layout. Quick graph navigation.",
|
||||
"author": "Rafael Hazan",
|
||||
"authorUrl": "https://hayio.github.io/n-brace/",
|
||||
"authorUrl": "https://nn-ninja.github.io/n-brace",
|
||||
"fundingUrl": "https://buymeacoffee.com/nbrace",
|
||||
"isDesktopOnly": false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n-brace",
|
||||
"version": "1.1.11",
|
||||
"version": "1.0.0",
|
||||
"description": "A graph navigation and visualization for Obsidian",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
|
@ -55,7 +55,7 @@
|
|||
"react-dom": "18.3.1",
|
||||
"lucide-react": "^0.511.0",
|
||||
"jotai": "^2.9.1",
|
||||
"force-graph": "^1.43.5",
|
||||
"react-force-graph-2d": "^1.29.0",
|
||||
"@tweenjs/tween.js": "^21.0.0",
|
||||
"copy-anything": "^3.0.5",
|
||||
"d3": "^7.9.0",
|
||||
|
|
|
|||
|
|
@ -63,4 +63,4 @@ echo "Created tag $NEW_VERSION"
|
|||
git push origin "$NEW_VERSION"
|
||||
echo "Pushed tag $NEW_VERSION to the origin branch $NEW_VERSION"
|
||||
git push
|
||||
echo "Pushed to the origin master branch"
|
||||
echo "Pushed to the origin main branch"
|
||||
|
|
|
|||
|
|
@ -1,18 +1,7 @@
|
|||
import type { ISettingManager } from "@/Interfaces";
|
||||
import { AsyncQueue } from "@/util/AsyncQueue";
|
||||
import type {
|
||||
GraphSetting,
|
||||
LocalGraphSettings,
|
||||
MarkdownPostProcessorGraphSettings,
|
||||
Setting,
|
||||
} from "@/SettingsSchemas";
|
||||
import {
|
||||
defaultLocalGraphSetting,
|
||||
defaultMarkdownPostProcessorGraphSetting,
|
||||
GraphType,
|
||||
SearchEngineType,
|
||||
SettingSchema,
|
||||
} from "@/SettingsSchemas";
|
||||
import type { Setting } from "@/SettingsSchemas";
|
||||
import { defaultLocalGraphSetting, SearchEngineType, SettingSchema } from "@/SettingsSchemas";
|
||||
import { createNotice } from "@/util/createNotice";
|
||||
import { State } from "@/util/State";
|
||||
import type { Plugin } from "obsidian";
|
||||
|
|
@ -105,17 +94,6 @@ export class PluginSettingManager implements ISettingManager<Setting> {
|
|||
}
|
||||
await this.plugin.saveData(this.setting.value);
|
||||
}
|
||||
|
||||
static getNewSetting(type: GraphType.local): LocalGraphSettings;
|
||||
static getNewSetting(type: GraphType.postProcessor): MarkdownPostProcessorGraphSettings;
|
||||
static getNewSetting(type: GraphType): GraphSetting;
|
||||
static getNewSetting(type: GraphType) {
|
||||
if (type === GraphType.local) {
|
||||
return defaultLocalGraphSetting;
|
||||
} else {
|
||||
return defaultMarkdownPostProcessorGraphSetting;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const DEFAULT_SETTING: Setting = {
|
||||
|
|
|
|||
|
|
@ -49,23 +49,6 @@ export enum SearchEngineType {
|
|||
builtIn = "builtIn",
|
||||
}
|
||||
|
||||
export enum DagOrientation {
|
||||
td = "td",
|
||||
bu = "bu",
|
||||
lr = "lr",
|
||||
rl = "rl",
|
||||
zout = "zout",
|
||||
zin = "zin",
|
||||
radialout = "radialout",
|
||||
radialin = "radialin",
|
||||
null = "null",
|
||||
}
|
||||
|
||||
export enum CommandClickNodeAction {
|
||||
openNodeInNewTab = "openNodeInNewTab",
|
||||
focusNode = "focusNode",
|
||||
}
|
||||
|
||||
const commonSetting = {
|
||||
display: {
|
||||
nodeSize: nodeSize.default,
|
||||
|
|
@ -101,7 +84,6 @@ export const BaseDisplaySettingsSchema = z.object({
|
|||
showFullPath: z.boolean().default(commonSetting.display.showFullPath),
|
||||
showLinkArrow: z.boolean().default(commonSetting.display.showLinkArrow),
|
||||
dontMoveWhenDrag: z.boolean().default(commonSetting.display.dontMoveWhenDrag),
|
||||
dagOrientation: z.undefined().or(z.nativeEnum(DagOrientation)).default(DagOrientation.null),
|
||||
});
|
||||
|
||||
export const LocalDisplaySettingsSchema = z.object({
|
||||
|
|
@ -189,24 +171,12 @@ export const SettingSchema = z.object({
|
|||
}),
|
||||
});
|
||||
|
||||
export type BaseFilterSettings = Prettify<z.TypeOf<typeof BaseFilterSettingsSchema>>;
|
||||
|
||||
export type LocalFilterSetting = Prettify<z.TypeOf<typeof LocalFilterSettingSchema>>;
|
||||
|
||||
export type GroupSettings = Prettify<z.TypeOf<typeof GroupSettingsSchema>>;
|
||||
|
||||
export type BaseDisplaySettings = Prettify<z.TypeOf<typeof BaseDisplaySettingsSchema>>;
|
||||
|
||||
export type LocalDisplaySettings = Prettify<z.TypeOf<typeof LocalDisplaySettingsSchema>>;
|
||||
|
||||
export type LocalGraphSettings = Prettify<z.TypeOf<typeof LocalGraphSettingsSchema>>;
|
||||
|
||||
export type MarkdownPostProcessorGraphSettings = Prettify<
|
||||
z.TypeOf<typeof MarkdownPostProcessorGraphSettingSchema>
|
||||
>;
|
||||
|
||||
export type SavedSetting = Prettify<z.TypeOf<typeof SavedSettingSchema>>;
|
||||
|
||||
export type Setting = Prettify<z.TypeOf<typeof SettingSchema>>;
|
||||
|
||||
export type GraphSetting = Exclude<
|
||||
|
|
@ -225,7 +195,6 @@ export const defaultLocalGraphSetting: LocalGraphSettings = {
|
|||
groups: [],
|
||||
display: {
|
||||
...commonSetting.display,
|
||||
dagOrientation: DagOrientation.null,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -240,6 +209,5 @@ export const defaultMarkdownPostProcessorGraphSetting: MarkdownPostProcessorGrap
|
|||
groups: [],
|
||||
display: {
|
||||
...commonSetting.display,
|
||||
dagOrientation: DagOrientation.null,
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,6 +4,6 @@ export const config = {
|
|||
local: "n-brace-view",
|
||||
},
|
||||
displayText: {
|
||||
local: "N-brace your brain",
|
||||
local: "N-brace view",
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import type { LinkCache } from "@/graph/Link";
|
||||
import { Link } from "@/graph/Link";
|
||||
import { Node } from "@/graph/Node";
|
||||
import { copy } from "copy-anything";
|
||||
import type { App, TAbstractFile } from "obsidian";
|
||||
import type { App } from "obsidian";
|
||||
|
||||
export class Graph {
|
||||
public rootPath: string | undefined;
|
||||
|
|
@ -31,14 +30,6 @@ export class Graph {
|
|||
this.nodes.sort((a, b) => (a.zIndex || 0) - (b.zIndex || 0));
|
||||
}
|
||||
|
||||
public getNodeByPath = (path: string): Node | null => {
|
||||
const index = this.nodeIndex.get(path);
|
||||
if (typeof index === "number") {
|
||||
return this.nodes[index] ?? null;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
public getNodeById(id: string): Node | null {
|
||||
const index = this.nodeIndex.get(id);
|
||||
if (typeof index === "number") {
|
||||
|
|
@ -47,19 +38,6 @@ export class Graph {
|
|||
return null;
|
||||
}
|
||||
|
||||
// Returns a link by its source and target node ids
|
||||
public getLinkByIds(sourceNodeId: string, targetNodeId: string): Link | null {
|
||||
const sourceLinkMap = this.linkIndex.get(sourceNodeId);
|
||||
if (sourceLinkMap) {
|
||||
const index = sourceLinkMap.get(targetNodeId);
|
||||
if (index !== undefined) {
|
||||
// @ts-ignore
|
||||
return this.links[index];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public getLinksFromNode(sourceNodeId: string): Link[] {
|
||||
const linkIndexes = this.linkIndex.get(sourceNodeId);
|
||||
if (linkIndexes) {
|
||||
|
|
@ -70,41 +48,6 @@ export class Graph {
|
|||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* given a node id, return all the links that contains this node
|
||||
*/
|
||||
public getLinksWithNode(nodeId: string): Link[] {
|
||||
const links: Link[] = [];
|
||||
|
||||
this.linkIndex.forEach((targetMap, sourceId) => {
|
||||
if (sourceId === nodeId) {
|
||||
links.push(
|
||||
// @ts-ignore
|
||||
...Array.from(targetMap.values()).map((index) => this.links[index])
|
||||
);
|
||||
} else if (targetMap.has(nodeId)) {
|
||||
const index = targetMap.get(nodeId);
|
||||
if (typeof index === "number") {
|
||||
// @ts-ignore
|
||||
links.push(this.links[index]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return links;
|
||||
}
|
||||
|
||||
// Clones the graph
|
||||
public clone = (): Graph => {
|
||||
return new Graph(
|
||||
this.rootPath,
|
||||
copy(this.nodes),
|
||||
copy(this.links),
|
||||
copy(this.nodeIndex),
|
||||
copy(this.linkIndex)
|
||||
);
|
||||
};
|
||||
|
||||
public static createEmpty = (): Graph => {
|
||||
return new Graph(undefined, [], [], new Map(), new Map());
|
||||
};
|
||||
|
|
@ -231,64 +174,6 @@ export class Graph {
|
|||
this.nodes.forEach(fun);
|
||||
};
|
||||
|
||||
public static compare = (graph1: Graph, graph2: Graph): boolean => {
|
||||
// Quick checks for lengths
|
||||
if (graph1.nodes.length !== graph2.nodes.length) {
|
||||
return false;
|
||||
}
|
||||
if (graph1.links.length !== graph2.links.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compare nodes
|
||||
for (const node1 of graph1.nodes) {
|
||||
const node2Index = graph2.nodeIndex.get(node1.id);
|
||||
// @ts-ignore
|
||||
if (node2Index === undefined || graph2.nodes[node2Index].id !== node1.id) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Compare links
|
||||
for (const link1 of graph1.links) {
|
||||
const graph2SourceLinkMap = graph2.linkIndex.get(link1.source.id);
|
||||
if (!graph2SourceLinkMap) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const graph2LinkIndex = graph2SourceLinkMap.get(link1.target.id);
|
||||
if (graph2LinkIndex === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const link2 = graph2.links[graph2LinkIndex];
|
||||
if (!link2 || link2.source.id !== link1.source.id || link2.target.id !== link1.target.id) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* get the files from the graph
|
||||
*/
|
||||
public static getFiles(app: App, graph: Graph): TAbstractFile[] {
|
||||
return graph.nodes.map((node) => app.vault.getAbstractFileByPath(node.path)).filter(Boolean);
|
||||
}
|
||||
|
||||
public isAcyclic = (): boolean => {
|
||||
const visited = new Set<string>();
|
||||
const recStack = new Set<string>();
|
||||
|
||||
for (const node of this.nodes) {
|
||||
if (this.isCyclicUtil(node.id, visited, recStack)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
private isCyclicUtil = (nodeId: string, visited: Set<string>, recStack: Set<string>): boolean => {
|
||||
if (!visited.has(nodeId)) {
|
||||
// Add to visited and recursion stack
|
||||
|
|
|
|||
19
src/main.ts
19
src/main.ts
|
|
@ -114,7 +114,6 @@ export default class ForceGraphPlugin extends Plugin implements HoverParent {
|
|||
// unregister the resolved cache listener
|
||||
this.app.metadataCache.off("resolved", this.onGraphCacheReady);
|
||||
this.app.metadataCache.off("resolve", this.onGraphCacheChanged);
|
||||
this.app.workspace.detachLeavesOfType(VIEW_TYPE_REACT_FORCE_GRAPH);
|
||||
}
|
||||
|
||||
private initListeners() {
|
||||
|
|
@ -129,7 +128,7 @@ export default class ForceGraphPlugin extends Plugin implements HoverParent {
|
|||
if (!file) return;
|
||||
menu.addItem((item) => {
|
||||
item
|
||||
.setTitle("N-brace")
|
||||
.setTitle("use N-brace")
|
||||
.setIcon(config.icon)
|
||||
.onClick(() => this.openGraph());
|
||||
});
|
||||
|
|
@ -146,22 +145,6 @@ export default class ForceGraphPlugin extends Plugin implements HoverParent {
|
|||
if (!file || !(file instanceof TFile)) {
|
||||
return;
|
||||
}
|
||||
// if (markdownLeaves.length === 0) {
|
||||
// const rootLeaf = workspace.getMostRecentLeaf() || workspace.getLeaf();
|
||||
// const newLeaf = workspace.createLeafBySplit(rootLeaf, "vertical", true); // Split left
|
||||
// await newLeaf.openFile(file);
|
||||
// workspace.revealLeaf(newLeaf);
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // Use the first existing Markdown leaf
|
||||
// const targetLeaf = markdownLeaves[markdownLeaves.length - 1];
|
||||
// if (file) {
|
||||
// await targetLeaf.openFile(file as any); // Open the file in the leaf
|
||||
// workspace.revealLeaf(targetLeaf); // Ensure it’s visible
|
||||
// } else {
|
||||
// console.error(`File not found: ${filePath}`);
|
||||
// }
|
||||
|
||||
const graphLeaf = workspace.getLeavesOfType(VIEW_TYPE_REACT_FORCE_GRAPH)[0];
|
||||
if (!graphLeaf) {
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ export class ReactForceGraphView extends ItemView {
|
|||
readonly plugin: ForceGraphPlugin;
|
||||
readonly store = getDefaultStore();
|
||||
root: Root | null = null;
|
||||
private width: number = this.contentEl.offsetWidth;
|
||||
private settingManager: PluginSettingManager;
|
||||
|
||||
constructor(leaf: WorkspaceLeaf, plugin: ForceGraphPlugin) {
|
||||
|
|
@ -85,7 +84,7 @@ export class ReactForceGraphView extends ItemView {
|
|||
console.debug("node to expand ", nodePath);
|
||||
if (!nodePath || !this.plugin.globalGraph) {
|
||||
console.debug("Returning empty graph.");
|
||||
return Promise.resolve(Graph.createEmpty());
|
||||
return Graph.createEmpty();
|
||||
}
|
||||
const graph = getNewLocalGraph(this.plugin, {
|
||||
centerFilePath: nodePath,
|
||||
|
|
|
|||
|
|
@ -37,11 +37,11 @@ export class SettingTab extends PluginSettingTab {
|
|||
containerEl.empty();
|
||||
containerEl.addClasses(["n-brace-setting-tab"]);
|
||||
|
||||
containerEl.createEl("h2", { text: "Settings" });
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Base Folder")
|
||||
.setDesc("Base folder, within which the mind map can navigate.")
|
||||
.setDesc(
|
||||
"The base directory where your notes for exploration live — navigation builds from here."
|
||||
)
|
||||
.addText((text) => {
|
||||
text
|
||||
.setPlaceholder(`${DEFAULT_BASE_FOLDER}`)
|
||||
|
|
@ -68,35 +68,6 @@ export class SettingTab extends PluginSettingTab {
|
|||
text.inputEl.setAttribute("type", "string");
|
||||
});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Note title font size")
|
||||
.setDesc("The font size of the title displayed on a graph node.")
|
||||
.addText((text) => {
|
||||
text
|
||||
.setPlaceholder(`${DEFAULT_TITLE_FONT_SIZE}`)
|
||||
.setValue(String(pluginSetting.titleFontSize ?? DEFAULT_TITLE_FONT_SIZE))
|
||||
.onChange(async (value) => {
|
||||
// check if value is a number
|
||||
if (isNaN(Number(value)) || Number(value) <= 3) {
|
||||
// set the error to the input
|
||||
text.inputEl.setCustomValidity("Please enter a number higher than 3");
|
||||
this.plugin.settingManager.updateSettings((setting) => {
|
||||
setting.value.pluginSetting.titleFontSize = DEFAULT_TITLE_FONT_SIZE;
|
||||
});
|
||||
} else {
|
||||
text.inputEl.setCustomValidity("");
|
||||
this.plugin.settingManager.updateSettings((setting) => {
|
||||
setting.value.pluginSetting.titleFontSize = Number(value);
|
||||
eventBus.trigger("settings-updated");
|
||||
});
|
||||
}
|
||||
text.inputEl.reportValidity();
|
||||
});
|
||||
text.inputEl.setAttribute("type", "number");
|
||||
text.inputEl.setAttribute("min", "4");
|
||||
return text;
|
||||
});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Default display graph span")
|
||||
.setDesc("Size of the graph that is displayed at once.")
|
||||
|
|
@ -126,6 +97,37 @@ export class SettingTab extends PluginSettingTab {
|
|||
return text;
|
||||
});
|
||||
|
||||
new Setting(containerEl).setName("Appearance").setHeading();
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Note title font size")
|
||||
.setDesc("The font size of the title displayed on a graph node.")
|
||||
.addText((text) => {
|
||||
text
|
||||
.setPlaceholder(`${DEFAULT_TITLE_FONT_SIZE}`)
|
||||
.setValue(String(pluginSetting.titleFontSize ?? DEFAULT_TITLE_FONT_SIZE))
|
||||
.onChange(async (value) => {
|
||||
// check if value is a number
|
||||
if (isNaN(Number(value)) || Number(value) <= 3) {
|
||||
// set the error to the input
|
||||
text.inputEl.setCustomValidity("Please enter a number higher than 3");
|
||||
this.plugin.settingManager.updateSettings((setting) => {
|
||||
setting.value.pluginSetting.titleFontSize = DEFAULT_TITLE_FONT_SIZE;
|
||||
});
|
||||
} else {
|
||||
text.inputEl.setCustomValidity("");
|
||||
this.plugin.settingManager.updateSettings((setting) => {
|
||||
setting.value.pluginSetting.titleFontSize = Number(value);
|
||||
eventBus.trigger("settings-updated");
|
||||
});
|
||||
}
|
||||
text.inputEl.reportValidity();
|
||||
});
|
||||
text.inputEl.setAttribute("type", "number");
|
||||
text.inputEl.setAttribute("min", "4");
|
||||
return text;
|
||||
});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Link Color")
|
||||
.setDesc("Choose predefined colors or a custom color for graph links")
|
||||
|
|
@ -157,7 +159,7 @@ export class SettingTab extends PluginSettingTab {
|
|||
setting.value.pluginSetting.linkColorTheme = value;
|
||||
});
|
||||
}
|
||||
this.display();
|
||||
await this.display();
|
||||
})
|
||||
)
|
||||
.addColorPicker((color) => {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,15 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { FaArrowDown, FaArrowLeft, FaArrowRight, FaArrowUp } from "react-icons/fa";
|
||||
import {
|
||||
ArrowDown,
|
||||
ArrowLeft,
|
||||
ArrowRight,
|
||||
ArrowUp,
|
||||
FlipVertical,
|
||||
FlipVertical2,
|
||||
Sun,
|
||||
Sunrise,
|
||||
} from "lucide-react";
|
||||
import "@/views/graph/GraphControls.css";
|
||||
import { RiFlipVerticalFill, RiFlipVerticalLine } from "react-icons/ri";
|
||||
import { PiSunHorizonBold } from "react-icons/pi";
|
||||
import { IoSunny } from "react-icons/io5";
|
||||
import { useAtomValue } from "jotai/react";
|
||||
import { graphSettingsAtom } from "@/atoms/graphAtoms";
|
||||
|
||||
|
|
@ -73,24 +79,24 @@ export const GraphControls: React.FC<GraphControlsProps> = ({
|
|||
)}
|
||||
<div className="pm-graph-controls">
|
||||
<button onClick={handleToggle} title="Flip direction (Ctrl)">
|
||||
{isDescending ? <RiFlipVerticalLine /> : <RiFlipVerticalFill />}
|
||||
{isDescending ? <FlipVertical /> : <FlipVertical2 />}
|
||||
</button>
|
||||
<button onClick={handleHidden} title={isHidden ? "Show" : "Hide"}>
|
||||
{isHidden ? <IoSunny /> : <PiSunHorizonBold />}
|
||||
{isHidden ? <Sun /> : <Sunrise />}
|
||||
</button>
|
||||
{!isHidden && (
|
||||
<>
|
||||
<button onClick={onPanUp} title="Descend">
|
||||
<FaArrowUp />
|
||||
<ArrowUp />
|
||||
</button>
|
||||
<button onClick={onPanDown} title="Go Back">
|
||||
<FaArrowDown />
|
||||
<ArrowDown />
|
||||
</button>
|
||||
<button onClick={onPanLeft} title="Anti Clockwise">
|
||||
<FaArrowLeft />
|
||||
<ArrowLeft />
|
||||
</button>
|
||||
<button onClick={onPanRight} title="Clockwise">
|
||||
<FaArrowRight />
|
||||
<ArrowRight />
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ export const ReactForceGraph: React.FC<GraphComponentProps> = ({
|
|||
// directional forces depending on if a screen is vertical or horizontal
|
||||
useEffect(() => {
|
||||
const fg = fgRef.current;
|
||||
// TODO optimize: change forces only if dimensions changed a lot
|
||||
// TODO optimize: change forces only if dimensions changed many times
|
||||
if (fg) {
|
||||
console.debug("Setting Graph forces");
|
||||
// fg.d3Force("link", d3.forceLink()
|
||||
|
|
@ -262,9 +262,6 @@ export const ReactForceGraph: React.FC<GraphComponentProps> = ({
|
|||
} else {
|
||||
const nodeDuplicates = new Set<string>();
|
||||
newLinks = links.filter((l) => {
|
||||
// const exists = graphData.links.find((existingLink) => {
|
||||
// return Link.compare(l, existingLink);
|
||||
// });
|
||||
const exists = graphData.links.find((existingLink) => {
|
||||
return (
|
||||
(existingLink.source.idx === expandedNode.idx ||
|
||||
|
|
@ -303,40 +300,12 @@ export const ReactForceGraph: React.FC<GraphComponentProps> = ({
|
|||
return false;
|
||||
}
|
||||
return !nodeDuplicates.has(n.path);
|
||||
// const isItParentOfExpanded = n.links.find((l) => l.target.path === expandedNode.path);
|
||||
// if (isItParentOfExpanded) {
|
||||
// return !graphData.nodes.find((existingNode) => {
|
||||
// const exists = Node.compare(n, existingNode);
|
||||
// // const hasLink = exists && existingNode.links.find((l) => l.tar)
|
||||
// if (exists) {
|
||||
// links.forEach((l) => {
|
||||
// if (l.source.path === existingNode.path) {
|
||||
// l.source = existingNode;
|
||||
// } else if (l.target.path === existingNode.path) {
|
||||
// l.target = existingNode;
|
||||
// }
|
||||
// });
|
||||
// existingNode.links = [
|
||||
// ...existingNode.links,
|
||||
// ...n.links.filter(
|
||||
// (l) =>
|
||||
// !existingNode.links.find(
|
||||
// (existingLink) =>
|
||||
// l.source.path === existingLink.source.path &&
|
||||
// l.target.path === existingLink.target.path
|
||||
// )
|
||||
// ),
|
||||
// ];
|
||||
// }
|
||||
// return exists;
|
||||
// });
|
||||
// }
|
||||
// return true;
|
||||
});
|
||||
}
|
||||
|
||||
graphData.nodes.sort((a, b) => (a.zIndex || 0) - (b.zIndex || 0));
|
||||
|
||||
//// debug code
|
||||
// const simplifiedNodes = [...graphData.nodes, ...newNodes].map(node => ({
|
||||
// path: node.path,
|
||||
// links: node.links.map((link) => link.source.path + "->" + link.target.path),
|
||||
|
|
@ -364,7 +333,6 @@ export const ReactForceGraph: React.FC<GraphComponentProps> = ({
|
|||
graphData.nodes.find((n) => n.path === selectedNode.selectedPath) as Node,
|
||||
node
|
||||
);
|
||||
// console.info(`HISTORY: ${JSON.stringify(newHistory)}`);
|
||||
setNavHistory(newHistory);
|
||||
if (selectedNode.selectedIndex !== undefined) {
|
||||
const newIndexHistory = stackOnIndexHistory(
|
||||
|
|
|
|||
|
|
@ -51,4 +51,3 @@
|
|||
border-radius: 3px;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
/*# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsic3JjL3ZpZXdzL2dyYXBoL0dyYXBoQ29udHJvbHMuY3NzIl0sCiAgInNvdXJjZXNDb250ZW50IjogWyIucG0tZ3JhcGgtY29udHJvbHMge1xuICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gIHRvcDogNTBweDtcbiAgcmlnaHQ6IDEwcHg7XG4gIGRpc3BsYXk6IGdyaWQ7XG4gIGdyaWQtdGVtcGxhdGUtY29sdW1uczogMWZyIDFmcjsgLyogMiBjb2x1bW5zICovXG4gIGdhcDogNXB4O1xuICBiYWNrZ3JvdW5kOiByZ2JhKDI1NSwgMjU1LCAyNTUsIDAuOCk7IC8qIFNlbWktdHJhbnNwYXJlbnQgYmFja2dyb3VuZCAqL1xuICBwYWRkaW5nOiA1cHg7XG4gIGJvcmRlci1yYWRpdXM6IDVweDtcbiAgYm94LXNoYWRvdzogMCAycHggNHB4IHJnYmEoMCwgMCwgMCwgMC4yKTtcbn1cblxuLnBtLWdyYXBoLWNvbnRyb2xzIGJ1dHRvbiB7XG4gIGRpc3BsYXk6IGZsZXg7XG4gIGFsaWduLWl0ZW1zOiBjZW50ZXI7XG4gIGp1c3RpZnktY29udGVudDogY2VudGVyO1xuICBiYWNrZ3JvdW5kOiB2YXIoLS1jb2xvci1iYXNlLTQwKTtcbiAgYm9yZGVyOiBub25lO1xuICBib3JkZXItcmFkaXVzOiA0cHg7XG4gIGN1cnNvcjogcG9pbnRlcjtcbiAgZm9udC1zaXplOiAxNnB4O1xufVxuXG4ucG0tZ3JhcGgtY29udHJvbHMgYnV0dG9uOmhvdmVyIHtcbiAgYmFja2dyb3VuZDogdmFyKC0tY29sb3ItYmFzZS02MCk7XG59XG5cbi5zbGlkZXItY29udHJvbCB7XG4gIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgdG9wOiA1MHB4O1xuICBsZWZ0OiAxMHB4O1xuICBkaXNwbGF5OiBncmlkO1xuICBncmlkLXRlbXBsYXRlLWNvbHVtbnM6IDFmcjtcbiAgZ2FwOiA1cHg7XG4gIGJhY2tncm91bmQ6IHJnYmEoMjU1LCAyNTUsIDI1NSwgMC44KTsgLyogU2VtaS10cmFuc3BhcmVudCBiYWNrZ3JvdW5kICovXG4gIHBhZGRpbmc6IDVweDtcbiAgYm9yZGVyLXJhZGl1czogNXB4O1xuICBib3gtc2hhZG93OiAwIDJweCA0cHggcmdiYSgwLCAwLCAwLCAwLjIpO1xuICBjb2xvcjogdmFyKC0tY29sb3ItYmFzZS0wMCk7XG59XG5cbi5zbGlkZXItY29udHJvbCBpbnB1dFt0eXBlPVwicmFuZ2VcIl0ge1xuICB3aWR0aDogMTAwcHg7XG4gIGN1cnNvcjogcG9pbnRlcjtcbn1cblxuLnNsaWRlci12YWx1ZSB7XG4gIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgdG9wOiAtMjBweDsgLyogQWJvdmUgdGhlIHNsaWRlciAqL1xuICBsZWZ0OiA1MCU7XG4gIHRyYW5zZm9ybTogdHJhbnNsYXRlWCgtNTAlKTtcbiAgZm9udC1zaXplOiAxMnB4O1xuICBwYWRkaW5nOiAycHggNXB4O1xuICBib3JkZXItcmFkaXVzOiAzcHg7XG4gIGJveC1zaGFkb3c6IDAgMXB4IDJweCByZ2JhKDAsIDAsIDAsIDAuMSk7XG59XG4iXSwKICAibWFwcGluZ3MiOiAiO0FBQUE7QUFDRTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUFBO0FBR0Y7QUFDRTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQUE7QUFHRjtBQUNFO0FBQUE7QUFHRjtBQUNFO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFBQTtBQUdGO0FBQ0U7QUFDQTtBQUFBO0FBR0Y7QUFDRTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQUE7IiwKICAibmFtZXMiOiBbXQp9Cg== */
|
||||
|
|
|
|||
Loading…
Reference in a new issue