204 lines
7.0 KiB
JavaScript
204 lines
7.0 KiB
JavaScript
// Matrix Display Component
|
|
|
|
class MatrixDisplay extends Component {
|
|
constructor(container, viewModel, eventBus) {
|
|
super(container, viewModel, eventBus);
|
|
this.canvas = null;
|
|
this.ctx = null;
|
|
this.pixelSize = 20;
|
|
this.matrixWidth = 16;
|
|
this.matrixHeight = 16;
|
|
this.frameData = null;
|
|
this.animationId = null;
|
|
}
|
|
|
|
mount() {
|
|
super.mount();
|
|
this.setupCanvas();
|
|
this.setupEventListeners();
|
|
this.setupViewModelListeners();
|
|
}
|
|
|
|
setupCanvas() {
|
|
this.canvas = this.findElement('#matrix-canvas');
|
|
if (!this.canvas) {
|
|
console.error('Matrix canvas element not found');
|
|
return;
|
|
}
|
|
|
|
this.ctx = this.canvas.getContext('2d');
|
|
this.updateCanvasSize();
|
|
}
|
|
|
|
updateCanvasSize() {
|
|
if (!this.canvas || !this.ctx) return;
|
|
|
|
const container = this.canvas.parentElement;
|
|
const containerWidth = container.clientWidth - 32; // Account for padding
|
|
const containerHeight = container.clientHeight - 32;
|
|
|
|
// Calculate pixel size to fit the matrix in the container
|
|
const maxPixelWidth = Math.floor(containerWidth / this.matrixWidth);
|
|
const maxPixelHeight = Math.floor(containerHeight / this.matrixHeight);
|
|
this.pixelSize = Math.min(maxPixelWidth, maxPixelHeight, 40); // Cap at 40px
|
|
|
|
this.canvas.width = this.matrixWidth * this.pixelSize;
|
|
this.canvas.height = this.matrixHeight * this.pixelSize;
|
|
|
|
// Center the canvas
|
|
const canvasContainer = this.canvas.parentElement;
|
|
canvasContainer.style.display = 'flex';
|
|
canvasContainer.style.alignItems = 'center';
|
|
canvasContainer.style.justifyContent = 'center';
|
|
}
|
|
|
|
setupEventListeners() {
|
|
// Handle window resize
|
|
this.addEventListener(window, 'resize', () => {
|
|
this.updateCanvasSize();
|
|
this.renderFrame();
|
|
});
|
|
}
|
|
|
|
setupViewModelListeners() {
|
|
this.subscribeToEvent('frame', (data) => {
|
|
this.frameData = data.data;
|
|
this.renderFrame();
|
|
});
|
|
|
|
this.subscribeToEvent('matrixSizeChanged', (data) => {
|
|
this.matrixWidth = data.size.width;
|
|
this.matrixHeight = data.size.height;
|
|
this.updateCanvasSize();
|
|
this.updateMatrixSize(data.size.width, data.size.height);
|
|
this.renderFrame();
|
|
});
|
|
|
|
this.subscribeToEvent('streamingStarted', (data) => {
|
|
this.updateStreamingStatus('streaming');
|
|
});
|
|
|
|
this.subscribeToEvent('streamingStopped', () => {
|
|
this.updateStreamingStatus('stopped');
|
|
});
|
|
|
|
this.subscribeToEvent('status', (data) => {
|
|
// Update UI to reflect current server state
|
|
this.updateStreamingStatus(data.data.streaming ? 'streaming' : 'stopped');
|
|
if (data.data.matrixSize) {
|
|
this.matrixWidth = data.data.matrixSize.width;
|
|
this.matrixHeight = data.data.matrixSize.height;
|
|
this.updateCanvasSize();
|
|
this.updateMatrixSize(data.data.matrixSize.width, data.data.matrixSize.height);
|
|
}
|
|
});
|
|
}
|
|
|
|
updateStreamingStatus(status) {
|
|
const statusElement = this.findElement('#streaming-status');
|
|
if (statusElement) {
|
|
statusElement.className = `status-indicator status-${status}`;
|
|
statusElement.textContent = status === 'streaming' ? 'Streaming' : 'Stopped';
|
|
}
|
|
}
|
|
|
|
renderFrame() {
|
|
if (!this.ctx || !this.canvas) return;
|
|
|
|
// Clear canvas
|
|
this.ctx.fillStyle = 'var(--matrix-bg)';
|
|
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
|
|
|
|
// Render pixels if we have frame data
|
|
if (this.frameData && this.frameData.startsWith('RAW:')) {
|
|
const pixelData = this.frameData.substring(4); // Remove 'RAW:' prefix
|
|
|
|
// Render pixels in serpentine order to match hardware layout
|
|
for (let row = 0; row < this.matrixHeight; row++) {
|
|
for (let col = 0; col < this.matrixWidth; col++) {
|
|
// Calculate serpentine index manually
|
|
const hardwareIndex = (row % 2 === 0) ?
|
|
(row * this.matrixWidth + col) :
|
|
(row * this.matrixWidth + (this.matrixWidth - 1 - col));
|
|
const pixelStart = hardwareIndex * 6;
|
|
|
|
if (pixelStart + 5 < pixelData.length) {
|
|
const hexColor = pixelData.substring(pixelStart, pixelStart + 6);
|
|
this.renderPixel(col, row, hexColor);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
renderPixel(col, row, hexColor) {
|
|
const x = col * this.pixelSize;
|
|
const y = row * this.pixelSize;
|
|
|
|
// Convert hex to RGB
|
|
const r = parseInt(hexColor.substring(0, 2), 16);
|
|
const g = parseInt(hexColor.substring(2, 4), 16);
|
|
const b = parseInt(hexColor.substring(4, 6), 16);
|
|
|
|
// Skip rendering if pixel is black (optimization)
|
|
if (r === 0 && g === 0 && b === 0) {
|
|
return;
|
|
}
|
|
|
|
// Draw pixel with a subtle border for better visibility
|
|
this.ctx.fillStyle = `rgb(${r}, ${g}, ${b})`;
|
|
this.ctx.fillRect(x + 1, y + 1, this.pixelSize - 2, this.pixelSize - 2);
|
|
|
|
// Add a subtle glow effect for brighter pixels
|
|
if (r > 128 || g > 128 || b > 128) {
|
|
this.ctx.shadowColor = `rgb(${r}, ${g}, ${b})`;
|
|
this.ctx.shadowBlur = 2;
|
|
this.ctx.fillRect(x + 1, y + 1, this.pixelSize - 2, this.pixelSize - 2);
|
|
this.ctx.shadowBlur = 0;
|
|
}
|
|
}
|
|
|
|
// Method to render a test pattern
|
|
renderTestPattern() {
|
|
this.frameData = 'RAW:';
|
|
for (let row = 0; row < this.matrixHeight; row++) {
|
|
for (let col = 0; col < this.matrixWidth; col++) {
|
|
// Calculate serpentine index manually
|
|
const hardwareIndex = (row % 2 === 0) ?
|
|
(row * this.matrixWidth + col) :
|
|
(row * this.matrixWidth + (this.matrixWidth - 1 - col));
|
|
// Create a checkerboard pattern
|
|
if ((row + col) % 2 === 0) {
|
|
this.frameData += '00ff00'; // Green
|
|
} else {
|
|
this.frameData += '000000'; // Black
|
|
}
|
|
}
|
|
}
|
|
|
|
this.renderFrame();
|
|
}
|
|
|
|
// Method to clear the matrix
|
|
clearMatrix() {
|
|
if (this.ctx && this.canvas) {
|
|
this.ctx.fillStyle = 'var(--matrix-bg)';
|
|
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
|
|
}
|
|
this.frameData = null;
|
|
}
|
|
|
|
// Update matrix size display
|
|
updateMatrixSize(width, height) {
|
|
const sizeElement = this.findElement('#matrix-size');
|
|
if (sizeElement) {
|
|
sizeElement.textContent = `${width}x${height}`;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Export for use in other modules
|
|
if (typeof module !== 'undefined' && module.exports) {
|
|
module.exports = MatrixDisplay;
|
|
}
|