feat(app): minor changes in utils.js

This commit is contained in:
dextmorgn
2026-01-16 09:09:45 +01:00
parent 93b628480b
commit aa1e39b8af
2 changed files with 140 additions and 139 deletions
@@ -24,7 +24,7 @@ export const transformGraphData = ({
nodeColor: nodeColors[type] || GRAPH_COLORS.NODE_DEFAULT,
nodeSize: baseNodeSize,
nodeType: type,
val: baseNodeSize * CONSTANTS.ZOOMED_OUT_SIZE_MULTIPLIER / 5,
val: (baseNodeSize * CONSTANTS.ZOOMED_OUT_SIZE_MULTIPLIER) / 5,
neighbors: [] as any[],
links: [] as any[]
} as any
@@ -91,7 +91,6 @@ export const transformGraphData = ({
node.neighbors = Array.from(neighborsMap.get(node.id) || [])
node.links = linksMap.get(node.id) || []
})
return {
nodes: transformedNodes,
links: transformedEdges
+139 -137
View File
@@ -1,91 +1,92 @@
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
import Dagre from '@dagrejs/dagre';
import { GraphEdge, GraphNode } from '@/types';
import { FlowEdge, FlowNode } from "@/stores/flow-store";
import { forceSimulation, forceLink, forceManyBody, forceCenter } from 'd3-force';
import { clsx, type ClassValue } from 'clsx'
import { twMerge } from 'tailwind-merge'
import Dagre from '@dagrejs/dagre'
import { GraphEdge, GraphNode } from '@/types'
import { FlowEdge, FlowNode } from '@/stores/flow-store'
import { forceSimulation, forceLink, forceManyBody, forceCenter } from 'd3-force'
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
interface LayoutOptions {
direction?: "LR" | "TB";
strength?: number;
distance?: number;
iterations?: number;
dagLevelDistance?: number;
direction?: 'LR' | 'TB'
strength?: number
distance?: number
iterations?: number
dagLevelDistance?: number
}
interface ForceLayoutOptions {
width?: number;
height?: number;
chargeStrength?: number;
linkDistance?: number;
linkStrength?: number;
alphaDecay?: number;
alphaMin?: number;
velocityDecay?: number;
iterations?: number;
width?: number
height?: number
chargeStrength?: number
linkDistance?: number
linkStrength?: number
alphaDecay?: number
alphaMin?: number
velocityDecay?: number
iterations?: number
}
// dagre layout function for the main graph component.
export const getDagreLayoutedElements = (nodes: GraphNode[],
export const getDagreLayoutedElements = (
nodes: GraphNode[],
edges: GraphEdge[],
options: LayoutOptions = {
direction: "TB",
direction: 'TB',
strength: -300,
distance: 10,
iterations: 300,
dagLevelDistance: 50,
},) => {
const g = new Dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({}));
dagLevelDistance: 50
}
) => {
const g = new Dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({}))
// Use dagLevelDistance for both node and rank separation
const levelDistance = options.dagLevelDistance ?? 50;
const levelDistance = options.dagLevelDistance ?? 50
// Configure dagre with proper spacing
g.setGraph({
rankdir: options.direction,
nodesep: levelDistance, // Horizontal spacing between nodes (fixed distance)
ranksep: levelDistance, // Vertical spacing between ranks (fixed distance)
nodesep: levelDistance, // Horizontal spacing between nodes (fixed distance)
ranksep: levelDistance, // Vertical spacing between ranks (fixed distance)
marginx: 10,
marginy: 10
});
})
// Set node dimensions first - use a consistent size for all nodes
const nodeWidth = 50;
const nodeHeight = 50;
const nodeWidth = 50
const nodeHeight = 50
nodes.forEach((node) =>
g.setNode(node.id, {
width: nodeWidth,
height: nodeHeight,
}),
);
height: nodeHeight
})
)
// Then add edges
// Note: react-force-graph transforms edges so source/target become node objects
// We need to extract the IDs for Dagre
edges.forEach((edge: GraphEdge | any) => {
const source = typeof edge.source === 'object' ? edge.source.id : edge.source;
const target = typeof edge.target === 'object' ? edge.target.id : edge.target;
g.setEdge(source, target);
});
const source = typeof edge.source === 'object' ? edge.source.id : edge.source
const target = typeof edge.target === 'object' ? edge.target.id : edge.target
g.setEdge(source, target)
})
Dagre.layout(g);
Dagre.layout(g)
return {
nodes: nodes.map((node) => {
const position = g.node(node.id);
const x = position.x;
const y = position.y;
return { ...node, x, y };
const position = g.node(node.id)
const x = position.x
const y = position.y
return { ...node, x, y }
}),
edges,
};
};
edges
}
}
// force layout function for the main graph component
export const getForceLayoutedElements = (
@@ -100,7 +101,7 @@ export const getForceLayoutedElements = (
alphaDecay: 0.045,
alphaMin: 0,
velocityDecay: 0.41,
iterations: 300,
iterations: 300
}
) => {
const {
@@ -112,23 +113,23 @@ export const getForceLayoutedElements = (
alphaDecay = 0.045,
alphaMin = 0,
velocityDecay = 0.41,
iterations = 300,
} = options;
iterations = 300
} = options
// Create simulation nodes with initial random positions
const simNodes = nodes.map((node) => ({
...node,
x: node.x ?? Math.random() * width,
y: node.y ?? Math.random() * height,
}));
y: node.y ?? Math.random() * height
}))
// Create simulation links
const simLinks = edges.map((edge) => ({
// @ts-ignore
source: typeof edge.source === 'object' ? edge.source.id : edge.source,
// @ts-ignore
target: typeof edge.target === 'object' ? edge.target.id : edge.target,
}));
target: typeof edge.target === 'object' ? edge.target.id : edge.target
}))
// Create D3 force simulation
const simulation = forceSimulation(simNodes as any)
@@ -143,98 +144,95 @@ export const getForceLayoutedElements = (
.force('center', forceCenter(width / 2, height / 2))
.alphaDecay(alphaDecay)
.alphaMin(alphaMin)
.velocityDecay(velocityDecay);
.velocityDecay(velocityDecay)
// Run simulation synchronously for specified iterations
for (let i = 0; i < iterations; i++) {
simulation.tick();
simulation.tick()
}
simulation.stop();
simulation.stop()
return {
nodes: simNodes.map((node) => ({
...node,
x: node.x,
y: node.y,
y: node.y
})),
edges,
};
};
edges
}
}
// dagre layout function for the flow component.
export const getFlowDagreLayoutedElements = (nodes: FlowNode[],
export const getFlowDagreLayoutedElements = (
nodes: FlowNode[],
edges: FlowEdge[],
options: LayoutOptions = {
direction: "TB",
direction: 'TB',
strength: -300,
distance: 10,
iterations: 300,
},) => {
const g = new Dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({}));
g.setGraph({ rankdir: options.direction });
edges.forEach((edge) => g.setEdge(edge.source, edge.target));
iterations: 300
}
) => {
const g = new Dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({}))
g.setGraph({ rankdir: options.direction })
edges.forEach((edge) => g.setEdge(edge.source, edge.target))
nodes.forEach((node) =>
g.setNode(node.id, {
...node,
width: node.measured?.width ?? 0,
height: node.measured?.height ?? 0,
}),
);
Dagre.layout(g);
height: node.measured?.height ?? 0
})
)
Dagre.layout(g)
return {
nodes: nodes.map((node) => {
const position = g.node(node.id);
const x = position.x - (node.measured?.width ?? 0) / 2;
const y = position.y - (node.measured?.height ?? 0) / 2;
return { ...node, position: { x, y } };
const position = g.node(node.id)
const x = position.x - (node.measured?.width ?? 0) / 2
const y = position.y - (node.measured?.height ?? 0) / 2
return { ...node, position: { x, y } }
}),
edges,
};
};
edges
}
}
export const sanitize = (name: string) => {
return name
.normalize("NFKD") // Decompose special characters (e.g., accents)
.replace(/[\u0300-\u036f]/g, "") // Remove accent marks
.replace(/[^a-zA-Z0-9.\-_]/g, "_") // Replace invalid characters with underscores
.replace(/_{2,}/g, "_") // Replace multiple underscores with a single one
.replace(/^_|_$/g, "") // Trim leading or trailing underscores
.toLowerCase(); // Convert to lowercase for consistency
};
export const formatFileSize = (bytes: number) => {
if (bytes < 1024) return bytes + " B"
else if (bytes < 1048576) return (bytes / 1024).toFixed(1) + " KB"
else return (bytes / 1048576).toFixed(1) + " MB"
.normalize('NFKD') // Decompose special characters (e.g., accents)
.replace(/[\u0300-\u036f]/g, '') // Remove accent marks
.replace(/[^a-zA-Z0-9.\-_]/g, '_') // Replace invalid characters with underscores
.replace(/_{2,}/g, '_') // Replace multiple underscores with a single one
.replace(/^_|_$/g, '') // Trim leading or trailing underscores
.toLowerCase() // Convert to lowercase for consistency
}
export const formatFileSize = (bytes: number) => {
if (bytes < 1024) return bytes + ' B'
else if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB'
else return (bytes / 1048576).toFixed(1) + ' MB'
}
export const getInitials = (name: string) => {
return name
.split(" ")
.split(' ')
.map((part) => part[0])
.join("")
.join('')
.toUpperCase()
.substring(0, 2)
}
export function getAvatarColor(name: string): string {
const colors = [
"bg-red-500",
"bg-blue-500",
"bg-green-500",
"bg-yellow-500",
"bg-purple-500",
"bg-pink-500",
"bg-indigo-500",
"bg-teal-500",
"bg-orange-500",
"bg-cyan-500",
'bg-red-500',
'bg-blue-500',
'bg-green-500',
'bg-yellow-500',
'bg-purple-500',
'bg-pink-500',
'bg-indigo-500',
'bg-teal-500',
'bg-orange-500',
'bg-cyan-500'
]
let hash = 0
for (let i = 0; i < name.length; i++) {
@@ -244,43 +242,42 @@ export function getAvatarColor(name: string): string {
return colors[index]
}
export const flattenObj = (ob: Record<string, any>) => {
let result: Record<string, any> = {};
let result: Record<string, any> = {}
for (const i in ob) {
if (ob[i] && typeof ob[i] === 'object' && !Array.isArray(ob[i])) {
const temp = flattenObj(ob[i]);
const temp = flattenObj(ob[i])
for (const j in temp) {
result[i + '.' + j] = temp[j];
result[i + '.' + j] = temp[j]
}
}
else {
result[i] = ob[i];
} else {
result[i] = ob[i]
}
}
return result;
return result
}
interface FlattenableNode {
[key: string]: unknown;
children?: FlattenableNode[];
[key: string]: unknown
children?: FlattenableNode[]
}
export function flattenArray<T extends FlattenableNode>(nodes: T[], key: string): Omit<T, typeof key>[] {
let result: Omit<T, typeof key>[] = [];
export function flattenArray<T extends FlattenableNode>(
nodes: T[],
key: string
): Omit<T, typeof key>[] {
let result: Omit<T, typeof key>[] = []
for (const node of nodes) {
const { [key]: children, ...rest } = node;
result.push(rest as Omit<T, typeof key>);
const { [key]: children, ...rest } = node
result.push(rest as Omit<T, typeof key>)
if (children) {
result = result.concat(flattenArray(children as T[], key));
result = result.concat(flattenArray(children as T[], key))
}
}
return result;
return result
}
export function hexToRgba(hex: string, alpha: number) {
const r = parseInt(hex.slice(1, 3), 16)
const g = parseInt(hex.slice(3, 5), 16)
@@ -288,15 +285,13 @@ export function hexToRgba(hex: string, alpha: number) {
return `rgba(${r}, ${g}, ${b}, ${alpha})`
}
export function capitalizeFirstLetter(string: string) {
return string.charAt(0).toUpperCase() + string.slice(1)
}
export const getAllNodeTypes = (actionItems: any[]) => {
const types: string[] = []
actionItems.forEach(item => {
actionItems.forEach((item) => {
if (item.children) {
item.children.forEach((child: GraphNode) => {
if (child.data.type && !types.includes(child.data.type)) {
@@ -312,29 +307,36 @@ export const getAllNodeTypes = (actionItems: any[]) => {
export const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0
interface Dictionary {
[Key: string]: any;
[Key: string]: any
}
export function deepObjectDiff(obj1: Dictionary, obj2: Dictionary): Dictionary {
let diffObject = {}
// We want object 2 to be compared against object 1
if (typeof obj1 != "object" || typeof obj2 != "object") throw Error("Items to compare mustr be objects.")
if (typeof obj1 != 'object' || typeof obj2 != 'object')
throw Error('Items to compare mustr be objects.')
// We map over the obj2 key:value duos to retrieve new keys that obj2 might have
Object.entries(obj2).forEach(([key, value]) => {
// We check for additional keys
if (!obj1.hasOwnProperty(key))
diffObject = { ...diffObject, [key]: { value, new: true } }
if (!obj1.hasOwnProperty(key)) diffObject = { ...diffObject, [key]: { value, new: true } }
else {
diffObject = { ...diffObject, [key]: { value, new: false, oldValue: obj1[key] ?? null, newValue: obj2[key] ?? null, identical: obj2[key] === obj1[key] } }
diffObject = {
...diffObject,
[key]: {
value,
new: false,
oldValue: obj1[key] ?? null,
newValue: obj2[key] ?? null,
identical: obj2[key] === obj1[key]
}
}
}
})
// We map over the obj1 key:value duos to retrieve keys that might have disapeared
Object.entries(obj1).forEach(([key, value]) => {
// We check for additional keys
if (!obj2.hasOwnProperty(key))
diffObject = { ...diffObject, [key]: { value, removed: true } }
if (!obj2.hasOwnProperty(key)) diffObject = { ...diffObject, [key]: { value, removed: true } }
})
return diffObject
}
}