2024-04-07 21:09:52 +02:00
|
|
|
import useWebSocket from 'react-use-websocket';
|
2024-04-07 00:33:54 +02:00
|
|
|
import {useEffect, useRef} from 'react';
|
|
|
|
import './ClientScreen.css';
|
2024-04-14 14:55:01 +02:00
|
|
|
import {hslToString, Theme} from "./theme.ts";
|
2024-04-07 00:33:54 +02:00
|
|
|
|
|
|
|
const pixelsPerRow = 352;
|
|
|
|
const pixelsPerCol = 160;
|
|
|
|
|
|
|
|
function getIndexes(bitIndex: number) {
|
|
|
|
return {
|
2024-04-12 16:05:24 +02:00
|
|
|
byteIndex: Math.floor(bitIndex / 8),
|
2024-04-07 00:33:54 +02:00
|
|
|
bitInByteIndex: 7 - bitIndex % 8
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2024-04-14 13:23:04 +02:00
|
|
|
function normalizeColor(context: CanvasRenderingContext2D, color: string) {
|
|
|
|
context.fillStyle = color;
|
|
|
|
context.fillRect(0, 0, 1, 1);
|
|
|
|
return context.getImageData(0, 0, 1, 1).data;
|
|
|
|
}
|
|
|
|
|
2024-04-14 14:55:01 +02:00
|
|
|
function drawPixelsToCanvas(pixels: Uint8Array, canvas: HTMLCanvasElement, theme: Theme) {
|
2024-04-07 00:33:54 +02:00
|
|
|
const drawContext = canvas.getContext('2d');
|
|
|
|
if (!drawContext)
|
|
|
|
throw new Error('could not get draw context');
|
|
|
|
|
2024-04-14 14:55:01 +02:00
|
|
|
const colorPrimary = normalizeColor(drawContext, hslToString(theme.primary));
|
|
|
|
const colorBackground = normalizeColor(drawContext, hslToString(theme.background));
|
2024-04-14 13:23:04 +02:00
|
|
|
|
2024-04-07 00:33:54 +02:00
|
|
|
const imageData = drawContext.getImageData(0, 0, canvas.width, canvas.height, {colorSpace: 'srgb'});
|
|
|
|
const data = imageData.data;
|
|
|
|
|
|
|
|
for (let y = 0; y < canvas.height; y++) {
|
|
|
|
const rowStartPixelIndex = y * pixelsPerRow;
|
|
|
|
for (let x = 0; x < canvas.width; x++) {
|
|
|
|
const pixelIndex = rowStartPixelIndex + x;
|
|
|
|
const {byteIndex, bitInByteIndex} = getIndexes(pixelIndex);
|
|
|
|
const mask = (1 << bitInByteIndex);
|
2024-04-11 20:48:21 +02:00
|
|
|
const isOn = (pixels[byteIndex] & mask) !== 0;
|
2024-04-14 13:23:04 +02:00
|
|
|
const color = isOn ? colorPrimary : colorBackground;
|
2024-04-07 00:33:54 +02:00
|
|
|
|
2024-04-11 20:48:21 +02:00
|
|
|
for (let colorChannel of [0, 1, 2, 3])
|
|
|
|
data[pixelIndex * 4 + colorChannel] = color[colorChannel];
|
2024-04-07 00:33:54 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
drawContext.putImageData(imageData, 0, 0);
|
|
|
|
}
|
2024-04-11 20:48:21 +02:00
|
|
|
|
2024-04-14 14:55:01 +02:00
|
|
|
export default function ClientScreen({logout, theme}: { logout: () => void, theme: Theme }) {
|
2024-04-07 00:33:54 +02:00
|
|
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
|
|
|
|
|
|
const {
|
|
|
|
lastMessage,
|
|
|
|
sendMessage,
|
|
|
|
getWebSocket
|
|
|
|
} = useWebSocket(import.meta.env.VITE_TANK_SCREEN_URL, {
|
2024-04-13 19:50:37 +02:00
|
|
|
onError: logout
|
2024-04-07 00:33:54 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
const socket = getWebSocket();
|
|
|
|
if (socket)
|
|
|
|
(socket as WebSocket).binaryType = 'arraybuffer';
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (lastMessage === null)
|
|
|
|
return;
|
|
|
|
if (canvasRef.current === null)
|
|
|
|
throw new Error('canvas null');
|
|
|
|
|
2024-04-14 14:55:01 +02:00
|
|
|
drawPixelsToCanvas(new Uint8Array(lastMessage.data), canvasRef.current, theme);
|
2024-04-07 00:33:54 +02:00
|
|
|
sendMessage('');
|
2024-04-14 14:55:01 +02:00
|
|
|
}, [lastMessage, canvasRef.current, theme]);
|
2024-04-07 00:33:54 +02:00
|
|
|
|
2024-04-13 23:07:08 +02:00
|
|
|
return <canvas ref={canvasRef} id="screen" width={pixelsPerRow} height={pixelsPerCol}/>;
|
2024-04-07 00:33:54 +02:00
|
|
|
}
|