reinit updates (#500)

* working on re-init when you create a tab.  some refactoring of existing reinit to make the messaging clearer.  auto-connect, etc.

* working to remove the 'default' shell states out of MShellProc.  each tab should have its own state that gets set on open.

* refactor newtab settings into individual components (and move to a new file)

* more refactoring of tab settings -- use same control in settings and newtab

* have screensettings use the same newtab settings components

* use same conn dropdown, fix classes, update some of the confirm messages to be less confusing (replace screen with tab)

* force a cr on a new tab to initialize state in a new line.  poc right now, need to add to new workspace workflow as well

* small fixups

* remove nohist from GetRawStr, make const

* update hover behavior for tabs

* fix interaction between change remote dropdown, cmdinput, error handling, and selecting a remote

* only switch screen remote if the activemainview is session (new remote flow).  don't switch it if we're on the connections page which is confusing.  also make it interactive

* fix wording on tos modal

* allow empty workspaces. also allow the last workspace to be deleted.  (prep for new startup sequence where we initialize the first session after tos modal)

* add some dead code that might come in use later (when we change how we show connection in cmdinput)

* working a cople different angles.  new settings tab-pulldown (likely orphaned).  and then allowing null activeScreen and null activeSession in workspaceview (show appropriate messages, and give buttons to create new tabs/workspaces).  prep for new startup flow

* don't call initActiveShells anymore.  also call ensureWorkspace() on TOS close

* trying to use new pulldown screen settings

* experiment with an escape keybinding

* working on tab settings close triggers

* close tab settings on tab switch

* small updates to tos popup, reorder, update button text/size, small wording updates

* when deleting a screen, send SIGHUP to all running commands

* not sure how this happened, lineid should not be passed to setLineFocus

* remove context timeouts for ReInit (it is now interactive, so it gets canceled like a normal command -- via ^C, and should not timeout on its own)

* deal with screen/session tombstones updates (ignore to quite warning)

* remove defaultfestate from remote

* fix issue with removing default ris

* remove dead code

* open the settings pulldown for new screens

* update prompt to show when the shell is still initializing (or if it failed)

* switch buttons to use wave button class, update messages, and add warning for no shell state

* all an override of rptr for dyncmds.  needed for the 'connect' command (we need to set the rptr to the *new* connection rather than the old one)

* remove old commented out code
This commit is contained in:
Mike Sawka
2024-03-27 00:22:57 -07:00
committed by GitHub
parent 6065ee931f
commit 3c3eec73aa
41 changed files with 1070 additions and 770 deletions
+77 -39
View File
@@ -251,6 +251,13 @@ input[type="checkbox"] {
justify-content: center;
}
.flex-centered-column {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
a.a-block {
display: block;
}
@@ -786,6 +793,75 @@ a.a-block {
margin-top: 6px;
}
.conn-dropdown {
width: 412px;
.lefticon {
position: absolute;
top: 50%;
left: 16px;
transform: translateY(-50%);
.globe-icon {
width: 16px;
height: 16px;
flex-shrink: 0;
fill: var(--app-text-secondary-color);
}
.status-icon {
position: absolute;
left: 7px;
top: 8px;
circle {
stroke: var(--app-bg-color);
}
}
}
.wave-dropdown-display {
bottom: 7px;
}
}
.tab-colors,
.tab-icons {
display: flex;
flex-direction: row;
align-items: center;
.tab-color-sep,
.tab-icon-sep {
padding-left: 10px;
padding-right: 10px;
font-weight: bold;
user-select: none;
}
.tab-color-icon,
.tab-icon-icon {
width: 1.1em;
vertical-align: middle;
}
.tab-color-name,
.tab-icon-name {
display: inline-block;
margin-left: 1em;
min-width: 70px;
user-select: none;
}
.tab-color-select,
.tab-icon-select {
cursor: pointer;
margin: 3px;
&:hover {
outline: 2px solid white;
}
}
}
.settings-field {
display: flex;
flex-direction: row;
@@ -847,7 +923,7 @@ a.a-block {
}
}
input {
input:not(.wave-input) {
padding: 4px;
border-radius: 3px;
}
@@ -858,44 +934,6 @@ a.a-block {
}
}
.tab-colors,
.tab-icons {
display: flex;
flex-direction: row;
align-items: center;
.tab-color-sep,
.tab-icon-sep {
padding-left: 10px;
padding-right: 10px;
font-weight: bold;
user-select: none;
}
.tab-color-icon,
.tab-icon-icon {
width: 1.1em;
vertical-align: middle;
}
.tab-color-name,
.tab-icon-name {
display: inline-block;
margin-left: 1em;
min-width: 70px;
user-select: none;
}
.tab-color-select,
.tab-icon-select {
cursor: pointer;
margin: 3px;
&:hover {
outline: 2px solid white;
}
}
}
.action-text {
margin-left: 20px;
+3 -1
View File
@@ -159,7 +159,9 @@ class TextField extends React.Component<TextFieldProps, TextFieldState> {
</label>
</If>
<input
className={cn("wave-textfield-inner-input", { "offset-left": decoration?.startDecoration })}
className={cn("wave-textfield-inner-input", "wave-input", {
"offset-left": decoration?.startDecoration,
})}
ref={this.inputRef}
id={label}
value={inputValue}
+11 -9
View File
@@ -107,15 +107,17 @@ class CreateRemoteConnModal extends React.Component<{}, {}> {
this.model.setRecentConnAdded(true);
this.model.closeModal();
let crRtn = GlobalCommandRunner.screenSetRemote(cname, true, false);
crRtn.then((crcrtn) => {
if (crcrtn.success) {
return;
}
mobx.action(() => {
this.errorStr.set(crcrtn.error);
})();
});
if (GlobalModel.activeMainView.get() == "session") {
let crRtn = GlobalCommandRunner.screenSetRemote(cname, true, true);
crRtn.then((crcrtn) => {
if (crcrtn.success) {
return;
}
mobx.action(() => {
this.errorStr.set(crcrtn.error);
})();
});
}
return;
}
mobx.action(() => {
+12 -148
View File
@@ -8,19 +8,22 @@ import { boundMethod } from "autobind-decorator";
import { For } from "tsx-control-statements/components";
import cn from "classnames";
import { GlobalModel, GlobalCommandRunner, Screen } from "@/models";
import { Toggle, InlineSettingsTextEdit, SettingsError, Modal, Dropdown, Tooltip } from "@/elements";
import { SettingsError, Modal, Dropdown, Tooltip } from "@/elements";
import * as util from "@/util/util";
import { TabIcon, Button } from "@/elements";
import { Button } from "@/elements";
import { ReactComponent as GlobeIcon } from "@/assets/icons/globe.svg";
import { ReactComponent as StatusCircleIcon } from "@/assets/icons/statuscircle.svg";
import * as appconst from "@/app/appconst";
import {
TabColorSelector,
TabIconSelector,
TabNameTextField,
TabRemoteSelector,
} from "@/app/workspace/screen/newtabsettings";
import "./screensettings.less";
const ScreenDeleteMessage = `
Are you sure you want to delete this tab?
All commands and output will be deleted. To hide the tab, and retain the commands and output, use 'archive'.
`.trim();
const WebShareConfirmMarkdown = `
@@ -82,39 +85,6 @@ class ScreenSettingsModal extends React.Component<{}, {}> {
GlobalModel.modalsModel.popModal();
}
@boundMethod
selectTabColor(color: string): void {
if (this.screen == null) {
return;
}
if (this.screen.getTabColor() == color) {
return;
}
const prtn = GlobalCommandRunner.screenSetSettings(this.screenId, { tabcolor: color }, false);
util.commandRtnHandler(prtn, this.errorMessage);
}
@boundMethod
selectTabIcon(icon: string): void {
if (this.screen.getTabIcon() == icon) {
return;
}
const prtn = GlobalCommandRunner.screenSetSettings(this.screen.screenId, { tabicon: icon }, false);
util.commandRtnHandler(prtn, this.errorMessage);
}
@boundMethod
handleChangeArchived(val: boolean): void {
if (this.screen == null) {
return;
}
if (this.screen.archived.get() == val) {
return;
}
const prtn = GlobalCommandRunner.screenArchive(this.screenId, val);
util.commandRtnHandler(prtn, this.errorMessage);
}
@boundMethod
handleChangeWebShare(val: boolean): void {
if (this.screen == null) {
@@ -154,30 +124,6 @@ class ScreenSettingsModal extends React.Component<{}, {}> {
}, 600);
}
@boundMethod
inlineUpdateName(val: string): void {
if (this.screen == null) {
return;
}
if (util.isStrEq(val, this.screen.name.get())) {
return;
}
const prtn = GlobalCommandRunner.screenSetSettings(this.screenId, { name: val }, false);
util.commandRtnHandler(prtn, this.errorMessage);
}
@boundMethod
inlineUpdateShareName(val: string): void {
if (this.screen == null) {
return;
}
if (util.isStrEq(val, this.screen.getShareName())) {
return;
}
const prtn = GlobalCommandRunner.screenSetSettings(this.screenId, { sharename: val }, false);
util.commandRtnHandler(prtn, this.errorMessage);
}
@boundMethod
dismissError(): void {
mobx.action(() => {
@@ -207,119 +153,37 @@ class ScreenSettingsModal extends React.Component<{}, {}> {
});
}
@boundMethod
selectRemote(cname: string): void {
let prtn = GlobalCommandRunner.screenSetRemote(cname, true, false);
util.commandRtnHandler(prtn, this.errorMessage);
}
render() {
const screen = this.screen;
if (screen == null) {
return null;
}
let color: string = null;
let icon: string = null;
let index: number = 0;
const curRemote = GlobalModel.getRemote(GlobalModel.getActiveScreen().getCurRemoteInstance().remoteid);
return (
<Modal className="screen-settings-modal">
<Modal.Header onClose={this.closeModal} title={`Tab Settings (${screen.name.get()})`} />
<div className="wave-modal-body">
<div className="settings-field">
<div className="settings-label">Tab Id</div>
<div className="settings-input">{screen.screenId}</div>
</div>
<div className="settings-field">
<div className="settings-label">Name</div>
<div className="settings-input">
<InlineSettingsTextEdit
placeholder="name"
text={screen.name.get() ?? "(none)"}
value={screen.name.get() ?? ""}
onChange={this.inlineUpdateName}
maxLength={50}
showIcon={true}
/>
<TabNameTextField screen={screen} errorMessage={this.errorMessage} />
</div>
</div>
<div className="settings-field">
<div className="settings-label">Connection</div>
<div className="settings-input">
<Dropdown
className="screen-settings-dropdown"
options={this.getOptions()}
defaultValue={curRemote.remotecanonicalname}
onChange={this.selectRemote}
decoration={{
startDecoration: (
<div className="lefticon">
<GlobeIcon className="globe-icon" />
<StatusCircleIcon
className={cn("status-icon", "status-" + curRemote.status)}
/>
</div>
),
}}
/>
<TabRemoteSelector screen={screen} errorMessage={this.errorMessage} />
</div>
</div>
<div className="settings-field">
<div className="settings-label">Tab Color</div>
<div className="settings-input">
<div className="tab-colors">
<div className="tab-color-cur">
<TabIcon icon={screen.getTabIcon()} color={screen.getTabColor()} />
<div className="tab-color-name">{screen.getTabColor()}</div>
</div>
<div className="tab-color-sep">|</div>
<For each="color" of={appconst.TabColors}>
<div
key={color}
className="tab-color-select"
onClick={() => this.selectTabColor(color)}
>
<TabIcon icon="square" color={color} />
</div>
</For>
</div>
<TabColorSelector screen={screen} errorMessage={this.errorMessage} />
</div>
</div>
<div className="settings-field">
<div className="settings-label">Tab Icon</div>
<div className="settings-input">
<div className="tab-icons">
<div className="tab-icon-cur">
<TabIcon icon={screen.getTabIcon()} color="white" />
<div className="tab-icon-name">{screen.getTabIcon()}</div>
</div>
<div className="tab-icon-sep">|</div>
<For each="icon" index="index" of={appconst.TabIcons}>
<div
key={`${color}-${index}`}
className="tab-icon-select"
onClick={() => this.selectTabIcon(icon)}
>
<TabIcon icon={icon} color="white" />
</div>
</For>
</div>
</div>
</div>
<div className="settings-field">
<div className="settings-label archived-label">
<div className="">Archived</div>
<Tooltip
message={`Archive will hide the tab. Commands and output will be retained, but hidden.`}
icon={<i className="fa-sharp fa-regular fa-circle-question" />}
className="screen-settings-tooltip"
>
<i className="fa-sharp fa-regular fa-circle-question" />
</Tooltip>
</div>
<div className="settings-input">
<Toggle checked={screen.archived.get()} onChange={this.handleChangeArchived} />
<TabIconSelector screen={screen} errorMessage={this.errorMessage} />
</div>
</div>
<div className="settings-field">
@@ -14,8 +14,6 @@ import "./sessionsettings.less";
const SessionDeleteMessage = `
Are you sure you want to delete this workspace?
All commands and output will be deleted. To hide the workspace, and retain the commands and output, use 'archive'.
`.trim();
@mobxReact.observer
+1 -1
View File
@@ -86,7 +86,7 @@
justify-content: center;
button {
font-size: 12.5px !important;
font-size: 15px;
margin-top: 16px;
}
+29 -29
View File
@@ -20,6 +20,7 @@ class TosModal extends React.Component<{}, {}> {
acceptTos(): void {
GlobalCommandRunner.clientAcceptTos();
GlobalModel.modalsModel.popModal();
GlobalCommandRunner.ensureWorkspace();
}
@boundMethod
@@ -40,32 +41,28 @@ class TosModal extends React.Component<{}, {}> {
<div className="wave-modal-body-inner">
<header className="tos-header unselectable">
<div className="modal-title">Welcome to Wave Terminal!</div>
<div className="modal-subtitle">Lets set everything for you</div>
</header>
<div className="content tos-content unselectable">
<div className="item">
<img src={shield} alt="Privacy" />
<a
target="_blank"
href={util.makeExternLink("https://github.com/wavetermdev/waveterm")}
rel={"noopener"}
>
<img src={github} alt="Github" />
</a>
<div className="item-inner">
<div className="item-title">Telemetry</div>
<div className="item-title">Support us on GitHub</div>
<div className="item-text">
We only collect minimal <i>anonymous</i>
We're <i>open source</i> and committed to providing a free terminal for
individual users. Please show your support us by giving us a star on{" "}
<a
target="_blank"
href={util.makeExternLink("https://docs.waveterm.dev/reference/telemetry")}
href={util.makeExternLink("https://github.com/wavetermdev/waveterm")}
rel={"noopener"}
>
&nbsp;telemetry data&nbsp;
Github&nbsp;(wavetermdev/waveterm)
</a>
to help us understand how many people are using Wave.
</div>
<div className="item-field" style={{ marginTop: 2 }}>
<Toggle
checked={!cdata.clientopts.notelemetry}
onChange={this.handleChangeTelemetry}
/>
<div className="item-label">
Telemetry {cdata.clientopts.notelemetry ? "Disabled" : "Enabled"}
</div>
</div>
</div>
</div>
@@ -94,25 +91,28 @@ class TosModal extends React.Component<{}, {}> {
</div>
</div>
<div className="item">
<a
target="_blank"
href={util.makeExternLink("https://github.com/wavetermdev/waveterm")}
rel={"noopener"}
>
<img src={github} alt="Github" />
</a>
<img src={shield} alt="Privacy" />
<div className="item-inner">
<div className="item-title">Support us on GitHub</div>
<div className="item-title">Telemetry</div>
<div className="item-text">
We're <i>open source</i> and committed to providing a free terminal for
individual users. Please show your support us by giving us a star on{" "}
We collect minimal anonymous
<a
target="_blank"
href={util.makeExternLink("https://github.com/wavetermdev/waveterm")}
href={util.makeExternLink("https://docs.waveterm.dev/reference/telemetry")}
rel={"noopener"}
>
Github&nbsp;(wavetermdev/waveterm)
&nbsp;telemetry data&nbsp;
</a>
to help us understand how people are using Wave.
</div>
<div className="item-field" style={{ marginTop: 2 }}>
<Toggle
checked={!cdata.clientopts.notelemetry}
onChange={this.handleChangeTelemetry}
/>
<div className="item-label">
Telemetry {cdata.clientopts.notelemetry ? "Disabled" : "Enabled"}
</div>
</div>
</div>
</div>
@@ -123,7 +123,7 @@ class TosModal extends React.Component<{}, {}> {
<a href="https://www.waveterm.dev/tos">Terms of Service</a>
</div>
<div className="button-wrapper">
<Button onClick={this.acceptTos}>Continue</Button>
<Button onClick={this.acceptTos}>Get Started</Button>
</div>
</footer>
</div>
+8
View File
@@ -21,6 +21,10 @@
color: var(--term-bright-green);
}
.term-prompt-shellmsg {
color: var(--term-bright-green);
}
.term-prompt-cwd {
color: var(--term-bright-green);
}
@@ -34,6 +38,10 @@
color: var(--term-black);
}
.term-prompt-shellmsg {
color: var(--term-green);
}
.term-prompt-remote {
color: var(--term-green);
}
+53 -18
View File
@@ -26,6 +26,20 @@ function makeFullRemoteRef(ownerName: string, remoteRef: string, name: string):
return ownerName + ":" + remoteRef + ":" + name;
}
function getRemoteStrWithAlias(rptr: RemotePtrType): string {
if (rptr == null || isBlank(rptr.remoteid)) {
return "(null)";
}
let remote = GlobalModel.getRemote(rptr.remoteid);
if (remote == null) {
return "(invalid)";
}
if (!isBlank(remote.remotealias)) {
return `${remote.remotealias} (${remote.remotecanonicalname})`;
}
return `${remote.remotecanonicalname}`;
}
function getRemoteStr(rptr: RemotePtrType): string {
if (rptr == null || isBlank(rptr.remoteid)) {
return "(invalid remote)";
@@ -69,17 +83,16 @@ function getCwdStr(remote: RemoteType, state: Record<string, string>): string {
}
@mobxReact.observer
class Prompt extends React.Component<{ rptr: RemotePtrType; festate: Record<string, string>; color: boolean }, {}> {
render() {
class Prompt extends React.Component<
{ rptr: RemotePtrType; festate: Record<string, string>; color: boolean; shellInitMsg?: string },
{}
> {
getRemoteElem() {
const rptr = this.props.rptr;
if (rptr == null || isBlank(rptr.remoteid)) {
return <span className={cn("term-prompt", "color-green")}>&nbsp;</span>;
}
const remote = GlobalModel.getRemote(this.props.rptr.remoteid);
const remoteStr = getRemoteStr(rptr);
const festate = this.props.festate ?? {};
const cwd = getCwdStr(remote, festate);
let remoteTitle: string = null;
let isRoot = false;
let remote = this.getRemote();
if (remote?.remotevars) {
if (remote.remotevars["sudo"] || remote.remotevars["bestuser"] == "root") {
isRoot = true;
@@ -89,11 +102,9 @@ class Prompt extends React.Component<{ rptr: RemotePtrType; festate: Record<stri
if (remote?.remoteopts?.color) {
remoteColorClass = "color-" + remote.remoteopts.color;
}
let remoteTitle: string = null;
if (remote?.remotecanonicalname) {
remoteTitle = "connected to " + remote.remotecanonicalname;
}
const cwdElem = <span className="term-prompt-cwd">{cwd}</span>;
let remoteElem = null;
if (remoteStr != "local") {
remoteElem = (
@@ -102,6 +113,36 @@ class Prompt extends React.Component<{ rptr: RemotePtrType; festate: Record<stri
</span>
);
}
return { remoteElem, isRoot };
}
getRemote(): RemoteType {
const remote = GlobalModel.getRemote(this.props.rptr.remoteid);
return remote;
}
render() {
const rptr = this.props.rptr;
if (rptr == null || isBlank(rptr.remoteid)) {
return <span className={cn("term-prompt", "color-green")}>&nbsp;</span>;
}
let { remoteElem, isRoot } = this.getRemoteElem();
let termClassNames = cn(
"term-prompt",
{ "term-prompt-color": this.props.color },
{ "term-prompt-isroot": isRoot }
);
if (this.props.shellInitMsg != null) {
return (
<span className={termClassNames}>
{remoteElem} <span className="term-prompt-shellmsg">{this.props.shellInitMsg}</span>
</span>
);
}
const festate = this.props.festate ?? {};
const remote = this.getRemote();
const cwd = getCwdStr(remote, festate);
const cwdElem = <span className="term-prompt-cwd">{cwd}</span>;
let branchElem = null;
let pythonElem = null;
let condaElem = null;
@@ -131,17 +172,11 @@ class Prompt extends React.Component<{ rptr: RemotePtrType; festate: Record<stri
);
}
return (
<span
className={cn(
"term-prompt",
{ "term-prompt-color": this.props.color },
{ "term-prompt-isroot": isRoot }
)}
>
<span className={termClassNames}>
{remoteElem} {cwdElem} {branchElem} {condaElem} {pythonElem}
</span>
);
}
}
export { Prompt, getRemoteStr };
export { Prompt, getRemoteStr, getRemoteStrWithAlias };
+23 -1
View File
@@ -24,6 +24,25 @@
height: 31px;
}
.cmdinput-conn {
position: absolute;
top: 0;
left: 0;
border-radius: 0 0 4px 0;
background-color: rgba(88, 193, 66, 0.3);
padding: 2px 10px 4px 10px;
font-size: calc(var(--termfontsize));
cursor: pointer;
i {
margin-left: 5px;
}
&:hover {
background-color: rgba(88, 193, 66, 0.5);
}
}
.cmdinput-actions {
position: absolute;
border-radius: 4px;
@@ -138,8 +157,10 @@
color: var(--app-warning-color);
align-items: center;
.wave-button {
.wave-button,
.button {
margin-left: 10px;
padding: 4px 10px;
}
}
@@ -170,6 +191,7 @@
font-family: var(--termfontfamily);
font-size: var(--termfontsize);
line-height: var(--termlineheight);
margin-left: 2px;
}
.cmd-input-filter {
+57 -12
View File
@@ -17,6 +17,7 @@ import { HistoryInfo } from "./historyinfo";
import { Prompt } from "@/common/prompt/prompt";
import { CenteredIcon, RotateIcon } from "@/common/icons/icons";
import { AIChat } from "./aichat";
import * as util from "@/util/util";
import "./cmdinput.less";
@@ -107,6 +108,24 @@ class CmdInput extends React.Component<{}, {}> {
GlobalCommandRunner.resetShellState();
}
getRemoteDisplayName(rptr: RemotePtrType): string {
if (rptr == null) {
return "(unknown)";
}
const remote = GlobalModel.getRemote(rptr.remoteid);
if (remote == null) {
return "(invalid)";
}
let remoteNamePart = "";
if (!util.isBlank(rptr.name)) {
remoteNamePart = "#" + rptr.name;
}
if (remote.remotealias) {
return remote.remotealias + remoteNamePart;
}
return remote.remotecanonicalname + remoteNamePart;
}
render() {
const model = GlobalModel;
const inputModel = model.inputModel;
@@ -123,6 +142,9 @@ class CmdInput extends React.Component<{}, {}> {
remote = GlobalModel.getRemote(ri.remoteid);
feState = ri.festate;
}
if (remote == null && rptr != null) {
remote = GlobalModel.getRemote(rptr.remoteid);
}
feState = feState || {};
const infoShow = inputModel.infoShow.get();
const historyShow = !infoShow && inputModel.historyShow.get();
@@ -136,6 +158,19 @@ class CmdInput extends React.Component<{}, {}> {
if (win != null) {
numRunningLines = mobx.computed(() => win.getRunningCmdLines().length).get();
}
let shellInitMsg: string = null;
let hidePrompt = false;
if (ri == null) {
let shellStr = "shell";
if (!util.isBlank(remote?.defaultshelltype)) {
shellStr = remote.defaultshelltype;
}
if (numRunningLines > 0) {
shellInitMsg = `initializing ${shellStr}...`;
} else {
hidePrompt = true;
}
}
return (
<div
ref={this.cmdInputRef}
@@ -189,34 +224,44 @@ class CmdInput extends React.Component<{}, {}> {
&nbsp;is {remote.status}
<If condition={remote.status != "connecting"}>
<Button
className="secondary small connect"
className="primary outlined"
onClick={() => this.clickConnectRemote(remote.remoteid)}
>
connect now
Connect Now
</Button>
</If>
</div>
</If>
<If condition={feState["invalidshellstate"]}>
<div className="remote-status-warning">
WARNING:&nbsp; The shell state for this tab is invalid (
The shell state for this tab is invalid (
<a target="_blank" href="https://docs.waveterm.dev/reference/faq">
see FAQ
</a>
). Must reset to continue.
<div className="button is-wave-green is-outlined is-small" onClick={this.clickResetState}>
reset shell state
</div>
<Button className="primary outlined" onClick={this.clickResetState}>
Reset Now
</Button>
</div>
</If>
<If condition={ri == null && numRunningLines == 0}>
<div className="remote-status-warning">
Shell is not initialized, must reset to continue.
<Button className="primary outlined" onClick={this.clickResetState}>
Reset Now
</Button>
</div>
</If>
<div key="base-cmdinput" className="base-cmdinput">
<div key="prompt" className="cmd-input-context">
<div className="has-text-white">
<span ref={this.promptRef}>
<Prompt rptr={rptr} festate={feState} color={true} />
</span>
<If condition={!hidePrompt}>
<div key="prompt" className="cmd-input-context">
<div className="has-text-white">
<span ref={this.promptRef}>
<Prompt rptr={rptr} festate={feState} color={true} shellInitMsg={shellInitMsg} />
</span>
</div>
</div>
</div>
</If>
<div
key="input"
className={cn(
@@ -131,6 +131,7 @@ class CmdInputKeybindings extends React.Component<{ inputObject: TextAreaInput }
return true;
});
keybindManager.registerKeybinding("pane", "cmdinput", "generic:confirm", (waveEvent) => {
GlobalModel.closeTabSettings();
if (GlobalModel.inputModel.isEmpty()) {
let activeWindow = GlobalModel.getScreenLinesForActiveScreen();
let activeScreen = GlobalModel.getActiveScreen();
@@ -144,6 +145,7 @@ class CmdInputKeybindings extends React.Component<{ inputObject: TextAreaInput }
return true;
});
keybindManager.registerKeybinding("pane", "cmdinput", "generic:cancel", (waveEvent) => {
GlobalModel.closeTabSettings();
inputModel.toggleInfoMsg();
if (inputModel.inputMode.get() != null) {
inputModel.resetInputMode();
@@ -614,6 +616,15 @@ class TextAreaInput extends React.Component<{ screen: Screen; onHeightChange: ()
if (ri != null && ri.shelltype != null) {
shellType = ri.shelltype;
}
if (shellType == "") {
let rptr = screen.curRemote.get();
if (rptr != null) {
let remote = GlobalModel.getRemote(rptr.remoteid);
if (remote != null) {
shellType = remote.defaultshelltype;
}
}
}
}
let isMainInputFocused = this.mainInputFocused.get();
let isHistoryFocused = this.historyFocused.get();
+230
View File
@@ -0,0 +1,230 @@
import * as React from "react";
import * as mobxReact from "mobx-react";
import * as mobx from "mobx";
import { boundMethod } from "autobind-decorator";
import { If, For } from "tsx-control-statements/components";
import cn from "classnames";
import { GlobalCommandRunner, GlobalModel, Screen } from "@/models";
import { Button, TextField, Dropdown } from "@/elements";
import { getRemoteStr, getRemoteStrWithAlias } from "@/common/prompt/prompt";
import * as util from "@/util/util";
import { TabIcon } from "@/elements/tabicon";
import { ReactComponent as EllipseIcon } from "@/assets/icons/ellipse.svg";
import { ReactComponent as Check12Icon } from "@/assets/icons/check12.svg";
import { ReactComponent as GlobeIcon } from "@/assets/icons/globe.svg";
import { ReactComponent as StatusCircleIcon } from "@/assets/icons/statuscircle.svg";
import * as appconst from "@/app/appconst";
import "./screenview.less";
import "./tabs.less";
@mobxReact.observer
class NewTabSettings extends React.Component<{ screen: Screen }, {}> {
errorMessage: OV<string | null> = mobx.observable.box(null, { name: "NewTabSettings-errorMessage" });
constructor(props) {
super(props);
}
@boundMethod
clickNewConnection(): void {
GlobalModel.remotesModel.openAddModal({ remoteedit: true });
}
render() {
let { screen } = this.props;
let rptr = screen.curRemote.get();
return (
<div className="newtab-container">
<div className="newtab-section name-section">
<TabNameTextField screen={screen} errorMessage={this.errorMessage} />
</div>
<div className="newtab-spacer" />
<div className="newtab-section conn-section">
<div className="unselectable">
You're connected to <b>[{getRemoteStrWithAlias(rptr)}]</b>. Do you want to change it?
</div>
<div>
<TabRemoteSelector screen={screen} errorMessage={this.errorMessage} />
</div>
<div className="text-caption cr-help-text">
To change connection from the command line use `/connect [alias|user@host]`
</div>
</div>
<div className="newtab-spacer" />
<div className="newtab-section">
<TabIconSelector screen={screen} errorMessage={this.errorMessage} />
</div>
<div className="newtab-spacer" />
<div className="newtab-section">
<TabColorSelector screen={screen} errorMessage={this.errorMessage} />
</div>
</div>
);
}
}
@mobxReact.observer
class TabNameTextField extends React.Component<{ screen: Screen; errorMessage?: OV<string> }, {}> {
@boundMethod
updateName(val: string): void {
let { screen } = this.props;
if (util.isStrEq(val, screen.name.get())) {
return;
}
let prtn = GlobalCommandRunner.screenSetSettings(screen.screenId, { name: val }, false);
util.commandRtnHandler(prtn, this.props.errorMessage);
}
render() {
let { screen } = this.props;
return (
<TextField label="Name" required={true} defaultValue={screen.name.get() ?? ""} onChange={this.updateName} />
);
}
}
@mobxReact.observer
class TabColorSelector extends React.Component<{ screen: Screen; errorMessage?: OV<string> }, {}> {
@boundMethod
selectTabColor(color: string): void {
let { screen } = this.props;
if (screen.getTabColor() == color) {
return;
}
let prtn = GlobalCommandRunner.screenSetSettings(screen.screenId, { tabcolor: color }, false);
util.commandRtnHandler(prtn, this.props.errorMessage);
}
render() {
let { screen } = this.props;
let curColor = screen.getTabColor();
if (util.isBlank(curColor) || curColor == "default") {
curColor = "green";
}
let color: string | null = null;
return (
<div className="tab-colors">
<div className="tab-color-cur">
<TabIcon icon={screen.getTabIcon()} color={screen.getTabColor()} />
<div className="tab-color-name">{screen.getTabColor()}</div>
</div>
<div className="tab-color-sep">|</div>
<For each="color" of={appconst.TabColors}>
<div key={color} className="tab-color-select" onClick={() => this.selectTabColor(color)}>
<TabIcon icon="square" color={color} />
</div>
</For>
</div>
);
}
}
@mobxReact.observer
class TabIconSelector extends React.Component<{ screen: Screen; errorMessage?: OV<string> }, {}> {
@boundMethod
selectTabIcon(icon: string): void {
let { screen } = this.props;
if (screen.getTabIcon() == icon) {
return;
}
let prtn = GlobalCommandRunner.screenSetSettings(screen.screenId, { tabicon: icon }, false);
util.commandRtnHandler(prtn, this.props.errorMessage);
}
render() {
let { screen } = this.props;
let curIcon = screen.getTabIcon();
if (util.isBlank(curIcon) || curIcon == "default") {
curIcon = "square";
}
let icon: string | null = null;
let curColor = screen.getTabColor();
return (
<div className="tab-icons">
<div className="tab-icon-cur">
<TabIcon icon={screen.getTabIcon()} color={curColor} />
<div className="tab-icon-name">{screen.getTabIcon()}</div>
</div>
<div className="tab-icon-sep">|</div>
<For each="icon" index="index" of={appconst.TabIcons}>
<div key={icon} className="tab-icon-select" onClick={() => this.selectTabIcon(icon)}>
<TabIcon icon={icon} color={curColor} />
</div>
</For>
</div>
);
}
}
@mobxReact.observer
class TabRemoteSelector extends React.Component<{ screen: Screen; errorMessage?: OV<string> }, {}> {
selectedRemoteCN: OV<string> = mobx.observable.box(null, { name: "TabRemoteSelector-selectedRemoteCN" });
@boundMethod
selectRemote(cname: string): void {
mobx.action(() => {
this.selectedRemoteCN.set(cname);
})();
let prtn = GlobalCommandRunner.screenSetRemote(cname, true, true);
util.commandRtnHandler(prtn, this.props.errorMessage);
prtn.then((crtn) => {
GlobalModel.inputModel.giveFocus();
});
}
@boundMethod
getOptions(): DropdownItem[] {
const remotes = GlobalModel.remotes;
const options = remotes
.filter((r) => !r.archived)
.map((remote) => ({
...remote,
label: getRemoteStrWithAlias(remote),
value: remote.remotecanonicalname,
}))
.sort((a, b) => {
let connValA = util.getRemoteConnVal(a);
let connValB = util.getRemoteConnVal(b);
if (connValA !== connValB) {
return connValA - connValB;
}
return a.remoteidx - b.remoteidx;
});
return options;
}
render() {
const { screen } = this.props;
let selectedRemote = this.selectedRemoteCN.get();
if (selectedRemote == null) {
const curRI = screen.getCurRemoteInstance();
if (curRI != null) {
const curRemote = GlobalModel.getRemote(curRI.remoteid);
selectedRemote = curRemote.remotecanonicalname;
} else {
const localRemote = GlobalModel.getLocalRemote();
selectedRemote = localRemote.remotecanonicalname;
}
}
let curRemote = GlobalModel.getRemoteByName(selectedRemote);
return (
<Dropdown
className="conn-dropdown"
options={this.getOptions()}
defaultValue={curRemote.remotecanonicalname}
onChange={this.selectRemote}
decoration={{
startDecoration: (
<div className="lefticon">
<GlobeIcon className="globe-icon" />
<StatusCircleIcon className={cn("status-icon", "status-" + curRemote.status)} />
</div>
),
}}
/>
);
}
}
export { NewTabSettings, TabColorSelector, TabIconSelector, TabNameTextField, TabRemoteSelector };
-31
View File
@@ -171,37 +171,6 @@
gap: 8px;
align-self: stretch;
.conn-dropdown {
width: 412px;
.lefticon {
position: absolute;
top: 50%;
left: 16px;
transform: translateY(-50%);
.globe-icon {
width: 16px;
height: 16px;
flex-shrink: 0;
fill: var(--app-text-secondary-color);
}
.status-icon {
position: absolute;
left: 7px;
top: 8px;
circle {
stroke: var(--app-bg-color);
}
}
}
.wave-dropdown-display {
bottom: 7px;
}
}
&.conn-section {
gap: 8px;
}
+71 -208
View File
@@ -12,18 +12,13 @@ import { debounce } from "throttle-debounce";
import dayjs from "dayjs";
import { GlobalCommandRunner, ForwardLineContainer, GlobalModel, ScreenLines, Screen, Session } from "@/models";
import localizedFormat from "dayjs/plugin/localizedFormat";
import { Button, TextField, Dropdown } from "@/elements";
import { getRemoteStr } from "@/common/prompt/prompt";
import { Button } from "@/elements";
import { Line } from "@/app/line/linecomps";
import { LinesView } from "@/app/line/linesview";
import * as util from "@/util/util";
import { TabIcon } from "@/elements/tabicon";
import { ReactComponent as EllipseIcon } from "@/assets/icons/ellipse.svg";
import { ReactComponent as Check12Icon } from "@/assets/icons/check12.svg";
import { ReactComponent as GlobeIcon } from "@/assets/icons/globe.svg";
import { ReactComponent as StatusCircleIcon } from "@/assets/icons/statuscircle.svg";
import * as appconst from "@/app/appconst";
import * as textmeasure from "@/util/textmeasure";
import { NewTabSettings } from "./newtabsettings";
import "./screenview.less";
import "./tabs.less";
@@ -40,9 +35,16 @@ class ScreenView extends React.Component<{ session: Session; screen: Screen }, {
sidebarShowing: OV<boolean> = mobx.observable.box(false, { name: "screenview-sidebarShowing" });
sidebarShowingTimeoutId: any = null;
constructor(props: any) {
constructor(props: { session: Session; screen: Screen }) {
super(props);
this.handleResize_debounced = debounce(100, this.handleResize.bind(this));
let screen = this.props.screen;
let hasSidebar = false;
if (screen != null) {
let viewOpts = screen.viewOpts.get();
hasSidebar = viewOpts?.sidebar?.open;
}
this.sidebarShowing = mobx.observable.box(hasSidebar, { name: "screenview-sidebarShowing" });
}
componentDidMount(): void {
@@ -53,15 +55,13 @@ class ScreenView extends React.Component<{ session: Session; screen: Screen }, {
this.rszObs.observe(elem);
this.handleResize();
}
let viewOpts = screen.viewOpts.get();
let hasSidebar = viewOpts?.sidebar?.open;
if (hasSidebar) {
mobx.action(() => this.sidebarShowing.set(true))();
}
}
componentDidUpdate(): void {
let { screen } = this.props;
if (screen == null) {
return;
}
let viewOpts = screen.viewOpts.get();
let hasSidebar = viewOpts?.sidebar?.open;
if (hasSidebar && !this.sidebarShowing.get()) {
@@ -96,19 +96,62 @@ class ScreenView extends React.Component<{ session: Session; screen: Screen }, {
})();
}
@boundMethod
createWorkspace() {
GlobalCommandRunner.createNewSession();
}
@boundMethod
createTab() {
GlobalCommandRunner.createNewScreen();
}
render() {
let { session, screen } = this.props;
if (screen == null) {
return (
<div className="screen-view" ref={this.screenViewRef}>
(no screen found)
</div>
);
}
let screenWidth = this.width.get();
if (screenWidth == null) {
return <div className="screen-view" ref={this.screenViewRef}></div>;
}
if (session == null) {
let sessionCount = GlobalModel.sessionList.length;
return (
<div className="screen-view" ref={this.screenViewRef}>
<div className="window-view" style={{ width: "100%" }}>
<div key="lines" className="lines"></div>
<div key="window-empty" className={cn("window-empty")}>
<div className="flex-centered-column">
<code className="text-standard">[no workspace]</code>
<If condition={sessionCount == 0}>
<Button onClick={this.createWorkspace} style={{ marginTop: 10 }}>
Create New Workspace
</Button>
</If>
</div>
</div>
</div>
</div>
);
}
if (screen == null) {
let screens = GlobalModel.getSessionScreens(session.sessionId);
return (
<div className="screen-view" ref={this.screenViewRef}>
<div className="window-view" style={{ width: "100%" }}>
<div key="lines" className="lines"></div>
<div key="window-empty" className={cn("window-empty")}>
<div className="flex-centered-column">
<code className="text-standard">[no active tab]</code>
<If condition={screens.length == 0}>
<Button onClick={this.createTab} style={{ marginTop: 10 }}>
Create New Tab
</Button>
</If>
</div>
</div>
</div>
</div>
);
}
let fontSize = GlobalModel.getTermFontSize();
let dprStr = sprintf("%0.3f", GlobalModel.devicePixelRatio.get());
let viewOpts = screen.viewOpts.get();
@@ -339,193 +382,6 @@ class ScreenSidebar extends React.Component<{ screen: Screen; width: string }, {
}
}
@mobxReact.observer
class NewTabSettings extends React.Component<{ screen: Screen }, {}> {
connDropdownActive: OV<boolean> = mobx.observable.box(false, { name: "NewTabSettings-connDropdownActive" });
errorMessage: OV<string | null> = mobx.observable.box(null, { name: "NewTabSettings-errorMessage" });
remotes: RemoteType[];
constructor(props) {
super(props);
this.remotes = GlobalModel.remotes;
}
@boundMethod
selectTabColor(color: string): void {
let { screen } = this.props;
if (screen.getTabColor() == color) {
return;
}
let prtn = GlobalCommandRunner.screenSetSettings(screen.screenId, { tabcolor: color }, false);
util.commandRtnHandler(prtn, this.errorMessage);
}
@boundMethod
selectTabIcon(icon: string): void {
let { screen } = this.props;
if (screen.getTabIcon() == icon) {
return;
}
let prtn = GlobalCommandRunner.screenSetSettings(screen.screenId, { tabicon: icon }, false);
util.commandRtnHandler(prtn, this.errorMessage);
}
@boundMethod
updateName(val: string): void {
let { screen } = this.props;
let prtn = GlobalCommandRunner.screenSetSettings(screen.screenId, { name: val }, false);
util.commandRtnHandler(prtn, this.errorMessage);
}
@boundMethod
toggleConnDropdown(): void {
mobx.action(() => {
this.connDropdownActive.set(!this.connDropdownActive.get());
})();
}
@boundMethod
selectRemote(cname: string): void {
let prtn = GlobalCommandRunner.screenSetRemote(cname, true, false);
util.commandRtnHandler(prtn, this.errorMessage);
}
@boundMethod
clickNewConnection(): void {
GlobalModel.remotesModel.openAddModal({ remoteedit: true });
}
@boundMethod
getOptions(): { label: string; value: string }[] {
return this.remotes
.filter((r) => !r.archived)
.map((remote) => ({
...remote,
label: !util.isBlank(remote.remotealias)
? `${remote.remotealias} - ${remote.remotecanonicalname}`
: remote.remotecanonicalname,
value: remote.remotecanonicalname,
}))
.sort((a, b) => {
let connValA = util.getRemoteConnVal(a);
let connValB = util.getRemoteConnVal(b);
if (connValA !== connValB) {
return connValA - connValB;
}
return a.remoteidx - b.remoteidx;
});
}
renderTabIconSelector(): React.ReactNode {
let { screen } = this.props;
let curIcon = screen.getTabIcon();
if (util.isBlank(curIcon) || curIcon == "default") {
curIcon = "square";
}
let icon: string | null = null;
let curColor = screen.getTabColor();
return (
<>
<div className="bold unselectable">Tab Icon:</div>
<div className="control-iconlist tabicon-list">
<For each="icon" of={appconst.TabIcons}>
<div
className="icondiv tabicon"
key={icon}
title={icon || ""}
onClick={() => this.selectTabIcon(icon || "")}
>
<TabIcon icon={icon} color={curColor} />
</div>
</For>
</div>
</>
);
}
renderTabColorSelector(): React.ReactNode {
let { screen } = this.props;
let curColor = screen.getTabColor();
if (util.isBlank(curColor) || curColor == "default") {
curColor = "green";
}
let color: string | null = null;
return (
<>
<div className="bold unselectable">Tab Color:</div>
<div className="control-iconlist">
<For each="color" of={appconst.TabColors}>
<div
className="icondiv"
key={color}
title={color || ""}
onClick={() => this.selectTabColor(color || "")}
>
<EllipseIcon className={cn("icon", "color-" + color)} />
<If condition={color == curColor}>
<Check12Icon className="check-icon" />
</If>
</div>
</For>
</div>
</>
);
}
render() {
let { screen } = this.props;
let rptr = screen.curRemote.get();
let curRemote = GlobalModel.getRemote(GlobalModel.getActiveScreen().getCurRemoteInstance().remoteid);
return (
<div className="newtab-container">
<div className="newtab-section name-section">
<TextField
label="Name"
required={true}
defaultValue={screen.name.get() ?? ""}
onChange={this.updateName}
/>
</div>
<div className="newtab-spacer" />
<div className="newtab-section conn-section">
<div className="unselectable">
You're connected to [{getRemoteStr(rptr)}]. Do you want to change it?
</div>
<div>
<Dropdown
className="conn-dropdown"
options={this.getOptions()}
defaultValue={curRemote.remotecanonicalname}
onChange={this.selectRemote}
decoration={{
startDecoration: (
<div className="lefticon">
<GlobeIcon className="globe-icon" />
<StatusCircleIcon className={cn("status-icon", "status-" + curRemote.status)} />
</div>
),
}}
/>
</div>
<div className="text-caption cr-help-text">
To change connection from the command line use `cr [alias|user@host]`
</div>
</div>
<div className="newtab-spacer" />
<div className="newtab-section">
<div>{this.renderTabIconSelector()}</div>
</div>
<div className="newtab-spacer" />
<div className="newtab-section">
<div>{this.renderTabColorSelector()}</div>
</div>
</div>
);
}
}
// screen is not null
@mobxReact.observer
class ScreenWindowView extends React.Component<{ session: Session; screen: Screen; width: string }, {}> {
@@ -561,6 +417,7 @@ class ScreenWindowView extends React.Component<{ session: Session; screen: Scree
}
componentDidMount() {
const { screen } = this.props;
let wvElem = this.windowViewRef.current;
if (wvElem != null) {
let width = wvElem.offsetWidth;
@@ -569,6 +426,12 @@ class ScreenWindowView extends React.Component<{ session: Session; screen: Scree
this.rszObs = new ResizeObserver(this.handleResize.bind(this));
this.rszObs.observe(wvElem);
}
if (screen.isNew) {
screen.isNew = false;
mobx.action(() => {
GlobalModel.tabSettingsOpen.set(true);
})();
}
}
componentWillUnmount() {
@@ -695,7 +558,7 @@ class ScreenWindowView extends React.Component<{ session: Session; screen: Scree
return (
<div className="window-view" ref={this.windowViewRef} style={{ width: this.props.width }}>
<If condition={lines.length == 0}>
<If condition={screen.nextLineNum.get() == 1}>
<If condition={false && screen.nextLineNum.get() == 1}>
<NewTabSettings screen={screen} />
</If>
<If condition={screen.nextLineNum.get() != 1}>
+1 -2
View File
@@ -53,9 +53,8 @@ class ScreenTab extends React.Component<
e.preventDefault();
e.stopPropagation();
mobx.action(() => {
GlobalModel.screenSettingsModal.set({ sessionId: screen.sessionId, screenId: screen.screenId });
GlobalModel.tabSettingsOpen.set(!GlobalModel.tabSettingsOpen.get());
})();
GlobalModel.modalsModel.pushModal(constants.SCREEN_SETTINGS);
}
render() {
+22 -10
View File
@@ -112,6 +112,27 @@
opacity: 1;
font-weight: var(--screentabs-selected-font-weight);
border-top: 2px solid var(--tab-color);
.screen-tab-inner {
cursor: default;
}
}
&:not(.is-active) .status-indicator {
.status-indicator-visible;
}
&.is-active:not(:hover) .status-indicator {
.status-indicator-visible;
}
&.is-active:hover .actions {
.positional-icon-visible;
&:hover {
background-color: var(--app-selected-mask-color);
border-radius: 4px;
}
}
&.is-archived {
@@ -148,20 +169,11 @@
}
}
}
.vertical-line {
border-left: 1px solid var(--app-border-color);
margin: 10px 0 8px 0;
}
&:not(:hover) .status-indicator {
.status-indicator-visible;
}
&:hover {
.actions {
.positional-icon-visible;
}
}
}
}
+30
View File
@@ -23,4 +23,34 @@
width: 100%;
color: var(--app-text-secondary-color);
}
.tab-settings-pulldown {
position: absolute;
top: var(--screentabs-height);
width: 100%;
height: 330px;
transition: height 0.2s ease-in-out;
overflow: hidden;
z-index: 11;
border-bottom: 3px solid var(--app-border-color);
background-color: var(--app-panel-bg-color);
border-radius: 0 0 5px 5px;
&.closed {
height: 0;
border-bottom: none;
}
.close-icon {
position: absolute;
right: 10px;
top: 10px;
cursor: pointer;
padding: 5px;
border-radius: 4px;
&:hover {
background-color: var(--app-selected-mask-color);
}
}
}
}
+95 -19
View File
@@ -15,6 +15,10 @@ import { ScreenTabs } from "./screen/tabs";
import { ErrorBoundary } from "@/common/error/errorboundary";
import * as textmeasure from "@/util/textmeasure";
import "./workspace.less";
import { boundMethod } from "autobind-decorator";
import type { Screen } from "@/models";
import { getRemoteStr, getRemoteStrWithAlias } from "@/common/prompt/prompt";
import { TabColorSelector, TabIconSelector, TabNameTextField, TabRemoteSelector } from "./screen/newtabsettings";
dayjs.extend(localizedFormat);
@@ -79,43 +83,115 @@ class SessionKeybindings extends React.Component<{}, {}> {
}
@mobxReact.observer
class WorkspaceView extends React.Component<{}, {}> {
class TabSettingsPulldownKeybindings extends React.Component<{}, {}> {
componentDidMount() {
let keybindManager = GlobalModel.keybindManager;
keybindManager.registerKeybinding("pane", "tabsettings", "generic:cancel", (waveEvent) => {
GlobalModel.closeTabSettings();
return true;
});
}
componentWillUnmount() {
GlobalModel.keybindManager.unregisterDomain("tabsettings");
}
render() {
let model = GlobalModel;
let session = model.getActiveSession();
if (session == null) {
return (
<div className="session-view">
<div className="center-message">
<div>(no active workspace)</div>
return null;
}
}
@mobxReact.observer
class TabSettings extends React.Component<{ screen: Screen }, {}> {
errorMessage: OV<string> = mobx.observable.box(null, { name: "TabSettings-errorMessage" });
render() {
let { screen } = this.props;
let rptr = screen.curRemote.get();
return (
<div className="newtab-container">
<div className="newtab-section name-section">
<TabNameTextField screen={screen} errorMessage={this.errorMessage} />
</div>
<div className="newtab-spacer" />
<div className="newtab-section conn-section">
<div className="unselectable">
You're connected to "{getRemoteStrWithAlias(rptr)}". Do you want to change it?
</div>
<div>
<TabRemoteSelector screen={screen} errorMessage={this.errorMessage} />
</div>
<div className="text-caption cr-help-text">
To change connection from the command line use `cr [alias|user@host]`
</div>
</div>
);
<div className="newtab-spacer" />
<div className="newtab-section">
<TabIconSelector screen={screen} errorMessage={this.errorMessage} />
</div>
<div className="newtab-spacer" />
<div className="newtab-section">
<TabColorSelector screen={screen} errorMessage={this.errorMessage} />
</div>
</div>
);
}
}
@mobxReact.observer
class WorkspaceView extends React.Component<{}, {}> {
@boundMethod
toggleTabSettings() {
mobx.action(() => {
GlobalModel.tabSettingsOpen.set(!GlobalModel.tabSettingsOpen.get());
})();
}
render() {
const model = GlobalModel;
const session = model.getActiveSession();
let activeScreen: Screen = null;
let sessionId: string = "none";
if (session != null) {
sessionId = session.sessionId;
activeScreen = session.getActiveScreen();
}
let activeScreen = session.getActiveScreen();
let cmdInputHeight = model.inputModel.cmdInputHeight.get();
if (cmdInputHeight == 0) {
cmdInputHeight = textmeasure.baseCmdInputHeight(GlobalModel.lineHeightEnv); // this is the base size of cmdInput (measured using devtools)
}
let isHidden = GlobalModel.activeMainView.get() != "session";
let mainSidebarModel = GlobalModel.mainSidebarModel;
const isHidden = GlobalModel.activeMainView.get() != "session";
const mainSidebarModel = GlobalModel.mainSidebarModel;
const showTabSettings = GlobalModel.tabSettingsOpen.get();
return (
<div
className={cn("mainview", "session-view", { "is-hidden": isHidden })}
data-sessionid={session.sessionId}
data-sessionid={sessionId}
style={{
width: `${window.innerWidth - mainSidebarModel.getWidth()}px`,
}}
>
<If condition={!isHidden}>
<SessionKeybindings></SessionKeybindings>
<SessionKeybindings key="keybindings"></SessionKeybindings>
</If>
<ScreenTabs key={"tabs-" + session.sessionId} session={session} />
<ErrorBoundary>
<ScreenView key={"screenview-" + session.sessionId} session={session} screen={activeScreen} />
<ScreenTabs key={"tabs-" + sessionId} session={session} />
<If condition={activeScreen != null}>
<div key="pulldown" className={cn("tab-settings-pulldown", { closed: !showTabSettings })}>
<div className="close-icon" onClick={this.toggleTabSettings}>
<i className="fa-solid fa-sharp fa-xmark-large" />
</div>
<TabSettings key={activeScreen.screenId} screen={activeScreen} />
<If condition={showTabSettings}>
<TabSettingsPulldownKeybindings />
</If>
</div>
</If>
<ErrorBoundary key="eb">
<ScreenView key={"screenview-" + sessionId} session={session} screen={activeScreen} />
<div className="cmdinput-height-placeholder" style={{ height: cmdInputHeight }}></div>
<CmdInput key={"cmdinput-" + session.sessionId} />
<If condition={activeScreen != null}>
<CmdInput key={"cmdinput-" + sessionId} />
</If>
</ErrorBoundary>
</div>
);
+4
View File
@@ -94,6 +94,10 @@ class CommandRunner {
return GlobalModel.submitCommand("line", "set", [lineArg], kwargs, false);
}
ensureWorkspace() {
GlobalModel.submitCommand("session", "ensureone", null, { nohist: "1" }, true);
}
createNewSession() {
GlobalModel.submitCommand("session", "open", null, { nohist: "1" }, false);
}

Some files were not shown because too many files have changed in this diff Show More