pkgviz 0.7.1 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/BUILD_ID +1 -1
- package/.next/build-manifest.json +2 -2
- package/.next/cache/webpack/client-production/4.pack +0 -0
- package/.next/cache/webpack/client-production/5.pack +0 -0
- package/.next/cache/webpack/client-production/6.pack +0 -0
- package/.next/cache/webpack/client-production/7.pack +0 -0
- package/.next/cache/webpack/client-production/8.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack.old +0 -0
- package/.next/cache/webpack/server-production/10.pack +0 -0
- package/.next/cache/webpack/server-production/11.pack +0 -0
- package/.next/cache/webpack/server-production/12.pack +0 -0
- package/.next/cache/webpack/server-production/13.pack +0 -0
- package/.next/cache/webpack/server-production/14.pack +0 -0
- package/.next/cache/webpack/server-production/index.pack +0 -0
- package/.next/cache/webpack/server-production/index.pack.old +0 -0
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +1 -1
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +1 -1
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/trace +2 -2
- package/package.json +2 -2
- package/src/app/actions/audit.actions.ts +71 -0
- package/src/app/actions/graph.actions.ts +25 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +77 -0
- package/src/app/layout.tsx +30 -0
- package/src/app/page.tsx +5 -0
- package/src/app/utils/buildGraph.ts +119 -0
- package/src/app/utils/getParsedFileStructure.ts +225 -0
- package/src/app/utils/markCyclicPackages.ts +275 -0
- package/src/app/utils/parser/cpp/extractCppPackageFromImport.ts +18 -0
- package/src/app/utils/parser/cpp/parseCppFile.ts +150 -0
- package/src/app/utils/parser/delphi/extractPackageFromImport.ts +21 -0
- package/src/app/utils/parser/delphi/parseFile.ts +179 -0
- package/src/app/utils/parser/java/extractJavaPackageFromImport.ts +39 -0
- package/src/app/utils/parser/java/findEntryPoint.ts +24 -0
- package/src/app/utils/parser/java/getIntrinsicPackagesRecursive.ts +33 -0
- package/src/app/utils/parser/java/parseJavaFile.ts +114 -0
- package/src/app/utils/parser/kotlin/extractPackageFromImport.ts +19 -0
- package/src/app/utils/parser/kotlin/parseFile.ts +147 -0
- package/src/app/utils/parser/python/extractPythonPackageFromImport.ts +18 -0
- package/src/app/utils/parser/python/parseFile.ts +171 -0
- package/src/app/utils/parser/typescript/extractTypeScriptPackageFromImport.ts +18 -0
- package/src/app/utils/parser/typescript/parseFile.ts +130 -0
- package/src/components/Breadcrumb.tsx +34 -0
- package/src/components/Cytoscape.tsx +23 -0
- package/src/components/Header.tsx +28 -0
- package/src/components/Loader.tsx +10 -0
- package/src/components/Setting.tsx +17 -0
- package/src/components/Settings.tsx +189 -0
- package/src/components/Switch.tsx +31 -0
- package/src/components/ThemeToggle.tsx +25 -0
- package/src/components/ZoomInput.tsx +94 -0
- package/src/components/useCytoscape.ts +343 -0
- package/src/contexts/SettingsContext.tsx +88 -0
- package/src/i18n/en.ts +27 -0
- package/src/i18n/i18n.ts +12 -0
- package/src/layouts/breadthfirst/layout.ts +30 -0
- package/src/layouts/breadthfirst/style.ts +8 -0
- package/src/layouts/circle/layout.ts +11 -0
- package/src/layouts/circle/style.ts +18 -0
- package/src/layouts/concentric/layout.ts +10 -0
- package/src/layouts/concentric/style.ts +16 -0
- package/src/layouts/constants.ts +17 -0
- package/src/layouts/elk/layout.ts +55 -0
- package/src/layouts/elk/style.ts +14 -0
- package/src/layouts/getLayoutStyle.ts +19 -0
- package/src/layouts/getWeightBuckets.ts +58 -0
- package/src/layouts/grid/layout.ts +11 -0
- package/src/layouts/grid/style.ts +20 -0
- package/src/layouts/index.ts +14 -0
- package/src/layouts/style.ts +191 -0
- package/src/screens/home/Home.tsx +48 -0
- package/src/shared/constants/index.ts +7 -0
- package/src/shared/types/index.ts +68 -0
- package/src/shared/utils/detectLanguage.ts +255 -0
- package/src/shared/utils/getJsonAsync.ts +13 -0
- package/src/shared/utils/getProjectName.ts +3 -0
- package/src/shared/utils/parseEnv.ts +91 -0
- package/src/shared/utils/parseProjectPath.ts +8 -0
- package/src/store/useLocalStorage.ts +29 -0
- package/src/utils/filter/filterByPackagePrefix.ts +23 -0
- package/src/utils/filter/filterEmptyPackages.ts +36 -0
- package/src/utils/filter/filterSubPackagesFromDepth.ts +170 -0
- package/src/utils/filter/filterVendorPackages.ts +17 -0
- package/src/utils/filter/toggleCompoundNodes.ts +40 -0
- package/src/utils/hasChildren.ts +7 -0
- /package/.next/static/{oEEpBF0R0rk0kWAKiKYbp → cGbLRcmdy7k8HGus5vhzm}/_buildManifest.js +0 -0
- /package/.next/static/{oEEpBF0R0rk0kWAKiKYbp → cGbLRcmdy7k8HGus5vhzm}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parses an environment variable value into the appropriate type.
|
|
3
|
+
* @example 'true' => true
|
|
4
|
+
* @example 'false' => false
|
|
5
|
+
* @example '123' => 123
|
|
6
|
+
* @example 'text' => 'text'
|
|
7
|
+
*/
|
|
8
|
+
export const parseEnv = (name: string, value: string | undefined) => {
|
|
9
|
+
if (!value) return undefined;
|
|
10
|
+
if (value === 'true') return true;
|
|
11
|
+
if (value === 'false') return false;
|
|
12
|
+
if (!isNaN(Number(value))) return Number(value);
|
|
13
|
+
return value;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Determines the active Cytoscape layout
|
|
18
|
+
* @default 'concentric'
|
|
19
|
+
* @todo Create enum for the layouts
|
|
20
|
+
* @returns 'grid' | 'circle' | 'elk' | 'concentric'
|
|
21
|
+
*/
|
|
22
|
+
export const getCytoscapeLayout = () => {
|
|
23
|
+
const env = parseEnv('NEXT_PUBLIC_SETTINGS_LAYOUT', process.env.NEXT_PUBLIC_SETTINGS_LAYOUT);
|
|
24
|
+
return env === 'grid'
|
|
25
|
+
? 'grid'
|
|
26
|
+
: env === 'circle'
|
|
27
|
+
? 'circle'
|
|
28
|
+
: env === 'elk'
|
|
29
|
+
? 'elk'
|
|
30
|
+
: 'concentric';
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Determines the active Cytoscape layout spacing.
|
|
35
|
+
* @returns number
|
|
36
|
+
* @default 1
|
|
37
|
+
* @min 0.1
|
|
38
|
+
* @max 1
|
|
39
|
+
*/
|
|
40
|
+
export const getCytoscapeLayoutSpacing = () => {
|
|
41
|
+
const env = parseEnv(
|
|
42
|
+
'NEXT_PUBLIC_SETTINGS_LAYOUT_SPACING',
|
|
43
|
+
process.env.NEXT_PUBLIC_SETTINGS_LAYOUT_SPACING
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
return isNaN(Number(env)) ? 1 : Number(env);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Determines whether to show compound nodes.
|
|
51
|
+
* @default true
|
|
52
|
+
* @returns boolean
|
|
53
|
+
*/
|
|
54
|
+
export const getShowCompoundNodes = () => {
|
|
55
|
+
const env = parseEnv(
|
|
56
|
+
'NEXT_PUBLIC_SETTINGS_SHOW_COMPOUNDNODES',
|
|
57
|
+
process.env.NEXT_PUBLIC_SETTINGS_SHOW_COMPOUNDNODES
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
return env === false ? false : true;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Determines whether to show vendor packages.
|
|
65
|
+
* @default false
|
|
66
|
+
* @returns boolean
|
|
67
|
+
*/
|
|
68
|
+
export const getShowVendorPackages = () => {
|
|
69
|
+
const env = parseEnv(
|
|
70
|
+
'NEXT_PUBLIC_SETTINGS_SHOW_VENDORPACKAGES',
|
|
71
|
+
process.env.NEXT_PUBLIC_SETTINGS_SHOW_VENDORPACKAGES
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
return env === true ? true : false;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Determines how many subpackage levels to show.
|
|
79
|
+
* @default 1
|
|
80
|
+
* @min 1
|
|
81
|
+
* @max {automatic based on package depth}
|
|
82
|
+
* @returns number
|
|
83
|
+
*/
|
|
84
|
+
export const getSubPackageDepth = () => {
|
|
85
|
+
const env = parseEnv(
|
|
86
|
+
'NEXT_PUBLIC_SETTINGS_SUBPACKAGE_DEPTH',
|
|
87
|
+
process.env.NEXT_PUBLIC_SETTINGS_SUBPACKAGE_DEPTH
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
return typeof env === 'string' && env.length ? parseInt(env) : 1;
|
|
91
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { toPosix } from '@/shared/utils/toPosix';
|
|
2
|
+
|
|
3
|
+
export const parseProjectPath = () => {
|
|
4
|
+
const projectPath = process.env.NEXT_PUBLIC_PROJECT_PATH;
|
|
5
|
+
if (!projectPath) throw new Error('Missing ENV: NEXT_PUBLIC_PROJECT_PATH');
|
|
6
|
+
|
|
7
|
+
return toPosix(projectPath);
|
|
8
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
|
|
3
|
+
export function useLocalStorage<T>(key: string, initialValue: T) {
|
|
4
|
+
const [storedValue, setStoredValue] = useState<T>(() => {
|
|
5
|
+
if (typeof window === 'undefined') return initialValue;
|
|
6
|
+
|
|
7
|
+
try {
|
|
8
|
+
const item = window.localStorage.getItem(key);
|
|
9
|
+
return item ? (JSON.parse(item) as T) : initialValue;
|
|
10
|
+
} catch (error) {
|
|
11
|
+
console.warn(`Error reading localStorage key "${key}":`, error);
|
|
12
|
+
return initialValue;
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const setValue = (value: T | ((val: T) => T)) => {
|
|
17
|
+
try {
|
|
18
|
+
const valueToStore = value instanceof Function ? value(storedValue) : value;
|
|
19
|
+
setStoredValue(valueToStore);
|
|
20
|
+
if (typeof window !== 'undefined') {
|
|
21
|
+
window.localStorage.setItem(key, JSON.stringify(valueToStore));
|
|
22
|
+
}
|
|
23
|
+
} catch (error) {
|
|
24
|
+
console.warn(`Error setting localStorage key "${key}":`, error);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
return [storedValue, setValue] as const;
|
|
29
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function filterByPackagePrefix(
|
|
2
|
+
allElements: cytoscape.ElementsDefinition,
|
|
3
|
+
packagePrefix: string
|
|
4
|
+
): cytoscape.ElementsDefinition {
|
|
5
|
+
// PackageView entrypoint, no prefix
|
|
6
|
+
if (!packagePrefix) return allElements;
|
|
7
|
+
|
|
8
|
+
// Active filtering (subpackage view)
|
|
9
|
+
const pkgPrefix = packagePrefix.endsWith('.') ? packagePrefix : packagePrefix + '.';
|
|
10
|
+
const allowedNodes = allElements.nodes.filter(node => {
|
|
11
|
+
return (node.data.id as string).startsWith(pkgPrefix);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const allowedNodeIds = new Set(allowedNodes.map(node => node.data.id));
|
|
15
|
+
const allowedEdges = allElements.edges.filter(
|
|
16
|
+
edge => allowedNodeIds.has(edge.data.source) && allowedNodeIds.has(edge.data.target)
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
nodes: allowedNodes,
|
|
21
|
+
edges: allowedEdges,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { ElementsDefinition } from 'cytoscape';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns root package if only one inside, or '' (root folder)
|
|
5
|
+
*/
|
|
6
|
+
function getRootPackage(elements: ElementsDefinition) {
|
|
7
|
+
const rootPackages = elements.nodes.filter(n => {
|
|
8
|
+
return !n.data.id?.includes('.');
|
|
9
|
+
});
|
|
10
|
+
return rootPackages.length > 1 ? '' : (rootPackages[0].data.id as string);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Filter empty packages from graph (skip to next interesting package)
|
|
15
|
+
*/
|
|
16
|
+
export function filterEmptyPackages(currentPackage: string, elements: ElementsDefinition): string {
|
|
17
|
+
const nodes = elements.nodes.map(n => n.data.id!); // Non-null assertion
|
|
18
|
+
|
|
19
|
+
if (!currentPackage) return getRootPackage(elements);
|
|
20
|
+
|
|
21
|
+
while (true) {
|
|
22
|
+
const childPackages = nodes.filter(
|
|
23
|
+
id =>
|
|
24
|
+
id.startsWith(currentPackage + '.') &&
|
|
25
|
+
id.split('.').length === currentPackage.split('.').length + 1
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
if (childPackages.length === 1) {
|
|
29
|
+
currentPackage = childPackages[0];
|
|
30
|
+
} else {
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return currentPackage;
|
|
36
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import type { EdgeDefinition, ElementsDefinition } from 'cytoscape';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Compute the minimal set of "root" packages that have no ancestor in the list.
|
|
5
|
+
* Example: ['x.y', 'x.y.z', 'a', 'a.c.x'] => ['a', 'x.y']
|
|
6
|
+
*/
|
|
7
|
+
function findRoots(packages: string[]): string[] {
|
|
8
|
+
const sorted = [...packages].sort(); // parents before children (lexicographically close enough for dotted paths)
|
|
9
|
+
const roots: string[] = [];
|
|
10
|
+
|
|
11
|
+
for (const pkg of sorted) {
|
|
12
|
+
if (!roots.some(parent => pkg.startsWith(parent + '.'))) {
|
|
13
|
+
roots.push(pkg);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return roots;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Ascend from a package to its nearest ancestor that is in the `roots` set.
|
|
21
|
+
*/
|
|
22
|
+
function nearestRoot(pkg: string, roots: Set<string>): string {
|
|
23
|
+
if (roots.has(pkg)) return pkg;
|
|
24
|
+
const parts = pkg.split('.');
|
|
25
|
+
while (parts.length > 1) {
|
|
26
|
+
parts.pop();
|
|
27
|
+
const ancestor = parts.join('.');
|
|
28
|
+
if (roots.has(ancestor)) return ancestor;
|
|
29
|
+
}
|
|
30
|
+
// Fallback: if nothing matched (shouldn't happen if roots are computed from the same list)
|
|
31
|
+
return pkg;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Given a root and a leaf pkg, return the visible ancestor at `maxDepth`
|
|
36
|
+
* under the root, preferring the deepest existing package <= target depth.
|
|
37
|
+
*
|
|
38
|
+
* Example:
|
|
39
|
+
* root = 'a', pkg = 'a.b.c.d', maxDepth=2 → tries 'a.b' (if exists), else falls back to 'a'
|
|
40
|
+
*/
|
|
41
|
+
function visibleAncestorForDepth(
|
|
42
|
+
root: string,
|
|
43
|
+
pkg: string,
|
|
44
|
+
maxDepth: number,
|
|
45
|
+
existing: Set<string>
|
|
46
|
+
): string {
|
|
47
|
+
// Depth is counted as: root = depth 1; root.child = depth 2; etc.
|
|
48
|
+
if (maxDepth <= 1) return root;
|
|
49
|
+
|
|
50
|
+
const rootDepth = root.split('.').length;
|
|
51
|
+
const pkgParts = pkg.split('.');
|
|
52
|
+
const desiredDepth = Math.min(rootDepth + (maxDepth - 1), pkgParts.length);
|
|
53
|
+
|
|
54
|
+
// Walk downwards from desiredDepth to rootDepth until we find an existing node
|
|
55
|
+
for (let d = desiredDepth; d >= rootDepth; d--) {
|
|
56
|
+
const candidate = pkgParts.slice(0, d).join('.');
|
|
57
|
+
if (existing.has(candidate)) return candidate;
|
|
58
|
+
}
|
|
59
|
+
return root; // fallback
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Global maximum depth across all roots.
|
|
64
|
+
* Depth is counted relative to the nearest root: root=1, root.child=2, etc.
|
|
65
|
+
*/
|
|
66
|
+
export function getMaxDepth(elements: ElementsDefinition): number {
|
|
67
|
+
const pkgs = elements.nodes.map(n => String(n.data.id));
|
|
68
|
+
if (pkgs.length === 0) return 0;
|
|
69
|
+
|
|
70
|
+
const roots = findRoots(pkgs);
|
|
71
|
+
const rootSet = new Set(roots);
|
|
72
|
+
|
|
73
|
+
let maxDepth = 1;
|
|
74
|
+
for (const pkg of pkgs) {
|
|
75
|
+
const root = nearestRoot(pkg, rootSet);
|
|
76
|
+
const depth = pkg.split('.').length - root.split('.').length + 1; // root=1
|
|
77
|
+
if (depth > maxDepth) maxDepth = depth;
|
|
78
|
+
}
|
|
79
|
+
return maxDepth;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Maximum depth per root, e.g. { 'a': 3, 'x.y': 2 }.
|
|
84
|
+
*/
|
|
85
|
+
export function getMaxDepthByRoot(elements: ElementsDefinition): Record<string, number> {
|
|
86
|
+
const pkgs = elements.nodes.map(n => String(n.data.id));
|
|
87
|
+
const roots = findRoots(pkgs);
|
|
88
|
+
const rootSet = new Set(roots);
|
|
89
|
+
|
|
90
|
+
const result: Record<string, number> = Object.fromEntries(roots.map(r => [r, 1]));
|
|
91
|
+
for (const pkg of pkgs) {
|
|
92
|
+
const root = nearestRoot(pkg, rootSet);
|
|
93
|
+
const depth = pkg.split('.').length - root.split('.').length + 1;
|
|
94
|
+
if (depth > result[root]) result[root] = depth;
|
|
95
|
+
}
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Filters sub-packages so only the desired number of levels under each root are shown.
|
|
101
|
+
*
|
|
102
|
+
* @param elements - Cytoscape elements
|
|
103
|
+
* @param allowSelfLoops - Keep self loops after lifting/aggregation
|
|
104
|
+
* @param maxDepth - Number of levels to keep under each root (1 = roots only, 2 = +children, etc.)
|
|
105
|
+
*/
|
|
106
|
+
export function filterSubPackagesByDepth(
|
|
107
|
+
elements: ElementsDefinition,
|
|
108
|
+
allowSelfLoops: boolean = false,
|
|
109
|
+
maxDepth: number = 1
|
|
110
|
+
): ElementsDefinition {
|
|
111
|
+
const allPackages = elements.nodes.map(n => n.data.id as string);
|
|
112
|
+
const existingSet = new Set(allPackages);
|
|
113
|
+
|
|
114
|
+
// 1) Identify minimal roots (no ancestor present)
|
|
115
|
+
const roots = findRoots(allPackages);
|
|
116
|
+
const rootSet = new Set(roots);
|
|
117
|
+
|
|
118
|
+
// 2) Map every package to its visible ancestor according to maxDepth
|
|
119
|
+
const pkgToVisible = new Map<string, string>();
|
|
120
|
+
for (const pkg of allPackages) {
|
|
121
|
+
const root = nearestRoot(pkg, rootSet);
|
|
122
|
+
const visible = visibleAncestorForDepth(root, pkg, maxDepth, existingSet);
|
|
123
|
+
pkgToVisible.set(pkg, visible);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 3) Visible nodes = unique set of chosen ancestors
|
|
127
|
+
const visibleNodeIds = new Set<string>(pkgToVisible.values());
|
|
128
|
+
const filteredNodes = elements.nodes.filter(n => visibleNodeIds.has(n.data.id as string));
|
|
129
|
+
|
|
130
|
+
// 4) Lift & aggregate edges to visible ancestors (sum weights)
|
|
131
|
+
const edgeMap = new Map<string, EdgeDefinition>();
|
|
132
|
+
|
|
133
|
+
for (const e of elements.edges) {
|
|
134
|
+
const rawSource = e.data.source as string;
|
|
135
|
+
const rawTarget = e.data.target as string;
|
|
136
|
+
|
|
137
|
+
const liftedSource = pkgToVisible.get(rawSource) ?? rawSource;
|
|
138
|
+
const liftedTarget = pkgToVisible.get(rawTarget) ?? rawTarget;
|
|
139
|
+
|
|
140
|
+
if (!visibleNodeIds.has(liftedSource) || !visibleNodeIds.has(liftedTarget)) {
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (!allowSelfLoops && liftedSource === liftedTarget) {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const key = `${liftedSource}->${liftedTarget}`;
|
|
148
|
+
if (!edgeMap.has(key)) {
|
|
149
|
+
// clone with lifted endpoints; keep your metadata; (optionally set a stable id)
|
|
150
|
+
edgeMap.set(key, {
|
|
151
|
+
...e,
|
|
152
|
+
data: {
|
|
153
|
+
...e.data,
|
|
154
|
+
// id: key, // uncomment if you want stable, deduped edge ids
|
|
155
|
+
source: liftedSource,
|
|
156
|
+
target: liftedTarget,
|
|
157
|
+
// initialize aggregated weight
|
|
158
|
+
weight: (e.data.weight as number | undefined) ?? 1,
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
} else {
|
|
162
|
+
const existing = edgeMap.get(key)!;
|
|
163
|
+
const add = (e.data.weight as number | undefined) ?? 1;
|
|
164
|
+
existing.data.weight = ((existing.data.weight as number | undefined) ?? 0) + add;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const filteredEdges = Array.from(edgeMap.values());
|
|
169
|
+
return { nodes: filteredNodes, edges: filteredEdges };
|
|
170
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ElementsDefinition } from 'cytoscape';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Filters vendor packages by known package prefix
|
|
5
|
+
*/
|
|
6
|
+
export function filterVendorPackages(elements: ElementsDefinition): ElementsDefinition {
|
|
7
|
+
const filteredNodes = elements.nodes.filter(node => !!node.data.isIntrinsic);
|
|
8
|
+
const allowedNodeIds = new Set(filteredNodes.map(node => node.data.id));
|
|
9
|
+
const filteredEdges = elements.edges.filter(
|
|
10
|
+
edge => allowedNodeIds.has(edge.data.source) && allowedNodeIds.has(edge.data.target)
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
nodes: filteredNodes,
|
|
15
|
+
edges: filteredEdges,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ElementsDefinition } from 'cytoscape';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Toggles display of compound nodes ('parent' attribute) in the node
|
|
5
|
+
* @param elements - Cytoscape elements (nodes and edges)
|
|
6
|
+
* @param show - Whether to show compound nodes
|
|
7
|
+
* @param currentPackage - Current package path to adjust node names when showing compound nodes
|
|
8
|
+
* @returns Updated Cytoscape elements with compound nodes toggled
|
|
9
|
+
*/
|
|
10
|
+
export function toggleCompoundNodes(
|
|
11
|
+
{ nodes, edges }: ElementsDefinition,
|
|
12
|
+
show: boolean,
|
|
13
|
+
currentPackage: string
|
|
14
|
+
): ElementsDefinition {
|
|
15
|
+
const updatedNodes = nodes.map(node => {
|
|
16
|
+
if (!node.data.idInactive) node.data.idInactive = node.data.id;
|
|
17
|
+
// ACTIVE Compound Nodes
|
|
18
|
+
if (show && node.data.parentInactive) {
|
|
19
|
+
node.data.parent = node.data.parentInactive;
|
|
20
|
+
node.data.parentInactive = undefined;
|
|
21
|
+
} else if (!show && node.data.parent) {
|
|
22
|
+
node.data.parentInactive = node.data.parent;
|
|
23
|
+
node.data.parent = undefined;
|
|
24
|
+
}
|
|
25
|
+
return node;
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const labelledNodes = updatedNodes.map(node => {
|
|
29
|
+
if (show) {
|
|
30
|
+
node.data.name = node.data.idInactive.split('.').pop();
|
|
31
|
+
} else {
|
|
32
|
+
node.data.name = node.data.idInactive.slice(
|
|
33
|
+
currentPackage.length ? currentPackage.length + 1 : 0
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
return node;
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
return { nodes: labelledNodes, edges };
|
|
40
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { NodeDataDefinition, NodeDefinition } from 'cytoscape';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Checks if a node (package) has children based on id prefix.
|
|
5
|
+
*/
|
|
6
|
+
export const hasChildren = (node: NodeDefinition, nodes: NodeDataDefinition[]) =>
|
|
7
|
+
nodes.some(n => n.data.id?.startsWith(`${node.data.id}.`));
|
|
File without changes
|
|
File without changes
|