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" ;
2023-03-14 19:48:14 +01:00
import { If , For , When , Otherwise , Choose } from "tsx-control-statements/components" ;
import cn from "classnames" ;
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" ;
2023-03-02 09:33:10 +01:00
import type { SessionDataType , LineType , CmdDataType , RemoteType , RemoteStateType , RemoteInstanceType , RemotePtrType , HistoryItem , HistoryQueryOpts , RemoteEditType , FeStateType , ContextMenuOpts , BookmarkType , RenderModeType } from "./types" ;
2023-03-03 19:16:31 +01:00
import type * as T from "./types" ;
2022-06-18 02:54:14 +02:00
import localizedFormat from 'dayjs/plugin/localizedFormat' ;
2023-03-15 04:09:59 +01:00
import { GlobalModel , GlobalCommandRunner , Session , Cmd , ScreenLines , Screen , riToRPtr , windowWidthToCols , windowHeightToRows , termHeightFromRows , termWidthFromCols , TabColors , RemoteColors } from "./model" ;
2022-10-14 03:58:21 +02:00
import { isModKeyPress } from "./util" ;
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" ;
2023-03-02 09:33:10 +01:00
import { HistoryView } from "./history" ;
import { Line , Prompt } from "./linecomps" ;
2023-03-17 20:37:10 +01:00
import { ScreenSettingsModal , SessionSettingsModal , LineSettingsModal } from "./settings" ;
2023-03-15 05:15:26 +01:00
import { renderCmdText } from "./elements" ;
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 ;
2023-03-15 19:14:28 +01:00
const TDots = "⋮" ;
2022-09-15 09:18:20 +02:00
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-06 09:31:34 +01:00
2022-08-13 03:34:56 +02:00
type InterObsValue = {
sessionid : 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 == "" ) ;
}
2023-03-02 09:33:10 +01:00
function getTodayStr ( ) : string {
return getDateStr ( new Date ( ) ) ;
}
function getYesterdayStr ( ) : string {
let d = new Date ( ) ;
d . setDate ( d . getDate ( ) - 1 ) ;
return getDateStr ( d ) ;
}
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" } ) ;
}
2023-03-15 19:14:28 +01:00
function truncateWithTDots ( str : string , maxLen : number ) : string {
if ( str == null ) {
return null ;
}
if ( str . length <= maxLen ) {
return str ;
}
return str . slice ( 0 , maxLen - 1 ) + TDots ;
}
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-06-08 02:25:35 +02:00
@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() {
2023-03-13 20:09:17 +01:00
let activeScreen = GlobalModel . getActiveScreen ( ) ;
if ( activeScreen != null ) {
let focusType = activeScreen . focusType . get ( ) ;
2022-10-11 08:44:04 +02:00
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() {
2023-03-13 20:09:17 +01:00
let activeScreen = GlobalModel . getActiveScreen ( ) ;
if ( activeScreen != null ) {
let focusType = activeScreen . focusType . get ( ) ;
2022-10-11 08:44:04 +02:00
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 ;
2023-03-15 04:09:59 +01:00
let win = model . getScreenLinesForActiveScreen ( ) ;
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 ( ) ) {
2023-03-15 04:09:59 +01:00
let activeWindow = GlobalModel . getScreenLinesForActiveScreen ( ) ;
2023-03-13 20:09:17 +01:00
let activeScreen = GlobalModel . getActiveScreen ( ) ;
if ( activeScreen != null && activeWindow != null && activeWindow . lines . length > 0 ) {
activeScreen . setSelectedLine ( 0 ) ;
GlobalCommandRunner . screenSelectLine ( "E" ) ;
2023-02-01 07:22:51 +01:00
}
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 ;
}
2023-03-08 05:40:30 +01:00
if ( e . code == "KeyU" && e . getModifierState ( "Control" ) ) {
e . preventDefault ( ) ;
this . controlU ( ) ;
return ;
}
2023-03-18 00:21:43 +01:00
if ( e . code == "KeyW" && e . getModifierState ( "Control" ) ) {
e . preventDefault ( ) ;
this . controlW ( ) ;
return ;
}
2023-03-08 05:40:30 +01:00
if ( e . code == "KeyY" && e . getModifierState ( "Control" ) ) {
e . preventDefault ( ) ;
this . controlY ( ) ;
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 ;
2023-03-15 00:38:26 +01:00
inputModel . loadHistory ( false , 1 , "screen" ) ;
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 == "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 ;
2023-03-15 00:38:26 +01:00
if ( htype == "screen" ) {
2022-08-31 22:29:59 +02:00
htype = "session" ;
}
else if ( htype == "session" ) {
htype = "global" ;
}
else {
2023-03-15 00:38:26 +01:00
htype = "screen" ;
2022-08-31 22:29:59 +02:00
}
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 ;
}
}
2023-03-08 05:40:30 +01:00
@boundMethod
controlU() {
if ( this . mainInputRef . current == null ) {
return ;
}
let selStart = this . mainInputRef . current . selectionStart ;
let value = this . mainInputRef . current . value ;
if ( selStart > value . length ) {
return ;
}
let cutValue = value . substr ( 0 , selStart ) ;
let restValue = value . substr ( selStart ) ;
let cmdLineUpdate = { cmdline : restValue , cursorpos : 0 } ;
console . log ( "ss" , selStart , value , "[" + cutValue + "]" , "[" + restValue + "]" ) ;
navigator . clipboard . writeText ( cutValue ) ;
GlobalModel . inputModel . updateCmdLine ( cmdLineUpdate ) ;
}
2023-03-18 00:21:43 +01:00
@boundMethod
controlW() {
if ( this . mainInputRef . current == null ) {
return ;
}
let selStart = this . mainInputRef . current . selectionStart ;
let value = this . mainInputRef . current . value ;
if ( selStart > value . length ) {
return ;
}
let cutSpot = selStart - 1 ;
let initial = true ;
for ( ; cutSpot >= 0 ; cutSpot -- ) {
let ch = value [ cutSpot ] ;
console . log ( cutSpot , "[" + ch + "]" ) ;
if ( ch == " " && initial ) {
continue ;
}
initial = false ;
if ( ch == " " ) {
cutSpot ++ ;
break ;
}
}
let cutValue = value . slice ( cutSpot , selStart ) ;
let prevValue = value . slice ( 0 , cutSpot ) ;
let restValue = value . slice ( selStart ) ;
let cmdLineUpdate = { cmdline : prevValue + restValue , cursorpos : prevValue.length } ;
console . log ( "ss" , selStart , value , "prev[" + prevValue + "]" , "cut[" + cutValue + "]" , "rest[" + restValue + "]" ) ;
console . log ( " " , cmdLineUpdate ) ;
navigator . clipboard . writeText ( cutValue ) ;
GlobalModel . inputModel . updateCmdLine ( cmdLineUpdate ) ;
}
2023-03-08 05:40:30 +01:00
@boundMethod
controlY() {
if ( this . mainInputRef . current == null ) {
return ;
}
let pastePromise = navigator . clipboard . readText ( ) ;
pastePromise . then ( ( clipText ) = > {
clipText = clipText ? ? "" ;
let selStart = this . mainInputRef . current . selectionStart ;
let selEnd = this . mainInputRef . current . selectionEnd ;
let value = this . mainInputRef . current . value ;
if ( selStart > value . length || selEnd > value . length ) {
return ;
}
let newValue = value . substr ( 0 , selStart ) + clipText + value . substr ( selEnd ) ;
let cmdLineUpdate = { cmdline : newValue , cursorpos : selStart + clipText . length } ;
GlobalModel . inputModel . updateCmdLine ( cmdLineUpdate ) ;
} ) ;
}
2022-08-31 08:12:37 +02:00
@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 ;
}
2023-03-13 20:09:17 +01:00
let activeScreen = GlobalModel . getActiveScreen ( ) ;
if ( activeScreen != null ) {
activeScreen . focusType . get ( ) ; // for reaction
2022-10-11 08:44:04 +02:00
}
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 } >
2023-03-18 00:21:43 +01:00
< textarea ref = { this . mainInputRef } spellCheck = "false" autoComplete = "off" autoCorrect = "off" 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" autoComplete = "off" autoCorrect = "off" 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" ;
}
2023-03-17 23:07:45 +01:00
if ( this . portStr . get ( ) != "" && this . portStr . get ( ) != "22" ) {
kwargs [ "port" ] = this . portStr . get ( ) ;
}
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 ( ) } >
2023-02-28 03:01:05 +01:00
< i onClick = { this . resetPw } title = "restore to original password" className = "icon fa-sharp fa-solid fa-rotate-left undo-icon" / >
2022-10-04 19:52:55 +02:00
< / 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 ;
}
2023-03-15 19:14:28 +01:00
let rtn = sprintf ( "%-15s " , "[" + truncateWithTDots ( rname , 13 ) + "]" )
2022-08-31 21:00:53 +02:00
return rtn ;
}
2023-03-15 19:14:28 +01:00
renderHInfoText ( hitem : HistoryItem , opts : HistoryQueryOpts , isSelected : boolean , snames : Record < string , string > , scrNames : Record < string , string > ) : string {
let remoteStr = "" ;
if ( ! opts . limitRemote ) {
remoteStr = this . renderRemote ( hitem ) ;
}
let selectedStr = ( isSelected ? "*" : " " ) ;
let lineNumStr = ( hitem . linenum > 0 ? "(" + hitem . linenum + ")" : "" ) ;
if ( isBlank ( opts . queryType ) || opts . queryType == "screen" ) {
return selectedStr + sprintf ( "%7s" , lineNumStr ) + " " + remoteStr ;
}
if ( opts . queryType == "session" ) {
let screenStr = "" ;
if ( ! isBlank ( hitem . screenid ) ) {
let scrName = scrNames [ hitem . screenid ] ;
if ( scrName != null ) {
screenStr = "[" + truncateWithTDots ( scrName , 15 ) + "]" ;
}
}
return selectedStr + sprintf ( "%17s" , screenStr ) + sprintf ( "%7s" , lineNumStr ) + " " + remoteStr ;
}
2022-08-31 22:29:59 +02:00
if ( opts . queryType == "global" ) {
2023-03-15 19:14:28 +01:00
let sessionStr = "" ;
2022-08-31 22:29:59 +02:00
if ( ! isBlank ( hitem . sessionid ) ) {
2023-03-15 19:14:28 +01:00
let sessionName = snames [ hitem . sessionid ] ;
if ( sessionName != null ) {
sessionStr = "#" + truncateWithTDots ( sessionName , 15 ) ;
}
}
let screenStr = "" ;
if ( ! isBlank ( hitem . screenid ) ) {
let scrName = scrNames [ hitem . screenid ] ;
if ( scrName != null ) {
screenStr = "[" + truncateWithTDots ( scrName , 13 ) + "]" ;
2022-08-31 22:29:59 +02:00
}
}
2023-03-15 19:14:28 +01:00
let ssStr = sessionStr + screenStr ;
return selectedStr + sprintf ( "%15s " , sessionStr ) + " " + sprintf ( "%15s" , screenStr ) + sprintf ( "%7s" , lineNumStr ) + " " + remoteStr ;
2022-08-31 22:29:59 +02:00
}
2023-03-15 19:14:28 +01:00
return "-" ;
}
renderHItem ( hitem : HistoryItem , opts : HistoryQueryOpts , isSelected : boolean , snames : Record < string , string > , scrNames : Record < string , string > ) : any {
let lines = hitem . cmdstr . split ( "\n" ) ;
let line : string = "" ;
let idx = 0 ;
let infoText = this . renderHInfoText ( hitem , opts , isSelected , snames , scrNames ) ;
let infoTextSpacer = sprintf ( "%" + infoText . length + "s" , "" ) ;
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 ) } >
2023-03-15 19:14:28 +01:00
< div className = "history-line" > { infoText } { lines [ 0 ] } < / div >
2022-08-31 22:29:59 +02:00
< For each = "line" index = "idx" of = { lines . slice ( 1 ) } >
2023-03-15 19:14:28 +01:00
< div key = { idx } className = "history-line" > { infoTextSpacer } { line } < / div >
2022-08-31 00:25:51 +02:00
< / 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 ( ) ;
2023-03-15 19:14:28 +01:00
let snames : Record < string , string > = { } ;
let scrNames : Record < string , string > = { } ;
if ( opts . queryType == "global" ) {
scrNames = GlobalModel . getScreenNames ( ) ;
snames = GlobalModel . getSessionNames ( ) ;
}
else if ( opts . queryType == "session" ) {
scrNames = GlobalModel . getScreenNames ( ) ;
}
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 = "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 } >
2023-03-15 19:14:28 +01:00
{ this . renderHItem ( hitem , opts , ( hitem == selItem ) , snames , scrNames ) }
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 ( ) ;
}
2023-03-14 05:56:29 +01:00
@boundMethod
clickFocusInputHint ( ) : void {
GlobalModel . inputModel . giveFocus ( ) ;
}
@boundMethod
2023-03-15 05:15:26 +01:00
clickHistoryHint ( e : any ) : void {
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
2023-03-14 05:56:29 +01:00
let inputModel = GlobalModel . inputModel ;
if ( inputModel . historyShow . get ( ) ) {
inputModel . resetHistory ( ) ;
}
else {
inputModel . openHistory ( ) ;
}
}
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 ;
2023-03-13 20:09:17 +01:00
let screen = GlobalModel . getActiveScreen ( ) ;
2022-08-11 03:35:18 +02:00
let ri : RemoteInstanceType = null ;
2022-08-24 22:19:59 +02:00
let rptr : RemotePtrType = null ;
2023-03-13 20:09:17 +01:00
if ( screen != null ) {
ri = screen . getCurRemoteInstance ( ) ;
rptr = screen . 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 } >
2023-02-28 03:01:05 +01:00
< i className = "fa-sharp fa-solid fa-chevron-down" / >
2022-09-17 01:37:54 +02:00
< / If >
< If condition = { ! ( infoShow || historyShow ) && hasInfo } >
2023-02-28 03:01:05 +01:00
< i className = "fa-sharp fa-solid fa-chevron-up" / >
2022-09-17 01:37:54 +02:00
< / 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" >
2023-03-14 05:56:29 +01:00
< div onClick = { GlobalModel . inputModel . uiSubmitCommand } className = "button" title = "Run Command" >
2022-06-08 02:25:35 +02:00
< span className = "icon" >
2023-02-28 03:01:05 +01:00
< i className = "fa-sharp fa-solid fa-rocket" / >
2022-06-08 02:25:35 +02:00
< / span >
< / div >
< / div >
2023-03-14 07:36:27 +01:00
< div className = "cmd-hints" >
< If condition = { ! focusVal } > < div onClick = { this . clickFocusInputHint } className = "hint-item color-white" > focus input ( { renderCmdText ( "I" ) } ) < / div > < / If >
2023-03-15 05:15:26 +01:00
< If condition = { focusVal } > < div onMouseDown = { this . clickHistoryHint } className = "hint-item color-green" > < i className = { cn ( "fa-sharp fa-solid" , ( historyShow ? "fa-angle-down" : "fa-angle-up" ) ) } / > { historyShow ? "close history (esc)" : "show history (ctrl-r)" } < / div > < / If >
2023-03-14 05:56:29 +01:00
< / div >
2022-06-08 02:25:35 +02:00
< / div >
< / div >
) ;
}
}
2023-03-02 09:33:10 +01:00
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-06-08 02:25:35 +02:00
@mobxReact . observer
2023-03-13 20:09:17 +01:00
class LinesView extends React . Component < { screen : Screen , 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 {
2023-03-13 20:09:17 +01:00
let { screen } = this . props ;
2022-10-08 03:25:47 +02:00
let linesElem = this . linesRef . current ;
if ( linesElem == null ) {
2023-03-13 20:09:17 +01:00
screen . 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 ) {
2023-03-13 20:09:17 +01:00
screen . 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 ] ;
}
2023-03-13 20:09:17 +01:00
screen . 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 ;
}
2023-03-20 08:05:25 +01:00
if ( linesElem . offsetParent == null ) {
return ; // handles when parent is set to display:none (is-hidden)
}
2022-10-08 03:25:47 +02:00
let lineElemArr = linesElem . querySelectorAll ( ".line" ) ;
if ( lineElemArr == null ) {
return ;
}
2023-03-20 08:05:25 +01:00
if ( linesElem . clientHeight == 0 ) {
return ; // when linesElem is collapsed (or display:none)
}
2022-10-08 03:25:47 +02:00
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
}
2023-03-20 08:05:25 +01:00
// console.log("line", lineElem.dataset.linenum, "top=" + lineTop, "bot=" + lineTop, isVis);
2023-01-03 07:54:40 +01:00
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 ) ;
}
}
} ) ( ) ;
}
2023-03-20 08:05:25 +01:00
printVisMap ( ) : void {
let visMap = this . visibleMap ;
let lines = this . props . lines ;
let visLines : string [ ] = [ ] ;
for ( let i = 0 ; i < lines.length ; i + + ) {
let linenum = String ( lines [ i ] . linenum ) ;
if ( visMap . get ( linenum ) . get ( ) ) {
visLines . push ( linenum ) ;
}
}
console . log ( "vislines" , visLines ) ;
}
2022-10-08 03:25:47 +02:00
restoreAnchorOffset ( reason : string ) : void {
2023-03-13 20:09:17 +01:00
let { screen } = this . props ;
2022-10-08 03:25:47 +02:00
let linesElem = this . linesRef . current ;
if ( linesElem == null ) {
return ;
}
2023-03-13 20:09:17 +01:00
if ( screen . anchorLine == null || screen . anchorLine == 0 ) {
2022-10-08 03:25:47 +02:00
return ;
}
2023-03-13 20:09:17 +01:00
let anchorElem = linesElem . querySelector ( sprintf ( ".line[data-linenum=\"%d\"]" , screen . anchorLine ) ) ;
2022-10-08 03:25:47 +02:00
if ( anchorElem == null ) {
return ;
}
2023-03-13 20:09:17 +01:00
let isLastLine = screen . isLastLine ( screen . anchorLine ) ;
2022-10-08 03:25:47 +02:00
let scrollTop = linesElem . scrollTop ;
let height = linesElem . clientHeight ;
let containerBottom = scrollTop + height ;
let curAnchorOffset = containerBottom - ( anchorElem . offsetTop + anchorElem . offsetHeight ) ;
2023-03-13 20:09:17 +01:00
let newAnchorOffset = screen . anchorOffset ;
2023-03-03 21:06:17 +01:00
if ( isLastLine && newAnchorOffset == 0 ) {
newAnchorOffset = 10 ;
}
if ( curAnchorOffset != newAnchorOffset ) {
let offsetDiff = curAnchorOffset - newAnchorOffset ;
2022-10-08 03:25:47 +02:00
let newScrollTop = scrollTop - offsetDiff ;
2023-03-13 20:09:17 +01:00
// console.log("update scrolltop", reason, "line=" + screen.anchorLine, -offsetDiff, linesElem.scrollTop, "=>", newScrollTop);
2022-10-08 03:25:47 +02:00
linesElem . scrollTop = newScrollTop ;
this . ignoreNextScroll = true ;
}
}
componentDidMount ( ) : void {
2023-03-13 20:09:17 +01:00
let { screen , lines } = this . props ;
2023-03-04 03:04:53 +01:00
let linesElem = this . linesRef . current ;
2023-03-13 20:09:17 +01:00
let anchorLineObj = screen . getLineByNum ( screen . anchorLine ) ;
2023-03-04 03:04:53 +01:00
if ( anchorLineObj == null ) {
// scroll to bottom
if ( linesElem != null ) {
linesElem . scrollTop = linesElem . clientHeight ;
}
2022-10-10 21:45:57 +02:00
this . computeAnchorLine ( ) ;
}
else {
this . restoreAnchorOffset ( "re-mount" ) ;
}
2023-03-13 20:09:17 +01:00
this . lastSelectedLine = screen . getSelectedLine ( ) ;
2022-10-11 02:18:40 +02:00
this . lastLinesLength = lines . length ;
2022-10-08 03:25:47 +02:00
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 {
2023-03-13 20:09:17 +01:00
let { screen , lines } = this . props ;
2022-10-10 21:45:57 +02:00
let linesElem = this . linesRef . current ;
if ( linesElem == null ) {
return null ;
}
2023-03-13 20:09:17 +01:00
let newLine = screen . getSelectedLine ( ) ;
2023-02-01 07:22:51 +01:00
if ( newLine == 0 ) {
return ;
}
2022-10-11 22:25:23 +02:00
this . setLineVisible ( newLine , true ) ;
2023-03-13 20:09:17 +01:00
// console.log("update selected line", this.lastSelectedLine, "=>", newLine, sprintf("anchor=%d:%d", screen.anchorLine, screen.anchorOffset));
2022-10-10 21:45:57 +02:00
let viewInfo = this . getLineViewInfo ( newLine ) ;
if ( viewInfo == null ) {
return ;
}
2023-03-13 20:09:17 +01:00
screen . 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 ;
2023-03-13 20:09:17 +01:00
screen . 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 ;
2023-03-13 20:09:17 +01:00
screen . anchorOffset = linesElem . clientHeight - viewInfo . height ;
2022-10-10 21:45:57 +02:00
}
2023-03-13 20:09:17 +01:00
// console.log("new anchor", screen.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 {
2023-03-13 20:09:17 +01:00
let { screen , lines } = this . props ;
if ( screen . getSelectedLine ( ) != this . lastSelectedLine ) {
2022-10-10 21:45:57 +02:00
this . updateSelectedLine ( ) ;
2023-03-13 20:09:17 +01:00
this . lastSelectedLine = screen . getSelectedLine ( ) ;
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-03-13 20:09:17 +01:00
let { screen , width , lines , renderMode } = this . props ;
let selectedLine = screen . getSelectedLine ( ) ; // 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-03-13 20:09:17 +01:00
let lineElem = < Line key = { line . lineid } line = { line } screen = { screen } 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 >
) ;
}
}
2023-03-13 20:09:17 +01:00
// screen is not null
2022-10-08 03:25:47 +02:00
@mobxReact . observer
2023-03-13 20:09:17 +01:00
class ScreenWindowView extends React . Component < { screen : Screen } , { } > {
2022-10-08 03:25:47 +02:00
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-03-13 20:09:17 +01:00
let { screen } = this . props ;
if ( screen == null ) {
2023-02-26 09:04:24 +01:00
return ;
}
2023-03-17 05:48:30 +01:00
if ( width == null || height == null || width == 0 || height == 0 ) {
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-03-17 05:48:30 +01:00
screen . screenSizeCallback ( { height : height , width : width } ) ;
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
}
2023-03-15 04:09:59 +01:00
getScreenLines ( ) : ScreenLines {
2023-03-13 20:09:17 +01:00
let { screen } = this . props ;
2023-03-16 02:13:36 +01:00
let win = GlobalModel . getScreenLinesById ( screen . screenId ) ;
2022-07-15 03:41:49 +02:00
if ( win == null ) {
2023-03-16 02:13:36 +01:00
win = GlobalModel . loadScreenLines ( screen . screenId ) ;
2022-07-15 03:41:49 +02:00
}
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 ) {
2023-03-13 20:09:17 +01:00
let { screen } = this . props ;
2022-07-12 07:43:58 +02:00
return (
2023-03-15 00:38:26 +01:00
< div className = "window-view" style = { this . getWindowViewStyle ( ) } ref = { this . windowViewRef } data - screenid = { screen . screenId } >
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() {
2023-03-13 20:09:17 +01:00
let { screen } = this . props ;
2023-03-15 04:09:59 +01:00
let win = this . getScreenLines ( ) ;
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 ;
2023-03-13 20:09:17 +01:00
let session = GlobalModel . getSessionById ( screen . sessionId ) ;
let isActive = screen . isActive ( ) ;
let selectedLine = screen . getSelectedLine ( ) ;
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 } >
2023-03-15 00:38:26 +01:00
< div key = "rendermode-tag" className = { cn ( "rendermode-tag" , { "is-active" : isActive } ) } >
2023-02-28 00:52:55 +01:00
< 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-03-13 20:09:17 +01:00
< LinesView screen = { screen } 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" >
2023-03-15 00:38:26 +01:00
< div > < code > [ session = "{session.name.get()}" screen = "{screen.name.get()}" ] < / code > < / div >
2022-07-14 08:11:45 +02:00
< / 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 ;
2023-03-13 20:09:17 +01:00
if ( screen == null ) {
2022-07-13 08:29:39 +02:00
return (
< div className = "screen-view" >
2023-03-13 20:09:17 +01:00
( no screen found )
2022-07-13 08:29:39 +02:00
< / div >
) ;
}
2023-02-26 08:16:58 +01:00
let fontSize = GlobalModel . termFontSize . get ( ) ;
2022-07-13 08:29:39 +02:00
return (
2023-03-13 20:09:17 +01:00
< div className = "screen-view" data - screenid = { screen . screenId } >
< ScreenWindowView key = { screen . screenId } screen = { screen } / >
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
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 ( ) ;
}
2023-03-14 07:36:27 +01:00
@boundMethod
openScreenSettings ( e : any , screen : Screen ) : void {
e . preventDefault ( ) ;
2023-03-14 19:48:14 +01:00
e . stopPropagation ( ) ;
2023-03-14 07:36:27 +01:00
mobx . action ( ( ) = > {
GlobalModel . screenSettingsModal . set ( { sessionId : screen.sessionId , screenId : screen.screenId } ) ;
} ) ( ) ;
}
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 ( ) ;
2023-03-13 20:09:17 +01:00
let screens = GlobalModel . getSessionScreens ( session . sessionId ) ;
for ( let screen of screens ) {
2022-12-26 21:36:24 +01:00
if ( ! screen . archived . get ( ) || activeScreenId == screen . screenId ) {
showingScreens . push ( screen ) ;
}
}
2023-03-15 05:15:26 +01:00
showingScreens . sort ( ( a , b ) = > {
let aidx = a . screenIdx . get ( ) ;
let bidx = b . screenIdx . get ( ) ;
if ( aidx < bidx ) {
return - 1 ;
}
if ( aidx > bidx ) {
return 1 ;
}
return 0 ;
} ) ;
2022-07-13 08:29:39 +02:00
return (
2023-03-14 07:36:27 +01:00
< div className = "screen-tabs-container" >
< div className = { cn ( "screen-tabs" , { "scrolling" : this . scrolling . get ( ) } ) } ref = { this . tabsRef } onScroll = { this . handleScroll } >
< For each = "screen" index = "index" of = { showingScreens } >
< 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 . openScreenSettings ( event , screen ) } >
< If condition = { screen . archived . get ( ) } > < i title = "archived" className = "fa-sharp fa-solid fa-box-archive" / > < / If > { screen . name . get ( ) }
< If condition = { index + 1 <= 9 } >
< div className = "tab-index" > { renderCmdText ( String ( index + 1 ) ) } < / div >
< div onClick = { ( e ) = > this . openScreenSettings ( e , screen ) } title = "Settings" className = "tab-gear" > < i className = "fa-sharp fa-solid fa-gear" / > < / div >
< / If >
< If condition = { index + 1 > 9 } >
< div onClick = { ( e ) = > this . openScreenSettings ( e , screen ) } title = "Settings" className = "tab-gear" > < i className = "fa-sharp fa-solid fa-gear" / > < / div >
< / If >
< / div >
< / For >
< div key = "new-screen" className = "screen-tab new-screen" onClick = { this . handleNewScreen } >
< i className = "fa-sharp fa-solid fa-plus" / >
2022-07-14 08:11:45 +02:00
< / div >
2023-03-14 07:36:27 +01:00
< / div >
< div className = "cmd-hints" >
< div className = "hint-item color-green" > move left { renderCmdText ( "[" ) } < / div >
< div className = "hint-item color-green" > move right { renderCmdText ( "]" ) } < / div >
< div className = "hint-item color-green" > new tab { renderCmdText ( "T" ) } < / div >
2022-07-14 08:11:45 +02:00
< / 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 >
) ;
}
}
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 ;
}
2023-02-28 03:01:05 +01:00
let icon = "fa-sharp fa-solid fa-circle"
2022-10-01 02:23:28 +02:00
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-03-02 09:33:10 +01:00
if ( GlobalModel . activeMainView . get ( ) == "history" ) {
mobx . action ( ( ) = > {
GlobalModel . activeMainView . set ( "session" ) ;
} ) ( ) ;
return ;
}
2023-03-06 22:57:28 +01:00
GlobalModel . historyViewModel . reSearch ( ) ;
2023-03-02 09:33:10 +01:00
}
@boundMethod
handlePlaybookClick ( ) : void {
console . log ( "playbook click" ) ;
return ;
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
}
2023-03-08 05:40:30 +01:00
@boundMethod
handleWelcomeClick ( ) : void {
mobx . action ( ( ) = > {
GlobalModel . welcomeModalOpen . set ( true ) ;
} ) ( ) ;
}
2023-03-14 19:48:14 +01:00
@boundMethod
openSessionSettings ( e : any , session : Session ) : void {
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
mobx . action ( ( ) = > {
GlobalModel . sessionSettingsModal . set ( session . sessionId ) ;
} ) ( ) ;
}
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 ( ) ;
2023-03-15 04:09:59 +01:00
let activeWindow = model . getScreenLinesForActiveScreen ( ) ;
2023-03-13 20:09:17 +01:00
let activeScreen = model . getActiveScreen ( ) ;
2022-09-30 23:57:23 +02:00
let activeRemoteId : string = null ;
2023-03-13 20:09:17 +01:00
if ( activeScreen != null ) {
let rptr = activeScreen . curRemote . get ( ) ;
2022-09-30 23:57:23 +02:00
if ( rptr != null && ! isBlank ( rptr . remoteid ) ) {
activeRemoteId = rptr . remoteid ;
}
}
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 ( ) ;
2023-03-02 09:33:10 +01:00
let activePlaybookId : string = null ;
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 } >
2023-02-28 03:01:05 +01:00
< If condition = { ! isCollapsed } > < i className = "fa-sharp fa-solid fa-arrow-left" / > < / If >
< If condition = { isCollapsed } > < i className = "fa-sharp fa-solid 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 >
2023-03-14 19:48:14 +01:00
< ul className = "menu-list session-menu-list" >
2022-07-12 02:55:03 +02:00
< If condition = { ! model . sessionListLoaded . get ( ) } >
2023-03-14 19:48:14 +01:00
< li className = "menu-loading-message" > < a > . . . < / 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 ( ) } >
2023-03-14 19:48:14 +01:00
< div className = "session-num" > { idx + 1 } < / div >
2022-12-27 03:50:22 +01:00
< / If >
< If condition = { session . archived . get ( ) } >
2023-03-14 19:48:14 +01:00
< div className = "session-num" > < i title = "archived" className = "fa-sharp fa-solid fa-box-archive" / > < / div >
2022-12-27 03:50:22 +01:00
< / If >
2023-03-14 19:48:14 +01:00
< div >
{ session . name . get ( ) }
< / div >
< div className = "flex-spacer" / >
< div className = "session-gear" onClick = { ( e ) = > this . openSessionSettings ( e , session ) } >
< i className = "fa-sharp fa-solid fa-gear" / >
< / div >
2022-08-27 02:28:56 +02:00
< / a > < / li >
2022-07-11 23:43:18 +02:00
< / For >
2023-02-28 03:01:05 +01:00
< li className = "new-session" > < a onClick = { ( ) = > this . handleNewSession ( ) } > < i className = "fa-sharp fa-solid 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" >
2023-02-28 03:01:05 +01:00
< li className = "new-session" > < a onClick = { ( ) = > this . handleNewSharedSession ( ) } > < i className = "fa-sharp fa-solid fa-plus" / > New Session < / a > < / li >
2023-02-21 03:01:47 +01:00
< / ul >
2023-03-02 09:33:10 +01:00
< ul className = "menu-list" style = { { marginTop : 20 } } >
2023-03-14 03:54:41 +01:00
< li className = "menu-history" > < a onClick = { this . handleHistoryClick } className = { cn ( { "is-active" : ( mainView == "history" ) } ) } > < i className = "fa-sharp fa-solid fa-clock" / > HISTORY < span className = "hotkey" > & # x2318 ; H < / span > < / a > < / li >
2023-02-03 23:26:46 +01:00
< / ul >
2023-03-02 09:33:10 +01:00
< ul className = "menu-list" >
2023-03-14 03:54:41 +01:00
< li className = "menu-bookmarks" > < a onClick = { this . handleBookmarksClick } className = { cn ( { "is-active" : ( mainView == "bookmarks" ) } ) } > < i className = "fa-sharp fa-solid fa-bookmark" / > BOOKMARKS < span className = "hotkey" > & # x2318 ; B < / span > < / a > < / li >
2023-02-21 03:01:47 +01:00
< / ul >
2023-03-07 00:51:50 +01:00
< p className = "menu-label display-none" >
2023-03-02 09:33:10 +01:00
Playbooks
< / p >
2023-03-07 00:51:50 +01:00
< ul className = "menu-list display-none" >
2023-03-02 09:33:10 +01:00
< li key = "default" > < a onClick = { this . handlePlaybookClick } > < i className = "fa-sharp fa-solid fa-file-lines" / > default < / a > < / li >
< li key = "prompt-dev" > < a onClick = { this . handlePlaybookClick } > < i className = "fa-sharp fa-solid fa-file-lines" / > prompt - dev < / a > < / li >
< / ul >
2022-06-20 22:03:20 +02:00
< div className = "spacer" > < / div >
2023-03-13 20:09:17 +01:00
< If condition = { GlobalModel . debugScreen . get ( ) && activeScreen != null } >
2022-10-11 23:57:22 +02:00
< div >
2023-03-13 20:09:17 +01:00
focus = { activeScreen . focusType . get ( ) } < br / >
sline = { activeScreen . getSelectedLine ( ) } < br / >
termfocus = { activeScreen . termLineNumFocus . get ( ) } < br / >
2022-10-11 23:57:22 +02:00
< / div >
< / If >
2023-03-15 05:24:35 +01:00
< ul className = "menu-list" style = { { display : "none" } } >
2023-03-08 05:40:30 +01:00
< li className = "menu-bookmarks" > < a onClick = { this . handleWelcomeClick } className = { cn ( { "is-active" : GlobalModel . welcomeModalOpen . get ( ) } ) } > < i className = "fa-sharp fa-solid fa-door-open" / > WELCOME < / a > < / li >
< / ul >
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-28 03:01:05 +01:00
< a target = "_blank" href = "https://docs.getprompt.dev/releasenotes" > < i style = { { width : 20 } } className = "fa-sharp fa-solid fa-notes" / > release notes < / a >
< / li >
< li >
< a target = "_blank" href = "https://docs.getprompt.dev/" > < i style = { { width : 20 } } className = "fa-sharp fa-solid 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" >
2023-02-28 03:01:05 +01:00
< a onClick = { ( ) = > this . handleAddRemote ( ) } > < i className = "fa-sharp fa-solid 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 (
2023-03-19 22:59:21 +01:00
< div className = "prompt-modal disconnected-modal modal is-active" >
2022-10-28 23:17:45 +02:00
< div className = "modal-background" > < / div >
2023-03-19 22:59:21 +01:00
< div className = "modal-content" >
2022-08-18 09:39:06 +02:00
< div className = "message-header" >
2023-03-19 22:59:21 +01:00
< div className = "modal-title" > Prompt Client Disconnected < / div >
2022-08-18 09:39:06 +02:00
< / div >
2022-10-28 23:17:45 +02:00
< If condition = { this . showLog . get ( ) } >
2023-03-19 22:59:21 +01:00
< div className = "inner-content" >
2022-10-28 23:17:45 +02:00
< 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 >
2023-03-19 22:59:21 +01:00
< 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 ( ) } >
2023-02-28 03:01:05 +01:00
< i className = "fa-sharp fa-solid fa-plus" / > Show Log
2022-10-28 23:17:45 +02:00
< / If >
< If condition = { this . showLog . get ( ) } >
2023-02-28 03:01:05 +01:00
< i className = "fa-sharp fa-solid fa-minus" / > Hide Log
2022-10-28 23:17:45 +02:00
< / If >
< / div >
2023-03-19 22:59:21 +01:00
< div className = "flex-spacer" / >
2022-10-28 23:17:45 +02:00
< button onClick = { this . tryReconnect } className = "button" >
2022-08-18 09:39:06 +02:00
< span className = "icon" >
2023-02-28 03:01:05 +01:00
< i className = "fa-sharp fa-solid 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" >
2023-02-28 03:01:05 +01:00
< i className = "fa-sharp fa-solid fa-triangle-exclamation" / >
2022-10-28 23:17:45 +02:00
< / span >
< span > Restart Server < / span >
2022-08-18 09:39:06 +02:00
< / button >
2023-03-19 22:59:21 +01:00
< / footer >
2022-08-18 09:39:06 +02:00
< / 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 >
) ;
}
}
2023-03-03 19:16:31 +01:00
@mobxReact . observer
class AlertModal extends React . Component < { } , { } > {
2023-03-03 21:38:55 +01:00
@boundMethod
2023-03-03 19:16:31 +01:00
closeModal ( ) : void {
2023-03-03 21:38:55 +01:00
GlobalModel . cancelAlert ( ) ;
}
@boundMethod
handleOK ( ) : void {
GlobalModel . confirmAlert ( ) ;
2023-03-03 19:16:31 +01:00
}
render() {
let message = GlobalModel . alertMessage . get ( ) ;
if ( message == null ) {
return null ;
}
let title = message . title ? ? "Alert" ;
2023-03-03 21:38:55 +01:00
let isConfirm = message . confirm ;
2023-03-03 19:16:31 +01:00
return (
2023-03-14 07:36:27 +01:00
< div className = "modal prompt-modal is-active alert-modal" >
2023-03-03 19:16:31 +01:00
< div className = "modal-background" / >
2023-03-14 07:36:27 +01:00
< div className = "modal-content" >
< header >
< p className = "modal-title" > < i className = "fa-sharp fa-solid fa-triangle-exclamation" / > { title } < / p >
< div className = "close-icon" >
< i onClick = { this . closeModal } className = "fa-sharp fa-solid fa-times" / >
< / div >
2023-03-03 19:16:31 +01:00
< / header >
2023-03-14 07:36:27 +01:00
< div className = "inner-content content" >
2023-03-03 19:16:31 +01:00
< p > { message . message } < / p >
2023-03-14 07:36:27 +01:00
< / div >
< footer >
2023-03-03 21:38:55 +01:00
< If condition = { isConfirm } >
2023-03-15 04:09:59 +01:00
< div onClick = { this . closeModal } className = "button is-prompt-cancel is-outlined is-small" > Cancel < / div >
< div onClick = { this . handleOK } className = "button is-prompt-green is-outlined is-small" > OK < / div >
2023-03-03 21:38:55 +01:00
< / If >
< If condition = { ! isConfirm } >
2023-03-15 04:09:59 +01:00
< div onClick = { this . handleOK } className = "button is-prompt-green is-small" > OK < / div >
2023-03-03 21:38:55 +01:00
< / If >
2023-03-03 19:16:31 +01:00
< / footer >
< / div >
2023-03-14 07:36:27 +01:00
< / div >
) ;
}
}
2023-03-08 05:40:30 +01:00
@mobxReact . observer
class WelcomeModal extends React . Component < { } , { } > {
totalPages : number = 3 ;
pageNum : OV < number > = mobx . observable . box ( 1 , { name : "welcome-pagenum" } ) ;
@boundMethod
closeModal ( ) : void {
mobx . action ( ( ) = > {
GlobalModel . welcomeModalOpen . set ( false ) ;
} ) ( ) ;
}
@boundMethod
goNext ( ) : void {
mobx . action ( ( ) = > {
this . pageNum . set ( this . pageNum . get ( ) + 1 ) ;
} ) ( ) ;
}
@boundMethod
goPrev ( ) : void {
mobx . action ( ( ) = > {
this . pageNum . set ( this . pageNum . get ( ) - 1 ) ;
} ) ( ) ;
}
renderDot ( num : number ) : any {
if ( num == this . pageNum . get ( ) ) {
return < i key = { String ( num ) } className = "fa-sharp fa-solid fa-circle" / > ;
}
return < i key = { String ( num ) } className = "fa-sharp fa-regular fa-circle" / > ;
}
renderDots ( ) : any {
let elems : any = [ ] ;
for ( let i = 1 ; i <= this . totalPages ; i ++ ) {
let elem = this . renderDot ( i ) ;
elems . push ( elem ) ;
}
return elems ;
}
render() {
let pageNum = this . pageNum . get ( ) ;
return (
2023-03-14 07:36:27 +01:00
< div className = { cn ( "modal welcome-modal prompt-modal is-active" ) } >
2023-03-08 05:40:30 +01:00
< div className = "modal-background" / >
< div className = "modal-content" >
< header >
< div className = "modal-title" > welcome to [ prompt ] < / div >
< div className = "close-icon" >
< i onClick = { this . closeModal } className = "fa-sharp fa-solid fa-times" / >
< / div >
< / header >
2023-03-14 07:36:27 +01:00
< div className = { cn ( "inner-content content" , { "is-hidden" : pageNum != 1 } ) } >
2023-03-08 05:40:30 +01:00
< p >
Prompt is a new terminal to help save you time and keep your command - line life organized .
Here ' s a couple quick tips to get your started !
< / p >
< / div >
< footer >
< If condition = { pageNum > 1 } >
< button className = { cn ( "button is-dark prev-button is-small" ) } onClick = { this . goPrev } >
< span className = "icon is-small" >
< i className = "fa-sharp fa-regular fa-angle-left" / >
< / span >
< span > Prev < / span >
< / button >
< / If >
< If condition = { pageNum == 1 } >
< div className = "prev-spacer" / >
< / If >
< div className = "flex-spacer" / >
< div className = "dots" >
{ this . renderDots ( ) }
< / div >
< div className = "flex-spacer" / >
< If condition = { pageNum < this . totalPages } >
< button className = "button is-dark next-button is-small" onClick = { this . goNext } >
< span > Next < / span >
< span className = "icon is-small" >
< i className = "fa-sharp fa-regular fa-angle-right" / >
< / span >
< / button >
< / If >
< If condition = { pageNum == this . totalPages } >
< button className = "button is-dark next-button is-small" onClick = { this . closeModal } >
< span > Done < / span >
< / button >
< / If >
< / footer >
< / 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() {
2023-03-14 19:48:14 +01:00
let screenSettingsModal = GlobalModel . screenSettingsModal . get ( ) ;
let sessionSettingsModal = GlobalModel . sessionSettingsModal . get ( ) ;
2023-03-17 20:37:10 +01:00
let lineSettingsModal = GlobalModel . lineSettingsModal . get ( ) ;
2022-06-16 03:12:22 +02:00
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 >
2023-03-03 19:16:31 +01:00
< AlertModal / >
2023-03-08 05:40:30 +01:00
< If condition = { GlobalModel . welcomeModalOpen . get ( ) } >
< WelcomeModal / >
< / If >
2023-03-14 19:48:14 +01:00
< If condition = { screenSettingsModal != null } >
< ScreenSettingsModal key = { screenSettingsModal . sessionId + ":" + screenSettingsModal . screenId } sessionId = { screenSettingsModal . sessionId } screenId = { screenSettingsModal . screenId } / >
< / If >
< If condition = { sessionSettingsModal != null } >
< SessionSettingsModal key = { sessionSettingsModal } sessionId = { sessionSettingsModal } / >
2023-03-14 07:36:27 +01:00
< / If >
2023-03-17 20:37:10 +01:00
< If condition = { lineSettingsModal != null } >
< LineSettingsModal key = { lineSettingsModal . lineid } line = { lineSettingsModal } / >
< / 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