This commit is contained in:
Team3
2026-05-22 00:27:57 +02:00
parent b7759d6542
commit 93bc13dc78
3 changed files with 33 additions and 65 deletions

30
package-lock.json generated
View File

@@ -10,8 +10,7 @@
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/cytoscape": "^3.21.9", "@types/cytoscape": "^3.21.9",
"cytoscape": "^3.33.4", "cytoscape": "^3.33.4"
"cytoscape-fcose": "^2.2.0"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^20.11.0", "@types/node": "^20.11.0",
@@ -502,15 +501,6 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/cose-base": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz",
"integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==",
"license": "MIT",
"dependencies": {
"layout-base": "^2.0.0"
}
},
"node_modules/crelt": { "node_modules/crelt": {
"version": "1.0.6", "version": "1.0.6",
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
@@ -528,18 +518,6 @@
"node": ">=0.10" "node": ">=0.10"
} }
}, },
"node_modules/cytoscape-fcose": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz",
"integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==",
"license": "MIT",
"dependencies": {
"cose-base": "^2.2.0"
},
"peerDependencies": {
"cytoscape": "^3.2.0"
}
},
"node_modules/esbuild": { "node_modules/esbuild": {
"version": "0.20.2", "version": "0.20.2",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz",
@@ -579,12 +557,6 @@
"@esbuild/win32-x64": "0.20.2" "@esbuild/win32-x64": "0.20.2"
} }
}, },
"node_modules/layout-base": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz",
"integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==",
"license": "MIT"
},
"node_modules/moment": { "node_modules/moment": {
"version": "2.29.4", "version": "2.29.4",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",

View File

@@ -20,7 +20,6 @@
}, },
"dependencies": { "dependencies": {
"@types/cytoscape": "^3.21.9", "@types/cytoscape": "^3.21.9",
"cytoscape": "^3.33.4", "cytoscape": "^3.33.4"
"cytoscape-fcose": "^2.2.0"
} }
} }

View File

