mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-01-03 18:47:56 +01:00
big refactor for webshare, reduce GlobalModel dependencies
This commit is contained in:
parent
6a42dcf07f
commit
e83abeaa5b
@ -547,7 +547,7 @@ class LineContainer extends React.Component<{historyId : string, width : number}
|
||||
<If condition={session == null}>
|
||||
<div className="no-line-context"/>
|
||||
</If>
|
||||
<Line screen={hvm.specialLineContainer} line={this.line} width={width} staticRender={false} visible={this.visible} onHeightChange={this.handleHeightChange} overrideCollapsed={this.overrideCollapsed} topBorder={false} renderMode="normal"/>
|
||||
<Line screen={hvm.specialLineContainer} line={this.line} width={width} staticRender={false} visible={this.visible} onHeightChange={this.handleHeightChange} overrideCollapsed={this.overrideCollapsed} topBorder={false} renderMode="normal" noSelect={true}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -4,7 +4,8 @@ import * as mobxReact from "mobx-react";
|
||||
import cn from "classnames";
|
||||
import {If, For, When, Otherwise, Choose} from "tsx-control-statements/components";
|
||||
import {WindowSize, RendererContext, TermOptsType, LineType, RendererOpts} from "./types";
|
||||
import {getPtyData, termWidthFromCols, termHeightFromRows, GlobalModel, LineContainerModel} from "./model";
|
||||
import {LineContainerModel} from "./model";
|
||||
import {termWidthFromCols, termHeightFromRows} from "./textmeasure";
|
||||
import {incObs} from "./util";
|
||||
import {PtyDataBuffer} from "./ptydata";
|
||||
|
||||
|
@ -6,13 +6,17 @@ import {boundMethod} from "autobind-decorator";
|
||||
import dayjs from "dayjs";
|
||||
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||
import {If, For, When, Otherwise, Choose} from "tsx-control-statements/components";
|
||||
import {GlobalModel, GlobalCommandRunner, Session, Cmd, ScreenLines, Screen, windowWidthToCols, windowHeightToRows, termHeightFromRows, termWidthFromCols, getRendererContext, getRendererType} from "./model";
|
||||
import {GlobalModel, GlobalCommandRunner, Session, Cmd, ScreenLines, Screen, getRendererContext} from "./model";
|
||||
import {windowWidthToCols, windowHeightToRows, termHeightFromRows, termWidthFromCols} from "./textmeasure";
|
||||
import type {LineType, CmdDataType, FeStateType, RemoteType, RemotePtrType, RenderModeType, RendererContext, RendererOpts, SimpleBlobRendererComponent, RendererPluginType} from "./types";
|
||||
import cn from "classnames";
|
||||
import {TermWrap} from "./term";
|
||||
import type {LineContainerModel} from "./model";
|
||||
import {renderCmdText} from "./elements";
|
||||
import {SimpleBlobRendererModel, SimpleBlobRenderer} from "./simplerenderer";
|
||||
import {isBlank} from "./util";
|
||||
import {PluginModel} from "./plugins";
|
||||
import * as lineutil from "./lineutil";
|
||||
|
||||
dayjs.extend(localizedFormat)
|
||||
|
||||
@ -24,10 +28,6 @@ type HeightChangeCallbackType = (lineNum : number, newHeight : number, oldHeight
|
||||
type RendererComponentProps = {screen : LineContainerModel, line : LineType, width : number, staticRender : boolean, visible : OV<boolean>, onHeightChange : HeightChangeCallbackType, collapsed : boolean};
|
||||
type RendererComponentType = { new(props : RendererComponentProps) : React.Component<RendererComponentProps, {}> };
|
||||
|
||||
function isBlank(s : string) : boolean {
|
||||
return (s == null || s == "");
|
||||
}
|
||||
|
||||
function makeFullRemoteRef(ownerName : string, remoteRef : string, name : string) : string {
|
||||
if (isBlank(ownerName) && isBlank(name)) {
|
||||
return remoteRef;
|
||||
@ -75,40 +75,8 @@ function getCwdStr(remote : RemoteType, state : FeStateType) : string {
|
||||
return cwd;
|
||||
}
|
||||
|
||||
function getLineDateTimeStr(ts : number) : string {
|
||||
let lineDate = new Date(ts);
|
||||
let nowDate = new Date();
|
||||
|
||||
if (nowDate.getFullYear() != lineDate.getFullYear()) {
|
||||
return dayjs(lineDate).format("ddd L LTS");
|
||||
}
|
||||
else if (nowDate.getMonth() != lineDate.getMonth() || nowDate.getDate() != lineDate.getDate()) {
|
||||
let yesterdayDate = (new Date());
|
||||
yesterdayDate.setDate(yesterdayDate.getDate()-1);
|
||||
if (yesterdayDate.getMonth() == lineDate.getMonth() && yesterdayDate.getDate() == lineDate.getDate()) {
|
||||
return "Yesterday " + dayjs(lineDate).format("LTS");;
|
||||
}
|
||||
return dayjs(lineDate).format("ddd L LTS");
|
||||
}
|
||||
else {
|
||||
return dayjs(lineDate).format("LTS");
|
||||
}
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class LineAvatar extends React.Component<{line : LineType, cmd : Cmd}, {}> {
|
||||
@boundMethod
|
||||
handleRightClick(e : any) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
let {line} = this.props;
|
||||
if (line != null) {
|
||||
mobx.action(() => {
|
||||
GlobalModel.lineSettingsModal.set(line);
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
class LineAvatar extends React.Component<{line : LineType, cmd : Cmd, onRightClick? : (e : any) => void}, {}> {
|
||||
render() {
|
||||
let {line, cmd} = this.props;
|
||||
let lineNumStr = (line.linenumtemp ? "~" : "") + String(line.linenum);
|
||||
@ -116,7 +84,7 @@ class LineAvatar extends React.Component<{line : LineType, cmd : Cmd}, {}> {
|
||||
let rtnstate = (cmd != null ? cmd.getRtnState() : false);
|
||||
let isComment = (line.linetype == "text");
|
||||
return (
|
||||
<div onContextMenu={(e) => this.handleRightClick(e)} className={cn("avatar", "num-"+lineNumStr.length, "status-" + status, {"ephemeral": line.ephemeral}, {"rtnstate": rtnstate})}>
|
||||
<div onContextMenu={this.props.onRightClick} className={cn("avatar", "num-"+lineNumStr.length, "status-" + status, {"ephemeral": line.ephemeral}, {"rtnstate": rtnstate})}>
|
||||
{lineNumStr}
|
||||
<If condition={status == "hangup" || status == "error"}>
|
||||
<i className="fa-sharp fa-solid fa-triangle-exclamation status-icon"/>
|
||||
@ -132,7 +100,6 @@ class LineAvatar extends React.Component<{line : LineType, cmd : Cmd}, {}> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@mobxReact.observer
|
||||
class LineCmd extends React.Component<{screen : LineContainerModel, line : LineType, width : number, staticRender : boolean, visible : OV<boolean>, onHeightChange : HeightChangeCallbackType, topBorder : boolean, renderMode : RenderModeType, overrideCollapsed : OV<boolean>, noSelect? : boolean, showHints? : boolean}, {}> {
|
||||
lineRef : React.RefObject<any> = React.createRef();
|
||||
@ -231,18 +198,18 @@ class LineCmd extends React.Component<{screen : LineContainerModel, line : LineT
|
||||
</div>
|
||||
</div>
|
||||
<div key="meta3" className="meta meta-line3 cmdtext-expanded-wrapper">
|
||||
<div className="cmdtext-expanded">{cmd.getFullCmdText()}</div>
|
||||
<div className="cmdtext-expanded">{lineutil.getFullCmdText(cmd.getCmdStr())}</div>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
let isMultiLine = cmd.isMultiLineCmdText();
|
||||
let isMultiLine = lineutil.isMultiLineCmdText(cmd.getCmdStr());
|
||||
return (
|
||||
<div key="meta2" className="meta meta-line2" ref={this.cmdTextRef}>
|
||||
<div className="metapart-mono cmdtext">
|
||||
<Prompt rptr={cmd.remote} festate={cmd.getRemoteFeState()}/>
|
||||
<span> </span>
|
||||
<span>{cmd.getSingleLineCmdText()}</span>
|
||||
<span>{lineutil.getSingleLineCmdText(cmd.getCmdStr())}</span>
|
||||
</div>
|
||||
<If condition={this.isOverflow.get() || isMultiLine}>
|
||||
<div className="cmdtext-overflow" onClick={this.handleExpandCmd}>...▼</div>
|
||||
@ -389,6 +356,21 @@ class LineCmd extends React.Component<{screen : LineContainerModel, line : LineT
|
||||
return height;
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
onAvatarRightClick(e : any) : void {
|
||||
let {line, noSelect} = this.props;
|
||||
if (noSelect) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (line != null) {
|
||||
mobx.action(() => {
|
||||
GlobalModel.lineSettingsModal.set(line);
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
renderSimple() {
|
||||
let {screen, line, topBorder} = this.props;
|
||||
let cmd = screen.getCmd(line);
|
||||
@ -436,7 +418,7 @@ class LineCmd extends React.Component<{screen : LineContainerModel, line : LineT
|
||||
renderMetaWrap(cmd : Cmd) {
|
||||
let {line} = this.props;
|
||||
let model = GlobalModel;
|
||||
let formattedTime = getLineDateTimeStr(line.ts);
|
||||
let formattedTime = lineutil.getLineDateTimeStr(line.ts);
|
||||
let termOpts = cmd.getTermOpts();
|
||||
let remote = model.getRemote(cmd.remoteId);
|
||||
let renderer = line.renderer;
|
||||
@ -460,6 +442,16 @@ class LineCmd extends React.Component<{screen : LineContainerModel, line : LineT
|
||||
);
|
||||
}
|
||||
|
||||
getRendererOpts(cmd : Cmd) : RendererOpts {
|
||||
let {screen} = this.props;
|
||||
return {
|
||||
maxSize: screen.getMaxContentSize(),
|
||||
idealSize: screen.getIdealContentSize(),
|
||||
termOpts: cmd.getTermOpts(),
|
||||
termFontSize: GlobalModel.termFontSize.get(),
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
let {screen, line, width, staticRender, visible, topBorder, renderMode} = this.props;
|
||||
let model = GlobalModel;
|
||||
@ -468,7 +460,7 @@ class LineCmd extends React.Component<{screen : LineContainerModel, line : LineT
|
||||
if (staticRender || !isVisible) {
|
||||
return this.renderSimple();
|
||||
}
|
||||
let formattedTime = getLineDateTimeStr(line.ts);
|
||||
let formattedTime = lineutil.getLineDateTimeStr(line.ts);
|
||||
let cmd = screen.getCmd(line);
|
||||
if (cmd == null) {
|
||||
return (
|
||||
@ -507,16 +499,16 @@ class LineCmd extends React.Component<{screen : LineContainerModel, line : LineT
|
||||
let rendererPlugin : RendererPluginType = null;
|
||||
let isNoneRenderer = (line.renderer == "none");
|
||||
if (!isBlank(line.renderer) && line.renderer != "terminal" && !isNoneRenderer) {
|
||||
rendererPlugin = GlobalModel.getRendererPluginByName(line.renderer);
|
||||
rendererPlugin = PluginModel.getRendererPluginByName(line.renderer);
|
||||
}
|
||||
let rendererType = getRendererType(line);
|
||||
let rendererType = lineutil.getRendererType(line);
|
||||
return (
|
||||
<div className={mainDivCn}
|
||||
ref={this.lineRef} onClick={this.handleClick}
|
||||
data-lineid={line.lineid} data-linenum={line.linenum} data-screenid={line.screenid} data-cmdid={line.cmdid}>
|
||||
<div key="focus" className={cn("focus-indicator", {"selected": isSelected}, {"active": isSelected && isFocused}, {"fg-focus": isFgFocused})}/>
|
||||
<div key="header" className={cn("line-header", {"is-expanded": isExpanded}, {"is-collapsed": isCollapsed})}>
|
||||
<LineAvatar line={line} cmd={cmd}/>
|
||||
<LineAvatar line={line} cmd={cmd} onRightClick={this.onAvatarRightClick}/>
|
||||
<If condition={renderMode == "collapsed"}>
|
||||
<div key="collapsed" className="collapsed-indicator" title={isCollapsed ? "output collapsed, click to show" : "click to hide output" } onClick={this.handleCollapsedClick}>
|
||||
<If condition={isCollapsed}><i className="fa-sharp fa-solid fa-caret-right"/></If>
|
||||
@ -535,7 +527,7 @@ class LineCmd extends React.Component<{screen : LineContainerModel, line : LineT
|
||||
<TerminalRenderer screen={screen} line={line} width={width} staticRender={staticRender} visible={visible} onHeightChange={this.handleHeightChange} collapsed={isCollapsed}/>
|
||||
</If>
|
||||
<If condition={rendererPlugin != null}>
|
||||
<SimpleBlobRenderer lcm={screen} line={line} cmd={cmd} plugin={rendererPlugin} onHeightChange={this.handleHeightChange}/>
|
||||
<SimpleBlobRenderer lcm={screen} line={line} cmd={cmd} plugin={rendererPlugin} onHeightChange={this.handleHeightChange} rendererOpts={this.getRendererOpts(cmd)}/>
|
||||
</If>
|
||||
<If condition={!isCollapsed && cmd.getRtnState()}>
|
||||
<div key="rtnstate" className="cmd-rtnstate" style={{visibility: ((cmd.getStatus() == "done") ? "visible" : "hidden")}}>
|
||||
@ -622,9 +614,24 @@ class LineText extends React.Component<{screen : LineContainerModel, line : Line
|
||||
GlobalCommandRunner.screenSelectLine(String(line.linenum));
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
onAvatarRightClick(e : any) : void {
|
||||
let {line, noSelect} = this.props;
|
||||
if (noSelect) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (line != null) {
|
||||
mobx.action(() => {
|
||||
GlobalModel.lineSettingsModal.set(line);
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let {screen, line, topBorder, renderMode} = this.props;
|
||||
let formattedTime = getLineDateTimeStr(line.ts);
|
||||
let formattedTime = lineutil.getLineDateTimeStr(line.ts);
|
||||
let isSelected = mobx.computed(() => (screen.getSelectedLine() == line.linenum), {name: "computed-isSelected"}).get();
|
||||
let isFocused = mobx.computed(() => (screen.getFocusType() == "cmd"), {name: "computed-isFocused"}).get();
|
||||
let isCollapsed = (renderMode == "collapsed");
|
||||
@ -638,7 +645,7 @@ class LineText extends React.Component<{screen : LineContainerModel, line : Line
|
||||
return (
|
||||
<div className={mainClass} data-lineid={line.lineid} data-linenum={line.linenum} data-screenid={line.screenid} onClick={this.clickHandler}>
|
||||
<div className={cn("focus-indicator", {"selected": isSelected}, {"active": isSelected && isFocused})}/>
|
||||
<LineAvatar line={line} cmd={null}/>
|
||||
<LineAvatar line={line} cmd={null} onRightClick={this.onAvatarRightClick}/>
|
||||
<div className="line-content">
|
||||
<div className="meta">
|
||||
<div className="ts">{formattedTime}</div>
|
||||
|
76
src/lineutil.ts
Normal file
76
src/lineutil.ts
Normal file
@ -0,0 +1,76 @@
|
||||
import dayjs from "dayjs";
|
||||
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||
import {isBlank, getDateStr} from "./util";
|
||||
import {LineType, WebLine} from "./types";
|
||||
|
||||
dayjs.extend(localizedFormat)
|
||||
|
||||
function getRendererType(line : LineType|WebLine) : "terminal" | "plugin" {
|
||||
if (isBlank(line.renderer) || line.renderer == "terminal") {
|
||||
return "terminal";
|
||||
}
|
||||
return "plugin";
|
||||
}
|
||||
|
||||
function getLineDateStr(todayDate : string, yesterdayDate : string, ts : number) : string {
|
||||
let lineDate = new Date(ts);
|
||||
let dateStr = getDateStr(lineDate);
|
||||
if (dateStr == todayDate) {
|
||||
return "today";
|
||||
}
|
||||
if (dateStr == yesterdayDate) {
|
||||
return "yesterday";
|
||||
}
|
||||
return dateStr;
|
||||
}
|
||||
|
||||
function getLineDateTimeStr(ts : number) : string {
|
||||
let lineDate = new Date(ts);
|
||||
let nowDate = new Date();
|
||||
|
||||
if (nowDate.getFullYear() != lineDate.getFullYear()) {
|
||||
return dayjs(lineDate).format("ddd L LTS");
|
||||
}
|
||||
else if (nowDate.getMonth() != lineDate.getMonth() || nowDate.getDate() != lineDate.getDate()) {
|
||||
let yesterdayDate = (new Date());
|
||||
yesterdayDate.setDate(yesterdayDate.getDate()-1);
|
||||
if (yesterdayDate.getMonth() == lineDate.getMonth() && yesterdayDate.getDate() == lineDate.getDate()) {
|
||||
return "Yesterday " + dayjs(lineDate).format("LTS");;
|
||||
}
|
||||
return dayjs(lineDate).format("ddd L LTS");
|
||||
}
|
||||
else {
|
||||
return dayjs(lineDate).format("LTS");
|
||||
}
|
||||
}
|
||||
|
||||
function isMultiLineCmdText(cmdText : string) : boolean {
|
||||
if (cmdText == null) {
|
||||
return false;
|
||||
}
|
||||
cmdText = cmdText.trim();
|
||||
let nlIdx = cmdText.indexOf("\n");
|
||||
return (nlIdx != -1);
|
||||
}
|
||||
|
||||
function getFullCmdText(cmdText : string) {
|
||||
if (cmdText == null) {
|
||||
return "(none)";
|
||||
}
|
||||
cmdText = cmdText.trim();
|
||||
return cmdText;
|
||||
}
|
||||
|
||||
function getSingleLineCmdText(cmdText : string) {
|
||||
if (cmdText == null) {
|
||||
return "(none)";
|
||||
}
|
||||
cmdText = cmdText.trim();
|
||||
let nlIdx = cmdText.indexOf("\n");
|
||||
if (nlIdx != -1) {
|
||||
cmdText = cmdText.substr(0, nlIdx);
|
||||
}
|
||||
return cmdText;
|
||||
}
|
||||
|
||||
export {getRendererType, getLineDateStr, getLineDateTimeStr, isMultiLineCmdText, getFullCmdText, getSingleLineCmdText};
|
@ -11,7 +11,8 @@ import dayjs from "dayjs";
|
||||
import type {SessionDataType, LineType, CmdDataType, RemoteType, RemoteStateType, RemoteInstanceType, RemotePtrType, HistoryItem, HistoryQueryOpts, RemoteEditType, FeStateType, ContextMenuOpts, BookmarkType, RenderModeType, ClientMigrationInfo} from "./types";
|
||||
import type * as T from "./types";
|
||||
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||
import {GlobalModel, GlobalCommandRunner, Session, Cmd, ScreenLines, Screen, riToRPtr, windowWidthToCols, windowHeightToRows, termHeightFromRows, termWidthFromCols, TabColors, RemoteColors} from "./model";
|
||||
import {GlobalModel, GlobalCommandRunner, Session, Cmd, ScreenLines, Screen, riToRPtr, TabColors, RemoteColors} from "./model";
|
||||
import {windowWidthToCols, windowHeightToRows, termHeightFromRows, termWidthFromCols} from "./textmeasure";
|
||||
import {isModKeyPress, boundInt} from "./util";
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
|
112
src/model.ts
112
src/model.ts
@ -5,9 +5,9 @@ import {debounce} from "throttle-debounce";
|
||||
import {handleJsonFetchResponse, base64ToArray, genMergeData, genMergeDataMap, genMergeSimpleData, boundInt, isModKeyPress} from "./util";
|
||||
import {TermWrap} from "./term";
|
||||
import {v4 as uuidv4} from "uuid";
|
||||
import type {SessionDataType, LineType, RemoteType, HistoryItem, RemoteInstanceType, RemotePtrType, CmdDataType, FeCmdPacketType, TermOptsType, RemoteStateType, ScreenDataType, ScreenOptsType, PtyDataUpdateType, ModelUpdateType, UpdateMessage, InfoType, CmdLineUpdateType, UIContextType, HistoryInfoType, HistoryQueryOpts, FeInputPacketType, TermWinSize, RemoteInputPacketType, FeStateType, ContextMenuOpts, RendererContext, RendererModel, PtyDataType, BookmarkType, ClientDataType, HistoryViewDataType, AlertMessageType, HistorySearchParams, FocusTypeStrs, ScreenLinesType, HistoryTypeStrs, RendererPluginType, WindowSize, ClientMigrationInfo, WebShareOpts} from "./types";
|
||||
import type {SessionDataType, LineType, RemoteType, HistoryItem, RemoteInstanceType, RemotePtrType, CmdDataType, FeCmdPacketType, TermOptsType, RemoteStateType, ScreenDataType, ScreenOptsType, PtyDataUpdateType, ModelUpdateType, UpdateMessage, InfoType, CmdLineUpdateType, UIContextType, HistoryInfoType, HistoryQueryOpts, FeInputPacketType, TermWinSize, RemoteInputPacketType, FeStateType, ContextMenuOpts, RendererContext, RendererModel, PtyDataType, BookmarkType, ClientDataType, HistoryViewDataType, AlertMessageType, HistorySearchParams, FocusTypeStrs, ScreenLinesType, HistoryTypeStrs, RendererPluginType, WindowSize, ClientMigrationInfo, WebShareOpts, TermContextUnion} from "./types";
|
||||
import {WSControl} from "./ws";
|
||||
import {measureText, getMonoFontSize} from "./textmeasure";
|
||||
import {measureText, getMonoFontSize, windowWidthToCols, windowHeightToRows, termWidthFromCols, termHeightFromRows} from "./textmeasure";
|
||||
import dayjs from "dayjs";
|
||||
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||
import customParseFormat from 'dayjs/plugin/customParseFormat';
|
||||
@ -18,8 +18,6 @@ dayjs.extend(localizedFormat)
|
||||
var GlobalUser = "sawka";
|
||||
const RemotePtyRows = 8; // also in main.tsx
|
||||
const RemotePtyCols = 80;
|
||||
const MinTermCols = 10;
|
||||
const MaxTermCols = 1024;
|
||||
const ProdServerEndpoint = "http://localhost:1619";
|
||||
const ProdServerWsEndpoint = "ws://localhost:1623";
|
||||
const DevServerEndpoint = "http://localhost:8090";
|
||||
@ -60,13 +58,6 @@ type SWLinePtr = {
|
||||
screen : Screen,
|
||||
};
|
||||
|
||||
function getRendererType(line : LineType) : "terminal" | "plugin" {
|
||||
if (isBlank(line.renderer) || line.renderer == "terminal") {
|
||||
return "terminal";
|
||||
}
|
||||
return "plugin";
|
||||
}
|
||||
|
||||
function getRendererContext(line : LineType) : RendererContext {
|
||||
return {
|
||||
screenId: line.screenid,
|
||||
@ -76,32 +67,6 @@ function getRendererContext(line : LineType) : RendererContext {
|
||||
};
|
||||
}
|
||||
|
||||
function windowWidthToCols(width : number, fontSize : number) : number {
|
||||
let dr = getMonoFontSize(fontSize);
|
||||
let cols = Math.trunc((width - 50) / dr.width) - 1;
|
||||
cols = boundInt(cols, MinTermCols, MaxTermCols);
|
||||
return cols;
|
||||
}
|
||||
|
||||
function windowHeightToRows(height : number, fontSize : number) : number {
|
||||
let dr = getMonoFontSize(fontSize);
|
||||
let rows = Math.floor((height - 80) / dr.height) - 1;
|
||||
if (rows <= 0) {
|
||||
rows = 1;
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
function termWidthFromCols(cols : number, fontSize : number) : number {
|
||||
let dr = getMonoFontSize(fontSize);
|
||||
return Math.ceil(dr.width*cols) + 15;
|
||||
}
|
||||
|
||||
function termHeightFromRows(rows : number, fontSize : number) : number {
|
||||
let dr = getMonoFontSize(fontSize);
|
||||
return Math.ceil(dr.height*rows);
|
||||
}
|
||||
|
||||
function cmdStatusIsRunning(status : string) : boolean {
|
||||
return status == "running" || status == "detached";
|
||||
}
|
||||
@ -227,38 +192,6 @@ class Cmd {
|
||||
return this.data.get().festate;
|
||||
}
|
||||
|
||||
isMultiLineCmdText() : boolean {
|
||||
let cmdText = this.data.get().cmdstr;
|
||||
if (cmdText == null) {
|
||||
return false;
|
||||
}
|
||||
cmdText = cmdText.trim();
|
||||
let nlIdx = cmdText.indexOf("\n");
|
||||
return (nlIdx != -1);
|
||||
}
|
||||
|
||||
getSingleLineCmdText() {
|
||||
let cmdText = this.data.get().cmdstr;
|
||||
if (cmdText == null) {
|
||||
return "(none)";
|
||||
}
|
||||
cmdText = cmdText.trim();
|
||||
let nlIdx = cmdText.indexOf("\n");
|
||||
if (nlIdx != -1) {
|
||||
cmdText = cmdText.substr(0, nlIdx);
|
||||
}
|
||||
return cmdText;
|
||||
}
|
||||
|
||||
getFullCmdText() {
|
||||
let cmdText = this.data.get().cmdstr;
|
||||
if (cmdText == null) {
|
||||
return "(none)";
|
||||
}
|
||||
cmdText = cmdText.trim();
|
||||
return cmdText;
|
||||
}
|
||||
|
||||
isRunning() : boolean {
|
||||
let data = this.data.get();
|
||||
return cmdStatusIsRunning(data.status);
|
||||
@ -691,6 +624,8 @@ class Screen {
|
||||
isRunning: cmd.isRunning(),
|
||||
customKeyHandler: this.termCustomKeyHandler.bind(this),
|
||||
fontSize: GlobalModel.termFontSize.get(),
|
||||
ptyDataSource: getTermPtyData,
|
||||
onUpdateContentHeight: (termContext : RendererContext, height : number) => { GlobalModel.setContentHeight(termContext, height); },
|
||||
});
|
||||
this.terminals[cmdId] = termWrap;
|
||||
if ((this.focusType.get() == "cmd" || this.focusType.get() == "cmd-fg") && this.selectedLine.get() == line.linenum) {
|
||||
@ -1639,6 +1574,8 @@ class InputModel {
|
||||
focusHandler: this.setRemoteTermWrapFocus.bind(this),
|
||||
isRunning: true,
|
||||
fontSize: GlobalModel.termFontSize.get(),
|
||||
ptyDataSource: getTermPtyData,
|
||||
onUpdateContentHeight: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1757,7 +1694,8 @@ class SpecialHistoryViewLineContainer {
|
||||
isRunning: cmd.isRunning(),
|
||||
customKeyHandler: null,
|
||||
fontSize: GlobalModel.termFontSize.get(),
|
||||
noSetTUR: true,
|
||||
ptyDataSource: getTermPtyData,
|
||||
onUpdateContentHeight: null,
|
||||
});
|
||||
this.terminal = termWrap;
|
||||
return;
|
||||
@ -2369,7 +2307,6 @@ class Model {
|
||||
sessionSettingsModal : OV<string> = mobx.observable.box(null, {name: "sessionSettingsModal"});
|
||||
clientSettingsModal : OV<boolean> = mobx.observable.box(false, {name: "clientSettingsModal"});
|
||||
lineSettingsModal : OV<LineType> = mobx.observable.box(null, {name: "lineSettingsModal"});
|
||||
rendererPlugins : RendererPluginType[] = [];
|
||||
|
||||
inputModel : InputModel;
|
||||
bookmarksModel : BookmarksModel;
|
||||
@ -2423,20 +2360,6 @@ class Model {
|
||||
getApi().reloadWindow();
|
||||
}
|
||||
|
||||
registerRendererPlugin(plugin : RendererPluginType) {
|
||||
if (isBlank(plugin.name)) {
|
||||
throw new Error("invalid plugin, no name");
|
||||
}
|
||||
if (plugin.name == "terminal" || plugin.name == "none") {
|
||||
throw new Error(sprintf("invalid plugin, name '%s' is reserved", plugin.name));
|
||||
}
|
||||
let existingPlugin = this.getRendererPluginByName(plugin.name);
|
||||
if (existingPlugin != null) {
|
||||
throw new Error(sprintf("plugin with name %s already registered", plugin.name));
|
||||
}
|
||||
this.rendererPlugins.push(plugin);
|
||||
}
|
||||
|
||||
getHasClientStop() : boolean {
|
||||
if (this.clientData.get() == null) {
|
||||
return true;
|
||||
@ -2448,16 +2371,6 @@ class Model {
|
||||
return false;
|
||||
}
|
||||
|
||||
getRendererPluginByName(name : string) : RendererPluginType {
|
||||
for (let i=0; i<this.rendererPlugins.length; i++) {
|
||||
let plugin = this.rendererPlugins[i];
|
||||
if (plugin.name == name) {
|
||||
return plugin;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
showAlert(alertMessage : AlertMessageType) : Promise<boolean> {
|
||||
mobx.action(() => {
|
||||
this.alertMessage.set(alertMessage);
|
||||
@ -3494,6 +3407,13 @@ function _getPtyDataFromUrl(url : string) : Promise<PtyDataType> {
|
||||
});
|
||||
}
|
||||
|
||||
function getTermPtyData(termContext : TermContextUnion) : Promise<PtyDataType> {
|
||||
if ("remoteId" in termContext) {
|
||||
return getRemotePtyData(termContext.remoteId);
|
||||
}
|
||||
return getPtyData(termContext.screenId, termContext.cmdId, termContext.lineNum);
|
||||
}
|
||||
|
||||
function getPtyData(screenId : string, cmdId : string, lineNum : number) : Promise<PtyDataType> {
|
||||
let url = sprintf(GlobalModel.getBaseHostPort() + "/api/ptyout?linenum=%d&screenid=%s&cmdid=%s", lineNum, screenId, cmdId);
|
||||
return _getPtyDataFromUrl(url);
|
||||
@ -3514,7 +3434,7 @@ if ((window as any).GlobalModel == null) {
|
||||
GlobalModel = (window as any).GlobalModel;
|
||||
GlobalCommandRunner = (window as any).GlobalCommandRunner;
|
||||
|
||||
export {Model, Session, ScreenLines, GlobalModel, GlobalCommandRunner, Cmd, Screen, riToRPtr, windowWidthToCols, windowHeightToRows, termWidthFromCols, termHeightFromRows, getPtyData, getRemotePtyData, TabColors, RemoteColors, getRendererType, getRendererContext};
|
||||
export {Model, Session, ScreenLines, GlobalModel, GlobalCommandRunner, Cmd, Screen, riToRPtr, TabColors, RemoteColors, getRendererContext, getTermPtyData};
|
||||
export type {LineContainerModel};
|
||||
|
||||
|
||||
|
67
src/plugins.ts
Normal file
67
src/plugins.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import {RendererPluginType} from "./types";
|
||||
import {SimpleImageRenderer} from "./imagerenderer";
|
||||
import {SimpleMarkdownRenderer} from "./markdownrenderer";
|
||||
import {isBlank} from "./util";
|
||||
import {sprintf} from "sprintf-js";
|
||||
|
||||
const ImagePlugin : RendererPluginType = {
|
||||
name: "image",
|
||||
rendererType: "simple",
|
||||
heightType: "pixels",
|
||||
dataType: "blob",
|
||||
collapseType: "hide",
|
||||
globalCss: null,
|
||||
mimeTypes: ["image/*"],
|
||||
component: SimpleImageRenderer,
|
||||
};
|
||||
|
||||
const MarkdownPlugin : RendererPluginType = {
|
||||
name: "markdown",
|
||||
rendererType: "simple",
|
||||
heightType: "pixels",
|
||||
dataType: "blob",
|
||||
collapseType: "hide",
|
||||
globalCss: null,
|
||||
mimeTypes: ["text/markdown"],
|
||||
component: SimpleMarkdownRenderer,
|
||||
};
|
||||
|
||||
let AllPlugins = [ImagePlugin, MarkdownPlugin];
|
||||
|
||||
class PluginModelClass {
|
||||
rendererPlugins : RendererPluginType[] = [];
|
||||
|
||||
registerRendererPlugin(plugin : RendererPluginType) {
|
||||
if (isBlank(plugin.name)) {
|
||||
throw new Error("invalid plugin, no name");
|
||||
}
|
||||
if (plugin.name == "terminal" || plugin.name == "none") {
|
||||
throw new Error(sprintf("invalid plugin, name '%s' is reserved", plugin.name));
|
||||
}
|
||||
let existingPlugin = this.getRendererPluginByName(plugin.name);
|
||||
if (existingPlugin != null) {
|
||||
throw new Error(sprintf("plugin with name %s already registered", plugin.name));
|
||||
}
|
||||
this.rendererPlugins.push(plugin);
|
||||
}
|
||||
|
||||
getRendererPluginByName(name : string) : RendererPluginType {
|
||||
for (let i=0; i<this.rendererPlugins.length; i++) {
|
||||
let plugin = this.rendererPlugins[i];
|
||||
if (plugin.name == name) {
|
||||
return plugin;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
let PluginModel : PluginModelClass = null;
|
||||
if ((window as any).PluginModel == null) {
|
||||
PluginModel = new PluginModelClass();
|
||||
PluginModel.registerRendererPlugin(ImagePlugin);
|
||||
PluginModel.registerRendererPlugin(MarkdownPlugin);
|
||||
(window as any).PluginModel = PluginModel;
|
||||
}
|
||||
|
||||
export {PluginModel};
|
@ -8,6 +8,7 @@ import cn from "classnames";
|
||||
import {GlobalModel, GlobalCommandRunner, TabColors} from "./model";
|
||||
import {Toggle} from "./elements";
|
||||
import {LineType, RendererPluginType, ClientDataType} from "./types";
|
||||
import {PluginModel} from "./plugins";
|
||||
|
||||
type OV<V> = mobx.IObservableValue<V>;
|
||||
type OArr<V> = mobx.IObservableArray<V>;
|
||||
@ -307,7 +308,7 @@ class LineSettingsModal extends React.Component<{line : LineType}, {}> {
|
||||
renderRendererDropdown() : any {
|
||||
let {line} = this.props;
|
||||
let renderer = this.tempRenderer.get() ?? "terminal";
|
||||
let plugins = GlobalModel.rendererPlugins;
|
||||
let plugins = PluginModel.rendererPlugins;
|
||||
let plugin : RendererPluginType = null;
|
||||
return (
|
||||
<div className={cn("dropdown", "renderer-dropdown", {"is-active": this.rendererDropdownActive.get()})}>
|
||||
|
13
src/sh2.less
13
src/sh2.less
@ -3185,8 +3185,16 @@ body.prompt-webshare #main {
|
||||
|
||||
.logo-text {
|
||||
.mono-font(32px);
|
||||
a {
|
||||
color: @prompt-green;
|
||||
}
|
||||
}
|
||||
|
||||
.screen-name {
|
||||
color: white;
|
||||
.mono-font(24px);
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.download-button {
|
||||
margin-right: 20px;
|
||||
@ -3218,4 +3226,9 @@ body.prompt-webshare #main {
|
||||
#app {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.lines .line-sep {
|
||||
margin-left: 0px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
43
src/sh2.ts
43
src/sh2.ts
@ -6,53 +6,14 @@ import {Terminal} from 'xterm';
|
||||
import {Main} from "./main";
|
||||
import {GlobalModel} from "./model";
|
||||
import {v4 as uuidv4} from "uuid";
|
||||
import {RendererPluginType} from "./types";
|
||||
import {SimpleImageRenderer} from "./imagerenderer";
|
||||
import {SimpleMarkdownRenderer} from "./markdownrenderer";
|
||||
import {loadFonts} from "./util";
|
||||
|
||||
// @ts-ignore
|
||||
let VERSION = __PROMPT_VERSION__;
|
||||
// @ts-ignore
|
||||
let BUILD = __PROMPT_BUILD__;
|
||||
|
||||
let jbmFontNormal = new FontFace("JetBrains Mono", "url('static/fonts/jetbrains-mono-v13-latin-regular.woff2')", {style: "normal", weight: "400"});
|
||||
let jbmFont200 = new FontFace("JetBrains Mono", "url('static/fonts/jetbrains-mono-v13-latin-200.woff2')", {style: "normal", weight: "200"});
|
||||
let jbmFont700 = new FontFace("JetBrains Mono", "url('static/fonts/jetbrains-mono-v13-latin-700.woff2')", {style: "normal", weight: "700"});
|
||||
let faFont = new FontFace("FontAwesome", "url(static/fonts/fontawesome-webfont-4.7.woff2)", {style: "normal", weight: "normal"});
|
||||
let docFonts : any = document.fonts; // work around ts typing issue
|
||||
docFonts.add(jbmFontNormal);
|
||||
docFonts.add(jbmFont200);
|
||||
docFonts.add(jbmFont700);
|
||||
docFonts.add(faFont);
|
||||
jbmFontNormal.load();
|
||||
jbmFont200.load();
|
||||
jbmFont700.load();
|
||||
faFont.load();
|
||||
|
||||
const ImagePlugin : RendererPluginType = {
|
||||
name: "image",
|
||||
rendererType: "simple",
|
||||
heightType: "pixels",
|
||||
dataType: "blob",
|
||||
collapseType: "hide",
|
||||
globalCss: null,
|
||||
mimeTypes: ["image/*"],
|
||||
component: SimpleImageRenderer,
|
||||
};
|
||||
|
||||
const MarkdownPlugin : RendererPluginType = {
|
||||
name: "markdown",
|
||||
rendererType: "simple",
|
||||
heightType: "pixels",
|
||||
dataType: "blob",
|
||||
collapseType: "hide",
|
||||
globalCss: null,
|
||||
mimeTypes: ["text/markdown"],
|
||||
component: SimpleMarkdownRenderer,
|
||||
};
|
||||
|
||||
GlobalModel.registerRendererPlugin(ImagePlugin);
|
||||
GlobalModel.registerRendererPlugin(MarkdownPlugin);
|
||||
loadFonts();
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
let reactElem = React.createElement(Main, null, null);
|
||||
|
@ -5,7 +5,7 @@ import {sprintf} from "sprintf-js";
|
||||
import {boundMethod} from "autobind-decorator";
|
||||
import {If, For, When, Otherwise, Choose} from "tsx-control-statements/components";
|
||||
import type {RendererModelInitializeParams, TermOptsType, RendererContext, RendererOpts, SimpleBlobRendererComponent, RendererModelContainerApi, RendererPluginType, PtyDataType, RendererModel, RendererOptsUpdate, LineType} from "./types";
|
||||
import {GlobalModel, LineContainerModel, getPtyData, Cmd} from "./model";
|
||||
import {LineContainerModel, getTermPtyData, Cmd} from "./model";
|
||||
import {PtyDataBuffer} from "./ptydata";
|
||||
import {debounce, throttle} from "throttle-debounce";
|
||||
|
||||
@ -72,7 +72,7 @@ class SimpleBlobRendererModel {
|
||||
mobx.action(() => {
|
||||
this.loading.set(true);
|
||||
})();
|
||||
let rtnp = getPtyData(this.context.screenId, this.context.cmdId, this.context.lineNum);
|
||||
let rtnp = getTermPtyData(this.context);
|
||||
rtnp.then((ptydata) => {
|
||||
setTimeout(() => {
|
||||
this.ptyData = ptydata;
|
||||
@ -120,14 +120,14 @@ function apiAdapter(lcm : LineContainerModel, line : LineType, cmd : Cmd) : Rend
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class SimpleBlobRenderer extends React.Component<{lcm : LineContainerModel, line : LineType, cmd : Cmd, plugin : RendererPluginType, onHeightChange : () => void}, {}> {
|
||||
class SimpleBlobRenderer extends React.Component<{lcm : LineContainerModel, line : LineType, cmd : Cmd, rendererOpts : RendererOpts, plugin : RendererPluginType, onHeightChange : () => void}, {}> {
|
||||
model : SimpleBlobRendererModel;
|
||||
wrapperDivRef : React.RefObject<any> = React.createRef();
|
||||
rszObs : ResizeObserver;
|
||||
|
||||
constructor(props : any) {
|
||||
super(props);
|
||||
let {lcm, line, cmd} = this.props;
|
||||
let {lcm, line, cmd, rendererOpts} = this.props;
|
||||
let context = contextFromLine(line);
|
||||
let savedHeight = lcm.getContentHeight(context);
|
||||
if (savedHeight == null) {
|
||||
@ -142,12 +142,7 @@ class SimpleBlobRenderer extends React.Component<{lcm : LineContainerModel, line
|
||||
context: context,
|
||||
isDone: !cmd.isRunning(),
|
||||
savedHeight: savedHeight,
|
||||
opts: {
|
||||
maxSize: lcm.getMaxContentSize(),
|
||||
idealSize: lcm.getIdealContentSize(),
|
||||
termOpts: cmd.getTermOpts(),
|
||||
termFontSize: GlobalModel.termFontSize.get(),
|
||||
},
|
||||
opts: rendererOpts,
|
||||
api: apiAdapter(lcm, line, cmd),
|
||||
};
|
||||
this.model = new SimpleBlobRendererModel();
|
||||
|
34
src/term.ts
34
src/term.ts
@ -3,9 +3,9 @@ import {Terminal} from 'xterm';
|
||||
import {sprintf} from "sprintf-js";
|
||||
import {boundMethod} from "autobind-decorator";
|
||||
import {v4 as uuidv4} from "uuid";
|
||||
import {GlobalModel, GlobalCommandRunner, termHeightFromRows, windowWidthToCols, windowHeightToRows, getPtyData, getRemotePtyData} from "./model";
|
||||
import {termHeightFromRows, windowWidthToCols, windowHeightToRows} from "./textmeasure";
|
||||
import {boundInt} from "./util";
|
||||
import type {TermOptsType, TermWinSize, RendererContext, WindowSize, PtyDataType} from "./types";
|
||||
import type {TermContextUnion, TermOptsType, TermWinSize, RendererContext, WindowSize, PtyDataType} from "./types";
|
||||
|
||||
type DataUpdate = {
|
||||
data : Uint8Array,
|
||||
@ -15,12 +15,8 @@ type DataUpdate = {
|
||||
const MinTermCols = 10;
|
||||
const MaxTermCols = 1024;
|
||||
|
||||
type RemoteTermContext = {remoteId : string};
|
||||
|
||||
type TermContext = RendererContext | RemoteTermContext;
|
||||
|
||||
type TermWrapOpts = {
|
||||
termContext : TermContext,
|
||||
termContext : TermContextUnion,
|
||||
usedRows? : number,
|
||||
termOpts : TermOptsType,
|
||||
winSize : WindowSize,
|
||||
@ -30,13 +26,14 @@ type TermWrapOpts = {
|
||||
isRunning : boolean,
|
||||
customKeyHandler? : (event : any, termWrap : TermWrap) => boolean,
|
||||
fontSize : number,
|
||||
noSetTUR? : boolean,
|
||||
ptyDataSource : (termContext : TermContextUnion) => Promise<PtyDataType>,
|
||||
onUpdateContentHeight : (termContext : RendererContext, height : number) => void,
|
||||
};
|
||||
|
||||
// cmd-instance
|
||||
class TermWrap {
|
||||
terminal : any;
|
||||
termContext : TermContext;
|
||||
termContext : TermContextUnion;
|
||||
atRowMax : boolean;
|
||||
usedRows : mobx.IObservableValue<number>;
|
||||
flexRows : boolean;
|
||||
@ -51,7 +48,8 @@ class TermWrap {
|
||||
focusHandler : (focus : boolean) => void;
|
||||
isRunning : boolean;
|
||||
fontSize : number;
|
||||
noSetTUR : boolean;
|
||||
onUpdateContentHeight : (termContext : RendererContext, height : number) => void;
|
||||
ptyDataSource : (termContext : TermContextUnion) => Promise<PtyDataType>;
|
||||
|
||||
constructor(elem : Element, opts : TermWrapOpts) {
|
||||
opts = opts ?? ({} as any);
|
||||
@ -62,7 +60,8 @@ class TermWrap {
|
||||
this.focusHandler = opts.focusHandler;
|
||||
this.isRunning = opts.isRunning;
|
||||
this.fontSize = opts.fontSize;
|
||||
this.noSetTUR = !!opts.noSetTUR;
|
||||
this.ptyDataSource = opts.ptyDataSource;
|
||||
this.onUpdateContentHeight = opts.onUpdateContentHeight;
|
||||
if (this.flexRows) {
|
||||
this.atRowMax = false;
|
||||
this.usedRows = mobx.observable.box(opts.usedRows ?? (opts.isRunning ? 1 : 0), {name: "term-usedrows"});
|
||||
@ -221,8 +220,8 @@ class TermWrap {
|
||||
return;
|
||||
}
|
||||
this.usedRows.set(tur);
|
||||
if (!this.noSetTUR) {
|
||||
GlobalModel.setContentHeight(termContext, tur);
|
||||
if (this.onUpdateContentHeight != null) {
|
||||
this.onUpdateContentHeight(termContext, tur);
|
||||
}
|
||||
})();
|
||||
}
|
||||
@ -272,14 +271,7 @@ class TermWrap {
|
||||
}
|
||||
this.reloading = true;
|
||||
this.terminal.reset();
|
||||
let rtnp : Promise<PtyDataType> = null;
|
||||
if (this.getContextRemoteId() != null) {
|
||||
rtnp = getRemotePtyData(this.getContextRemoteId());
|
||||
}
|
||||
else {
|
||||
let termContext = this.getRendererContext();
|
||||
rtnp = getPtyData(termContext.screenId, termContext.cmdId, termContext.lineNum);
|
||||
}
|
||||
let rtnp = this.ptyDataSource(this.termContext);
|
||||
rtnp.then((ptydata) => {
|
||||
setTimeout(() => {
|
||||
this._reloadThenHandler(ptydata);
|
||||
|
@ -1,3 +1,8 @@
|
||||
import {boundInt} from "./util";
|
||||
|
||||
const MinTermCols = 10;
|
||||
const MaxTermCols = 1024;
|
||||
|
||||
let MonoFontSizes : {height : number, width : number}[] = [];
|
||||
|
||||
MonoFontSizes[8] = {height: 11, width: 4.797};
|
||||
@ -41,4 +46,30 @@ function measureText(text : string, textOpts? : {pre? : boolean, mono? : boolean
|
||||
return measureDiv.getBoundingClientRect()
|
||||
}
|
||||
|
||||
export {measureText, getMonoFontSize};
|
||||
function windowWidthToCols(width : number, fontSize : number) : number {
|
||||
let dr = getMonoFontSize(fontSize);
|
||||
let cols = Math.trunc((width - 50) / dr.width) - 1;
|
||||
cols = boundInt(cols, MinTermCols, MaxTermCols);
|
||||
return cols;
|
||||
}
|
||||
|
||||
function windowHeightToRows(height : number, fontSize : number) : number {
|
||||
let dr = getMonoFontSize(fontSize);
|
||||
let rows = Math.floor((height - 80) / dr.height) - 1;
|
||||
if (rows <= 0) {
|
||||
rows = 1;
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
function termWidthFromCols(cols : number, fontSize : number) : number {
|
||||
let dr = getMonoFontSize(fontSize);
|
||||
return Math.ceil(dr.width*cols) + 15;
|
||||
}
|
||||
|
||||
function termHeightFromRows(rows : number, fontSize : number) : number {
|
||||
let dr = getMonoFontSize(fontSize);
|
||||
return Math.ceil(dr.height*rows);
|
||||
}
|
||||
|
||||
export {measureText, getMonoFontSize, windowWidthToCols, windowHeightToRows, termWidthFromCols, termHeightFromRows};
|
||||
|
54
src/types.ts
54
src/types.ts
@ -355,6 +355,10 @@ type RendererContext = {
|
||||
lineNum : number,
|
||||
};
|
||||
|
||||
type RemoteTermContext = {remoteId : string};
|
||||
|
||||
type TermContextUnion = RendererContext | RemoteTermContext;
|
||||
|
||||
type RendererOpts = {
|
||||
maxSize : WindowSize,
|
||||
idealSize : WindowSize,
|
||||
@ -482,4 +486,52 @@ type HistorySearchParams = {
|
||||
|
||||
type RenderModeType = "normal" | "collapsed";
|
||||
|
||||
export type {SessionDataType, LineType, RemoteType, RemoteStateType, RemoteInstanceType, HistoryItem, CmdRemoteStateType, FeCmdPacketType, TermOptsType, CmdStartPacketType, CmdDataType, ScreenDataType, ScreenOptsType, PtyDataUpdateType, ModelUpdateType, UpdateMessage, InfoType, CmdLineUpdateType, RemotePtrType, UIContextType, HistoryInfoType, HistoryQueryOpts, WatchScreenPacketType, TermWinSize, FeInputPacketType, RemoteInputPacketType, RemoteEditType, FeStateType, ContextMenuOpts, RendererContext, WindowSize, RendererModel, PtyDataType, BookmarkType, ClientDataType, PlaybookType, PlaybookEntryType, HistoryViewDataType, RenderModeType, AlertMessageType, HistorySearchParams, ScreenLinesType, FocusTypeStrs, HistoryTypeStrs, RendererOpts, RendererPluginType, SimpleBlobRendererComponent, RendererModelContainerApi, RendererModelInitializeParams, RendererOptsUpdate, ClientMigrationInfo, WebShareOpts, RemoteStatusTypeStrs};
|
||||
type WebScreen = {
|
||||
screenid : string,
|
||||
sharename : string,
|
||||
vts : number,
|
||||
};
|
||||
|
||||
type WebLine = {
|
||||
screenid : string,
|
||||
lineid : string,
|
||||
ts : number,
|
||||
linenum : number,
|
||||
linetype : string,
|
||||
text : string,
|
||||
contentheight : number,
|
||||
renderer : string,
|
||||
archived : boolean,
|
||||
vts : number,
|
||||
};
|
||||
|
||||
type WebRemote = {
|
||||
alias : string,
|
||||
canonicalname : string,
|
||||
name : string,
|
||||
};
|
||||
|
||||
type WebCmd = {
|
||||
screeid : string,
|
||||
lineid : string,
|
||||
remote : WebRemote,
|
||||
cmdstr : string,
|
||||
rawcmdstr : string,
|
||||
festate : FeStateType,
|
||||
termopts : TermOptsType,
|
||||
status : string,
|
||||
startpk : CmdStartPacketType,
|
||||
doneinfo : CmdDoneInfoType,
|
||||
rtnstate : boolean,
|
||||
rtnstatestr : string,
|
||||
vts : number,
|
||||
};
|
||||
|
||||
type WebFullScreen = {
|
||||
screen : WebScreen,
|
||||
lines : WebLine[],
|
||||
cmds : WebCmd[],
|
||||
vts : number,
|
||||
}
|
||||
|
||||
export type {SessionDataType, LineType, RemoteType, RemoteStateType, RemoteInstanceType, HistoryItem, CmdRemoteStateType, FeCmdPacketType, TermOptsType, CmdStartPacketType, CmdDataType, ScreenDataType, ScreenOptsType, PtyDataUpdateType, ModelUpdateType, UpdateMessage, InfoType, CmdLineUpdateType, RemotePtrType, UIContextType, HistoryInfoType, HistoryQueryOpts, WatchScreenPacketType, TermWinSize, FeInputPacketType, RemoteInputPacketType, RemoteEditType, FeStateType, ContextMenuOpts, RendererContext, WindowSize, RendererModel, PtyDataType, BookmarkType, ClientDataType, PlaybookType, PlaybookEntryType, HistoryViewDataType, RenderModeType, AlertMessageType, HistorySearchParams, ScreenLinesType, FocusTypeStrs, HistoryTypeStrs, RendererOpts, RendererPluginType, SimpleBlobRendererComponent, RendererModelContainerApi, RendererModelInitializeParams, RendererOptsUpdate, ClientMigrationInfo, WebShareOpts, RemoteStatusTypeStrs, WebFullScreen, WebScreen, WebLine, WebCmd, RemoteTermContext, TermContextUnion};
|
||||
|
52
src/util.ts
52
src/util.ts
@ -1,5 +1,13 @@
|
||||
import * as mobx from "mobx";
|
||||
import {sprintf} from "sprintf-js";
|
||||
import dayjs from "dayjs";
|
||||
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||
|
||||
dayjs.extend(localizedFormat)
|
||||
|
||||
function isBlank(s : string) : boolean {
|
||||
return (s == null || s == "");
|
||||
}
|
||||
|
||||
function handleNotOkResp(resp : any, url : URL) : Promise<any> {
|
||||
let errMsg = sprintf("Bad status code response from fetch '%s': code=%d %s", url.toString(), resp.status, resp.statusText);
|
||||
@ -249,4 +257,46 @@ function incObs(inum : mobx.IObservableValue<number>) {
|
||||
})();
|
||||
}
|
||||
|
||||
export {handleJsonFetchResponse, base64ToArray, genMergeData, genMergeDataMap, genMergeSimpleData, parseEnv0, boundInt, isModKeyPress, incObs};
|
||||
function loadFonts() {
|
||||
let jbmFontNormal = new FontFace("JetBrains Mono", "url('static/fonts/jetbrains-mono-v13-latin-regular.woff2')", {style: "normal", weight: "400"});
|
||||
let jbmFont200 = new FontFace("JetBrains Mono", "url('static/fonts/jetbrains-mono-v13-latin-200.woff2')", {style: "normal", weight: "200"});
|
||||
let jbmFont700 = new FontFace("JetBrains Mono", "url('static/fonts/jetbrains-mono-v13-latin-700.woff2')", {style: "normal", weight: "700"});
|
||||
let faFont = new FontFace("FontAwesome", "url(static/fonts/fontawesome-webfont-4.7.woff2)", {style: "normal", weight: "normal"});
|
||||
let docFonts : any = document.fonts; // work around ts typing issue
|
||||
docFonts.add(jbmFontNormal);
|
||||
docFonts.add(jbmFont200);
|
||||
docFonts.add(jbmFont700);
|
||||
docFonts.add(faFont);
|
||||
jbmFontNormal.load();
|
||||
jbmFont200.load();
|
||||
jbmFont700.load();
|
||||
faFont.load();
|
||||
}
|
||||
|
||||
const DOW_STRS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
||||
|
||||
function getTodayStr() : string {
|
||||
return getDateStr(new Date());
|
||||
}
|
||||
|
||||
function getYesterdayStr() : string {
|
||||
let d = new Date();
|
||||
d.setDate(d.getDate()-1);
|
||||
return getDateStr(d);
|
||||
}
|
||||
|
||||
function getDateStr(d : Date) : string {
|
||||
let yearStr = String(d.getFullYear());
|
||||
let monthStr = String(d.getMonth()+1);
|
||||
if (monthStr.length == 1) {
|
||||
monthStr = "0" + monthStr;
|
||||
}
|
||||
let dayStr = String(d.getDate());
|
||||
if (dayStr.length == 1) {
|
||||
dayStr = "0" + dayStr;
|
||||
}
|
||||
let dowStr = DOW_STRS[d.getDay()];
|
||||
return dowStr + " " + yearStr + "-" + monthStr + "-" + dayStr;
|
||||
}
|
||||
|
||||
export {handleJsonFetchResponse, base64ToArray, genMergeData, genMergeDataMap, genMergeSimpleData, parseEnv0, boundInt, isModKeyPress, incObs, isBlank, loadFonts, getTodayStr, getYesterdayStr, getDateStr};
|
||||
|
@ -6,6 +6,282 @@ import {boundMethod} from "autobind-decorator";
|
||||
import {If, For, When, Otherwise, Choose} from "tsx-control-statements/components";
|
||||
import cn from "classnames";
|
||||
import {WebShareModel} from "./webshare-model";
|
||||
import * as T from "./types";
|
||||
import {isBlank} from "./util";
|
||||
import {PluginModel} from "./plugins";
|
||||
import * as lineutil from "./lineutil";
|
||||
import * as util from "./util";
|
||||
|
||||
// TODO selection
|
||||
// TODO remotevars
|
||||
|
||||
function makeFullRemoteRef(ownerName : string, remoteRef : string, name : string) : string {
|
||||
if (isBlank(ownerName) && isBlank(name)) {
|
||||
return remoteRef;
|
||||
}
|
||||
if (!isBlank(ownerName) && isBlank(name)) {
|
||||
return ownerName + ":" + remoteRef;
|
||||
}
|
||||
if (isBlank(ownerName) && !isBlank(name)) {
|
||||
return remoteRef + ":" + name;
|
||||
}
|
||||
return ownerName + ":" + remoteRef + ":" + name;
|
||||
}
|
||||
|
||||
function replaceHomePath(path : string, homeDir : string) : string {
|
||||
if (path == homeDir) {
|
||||
return "~";
|
||||
}
|
||||
if (path.startsWith(homeDir + "/")) {
|
||||
return "~" + path.substr(homeDir.length);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
function getCwdStr(state : FeStateType) : string {
|
||||
if ((state == null || state.cwd == null) && remote != null) {
|
||||
return "~";
|
||||
}
|
||||
let cwd = "?";
|
||||
if (state && state.cwd) {
|
||||
cwd = state.cwd;
|
||||
}
|
||||
// if (remote && remote.remotevars.home) {
|
||||
// cwd = replaceHomePath(cwd, remote.remotevars.cwd)
|
||||
// }
|
||||
return cwd;
|
||||
}
|
||||
|
||||
function getRemoteStr(remote : WebRemote) : string {
|
||||
if (remote == null) {
|
||||
return "(invalid remote)";
|
||||
}
|
||||
let remoteRef = (!isBlank(remote.alias) ? remote.alias : remote.canonicalname);
|
||||
let fullRef = makeFullRemoteRef(null, remoteRef, remote.name);
|
||||
return fullRef;
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class Prompt extends React.Component<{remote : T.WebRemote, festate : T.FeStateType}, {}> {
|
||||
render() {
|
||||
let {remote, festate} = this.props;
|
||||
let remoteStr = getRemoteStr(remote);
|
||||
let cwd = getCwdStr(festate);
|
||||
let isRoot = false;
|
||||
// if (remote && remote.remotevars) {
|
||||
// if (remote.remotevars["sudo"] || remote.remotevars["bestuser"] == "root") {
|
||||
// isRoot = true;
|
||||
// }
|
||||
// }
|
||||
let remoteColorClass = (isRoot ? "color-red" : "color-green");
|
||||
if (remote && remote.remoteopts && remote.remoteopts.color) {
|
||||
remoteColorClass = "color-" + remote.remoteopts.color;
|
||||
}
|
||||
let remoteTitle : string = null;
|
||||
if (remote && remote.canonicalname) {
|
||||
remoteTitle = remote.canonicalname;
|
||||
}
|
||||
return (
|
||||
<span className="term-prompt"><span title={remoteTitle} className={cn("term-prompt-remote", remoteColorClass)}>[{remoteStr}]</span> <span className="term-prompt-cwd">{cwd}</span> <span className="term-prompt-end">{isRoot ? "#" : "$"}</span></span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class LineAvatar extends React.Component<{line : T.WebLine, cmd : T.WebCmd}, {}> {
|
||||
render() {
|
||||
let {line, cmd} = this.props;
|
||||
let lineNumStr = String(line.linenum);
|
||||
let status = (cmd != null ? cmd.status : "done");
|
||||
let rtnstate = (cmd != null ? cmd.rtnstate : false);
|
||||
let isComment = (line.linetype == "text");
|
||||
return (
|
||||
<div className={cn("avatar", "num-"+lineNumStr.length, "status-" + status, {"rtnstate": rtnstate})}>
|
||||
{lineNumStr}
|
||||
<If condition={status == "hangup" || status == "error"}>
|
||||
<i className="fa-sharp fa-solid fa-triangle-exclamation status-icon"/>
|
||||
</If>
|
||||
<If condition={status == "detached"}>
|
||||
<i className="fa-sharp fa-solid fa-rotate status-icon"/>
|
||||
</If>
|
||||
<If condition={isComment}>
|
||||
<i className="fa-sharp fa-solid fa-comment comment-icon"/>
|
||||
</If>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class WebLineCmdView extends React.Component<{line : T.WebLine, cmd : T.WebCmd}, {}> {
|
||||
isCmdExpanded : OV<boolean> = mobx.observable.box(false, {name: "cmd-expanded"});
|
||||
isOverflow : OV<boolean> = mobx.observable.box(false, {name: "line-overflow"});
|
||||
|
||||
renderSimple() {
|
||||
<div className={cn("web-line line", (line.linetype == "cmd" ? "line-cmd" : "line-text"))}>
|
||||
<LineAvatar line={line}/>
|
||||
</div>
|
||||
}
|
||||
|
||||
renderCmdText(cmd : Cmd, remote : WebRemote) : any {
|
||||
if (cmd == null) {
|
||||
return (
|
||||
<div className="metapart-mono cmdtext">
|
||||
<span className="term-bright-green">(cmd not found)</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (this.isCmdExpanded.get()) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div key="meta2" className="meta meta-line2">
|
||||
<div className="metapart-mono cmdtext">
|
||||
<Prompt remote={cmd.remote} festate={cmd.festate}/>
|
||||
</div>
|
||||
</div>
|
||||
<div key="meta3" className="meta meta-line3 cmdtext-expanded-wrapper">
|
||||
<div className="cmdtext-expanded">{getFullCmdText(cmd.cmdstr)}</div>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
let isMultiLine = lineutil.isMultiLineCmdText(cmd.cmdstr);
|
||||
return (
|
||||
<div key="meta2" className="meta meta-line2" ref={this.cmdTextRef}>
|
||||
<div className="metapart-mono cmdtext">
|
||||
<Prompt remote={cmd.remote} festate={cmd.festate}/>
|
||||
<span> </span>
|
||||
<span>{lineutil.getSingleLineCmdText(cmd.cmdstr)}</span>
|
||||
</div>
|
||||
<If condition={this.isOverflow.get() || isMultiLine}>
|
||||
<div className="cmdtext-overflow" onClick={this.handleExpandCmd}>...▼</div>
|
||||
</If>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderMetaWrap() {
|
||||
let {line, cmd} = this.props;
|
||||
let formattedTime = lineutil.getLineDateTimeStr(line.ts);
|
||||
let termOpts = cmd.termopts;
|
||||
let remote = cmd.remote;
|
||||
let renderer = line.renderer;
|
||||
return (
|
||||
<div key="meta" className="meta-wrap">
|
||||
<div key="meta1" className="meta meta-line1">
|
||||
<div className="ts">{formattedTime}</div>
|
||||
<div> </div>
|
||||
<If condition={!isBlank(renderer) && renderer != "terminal"}>
|
||||
<div className="renderer"><i className="fa-sharp fa-solid fa-fill"/>{renderer} </div>
|
||||
</If>
|
||||
<div className="termopts">
|
||||
({termOpts.rows}x{termOpts.cols})
|
||||
</div>
|
||||
</div>
|
||||
{this.renderCmdText(cmd, remote)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
let {line, cmd} = this.props;
|
||||
let model = WebShareModel;
|
||||
let isSelected = mobx.computed(() => (model.getSelectedLine() == line.linenum), {name: "computed-isSelected"}).get();
|
||||
let rendererPlugin : RendererPluginType = null;
|
||||
let isNoneRenderer = (line.renderer == "none");
|
||||
if (!isBlank(line.renderer) && line.renderer != "terminal" && !isNoneRenderer) {
|
||||
rendererPlugin = PluginModel.getRendererPluginByName(line.renderer);
|
||||
}
|
||||
let rendererType = lineutil.getRendererType(line);
|
||||
return (
|
||||
<div className={cn("web-line line line-cmd")}>
|
||||
<div key="focus" className={cn("focus-indicator", {"selected active": isSelected})}/>
|
||||
<div className="line-header">
|
||||
<LineAvatar line={line} cmd={cmd}/>
|
||||
{this.renderMetaWrap()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class WebLineTextView extends React.Component<{line : T.WebLine, cmd : T.WebCmd}, {}> {
|
||||
render() {
|
||||
let {line} = this.props;
|
||||
let isSelected = mobx.computed(() => (model.getSelectedLine() == line.linenum), {name: "computed-isSelected"}).get();
|
||||
return (
|
||||
<div className={cn("web-line line line-text")}>
|
||||
<div key="focus" className={cn("focus-indicator", {"selected active": isSelected})}/>
|
||||
<div className="line-header">
|
||||
<LineAvatar line={line}/>
|
||||
</div>
|
||||
<div>
|
||||
<div>{line.text}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class WebLineView extends React.Component<{line : T.WebLine, cmd : T.WebCmd}, {}> {
|
||||
render() {
|
||||
let {line} = this.props;
|
||||
if (line.linetype == "text") {
|
||||
return <WebLineTextView {...this.props}/>
|
||||
}
|
||||
if (line.linetype == "cmd") {
|
||||
return <WebLineCmdView {...this.props}/>
|
||||
}
|
||||
return (
|
||||
<div className="web-line line">invalid linetype "{line.linetype}"</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class WebScreenView extends React.Component<{screen : T.WebFullScreen}, {}> {
|
||||
render() {
|
||||
let {screen} = this.props;
|
||||
let lines = screen.lines ?? [];
|
||||
let cmds = screen.cmds ?? [];
|
||||
let cmdMap : Record<string, WebCmd> = {};
|
||||
for (let i=0; i<cmds.length; i++) {
|
||||
let cmd = cmds[i];
|
||||
cmdMap[cmd.lineid] = cmd;
|
||||
}
|
||||
let lineElements : any[] = [];
|
||||
let todayStr = util.getTodayStr();
|
||||
let yesterdayStr = util.getYesterdayStr();
|
||||
let prevDateStr : string = null;
|
||||
for (let idx=0; idx<lines.length; idx++) {
|
||||
let line = lines[idx];
|
||||
let lineNumStr = String(line.linenum);
|
||||
let dateSepStr = null;
|
||||
let curDateStr = lineutil.getLineDateStr(todayStr, yesterdayStr, line.ts);
|
||||
if (curDateStr != prevDateStr) {
|
||||
dateSepStr = curDateStr;
|
||||
}
|
||||
prevDateStr = curDateStr;
|
||||
if (dateSepStr != null) {
|
||||
let sepElem = <div key={"sep-" + line.lineid} className="line-sep">{dateSepStr}</div>
|
||||
lineElements.push(sepElem);
|
||||
}
|
||||
let topBorder = (dateSepStr == null) && (idx != 0);
|
||||
let lineElem = <WebLineView key={line.lineid} line={line} cmd={cmdMap[line.lineid]} topBorder={topBorder}/>;
|
||||
lineElements.push(lineElem);
|
||||
}
|
||||
return (
|
||||
<div className="web-screen-view">
|
||||
<div className="web-lines lines">
|
||||
<div className="lines-spacer"></div>
|
||||
{lineElements}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class WebShareMain extends React.Component<{}, {}> {
|
||||
@ -14,10 +290,17 @@ class WebShareMain extends React.Component<{}, {}> {
|
||||
}
|
||||
|
||||
render() {
|
||||
let screen = WebShareModel.screen.get();
|
||||
let errMessage = WebShareModel.errMessage.get();
|
||||
return (
|
||||
<div id="main">
|
||||
<div className="logo-header">
|
||||
<div className="logo-text">[prompt]</div>
|
||||
<div className="logo-text">
|
||||
<a target="_blank" href="https://www.getprompt.dev">[prompt]</a>
|
||||
</div>
|
||||
<If condition={screen != null}>
|
||||
<div className="screen-name">{screen.screen.sharename}</div>
|
||||
</If>
|
||||
<div className="flex-spacer"/>
|
||||
<a href="https://getprompt.dev/download/" target="_blank" className="download-button button is-link">
|
||||
<span>Download Prompt</span>
|
||||
@ -27,8 +310,12 @@ class WebShareMain extends React.Component<{}, {}> {
|
||||
</a>
|
||||
</div>
|
||||
<div className="prompt-content">
|
||||
<div>screenid={WebShareModel.screenId}, viewkey={WebShareModel.viewKey}</div>
|
||||
<div>{WebShareModel.errMessage.get()}</div>
|
||||
<If condition={screen != null}>
|
||||
<WebScreenView screen={screen}/>
|
||||
</If>
|
||||
<If condition={errMessage != null}>
|
||||
<div className="err-message">{WebShareModel.errMessage.get()}</div>
|
||||
</If>
|
||||
</div>
|
||||
<div className="prompt-footer">
|
||||
{this.renderCopy()}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import * as mobx from "mobx";
|
||||
import {boundMethod} from "autobind-decorator";
|
||||
import {handleJsonFetchResponse} from "./util";
|
||||
import * as T from "./types";
|
||||
|
||||
type OV<V> = mobx.IObservableValue<V>;
|
||||
type OArr<V> = mobx.IObservableArray<V>;
|
||||
@ -19,6 +20,7 @@ class WebShareModelClass {
|
||||
viewKey : string;
|
||||
screenId : string;
|
||||
errMessage : OV<string> = mobx.observable.box(null, {name: "errMessage"});
|
||||
screen : OV<T.WebFullScreen> = mobx.observable.box(null, {name: "webScreen"});
|
||||
|
||||
constructor() {
|
||||
let urlParams = new URLSearchParams(window.location.search);
|
||||
@ -34,6 +36,10 @@ class WebShareModelClass {
|
||||
})();
|
||||
}
|
||||
|
||||
getSelectedLine() : number {
|
||||
return 10;
|
||||
}
|
||||
|
||||
loadFullScreenData() : void {
|
||||
if (isBlank(this.screenId)) {
|
||||
this.setErrMessage("No ScreenId Specified, Cannot Load.");
|
||||
@ -46,7 +52,7 @@ class WebShareModelClass {
|
||||
let usp = new URLSearchParams({screenid: this.screenId, viewkey: this.viewKey});
|
||||
let url = new URL(getBaseUrl() + "/webshare/screen?" + usp.toString());
|
||||
fetch(url, {method: "GET", mode: "cors", cache: "no-cache"}).then((resp) => handleJsonFetchResponse(url, resp)).then((data) => {
|
||||
console.log("got data", data);
|
||||
mobx.action(() => this.screen.set(data))();
|
||||
}).catch((err) => {
|
||||
this.errMessage.set("Cannot get screen: " + err.message);
|
||||
});
|
||||
|
@ -3,12 +3,24 @@ import * as React from "react";
|
||||
import {createRoot} from 'react-dom/client';
|
||||
import {sprintf} from "sprintf-js";
|
||||
import {WebShareMain} from "./webshare-elems";
|
||||
import {loadFonts} from "./util";
|
||||
import {WebShareModel} from "./webshare-model";
|
||||
|
||||
loadFonts();
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
let elem = document.getElementById("app");
|
||||
let root = createRoot(elem);
|
||||
let reactElem = React.createElement(WebShareMain, null, null);
|
||||
let isFontLoaded = document.fonts.check("12px 'JetBrains Mono'");
|
||||
if (isFontLoaded) {
|
||||
root.render(reactElem);
|
||||
}
|
||||
else {
|
||||
document.fonts.ready.then(() => {
|
||||
root.render(reactElem);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
(window as any).mobx = mobx;
|
||||
|
BIN
webshare/static/fonts/fontawesome-webfont-4.7.woff2
Normal file
BIN
webshare/static/fonts/fontawesome-webfont-4.7.woff2
Normal file
Binary file not shown.
BIN
webshare/static/fonts/jetbrains-mono-v13-latin-200.woff
Normal file
BIN
webshare/static/fonts/jetbrains-mono-v13-latin-200.woff
Normal file
Binary file not shown.
BIN
webshare/static/fonts/jetbrains-mono-v13-latin-200.woff2
Normal file
BIN
webshare/static/fonts/jetbrains-mono-v13-latin-200.woff2
Normal file
Binary file not shown.
BIN
webshare/static/fonts/jetbrains-mono-v13-latin-700.woff
Normal file
BIN
webshare/static/fonts/jetbrains-mono-v13-latin-700.woff
Normal file
Binary file not shown.
BIN
webshare/static/fonts/jetbrains-mono-v13-latin-700.woff2
Normal file
BIN
webshare/static/fonts/jetbrains-mono-v13-latin-700.woff2
Normal file
Binary file not shown.
BIN
webshare/static/fonts/jetbrains-mono-v13-latin-regular.woff
Normal file
BIN
webshare/static/fonts/jetbrains-mono-v13-latin-regular.woff
Normal file
Binary file not shown.
BIN
webshare/static/fonts/jetbrains-mono-v13-latin-regular.woff2
Normal file
BIN
webshare/static/fonts/jetbrains-mono-v13-latin-regular.woff2
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user