mirror of
https://github.com/PhotonVision/photonvision
synced 2026-06-26 01:51:40 +00:00
[PhotonClient] Vite and Typescript complete refactor (#884)
This commit is contained in:
92
photon-client/src/lib/AutoReconnectingWebsocket.ts
Normal file
92
photon-client/src/lib/AutoReconnectingWebsocket.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { decode, encode } from "@msgpack/msgpack";
|
||||
import type { IncomingWebsocketData } from "@/types/WebsocketDataTypes";
|
||||
|
||||
/**
|
||||
* {@link WebSocket} wrapper class that automatically reconnects to the provided host address if the connection was closed by the remote host or a connection failure.
|
||||
* Data sent and received by the Websocket is automatically encoded and decoded using msgpack.
|
||||
*/
|
||||
export class AutoReconnectingWebsocket {
|
||||
private readonly serverAddress: string | URL;
|
||||
private websocket: WebSocket | null | undefined;
|
||||
|
||||
private readonly onConnect: () => void;
|
||||
private readonly onData: (data: IncomingWebsocketData) => void;
|
||||
private readonly onDisconnect: () => void;
|
||||
|
||||
/**
|
||||
* Create an AutoReconnectingWebsocket
|
||||
*
|
||||
* @param serverAddress address of the websocket
|
||||
* @param onConnect action to run on websocket connection (when the websocket changes to the OPEN state)
|
||||
* @param onData decoded websocket message data consumer. The data is automatically decoded by msgpack.
|
||||
* @param onDisconnect action to run on websocket disconnection (when the websocket changes to the CLOSED state)
|
||||
*/
|
||||
constructor(serverAddress: string | URL, onConnect: () => void, onData: (data: IncomingWebsocketData) => void, onDisconnect: () => void) {
|
||||
this.serverAddress = serverAddress;
|
||||
|
||||
this.onConnect = onConnect;
|
||||
this.onData = onData;
|
||||
this.onDisconnect = onDisconnect;
|
||||
|
||||
this.initializeWebsocket();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send data over the websocket. This is a no-op if the websocket is not in the OPEN state.
|
||||
*
|
||||
* @param data data to send
|
||||
* @param encodeData whether or not to encode the data using msgpack (defaults to true)
|
||||
* @see isConnected
|
||||
*
|
||||
*/
|
||||
send(data, encodeData = true) {
|
||||
// Only send data if the websocket is open
|
||||
if(this.isConnected()) {
|
||||
if(encodeData) {
|
||||
this.websocket?.send(encode(data));
|
||||
} else {
|
||||
this.websocket?.send(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the WebSocket is OPEN and connected
|
||||
*/
|
||||
isConnected(): boolean {
|
||||
return this.websocket === null || this.websocket === undefined
|
||||
? false
|
||||
: this.websocket.readyState === WebSocket.OPEN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the creation of the websocket and the binding of the action consumers.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private initializeWebsocket() {
|
||||
this.websocket = new WebSocket(this.serverAddress);
|
||||
this.websocket.binaryType = "arraybuffer";
|
||||
|
||||
this.websocket.onopen = () => {
|
||||
console.debug("[WebSocket] Websocket Open");
|
||||
this.onConnect();
|
||||
};
|
||||
this.websocket.onmessage = (event: MessageEvent) => {
|
||||
this.onData(decode(event.data) as IncomingWebsocketData);
|
||||
};
|
||||
this.websocket.onclose = (event: CloseEvent) => {
|
||||
this.onDisconnect();
|
||||
|
||||
this.websocket = null;
|
||||
|
||||
console.info("[WebSocket] The WebSocket was closed. Will reattempt in 500 milliseconds.", event.reason);
|
||||
setTimeout(this.initializeWebsocket.bind(this), 500);
|
||||
};
|
||||
this.websocket.onerror = () => {
|
||||
this.websocket?.close();
|
||||
};
|
||||
|
||||
console.debug(`[WebSocket] Attempting to initialize Websocket connection to ${this.serverAddress}`);
|
||||
}
|
||||
}
|
||||
98
photon-client/src/lib/ColorPicker.ts
Normal file
98
photon-client/src/lib/ColorPicker.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
export type HSV = [number, number, number]
|
||||
export type RGBA = [number, number, number, number] | Uint8ClampedArray
|
||||
|
||||
export class ColorPicker {
|
||||
public hsvData: HSV;
|
||||
|
||||
constructor(pixelData: RGBA) {
|
||||
this.hsvData = this.RGBtoHSV(pixelData);
|
||||
}
|
||||
|
||||
public selectedColorRange() {
|
||||
return this.widenRange([[...this.hsvData], [...this.hsvData]]);
|
||||
}
|
||||
|
||||
public expandColorRange(currentRange: [HSV, HSV]) {
|
||||
const widenedHSV = this.widenRange([[...this.hsvData], [...this.hsvData]]);
|
||||
return this.createRange(currentRange.concat(widenedHSV));
|
||||
}
|
||||
|
||||
public shrinkColorRange(currentRange: [HSV, HSV]) {
|
||||
const widenedHSV = this.widenRange([[...this.hsvData], [...this.hsvData]]);
|
||||
|
||||
//Tries to shrink the lower part of to widened HSV
|
||||
if(!this.shrinkRange(currentRange, widenedHSV[0])) {
|
||||
//If the prev attempt failed, try to shrink the higher part of to widened HSV
|
||||
this.shrinkRange(currentRange, widenedHSV[1]);
|
||||
}
|
||||
|
||||
return currentRange;
|
||||
}
|
||||
|
||||
private createRange(range: HSV[]): [HSV, HSV] {
|
||||
const newRange: [HSV, HSV] = [[0, 0, 0], [0, 0, 0]];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
newRange[0][i] = range[0][i];
|
||||
newRange[1][i] = range[0][i];
|
||||
for (let j = range.length - 1; j >= 0; j--) {
|
||||
newRange[0][i] = Math.min(range[j][i], newRange[0][i]);
|
||||
newRange[1][i] = Math.max(range[j][i], newRange[1][i]);
|
||||
}
|
||||
}
|
||||
return newRange;
|
||||
}
|
||||
|
||||
private widenRange(range: [HSV, HSV]): [HSV, HSV] {
|
||||
const expanded: [HSV, HSV] = [[0, 0, 0], [0, 0, 0]];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
//Expanding the range by 10
|
||||
expanded[0][i] = Math.max(0, range[0][i] - 10);
|
||||
expanded[1][i] = Math.min(255, range[1][i] + 10);
|
||||
}
|
||||
expanded[1][0] = Math.min(180, expanded[1][0]); //h is up to 180
|
||||
return expanded;
|
||||
}
|
||||
|
||||
private shrinkRange(range: [HSV, HSV], color: HSV): boolean {
|
||||
for (let i = 0; i < color.length; i++) {
|
||||
if (!(range[0][i] <= color[i] && color[i] <= range[1][i])) return false;
|
||||
}
|
||||
|
||||
for (let i = 0; i < color.length; i++) {
|
||||
if (color[i] - range[0][i] < range[1][i] - color[i]) {
|
||||
//shrink from min side
|
||||
range[0][i] = Math.min(range[0][i] + 10, range[1][i]);
|
||||
} else {
|
||||
//shrink from max side
|
||||
range[1][i] = Math.max(range[1][i] - 10, range[0][i]);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private RGBtoHSV(rgba: RGBA ): HSV {
|
||||
// Normalize RGB ranges
|
||||
let r = rgba[0],
|
||||
g = rgba[1],
|
||||
b = rgba[2];
|
||||
r = r / 255;
|
||||
g = g / 255;
|
||||
b = b / 255;
|
||||
|
||||
const minRGB = Math.min(r, Math.min(g, b));
|
||||
const maxRGB = Math.max(r, Math.max(g, b));
|
||||
const d = (r === minRGB) ? g - b : ((b === minRGB) ? r - g : b - r);
|
||||
const h = (r === minRGB) ? 3 : ((b === minRGB) ? 1 : 5);
|
||||
let H = 30 * (h - d / (maxRGB - minRGB));
|
||||
let S = 255 * (maxRGB - minRGB) / maxRGB;
|
||||
let V = 255 * maxRGB;
|
||||
if (isNaN(H))
|
||||
H = 0;
|
||||
if (isNaN(S))
|
||||
S = 0;
|
||||
if (isNaN(V))
|
||||
V = 0;
|
||||
return [Math.round(H), Math.round(S), Math.round(V)];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user