sort working image renderer

This commit is contained in:
sawka
2023-02-06 00:31:34 -08:00
parent 2a1dcd97e9
commit 62b9503387
6 changed files with 206 additions and 8 deletions
+61 -5
View File
@@ -1,18 +1,32 @@
import * as mobx from "mobx";
import {WindowSize} from "./types";
import {WindowSize, RendererContext, TermOptsType} from "./types";
import {getPtyData, termWidthFromCols, termHeightFromRows} from "./model";
import {incObs} from "./util";
const InitialSize = 10*1024;
const IncreaseFactor = 1.5;
class ImageRenderer {
class ImageRendererModel {
context : RendererContext;
dataSize : number;
imageData : Uint8Array;
brokenData : boolean;
isDone : mobx.IObservableValue<boolean>;
reloading : boolean = false;
dataVersion : mobx.IObservableValue<number>;
htmlImgDivElem : any;
htmlImg : any;
termOpts : TermOptsType;
constructor() {
constructor(imgDivElem : any, context : RendererContext, termOpts : TermOptsType, isDone : boolean) {
this.htmlImgDivElem = imgDivElem;
this.termOpts = termOpts;
this.context = context;
this._resetData();
this.isDone = mobx.observable.box(false, {name: "isDone"});
this.isDone = mobx.observable.box(isDone, {name: "isDone"});
this.dataVersion = mobx.observable.box(0, {name: "dataVersion"});
this.reload(0);
console.log("image", this.termOpts);
}
_resetData() {
@@ -23,9 +37,44 @@ class ImageRenderer {
dispose() : void {
this._resetData();
this.removeImage();
}
removeImage() : void {
this.htmlImg = null;
this.htmlImgDivElem.replaceChildren();
}
renderImage() : void {
if (!this.isDone.get()) {
return;
}
let blob = new Blob([this.imageData.slice(0, this.dataSize)], {type: "image/jpeg"});
this.htmlImg = new Image();
this.htmlImg.src = URL.createObjectURL(blob);
this.htmlImg.style.maxHeight = termHeightFromRows(this.termOpts.rows) + "px";
this.htmlImg.style.maxWidth = termWidthFromCols(this.termOpts.cols) + "px";
this.htmlImgDivElem.replaceChildren(this.htmlImg);
}
reload(delayMs : number) : void {
if (this.reloading) {
return;
}
this._resetData();
this.reloading = true;
let rtnp = getPtyData(this.context.sessionId, this.context.cmdId);
rtnp.then((ptydata) => {
setTimeout(() => {
this.reloading = false;
this.receiveData(ptydata.pos, ptydata.data, "reload");
this.renderImage();
}, delayMs);
}).catch((e) => {
this.brokenData = true;
this.reloading = false;
console.log("error reloading image data", e);
});
}
_growArray(minSize : number) : void {
@@ -48,18 +97,25 @@ class ImageRenderer {
}
this.imageData.set(data, pos);
this.dataSize += data.length;
incObs(this.dataVersion);
}
cmdDone() : void {
mobx.action(() => {
this.isDone.set(true);
})();
}
resizeWindow(size : WindowSize) : void {
return;
}
resizeCols(cols : number) : void {
return;
}
giveFocus() : void {
return;
}
getUsedRows() : number {
@@ -67,5 +123,5 @@ class ImageRenderer {
}
}
export {ImageRenderer};
export {ImageRendererModel};
+117 -1
View File
@@ -13,6 +13,7 @@ import type {SessionDataType, LineType, CmdDataType, RemoteType, RemoteStateType
import localizedFormat from 'dayjs/plugin/localizedFormat';
import {GlobalModel, GlobalCommandRunner, Session, Cmd, Window, Screen, ScreenWindow, riToRPtr, windowWidthToCols, windowHeightToRows, termHeightFromRows, termWidthFromCols} from "./model";
import {isModKeyPress} from "./util";
import {ImageRendererModel} from "./imagerenderer";
dayjs.extend(localizedFormat)
@@ -29,6 +30,9 @@ type OMap<K,V> = mobx.ObservableMap<K,V>;
type HeightChangeCallbackType = (lineNum : number, newHeight : number, oldHeight : number) => void;
type RendererComponentProps = {sw : ScreenWindow, line : LineType, width : number, staticRender : boolean, visible : OV<boolean>, onHeightChange : HeightChangeCallbackType};
type RendererComponentType = { new(props : RendererComponentProps) : React.Component<RendererComponentProps, {}> };
type InterObsValue = {
sessionid : string,
windowid : string,
@@ -213,6 +217,114 @@ class Prompt extends React.Component<{rptr : RemotePtrType, festate : FeStateTyp
}
}
@mobxReact.observer
class ImageRenderer extends React.Component<{sw : ScreenWindow, line : LineType, width : number, staticRender : boolean, visible : OV<boolean>, onHeightChange : () => void}, {}> {
elemRef : React.RefObject<any> = React.createRef();
imageDivRef : React.RefObject<any> = React.createRef();
imageLoaded : mobx.IObservableValue<boolean> = mobx.observable.box(false, {name: "imageLoaded"});
imageModel : ImageRendererModel;
constructor(props) {
super(props);
}
componentDidMount() {
this.componentDidUpdate(null, null, null);
}
componentWillUnmount() {
if (this.imageLoaded.get()) {
this.unloadImage(true);
}
}
getSnapshotBeforeUpdate(prevProps, prevState) : {height : number} {
let elem = this.elemRef.current;
if (elem == null) {
return {height: 0};
}
return {height: elem.offsetHeight};
}
componentDidUpdate(prevProps, prevState, snapshot : {height : number}) : void {
if (this.props.onHeightChange == null) {
return;
}
let {line} = this.props;
let curHeight = 0;
let elem = this.elemRef.current;
if (elem != null) {
curHeight = elem.offsetHeight;
}
if (snapshot == null) {
snapshot = {height: 0};
}
if (snapshot.height != curHeight) {
this.props.onHeightChange();
// console.log("image-render height change: ", line.linenum, snapshot.height, "=>", curHeight);
}
this.checkLoad();
}
checkLoad() : void {
let {line, staticRender, visible} = this.props;
if (staticRender) {
return;
}
let vis = visible && visible.get();
let curVis = this.imageLoaded.get();
if (vis && !curVis) {
this.loadImage();
}
else if (!vis && curVis) {
this.unloadImage(false);
}
}
loadImage() : void {
let {sw, line} = this.props;
let model = GlobalModel;
let cmd = model.getCmd(line);
if (cmd == null) {
return;
}
let elem = this.imageDivRef.current;
if (elem == null) {
console.log("cannot load image, no elem found");
return;
}
this.imageModel = sw.loadImageRenderer(this.imageDivRef.current, line, cmd);
mobx.action(() => this.imageLoaded.set(true))();
}
unloadImage(unmount : boolean) : void {
let {sw, line} = this.props;
sw.unloadRenderer(line.cmdid);
this.imageModel = null;
if (!unmount) {
mobx.action(() => this.imageLoaded.set(false))();
if (this.imageDivRef.current != null) {
this.imageDivRef.current.replaceChildren();
}
}
}
render() {
let imageModel = this.imageModel;
let isLoaded = this.imageLoaded.get();
let isDone = (imageModel != null && imageModel.isDone.get());
if (imageModel != null) {
let dataVersion = imageModel.dataVersion.get();
}
return (
<div ref={this.elemRef} className={"image-wrapper"}>
<div key="imagediv" ref={this.imageDivRef}></div>
<If condition={!isDone}><div className="loading-div">...</div></If>
</div>
);
}
}
@mobxReact.observer
class TerminalRenderer extends React.Component<{sw : ScreenWindow, line : LineType, width : number, staticRender : boolean, visible : OV<boolean>, onHeightChange : () => void}, {}> {
termLoaded : mobx.IObservableValue<boolean> = mobx.observable.box(false, {name: "linecmd-term-loaded"});
@@ -545,6 +657,10 @@ class LineCmd extends React.Component<{sw : ScreenWindow, line : LineType, width
{"cmd-done": !cmd.isRunning()},
{"has-rtnstate": cmd.getRtnState()},
);
let RendererComponent : RendererComponentType = TerminalRenderer;
if (line.renderer == "image") {
RendererComponent = ImageRenderer;
}
return (
<div className={mainDivCn} id={"line-" + getLineId(line)}
ref={this.lineRef} onClick={this.handleClick}
@@ -575,7 +691,7 @@ class LineCmd extends React.Component<{sw : ScreenWindow, line : LineType, width
</If>
</div>
</div>
<TerminalRenderer sw={sw} line={line} width={width} staticRender={staticRender} visible={visible} onHeightChange={this.handleHeightChange}/>
<RendererComponent sw={sw} line={line} width={width} staticRender={staticRender} visible={visible} onHeightChange={this.handleHeightChange}/>
<If condition={cmd.getRtnState()}>
<div key="rtnstate" className="cmd-rtnstate" style={{visibility: ((cmd.getStatus() == "done") ? "visible" : "hidden")}}>
<If condition={rsdiff == null || rsdiff == ""}>
+9 -1
View File
@@ -7,7 +7,7 @@ import {TermWrap} from "./term";
import {v4 as uuidv4} from "uuid";
import type {SessionDataType, WindowDataType, LineType, RemoteType, HistoryItem, RemoteInstanceType, RemotePtrType, CmdDataType, FeCmdPacketType, TermOptsType, RemoteStateType, ScreenDataType, ScreenWindowType, ScreenOptsType, LayoutType, PtyDataUpdateType, ModelUpdateType, UpdateMessage, InfoType, CmdLineUpdateType, UIContextType, HistoryInfoType, HistoryQueryOpts, FeInputPacketType, TermWinSize, RemoteInputPacketType, FeStateType, ContextMenuOpts, RendererContext, RendererModel, PtyDataType} from "./types";
import {WSControl} from "./ws";
import {ImageRenderer} from "./imagerenderer";
import {ImageRendererModel} from "./imagerenderer";
var GlobalUser = "sawka";
const DefaultCellWidth = 7.203125;
@@ -550,6 +550,14 @@ class ScreenWindow {
return false;
}
loadImageRenderer(imageDivElem : any, line : LineType, cmd : Cmd) : ImageRendererModel {
let cmdId = cmd.cmdId;
let context = {sessionId: this.sessionId, screenId: this.screenId, windowId: this.windowId, cmdId: cmdId, lineId : line.lineid, lineNum: line.linenum};
let imageModel = new ImageRendererModel(imageDivElem, context, cmd.getTermOpts(), !cmd.isRunning());
this.renderers[cmdId] = imageModel;
return imageModel;
}
loadTerminalRenderer(elem : Element, line : LineType, cmd : Cmd, width : number) {
let cmdId = cmd.cmdId;
let termWrap = this.getRenderer(cmdId);
+11
View File
@@ -474,6 +474,17 @@ html, body, #main {
margin-bottom: -5px;
}
.image-wrapper {
padding: 2px 10px 5px 4px;
margin: 4px 8px 0 -4px;
.loading-div {
height: 20px;
margin-left: 50px;
.mono-font();
}
}
.terminal-wrapper {
background-color: #000;
padding: 2px 10px 5px 4px;
+1
View File
@@ -26,6 +26,7 @@ type LineType = {
linelocal : boolean,
linetype : string,
text : string,
renderer : string,
cmdid? : string,
contentheight? : number,
star? : number,
+7 -1
View File
@@ -176,4 +176,10 @@ function isModKeyPress(e : any) {
return e.code.match(/^(Control|Meta|Alt|Shift)(Left|Right)$/);
}
export {handleJsonFetchResponse, base64ToArray, genMergeData, genMergeSimpleData, parseEnv0, boundInt, isModKeyPress};
function incObs(inum : mobx.IObservableValue<number>) {
mobx.action(() => {
inum.set(inum.get() + 1);
})();
}
export {handleJsonFetchResponse, base64ToArray, genMergeData, genMergeSimpleData, parseEnv0, boundInt, isModKeyPress, incObs};