LLM-test/test/code/1/deepseel-cc/js/progressCircles.js

416 lines
12 KiB
JavaScript
Raw Permalink Normal View History

/* ==========================================================================
SYSTEM AWAKENING - Progress Circles Module
Canvas-based animated progress rings for system metrics
========================================================================== */
import {
COLORS,
METRICS_CONFIG,
ANIMATION,
generateRandomTargets,
getRandomValue
} from './config.js';
/**
* Progress Circles Class
* Manages the animated progress rings for the data core section
*/
export class ProgressCircles {
/**
* Create a new progress circles manager
* @param {Array} canvases - Array of canvas elements
*/
constructor(canvases) {
this.canvases = canvases;
this.ctxs = canvases.map(canvas => canvas.getContext('2d'));
// Animation state
this.currentValues = [0, 0, 0];
this.targetValues = generateRandomTargets();
this.animationStartTime = null;
this.isAnimating = false;
this.animationId = null;
// Performance tracking
this.lastFrameTime = 0;
this.fps = 60;
// Initialize
this.init();
}
/**
* Initialize the progress circles
*/
init() {
// Set up canvas dimensions
this.resize();
// Draw initial state
this.drawAll();
// Start animation after a short delay
setTimeout(() => {
this.animateToTargets();
}, 1000);
// Bind resize event
this.bindResize();
console.log('Progress circles initialized');
}
/**
* Resize all canvases to match display size
*/
resize() {
this.canvases.forEach((canvas, index) => {
const displayWidth = canvas.clientWidth;
const displayHeight = canvas.clientHeight;
// Check if canvas needs resizing
if (canvas.width !== displayWidth || canvas.height !== displayHeight) {
canvas.width = displayWidth;
canvas.height = displayHeight;
// Redraw this circle
this.drawRing(this.ctxs[index], this.currentValues[index], index);
}
});
}
/**
* Bind resize event listener
*/
bindResize() {
let resizeTimeout;
const handleResize = () => {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(() => {
this.resize();
}, 250);
};
window.addEventListener('resize', handleResize);
}
/**
* Animate all circles to their target values
* @param {number} duration - Animation duration in milliseconds
*/
animateToTargets(duration = ANIMATION.PROGRESS_DURATION) {
if (this.isAnimating) {
return;
}
this.isAnimating = true;
this.animationStartTime = Date.now();
const animate = (currentTime) => {
if (!this.isAnimating) return;
// Calculate progress (0 to 1)
const elapsed = currentTime - this.animationStartTime;
let progress = Math.min(elapsed / duration, 1);
// Apply easing function (easeOutCubic)
progress = 1 - Math.pow(1 - progress, 3);
// Update current values
this.currentValues = this.currentValues.map((current, index) => {
const target = this.targetValues[index];
return current + (target - current) * progress;
});
// Draw all circles
this.drawAll();
// Update metric displays
this.updateMetricDisplays();
if (progress < 1) {
// Continue animation
this.animationId = requestAnimationFrame(() => {
animate(Date.now());
});
} else {
// Animation complete
this.animationComplete();
}
};
// Start animation
this.animationId = requestAnimationFrame(() => {
animate(Date.now());
});
}
/**
* Handle animation completion
*/
animationComplete() {
this.isAnimating = false;
this.animationId = null;
// Update status to "complete"
this.updateStatusDisplays(METRICS_CONFIG.COMPLETE_STATUSES);
// Generate new random targets after delay
setTimeout(() => {
this.targetValues = generateRandomTargets();
// Reset status to "initializing"
this.updateStatusDisplays(METRICS_CONFIG.STATUSES);
// Start new animation
this.animateToTargets();
}, ANIMATION.PROGRESS_UPDATE_DELAY);
}
/**
* Draw all progress rings
*/
drawAll() {
this.ctxs.forEach((ctx, index) => {
this.drawRing(ctx, this.currentValues[index], index);
});
}
/**
* Draw a single progress ring
* @param {CanvasRenderingContext2D} ctx - Canvas context
* @param {number} value - Current value (0-100)
* @param {number} index - Circle index (0=compute, 1=storage, 2=sync)
*/
drawRing(ctx, value, index) {
const canvas = ctx.canvas;
const width = canvas.width;
const height = canvas.height;
const centerX = width / 2;
const centerY = height / 2;
// Calculate radius based on canvas size
const radius = Math.min(width, height) * 0.35;
const lineWidth = Math.min(width, height) * 0.05;
// Clear canvas with transparency
ctx.clearRect(0, 0, width, height);
// Draw background circle
ctx.beginPath();
ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)';
ctx.lineWidth = lineWidth;
ctx.stroke();
// Draw progress arc
const endAngle = (value / 100) * Math.PI * 2;
ctx.beginPath();
ctx.arc(centerX, centerY, radius, 0, endAngle);
// Create gradient based on metric type
const gradientColors = COLORS.RING_GRADIENTS[index];
const gradient = ctx.createLinearGradient(
centerX - radius, centerY,
centerX + radius, centerY
);
gradient.addColorStop(0, gradientColors[0]);
gradient.addColorStop(1, gradientColors[1]);
ctx.strokeStyle = gradient;
ctx.lineWidth = lineWidth;
ctx.lineCap = 'round';
ctx.stroke();
// Add glow effect with shadow
ctx.shadowBlur = 20;
ctx.shadowColor = gradientColors[0];
ctx.stroke();
ctx.shadowBlur = 0;
// Draw inner circle for depth effect
ctx.beginPath();
ctx.arc(centerX, centerY, radius * 0.7, 0, Math.PI * 2);
ctx.fillStyle = 'rgba(0, 0, 0, 0.2)';
ctx.fill();
// Draw pulse effect when near completion
if (value > 95 && this.isAnimating) {
this.drawPulseEffect(ctx, centerX, centerY, radius, gradientColors[0]);
}
}
/**
* Draw a pulse effect for completed rings
* @param {CanvasRenderingContext2D} ctx - Canvas context
* @param {number} centerX - Center X coordinate
* @param {number} centerY - Center Y coordinate
* @param {number} radius - Base radius
* @param {string} color - Pulse color
*/
drawPulseEffect(ctx, centerX, centerY, radius, color) {
const pulseTime = Date.now() % 2000 / 2000; // 2 second cycle
// Draw multiple concentric circles with varying opacity
for (let i = 0; i < 3; i++) {
const pulsePhase = (pulseTime + i * 0.2) % 1;
const pulseRadius = radius + pulsePhase * 30;
const pulseOpacity = (1 - pulsePhase) * 0.3;
ctx.beginPath();
ctx.arc(centerX, centerY, pulseRadius, 0, Math.PI * 2);
ctx.strokeStyle = color.replace(')', `, ${pulseOpacity})`).replace('rgb', 'rgba');
ctx.lineWidth = 2;
ctx.stroke();
}
}
/**
* Update the displayed metric values
*/
updateMetricDisplays() {
// Find metric display elements
const metricValues = document.querySelectorAll('.metric-value');
if (metricValues.length === 3) {
metricValues.forEach((element, index) => {
const value = Math.round(this.currentValues[index]);
// Update the displayed value
const numberSpan = element.querySelector('span:first-child') || element;
const unitSpan = element.querySelector('.unit');
// If it's just a text node, update it
if (element.childNodes.length === 1 && element.firstChild.nodeType === 3) {
element.textContent = value + (unitSpan ? unitSpan.textContent : '');
} else {
// Update the numeric part
const numericText = element.childNodes[0];
if (numericText.nodeType === 3) {
numericText.textContent = value;
}
}
});
}
}
/**
* Update the status text displays
* @param {Array} statuses - Array of status texts
*/
updateStatusDisplays(statuses) {
const statusElements = document.querySelectorAll('.metric-status');
if (statusElements.length === 3) {
statusElements.forEach((element, index) => {
if (statuses[index]) {
element.textContent = statuses[index];
// Add completion styling for final status
if (statuses === METRICS_CONFIG.COMPLETE_STATUSES) {
element.style.color = COLORS.PRIMARY;
element.style.textShadow = COLORS.PRIMARY + ' 0 0 10px';
} else {
element.style.color = COLORS.ACCENT;
element.style.textShadow = 'none';
}
}
});
}
}
/**
* Generate new random target values and update displays
*/
generateNewTargets() {
// Generate new random values
this.targetValues = generateRandomTargets();
// Reset current values to 0
this.currentValues = [0, 0, 0];
// Update target displays
this.updateTargetDisplays();
// Redraw circles at 0
this.drawAll();
// Start new animation after short delay
setTimeout(() => {
this.animateToTargets();
}, 500);
}
/**
* Update the displayed target values (for debugging or advanced UI)
*/
updateTargetDisplays() {
// This could be used to show target values alongside current values
// For now, we'll just log them for debugging
console.log('New target values:', this.targetValues);
}
/**
* Manually set target values (for testing or specific scenarios)
* @param {Array} targets - Array of target values [compute, storage, sync]
*/
setTargetValues(targets) {
if (targets.length === 3) {
this.targetValues = targets.slice();
// Stop current animation
if (this.isAnimating) {
cancelAnimationFrame(this.animationId);
this.isAnimating = false;
}
// Start new animation
this.animateToTargets();
}
}
/**
* Get the current progress values
* @returns {Array} Current values [compute, storage, sync]
*/
getCurrentValues() {
return this.currentValues.slice();
}
/**
* Get the target progress values
* @returns {Array} Target values [compute, storage, sync]
*/
getTargetValues() {
return this.targetValues.slice();
}
/**
* Check if animation is currently running
* @returns {boolean} True if animating
*/
isAnimationRunning() {
return this.isAnimating;
}
/**
* Clean up resources
*/
dispose() {
// Stop animation
if (this.isAnimating) {
cancelAnimationFrame(this.animationId);
this.isAnimating = false;
}
// Clear canvases
this.ctxs.forEach(ctx => {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
});
console.log('Progress circles disposed');
}
}