@@ -7,8 +7,6 @@ import {
normalizePath, normalizePath,
} from "obsidian"; } from "obsidian";
import cytoscape, { Core, NodeSingular } from "cytoscape"; import cytoscape, { Core, NodeSingular } from "cytoscape";
// @ts-ignore — no types for cytoscape-fcose
import fcose from "cytoscape-fcose";
import { import {
VIEW_TYPE_PROJECT_DETAILS_VIEW, VIEW_TYPE_PROJECT_DETAILS_VIEW,
VIEW_TYPE_PROJECT_VIEW, VIEW_TYPE_PROJECT_VIEW,
@@ -30,14 +28,12 @@ import {
import { menu, breadcrumb, emptyState, openMarkdown } from "../ui"; import { menu, breadcrumb, emptyState, openMarkdown } from "../ui";
import { NameModal } from "../modals/NameModal"; import { NameModal } from "../modals/NameModal";
cytoscape.use(fcose);
export interface ProjectDetailsState extends Record<string, unknown> { export interface ProjectDetailsState extends Record<string, unknown> {
project: string; project: string;
} }
const NAME_RX = /^[^\\/:*?"<>|]+$/; const NAME_RX = /^[^\\/:*?"<>|]+$/;
const ROOT_ID = ":root:"; const ROOT_ID = "__pk_root__";
function validateName(name: string, taken: string[], current?: string): string | null { function validateName(name: string, taken: string[], current?: string): string | null {
if (!name) return "Name darf nicht leer sein."; if (!name) return "Name darf nicht leer sein.";
@@ -155,6 +151,7 @@ export class ProjectDetailsView extends ItemView {
const styles = getComputedStyle(this.containerEl); const styles = getComputedStyle(this.containerEl);
const textColor = styles.getPropertyValue("--text-normal").trim() || "#222"; const textColor = styles.getPropertyValue("--text-normal").trim() || "#222";
const borderColor = styles.getPropertyValue("--background-modifier-border").trim() || "#ccc"; const borderColor = styles.getPropertyValue("--background-modifier-border").trim() || "#ccc";
const edgeColor = styles.getPropertyValue("--text-muted").trim() || "#888";
const accent = styles.getPropertyValue("--interactive-accent").trim() || "#7b6cd9"; const accent = styles.getPropertyValue("--interactive-accent").trim() || "#7b6cd9";
const bgNode = styles.getPropertyValue("--background-primary").trim() || "#fff"; const bgNode = styles.getPropertyValue("--background-primary").trim() || "#fff";
@@ -201,28 +198,25 @@ export class ProjectDetailsView extends ItemView {
{ {
selector: "edge", selector: "edge",
style: { style: {
width: 1.5, width: 2,
"line-color": borderColor, "line-color": edgeColor,
"target-arrow-color": borderColor, "target-arrow-color": edgeColor,
"target-arrow-shape": "triangle", "target-arrow-shape": "triangle",
"curve-style": "bezier", "curve-style": "bezier",
}, },
}, },
], ],
layout: { layout: {
name: "fcose", name: "concentric",
// @ts-ignore — fcose options not in cytoscape types concentric: (node) => -((node.data("depth") as number) ?? 0),
quality: "default", levelWidth: () => 1,
randomize: true, minNodeSpacing: 60,
animate: false, // @ts-ignore — spacingFactor exists in concentric layout
spacingFactor: 1.4,
fit: true, fit: true,
padding: 30, padding: 30,
nodeRepulsion: 8000, startAngle: -Math.PI / 2,
idealEdgeLength: 100, clockwise: true,
gravity: 0.25,
fixedNodeConstraint: [
{ nodeId: ROOT_ID, position: { x: 0, y: 0 } },
],
}, },
}); });
@@ -231,15 +225,17 @@ export class ProjectDetailsView extends ItemView {
private treeToElements(tree: NodeTree): cytoscape.ElementDefinition[] { private treeToElements(tree: NodeTree): cytoscape.ElementDefinition[] {
const els: cytoscape.ElementDefinition[] = []; const els: cytoscape.ElementDefinition[] = [];
els.push({ data: { id: ROOT_ID, label: tree.name } }); els.push({ data: { id: ROOT_ID, label: tree.name, path: tree.path, depth: 0 } });
const walk = (node: NodeTree, parentId: string) => { let counter = 0;
for (const child of node.children) { const walk = (children: NodeTree[], parentId: string, depth: number) => {
els.push({ data: { id: child.path, label: child.name } }); for (const child of children) {
els.push({ data: { id: `${parentId}>${child.path}`, source: parentId, target: child.path } }); const id = `n${counter++}`;
walk(child, child.path); els.push({ data: { id, label: child.name, path: child.path, depth } });
els.push({ data: { source: parentId, target: id } });
walk(child.children, id, depth + 1);
} }
}; };
walk(tree, ROOT_ID); walk(tree.children, ROOT_ID, 1);
return els; return els;
} }
@@ -249,28 +245,27 @@ export class ProjectDetailsView extends ItemView {
cy.on("tap", "node", (ev) => { cy.on("tap", "node", (ev) => {
const node = ev.target as NodeSingular; const node = ev.target as NodeSingular;
const id = node.id(); if (node.id() === ROOT_ID) {
if (id === ROOT_ID) {
const corePath = normalizePath(`${projectPath(this.project)}/${CORE_FILE}`); const corePath = normalizePath(`${projectPath(this.project)}/${CORE_FILE}`);
void openMarkdown(this.app, corePath, this.leaf); void openMarkdown(this.app, corePath, this.leaf);
return; return;
} }
void openMarkdown(this.app, nodeMdPath(id), this.leaf); const path = node.data("path") as string;
void openMarkdown(this.app, nodeMdPath(path), this.leaf);
}); });
cy.on("cxttap", "node", (ev) => { cy.on("cxttap", "node", (ev) => {
const node = ev.target as NodeSingular; const node = ev.target as NodeSingular;
const id = node.id();
const originalEvent = ev.originalEvent as MouseEvent; const originalEvent = ev.originalEvent as MouseEvent;
originalEvent.preventDefault(); originalEvent.preventDefault();
if (id === ROOT_ID) { if (node.id() === ROOT_ID) {
const parent = projectPath(this.project); const parent = projectPath(this.project);
menu(originalEvent, [ menu(originalEvent, [
{ title: "Neuer Child-Node", icon: "plus", onClick: () => this.openCreateChild(parent) }, { title: "Neuer Child-Node", icon: "plus", onClick: () => this.openCreateChild(parent) },
]); ]);
return; return;
} }
const folder = id; const folder = node.data("path") as string;
const parentPath = folder.substring(0, folder.lastIndexOf("/")); const parentPath = folder.substring(0, folder.lastIndexOf("/"));
const currentName = folder.split("/").pop() ?? ""; const currentName = folder.split("/").pop() ?? "";
menu(originalEvent, [ menu(originalEvent, [
@@ -320,8 +315,10 @@ export class ProjectDetailsView extends ItemView {
if (start) node.position(start); if (start) node.position(start);
return; return;
} }
const sourcePath = node.id(); const sourcePath = node.data("path") as string;
const targetPath = target.id() === ROOT_ID ? projectPath(this.project) : target.id(); const targetPath = target.id() === ROOT_ID
? projectPath(this.project)
: (target.data("path") as string);
const currentParent = sourcePath.substring(0, sourcePath.lastIndexOf("/")); const currentParent = sourcePath.substring(0, sourcePath.lastIndexOf("/"));
if (currentParent === targetPath) { if (currentParent === targetPath) {
if (start) node.position(start); if (start) node.position(start);