class GraphOperationQueue {
constructor() {
this.queue = [];
this.isProcessing = false;
this.currentOperation = null;
}
enqueue(name, operation, options = {}) {
const { cancelPending = false, timeout = 5000 } = options;
if (cancelPending) {
this.queue = this.queue.filter(op => op.name !== name);
}
return new Promise((resolve, reject) => {
this.queue.push({
name,
operation,
timeout,
resolve,
reject,
timestamp: Date.now()
});
this._processNext();
});
}
async _processNext() {
if (this.isProcessing || this.queue.length === 0) {
return;
}
this.isProcessing = true;
this.currentOperation = this.queue.shift();
const { name, operation, timeout, resolve, reject } = this.currentOperation;
try {
const timeoutPromise = new Promise((_, timeoutReject) => {
setTimeout(() => {
timeoutReject(new Error(`Operation '${name}' timed out after ${timeout}ms`));
}, timeout);
});
const result = await Promise.race([
Promise.resolve(operation()),
timeoutPromise
]);
resolve(result);
} catch (error) {
console.warn(`Graph operation '${name}' failed:`, error);
reject(error);
} finally {
this.isProcessing = false;
this.currentOperation = null;
if (this.queue.length > 0) {
requestAnimationFrame(() => this._processNext());
}
}
}
clear() {
this.queue.forEach(op => {
op.reject(new Error('Operation cancelled'));
});
this.queue = [];
}
getStatus() {
return {
pending: this.queue.length,
processing: this.isProcessing,
currentOperation: this.currentOperation?.name || null
};
}
}
export const graphQueue = new GraphOperationQueue();
export function runLayoutAsync(cy, layoutConfig) {
return new Promise((resolve) => {
if (!cy) {
resolve();
return;
}
const layout = cy.layout({
...layoutConfig,
ready: () => {},
stop: () => resolve()
});
layout.run();
const animDuration = layoutConfig.animationDuration || 500;
setTimeout(resolve, animDuration + 100);
});
}
export function debounce(fn, delay) {
let timeoutId;
return function (...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn.apply(this, args), delay);
};
}