filmout_manager/browser/index.ts

544 lines
16 KiB
TypeScript
Raw Normal View History

let client : Client;
enum SequenceStatus {
IDLE,
RUNNING,
PAUSED
}
class Display {
private parentElement : HTMLUListElement;
private canvas : HTMLCanvasElement;
private ctx : CanvasRenderingContext2D;
2024-10-22 20:31:04 +00:00
private img : HTMLImageElement;
private screen : any;
private sequence : boolean = false;
private offsetX : number = 0;
private offsetY : number = 0;
private width : number = 0;
private height : number = 0;
private canvasScale : number = 0;
private canvasWidth : number = 0;
private canvasHeight : number = 0;
private canvasOffsetX : number = 0;
private canvasOffsetY : number = 0;
private screenWidth : number = 0;
private screenHeight : number = 0;
private screenOffsetX : number = 0;
private screenOffsetY : number = 0;
private displayWidth : number = 0;
private displayHeight : number = 0;
private displayOffsetX : number = 0;
private displayOffsetY : number = 0;
constructor () {
this.parentElement = document.getElementById('display') as HTMLUListElement;
this.create();
window.onresize = this.onResize.bind(this);
}
private create () {
this.canvas = this.parentElement.getElementsByTagName('canvas')[0];
this.ctx = this.canvas.getContext('2d');
this.screen = {
width : parseInt((document.getElementById('width') as HTMLInputElement).value),
height : parseInt((document.getElementById('height') as HTMLInputElement).value)
}
this.updateSize();
this.clear();
this.updateScreen();
this.updateDisplay();
}
private updateSize () {
this.canvasScale = window.devicePixelRatio;
this.canvasWidth = this.parentElement.clientWidth - 12;
this.canvasHeight = this.parentElement.clientHeight - 12;
//console.log(`${this.canvasWidth},${this.canvasHeight}`);
this.canvas.width = this.canvasWidth;
this.canvas.height = this.canvasHeight;
}
public clear () {
this.ctx.fillStyle = 'rgb(0, 0, 0)';
this.ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight);
}
public updateScreen () {
const clientRatio : number = this.canvasWidth / this.canvasHeight;
const screenRatio : number = this.screen.width / this.screen.height;
if (screenRatio > clientRatio) {
this.screenWidth = this.canvasWidth - 2;
this.screenHeight = Math.floor(this.canvasWidth / screenRatio);
this.screenOffsetX = 1;
this.screenOffsetY = Math.round((this.canvasHeight - this.screenHeight) / 2);
} else {
this.screenWidth = Math.round(this.canvasHeight * screenRatio);
this.screenHeight = this.canvasHeight - 2;
this.screenOffsetY = 1;
this.screenOffsetX = Math.round((this.canvasWidth - this.screenWidth) / 2);
}
this.ctx.strokeStyle = 'rgb(0, 0, 255)';
this.ctx.rect(this.screenOffsetX, this.screenOffsetY, this.screenWidth, this.screenHeight);
this.ctx.stroke();
}
public updateDisplay () {
if (!this.sequence) {
return;
}
//console.log(this.sequence);
const screenScaleX : number = this.screenWidth / this.screen.width;
const screenScaleY : number = this.screenHeight / this.screen.height;
this.displayWidth = Math.round(this.width * screenScaleX);
this.displayHeight = Math.round(this.height * screenScaleY);
this.displayOffsetX = this.screenOffsetX + Math.round(this.offsetX * screenScaleX);
this.displayOffsetY = this.screenOffsetY + Math.round(this.offsetY * screenScaleY);
this.ctx.fillStyle = 'rgb(0, 0, 0)';
this.ctx.fillRect(this.displayOffsetX, this.displayOffsetY, this.displayWidth, this.displayHeight);
//console.log(`${this.displayOffsetX}, ${this.displayOffsetY}, ${this.displayWidth}, ${this.displayHeight}`);
this.updateImage();
}
public updateImage() {
2024-10-22 20:31:04 +00:00
this.img = new Image;
this.img.onload = function () {
this.ctx.drawImage(this.img, this.displayOffsetX, this.displayOffsetY, this.displayWidth, this.displayHeight);
}.bind(this);
2024-10-22 20:31:04 +00:00
this.img.src = `/${this.displayWidth}/${this.displayHeight}/image.jpg?cacheBreaker=${+new Date()}`;
}
public update (msg : Message) {
}
public set (state : State) {
this.sequence = true;
this.offsetX = state.offset.x;
this.offsetY = state.offset.y;
this.width = state.display.width;
this.height = state.display.height;
this.updateDisplay();
}
private onResize (event : any) {
this.updateSize();
this.clear();
this.updateScreen();
this.updateDisplay();
}
}
class Client {
private display : Display;
private client : WebSocket;
private connected : boolean = false;
private progress : HTMLProgressElement;
private progressText : HTMLElement;
private frames : number = 0;
constructor () {
let uri : string = this.getWebsocketUri();
this.progress = document.getElementById('progress') as HTMLProgressElement;
this.progressText = document.getElementById('progressText');
this.client = new WebSocket(uri);
this.display = new Display();
this.client.onopen = this.onOpen.bind(this);
this.client.onclose = this.onClose.bind(this);
this.client.onmessage = this.onMessage.bind(this);
this.resetForm('sequenceSelectForm');
this.resetForm('sequenceCtrlForm');
this.resetForm('manualCtrlForm');
this.resetForm('exposureCtrlForm');
this.resetForm('statisticsForm');
this.disableClass('sequenceCtrl');
this.disableClass('manualCtrl');
this.disableClass('exposureCtrl');
this.setProgress({ hash: null, progress: 0 });
}
private getWebsocketUri () : string {
const host : string = (window.location.host + '').split(':')[0];
//WEBSOCKET_PORT defined on page via template
//@ts-ignore
return `ws://${host}:${WEBSOCKET_PORT}`
}
private onMessage (event : any) {
const msg : Message = JSON.parse(event.data) as Message;
if (typeof msg.cmd !== 'undefined' && msg.cmd !== null) {
this.cmd(msg);
}
}
private onOpen (event : any) {
console.log('Connected');
this.connected = true;
this.active();
this.enableClass('manualCtrl');
}
private onClose (event : any) {
console.log('Disconnected');
this.connected = false;
this.inactive();
}
private setSequence(state : State) {
this.setProgress(state.sequence);
this.setFrame(state.sequence);
this.setStatus(state.sequence);
this.setExposure(state);
this.setDisplay(state);
this.set('sequence', state.sequence.hash);
this.removeClass('sequence', 'edited');
}
private setUpdate(state : State) {
this.setProgress(state.sequence);
this.setFrame(state.sequence);
this.setStatus(state.sequence);
this.setExposure(state);
this.setStatistics(state.statistics);
this.display.updateImage();
}
private setStatus (sequence : SequenceState) {
let status : string;
switch (sequence.status) {
case SequenceStatus.IDLE :
status = 'Idle';
break;
case SequenceStatus.RUNNING :
status = 'Running';
break;
case SequenceStatus.PAUSED :
status = 'Paused';
break;
default :
status = 'Unknown State';
}
this.frames = sequence.frames;
document.getElementById('sequenceStatus').innerText = status;
document.getElementById('sequenceName').innerText = sequence.name;
document.getElementById('sequenceLength').innerText = `Sequence Length: ${sequence.frames}`;
}
private setProgress (sequence : SequenceState) {
const percent : number = sequence.progress * 100.0;
this.progress.value = sequence.progress;
this.progressText.innerText = `Progress: ${Math.floor(percent)}%`;
}
private setFrame (sequence : SequenceState) {
if (typeof sequence.current !== 'undefined') {
this.set('frame', `${sequence.current}`.padStart(5, '0'));
this.removeClass('frame', 'edited');
}
}
private setExposure (state : State) {
if (typeof state.exposure !== 'undefined') {
const el : HTMLInputElement = document.getElementById('exposure') as HTMLInputElement;
this.enableClass('exposureCtrl');
this.set('exposure', `${state.exposure}`);
this.removeClass('exposure', 'edited');
}
}
private setDisplay (state : State) {
if (typeof state.display !== 'undefined') {
this.set('displayWidth', state.display.width.toString());
this.set('displayHeight', state.display.height.toString());
this.set('offsetLeft', state.offset.x.toString());
this.set('offsetTop', state.offset.y.toString());
this.set('sourceWidth', state.source.width.toString());
this.set('sourceHeight', state.source.height.toString());
//widthEl.readOnly = false;
//heightEl.readOnly = false;
//console.dir(state);
this.display.set(state);
}
}
2024-07-13 02:00:24 +00:00
public edited (el : HTMLElement) {
el.classList.add('edited');
}
private setStatistics (stats : SequenceStatistics) {
if (stats !== null) {
this.set('statsFrameTotalLast', this.roundDigits(stats.totalFrameLast, 0));
this.set('statsFrameTotalAvg', this.roundDigits(stats.totalFrameAvg, 2));
this.set('statsFrameTotalMargin', this.roundDigits(stats.totalFrameMargin, 1));
this.set('statsFPS', this.roundDigits(stats.fps, 2));
this.set('statsFrameLoadAvg', this.roundDigits(stats.loadAvg, 2));
this.set('statsFrameLoadMargin', this.roundDigits(stats.loadMargin, 1));
this.set('statsFrameOpenLast', this.roundDigits(stats.openLast, 0));
this.set('statsFrameOpenAvg', this.roundDigits(stats.openAvg, 2));
this.set('statsFrameOpenMargin', this.roundDigits(stats.openMargin, 1));
this.set('statsFrameCloseLast', this.roundDigits(stats.closeLast, 0));
this.set('statsFrameCloseAvg', this.roundDigits(stats.closeAvg, 2));
this.set('statsFrameCloseMargin', this.roundDigits(stats.closeMargin, 1));
this.set('statsExposureLast', this.roundDigits(stats.exposureLast, 0));
this.set('statsExposureAvg', this.roundDigits(stats.exposureAvg, 2));
this.set('statsExposureMargin', this.roundDigits(stats.exposureMargin, 1));
this.set('statsElapsed', this.roundDigits(stats.elapsed, 0));
this.set('statsEstimate', this.roundDigits(stats.estimate, 0));
this.set('statsElapsedHuman', this.shortenHumanize(Math.round(stats.elapsed)));
this.set('statsEstimateHuman', this.shortenHumanize(Math.round(stats.estimate)));
}
}
private cmd (msg : Message) {
switch (msg.cmd) {
case 'ping' :
this.receivePing();
break;
case 'open' :
this.receiveCameraOpen();
break;
case 'close' :
this.receiveCameraClose();
break;
case 'select' :
this.receiveSelect(msg);
break;
case 'update' :
this.receiveUpdate(msg);
break;
case 'focus' :
this.receiveFocus(msg);
break;
case 'display' :
this.receiveDisplay(msg);
break;
default:
console.warn(`No command "${msg.cmd}"`);
break;
}
}
public disableClass (className : string) {
console.log(`Disabling class: ${className}`);
document.querySelectorAll(`.${className}`).forEach((el : HTMLButtonElement) => {
el.disabled = true;
});
}
public enableClass (className : string) {
console.log(`Enabling class: ${className}`);
document.querySelectorAll(`.${className}`).forEach((el : HTMLButtonElement) => {
el.disabled = false;
});
}
public sendCameraOpen () {
console.log('send camera open');
this.disableClass('manualCtrl');
2024-07-13 02:00:24 +00:00
this.client.send(JSON.stringify({ cmd : 'open' }));
}
private receivePing() {
this.sendPong();
}
private receiveCameraOpen () {
console.log('got camera open');
this.enableClass('manualCtrl');
}
public sendCameraClose () {
console.log('send camera close');
this.disableClass('manualCtrl');
this.client.send(JSON.stringify({ cmd : 'close' }));
}
private receiveCameraClose () {
console.log('got camera close');
this.enableClass('manualCtrl');
}
private sendPong () {
this.client.send(JSON.stringify({ cmd : 'pong' }));
}
public sendAdvance () {
this.client.send(JSON.stringify({ cmd : 'advance' }));
}
public sendRewind () {
this.client.send(JSON.stringify({ cmd : 'rewind' }));
}
public sendFrameSet () {
const frameStr : string = (document.getElementById('frame') as HTMLInputElement).value;
let frameNum : number = null;
try {
frameNum = parseInt(frameStr);
} catch (err) {
console.error(`Error parsing ${frameStr}`);
}
if (frameNum === null) {
frameNum = 0;
}
if (frameNum < 0) {
frameNum = 0;
}
if (frameNum > this.frames - 1) {
frameNum = this.frames - 1;
}
this.client.send(JSON.stringify({ cmd : 'set', state : { sequence : { current : frameNum } } }));
}
public sendToEnd () {
this.client.send(JSON.stringify({ cmd : 'set', state : { sequence : { current : this.frames - 1 } } }));
}
public sendToStart () {
this.client.send(JSON.stringify({ cmd : 'set', state : { sequence : { current : 0 } } }));
}
public sendSelect () {
const hash : string = (document.getElementById('sequence') as HTMLSelectElement ).value;
let msg : Message;
if (hash === '- Select Image Sequence -') {
return;
}
msg = { cmd : 'select', state : { sequence : { hash } } };
console.log(`send select ${hash}`);
this.client.send(JSON.stringify(msg));
}
private receiveSelect (msg : Message) {
this.enableClass('sequenceCtrl');
this.setSequence(msg.state);
}
public sendStart () {
this.client.send(JSON.stringify({ cmd : 'start' }));
}
public sendStop () {
this.client.send(JSON.stringify({ cmd : 'stop' }));
}
public sendExposure () {
const exposure : number = parseInt(this.get('exposure'));
this.client.send(JSON.stringify({ cmd : 'exposure', state : { exposure }}));
}
private receiveUpdate (msg : Message) {
this.setUpdate(msg.state);
}
private receiveDisplay (msg: Message) {
this.display.clear();
this.display.updateScreen();
this.setDisplay(msg.state);
}
public sendFocus () {
console.log('send focus');
//this.disableClass('manualCtrl');
this.client.send(JSON.stringify({ cmd : 'focus' }));
}
private receiveFocus (msg : Message) {
this.display.updateImage();
}
public sendOffset (x : number, y : number) {
this.client.send(JSON.stringify({ cmd : 'offset', x, y }));
}
public sendSize (width : number, height : number) {
this.client.send(JSON.stringify({ cmd : 'size', width, height }));
}
public sendScale (scale : number) {
this.client.send(JSON.stringify({ cmd : 'scale', scale }));
}
/**
* HELPERS
**/
public fullscreen () {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen();
} else {
this.exitFullscreen();
}
}
public exitFullscreen () {
if (document.fullscreenElement) {
document.exitFullscreen()
}
}
private active () {
this.addClass('overlay', 'active');
}
private inactive () {
this.removeClass('overlay', 'active');
}
public addClass (id : string, className : string) {
document.getElementById(id).classList.add(className);
}
public removeClass (id : string, className : string) {
document.getElementById(id).classList.remove(className);
}
public set (id : string, value : string) {
try {
(document.getElementById(id) as HTMLInputElement).value = value;
} catch (err) {
console.warn(`Element ${id} does not exist or cannot be set`);
}
}
public get (id : string) {
return (document.getElementById(id) as HTMLInputElement).value;
}
private resetForm (id : string) {
(document.getElementById(id) as HTMLFormElement ).reset();
}
private shortenHumanize (val : number) : string {
const str : string = humanizeDuration(val, { round : true });
return str.replace('years', 'y').replace('year', 'y')
.replace('months', 'mo').replace('month', 'mo')
.replace('weeks', 'w').replace('week', 'w')
.replace('days', 'd').replace('day', 'd')
.replace('hours', 'h').replace('hour', 'h')
.replace('minutes', 'm').replace('minute', 'm')
.replace('seconds', 's').replace('second', 's');
}
2024-10-20 01:22:15 +00:00
private roundDigits (val : number, digits : number) : string {
2024-10-20 01:22:15 +00:00
const mult : number = Math.pow(10.0, digits);
const rounded : number = Math.round(val * mult) / mult;
return rounded.toString();
2024-10-20 01:22:15 +00:00
}
}
client = new Client();