2022-06-08 02:25:35 +02:00
import * as React from "react" ;
import * as mobxReact from "mobx-react" ;
import * as mobx from "mobx" ;
import { sprintf } from "sprintf-js" ;
import { boundMethod } from "autobind-decorator" ;
2022-10-08 03:25:47 +02:00
import { debounce , throttle } from "throttle-debounce" ;
2022-07-14 08:11:45 +02:00
import { v4 as uuidv4 } from "uuid" ;
2022-09-06 02:21:31 +02:00
import dayjs from "dayjs" ;
2022-06-08 02:25:35 +02:00
import { If , For , When , Otherwise , Choose } from "tsx-control-statements/components" ;
2022-09-06 02:21:31 +02:00
import cn from "classnames" ;
2022-06-17 01:34:46 +02:00
import { TermWrap } from "./term" ;
2023-02-21 07:32:11 +01:00
import type { SessionDataType , LineType , CmdDataType , RemoteType , RemoteStateType , RemoteInstanceType , RemotePtrType , HistoryItem , HistoryQueryOpts , RemoteEditType , FeStateType , ContextMenuOpts , BookmarkType } from "./types" ;
2022-06-18 02:54:14 +02:00
import localizedFormat from 'dayjs/plugin/localizedFormat' ;
2023-02-06 07:07:57 +01:00
import { GlobalModel , GlobalCommandRunner , Session , Cmd , Window , Screen , ScreenWindow , riToRPtr , windowWidthToCols , windowHeightToRows , termHeightFromRows , termWidthFromCols } from "./model" ;
2022-10-14 03:58:21 +02:00
import { isModKeyPress } from "./util" ;
2023-02-06 09:31:34 +01:00
import { ImageRendererModel } from "./imagerenderer" ;
2023-02-21 23:05:34 +01:00
import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
2023-02-23 18:21:37 +01:00
import { BookmarksView } from "./bookmarks" ;
2022-06-18 02:54:14 +02:00
dayjs . extend ( localizedFormat )
2022-06-13 20:12:39 +02:00
2022-09-16 02:10:02 +02:00
const RemotePtyRows = 8 ;
2022-09-15 09:18:20 +02:00
const RemotePtyCols = 80 ;
2022-10-04 04:05:52 +02:00
const PasswordUnchangedSentinel = "--unchanged--" ;
2022-10-08 03:25:47 +02:00
const LinesVisiblePadding = 500 ;
2022-09-15 09:18:20 +02:00
2022-09-30 23:57:23 +02:00
const RemoteColors = [ "red" , "green" , "yellow" , "blue" , "magenta" , "cyan" , "white" , "orange" ] ;
2022-10-08 03:25:47 +02:00
type OV < V > = mobx . IObservableValue < V > ;
type OArr < V > = mobx . IObservableArray < V > ;
type OMap < K , V > = mobx . ObservableMap < K , V > ;
2023-02-24 08:24:26 +01:00
type VisType = "visible" | "" ;
2023-02-28 00:52:55 +01:00
type RenderModeType = "normal" | "collapsed" ;
2023-02-24 08:24:26 +01:00
2022-10-08 03:25:47 +02:00
type HeightChangeCallbackType = ( lineNum : number , newHeight : number , oldHeight : number ) = > void ;
2023-02-16 05:32:17 +01:00
type RendererComponentProps = { sw : ScreenWindow , line : LineType , width : number , staticRender : boolean , visible : OV < boolean > , onHeightChange : HeightChangeCallbackType , collapsed : boolean } ;
2023-02-06 09:31:34 +01:00
type RendererComponentType = { new ( props : RendererComponentProps ) : React . Component < RendererComponentProps , {} > } ;
2022-08-13 03:34:56 +02:00
type InterObsValue = {
sessionid : string ,
windowid : string ,
lineid : string ,
cmdid : string ,
visible : mobx.IObservableValue < boolean > ,
timeoutid? : any ,
} ;
2022-08-18 09:39:06 +02:00
function isBlank ( s : string ) : boolean {
return ( s == null || s == "" ) ;
}
2022-08-29 22:54:11 +02:00
function scrollDiv ( div : any , amt : number ) {
if ( div == null ) {
return ;
}
let newScrollTop = div . scrollTop + amt ;
if ( newScrollTop < 0 ) {
newScrollTop = 0 ;
}
div . scrollTo ( { top : newScrollTop , behavior : "smooth" } ) ;
}
2022-08-30 00:42:50 +02:00
function pageSize ( div : any ) : number {
if ( div == null ) {
return 300 ;
}
let size = div . clientHeight ;
if ( size > 500 ) {
size = size - 100 ;
} else if ( size > 200 ) {
size = size - 30 ;
}
return size ;
}
2022-07-12 02:55:03 +02:00
function getLineId ( line : LineType ) : string {
return sprintf ( "%s-%s-%s" , line . sessionid , line . windowid , line . lineid ) ;
}
2022-08-24 22:19:59 +02:00
function makeFullRemoteRef ( ownerName : string , remoteRef : string , name : string ) : string {
if ( isBlank ( ownerName ) && isBlank ( name ) ) {
return remoteRef ;
2022-08-11 03:35:18 +02:00
}
2022-08-24 22:19:59 +02:00
if ( ! isBlank ( ownerName ) && isBlank ( name ) ) {
return ownerName + ":" + remoteRef ;
2022-08-11 03:35:18 +02:00
}
2022-08-24 22:19:59 +02:00
if ( isBlank ( ownerName ) && ! isBlank ( name ) ) {
return remoteRef + ":" + name ;
2022-08-11 03:35:18 +02:00
}
2022-08-24 22:19:59 +02:00
return ownerName + ":" + remoteRef + ":" + name ;
}
function getRemoteStr ( rptr : RemotePtrType ) : string {
if ( rptr == null || isBlank ( rptr . remoteid ) ) {
return "(invalid remote)" ;
2022-08-17 22:06:47 +02:00
}
2022-08-24 22:19:59 +02:00
let username = ( isBlank ( rptr . ownerid ) ? null : GlobalModel . resolveUserIdToName ( rptr . ownerid ) ) ;
let remoteRef = GlobalModel . resolveRemoteIdToRef ( rptr . remoteid ) ;
let fullRef = makeFullRemoteRef ( username , remoteRef , rptr . name ) ;
return fullRef ;
2022-08-11 03:35:18 +02:00
}
function replaceHomePath ( path : string , homeDir : string ) : string {
if ( path == homeDir ) {
return "~" ;
}
if ( path . startsWith ( homeDir + "/" ) ) {
return "~" + path . substr ( homeDir . length ) ;
}
return path ;
}
2022-11-29 03:08:19 +01:00
function getCwdStr ( remote : RemoteType , state : FeStateType ) : string {
2022-08-11 03:35:18 +02:00
if ( ( state == null || state . cwd == null ) && remote != null ) {
return "~" ;
}
let cwd = "(unknown)" ;
if ( state && state . cwd ) {
cwd = state . cwd ;
}
if ( remote && remote . remotevars . home ) {
2022-11-29 03:08:19 +01:00
cwd = replaceHomePath ( cwd , remote . remotevars . cwd )
2022-08-11 03:35:18 +02:00
}
return cwd ;
}
2023-02-16 05:32:17 +01:00
function getLineDateTimeStr ( ts : number ) : string {
2022-06-18 02:54:14 +02:00
let lineDate = new Date ( ts ) ;
let nowDate = new Date ( ) ;
2023-02-16 05:32:17 +01:00
2022-06-18 02:54:14 +02:00
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 ( ts ) . format ( "LTS" ) ;
}
}
2023-02-16 05:32:17 +01:00
function getTodayStr ( ) : string {
return getDateStr ( new Date ( ) ) ;
}
function getYesterdayStr ( ) : string {
let d = new Date ( ) ;
d . setDate ( d . getDate ( ) - 1 ) ;
return getDateStr ( d ) ;
}
const DOW_STRS = [ "Sun" , "Mon" , "Tue" , "Wed" , "Thu" , "Fri" , "Sat" ] ;
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 ;
}
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 ;
}
2022-10-28 00:25:58 +02:00
@mobxReact . observer
class LineAvatar extends React . Component < { line : LineType , cmd : Cmd } , { } > {
render() {
let { line , cmd } = this . props ;
let lineNumStr = ( line . linenumtemp ? "~" : "" ) + String ( line . linenum ) ;
let status = ( cmd != null ? cmd . getStatus ( ) : "done" ) ;
let rtnstate = ( cmd != null ? cmd . getRtnState ( ) : false ) ;
2023-02-28 00:52:55 +01:00
let isComment = ( line . linetype == "text" ) ;
2022-10-28 00:25:58 +02:00
return (
< div className = { cn ( "avatar" , "num-" + lineNumStr . length , "status-" + status , { "ephemeral" : line . ephemeral } , { "rtnstate" : rtnstate } ) } >
{ lineNumStr }
< If condition = { status == "hangup" || status == "error" } >
< i className = "fa fa-exclamation-triangle status-icon" / >
< / If >
< If condition = { status == "detached" } >
2023-02-27 21:22:45 +01:00
< i className = "fa fa-rotate status-icon" / >
2022-10-28 00:25:58 +02:00
< / If >
2023-02-28 00:52:55 +01:00
< If condition = { isComment } >
< i className = "fa-sharp fa-solid fa-comment comment-icon" / >
< / If >
2022-10-28 00:25:58 +02:00
< / div >
) ;
}
}
2022-06-08 02:25:35 +02:00
@mobxReact . observer
2023-02-28 00:52:55 +01:00
class LineText extends React . Component < { sw : ScreenWindow , line : LineType , renderMode : RenderModeType , topBorder : boolean } , { } > {
2022-12-06 19:24:44 +01:00
@boundMethod
clickHandler() {
let { line } = this . props ;
GlobalCommandRunner . swSelectLine ( String ( line . linenum ) ) ;
}
2022-06-08 02:25:35 +02:00
render() {
2023-02-28 00:52:55 +01:00
let { sw , line , topBorder , renderMode } = this . props ;
2023-02-16 05:32:17 +01:00
let formattedTime = getLineDateTimeStr ( line . ts ) ;
2023-02-01 07:22:51 +01:00
let isSelected = mobx . computed ( ( ) = > ( sw . selectedLine . get ( ) == line . linenum ) , { name : "computed-isSelected" } ) . get ( ) ;
let isFocused = mobx . computed ( ( ) = > ( sw . focusType . get ( ) == "cmd" ) , { name : "computed-isFocused" } ) . get ( ) ;
2023-02-28 00:52:55 +01:00
let isCollapsed = ( renderMode == "collapsed" ) ;
let mainClass = cn (
"line" ,
"line-text" ,
"focus-parent" ,
{ "top-border" : topBorder } ,
{ "collapsed" : isCollapsed } ,
) ;
2022-06-08 02:25:35 +02:00
return (
2023-02-28 00:52:55 +01:00
< div className = { mainClass } data - lineid = { line . lineid } data - linenum = { line . linenum } data - windowid = { line . windowid } onClick = { this . clickHandler } >
2022-10-11 02:29:59 +02:00
< div className = { cn ( "focus-indicator" , { "selected" : isSelected } , { "active" : isSelected && isFocused } ) } / >
2023-01-30 20:35:39 +01:00
< LineAvatar line = { line } cmd = { null } / >
2022-06-08 02:25:35 +02:00
< div className = "line-content" >
< div className = "meta" >
2022-06-18 02:54:14 +02:00
< div className = "ts" > { formattedTime } < / div >
2022-06-08 02:25:35 +02:00
< / div >
< div className = "text" >
{ line . text }
< / div >
< / div >
< / div >
) ;
}
}
2022-08-24 22:19:59 +02:00
@mobxReact . observer
2022-11-29 03:08:19 +01:00
class Prompt extends React . Component < { rptr : RemotePtrType , festate : FeStateType } , { } > {
2022-08-24 22:19:59 +02:00
render() {
2022-12-31 02:16:13 +01:00
let rptr = this . props . rptr ;
if ( rptr == null || isBlank ( rptr . remoteid ) ) {
return < span className = { cn ( "term-prompt" , "color-green" ) } > & nbsp ; < / span >
2022-08-24 22:19:59 +02:00
}
2022-12-31 02:16:13 +01:00
let remote = GlobalModel . getRemote ( this . props . rptr . remoteid ) ;
let remoteStr = getRemoteStr ( rptr ) ;
2022-11-29 03:08:19 +01:00
let cwd = getCwdStr ( remote , this . props . festate ) ;
2022-08-24 22:19:59 +02:00
let isRoot = false ;
if ( remote && remote . remotevars ) {
if ( remote . remotevars [ "sudo" ] || remote . remotevars [ "bestuser" ] == "root" ) {
isRoot = true ;
}
}
2022-12-29 09:06:53 +01:00
let colorClass = ( isRoot ? "color-red" : "color-green" ) ;
if ( remote && remote . remoteopts && remote . remoteopts . color ) {
colorClass = "color-" + remote . remoteopts . color ;
}
2023-02-28 00:52:55 +01:00
// TESTING cwd shortening with triple colon character
// if (cwd.startsWith("~/work/gopath/src/github.com/scripthaus-dev")) {
// cwd = cwd.replace("~/work/gopath/src/github.com/scripthaus-dev", "\u22EEscripthaus-dev");
// }
2022-08-24 22:19:59 +02:00
return (
2022-12-29 09:06:53 +01:00
< span className = { cn ( "term-prompt" , colorClass ) } > [ { remoteStr } ] { cwd } { isRoot ? "#" : "$" } < / span >
2022-08-24 22:19:59 +02:00
) ;
}
}
2023-02-06 09:31:34 +01:00
@mobxReact . observer
2023-02-16 05:32:17 +01:00
class ImageRenderer extends React . Component < { sw : ScreenWindow , line : LineType , width : number , staticRender : boolean , visible : OV < boolean > , onHeightChange : ( ) = > void , collapsed : boolean } , { } > {
2023-02-06 09:31:34 +01:00
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 {
2023-02-16 05:32:17 +01:00
let { line , staticRender , visible , collapsed } = this . props ;
2023-02-06 09:31:34 +01:00
if ( staticRender ) {
return ;
}
2023-02-16 05:32:17 +01:00
let vis = visible && visible . get ( ) && ! collapsed ;
2023-02-06 09:31:34 +01:00
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 ) {
2023-02-06 21:08:07 +01:00
let dataVersion = imageModel . dataBuf . dataVersion . get ( ) ;
2023-02-06 09:31:34 +01:00
}
2023-02-16 05:32:17 +01:00
let collapsed = this . props . collapsed ;
2023-02-06 09:31:34 +01:00
return (
2023-02-16 05:32:17 +01:00
< div ref = { this . elemRef } className = { cn ( "image-wrapper" , { "collapsed" : collapsed } ) } >
2023-02-06 09:31:34 +01:00
< div key = "imagediv" ref = { this . imageDivRef } > < / div >
< If condition = { ! isDone } > < div className = "loading-div" > . . . < / div > < / If >
< / div >
) ;
}
}
2023-02-04 03:12:50 +01:00
@mobxReact . observer
2023-02-16 05:32:17 +01:00
class TerminalRenderer extends React . Component < { sw : ScreenWindow , line : LineType , width : number , staticRender : boolean , visible : OV < boolean > , onHeightChange : ( ) = > void , collapsed : boolean } , { } > {
2023-02-04 03:29:07 +01:00
termLoaded : mobx.IObservableValue < boolean > = mobx . observable . box ( false , { name : "linecmd-term-loaded" } ) ;
elemRef : React.RefObject < any > = React . createRef ( ) ;
constructor ( props ) {
super ( props ) ;
}
componentDidMount() {
this . componentDidUpdate ( null , null , null ) ;
}
componentWillUnmount() {
if ( this . termLoaded . get ( ) ) {
this . unloadTerminal ( 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 ( ) ;
2023-02-06 07:07:57 +01:00
// console.log("term-render height change: ", line.linenum, snapshot.height, "=>", curHeight);
2023-02-04 03:29:07 +01:00
}
this . checkLoad ( ) ;
}
checkLoad ( ) : void {
2023-02-16 05:32:17 +01:00
let { line , staticRender , visible , collapsed } = this . props ;
2023-02-04 03:29:07 +01:00
if ( staticRender ) {
return ;
}
2023-02-16 05:32:17 +01:00
let vis = visible && visible . get ( ) && ! collapsed ;
2023-02-04 03:29:07 +01:00
let curVis = this . termLoaded . get ( ) ;
if ( vis && ! curVis ) {
this . loadTerminal ( ) ;
}
else if ( ! vis && curVis ) {
this . unloadTerminal ( false ) ;
}
}
loadTerminal ( ) : void {
let { sw , line } = this . props ;
let model = GlobalModel ;
let cmd = model . getCmd ( line ) ;
if ( cmd == null ) {
return ;
}
let termId = "term-" + getLineId ( line ) ;
let termElem = document . getElementById ( termId ) ;
if ( termElem == null ) {
console . log ( "cannot load terminal, no term elem found" , termId ) ;
return ;
}
2023-02-06 08:01:02 +01:00
sw . loadTerminalRenderer ( termElem , line , cmd , this . props . width ) ;
2023-02-04 03:29:07 +01:00
mobx . action ( ( ) = > this . termLoaded . set ( true ) ) ( ) ;
}
unloadTerminal ( unmount : boolean ) : void {
let { sw , line } = this . props ;
2023-02-06 07:07:57 +01:00
sw . unloadRenderer ( line . cmdid ) ;
2023-02-04 03:29:07 +01:00
if ( ! unmount ) {
mobx . action ( ( ) = > this . termLoaded . set ( false ) ) ( ) ;
let termId = "term-" + getLineId ( line ) ;
let termElem = document . getElementById ( termId ) ;
if ( termElem != null ) {
termElem . replaceChildren ( ) ;
}
}
}
@boundMethod
clickTermBlock ( e : any ) {
let { sw , line } = this . props ;
let model = GlobalModel ;
let termWrap = sw . getRenderer ( line . cmdid ) ;
if ( termWrap != null ) {
termWrap . giveFocus ( ) ;
}
}
2023-02-04 03:12:50 +01:00
render() {
2023-02-16 05:32:17 +01:00
let { sw , line , width , staticRender , visible , collapsed } = this . props ;
2023-02-06 07:07:57 +01:00
let isVisible = visible . get ( ) ; // for reaction
2023-02-04 03:29:07 +01:00
let isPhysicalFocused = mobx . computed ( ( ) = > sw . getIsFocused ( line . linenum ) , { name : "computed-getIsFocused" } ) . get ( ) ;
let isFocused = mobx . computed ( ( ) = > {
let swFocusType = sw . focusType . get ( ) ;
return isPhysicalFocused && ( swFocusType == "cmd" || swFocusType == "cmd-fg" )
} , { name : "computed-isFocused" } ) . get ( ) ;
let cmd = GlobalModel . getCmd ( line ) ; // will not be null
let usedRows = sw . getUsedRows ( line , cmd , width ) ;
2023-02-26 08:16:58 +01:00
let termHeight = termHeightFromRows ( usedRows , GlobalModel . termFontSize . get ( ) ) ;
2023-02-04 03:29:07 +01:00
let termLoaded = this . termLoaded . get ( ) ;
return (
2023-02-16 05:32:17 +01:00
< div ref = { this . elemRef } key = "term-wrap" className = { cn ( "terminal-wrapper" , { "focus" : isFocused } , { "cmd-done" : ! cmd . isRunning ( ) } , { "zero-height" : ( termHeight == 0 ) } , { "collapsed" : collapsed } ) } >
2023-02-04 03:29:07 +01:00
< If condition = { ! isFocused } >
< div key = "term-block" className = "term-block" onClick = { this . clickTermBlock } > < / div >
< / If >
< div key = "term-connectelem" className = "terminal-connectelem" id = { "term-" + getLineId ( line ) } data - cmdid = { line . cmdid } style = { { height : termHeight } } > < / div >
< If condition = { ! termLoaded } > < div key = "term-loading" className = "terminal-loading-message" > . . . < / div > < / If >
< / div >
) ;
2023-02-04 03:12:50 +01:00
}
}
@mobxReact . observer
class MarkdownRenderer extends React . Component < { sw : ScreenWindow , line : LineType , width : number , staticRender : boolean , visible : OV < boolean > , onHeightChange : HeightChangeCallbackType } , { } > {
render() {
return null ;
}
}
2022-06-08 02:25:35 +02:00
@mobxReact . observer
2023-02-28 00:52:55 +01:00
class LineCmd extends React . Component < { sw : ScreenWindow , line : LineType , width : number , staticRender : boolean , visible : OV < boolean > , onHeightChange : HeightChangeCallbackType , topBorder : boolean , renderMode : RenderModeType , overrideCollapsed : OV < boolean > } , { } > {
2022-08-13 03:34:56 +02:00
lineRef : React.RefObject < any > = React . createRef ( ) ;
2023-02-27 09:30:17 +01:00
cmdTextRef : React.RefObject < any > = React . createRef ( ) ;
2022-11-23 07:57:35 +01:00
rtnStateDiff : mobx.IObservableValue < string > = mobx . observable . box ( null , { name : "linecmd-rtn-state-diff" } ) ;
2022-10-27 09:36:03 +02:00
rtnStateDiffFetched : boolean = false ;
2023-02-04 03:29:07 +01:00
lastHeight : number ;
2023-02-27 09:30:17 +01:00
isOverflow : OV < boolean > = mobx . observable . box ( false , { name : "line-overflow" } ) ;
isCmdExpanded : OV < boolean > = mobx . observable . box ( false , { name : "cmd-expanded" } ) ;
2022-07-13 09:44:19 +02:00
2022-06-16 09:31:54 +02:00
constructor ( props ) {
super ( props ) ;
2022-10-08 03:25:47 +02:00
}
2022-10-27 09:36:03 +02:00
checkStateDiffLoad ( ) : void {
2023-02-28 00:52:55 +01:00
let { line , staticRender , visible } = this . props ;
if ( staticRender || this . isCollapsed ( ) ) {
2022-10-27 09:36:03 +02:00
return ;
}
if ( ! visible ) {
if ( this . rtnStateDiffFetched ) {
this . rtnStateDiffFetched = false ;
this . setRtnStateDiff ( null ) ;
}
return ;
}
let cmd = GlobalModel . getCmd ( line ) ;
if ( cmd == null || ! cmd . getRtnState ( ) || this . rtnStateDiffFetched ) {
return ;
}
if ( cmd . getStatus ( ) != "done" ) {
return ;
}
this . fetchRtnStateDiff ( ) ;
}
fetchRtnStateDiff ( ) : void {
if ( this . rtnStateDiffFetched ) {
return ;
}
let { line } = this . props ;
this . rtnStateDiffFetched = true ;
let usp = new URLSearchParams ( { sessionid : line.sessionid , cmdid : line.cmdid } ) ;
2022-12-28 22:47:51 +01:00
let url = GlobalModel . getBaseHostPort ( ) + "/api/rtnstate?" + usp . toString ( ) ;
2022-12-21 01:22:05 +01:00
let fetchHeaders = GlobalModel . getFetchHeaders ( ) ;
fetch ( url , { headers : fetchHeaders } ) . then ( ( resp ) = > {
2022-10-27 09:36:03 +02:00
if ( ! resp . ok ) {
throw new Error ( sprintf ( "Bad fetch response for /api/rtnstate: %d %s" , resp . status , resp . statusText ) ) ;
}
return resp . text ( ) ;
} ) . then ( ( text ) = > {
this . setRtnStateDiff ( text ? ? "" ) ;
} ) . catch ( ( err ) = > {
this . setRtnStateDiff ( "ERROR " + err . toString ( ) )
} ) ;
}
setRtnStateDiff ( val : string ) : void {
mobx . action ( ( ) = > {
this . rtnStateDiff . set ( val ) ;
} ) ( ) ;
}
2022-08-13 03:34:56 +02:00
componentDidMount() {
2022-12-25 22:03:34 +01:00
this . componentDidUpdate ( null , null , null ) ;
2023-02-27 09:30:17 +01:00
this . checkCmdText ( ) ;
2022-06-15 01:02:20 +02:00
}
2022-10-08 03:25:47 +02:00
// FIXME
2022-06-18 02:54:14 +02:00
scrollIntoView() {
let lineElem = document . getElementById ( "line-" + getLineId ( this . props . line ) ) ;
lineElem . scrollIntoView ( { block : "end" } ) ;
2022-06-08 02:25:35 +02:00
}
2022-06-13 20:12:39 +02:00
@boundMethod
doRefresh() {
2022-07-13 09:44:19 +02:00
let { sw , line } = this . props ;
2022-07-12 02:55:03 +02:00
let model = GlobalModel ;
2023-02-04 03:12:50 +01:00
let termWrap = sw . getRenderer ( line . cmdid ) ;
2022-08-13 03:34:56 +02:00
if ( termWrap != null ) {
2023-02-04 01:26:56 +01:00
termWrap . reload ( 500 ) ;
2022-07-12 02:55:03 +02:00
}
2022-06-15 01:02:20 +02:00
}
2023-02-27 09:30:17 +01:00
@boundMethod
handleExpandCmd ( ) : void {
mobx . action ( ( ) = > {
this . isCmdExpanded . set ( true ) ;
} ) ( ) ;
}
2022-07-11 23:43:18 +02:00
renderCmdText ( cmd : Cmd , remote : RemoteType ) : any {
2022-07-07 22:27:44 +02:00
if ( cmd == null ) {
return (
< div className = "metapart-mono cmdtext" >
< span className = "term-bright-green" > ( cmd not found ) < / span >
< / div >
) ;
}
2023-02-27 09:30:17 +01:00
if ( this . isCmdExpanded . get ( ) ) {
return (
< React.Fragment >
< div key = "meta2" className = "meta meta-line2" >
< div className = "metapart-mono cmdtext" >
< Prompt rptr = { cmd . remote } festate = { cmd . getRemoteFeState ( ) } / >
< / div >
< / div >
< div key = "meta3" className = "meta meta-line3 cmdtext-expanded-wrapper" >
< div className = "cmdtext-expanded" > { cmd . getFullCmdText ( ) } < / div >
< / div >
< / React.Fragment >
) ;
}
let isMultiLine = cmd . isMultiLineCmdText ( ) ;
2022-07-07 22:27:44 +02:00
return (
2023-02-27 09:30:17 +01:00
< 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 >
< / div >
< If condition = { this . isOverflow . get ( ) || isMultiLine } >
< div className = "cmdtext-overflow" onClick = { this . handleExpandCmd } > . . . & # x25BC ; < / div >
< / If >
2022-07-07 22:27:44 +02:00
< / div >
) ;
}
2022-08-25 04:00:03 +02:00
2023-02-04 03:29:07 +01:00
// TODO: this might not be necessary anymore because we're using this.lastHeight
2022-10-08 03:25:47 +02:00
getSnapshotBeforeUpdate ( prevProps , prevState ) : { height : number } {
let elem = this . lineRef . current ;
if ( elem == null ) {
return { height : 0 } ;
}
return { height : elem.offsetHeight } ;
}
componentDidUpdate ( prevProps , prevState , snapshot : { height : number } ) : void {
2023-02-04 03:29:07 +01:00
this . handleHeightChange ( ) ;
this . checkStateDiffLoad ( ) ;
2023-02-27 09:30:17 +01:00
this . checkCmdText ( ) ;
}
checkCmdText() {
let metaElem = this . cmdTextRef . current ;
if ( metaElem == null || metaElem . childNodes . length == 0 ) {
return ;
}
let metaElemWidth = metaElem . offsetWidth ;
let metaChild = metaElem . firstChild ;
let children = metaChild . childNodes ;
let childWidth = 0 ;
for ( let i = 0 ; i < children.length ; i + + ) {
let ch = children [ i ] ;
childWidth += ch . offsetWidth ;
}
let isOverflow = ( childWidth > metaElemWidth ) ;
if ( isOverflow != this . isOverflow . get ( ) ) {
mobx . action ( ( ) = > {
this . isOverflow . set ( isOverflow ) ;
} ) ( ) ;
}
2023-02-04 03:29:07 +01:00
}
@boundMethod
handleHeightChange() {
if ( this . props . onHeightChange == null ) {
return ;
}
2022-10-08 03:25:47 +02:00
let { line } = this . props ;
let curHeight = 0 ;
let elem = this . lineRef . current ;
if ( elem != null ) {
curHeight = elem . offsetHeight ;
}
2023-02-04 03:29:07 +01:00
if ( this . lastHeight == curHeight ) {
return ;
2022-10-08 03:25:47 +02:00
}
2023-02-04 03:29:07 +01:00
let lastHeight = this . lastHeight ;
this . lastHeight = curHeight ;
this . props . onHeightChange ( line . linenum , curHeight , lastHeight ) ;
// console.log("line height change: ", line.linenum, lastHeight, "=>", curHeight);
2022-10-08 03:25:47 +02:00
}
2022-10-28 00:25:58 +02:00
@boundMethod
handleClick() {
let { line } = this . props ;
2023-01-02 23:08:24 +01:00
let sel = window . getSelection ( ) ;
if ( this . lineRef . current != null ) {
let selText = sel . toString ( ) ;
if ( sel . anchorNode != null && this . lineRef . current . contains ( sel . anchorNode ) && ! isBlank ( selText ) ) {
return ;
}
}
2022-10-28 00:25:58 +02:00
GlobalCommandRunner . swSelectLine ( String ( line . linenum ) , "cmd" ) ;
}
2022-12-06 07:59:31 +01:00
@boundMethod
clickStar() {
let { line } = this . props ;
if ( ! line . star || line . star == 0 ) {
GlobalCommandRunner . lineStar ( line . lineid , 1 ) ;
}
else {
GlobalCommandRunner . lineStar ( line . lineid , 0 ) ;
}
}
2023-02-01 06:25:11 +01:00
2023-02-21 03:01:47 +01:00
@boundMethod
clickPin() {
let { line } = this . props ;
if ( ! line . pinned ) {
GlobalCommandRunner . linePin ( line . lineid , true ) ;
}
else {
GlobalCommandRunner . linePin ( line . lineid , false ) ;
}
}
@boundMethod
clickBookmark() {
let { line } = this . props ;
GlobalCommandRunner . lineBookmark ( line . lineid ) ;
}
2023-02-01 06:25:11 +01:00
@boundMethod
handleResizeButton() {
console . log ( "resize button" ) ;
}
2023-02-16 05:32:17 +01:00
@boundMethod
handleCollapsedClick() {
2023-02-28 00:52:55 +01:00
let { overrideCollapsed } = this . props ;
2023-02-16 05:32:17 +01:00
mobx . action ( ( ) = > {
2023-02-28 00:52:55 +01:00
let isCollapsed = overrideCollapsed . get ( ) ;
overrideCollapsed . set ( ! isCollapsed ) ;
2023-02-16 05:32:17 +01:00
} ) ( ) ;
}
2023-02-24 08:24:26 +01:00
getLineDomId ( ) : string {
let { line } = this . props ;
return "line-" + getLineId ( line ) ;
}
2023-02-28 00:52:55 +01:00
isCollapsed ( ) : boolean {
let { renderMode , overrideCollapsed } = this . props ;
return ( renderMode == "collapsed" && ! overrideCollapsed . get ( ) ) ;
}
2023-02-24 08:24:26 +01:00
renderSimple() {
2023-02-28 00:52:55 +01:00
let { sw , line , width , topBorder , renderMode } = this . props ;
2023-02-24 08:24:26 +01:00
let cmd = GlobalModel . getCmd ( line ) ;
2023-02-28 00:52:55 +01:00
let isCollapsed = this . isCollapsed ( ) ;
2023-02-24 08:24:26 +01:00
let mainDivCn = cn (
"line" ,
"line-cmd" ,
{ "top-border" : topBorder } ,
2023-02-28 00:52:55 +01:00
{ "collapsed" : isCollapsed } ,
2023-02-24 08:24:26 +01:00
) ;
// header is 36px tall, padding+border = 6px
2023-02-28 00:52:55 +01:00
// collapsed header is 24px tall + 6px
2023-02-24 08:24:26 +01:00
// zero-terminal is 0px
// terminal-wrapper overhead is 11px (margin/padding)
// inner-height, if zero-lines => 42
2023-02-26 08:16:58 +01:00
// else: 53+(lines*lineheight)
2023-02-28 00:52:55 +01:00
let height = ( isCollapsed ? 30 : 42 ) ; // height of zero height terminal
if ( ! isCollapsed ) {
let usedRows = sw . getUsedRows ( line , cmd , width ) ;
if ( usedRows > 0 ) {
height = 53 + termHeightFromRows ( usedRows , GlobalModel . termFontSize . get ( ) ) ;
}
2023-02-24 08:24:26 +01:00
}
return (
< div className = { mainDivCn } id = { this . getLineDomId ( ) } ref = { this . lineRef } data - lineid = { line . lineid } data - linenum = { line . linenum } data - windowid = { line . windowid } style = { { height : height } } >
< LineAvatar line = { line } cmd = { cmd } / >
< / div >
) ;
}
2023-02-27 09:30:17 +01:00
renderMetaWrap ( cmd : Cmd ) {
let { line } = this . props ;
let model = GlobalModel ;
let formattedTime = getLineDateTimeStr ( line . ts ) ;
let termOpts = cmd . getTermOpts ( ) ;
let remote = model . getRemote ( cmd . remoteId ) ;
return (
< div key = "meta" className = "meta-wrap" >
< div key = "meta1" className = "meta meta-line1" >
< div className = "ts" > { formattedTime } < / div >
< div > & nbsp ; < / div >
< div className = "termopts" >
( { termOpts . rows } x { termOpts . cols } )
< If condition = { cmd . isRunning ( ) && false } > < i onClick = { this . handleResizeButton } className = "resize-button fa fa-arrows-alt" / > < / If >
< / div >
< / div >
{ this . renderCmdText ( cmd , remote ) }
< / div >
) ;
}
2022-06-08 02:25:35 +02:00
render() {
2023-02-28 00:52:55 +01:00
let { sw , line , width , staticRender , visible , topBorder , renderMode } = this . props ;
2022-07-12 02:55:03 +02:00
let model = GlobalModel ;
2022-08-17 00:59:28 +02:00
let lineid = line . lineid ;
2022-10-11 10:11:36 +02:00
let isVisible = visible . get ( ) ;
2023-02-24 08:24:26 +01:00
if ( staticRender || ! isVisible ) {
return this . renderSimple ( ) ;
}
2023-02-16 05:32:17 +01:00
let formattedTime = getLineDateTimeStr ( line . ts ) ;
2022-07-12 02:55:03 +02:00
let cmd = model . getCmd ( line ) ;
if ( cmd == null ) {
2022-08-13 03:34:56 +02:00
return (
2023-02-24 08:24:26 +01:00
< div className = "line line-invalid" id = { this . getLineDomId ( ) } ref = { this . lineRef } data - lineid = { line . lineid } data - linenum = { line . linenum } data - windowid = { line . windowid } >
2022-08-13 03:34:56 +02:00
[ cmd not found '{line.cmdid}' ]
< / div >
) ;
2022-07-07 22:27:44 +02:00
}
2022-07-12 02:55:03 +02:00
let status = cmd . getStatus ( ) ;
2022-09-21 01:51:42 +02:00
let lineNumStr = ( line . linenumtemp ? "~" : "" ) + String ( line . linenum ) ;
2023-01-24 08:34:29 +01:00
let isSelected = mobx . computed ( ( ) = > ( sw . selectedLine . get ( ) == line . linenum ) , { name : "computed-isSelected" } ) . get ( ) ;
let isPhysicalFocused = mobx . computed ( ( ) = > sw . getIsFocused ( line . linenum ) , { name : "computed-getIsFocused" } ) . get ( ) ;
let isFocused = mobx . computed ( ( ) = > {
let swFocusType = sw . focusType . get ( ) ;
return isPhysicalFocused && ( swFocusType == "cmd" || swFocusType == "cmd-fg" )
} , { name : "computed-isFocused" } ) . get ( ) ;
let isFgFocused = mobx . computed ( ( ) = > {
let swFocusType = sw . focusType . get ( ) ;
return isPhysicalFocused && swFocusType == "cmd-fg"
} , { name : "computed-isFgFocused" } ) . get ( ) ;
2022-10-08 03:25:47 +02:00
let isStatic = staticRender ;
2023-02-16 05:32:17 +01:00
let isRunning = cmd . isRunning ( )
2023-02-28 00:52:55 +01:00
let isCollapsed = this . isCollapsed ( ) ;
let isExpanded = this . isCmdExpanded . get ( ) ;
2022-11-23 07:57:35 +01:00
let rsdiff = this . rtnStateDiff . get ( ) ;
// console.log("render", "#" + line.linenum, termHeight, usedRows, cmd.getStatus(), (this.rtnStateDiff.get() != null), (!cmd.isRunning() ? "cmd-done" : "running"));
let mainDivCn = cn (
"line" ,
"line-cmd" ,
{ "focus" : isFocused } ,
2023-02-16 05:32:17 +01:00
{ "cmd-done" : ! isRunning } ,
2022-11-23 07:57:35 +01:00
{ "has-rtnstate" : cmd . getRtnState ( ) } ,
2023-02-16 05:32:17 +01:00
{ "collapsed" : isCollapsed } ,
{ "top-border" : topBorder } ,
2022-11-23 07:57:35 +01:00
) ;
2023-02-06 09:31:34 +01:00
let RendererComponent : RendererComponentType = TerminalRenderer ;
if ( line . renderer == "image" ) {
RendererComponent = ImageRenderer ;
2023-02-27 09:30:17 +01:00
}
2022-06-08 02:25:35 +02:00
return (
2022-11-23 07:57:35 +01:00
< div className = { mainDivCn } id = { "line-" + getLineId ( line ) }
ref = { this . lineRef } onClick = { this . handleClick }
data - lineid = { line . lineid } data - linenum = { line . linenum } data - windowid = { line . windowid } data - cmdid = { line . cmdid } >
2023-01-02 23:08:24 +01:00
< div key = "focus" className = { cn ( "focus-indicator" , { "selected" : isSelected } , { "active" : isSelected && isFocused } , { "fg-focus" : isFgFocused } ) } / >
2023-02-28 00:52:55 +01:00
< div key = "header" className = { cn ( "line-header" , { "is-expanded" : isExpanded } , { "is-collapsed" : isCollapsed } ) } >
2022-10-28 00:25:58 +02:00
< LineAvatar line = { line } cmd = { cmd } / >
2023-02-28 00:52:55 +01:00
< 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 fa-caret-right" / > < / If >
< If condition = { ! isCollapsed } > < i className = "fa fa-caret-down" / > < / If >
< / div >
< / If >
2023-02-27 09:30:17 +01:00
{ this . renderMetaWrap ( cmd ) }
2023-02-22 07:10:45 +01:00
< div key = "pin" title = "Pin" className = { cn ( "line-icon" , { "active" : line . pinned } ) } onClick = { this . clickPin } style = { { display : "none" } } >
2023-02-21 03:01:47 +01:00
< i className = "fa fa-thumb-tack" / >
< / div >
< div key = "bookmark" title = "Bookmark" className = { cn ( "line-icon" , "line-bookmark" , { "active" : line . bookmarked } ) } onClick = { this . clickBookmark } >
< If condition = { ! line . bookmarked } >
2023-02-27 21:22:45 +01:00
< i className = "fa-sharp fa-regular fa-bookmark" / >
2022-12-06 07:59:31 +01:00
< / If >
2023-02-21 03:01:47 +01:00
< If condition = { line . bookmarked } >
2023-02-27 21:22:45 +01:00
< i className = "fa-sharp fa-solid fa-bookmark" / >
2022-12-06 07:59:31 +01:00
< / If >
< / div >
2022-06-08 02:25:35 +02:00
< / div >
2023-02-16 05:32:17 +01:00
< RendererComponent sw = { sw } line = { line } width = { width } staticRender = { staticRender } visible = { visible } onHeightChange = { this . handleHeightChange } collapsed = { isCollapsed } / >
< If condition = { ! isCollapsed && cmd . getRtnState ( ) } >
2023-01-02 23:08:24 +01:00
< div key = "rtnstate" className = "cmd-rtnstate" style = { { visibility : ( ( cmd . getStatus ( ) == "done" ) ? "visible" : "hidden" ) } } >
2022-11-23 07:57:35 +01:00
< If condition = { rsdiff == null || rsdiff == "" } >
2022-10-27 09:36:03 +02:00
< div className = "cmd-rtnstate-label" > state unchanged < / div >
< div className = "cmd-rtnstate-sep" > < / div >
< / If >
2022-11-23 07:57:35 +01:00
< If condition = { rsdiff != null && rsdiff != "" } >
2022-10-27 09:36:03 +02:00
< div className = "cmd-rtnstate-label" > new state < / div >
< div className = "cmd-rtnstate-sep" > < / div >
< div className = "cmd-rtnstate-diff" > { this . rtnStateDiff . get ( ) } < / div >
< / If >
< / div >
< / If >
2022-06-08 02:25:35 +02:00
< / div >
) ;
}
}
@mobxReact . observer
2023-02-28 00:52:55 +01:00
class Line extends React . Component < { sw : ScreenWindow , line : LineType , width : number , staticRender : boolean , visible : OV < boolean > , onHeightChange : HeightChangeCallbackType , overrideCollapsed : OV < boolean > , topBorder : boolean , renderMode : RenderModeType } , { } > {
2022-06-08 02:25:35 +02:00
render() {
let line = this . props . line ;
2022-12-28 07:55:42 +01:00
if ( line . archived ) {
2022-12-22 07:05:05 +01:00
return null ;
}
2022-06-08 02:25:35 +02:00
if ( line . linetype == "text" ) {
return < LineText { ...this.props } / > ;
}
if ( line . linetype == "cmd" ) {
return < LineCmd { ...this.props } / > ;
}
return < div className = "line line-invalid" > [ invalid line type '{line.linetype}' ] < / div > ;
}
}
@mobxReact . observer
2023-02-24 02:51:59 +01:00
class TextAreaInput extends React . Component < { onHeightChange : ( ) = > void } , { } > {
2022-08-12 20:44:29 +02:00
lastTab : boolean = false ;
lastHistoryUpDown : boolean = false ;
2022-08-11 03:35:18 +02:00
lastTabCurLine : mobx.IObservableValue < string > = mobx . observable . box ( null ) ;
2022-10-11 08:44:04 +02:00
lastFocusType : string = null ;
mainInputRef : React.RefObject < any > ;
historyInputRef : React.RefObject < any > ;
2023-02-24 02:51:59 +01:00
controlRef : React.RefObject < any > ;
lastHeight : number = 0 ;
2022-10-11 08:44:04 +02:00
constructor ( props ) {
super ( props ) ;
this . mainInputRef = React . createRef ( ) ;
this . historyInputRef = React . createRef ( ) ;
2023-02-24 02:51:59 +01:00
this . controlRef = React . createRef ( ) ;
2022-10-11 08:44:04 +02:00
}
setFocus ( ) : void {
let inputModel = GlobalModel . inputModel ;
if ( inputModel . historyShow . get ( ) ) {
this . historyInputRef . current . focus ( ) ;
}
else {
this . mainInputRef . current . focus ( ) ;
}
}
2023-02-24 02:51:59 +01:00
checkHeight ( shouldFire : boolean ) : void {
let elem = this . controlRef . current ;
if ( elem == null ) {
return ;
}
let curHeight = elem . offsetHeight ;
if ( this . lastHeight == curHeight ) {
return ;
}
this . lastHeight = curHeight ;
if ( shouldFire && this . props . onHeightChange != null ) {
this . props . onHeightChange ( ) ;
}
}
2022-08-24 02:27:12 +02:00
componentDidMount() {
2022-10-11 08:44:04 +02:00
let activeSW = GlobalModel . getActiveSW ( ) ;
if ( activeSW != null ) {
let focusType = activeSW . focusType . get ( ) ;
if ( focusType == "input" ) {
this . setFocus ( ) ;
}
this . lastFocusType = focusType ;
}
2023-02-24 02:51:59 +01:00
this . checkHeight ( false ) ;
2022-10-11 08:44:04 +02:00
}
componentDidUpdate() {
let activeSW = GlobalModel . getActiveSW ( ) ;
if ( activeSW != null ) {
let focusType = activeSW . focusType . get ( ) ;
if ( this . lastFocusType != focusType && focusType == "input" ) {
this . setFocus ( ) ;
}
this . lastFocusType = focusType ;
2022-08-24 02:27:12 +02:00
}
2022-11-11 03:52:38 +01:00
let inputModel = GlobalModel . inputModel ;
if ( inputModel . forceCursorPos . get ( ) != null ) {
if ( this . mainInputRef . current != null ) {
this . mainInputRef . current . selectionStart = inputModel . forceCursorPos . get ( ) ;
this . mainInputRef . current . selectionEnd = inputModel . forceCursorPos . get ( ) ;
}
mobx . action ( ( ) = > inputModel . forceCursorPos . set ( null ) ) ( ) ;
}
2023-02-24 02:51:59 +01:00
this . checkHeight ( true ) ;
2022-08-24 02:27:12 +02:00
}
2022-08-12 20:44:29 +02:00
getLinePos ( elem : any ) : { numLines : number , linePos : number } {
let numLines = elem . value . split ( "\n" ) . length ;
let linePos = elem . value . substr ( 0 , elem . selectionStart ) . split ( "\n" ) . length ;
return { numLines , linePos } ;
}
2022-06-08 02:25:35 +02:00
@mobx . action @boundMethod
onKeyDown ( e : any ) {
mobx . action ( ( ) = > {
2022-10-14 03:58:21 +02:00
if ( isModKeyPress ( e ) ) {
2022-08-12 20:44:29 +02:00
return ;
}
2022-07-11 23:43:18 +02:00
let model = GlobalModel ;
2022-08-11 03:35:18 +02:00
let inputModel = model . inputModel ;
2022-07-12 02:55:03 +02:00
let win = model . getActiveWindow ( ) ;
2022-06-08 02:25:35 +02:00
let ctrlMod = e . getModifierState ( "Control" ) || e . getModifierState ( "Meta" ) || e . getModifierState ( "Shift" ) ;
2022-08-11 03:35:18 +02:00
let curLine = inputModel . getCurLine ( ) ;
2022-08-12 20:44:29 +02:00
let lastTab = this . lastTab ;
this . lastTab = ( e . code == "Tab" ) ;
let lastHist = this . lastHistoryUpDown ;
this . lastHistoryUpDown = false ;
2022-08-11 03:35:18 +02:00
if ( e . code == "Tab" ) {
e . preventDefault ( ) ;
if ( lastTab ) {
2022-11-23 23:34:05 +01:00
GlobalModel . submitCommand ( "_compgen" , null , [ curLine ] , { "comppos" : String ( curLine . length ) , "compshow" : "1" , "nohist" : "1" } , true ) ;
2022-08-11 03:35:18 +02:00
return ;
}
else {
2022-11-23 23:34:05 +01:00
GlobalModel . submitCommand ( "_compgen" , null , [ curLine ] , { "comppos" : String ( curLine . length ) , "nohist" : "1" } , true ) ;
2022-08-11 03:35:18 +02:00
return ;
}
}
2022-08-12 20:44:29 +02:00
if ( e . code == "Enter" ) {
2022-06-08 02:25:35 +02:00
e . preventDefault ( ) ;
2022-08-12 20:44:29 +02:00
if ( ! ctrlMod ) {
2023-02-01 07:22:51 +01:00
if ( GlobalModel . inputModel . isEmpty ( ) ) {
let activeWindow = GlobalModel . getActiveWindow ( ) ;
let activeSW = GlobalModel . getActiveSW ( ) ;
if ( activeSW != null && activeWindow != null && activeWindow . lines . length > 0 ) {
activeSW . setSelectedLine ( 0 ) ;
GlobalCommandRunner . swSelectLine ( "E" ) ;
}
return ;
}
else {
setTimeout ( ( ) = > GlobalModel . inputModel . uiSubmitCommand ( ) , 0 ) ;
return ;
}
2022-08-12 20:44:29 +02:00
}
e . target . setRangeText ( "\n" , e . target . selectionStart , e . target . selectionEnd , "end" ) ;
GlobalModel . inputModel . setCurLine ( e . target . value ) ;
2022-06-08 02:25:35 +02:00
return ;
}
2022-08-11 03:35:18 +02:00
if ( e . code == "Escape" ) {
2022-08-09 01:22:36 +02:00
e . preventDefault ( ) ;
2022-12-25 22:03:34 +01:00
e . stopPropagation ( ) ;
2022-10-23 08:54:46 +02:00
let inputModel = GlobalModel . inputModel ;
inputModel . toggleInfoMsg ( ) ;
if ( inputModel . inputMode . get ( ) != null ) {
inputModel . resetInputMode ( ) ;
}
2022-08-11 03:35:18 +02:00
return ;
}
if ( e . code == "KeyC" && e . getModifierState ( "Control" ) ) {
e . preventDefault ( ) ;
2022-08-31 08:12:37 +02:00
inputModel . resetInput ( ) ;
2022-08-09 01:22:36 +02:00
return ;
}
2022-08-31 02:05:35 +02:00
if ( e . code == "KeyR" && e . getModifierState ( "Control" ) ) {
e . preventDefault ( ) ;
2022-08-31 08:12:37 +02:00
inputModel . openHistory ( ) ;
2022-08-31 02:05:35 +02:00
return ;
}
2023-02-22 07:10:45 +01:00
if ( e . code == "ArrowUp" && e . getModifierState ( "Shift" ) ) {
e . preventDefault ( ) ;
inputModel . openHistory ( ) ;
return ;
}
2022-08-12 20:44:29 +02:00
if ( e . code == "ArrowUp" || e . code == "ArrowDown" ) {
2022-08-31 08:12:37 +02:00
if ( ! inputModel . isHistoryLoaded ( ) ) {
if ( e . code == "ArrowUp" ) {
2022-08-31 22:29:59 +02:00
this . lastHistoryUpDown = true ;
inputModel . loadHistory ( false , 1 , "window" ) ;
2022-08-31 08:12:37 +02:00
}
2022-08-31 02:05:35 +02:00
return ;
}
2022-08-31 08:12:37 +02:00
// invisible history movement
2022-08-12 20:44:29 +02:00
let linePos = this . getLinePos ( e . target ) ;
if ( e . code == "ArrowUp" ) {
if ( ! lastHist && linePos . linePos > 1 ) {
// regular arrow
return ;
}
e . preventDefault ( ) ;
2022-08-31 08:12:37 +02:00
inputModel . moveHistorySelection ( 1 ) ;
2022-08-12 20:44:29 +02:00
this . lastHistoryUpDown = true ;
return ;
}
if ( e . code == "ArrowDown" ) {
if ( ! lastHist && linePos . linePos < linePos . numLines ) {
// regular arrow
return ;
}
e . preventDefault ( ) ;
2022-08-31 08:12:37 +02:00
inputModel . moveHistorySelection ( - 1 ) ;
2022-08-12 20:44:29 +02:00
this . lastHistoryUpDown = true ;
return ;
}
2022-06-21 01:06:37 +02:00
}
2022-08-29 22:54:11 +02:00
if ( e . code == "PageUp" || e . code == "PageDown" ) {
e . preventDefault ( ) ;
2022-08-31 02:05:35 +02:00
let infoScroll = inputModel . hasScrollingInfoMsg ( ) ;
2022-08-29 22:54:11 +02:00
if ( infoScroll ) {
2022-08-30 00:42:50 +02:00
let div = document . querySelector ( ".cmd-input-info" ) ;
let amt = pageSize ( div ) ;
scrollDiv ( div , ( e . code == "PageUp" ? - amt : amt ) ) ;
2022-08-29 22:54:11 +02:00
}
}
2022-06-13 20:12:39 +02:00
// console.log(e.code, e.keyCode, e.key, event.which, ctrlMod, e);
2022-06-08 02:25:35 +02:00
} ) ( ) ;
}
@boundMethod
onChange ( e : any ) {
mobx . action ( ( ) = > {
2022-08-11 03:35:18 +02:00
GlobalModel . inputModel . setCurLine ( e . target . value ) ;
2022-06-08 02:25:35 +02:00
} ) ( ) ;
}
2022-06-13 20:12:39 +02:00
2022-08-31 08:12:37 +02:00
@boundMethod
onHistoryKeyDown ( e : any ) {
let inputModel = GlobalModel . inputModel ;
if ( e . code == "Escape" ) {
e . preventDefault ( ) ;
inputModel . resetHistory ( ) ;
return ;
}
if ( e . code == "Enter" ) {
e . preventDefault ( ) ;
inputModel . grabSelectedHistoryItem ( ) ;
return ;
}
2022-10-23 08:54:46 +02:00
if ( e . code == "KeyG" && e . getModifierState ( "Control" ) ) {
e . preventDefault ( ) ;
inputModel . resetInput ( ) ;
return ;
}
2022-08-31 08:12:37 +02:00
if ( e . code == "KeyC" && e . getModifierState ( "Control" ) ) {
e . preventDefault ( ) ;
inputModel . resetInput ( ) ;
return ;
}
2022-08-31 22:29:59 +02:00
if ( e . code == "KeyM" && ( e . getModifierState ( "Meta" ) || e . getModifierState ( "Control" ) ) ) {
2022-08-31 09:02:16 +02:00
e . preventDefault ( ) ;
let opts = mobx . toJS ( inputModel . historyQueryOpts . get ( ) ) ;
opts . includeMeta = ! opts . includeMeta ;
inputModel . setHistoryQueryOpts ( opts ) ;
2022-08-31 21:00:53 +02:00
return ;
}
2022-08-31 22:29:59 +02:00
if ( e . code == "KeyR" && ( ( e . getModifierState ( "Meta" ) || e . getModifierState ( "Control" ) ) && ! e . getModifierState ( "Shift" ) ) ) {
2022-08-31 21:00:53 +02:00
e . preventDefault ( ) ;
let opts = mobx . toJS ( inputModel . historyQueryOpts . get ( ) ) ;
if ( opts . limitRemote ) {
opts . limitRemote = false ;
opts . limitRemoteInstance = false ;
}
else {
opts . limitRemote = true ;
opts . limitRemoteInstance = true ;
}
inputModel . setHistoryQueryOpts ( opts ) ;
return ;
2022-08-31 09:02:16 +02:00
}
2022-08-31 22:29:59 +02:00
if ( e . code == "KeyS" && ( e . getModifierState ( "Meta" ) || e . getModifierState ( "Control" ) ) ) {
e . preventDefault ( ) ;
let opts = mobx . toJS ( inputModel . historyQueryOpts . get ( ) ) ;
let htype = opts . queryType ;
if ( htype == "window" ) {
htype = "session" ;
}
else if ( htype == "session" ) {
htype = "global" ;
}
else {
htype = "window" ;
}
inputModel . setHistoryType ( htype ) ;
return ;
}
2022-08-31 08:12:37 +02:00
if ( e . code == "Tab" ) {
e . preventDefault ( ) ;
return ;
}
if ( e . code == "ArrowUp" || e . code == "ArrowDown" ) {
e . preventDefault ( ) ;
inputModel . moveHistorySelection ( e . code == "ArrowUp" ? 1 : - 1 ) ;
return ;
}
if ( e . code == "PageUp" || e . code == "PageDown" ) {
e . preventDefault ( ) ;
inputModel . moveHistorySelection ( e . code == "PageUp" ? 10 : - 10 ) ;
return ;
}
}
@boundMethod
handleHistoryInput ( e : any ) {
let inputModel = GlobalModel . inputModel ;
mobx . action ( ( ) = > {
2022-08-31 09:02:16 +02:00
let opts = mobx . toJS ( inputModel . historyQueryOpts . get ( ) ) ;
opts . queryStr = e . target . value ;
inputModel . setHistoryQueryOpts ( opts ) ;
2022-08-31 08:12:37 +02:00
} ) ( ) ;
}
@boundMethod
handleMainFocus ( e : any ) {
let inputModel = GlobalModel . inputModel ;
if ( inputModel . historyShow . get ( ) ) {
e . preventDefault ( ) ;
2022-10-11 22:25:23 +02:00
if ( this . historyInputRef . current != null ) {
this . historyInputRef . current . focus ( ) ;
}
2022-10-11 08:44:04 +02:00
return ;
2022-08-31 08:12:37 +02:00
}
2022-10-11 08:44:04 +02:00
inputModel . setPhysicalInputFocused ( true ) ;
2022-10-06 23:00:24 +02:00
}
@boundMethod
handleMainBlur ( e : any ) {
2022-10-11 08:44:04 +02:00
if ( document . activeElement == this . mainInputRef . current ) {
return ;
}
2022-10-06 23:00:24 +02:00
GlobalModel . inputModel . setPhysicalInputFocused ( false ) ;
2022-08-31 08:12:37 +02:00
}
@boundMethod
handleHistoryFocus ( e : any ) {
let inputModel = GlobalModel . inputModel ;
if ( ! inputModel . historyShow . get ( ) ) {
e . preventDefault ( ) ;
2022-10-11 22:25:23 +02:00
if ( this . mainInputRef . current != null ) {
this . mainInputRef . current . focus ( ) ;
}
2022-10-11 08:44:04 +02:00
return ;
2022-08-31 08:12:37 +02:00
}
2022-10-11 08:44:04 +02:00
inputModel . setPhysicalInputFocused ( true ) ;
2022-10-06 23:00:24 +02:00
}
@boundMethod
handleHistoryBlur ( e : any ) {
2022-10-11 08:44:04 +02:00
if ( document . activeElement == this . historyInputRef . current ) {
return ;
}
2022-10-06 23:00:24 +02:00
GlobalModel . inputModel . setPhysicalInputFocused ( false ) ;
2022-08-31 08:12:37 +02:00
}
2022-08-25 04:00:03 +02:00
render() {
let model = GlobalModel ;
let inputModel = model . inputModel ;
let curLine = inputModel . getCurLine ( ) ;
2022-11-11 03:52:38 +01:00
let fcp = inputModel . forceCursorPos . get ( ) ; // for reaction
2022-08-25 04:00:03 +02:00
let numLines = curLine . split ( "\n" ) . length ;
let displayLines = numLines ;
if ( displayLines > 5 ) {
displayLines = 5 ;
}
2022-08-31 08:12:37 +02:00
let disabled = inputModel . historyShow . get ( ) ;
2022-08-31 21:00:53 +02:00
if ( disabled ) {
displayLines = 1 ;
}
2022-10-11 08:44:04 +02:00
let activeSW = GlobalModel . getActiveSW ( ) ;
if ( activeSW != null ) {
activeSW . focusType . get ( ) ; // for reaction
}
2022-08-25 04:00:03 +02:00
return (
2023-02-24 02:51:59 +01:00
< div className = "control cmd-input-control is-expanded" ref = { this . controlRef } >
2022-10-11 08:44:04 +02:00
< textarea ref = { this . mainInputRef } spellCheck = "false" id = "main-cmd-input" onFocus = { this . handleMainFocus } onBlur = { this . handleMainBlur } rows = { displayLines } value = { curLine } onKeyDown = { this . onKeyDown } onChange = { this . onChange } className = { cn ( "textarea" , { "display-disabled" : disabled } ) } > < / textarea >
< input ref = { this . historyInputRef } spellCheck = "false" className = "history-input" type = "text" onFocus = { this . handleHistoryFocus } onKeyDown = { this . onHistoryKeyDown } onChange = { this . handleHistoryInput } value = { inputModel . historyQueryOpts . get ( ) . queryStr } / >
2022-08-31 08:12:37 +02:00
< / div >
2022-08-25 04:00:03 +02:00
) ;
}
}
2022-09-15 02:14:27 +02:00
@mobxReact . observer
2022-09-25 04:53:06 +02:00
class InfoRemoteShowAll extends React . Component < { } , { } > {
2022-10-01 00:42:10 +02:00
clickRow ( remoteId : string ) : void {
GlobalCommandRunner . showRemote ( remoteId ) ;
}
2022-09-25 04:53:06 +02:00
render() {
let inputModel = GlobalModel . inputModel ;
let infoMsg = inputModel . infoMsg . get ( ) ;
if ( infoMsg == null || ! infoMsg . remoteshowall ) {
return null ;
}
2022-10-01 00:42:10 +02:00
let remotes = GlobalModel . remotes ? ? [ ] ;
let remote : RemoteType = null ;
let idx : number = 0 ;
remotes = sortAndFilterRemotes ( remotes ) ;
2022-09-25 04:53:06 +02:00
return (
< div className = "info-remote-showall" >
2022-10-03 21:25:55 +02:00
< div className = "info-title" >
show all remotes
< / div >
2022-10-01 00:42:10 +02:00
< table className = "remotes-table" >
< thead >
< tr >
< th > status < / th >
< th > id < / th >
< th > alias < / th >
< th > user @host < / th >
< th > connectmode < / th >
< / tr >
< / thead >
< tbody >
< For each = "remote" of = { remotes } >
< tr key = { remote . remoteid } onClick = { ( ) = > this . clickRow ( remote . remoteid ) } >
< td className = "status-cell" >
2022-10-01 02:23:28 +02:00
< div > < RemoteStatusLight remote = { remote } / > { remote . status } < / div >
2022-10-01 00:42:10 +02:00
< / td >
< td >
{ remote . remoteid . substr ( 0 , 8 ) }
< / td >
< td >
{ isBlank ( remote . remotealias ) ? "-" : remote . remotealias }
< / td >
< td >
{ remote . remotecanonicalname }
< / td >
< td >
{ remote . connectmode }
< / td >
< / tr >
< / For >
< / tbody >
< / table >
2022-09-25 04:53:06 +02:00
< / div >
) ;
}
}
@mobxReact . observer
class InfoRemoteShow extends React . Component < { } , { } > {
2022-09-23 06:20:37 +02:00
getRemoteTypeStr ( remote : RemoteType ) : string {
let mshellStr = "" ;
if ( ! isBlank ( remote . mshellversion ) ) {
mshellStr = "mshell=" + remote . mshellversion ;
2022-09-15 02:14:27 +02:00
}
2022-09-23 06:20:37 +02:00
if ( ! isBlank ( remote . uname ) ) {
if ( mshellStr != "" ) {
mshellStr += " " ;
}
mshellStr += "uname=\"" + remote . uname + "\"" ;
2022-09-15 02:14:27 +02:00
}
2022-09-23 06:20:37 +02:00
if ( mshellStr == "" ) {
return remote . remotetype ;
2022-09-15 09:18:20 +02:00
}
2022-09-23 06:20:37 +02:00
return remote . remotetype + " (" + mshellStr + ")" ;
2022-09-15 09:18:20 +02:00
}
2022-09-15 09:37:52 +02:00
@boundMethod
connectRemote ( remoteId : string ) {
GlobalCommandRunner . connectRemote ( remoteId ) ;
}
@boundMethod
disconnectRemote ( remoteId : string ) {
GlobalCommandRunner . disconnectRemote ( remoteId ) ;
}
2022-09-16 02:10:02 +02:00
2022-09-27 08:24:15 +02:00
@boundMethod
installRemote ( remoteId : string ) {
GlobalCommandRunner . installRemote ( remoteId ) ;
}
@boundMethod
cancelInstall ( remoteId : string ) {
GlobalCommandRunner . installCancelRemote ( remoteId ) ;
}
2022-10-04 20:15:35 +02:00
@boundMethod
editRemote ( remoteId : string ) {
GlobalCommandRunner . openEditRemote ( remoteId ) ;
}
2022-09-16 02:10:02 +02:00
renderConnectButton ( remote : RemoteType ) : any {
2022-09-16 02:44:58 +02:00
if ( remote . status == "connected" || remote . status == "connecting" ) {
2022-09-16 02:10:02 +02:00
return < div onClick = { ( ) = > this . disconnectRemote ( remote . remoteid ) } className = "text-button disconnect-button" > [ disconnect remote ] < / div >
}
else {
return < div onClick = { ( ) = > this . connectRemote ( remote . remoteid ) } className = "text-button connect-button" > [ connect remote ] < / div >
}
}
2022-10-04 20:15:35 +02:00
renderEditButton ( remote : RemoteType ) : any {
return < div onClick = { ( ) = > this . editRemote ( remote . remoteid ) } className = "text-button" > [ edit remote ] < / div >
}
2022-09-27 08:24:15 +02:00
renderInstallButton ( remote : RemoteType ) : any {
if ( remote . status == "connected" || remote . status == "connecting" ) {
2023-02-01 09:45:08 +01:00
return null ;
2022-09-27 08:24:15 +02:00
}
if ( remote . installstatus == "disconnected" || remote . installstatus == "error" ) {
2022-10-03 21:25:55 +02:00
return < div key = "run-install" onClick = { ( ) = > this . installRemote ( remote . remoteid ) } className = "text-button connect-button" > [ run install ] < / div >
2022-09-27 08:24:15 +02:00
}
if ( remote . installstatus == "connecting" ) {
2022-10-03 21:25:55 +02:00
return < div key = "cancel-install" onClick = { ( ) = > this . cancelInstall ( remote . remoteid ) } className = "text-button disconnect-button" > [ cancel install ] < / div >
2022-09-27 08:24:15 +02:00
}
return null ;
}
renderInstallStatus ( remote : RemoteType ) : any {
let statusStr : string = null ;
if ( remote . installstatus == "disconnected" ) {
if ( remote . needsmshellupgrade ) {
2023-02-01 09:45:08 +01:00
statusStr = "mshell " + remote . mshellversion + " (needs upgrade)" ;
}
else if ( isBlank ( remote . mshellversion ) ) {
statusStr = "mshell unknown" ;
}
else {
statusStr = "mshell " + remote . mshellversion + " (current)" ;
2022-09-27 08:24:15 +02:00
}
}
else {
statusStr = remote . installstatus ;
}
if ( statusStr == null ) {
return null ;
}
let installButton = this . renderInstallButton ( remote ) ;
return (
2022-10-03 21:25:55 +02:00
< div key = "install-status" className = "remote-field" >
2022-09-27 08:24:15 +02:00
< div className = "remote-field-def" > install - status < / div >
< div className = "remote-field-val" >
{ statusStr } < If condition = { installButton != null } > | { this . renderInstallButton ( remote ) } < / If >
< / div >
< / div >
) ;
}
2022-09-23 06:20:37 +02:00
@boundMethod
clickTermBlock ( e : any ) {
let inputModel = GlobalModel . inputModel ;
if ( inputModel . remoteTermWrap != null ) {
2023-02-04 03:12:50 +01:00
inputModel . remoteTermWrap . giveFocus ( ) ;
2022-09-22 08:31:03 +02:00
}
2022-09-23 06:20:37 +02:00
}
2022-10-03 21:25:55 +02:00
getCanonicalNameDisplayWithPort ( remote : RemoteType ) {
if ( isBlank ( remote . remotevars . port ) || remote . remotevars . port == "22" ) {
return remote . remotecanonicalname ;
}
return remote . remotecanonicalname + " (port " + remote . remotevars . port + ")" ;
}
2022-09-23 06:20:37 +02:00
render() {
let inputModel = GlobalModel . inputModel ;
let infoMsg = inputModel . infoMsg . get ( ) ;
let ptyRemoteId = ( infoMsg == null ? null : infoMsg . ptyremoteid ) ;
2022-10-11 22:25:23 +02:00
let isTermFocused = ( inputModel . remoteTermWrap == null ? false : inputModel . remoteTermWrapFocus . get ( ) ) ;
2022-09-23 06:20:37 +02:00
let remote : RemoteType ;
if ( ptyRemoteId != null ) {
remote = GlobalModel . getRemote ( ptyRemoteId ) ;
2022-09-22 08:31:03 +02:00
}
2022-09-23 06:20:37 +02:00
if ( ptyRemoteId == null || remote == null ) {
return (
< >
< div key = "term" className = "terminal-wrapper" style = { { display : "none" } } >
2022-10-14 01:09:26 +02:00
< div key = "terminal" className = "terminal-connectelem" id = "term-remote" > < / div >
2022-09-23 06:20:37 +02:00
< / div >
< / >
) ;
2022-09-22 08:31:03 +02:00
}
2023-02-26 08:16:58 +01:00
let termFontSize = GlobalModel . termFontSize . get ( ) ;
2022-09-23 06:20:37 +02:00
return (
< >
2022-10-03 21:25:55 +02:00
< div key = "info" className = "info-remote" >
< div key = "title" className = "info-title" >
show remote [ { remote . remotecanonicalname } ]
2022-09-23 06:20:37 +02:00
< / div >
2022-10-03 21:25:55 +02:00
< div key = "remoteid" className = "remote-field" >
< div className = "remote-field-def" > remoteid < / div >
2022-10-04 20:15:35 +02:00
< div className = "remote-field-val" > { remote . remoteid } | { this . renderEditButton ( remote ) } < / div >
2022-09-23 06:20:37 +02:00
< / div >
2022-10-03 21:25:55 +02:00
< div key = "type" className = "remote-field" >
< div className = "remote-field-def" > type < / div >
< div className = "remote-field-val" > { this . getRemoteTypeStr ( remote ) } < / div >
< / div >
< div key = "cname" className = "remote-field" >
< div className = "remote-field-def" > canonicalname < / div >
< div className = "remote-field-val" > { this . getCanonicalNameDisplayWithPort ( remote ) } < / div >
< / div >
< div key = "alias" className = "remote-field" >
< div className = "remote-field-def" > alias < / div >
< div className = "remote-field-val" > { isBlank ( remote . remotealias ) ? "-" : remote . remotealias } < / div >
< / div >
< div key = "cm" className = "remote-field" >
< div className = "remote-field-def" > connectmode < / div >
< div className = "remote-field-val" > { remote . connectmode } < / div >
< / div >
< div key = "status" className = "remote-field" >
< div className = "remote-field-def" > status < / div >
< div className = "remote-field-val" > < RemoteStatusLight remote = { remote } / > { remote . status } | { this . renderConnectButton ( remote ) } < / div >
< / div >
< If condition = { ! isBlank ( remote . errorstr ) } >
< div key = "error" className = "remote-field" >
< div className = "remote-field-def" > error < / div >
< div className = "remote-field-val" > { remote . errorstr } < / div >
< / div >
< / If >
{ this . renderInstallStatus ( remote ) }
< If condition = { ! isBlank ( remote . installerrorstr ) } >
< div key = "ierror" className = "remote-field" >
< div className = "remote-field-def" > install error < / div >
< div className = "remote-field-val" > { remote . installerrorstr } < / div >
< / div >
< / If >
< / div >
2023-02-26 08:16:58 +01:00
< div key = "term" className = { cn ( "terminal-wrapper" , { "focus" : isTermFocused } , ( remote != null ? "status-" + remote.status : null ) ) } style = { { display : ( ptyRemoteId == null ? "none" : "block" ) , width : termWidthFromCols ( RemotePtyCols , termFontSize ) } } >
2022-10-03 21:25:55 +02:00
< If condition = { ! isTermFocused } >
< div key = "termblock" className = "term-block" onClick = { this . clickTermBlock } > < / div >
< / If >
< If condition = { inputModel . showNoInputMsg . get ( ) } >
< div key = "termtag" className = "term-tag" > input is only allowed while status is 'connecting' < / div >
< / If >
2023-02-26 08:16:58 +01:00
< div key = "terminal" className = "terminal-connectelem" id = "term-remote" data - remoteid = { ptyRemoteId } style = { { height : termHeightFromRows ( RemotePtyRows , termFontSize ) } } > < / div >
2022-10-03 21:25:55 +02:00
< / div >
2022-09-23 06:20:37 +02:00
< / >
) ;
}
}
2022-09-30 23:57:23 +02:00
@mobxReact . observer
class InfoRemoteEdit extends React . Component < { } , { } > {
alias : mobx.IObservableValue < string > ;
hostName : mobx.IObservableValue < string > ;
2022-10-01 00:42:10 +02:00
keyStr : mobx.IObservableValue < string > ;
2022-09-30 23:57:23 +02:00
portStr : mobx.IObservableValue < string > ;
2022-10-03 21:25:55 +02:00
passwordStr : mobx.IObservableValue < string > ;
2022-09-30 23:57:23 +02:00
colorStr : mobx.IObservableValue < string > ;
connectMode : mobx.IObservableValue < string > ;
sudoBool : mobx.IObservableValue < boolean > ;
autoInstallBool : mobx.IObservableValue < boolean > ;
2022-10-04 04:05:52 +02:00
authMode : mobx.IObservableValue < string > ;
2022-10-04 20:15:35 +02:00
archiveConfirm : mobx.IObservableValue < boolean > = mobx . observable . box ( false ) ;
2022-09-30 23:57:23 +02:00
constructor ( props ) {
super ( props ) ;
this . resetForm ( ) ;
}
2022-10-04 04:05:52 +02:00
getEditAuthMode ( redit : RemoteEditType ) : string {
if ( ! isBlank ( redit . keystr ) && redit . haspassword ) {
return "key+pw" ;
}
else if ( ! isBlank ( redit . keystr ) ) {
return "key" ;
}
else if ( redit . haspassword ) {
return "pw" ;
}
else {
return "none" ;
}
}
2022-09-30 23:57:23 +02:00
resetForm() {
2022-10-04 04:05:52 +02:00
let redit = this . getRemoteEdit ( ) ;
let remote = this . getEditingRemote ( ) ;
if ( redit == null ) {
return ;
}
let isEditMode = ! isBlank ( redit . remoteid ) ;
if ( isEditMode && remote == null ) {
return ;
}
// not editable
2022-09-30 23:57:23 +02:00
this . hostName = mobx . observable . box ( "" ) ;
2022-10-03 21:25:55 +02:00
this . portStr = mobx . observable . box ( "" ) ;
2022-09-30 23:57:23 +02:00
this . sudoBool = mobx . observable . box ( false ) ;
2022-10-04 04:05:52 +02:00
// editable
if ( isEditMode ) {
this . authMode = mobx . observable . box ( this . getEditAuthMode ( redit ) ) ;
this . alias = mobx . observable . box ( remote . remotealias ? ? "" ) ;
this . passwordStr = mobx . observable . box ( redit . haspassword ? PasswordUnchangedSentinel : "" ) ;
this . keyStr = mobx . observable . box ( redit . keystr ? ? "" ) ;
this . colorStr = mobx . observable . box ( remote . remotevars [ "color" ] ? ? "" ) ;
this . connectMode = mobx . observable . box ( remote . connectmode ) ;
this . autoInstallBool = mobx . observable . box ( remote . autoinstall ) ;
}
else {
this . authMode = mobx . observable . box ( "none" ) ;
this . alias = mobx . observable . box ( "" ) ;
this . passwordStr = mobx . observable . box ( "" ) ;
this . keyStr = mobx . observable . box ( "" ) ;
this . colorStr = mobx . observable . box ( "" ) ;
this . connectMode = mobx . observable . box ( "startup" ) ;
this . autoInstallBool = mobx . observable . box ( true ) ;
}
2022-09-30 23:57:23 +02:00
}
2022-10-04 19:52:55 +02:00
canResetPw ( ) : boolean {
let redit = this . getRemoteEdit ( ) ;
if ( redit == null ) {
return false ;
}
return redit . haspassword && this . passwordStr . get ( ) != PasswordUnchangedSentinel ;
}
@boundMethod
resetPw ( ) : void {
mobx . action ( ( ) = > {
this . passwordStr . set ( PasswordUnchangedSentinel ) ;
} ) ( ) ;
}
2022-10-04 20:15:35 +02:00
@boundMethod
updateArchiveConfirm ( e : any ) : void {
mobx . action ( ( ) = > {
this . archiveConfirm . set ( e . target . checked ) ;
} ) ( ) ;
}
@boundMethod
doArchiveRemote ( e : any ) {
e . preventDefault ( ) ;
if ( ! this . archiveConfirm . get ( ) ) {
return ;
}
let redit = this . getRemoteEdit ( ) ;
if ( redit == null || isBlank ( redit . remoteid ) ) {
return ;
}
GlobalCommandRunner . archiveRemote ( redit . remoteid ) ;
}
2022-09-30 23:57:23 +02:00
@boundMethod
2022-10-04 04:05:52 +02:00
doSubmitRemote() {
let redit = this . getRemoteEdit ( ) ;
let isEditing = ! isBlank ( redit . remoteid ) ;
2022-10-01 00:42:10 +02:00
let cname = this . hostName . get ( ) ;
2022-09-30 23:57:23 +02:00
let kwargs : Record < string , string > = { } ;
2022-10-04 04:05:52 +02:00
let authMode = this . authMode . get ( ) ;
if ( ! isEditing ) {
if ( this . sudoBool . get ( ) ) {
kwargs [ "sudo" ] = "1" ;
}
2022-09-30 23:57:23 +02:00
}
2022-10-04 04:05:52 +02:00
kwargs [ "alias" ] = this . alias . get ( ) ;
kwargs [ "color" ] = this . colorStr . get ( ) ;
if ( authMode == "key" || authMode == "key+pw" ) {
2022-10-01 00:42:10 +02:00
kwargs [ "key" ] = this . keyStr . get ( ) ;
}
2022-10-04 04:05:52 +02:00
else {
kwargs [ "key" ] = "" ;
}
if ( authMode == "pw" || authMode == "key+pw" ) {
2022-10-01 02:23:28 +02:00
kwargs [ "password" ] = this . passwordStr . get ( ) ;
}
2022-10-04 04:05:52 +02:00
else {
kwargs [ "password" ] = ""
2022-09-30 23:57:23 +02:00
}
2022-10-04 04:05:52 +02:00
kwargs [ "connectmode" ] = this . connectMode . get ( ) ;
kwargs [ "autoinstall" ] = ( this . autoInstallBool . get ( ) ? "1" : "0" ) ;
2022-09-30 23:57:23 +02:00
kwargs [ "visual" ] = "1" ;
kwargs [ "submit" ] = "1" ;
2022-10-04 04:05:52 +02:00
console . log ( "submit remote" , ( isEditing ? redit.remoteid : cname ) , kwargs ) ;
2022-09-30 23:57:23 +02:00
mobx . action ( ( ) = > {
2022-10-04 04:05:52 +02:00
if ( isEditing ) {
GlobalCommandRunner . editRemote ( redit . remoteid , kwargs ) ;
}
else {
GlobalCommandRunner . createRemote ( cname , kwargs ) ;
}
2022-09-30 23:57:23 +02:00
} ) ( ) ;
}
@boundMethod
doCancel() {
mobx . action ( ( ) = > {
this . resetForm ( ) ;
GlobalModel . inputModel . clearInfoMsg ( true ) ;
} ) ( ) ;
}
@boundMethod
keyDownCreateRemote ( e : any ) {
if ( e . code == "Enter" ) {
2022-10-04 04:05:52 +02:00
this . doSubmitRemote ( ) ;
2022-09-30 23:57:23 +02:00
}
}
@boundMethod
keyDownCancel ( e : any ) {
if ( e . code == "Enter" ) {
this . doCancel ( ) ;
}
}
@boundMethod
onChangeAlias ( e : any ) {
mobx . action ( ( ) = > {
this . alias . set ( e . target . value ) ;
} ) ( ) ;
}
@boundMethod
onChangeHostName ( e : any ) {
mobx . action ( ( ) = > {
this . hostName . set ( e . target . value ) ;
} ) ( ) ;
}
@boundMethod
2022-10-01 00:42:10 +02:00
onChangeKeyStr ( e : any ) {
2022-09-30 23:57:23 +02:00
mobx . action ( ( ) = > {
2022-10-01 00:42:10 +02:00
this . keyStr . set ( e . target . value ) ;
2022-09-30 23:57:23 +02:00
} ) ( ) ;
}
2022-10-01 02:23:28 +02:00
@boundMethod
2022-10-03 21:25:55 +02:00
onChangePortStr ( e : any ) {
2022-10-01 02:23:28 +02:00
mobx . action ( ( ) = > {
2022-10-03 21:25:55 +02:00
this . portStr . set ( e . target . value ) ;
2022-10-01 02:23:28 +02:00
} ) ( ) ;
}
2022-09-30 23:57:23 +02:00
@boundMethod
2022-10-03 21:25:55 +02:00
onChangePasswordStr ( e : any ) {
2022-09-30 23:57:23 +02:00
mobx . action ( ( ) = > {
2022-10-03 21:25:55 +02:00
this . passwordStr . set ( e . target . value ) ;
2022-09-30 23:57:23 +02:00
} ) ( ) ;
}
2022-10-04 04:05:52 +02:00
@boundMethod
onFocusPasswordStr ( e : any ) {
if ( this . passwordStr . get ( ) == PasswordUnchangedSentinel ) {
e . target . select ( ) ;
}
}
2022-09-30 23:57:23 +02:00
@boundMethod
onChangeColorStr ( e : any ) {
mobx . action ( ( ) = > {
this . colorStr . set ( e . target . value ) ;
} ) ( ) ;
}
@boundMethod
onChangeConnectMode ( e : any ) {
mobx . action ( ( ) = > {
this . connectMode . set ( e . target . value ) ;
} ) ( ) ;
}
2022-10-04 04:05:52 +02:00
@boundMethod
onChangeAuthMode ( e : any ) {
mobx . action ( ( ) = > {
this . authMode . set ( e . target . value ) ;
} ) ( ) ;
}
2022-09-30 23:57:23 +02:00
@boundMethod
onChangeSudo ( e : any ) {
mobx . action ( ( ) = > {
this . sudoBool . set ( e . target . checked ) ;
} ) ( ) ;
}
@boundMethod
onChangeAutoInstall ( e : any ) {
mobx . action ( ( ) = > {
this . autoInstallBool . set ( e . target . checked ) ;
} ) ( ) ;
}
2022-10-03 21:25:55 +02:00
getRemoteEdit ( ) : RemoteEditType {
let inputModel = GlobalModel . inputModel ;
let infoMsg = inputModel . infoMsg . get ( ) ;
if ( infoMsg == null ) {
return null ;
}
return infoMsg . remoteedit ;
}
getEditingRemote ( ) : RemoteType {
let inputModel = GlobalModel . inputModel ;
let infoMsg = inputModel . infoMsg . get ( ) ;
if ( infoMsg == null ) {
return null ;
}
let redit = infoMsg . remoteedit ;
if ( redit == null || isBlank ( redit . remoteid ) ) {
return null ;
}
let remote = GlobalModel . getRemote ( redit . remoteid ) ;
return remote ;
}
2022-10-01 00:42:10 +02:00
remoteCName ( ) : string {
2022-10-03 21:25:55 +02:00
let redit = this . getRemoteEdit ( ) ;
if ( isBlank ( redit . remoteid ) ) {
// new-mode
let hostName = this . hostName . get ( ) ;
if ( hostName == "" ) {
return "[no host]" ;
}
if ( hostName . indexOf ( "@" ) == - 1 ) {
hostName = "[no user]@" + hostName ;
}
if ( ! hostName . startsWith ( "sudo@" ) && this . sudoBool . get ( ) ) {
return "sudo@" + hostName ;
}
return hostName ;
}
else {
let remote = this . getEditingRemote ( ) ;
if ( remote == null ) {
return "[no remote]" ;
}
return remote . remotecanonicalname ;
2022-10-01 00:42:10 +02:00
}
}
2022-09-30 23:57:23 +02:00
render() {
let inputModel = GlobalModel . inputModel ;
let infoMsg = inputModel . infoMsg . get ( ) ;
if ( infoMsg == null || ! infoMsg . remoteedit ) {
return null ;
}
let redit = infoMsg . remoteedit ;
if ( ! redit . remoteedit ) {
return null ;
}
2022-10-03 21:25:55 +02:00
let isEditMode = ! isBlank ( redit . remoteid ) ;
let remote = this . getEditingRemote ( ) ;
if ( isEditMode && remote == null ) {
2022-09-30 23:57:23 +02:00
return (
2022-10-03 21:25:55 +02:00
< div className = "info-title" > cannot edit , remote { redit . remoteid } not found < / div >
2022-09-30 23:57:23 +02:00
) ;
}
2022-10-04 04:05:52 +02:00
let colorStr : string = null ;
2022-09-30 23:57:23 +02:00
return (
< form className = "info-remote" >
2022-10-04 04:05:52 +02:00
< div key = "title" className = "info-title" >
2022-10-03 21:25:55 +02:00
< If condition = { ! isEditMode } >
2022-12-12 21:11:02 +01:00
add new remote < If condition = { this . hostName . get ( ) != "" } > '{this.remoteCName()}' < / If >
2022-09-30 23:57:23 +02:00
< / If >
2022-10-03 21:25:55 +02:00
< If condition = { isEditMode } >
edit remote '{this.remoteCName()}'
2022-09-30 23:57:23 +02:00
< / If >
< / div >
2022-10-04 04:05:52 +02:00
< div key = "type" className = "remote-input-field" >
2022-09-30 23:57:23 +02:00
< div className = "remote-field-label" > type < / div >
< div className = "remote-field-control text-control" >
ssh
< / div >
< / div >
2022-10-03 21:25:55 +02:00
< If condition = { ! isEditMode } >
2022-10-04 04:05:52 +02:00
< div key = "hostname" className = "remote-input-field" >
2022-10-03 21:25:55 +02:00
< div className = "remote-field-label" > user @host < / div >
< div className = "remote-field-control text-input" >
2022-10-04 04:05:52 +02:00
< input type = "text" autoFocus = { ! isEditMode ? true : null } onChange = { this . onChangeHostName } value = { this . hostName . get ( ) } / >
2022-10-03 21:25:55 +02:00
< / div >
< / div >
2022-10-04 04:05:52 +02:00
< div key = "port" className = "remote-input-field" >
2022-10-03 21:25:55 +02:00
< div className = "remote-field-label" > port < / div >
< div className = "remote-field-control text-input" >
< input type = "number" placeholder = "22" onChange = { this . onChangePortStr } value = { this . portStr . get ( ) } / >
< / div >
< / div >
< / If >
< If condition = { isEditMode } >
2022-10-04 04:05:52 +02:00
< div key = "hostname" className = "remote-input-field" >
2022-10-03 21:25:55 +02:00
< div className = "remote-field-label" > user @host < / div >
< div className = "remote-field-control text-control" >
{ remote . remotecanonicalname }
< If condition = { remote . remotevars . port != "22" } >
2022-10-04 04:05:52 +02:00
& nbsp ; ( port { remote . remotevars . port } )
2022-10-03 21:25:55 +02:00
< / If >
< / div >
< / div >
< / If >
2022-10-04 04:05:52 +02:00
< div key = "alias" className = "remote-input-field" >
2022-09-30 23:57:23 +02:00
< div className = "remote-field-label" > alias < / div >
< div className = "remote-field-control text-input" >
2022-10-04 04:05:52 +02:00
< input type = "text" autoFocus = { isEditMode ? true : null } onChange = { this . onChangeAlias } value = { this . alias . get ( ) } / >
2022-09-30 23:57:23 +02:00
< / div >
< / div >
2022-10-04 04:05:52 +02:00
< div key = "auth" className = "remote-input-field" >
< div className = "remote-field-label" > authmode < / div >
< div className = "remote-field-control select-input" >
< select onChange = { this . onChangeAuthMode } value = { this . authMode . get ( ) } >
< option value = "none" > none < / option >
< option value = "key" > keyfile < / option >
< option value = "pw" > password < / option >
< option value = "key+pw" > keyfile and password < / option >
< / select >
2022-09-30 23:57:23 +02:00
< / div >
< / div >
2022-10-04 04:05:52 +02:00
< If condition = { this . authMode . get ( ) == "key" || this . authMode . get ( ) == "key+pw" } >
< div key = "keyfile" className = "remote-input-field" >
< div className = "remote-field-label" > ssh keyfile < / div >
< div className = "remote-field-control text-input" >
< input type = "text" onChange = { this . onChangeKeyStr } value = { this . keyStr . get ( ) } / >
< / div >
2022-10-01 02:23:28 +02:00
< / div >
2022-10-04 04:05:52 +02:00
< / If >
< If condition = { this . authMode . get ( ) == "pw" || this . authMode . get ( ) == "key+pw" } >
< div key = "pw" className = "remote-input-field" >
< div className = "remote-field-label" > ssh password < / div >
< div className = "remote-field-control text-input" >
< input type = "password" onFocus = { this . onFocusPasswordStr } onChange = { this . onChangePasswordStr } value = { this . passwordStr . get ( ) } / >
2022-10-04 19:52:55 +02:00
< If condition = { this . canResetPw ( ) } >
< i onClick = { this . resetPw } title = "restore to original password" className = "icon fa fa-undo undo-icon" / >
< / If >
2022-10-04 04:05:52 +02:00
< / div >
< / div >
< / If >
< div key = "sudo" className = "remote-input-field" style = { { display : "none" } } >
2022-09-30 23:57:23 +02:00
< div className = "remote-field-label" > sudo < / div >
< div className = "remote-field-control checkbox-input" >
< input type = "checkbox" onChange = { this . onChangeSudo } checked = { this . sudoBool . get ( ) } / >
< / div >
< / div >
2022-10-04 04:05:52 +02:00
< div key = "cm" className = "remote-input-field" >
2022-09-30 23:57:23 +02:00
< div className = "remote-field-label" > connectmode < / div >
< div className = "remote-field-control select-input" >
< select onChange = { this . onChangeConnectMode } value = { this . connectMode . get ( ) } >
< option > startup < / option >
< option > auto < / option >
< option > manual < / option >
< / select >
< / div >
< / div >
2022-10-04 04:05:52 +02:00
< div key = "ai" className = "remote-input-field" >
2022-09-30 23:57:23 +02:00
< div className = "remote-field-label" > autoinstall < / div >
< div className = "remote-field-control checkbox-input" >
< input type = "checkbox" onChange = { this . onChangeAutoInstall } checked = { this . autoInstallBool . get ( ) } / >
< / div >
< / div >
2022-12-12 21:26:55 +01:00
< div key = "color" className = "remote-input-field" style = { { display : "none" } } >
2022-09-30 23:57:23 +02:00
< div className = "remote-field-label" > color < / div >
< div className = "remote-field-control select-input" >
< select onChange = { this . onChangeColorStr } value = { this . colorStr . get ( ) } >
< option value = "" > ( default ) < / option >
< For each = "colorStr" of = { RemoteColors } >
< option key = { colorStr } value = { colorStr } > { colorStr } < / option >
< / For >
< / select >
< / div >
< / div >
2022-10-01 00:42:10 +02:00
< If condition = { ! isBlank ( redit . errorstr ) } >
2022-10-04 04:05:52 +02:00
< div key = "error" className = "info-error" >
2022-10-01 00:42:10 +02:00
{ redit . errorstr }
< / div >
< / If >
< If condition = { ! isBlank ( redit . infostr ) } >
2022-10-04 04:05:52 +02:00
< div key = "msg" className = "info-msg" >
2022-10-01 00:42:10 +02:00
{ redit . infostr }
< / div >
< / If >
2022-10-04 04:05:52 +02:00
< div key = "controls" style = { { marginTop : 15 , marginBottom : 10 } } className = "remote-input-field" >
< a tabIndex = { 0 } style = { { marginRight : 20 } } onClick = { this . doSubmitRemote } onKeyDown = { this . keyDownCreateRemote } className = "text-button success-button" > [ { isEditMode ? "update" : "create" } remote ] < / a >
2022-09-30 23:57:23 +02:00
{ "|" }
2022-12-12 21:26:55 +01:00
< If condition = { isEditMode } >
< a tabIndex = { 0 } style = { { marginLeft : 20 , marginRight : 5 } } onClick = { this . doArchiveRemote } onKeyDown = { this . keyDownCreateRemote } className = { cn ( "text-button" , ( this . archiveConfirm . get ( ) ? "error-button" : "disabled-button" ) ) } > [ archive remote ] < / a >
< input onChange = { this . updateArchiveConfirm } checked = { this . archiveConfirm . get ( ) } style = { { marginRight : 20 } } type = "checkbox" / >
{ "|" }
< / If >
2022-09-30 23:57:23 +02:00
< a tabIndex = { 0 } style = { { marginLeft : 20 } } onClick = { this . doCancel } onKeyDown = { this . keyDownCancel } className = "text-button grey-button" > [ cancel ( ESC ) ] < / a >
< / div >
< / form >
) ;
}
}
2022-09-23 06:20:37 +02:00
@mobxReact . observer
class InfoMsg extends React . Component < { } , { } > {
getAfterSlash ( s : string ) : string {
if ( s . startsWith ( "^/" ) ) {
return s . substr ( 1 ) ;
}
let slashIdx = s . lastIndexOf ( "/" ) ;
if ( slashIdx == s . length - 1 ) {
slashIdx = s . lastIndexOf ( "/" , slashIdx - 1 ) ;
}
if ( slashIdx == - 1 ) {
return s ;
}
return s . substr ( slashIdx + 1 ) ;
2022-09-22 08:31:03 +02:00
}
2023-02-03 23:26:46 +01:00
hasSpace ( s : string ) : boolean {
return s . indexOf ( " " ) != - 1 ;
}
handleCompClick ( s : string ) : void {
// TODO -> complete to this completion
}
2022-09-15 02:14:27 +02:00
render() {
let model = GlobalModel ;
let inputModel = model . inputModel ;
let infoMsg = inputModel . infoMsg . get ( ) ;
let infoShow = inputModel . infoShow . get ( ) ;
let line : string = null ;
let istr : string = null ;
let idx : number = 0 ;
2022-09-30 23:57:23 +02:00
let titleStr = null ;
2022-10-04 04:05:52 +02:00
let remoteEditKey = "inforemoteedit" ;
2022-09-30 23:57:23 +02:00
if ( infoMsg != null ) {
titleStr = infoMsg . infotitle ;
2022-10-04 04:05:52 +02:00
if ( infoMsg . remoteedit != null ) {
remoteEditKey += ( infoMsg . remoteedit . remoteid == null ? "-new" : "-" + infoMsg . remoteedit . remoteid ) ;
}
2022-09-30 23:57:23 +02:00
}
2022-09-15 02:14:27 +02:00
return (
< div className = "cmd-input-info" style = { { display : ( infoShow ? "block" : "none" ) } } >
< If condition = { infoMsg && infoMsg . infotitle != null } >
2022-09-23 06:20:37 +02:00
< div key = "infotitle" className = "info-title" >
2022-09-30 23:57:23 +02:00
{ titleStr }
2022-09-15 02:14:27 +02:00
< / div >
< / If >
< If condition = { infoMsg && infoMsg . infomsg != null } >
2022-09-23 06:20:37 +02:00
< div key = "infomsg" className = "info-msg" >
2022-09-15 02:14:27 +02:00
{ infoMsg . infomsg }
< / div >
< / If >
< If condition = { infoMsg && infoMsg . infolines != null } >
2022-09-23 06:20:37 +02:00
< div key = "infolines" className = "info-lines" >
2022-09-15 02:14:27 +02:00
< For index = "idx" each = "line" of = { infoMsg . infolines } >
< div key = { idx } > { line == "" ? " " : line } < / div >
< / For >
< / div >
< / If >
2022-10-01 00:42:10 +02:00
< If condition = { infoMsg && infoMsg . remoteedit } >
2022-10-04 04:05:52 +02:00
< InfoRemoteEdit key = { "inforemoteedit" } / >
2022-10-01 00:42:10 +02:00
< / If >
2022-09-25 04:53:06 +02:00
< InfoRemoteShow key = "inforemoteshow" / >
< InfoRemoteShowAll key = "inforemoteshowall" / >
2022-09-15 02:14:27 +02:00
< If condition = { infoMsg && infoMsg . infocomps != null && infoMsg . infocomps . length > 0 } >
2022-09-23 06:20:37 +02:00
< div key = "infocomps" className = "info-comps" >
2022-09-15 02:14:27 +02:00
< For each = "istr" index = "idx" of = { infoMsg . infocomps } >
2023-02-03 23:26:46 +01:00
< div onClick = { ( ) = > this . handleCompClick ( istr ) } key = { idx } className = { cn ( "info-comp" , { "has-space" : this . hasSpace ( istr ) } , { "metacmd-comp" : istr . startsWith ( "^" ) } ) } >
2022-09-15 02:14:27 +02:00
{ this . getAfterSlash ( istr ) }
< / div >
< / For >
< If condition = { infoMsg . infocompsmore } >
2023-02-03 23:26:46 +01:00
< div key = "more" className = "info-comp no-select" >
2022-09-15 02:14:27 +02:00
. . .
< / div >
< / If >
< / div >
< / If >
< If condition = { infoMsg && infoMsg . infoerror != null } >
2022-09-23 06:20:37 +02:00
< div key = "infoerror" className = "info-error" >
2022-09-15 02:14:27 +02:00
[ error ] { infoMsg . infoerror }
< / div >
< / If >
< / div >
) ;
}
}
2022-08-31 00:25:51 +02:00
@mobxReact . observer
class HistoryInfo extends React . Component < { } , { } > {
2022-08-31 02:05:35 +02:00
lastClickHNum : string = null ;
lastClickTs : number = 0 ;
2022-08-31 08:12:37 +02:00
containingText : mobx.IObservableValue < string > = mobx . observable . box ( "" ) ;
2022-08-31 02:05:35 +02:00
2022-08-31 00:25:51 +02:00
componentDidMount() {
let inputModel = GlobalModel . inputModel ;
2022-08-31 08:12:37 +02:00
let hitem = inputModel . getHistorySelectedItem ( ) ;
if ( hitem == null ) {
hitem = inputModel . getFirstHistoryItem ( ) ;
}
if ( hitem != null ) {
inputModel . scrollHistoryItemIntoView ( hitem . historynum ) ;
2022-08-31 00:25:51 +02:00
}
}
2022-08-31 02:05:35 +02:00
@boundMethod
handleItemClick ( hitem : HistoryItem ) {
let inputModel = GlobalModel . inputModel ;
2022-08-31 08:12:37 +02:00
let selItem = inputModel . getHistorySelectedItem ( ) ;
if ( this . lastClickHNum == hitem . historynum && selItem != null && selItem . historynum == hitem . historynum ) {
2022-08-31 02:05:35 +02:00
inputModel . grabSelectedHistoryItem ( ) ;
return ;
}
2022-08-31 08:12:37 +02:00
inputModel . giveFocus ( ) ;
2022-08-31 02:05:35 +02:00
inputModel . setHistorySelectionNum ( hitem . historynum ) ;
let now = Date . now ( ) ;
this . lastClickHNum = hitem . historynum ;
this . lastClickTs = now ;
setTimeout ( ( ) = > {
if ( this . lastClickTs == now ) {
this . lastClickHNum = null ;
this . lastClickTs = 0 ;
}
} , 3000 ) ;
}
2022-08-31 21:00:53 +02:00
renderRemote ( hitem : HistoryItem ) : any {
if ( hitem . remote == null || isBlank ( hitem . remote . remoteid ) ) {
return sprintf ( "%-15s " , "" )
}
let r = GlobalModel . getRemote ( hitem . remote . remoteid ) ;
if ( r == null ) {
return sprintf ( "%-15s " , "???" )
}
let rname = "" ;
if ( ! isBlank ( r . remotealias ) ) {
rname = r . remotealias ;
}
else {
rname = r . remotecanonicalname ;
}
if ( ! isBlank ( hitem . remote . name ) ) {
rname = rname + ":" + hitem . remote . name ;
}
let rtn = sprintf ( "%-15s " , "[" + rname + "]" )
return rtn ;
}
renderHItem ( hitem : HistoryItem , opts : HistoryQueryOpts , isSelected : boolean ) : any {
2022-08-31 00:25:51 +02:00
let lines = hitem . cmdstr . split ( "\n" ) ;
let line : string = "" ;
let idx = 0 ;
2022-08-31 21:00:53 +02:00
let limitRemote = opts . limitRemote ;
2022-08-31 22:29:59 +02:00
let sessionStr = "" ;
if ( opts . queryType == "global" ) {
if ( ! isBlank ( hitem . sessionid ) ) {
let s = GlobalModel . getSessionById ( hitem . sessionid ) ;
if ( s != null ) {
sessionStr = s . name . get ( ) ;
if ( sessionStr . indexOf ( " " ) != - 1 ) {
sessionStr = "[" + sessionStr + "]" ;
}
sessionStr = sprintf ( "#%-15s " , sessionStr ) ;
}
}
}
2022-08-31 00:25:51 +02:00
return (
2022-08-31 09:02:16 +02:00
< div key = { hitem . historynum } className = { cn ( "history-item" , { "is-selected" : isSelected } , { "history-haderror" : hitem . haderror } , "hnum-" + hitem . historynum ) } onClick = { ( ) = > this . handleItemClick ( hitem ) } >
2022-08-31 22:29:59 +02:00
< div className = "history-line" > { ( isSelected ? "*" : " " ) } { sprintf ( "%5s" , hitem . historynum ) } { opts . queryType == "global" ? sessionStr : "" } { ! limitRemote ? this . renderRemote ( hitem ) : "" } { lines [ 0 ] } < / div >
< For each = "line" index = "idx" of = { lines . slice ( 1 ) } >
2022-08-31 00:25:51 +02:00
< div key = { idx } className = "history-line" > { line } < / div >
< / For >
< / div >
) ;
}
2022-08-31 02:05:35 +02:00
@boundMethod
handleClose() {
GlobalModel . inputModel . toggleInfoMsg ( ) ;
}
2022-08-31 08:12:37 +02:00
2022-08-31 00:25:51 +02:00
render() {
let inputModel = GlobalModel . inputModel ;
let idx : number = 0 ;
2022-08-31 08:12:37 +02:00
let selItem = inputModel . getHistorySelectedItem ( ) ;
2022-08-31 02:05:35 +02:00
let hitems = inputModel . getFilteredHistoryItems ( ) ;
2022-08-31 00:25:51 +02:00
hitems = hitems . slice ( ) . reverse ( ) ;
2022-08-31 02:05:35 +02:00
let hitem : HistoryItem = null ;
2022-08-31 09:02:16 +02:00
let opts = inputModel . historyQueryOpts . get ( ) ;
2022-08-31 00:25:51 +02:00
return (
< div className = "cmd-history" >
< div className = "history-title" >
2022-08-31 21:00:53 +02:00
< div > history < / div >
< div className = "spacer" > < / div >
2022-08-31 22:29:59 +02:00
< div className = "history-opt" > [ for { opts . queryType } & # x2318 ; S ] < / div >
2022-08-31 21:00:53 +02:00
< div className = "spacer" > < / div >
< div className = "history-opt" > [ containing '{opts.queryStr}' ] < / div >
< div className = "spacer" > < / div >
< div className = "history-opt" > [ { opts . limitRemote ? "this" : "any" } remote & # x2318 ; R ] < / div >
< div className = "spacer" > < / div >
< div className = "history-opt" > [ { opts . includeMeta ? "" : "no " } metacmds & # x2318 ; M ] < / div >
< div className = "grow-spacer" > < / div >
< div className = "history-clickable-opt" onClick = { this . handleClose } > ( ESC ) < / div >
< div className = "spacer" > < / div >
2022-08-31 00:25:51 +02:00
< / div >
2022-08-31 22:29:59 +02:00
< div className = { cn ( "history-items" , { "show-remotes" : ! opts . limitRemote } , { "show-sessions" : opts . queryType == "global" } ) } >
2022-08-31 00:25:51 +02:00
< If condition = { hitems . length == 0 } >
[ no history ]
< / If >
< If condition = { hitems . length > 0 } >
< For each = "hitem" index = "idx" of = { hitems } >
2022-08-31 21:00:53 +02:00
{ this . renderHItem ( hitem , opts , ( hitem == selItem ) ) }
2022-08-31 00:25:51 +02:00
< / For >
< / If >
< / div >
< / div >
) ;
}
}
2022-08-25 04:00:03 +02:00
@mobxReact . observer
class CmdInput extends React . Component < { } , { } > {
2022-12-27 19:58:11 +01:00
cmdInputRef : React.RefObject < any > = React . createRef ( ) ;
2022-09-17 01:37:54 +02:00
@boundMethod
onInfoToggle ( ) : void {
GlobalModel . inputModel . toggleInfoMsg ( ) ;
return ;
}
2022-12-27 19:58:11 +01:00
componentDidMount() {
2023-02-24 02:51:59 +01:00
this . updateCmdInputHeight ( ) ;
2022-12-27 19:58:11 +01:00
}
2023-02-24 02:51:59 +01:00
updateCmdInputHeight() {
2022-12-27 19:58:11 +01:00
let elem = this . cmdInputRef . current ;
if ( elem == null ) {
return ;
}
let height = elem . offsetHeight ;
2023-02-24 02:51:59 +01:00
if ( height == GlobalModel . inputModel . cmdInputHeight ) {
return ;
}
2022-12-27 19:58:11 +01:00
mobx . action ( ( ) = > {
GlobalModel . inputModel . cmdInputHeight . set ( height ) ;
} ) ( ) ;
}
2023-02-24 02:51:59 +01:00
componentDidUpdate ( prevProps , prevState , snapshot : { } ) : void {
this . updateCmdInputHeight ( ) ;
}
@boundMethod
handleInnerHeightUpdate ( ) : void {
this . updateCmdInputHeight ( ) ;
}
2022-09-17 01:37:54 +02:00
2022-06-08 02:25:35 +02:00
render() {
2022-08-11 03:35:18 +02:00
let model = GlobalModel ;
2022-08-30 21:22:42 +02:00
let inputModel = model . inputModel ;
2022-08-11 03:35:18 +02:00
let win = GlobalModel . getActiveWindow ( ) ;
let ri : RemoteInstanceType = null ;
2022-08-24 22:19:59 +02:00
let rptr : RemotePtrType = null ;
2022-08-11 03:35:18 +02:00
if ( win != null ) {
ri = win . getCurRemoteInstance ( ) ;
2022-08-24 22:19:59 +02:00
rptr = win . curRemote . get ( ) ;
2022-08-11 03:35:18 +02:00
}
let remote : RemoteType = null ;
2022-11-29 03:08:19 +01:00
let remoteState : FeStateType = null ;
2022-08-11 03:35:18 +02:00
if ( ri != null ) {
remote = GlobalModel . getRemote ( ri . remoteid ) ;
2022-11-29 03:08:19 +01:00
remoteState = ri . festate ;
2022-08-11 03:35:18 +02:00
}
2022-08-30 21:22:42 +02:00
let infoShow = inputModel . infoShow . get ( ) ;
let historyShow = ! infoShow && inputModel . historyShow . get ( ) ;
2022-09-23 06:20:37 +02:00
let infoMsg = inputModel . infoMsg . get ( ) ;
let hasInfo = ( infoMsg != null ) ;
let remoteShow = ( infoMsg != null && ! isBlank ( infoMsg . ptyremoteid ) ) ;
2022-10-07 00:17:48 +02:00
let focusVal = inputModel . physicalInputFocused . get ( ) ;
2022-10-23 08:54:46 +02:00
let inputMode : string = inputModel . inputMode . get ( ) ;
2022-06-08 02:25:35 +02:00
return (
2022-12-27 19:58:11 +01:00
< div ref = { this . cmdInputRef } className = { cn ( "cmd-input has-background-black" , { "has-info" : infoShow } , { "has-history" : historyShow } , { "has-remote" : remoteShow } ) } >
2022-10-07 00:17:48 +02:00
< div key = "focus" className = { cn ( "focus-indicator" , { "active" : focusVal } ) } / >
2022-10-04 22:53:04 +02:00
< div key = "minmax" onClick = { this . onInfoToggle } className = "input-minmax-control" >
2022-09-17 01:37:54 +02:00
< If condition = { infoShow || historyShow } >
< i className = "fa fa-chevron-down" / >
< / If >
< If condition = { ! ( infoShow || historyShow ) && hasInfo } >
< i className = "fa fa-chevron-up" / >
< / If >
< / div >
2022-08-31 00:25:51 +02:00
< If condition = { historyShow } >
2022-08-31 21:00:53 +02:00
< div className = "cmd-input-grow-spacer" > < / div >
2022-08-31 00:25:51 +02:00
< HistoryInfo / >
< / If >
2022-10-04 22:53:04 +02:00
< InfoMsg key = "infomsg" / >
< div key = "prompt" className = "cmd-input-context" >
2022-06-08 02:25:35 +02:00
< div className = "has-text-white" >
2022-11-29 03:08:19 +01:00
< Prompt rptr = { rptr } festate = { remoteState } / >
2022-06-08 02:25:35 +02:00
< / div >
< / div >
2022-10-23 08:54:46 +02:00
< div key = "input" className = { cn ( "cmd-input-field field has-addons" , ( inputMode != null ? "inputmode-" + inputMode : null ) ) } >
< If condition = { inputMode != null } >
< div className = "control cmd-quick-context" >
< div className = "button is-static" > { inputMode } < / div >
< / div >
< / If >
2023-02-24 02:51:59 +01:00
< TextAreaInput onHeightChange = { this . handleInnerHeightUpdate } / >
2022-06-08 02:25:35 +02:00
< div className = "control cmd-exec" >
2022-08-25 21:12:56 +02:00
< div onClick = { GlobalModel . inputModel . uiSubmitCommand } className = "button" >
2022-06-08 02:25:35 +02:00
< span className = "icon" >
< i className = "fa fa-rocket" / >
< / span >
< / div >
< / div >
< / div >
< / div >
) ;
}
}
@mobxReact . observer
2023-02-28 00:52:55 +01:00
class LinesView extends React . Component < { sw : ScreenWindow , width : number , lines : LineType [ ] , renderMode : RenderModeType } , { } > {
2022-08-13 03:34:56 +02:00
rszObs : any ;
2022-10-07 20:32:58 +02:00
linesRef : React.RefObject < any > ;
2022-11-23 07:57:35 +01:00
staticRender : OV < boolean > = mobx . observable . box ( true , { name : "static-render" } ) ;
2022-10-08 03:25:47 +02:00
lastOffsetHeight : number = 0 ;
lastOffsetWidth : number = 0 ;
ignoreNextScroll : boolean = false ;
visibleMap : Map < string , OV < boolean > > ; // lineid => OV<vis>
2023-02-16 05:32:17 +01:00
collapsedMap : Map < string , OV < boolean > > ; // lineid => OV<collapsed>
2022-10-11 02:18:40 +02:00
lastLinesLength : number = 0 ;
2023-02-01 07:22:51 +01:00
lastSelectedLine : number = 0 ;
2022-10-08 03:25:47 +02:00
computeAnchorLine_throttled : ( ) = > void ;
computeVisibleMap_debounced : ( ) = > void ;
constructor ( props ) {
super ( props ) ;
this . linesRef = React . createRef ( ) ;
this . computeAnchorLine_throttled = throttle ( 100 , this . computeAnchorLine . bind ( this ) , { noLeading : true , noTrailing : false } ) ;
this . visibleMap = new Map ( ) ;
2023-02-16 05:32:17 +01:00
this . collapsedMap = new Map ( ) ;
2022-10-08 03:25:47 +02:00
this . computeVisibleMap_debounced = debounce ( 1000 , this . computeVisibleMap . bind ( this ) ) ;
}
@boundMethod
scrollHandler() {
2022-10-11 22:25:23 +02:00
// console.log("scroll", this.linesRef.current.scrollTop);
this . computeVisibleMap_debounced ( ) ; // always do this
2022-10-08 03:25:47 +02:00
if ( this . ignoreNextScroll ) {
this . ignoreNextScroll = false ;
return ;
}
2022-10-11 22:25:23 +02:00
this . computeAnchorLine_throttled ( ) ; // only do this when we're not ignoring the scroll
2022-10-08 03:25:47 +02:00
}
computeAnchorLine ( ) : void {
2022-10-10 21:45:57 +02:00
let { sw } = this . props ;
2022-10-08 03:25:47 +02:00
let linesElem = this . linesRef . current ;
if ( linesElem == null ) {
2022-11-23 07:57:35 +01:00
sw . setAnchorFields ( null , 0 , "no-lines" ) ;
2022-10-08 03:25:47 +02:00
return ;
}
let lineElemArr = linesElem . querySelectorAll ( ".line" ) ;
2023-02-06 07:07:57 +01:00
if ( lineElemArr == null || lineElemArr . length == 0 ) {
2022-11-23 07:57:35 +01:00
sw . setAnchorFields ( null , 0 , "no-line" ) ;
2022-10-08 03:25:47 +02:00
return ;
}
let scrollTop = linesElem . scrollTop ;
let height = linesElem . clientHeight ;
let containerBottom = scrollTop + height ;
let anchorElem : HTMLElement = null ;
for ( let i = lineElemArr . length - 1 ; i >= 0 ; i -- ) {
let lineElem = lineElemArr [ i ] ;
let bottomPos = lineElem . offsetTop + lineElem . offsetHeight ;
if ( anchorElem == null && ( bottomPos <= containerBottom || lineElem . offsetTop <= scrollTop ) ) {
anchorElem = lineElem ;
}
}
if ( anchorElem == null ) {
anchorElem = lineElemArr [ 0 ] ;
}
2022-11-23 07:57:35 +01:00
sw . setAnchorFields ( parseInt ( anchorElem . dataset . linenum ) , containerBottom - ( anchorElem . offsetTop + anchorElem . offsetHeight ) , "computeAnchorLine" ) ;
2022-10-08 03:25:47 +02:00
}
computeVisibleMap ( ) : void {
let linesElem = this . linesRef . current ;
if ( linesElem == null ) {
return ;
}
let lineElemArr = linesElem . querySelectorAll ( ".line" ) ;
if ( lineElemArr == null ) {
return ;
}
let containerTop = linesElem . scrollTop - LinesVisiblePadding ;
let containerBot = linesElem . scrollTop + linesElem . clientHeight + LinesVisiblePadding ;
let newMap = new Map < string , boolean > ( ) ;
2023-02-01 09:27:59 +01:00
// console.log("computevismap", linesElem.scrollTop, linesElem.clientHeight, containerTop + "-" + containerBot);
2022-10-08 03:25:47 +02:00
for ( let i = 0 ; i < lineElemArr.length ; i + + ) {
let lineElem = lineElemArr [ i ] ;
let lineTop = lineElem . offsetTop ;
let lineBot = lineElem . offsetTop + lineElem . offsetHeight ;
2023-01-03 07:54:40 +01:00
let isVis = false ;
if ( lineTop >= containerTop && lineTop <= containerBot ) {
isVis = true ;
}
if ( lineBot >= containerTop && lineBot <= containerBot ) {
isVis = true
}
newMap . set ( lineElem . dataset . linenum , isVis ) ;
2023-02-01 09:27:59 +01:00
// console.log("setvis", sprintf("%4d %4d-%4d (%4d) %s", lineElem.dataset.linenum, lineTop, lineBot, lineElem.offsetHeight, isVis));
2022-10-08 03:25:47 +02:00
}
mobx . action ( ( ) = > {
for ( let [ k , v ] of newMap ) {
let oldVal = this . visibleMap . get ( k ) ;
if ( oldVal == null ) {
2022-11-23 07:57:35 +01:00
oldVal = mobx . observable . box ( v , { name : "lines-vis-map" } ) ;
2022-10-08 03:25:47 +02:00
this . visibleMap . set ( k , oldVal ) ;
}
if ( oldVal . get ( ) != v ) {
oldVal . set ( v ) ;
}
}
for ( let [ k , v ] of this . visibleMap ) {
if ( ! newMap . has ( k ) ) {
this . visibleMap . delete ( k ) ;
}
}
} ) ( ) ;
}
restoreAnchorOffset ( reason : string ) : void {
2022-10-10 21:45:57 +02:00
let { sw } = this . props ;
2022-10-08 03:25:47 +02:00
let linesElem = this . linesRef . current ;
if ( linesElem == null ) {
return ;
}
2022-10-10 21:45:57 +02:00
if ( sw . anchorLine == null || sw . anchorLine == 0 ) {
2022-10-08 03:25:47 +02:00
return ;
}
2022-10-10 21:45:57 +02:00
let anchorElem = linesElem . querySelector ( sprintf ( ".line[data-linenum=\"%d\"]" , sw . anchorLine ) ) ;
2022-10-08 03:25:47 +02:00
if ( anchorElem == null ) {
return ;
}
let scrollTop = linesElem . scrollTop ;
let height = linesElem . clientHeight ;
let containerBottom = scrollTop + height ;
let curAnchorOffset = containerBottom - ( anchorElem . offsetTop + anchorElem . offsetHeight ) ;
2022-10-10 21:45:57 +02:00
if ( curAnchorOffset != sw . anchorOffset ) {
let offsetDiff = curAnchorOffset - sw . anchorOffset ;
2022-10-08 03:25:47 +02:00
let newScrollTop = scrollTop - offsetDiff ;
2022-10-11 00:21:43 +02:00
// console.log("update scrolltop", reason, "line=" + sw.anchorLine, -offsetDiff, linesElem.scrollTop, "=>", newScrollTop);
2022-10-08 03:25:47 +02:00
linesElem . scrollTop = newScrollTop ;
this . ignoreNextScroll = true ;
}
}
componentDidMount ( ) : void {
2022-10-11 02:18:40 +02:00
let { sw , lines } = this . props ;
2022-10-10 21:45:57 +02:00
if ( sw . anchorLine == null ) {
this . computeAnchorLine ( ) ;
}
else {
this . restoreAnchorOffset ( "re-mount" ) ;
}
this . lastSelectedLine = sw . selectedLine . get ( ) ;
2022-10-11 02:18:40 +02:00
this . lastLinesLength = lines . length ;
2022-10-08 03:25:47 +02:00
let linesElem = this . linesRef . current ;
if ( linesElem != null ) {
this . lastOffsetHeight = linesElem . offsetHeight ;
this . lastOffsetWidth = linesElem . offsetWidth ;
this . rszObs = new ResizeObserver ( this . handleResize . bind ( this ) ) ;
this . rszObs . observe ( linesElem ) ;
}
mobx . action ( ( ) = > {
this . staticRender . set ( false )
this . computeVisibleMap ( ) ;
} ) ( ) ;
}
2022-10-10 21:45:57 +02:00
getLineElem ( lineNum : number ) : HTMLElement {
let linesElem = this . linesRef . current ;
if ( linesElem == null ) {
return null ;
}
let elem = linesElem . querySelector ( sprintf ( ".line[data-linenum=\"%d\"]" , lineNum ) ) ;
return elem ;
}
2022-10-11 00:21:43 +02:00
getLineViewInfo ( lineNum : number ) : { height : number , topOffset : number , botOffset : number , anchorOffset : number } {
2022-10-10 21:45:57 +02:00
let linesElem = this . linesRef . current ;
if ( linesElem == null ) {
return null ;
}
let lineElem = this . getLineElem ( lineNum ) ;
if ( lineElem == null ) {
return null ;
}
let rtn = {
height : lineElem.offsetHeight ,
topOffset : 0 ,
botOffset : 0 ,
2022-10-11 02:18:40 +02:00
anchorOffset : 0 ,
2022-10-10 21:45:57 +02:00
} ;
let containerTop = linesElem . scrollTop ;
let containerBot = linesElem . scrollTop + linesElem . clientHeight ;
let lineTop = lineElem . offsetTop ;
let lineBot = lineElem . offsetTop + lineElem . offsetHeight ;
if ( lineTop < containerTop ) {
rtn . topOffset = lineTop - containerTop ;
}
else if ( lineTop > containerBot ) {
rtn . topOffset = lineTop - containerBot ;
}
if ( lineBot < containerTop ) {
rtn . botOffset = lineBot - containerTop ;
}
else if ( lineBot > containerBot ) {
rtn . botOffset = lineBot - containerBot ;
}
2022-10-11 02:18:40 +02:00
rtn . anchorOffset = containerBot - lineBot ;
2022-10-10 21:45:57 +02:00
return rtn ;
}
updateSelectedLine ( ) : void {
let { sw , lines } = this . props ;
let linesElem = this . linesRef . current ;
if ( linesElem == null ) {
return null ;
}
let newLine = sw . selectedLine . get ( ) ;
2023-02-01 07:22:51 +01:00
if ( newLine == 0 ) {
return ;
}
2022-10-11 22:25:23 +02:00
this . setLineVisible ( newLine , true ) ;
// console.log("update selected line", this.lastSelectedLine, "=>", newLine, sprintf("anchor=%d:%d", sw.anchorLine, sw.anchorOffset));
2022-10-10 21:45:57 +02:00
let viewInfo = this . getLineViewInfo ( newLine ) ;
if ( viewInfo == null ) {
return ;
}
2022-11-23 07:57:35 +01:00
sw . setAnchorFields ( newLine , viewInfo . anchorOffset , "updateSelectedLine" ) ;
2022-10-10 21:45:57 +02:00
let isFirst = ( newLine == lines [ 0 ] . linenum ) ;
let isLast = ( newLine == lines [ lines . length - 1 ] . linenum ) ;
if ( viewInfo . botOffset > 0 ) {
linesElem . scrollTop = linesElem . scrollTop + viewInfo . botOffset + ( isLast ? 10 : 0 ) ;
2022-10-11 00:21:43 +02:00
this . ignoreNextScroll = true ;
sw . anchorOffset = ( isLast ? 10 : 0 ) ;
2022-10-10 21:45:57 +02:00
}
else if ( viewInfo . topOffset < 0 ) {
linesElem . scrollTop = linesElem . scrollTop + viewInfo . topOffset + ( isFirst ? - 10 : 0 ) ;
2022-10-11 00:21:43 +02:00
this . ignoreNextScroll = true ;
sw . anchorOffset = linesElem . clientHeight - viewInfo . height ;
2022-10-10 21:45:57 +02:00
}
2022-10-11 22:25:23 +02:00
// console.log("new anchor", sw.getAnchorStr());
2022-10-11 00:21:43 +02:00
}
setLineVisible ( lineNum : number , vis : boolean ) : void {
mobx . action ( ( ) = > {
let key = String ( lineNum ) ;
let visObj = this . visibleMap . get ( key ) ;
if ( visObj == null ) {
2022-11-23 07:57:35 +01:00
visObj = mobx . observable . box ( true , { name : "lines-vis-map" } ) ;
2022-10-11 00:21:43 +02:00
this . visibleMap . set ( key , visObj ) ;
}
else {
visObj . set ( true ) ;
}
} ) ( ) ;
2022-10-10 21:45:57 +02:00
}
componentDidUpdate ( prevProps , prevState , snapshot ) : void {
2022-10-11 02:18:40 +02:00
let { sw , lines } = this . props ;
2022-10-10 21:45:57 +02:00
if ( sw . selectedLine . get ( ) != this . lastSelectedLine ) {
this . updateSelectedLine ( ) ;
this . lastSelectedLine = sw . selectedLine . get ( ) ;
2022-10-11 02:18:40 +02:00
} else if ( lines . length != this . lastLinesLength ) {
this . restoreAnchorOffset ( "line-length-change" ) ;
2022-10-10 21:45:57 +02:00
}
}
2022-10-08 03:25:47 +02:00
componentWillUnmount ( ) : void {
if ( this . rszObs != null ) {
this . rszObs . disconnect ( ) ;
}
}
handleResize ( entries : any ) {
let linesElem = this . linesRef . current ;
if ( linesElem == null ) {
return ;
}
let heightDiff = linesElem . offsetHeight - this . lastOffsetHeight ;
if ( heightDiff != 0 ) {
linesElem . scrollTop = linesElem . scrollTop - heightDiff ;
this . lastOffsetHeight = linesElem . offsetHeight ;
this . ignoreNextScroll = true ;
}
if ( this . lastOffsetWidth != linesElem . offsetWidth ) {
this . restoreAnchorOffset ( "resize-width" ) ;
this . lastOffsetWidth = linesElem . offsetWidth ;
}
this . computeVisibleMap_debounced ( ) ;
}
@boundMethod
onHeightChange ( lineNum : number , newHeight : number , oldHeight : number ) : void {
2022-10-11 00:21:43 +02:00
// console.log("height-change", lineNum, oldHeight, "=>", newHeight);
2022-10-08 03:25:47 +02:00
this . restoreAnchorOffset ( "height-change" ) ;
this . computeVisibleMap_debounced ( ) ;
}
2023-02-16 05:32:17 +01:00
hasTopBorder ( lines : LineType [ ] , idx : number ) : boolean {
if ( idx == 0 ) {
return false ;
}
let curLineNumStr = String ( lines [ idx ] . linenum ) ;
let prevLineNumStr = String ( lines [ idx - 1 ] . linenum ) ;
return ! this . collapsedMap . get ( curLineNumStr ) . get ( ) || ! this . collapsedMap . get ( prevLineNumStr ) . get ( ) ;
}
getDateSepStr ( lines : LineType [ ] , idx : number , prevStr : string , todayStr : string , yesterdayStr : string ) : string {
let curLineDate = new Date ( lines [ idx ] . ts ) ;
let curLineFormat = dayjs ( curLineDate ) . format ( "ddd YYYY-MM-DD" ) ;
if ( idx == 0 ) {
return ;
}
let prevLineDate = new Date ( lines [ idx ] . ts ) ;
let prevLineFormat = dayjs ( prevLineDate ) . format ( "YYYY-MM-DD" ) ;
return null ;
}
2022-10-08 03:25:47 +02:00
render() {
2023-02-28 00:52:55 +01:00
let { sw , width , lines , renderMode } = this . props ;
2022-10-10 21:45:57 +02:00
let selectedLine = sw . selectedLine . get ( ) ; // for re-rendering
2022-10-08 03:25:47 +02:00
let line : LineType = null ;
for ( let i = 0 ; i < lines.length ; i + + ) {
let key = String ( lines [ i ] . linenum ) ;
let visObs = this . visibleMap . get ( key ) ;
if ( visObs == null ) {
2022-11-23 07:57:35 +01:00
this . visibleMap . set ( key , mobx . observable . box ( false , { name : "lines-vis-map" } ) ) ;
2022-10-08 03:25:47 +02:00
}
2023-02-16 05:32:17 +01:00
let collObs = this . collapsedMap . get ( key ) ;
if ( collObs == null ) {
this . collapsedMap . set ( key , mobx . observable . box ( false , { name : "lines-collapsed-map" } ) ) ;
}
}
let lineElements : any = [ ] ;
let todayStr = getTodayStr ( ) ;
let yesterdayStr = 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 = 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 ) && this . hasTopBorder ( lines , idx ) ;
2023-02-28 00:52:55 +01:00
let lineElem = < Line key = { line . lineid } line = { line } sw = { sw } width = { width } visible = { this . visibleMap . get ( lineNumStr ) } staticRender = { this . staticRender . get ( ) } onHeightChange = { this . onHeightChange } overrideCollapsed = { this . collapsedMap . get ( lineNumStr ) } topBorder = { topBorder } renderMode = { renderMode } / > ;
2023-02-16 05:32:17 +01:00
lineElements . push ( lineElem ) ;
2022-10-08 03:25:47 +02:00
}
return (
< div key = "lines" className = "lines" onScroll = { this . scrollHandler } ref = { this . linesRef } >
< div className = "lines-spacer" > < / div >
2023-02-16 05:32:17 +01:00
{ lineElements }
2022-10-08 03:25:47 +02:00
< / div >
) ;
}
}
// sw is not null
@mobxReact . observer
class ScreenWindowView extends React . Component < { sw : ScreenWindow } , { } > {
rszObs : any ;
2022-10-07 20:32:58 +02:00
windowViewRef : React.RefObject < any > ;
2022-06-18 02:54:14 +02:00
2022-11-23 07:57:35 +01:00
width : mobx.IObservableValue < number > = mobx . observable . box ( 0 , { name : "sw-view-width" } ) ;
2022-11-23 23:45:20 +01:00
height : mobx.IObservableValue < number > = mobx . observable . box ( 0 , { name : "sw-view-height" } ) ;
setSize_debounced : ( width : number , height : number ) = > void ;
2022-09-22 02:20:16 +02:00
2023-02-28 00:52:55 +01:00
renderMode : OV < RenderModeType > = mobx . observable . box ( "normal" , { name : "renderMode" } ) ;
2022-09-04 08:39:50 +02:00
constructor ( props : any ) {
super ( props ) ;
2022-11-23 23:45:20 +01:00
this . setSize_debounced = debounce ( 1000 , this . setSize . bind ( this ) ) ;
2022-10-07 20:32:58 +02:00
this . windowViewRef = React . createRef ( ) ;
2022-09-22 02:20:16 +02:00
}
2022-11-23 23:45:20 +01:00
setSize ( width : number , height : number ) : void {
2023-02-26 09:04:24 +01:00
let { sw } = this . props ;
if ( sw == null ) {
return ;
}
2022-09-22 02:20:16 +02:00
mobx . action ( ( ) = > {
this . width . set ( width ) ;
2022-11-23 23:45:20 +01:00
this . height . set ( height ) ;
2023-02-26 08:16:58 +01:00
let cols = windowWidthToCols ( width , GlobalModel . termFontSize . get ( ) ) ;
let rows = windowHeightToRows ( height , GlobalModel . termFontSize . get ( ) ) ;
2023-02-26 09:04:24 +01:00
if ( cols == 0 || rows == 0 ) {
console . log ( "cannot set sw size" , rows , cols ) ;
2022-09-22 02:20:16 +02:00
return ;
}
2022-11-23 23:45:20 +01:00
sw . termSizeCallback ( rows , cols ) ;
2022-09-22 02:20:16 +02:00
} ) ( ) ;
2022-09-04 08:39:50 +02:00
}
2022-07-12 07:43:58 +02:00
componentDidMount() {
2022-10-07 20:32:58 +02:00
let wvElem = this . windowViewRef . current ;
2022-07-14 09:54:31 +02:00
if ( wvElem != null ) {
2022-09-22 02:20:16 +02:00
let width = wvElem . offsetWidth ;
2022-11-23 23:45:20 +01:00
let height = wvElem . offsetHeight ;
this . setSize ( width , height ) ;
2022-07-14 09:54:31 +02:00
this . rszObs = new ResizeObserver ( this . handleResize . bind ( this ) ) ;
this . rszObs . observe ( wvElem ) ;
2022-07-14 08:11:45 +02:00
}
2022-07-12 07:43:58 +02:00
}
componentWillUnmount() {
2022-07-14 09:54:31 +02:00
if ( this . rszObs ) {
this . rszObs . disconnect ( ) ;
}
}
handleResize ( entries : any ) {
if ( entries . length == 0 ) {
return ;
}
let entry = entries [ 0 ] ;
let width = entry . target . offsetWidth ;
2022-11-23 23:45:20 +01:00
let height = entry . target . offsetHeight ;
this . setSize_debounced ( width , height ) ;
2022-07-12 07:43:58 +02:00
}
getWindow ( ) : Window {
2022-07-13 08:29:39 +02:00
let { sw } = this . props ;
2022-07-15 03:41:49 +02:00
let win = GlobalModel . getWindowById ( sw . sessionId , sw . windowId ) ;
if ( win == null ) {
win = GlobalModel . loadWindow ( sw . sessionId , sw . windowId ) ;
}
return win ;
2022-07-12 07:43:58 +02:00
}
2022-07-13 08:29:39 +02:00
getWindowViewStyle ( ) : any {
2022-07-14 09:54:31 +02:00
return { position : "absolute" , width : "100%" , height : "100%" , overflowX : "hidden" } ;
}
2023-02-28 00:52:55 +01:00
@boundMethod
toggleRenderMode() {
let renderMode = this . renderMode . get ( ) ;
mobx . action ( ( ) = > {
this . renderMode . set ( renderMode == "normal" ? "collapsed" : "normal" ) ;
} ) ( ) ;
}
2023-02-24 08:36:35 +01:00
renderError ( message : string , fade : boolean ) {
2022-07-14 08:11:45 +02:00
let { sw } = this . props ;
2022-07-12 07:43:58 +02:00
return (
2022-10-07 20:32:58 +02:00
< div className = "window-view" style = { this . getWindowViewStyle ( ) } ref = { this . windowViewRef } data - windowid = { sw . windowId } >
2022-07-14 08:11:45 +02:00
< div key = "window-tag" className = "window-tag" >
2022-10-11 02:18:40 +02:00
< span > { sw . name . get ( ) } < / span >
2022-07-14 08:11:45 +02:00
< / div >
2022-10-08 03:25:47 +02:00
< div key = "lines" className = "lines" > < / div >
2023-02-24 08:36:35 +01:00
< div key = "window-empty" className = { cn ( "window-empty" , { "should-fade" : fade } ) } >
2022-07-14 08:11:45 +02:00
< div > { message } < / div >
2022-07-12 07:43:58 +02:00
< / div >
< / div >
) ;
}
2022-10-08 03:25:47 +02:00
2022-06-08 02:25:35 +02:00
render() {
2022-07-13 08:29:39 +02:00
let { sw } = this . props ;
let win = this . getWindow ( ) ;
2022-07-15 03:41:49 +02:00
if ( win == null || ! win . loaded . get ( ) ) {
2023-02-24 08:36:35 +01:00
return this . renderError ( "..." , true ) ;
2022-07-02 22:32:25 +02:00
}
2022-07-15 03:41:49 +02:00
if ( win . loadError . get ( ) != null ) {
2023-02-24 08:36:35 +01:00
return this . renderError ( sprintf ( "(%s)" , win . loadError . get ( ) ) , false ) ;
2022-07-15 03:41:49 +02:00
}
2022-09-22 02:20:16 +02:00
if ( this . width . get ( ) == 0 ) {
2023-02-24 08:36:35 +01:00
return this . renderError ( "" , false ) ;
2022-07-14 09:54:31 +02:00
}
2023-02-26 23:16:42 +01:00
let cdata = GlobalModel . clientData . get ( ) ;
if ( cdata == null ) {
return this . renderError ( "loading client data" , true ) ;
}
2022-07-05 07:18:36 +02:00
let idx = 0 ;
2022-07-05 07:37:45 +02:00
let line : LineType = null ;
2022-07-14 08:11:45 +02:00
let screen = GlobalModel . getScreenById ( sw . sessionId , sw . screenId ) ;
let session = GlobalModel . getSessionById ( sw . sessionId ) ;
2022-09-06 09:14:48 +02:00
let isActive = sw . isActive ( ) ;
2022-10-08 03:25:47 +02:00
let selectedLine = sw . selectedLine . get ( ) ;
2023-02-16 05:32:17 +01:00
let lines = win . getNonArchivedLines ( ) ;
2023-02-28 00:52:55 +01:00
let renderMode = this . renderMode . get ( ) ;
2022-06-08 02:25:35 +02:00
return (
2022-10-07 20:32:58 +02:00
< div className = "window-view" style = { this . getWindowViewStyle ( ) } ref = { this . windowViewRef } >
2022-09-06 09:14:48 +02:00
< div key = "window-tag" className = { cn ( "window-tag" , { "is-active" : isActive } ) } >
2023-02-28 00:52:55 +01:00
< div className = "window-name" > { sw . name . get ( ) } < / div >
< div className = "render-mode" onClick = { this . toggleRenderMode } >
< If condition = { renderMode == "normal" } >
< i title = "collapse" className = "fa-sharp fa-solid fa-arrows-to-line" / >
< / If >
< If condition = { renderMode == "collapsed" } >
< i title = "expand" className = "fa-sharp fa-solid fa-arrows-from-line" / >
< / If >
< / div >
2022-07-14 08:11:45 +02:00
< / div >
2023-02-16 05:32:17 +01:00
< If condition = { lines . length > 0 } >
2023-02-28 00:52:55 +01:00
< LinesView sw = { sw } width = { this . width . get ( ) } lines = { lines } renderMode = { renderMode } / >
2022-10-08 03:25:47 +02:00
< / If >
2023-02-16 05:32:17 +01:00
< If condition = { lines . length == 0 } >
2022-07-14 08:11:45 +02:00
< div key = "window-empty" className = "window-empty" >
< div > < code > [ session = "{session.name.get()}" screen = "{screen.name.get()}" window = "{sw.name.get()}" ] < / code > < / div >
< / div >
< / If >
2022-07-12 07:43:58 +02:00
< / div >
) ;
}
}
2022-07-13 08:29:39 +02:00
@mobxReact . observer
class ScreenView extends React . Component < { screen : Screen } , { } > {
render() {
let { screen } = this . props ;
2022-09-04 08:39:50 +02:00
let sw : ScreenWindow = null ;
if ( screen != null ) {
sw = screen . getActiveSW ( ) ;
}
if ( screen == null || sw == null ) {
2022-07-13 08:29:39 +02:00
return (
< div className = "screen-view" >
2022-09-04 08:39:50 +02:00
( no screen or window )
2022-07-13 08:29:39 +02:00
< / div >
) ;
}
2023-02-26 08:16:58 +01:00
let fontSize = GlobalModel . termFontSize . get ( ) ;
let swKey = sw . windowId + "-fs" + fontSize ;
2022-07-13 08:29:39 +02:00
return (
2022-09-12 05:49:53 +02:00
< div className = "screen-view" data - screenid = { sw . screenId } >
2023-02-26 08:16:58 +01:00
< ScreenWindowView key = { swKey } sw = { sw } / >
2022-07-13 08:29:39 +02:00
< / div >
) ;
}
}
@mobxReact . observer
2022-07-14 08:11:45 +02:00
class ScreenTabs extends React . Component < { session : Session } , { } > {
2023-02-16 05:48:19 +01:00
tabsRef : React.RefObject < any > = React . createRef ( ) ;
lastActiveScreenId : string = null ;
2023-02-24 01:06:44 +01:00
scrolling : OV < boolean > = mobx . observable . box ( false , { name : "screentabs-scrolling" } ) ;
stopScrolling_debounced : ( ) = > void ;
constructor ( props : any ) {
super ( props ) ;
this . stopScrolling_debounced = debounce ( 1500 , this . stopScrolling . bind ( this ) ) ;
}
2023-02-16 05:48:19 +01:00
2022-07-14 08:11:45 +02:00
@boundMethod
handleNewScreen() {
let { session } = this . props ;
2022-08-31 02:05:35 +02:00
GlobalCommandRunner . createNewScreen ( ) ;
2022-07-14 08:11:45 +02:00
}
@boundMethod
handleSwitchScreen ( screenId : string ) {
let { session } = this . props ;
if ( session == null ) {
return ;
}
if ( session . activeScreenId . get ( ) == screenId ) {
return ;
}
let screen = session . getScreenById ( screenId ) ;
if ( screen == null ) {
return ;
}
2022-08-31 02:05:35 +02:00
GlobalCommandRunner . switchScreen ( screenId ) ;
2022-07-14 08:11:45 +02:00
}
2022-08-09 01:22:36 +02:00
handleContextMenu ( e : any , screenId : string ) : void {
e . preventDefault ( ) ;
console . log ( "handle context menu!" , screenId ) ;
}
2022-08-27 06:43:48 +02:00
2023-02-16 05:48:19 +01:00
componentDidMount ( ) : void {
this . componentDidUpdate ( ) ;
}
componentDidUpdate ( ) : void {
let { session } = this . props ;
let activeScreenId = session . activeScreenId . get ( ) ;
if ( activeScreenId != this . lastActiveScreenId && this . tabsRef . current ) {
let tabElem = this . tabsRef . current . querySelector ( sprintf ( ".screen-tab[data-screenid=\"%s\"]" , activeScreenId ) ) ;
if ( tabElem != null ) {
tabElem . scrollIntoView ( ) ;
}
}
this . lastActiveScreenId = activeScreenId ;
}
2023-02-24 01:06:44 +01:00
stopScrolling ( ) : void {
mobx . action ( ( ) = > {
this . scrolling . set ( false ) ;
} ) ( ) ;
}
@boundMethod
handleScroll() {
if ( ! this . scrolling . get ( ) ) {
mobx . action ( ( ) = > {
this . scrolling . set ( true ) ;
} ) ( ) ;
}
this . stopScrolling_debounced ( ) ;
}
2022-07-13 08:29:39 +02:00
render() {
2022-07-14 08:11:45 +02:00
let { session } = this . props ;
2022-07-13 08:29:39 +02:00
if ( session == null ) {
return null ;
}
2022-07-14 08:11:45 +02:00
let screen : Screen = null ;
2022-07-15 03:41:49 +02:00
let index = 0 ;
2022-12-26 21:36:24 +01:00
let showingScreens = [ ] ;
let activeScreenId = session . activeScreenId . get ( ) ;
for ( let screen of session . screens ) {
if ( ! screen . archived . get ( ) || activeScreenId == screen . screenId ) {
showingScreens . push ( screen ) ;
}
}
2022-07-13 08:29:39 +02:00
return (
2023-02-24 01:06:44 +01:00
< div className = { cn ( "screen-tabs" , { "scrolling" : this . scrolling . get ( ) } ) } ref = { this . tabsRef } onScroll = { this . handleScroll } >
2022-12-26 21:36:24 +01:00
< For each = "screen" index = "index" of = { showingScreens } >
2023-02-16 05:48:19 +01:00
< div key = { screen . screenId } data - screenid = { screen . screenId } className = { cn ( "screen-tab" , { "is-active" : activeScreenId == screen . screenId , "is-archived" : screen . archived . get ( ) } , "color-" + screen . getTabColor ( ) ) } onClick = { ( ) = > this . handleSwitchScreen ( screen . screenId ) } onContextMenu = { ( event ) = > this . handleContextMenu ( event , screen . screenId ) } >
2022-12-26 21:36:24 +01:00
< If condition = { screen . archived . get ( ) } > < i title = "archived" className = "fa fa-archive" / > < / If > { screen . name . get ( ) }
2022-07-15 03:41:49 +02:00
< If condition = { index + 1 <= 9 } >
< div className = "tab-index" > & # x2318 ; { index + 1 } < / div >
< / If >
2022-07-14 08:11:45 +02:00
< / div >
< / For >
< div key = "new-screen" className = "screen-tab new-screen" onClick = { this . handleNewScreen } >
+
< / div >
2022-07-13 08:29:39 +02:00
< / div >
) ;
}
}
2022-07-12 07:43:58 +02:00
@mobxReact . observer
class SessionView extends React . Component < { } , { } > {
render() {
let model = GlobalModel ;
let session = model . getActiveSession ( ) ;
if ( session == null ) {
return < div className = "session-view" > ( no active session ) < / div > ;
}
2022-07-13 08:29:39 +02:00
let activeScreen = session . getActiveScreen ( ) ;
2022-12-27 19:58:11 +01:00
let cmdInputHeight = model . inputModel . cmdInputHeight . get ( ) ;
if ( cmdInputHeight == 0 ) {
cmdInputHeight = 110 ;
}
2023-02-21 07:32:11 +01:00
let isHidden = ( GlobalModel . activeMainView . get ( ) != "session" ) ;
2022-07-12 07:43:58 +02:00
return (
2023-02-03 23:26:46 +01:00
< div className = { cn ( "session-view" , { "is-hidden" : isHidden } ) } data - sessionid = { session . sessionId } >
2022-07-13 08:29:39 +02:00
< ScreenView screen = { activeScreen } / >
2022-07-14 08:11:45 +02:00
< ScreenTabs session = { session } / >
2022-12-27 19:58:11 +01:00
< div style = { { height : cmdInputHeight } } > < / div >
2022-07-12 02:55:03 +02:00
< CmdInput / >
2022-06-08 02:25:35 +02:00
< / div >
) ;
}
}
2023-02-03 23:26:46 +01:00
@mobxReact . observer
class HistoryView extends React . Component < { } , { } > {
render() {
2023-02-21 07:32:11 +01:00
let isHidden = ( GlobalModel . activeMainView . get ( ) != "history" ) ;
2023-02-03 23:26:46 +01:00
return (
< div className = { cn ( "history-view" , { "is-hidden" : isHidden } ) } >
< div className = "history-title" > HISTORY < / div >
< / div >
) ;
}
}
2022-09-14 21:07:31 +02:00
function getConnVal ( r : RemoteType ) : number {
if ( r . status == "connected" ) {
return 1 ;
}
if ( r . status == "init" || r . status == "disconnected" ) {
return 2 ;
}
if ( r . status == "error" ) {
return 3 ;
}
return 4 ;
}
2022-09-22 07:17:04 +02:00
@mobxReact . observer
2022-10-01 02:23:28 +02:00
class RemoteStatusLight extends React . Component < { remote : RemoteType } , { } > {
2022-09-22 07:17:04 +02:00
render() {
2022-10-01 02:23:28 +02:00
let remote = this . props . remote ;
let status = "error" ;
let wfp = false ;
if ( remote != null ) {
status = remote . status ;
wfp = remote . waitingforpassword ;
}
let icon = "fa-circle"
if ( status == "connecting" ) {
2023-02-27 21:22:45 +01:00
icon = ( wfp ? "fa-sharp fa-solid fa-key" : "fa-sharp fa-solid fa-rotate" ) ;
2022-10-01 02:23:28 +02:00
}
2022-09-22 07:17:04 +02:00
return (
2023-02-27 21:22:45 +01:00
< i className = { cn ( "remote-status" , icon , "status-" + status ) } / >
2022-09-22 07:17:04 +02:00
) ;
}
}
2022-06-20 22:03:20 +02:00
@mobxReact . observer
class MainSideBar extends React . Component < { } , { } > {
collapsed : mobx.IObservableValue < boolean > = mobx . observable . box ( false ) ;
@boundMethod
toggleCollapsed() {
mobx . action ( ( ) = > {
this . collapsed . set ( ! this . collapsed . get ( ) ) ;
} ) ( ) ;
}
2022-07-08 22:23:00 +02:00
handleSessionClick ( sessionId : string ) {
2022-08-31 02:05:35 +02:00
GlobalCommandRunner . switchSession ( sessionId ) ;
2022-08-09 01:22:36 +02:00
}
handleNewSession() {
2022-08-31 02:05:35 +02:00
GlobalCommandRunner . createNewSession ( ) ;
2022-07-08 22:23:00 +02:00
}
2022-07-09 10:37:19 +02:00
2023-02-21 03:01:47 +01:00
handleNewSharedSession() {
2023-02-22 07:42:44 +01:00
GlobalCommandRunner . openSharedSession ( ) ;
2023-02-21 03:01:47 +01:00
}
2022-08-18 09:39:06 +02:00
clickRemotes() {
2022-10-01 00:42:10 +02:00
GlobalCommandRunner . showAllRemotes ( ) ;
2022-08-18 09:39:06 +02:00
}
2022-09-14 02:17:52 +02:00
remoteDisplayName ( remote : RemoteType ) : any {
if ( ! isBlank ( remote . remotealias ) ) {
return (
< >
< span > { remote . remotealias } < / span >
< span className = "small-text" > { remote . remotecanonicalname } < / span >
< / >
) ;
}
return ( < span > { remote . remotecanonicalname } < / span > ) ;
}
2022-09-14 22:02:33 +02:00
clickRemote ( remote : RemoteType ) {
GlobalCommandRunner . showRemote ( remote . remoteid ) ;
}
2022-09-22 07:42:51 +02:00
@boundMethod
handleAddRemote ( ) : void {
2022-09-30 23:57:23 +02:00
GlobalCommandRunner . openCreateRemote ( ) ;
2022-09-22 07:42:51 +02:00
}
2023-02-03 23:26:46 +01:00
@boundMethod
handleHistoryClick ( ) : void {
2023-02-21 07:32:11 +01:00
console . log ( "history click" ) ;
2023-02-03 23:26:46 +01:00
}
2023-02-21 03:01:47 +01:00
@boundMethod
handleBookmarksClick ( ) : void {
2023-02-21 07:32:11 +01:00
if ( GlobalModel . activeMainView . get ( ) == "bookmarks" ) {
mobx . action ( ( ) = > {
GlobalModel . activeMainView . set ( "session" ) ;
} ) ( ) ;
return ;
}
GlobalCommandRunner . bookmarksView ( ) ;
2023-02-21 03:01:47 +01:00
}
2022-06-20 22:03:20 +02:00
render() {
2022-07-11 23:43:18 +02:00
let model = GlobalModel ;
2022-07-13 23:16:47 +02:00
let activeSessionId = model . activeSessionId . get ( ) ;
2022-09-30 23:57:23 +02:00
let activeWindow = model . getActiveWindow ( ) ;
let activeRemoteId : string = null ;
if ( activeWindow != null ) {
let rptr = activeWindow . curRemote . get ( ) ;
if ( rptr != null && ! isBlank ( rptr . remoteid ) ) {
activeRemoteId = rptr . remoteid ;
}
}
2022-10-11 23:57:22 +02:00
let sw : ScreenWindow = null ;
if ( GlobalModel . debugSW . get ( ) ) {
sw = GlobalModel . getActiveSW ( ) ;
}
2022-07-12 02:55:03 +02:00
let session : Session = null ;
2022-09-14 02:17:52 +02:00
let remotes = model . remotes ? ? [ ] ;
2022-08-17 22:06:47 +02:00
let remote : RemoteType = null ;
2022-08-27 02:28:56 +02:00
let idx : number = 0 ;
2022-09-22 07:17:04 +02:00
remotes = sortAndFilterRemotes ( remotes ) ;
2022-12-27 03:50:22 +01:00
let sessionList = [ ] ;
for ( let session of model . sessionList ) {
if ( ! session . archived . get ( ) || session . sessionId == activeSessionId ) {
sessionList . push ( session ) ;
}
}
2022-12-29 02:46:15 +01:00
let isCollapsed = this . collapsed . get ( ) ;
2023-02-21 07:32:11 +01:00
let mainView = GlobalModel . activeMainView . get ( ) ;
2022-06-20 22:03:20 +02:00
return (
2023-02-03 23:26:46 +01:00
< div className = { cn ( "main-sidebar" , { "collapsed" : isCollapsed } , { "is-dev" : GlobalModel . isDev } ) } >
< h1 className = { cn ( "title" , "prompt-logo-small" , { "collapsed" : isCollapsed } , { "is-dev" : GlobalModel . isDev } ) } >
2022-12-29 02:46:15 +01:00
{ ( isCollapsed ? "[p]" : "[prompt]" ) }
< / h1 >
2022-06-20 22:03:20 +02:00
< div className = "collapse-container" >
< div className = "arrow-container" onClick = { this . toggleCollapsed } >
2022-12-29 02:46:15 +01:00
< If condition = { ! isCollapsed } > < i className = "fa fa-arrow-left" / > < / If >
< If condition = { isCollapsed } > < i className = "fa fa-arrow-right" / > < / If >
2022-06-20 22:03:20 +02:00
< / div >
< / div >
< div className = "menu" >
< p className = "menu-label" >
2022-07-08 22:01:37 +02:00
Private Sessions
2022-06-20 22:03:20 +02:00
< / p >
< ul className = "menu-list" >
2022-07-12 02:55:03 +02:00
< If condition = { ! model . sessionListLoaded . get ( ) } >
2022-12-22 07:05:05 +01:00
< li className = "menu-loading-message" > < a > ( loading ) < / a > < / li >
2022-07-11 23:43:18 +02:00
< / If >
2022-07-12 02:55:03 +02:00
< If condition = { model . sessionListLoaded . get ( ) } >
2022-12-27 03:50:22 +01:00
< For each = "session" index = "idx" of = { sessionList } >
2023-02-21 07:32:11 +01:00
< li key = { session . sessionId } > < a className = { cn ( { "is-active" : mainView == "session" && activeSessionId == session . sessionId } ) } onClick = { ( ) = > this . handleSessionClick ( session . sessionId ) } >
2022-12-27 03:50:22 +01:00
< If condition = { ! session . archived . get ( ) } >
< span className = "session-num" > { idx + 1 } & nbsp ; < / span >
< / If >
< If condition = { session . archived . get ( ) } >
< i title = "archived" className = "fa fa-archive" / > & nbsp ;
< / If >
2022-08-27 02:28:56 +02:00
{ session . name . get ( ) }
< / a > < / li >
2022-07-11 23:43:18 +02:00
< / For >
2022-09-22 07:42:51 +02:00
< li className = "new-session" > < a onClick = { ( ) = > this . handleNewSession ( ) } > < i className = "fa fa-plus" / > New Session < / a > < / li >
2022-07-11 23:43:18 +02:00
< / If >
2022-06-20 22:03:20 +02:00
< / ul >
2023-02-21 03:01:47 +01:00
< p className = "menu-label" >
Shared Sessions
< / p >
< ul className = "menu-list" >
< li className = "new-session" > < a onClick = { ( ) = > this . handleNewSharedSession ( ) } > < i className = "fa fa-plus" / > New Session < / a > < / li >
< / ul >
2023-02-03 23:26:46 +01:00
< ul className = "menu-list" style = { { marginTop : 20 , display : "none" } } >
2023-02-21 07:32:11 +01:00
< li className = "menu-history" > < a onClick = { this . handleHistoryClick } className = { cn ( { "is-active" : ( mainView == "history" ) } ) } > < i className = "fa fa-clock-o" / > HISTORY < / a > < / li >
2023-02-03 23:26:46 +01:00
< / ul >
2023-02-21 03:01:47 +01:00
< ul className = "menu-list" style = { { marginTop : 20 } } >
2023-02-21 07:32:11 +01:00
< li className = "menu-bookmarks" > < a onClick = { this . handleBookmarksClick } className = { cn ( { "is-active" : ( mainView == "bookmarks" ) } ) } > < i className = "fa fa-bookmark" / > BOOKMARKS < / a > < / li >
2023-02-21 03:01:47 +01:00
< / ul >
2022-06-20 22:03:20 +02:00
< div className = "spacer" > < / div >
2022-10-11 23:57:22 +02:00
< If condition = { GlobalModel . debugSW . get ( ) && sw != null } >
< div >
focus = { sw . focusType . get ( ) } < br / >
2023-02-21 07:32:11 +01:00
sline = { sw . selectedLine . get ( ) } < br / >
termfocus = { sw . termLineNumFocus . get ( ) } < br / >
2022-10-11 23:57:22 +02:00
< / div >
< / If >
2022-12-12 21:11:02 +01:00
< p className = "menu-label" >
< a onClick = { ( ) = > this . clickRemotes ( ) } > Links < / a >
< / p >
< ul className = "menu-list" >
< li >
2023-02-27 21:22:45 +01:00
< a target = "_blank" href = "https://docs.getprompt.dev/" > < i style = { { width : 20 } } className = "fa fa-book" / > documentation < / a >
2022-12-12 21:11:02 +01:00
< / li >
< li >
2023-02-27 21:22:45 +01:00
< a target = "_blank" href = "https://discord.gg/XfvZ334gwU" > < i style = { { width : 20 } } className = "fa-brands fa-discord" / > discord < / a >
2022-12-12 21:11:02 +01:00
< / li >
< / ul >
2022-06-20 22:03:20 +02:00
< p className = "menu-label" >
2022-11-29 03:08:19 +01:00
< a onClick = { ( ) = > this . clickRemotes ( ) } > Connections < / a >
2022-06-20 22:03:20 +02:00
< / p >
2022-09-14 21:07:31 +02:00
< ul className = "menu-list remotes-menu-list" >
2022-08-17 22:06:47 +02:00
< For each = "remote" of = { remotes } >
2022-09-30 23:57:23 +02:00
< li key = { remote . remoteid } className = { cn ( "remote-menu-item" ) } > < a className = { cn ( { "is-active" : ( remote . remoteid == activeRemoteId ) } ) } onClick = { ( ) = > this . clickRemote ( remote ) } >
2022-10-01 02:23:28 +02:00
< RemoteStatusLight remote = { remote } / >
2022-09-14 02:17:52 +02:00
{ this . remoteDisplayName ( remote ) }
< / a > < / li >
2022-08-17 22:06:47 +02:00
< / For >
2022-09-22 07:42:51 +02:00
< li key = "add-remote" className = "add-remote" >
2022-11-29 03:08:19 +01:00
< a onClick = { ( ) = > this . handleAddRemote ( ) } > < i className = "fa fa-plus" / > Add Connection < / a >
2022-09-22 07:42:51 +02:00
< / li >
2022-06-20 22:03:20 +02:00
< / ul >
< div className = "bottom-spacer" > < / div >
< / div >
< / div >
) ;
}
}
2022-09-22 07:17:04 +02:00
function sortAndFilterRemotes ( origRemotes : RemoteType [ ] ) : RemoteType [ ] {
let remotes = origRemotes . filter ( ( r ) = > ! r . archived ) ;
remotes . sort ( ( a , b ) = > {
let connValA = getConnVal ( a ) ;
let connValB = getConnVal ( b ) ;
if ( connValA != connValB ) {
return connValA - connValB ;
}
return a . remoteidx - b . remoteidx ;
} ) ;
return remotes ;
}
2022-08-18 09:39:06 +02:00
@mobxReact . observer
2022-10-28 23:17:45 +02:00
class DisconnectedModal extends React . Component < { } , { } > {
logRef : any = React . createRef ( ) ;
showLog : mobx.IObservableValue < boolean > = mobx . observable . box ( false )
2022-09-22 07:42:51 +02:00
2022-10-28 23:17:45 +02:00
@boundMethod
restartServer() {
2022-10-30 20:53:39 +01:00
GlobalModel . restartLocalServer ( ) ;
2022-09-22 07:42:51 +02:00
}
2022-08-18 09:39:06 +02:00
@boundMethod
2022-10-28 23:17:45 +02:00
tryReconnect() {
GlobalModel . ws . connectNow ( "manual" ) ;
}
componentDidMount() {
if ( this . logRef . current != null ) {
this . logRef . current . scrollTop = this . logRef . current . scrollHeight ;
}
}
componentDidUpdate() {
if ( this . logRef . current != null ) {
this . logRef . current . scrollTop = this . logRef . current . scrollHeight ;
}
2022-08-18 09:39:06 +02:00
}
2022-09-22 08:31:03 +02:00
@boundMethod
2022-10-28 23:17:45 +02:00
handleShowLog ( ) : void {
2022-09-22 08:31:03 +02:00
mobx . action ( ( ) = > {
2022-10-28 23:17:45 +02:00
this . showLog . set ( ! this . showLog . get ( ) ) ;
2022-09-22 08:31:03 +02:00
} ) ( ) ;
}
2022-08-18 09:39:06 +02:00
render() {
let model = GlobalModel ;
2022-10-28 23:17:45 +02:00
let logLine : string = null ;
let idx : number = 0 ;
2022-08-18 09:39:06 +02:00
return (
2022-10-28 23:17:45 +02:00
< div className = "sc-modal disconnected-modal modal is-active" >
< div className = "modal-background" > < / div >
2022-08-18 09:39:06 +02:00
< div className = "modal-content message" >
< div className = "message-header" >
2022-11-29 03:08:19 +01:00
< p > Prompt Client Disconnected < / p >
2022-08-18 09:39:06 +02:00
< / div >
2022-10-28 23:17:45 +02:00
< If condition = { this . showLog . get ( ) } >
< div className = "message-content" >
< div className = "ws-log" ref = { this . logRef } >
< For each = "logLine" index = "idx" of = { GlobalModel . ws . wsLog } >
< div key = { idx } className = "ws-logline" > { logLine } < / div >
2022-08-18 09:39:06 +02:00
< / For >
2022-10-28 23:17:45 +02:00
< / div >
< / div >
< / If >
2022-09-22 07:42:51 +02:00
< div className = "message-footer" >
2022-10-28 23:17:45 +02:00
< div className = "footer-text-link" style = { { marginLeft : 10 } } onClick = { this . handleShowLog } >
< If condition = { ! this . showLog . get ( ) } >
< i className = "fa fa-plus" / > Show Log
< / If >
< If condition = { this . showLog . get ( ) } >
< i className = "fa fa-minus" / > Hide Log
< / If >
< / div >
< div className = "spacer" / >
< button onClick = { this . tryReconnect } className = "button" >
2022-08-18 09:39:06 +02:00
< span className = "icon" >
2023-02-27 21:22:45 +01:00
< i className = "fa fa-rotate" / >
2022-08-18 09:39:06 +02:00
< / span >
2022-10-28 23:17:45 +02:00
< span > Try Reconnect < / span >
< / button >
< button onClick = { this . restartServer } className = "button is-danger" style = { { marginLeft : 10 } } >
< span className = "icon" >
< i className = "fa fa-exclamation-triangle" / >
< / span >
< span > Restart Server < / span >
2022-08-18 09:39:06 +02:00
< / button >
< / div >
< / div >
< / div >
) ;
}
}
2023-01-03 07:54:40 +01:00
@mobxReact . observer
class LoadingSpinner extends React . Component < { } , { } > {
render() {
return (
< div className = "loading-spinner" > < div > < / div > < div > < / div > < div > < / div > < div > < / div > < / div >
) ;
}
}
2022-06-16 03:12:22 +02:00
@mobxReact . observer
2022-06-17 00:51:17 +02:00
class Main extends React . Component < { } , { } > {
2022-06-16 03:12:22 +02:00
constructor ( props : any ) {
super ( props ) ;
}
2023-01-02 23:52:53 +01:00
@boundMethod
handleContextMenu ( e : any ) {
let isInNonTermInput = false ;
let activeElem = document . activeElement ;
if ( activeElem != null && activeElem . nodeName == "TEXTAREA" ) {
if ( ! activeElem . classList . contains ( "xterm-helper-textarea" ) ) {
isInNonTermInput = true ;
}
}
if ( activeElem != null && activeElem . nodeName == "INPUT" && activeElem . getAttribute ( "type" ) == "text" ) {
isInNonTermInput = true ;
}
let opts : ContextMenuOpts = { } ;
if ( isInNonTermInput ) {
opts . showCut = true ;
}
let sel = window . getSelection ( ) ;
if ( ! isBlank ( sel . toString ( ) ) ) {
GlobalModel . contextEditMenu ( e , opts ) ;
}
else {
if ( isInNonTermInput ) {
GlobalModel . contextEditMenu ( e , opts ) ;
}
}
}
2022-06-16 03:12:22 +02:00
render() {
return (
2023-01-02 23:52:53 +01:00
< div id = "main" onContextMenu = { this . handleContextMenu } >
2022-06-20 22:03:20 +02:00
< div className = "main-content" >
< MainSideBar / >
2022-07-11 23:43:18 +02:00
< SessionView / >
2023-02-03 23:26:46 +01:00
< HistoryView / >
2023-02-21 07:32:11 +01:00
< BookmarksView / >
2022-06-20 22:03:20 +02:00
< / div >
2022-10-30 21:06:25 +01:00
< If condition = { ! GlobalModel . ws . open . get ( ) || ! GlobalModel . localServerRunning . get ( ) } >
2022-10-28 23:17:45 +02:00
< DisconnectedModal / >
2022-09-22 08:31:03 +02:00
< / If >
2022-06-16 03:12:22 +02:00
< / div >
) ;
}
}
2022-06-08 02:25:35 +02:00
export { Main } ;
2022-07-07 22:27:44 +02:00