diff --git a/package-lock.json b/package-lock.json index 2c437c5..69f0470 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,8 +10,7 @@ "license": "MIT", "dependencies": { "@types/cytoscape": "^3.21.9", - "cytoscape": "^3.33.4", - "cytoscape-fcose": "^2.2.0" + "cytoscape": "^3.33.4" }, "devDependencies": { "@types/node": "^20.11.0", @@ -502,15 +501,6 @@ "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": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", @@ -528,18 +518,6 @@ "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": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", @@ -579,12 +557,6 @@ "@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": { "version": "2.29.4", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", diff --git a/package.json b/package.json index 60bd159..bcbc94a 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,6 @@ }, "dependencies": { "@types/cytoscape": "^3.21.9", - "cytoscape": "^3.33.4", - "cytoscape-fcose": "^2.2.0" + "cytoscape": "^3.33.4" } } diff --git a/src/views/ProjectDetailsView.ts b/src/views/ProjectDetailsView.ts index 27d7b98..f7dfad6 100644 --- a/src/views/ProjectDetailsView.ts +++ b/src/views/ProjectDetailsView.ts @@ -7,8 +7,6 @@ import { normalizePath, } from "obsidian"; import cytoscape, { Core, NodeSingular } from "cytoscape"; -// @ts-ignore — no types for cytoscape-fcose -import fcose from "cytoscape-fcose"; import { VIEW_TYPE_PROJECT_DETAILS_VIEW, VIEW_TYPE_PROJECT_VIEW, @@ -30,14 +28,12 @@ import { import { menu, breadcrumb, emptyState, openMarkdown } from "../ui"; import { NameModal } from "../modals/NameModal"; -cytoscape.use(fcose); - export interface ProjectDetailsState extends Record { project: string; } const NAME_RX = /^[^\\/:*?"<>|]+$/; -const ROOT_ID = ":root:"; +const ROOT_ID = "__pk_root__"; function validateName(name: string, taken: string[], current?: string): string | null { if (!name) return "Name darf nicht leer sein."; @@ -155,6 +151,7 @@ export class ProjectDetailsView extends ItemView { const styles = getComputedStyle(this.containerEl); const textColor = styles.getPropertyValue("--text-normal").trim() || "#222"; 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 bgNode = styles.getPropertyValue("--background-primary").trim() || "#fff"; @@ -201,28 +198,25 @@ export class ProjectDetailsView extends ItemView { { selector: "edge", style: { - width: 1.5, - "line-color": borderColor, - "target-arrow-color": borderColor, + width: 2, + "line-color": edgeColor, + "target-arrow-color": edgeColor, "target-arrow-shape": "triangle", "curve-style": "bezier", }, }, ], layout: { - name: "fcose", - // @ts-ignore — fcose options not in cytoscape types - quality: "default", - randomize: true, - animate: false, + name: "concentric", + concentric: (node) => -((node.data("depth") as number) ?? 0), + levelWidth: () => 1, + minNodeSpacing: 60, + // @ts-ignore — spacingFactor exists in concentric layout + spacingFactor: 1.4, fit: true, padding: 30, - nodeRepulsion: 8000, - idealEdgeLength: 100, - gravity: 0.25, - fixedNodeConstraint: [ - { nodeId: ROOT_ID, position: { x: 0, y: 0 } }, - ], + startAngle: -Math.PI / 2, + clockwise: true, }, }); @@ -231,15 +225,17 @@ export class ProjectDetailsView extends ItemView { private treeToElements(tree: NodeTree): cytoscape.ElementDefinition[] { const els: cytoscape.ElementDefinition[] = []; - els.push({ data: { id: ROOT_ID, label: tree.name } }); - const walk = (node: NodeTree, parentId: string) => { - for (const child of node.children) { - els.push({ data: { id: child.path, label: child.name } }); - els.push({ data: { id: `${parentId}>${child.path}`, source: parentId, target: child.path } }); - walk(child, child.path); + els.push({ data: { id: ROOT_ID, label: tree.name, path: tree.path, depth: 0 } }); + let counter = 0; + const walk = (children: NodeTree[], parentId: string, depth: number) => { + for (const child of children) { + const id = `n${counter++}`; + 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; } @@ -249,28 +245,27 @@ export class ProjectDetailsView extends ItemView { cy.on("tap", "node", (ev) => { const node = ev.target as NodeSingular; - const id = node.id(); - if (id === ROOT_ID) { + if (node.id() === ROOT_ID) { const corePath = normalizePath(`${projectPath(this.project)}/${CORE_FILE}`); void openMarkdown(this.app, corePath, this.leaf); 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) => { const node = ev.target as NodeSingular; - const id = node.id(); const originalEvent = ev.originalEvent as MouseEvent; originalEvent.preventDefault(); - if (id === ROOT_ID) { + if (node.id() === ROOT_ID) { const parent = projectPath(this.project); menu(originalEvent, [ { title: "Neuer Child-Node", icon: "plus", onClick: () => this.openCreateChild(parent) }, ]); return; } - const folder = id; + const folder = node.data("path") as string; const parentPath = folder.substring(0, folder.lastIndexOf("/")); const currentName = folder.split("/").pop() ?? ""; menu(originalEvent, [ @@ -320,8 +315,10 @@ export class ProjectDetailsView extends ItemView { if (start) node.position(start); return; } - const sourcePath = node.id(); - const targetPath = target.id() === ROOT_ID ? projectPath(this.project) : target.id(); + const sourcePath = node.data("path") as string; + const targetPath = target.id() === ROOT_ID + ? projectPath(this.project) + : (target.data("path") as string); const currentParent = sourcePath.substring(0, sourcePath.lastIndexOf("/")); if (currentParent === targetPath) { if (start) node.position(start);