mirror of
https://github.com/wavetermdev/backup.git
synced 2026-08-05 13:57:07 -07:00
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:
+77
-39
@@ -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;
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
justify-content: center;
|
||||
|
||||
button {
|
||||
font-size: 12.5px !important;
|
||||
font-size: 15px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
|
||||
@@ -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"}
|
||||
>
|
||||
telemetry data
|
||||
Github (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 (wavetermdev/waveterm)
|
||||
telemetry data
|
||||
</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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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")}> </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")}> </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 };
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<{}, {}> {
|
||||
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: 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();
|
||||
|
||||
@@ -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 };
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user