mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-21 16:38:23 +01:00
merge 'main' into use-ssh-library--add-user-input
I had to reimplement a few front end changes but nothing too major aside from that.
This commit is contained in:
commit
ea253c33ae
@ -9,7 +9,7 @@ import { If } from "tsx-control-statements/components";
|
||||
import dayjs from "dayjs";
|
||||
import type { ContextMenuOpts } from "../types/types";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import { GlobalModel } from "../model/model";
|
||||
import { GlobalModel } from "../models";
|
||||
import { isBlank } from "../util/util";
|
||||
import { WorkspaceView } from "./workspace/workspaceview";
|
||||
import { PluginsView } from "./pluginsview/pluginsview";
|
||||
@ -32,7 +32,7 @@ class App extends React.Component<{}, {}> {
|
||||
dcWait: OV<boolean> = mobx.observable.box(false, { name: "dcWait" });
|
||||
mainContentRef: React.RefObject<HTMLDivElement> = React.createRef();
|
||||
|
||||
constructor(props: any) {
|
||||
constructor(props: {}) {
|
||||
super(props);
|
||||
if (GlobalModel.isDev) document.body.className = "is-dev";
|
||||
}
|
||||
|
@ -17,3 +17,33 @@ export const LineContainer_Sidebar = "sidebar";
|
||||
export const ConfirmKey_HideShellPrompt = "hideshellprompt";
|
||||
|
||||
export const NoStrPos = -1;
|
||||
|
||||
export const RemotePtyRows = 8; // also in main.tsx
|
||||
export const RemotePtyCols = 80;
|
||||
export const ProdServerEndpoint = "http://127.0.0.1:1619";
|
||||
export const ProdServerWsEndpoint = "ws://127.0.0.1:1623";
|
||||
export const DevServerEndpoint = "http://127.0.0.1:8090";
|
||||
export const DevServerWsEndpoint = "ws://127.0.0.1:8091";
|
||||
export const DefaultTermFontSize = 12;
|
||||
export const MinFontSize = 8;
|
||||
export const MaxFontSize = 24;
|
||||
export const InputChunkSize = 500;
|
||||
export const RemoteColors = ["red", "green", "yellow", "blue", "magenta", "cyan", "white", "orange"];
|
||||
export const TabColors = ["red", "orange", "yellow", "green", "mint", "cyan", "blue", "violet", "pink", "white"];
|
||||
export const TabIcons = [
|
||||
"sparkle",
|
||||
"fire",
|
||||
"ghost",
|
||||
"cloud",
|
||||
"compass",
|
||||
"crown",
|
||||
"droplet",
|
||||
"graduation-cap",
|
||||
"heart",
|
||||
"file",
|
||||
];
|
||||
|
||||
// @ts-ignore
|
||||
export const VERSION = __WAVETERM_VERSION__;
|
||||
// @ts-ignore
|
||||
export const BUILD = __WAVETERM_BUILD__;
|
||||
|
@ -8,7 +8,7 @@ import { boundMethod } from "autobind-decorator";
|
||||
import { If, For } from "tsx-control-statements/components";
|
||||
import cn from "classnames";
|
||||
import type { BookmarkType } from "../../types/types";
|
||||
import { GlobalModel } from "../../model/model";
|
||||
import { GlobalModel } from "../../models";
|
||||
import { CmdStrCode, Markdown } from "../common/elements";
|
||||
|
||||
import { ReactComponent as XmarkIcon } from "../assets/icons/line/xmark.svg";
|
||||
@ -19,8 +19,16 @@ import { ReactComponent as FavoritesIcon } from "../assets/icons/favourites.svg"
|
||||
|
||||
import "./bookmarks.less";
|
||||
|
||||
type BookmarkProps = {
|
||||
bookmark: BookmarkType;
|
||||
};
|
||||
|
||||
@mobxReact.observer
|
||||
class Bookmark extends React.Component<{ bookmark: BookmarkType }, {}> {
|
||||
class Bookmark extends React.Component<BookmarkProps, {}> {
|
||||
constructor(props: BookmarkProps) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleDeleteClick(): void {
|
||||
let { bookmark } = this.props;
|
||||
@ -179,6 +187,10 @@ class Bookmark extends React.Component<{ bookmark: BookmarkType }, {}> {
|
||||
|
||||
@mobxReact.observer
|
||||
class BookmarksView extends React.Component<{}, {}> {
|
||||
constructor(props: {}) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
closeView(): void {
|
||||
GlobalModel.bookmarksModel.closeView();
|
||||
|
@ -6,24 +6,20 @@ import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import cn from "classnames";
|
||||
import { GlobalModel, GlobalCommandRunner, MinFontSize, MaxFontSize, RemotesModel } from "../../model/model";
|
||||
import { GlobalModel, GlobalCommandRunner, RemotesModel } from "../../models";
|
||||
import { Toggle, InlineSettingsTextEdit, SettingsError, Dropdown } from "../common/elements";
|
||||
import { CommandRtnType, ClientDataType } from "../../types/types";
|
||||
import * as types from "../../types/types";
|
||||
import { commandRtnHandler, isBlank } from "../../util/util";
|
||||
import * as appconst from "../appconst";
|
||||
|
||||
import "./clientsettings.less";
|
||||
|
||||
type OV<V> = mobx.IObservableValue<V>;
|
||||
|
||||
// @ts-ignore
|
||||
const VERSION = __WAVETERM_VERSION__;
|
||||
// @ts-ignore
|
||||
const BUILD = __WAVETERM_BUILD__;
|
||||
|
||||
@mobxReact.observer
|
||||
class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hoveredItemId: string }> {
|
||||
fontSizeDropdownActive: OV<boolean> = mobx.observable.box(false, { name: "clientSettings-fontSizeDropdownActive" });
|
||||
errorMessage: OV<string> = mobx.observable.box(null, { name: "ClientSettings-errorMessage" });
|
||||
fontSizeDropdownActive: types.OV<boolean> = mobx.observable.box(false, {
|
||||
name: "clientSettings-fontSizeDropdownActive",
|
||||
});
|
||||
errorMessage: types.OV<string> = mobx.observable.box(null, { name: "ClientSettings-errorMessage" });
|
||||
|
||||
@boundMethod
|
||||
dismissError(): void {
|
||||
@ -52,7 +48,7 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove
|
||||
|
||||
@boundMethod
|
||||
handleChangeTelemetry(val: boolean): void {
|
||||
let prtn: Promise<CommandRtnType> = null;
|
||||
let prtn: Promise<types.CommandRtnType> = null;
|
||||
if (val) {
|
||||
prtn = GlobalCommandRunner.telemetryOn(false);
|
||||
} else {
|
||||
@ -63,7 +59,7 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove
|
||||
|
||||
@boundMethod
|
||||
handleChangeReleaseCheck(val: boolean): void {
|
||||
let prtn: Promise<CommandRtnType> = null;
|
||||
let prtn: Promise<types.CommandRtnType> = null;
|
||||
if (val) {
|
||||
prtn = GlobalCommandRunner.releaseCheckAutoOn(false);
|
||||
} else {
|
||||
@ -74,7 +70,7 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove
|
||||
|
||||
getFontSizes(): any {
|
||||
let availableFontSizes: { label: string; value: number }[] = [];
|
||||
for (let s = MinFontSize; s <= MaxFontSize; s++) {
|
||||
for (let s = appconst.MinFontSize; s <= appconst.MaxFontSize; s++) {
|
||||
availableFontSizes.push({ label: s + "px", value: s });
|
||||
}
|
||||
return availableFontSizes;
|
||||
@ -116,7 +112,7 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove
|
||||
return null;
|
||||
}
|
||||
|
||||
let cdata: ClientDataType = GlobalModel.clientData.get();
|
||||
let cdata: types.ClientDataType = GlobalModel.clientData.get();
|
||||
let openAIOpts = cdata.openaiopts ?? {};
|
||||
let apiTokenStr = isBlank(openAIOpts.apitoken) ? "(not set)" : "********";
|
||||
let maxTokensStr = String(
|
||||
@ -151,7 +147,7 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove
|
||||
<div className="settings-field">
|
||||
<div className="settings-label">Client Version</div>
|
||||
<div className="settings-input">
|
||||
{VERSION} {BUILD}
|
||||
{appconst.VERSION} {appconst.BUILD}
|
||||
</div>
|
||||
</div>
|
||||
<div className="settings-field">
|
||||
|
@ -6,7 +6,7 @@ import * as mobxReact from "mobx-react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import cn from "classnames";
|
||||
import { GlobalModel } from "../../../model/model";
|
||||
import { GlobalModel } from "../../../models";
|
||||
|
||||
import "./markdown.less";
|
||||
|
||||
|
@ -6,7 +6,7 @@ import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import cn from "classnames";
|
||||
import { GlobalModel, GlobalCommandRunner } from "../../../model/model";
|
||||
import { GlobalModel, GlobalCommandRunner } from "../../../models";
|
||||
import { MagicLayout } from "../../magiclayout";
|
||||
|
||||
import "./resizablesidebar.less";
|
||||
|
@ -1,7 +1,7 @@
|
||||
// Copyright 2023, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { GlobalModel } from "../../../model/model";
|
||||
import { GlobalModel } from "../../../models";
|
||||
import * as appconst from "../../appconst";
|
||||
|
||||
function ShowWaveShellInstallPrompt(callbackFn: () => void) {
|
||||
|
@ -5,18 +5,14 @@ import * as React from "react";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { GlobalModel } from "../../../model/model";
|
||||
import { GlobalModel } from "../../../models";
|
||||
import { Modal, LinkButton } from "../elements";
|
||||
import * as util from "../../../util/util";
|
||||
import * as appconst from "../../appconst";
|
||||
|
||||
import logo from "../../assets/waveterm-logo-with-bg.svg";
|
||||
import "./about.less";
|
||||
|
||||
// @ts-ignore
|
||||
const VERSION = __WAVETERM_VERSION__;
|
||||
// @ts-ignore
|
||||
let BUILD = __WAVETERM_BUILD__;
|
||||
|
||||
@mobxReact.observer
|
||||
class AboutModal extends React.Component<{}, {}> {
|
||||
@boundMethod
|
||||
@ -42,7 +38,7 @@ class AboutModal extends React.Component<{}, {}> {
|
||||
return (
|
||||
<div className="status updated">
|
||||
<div className="text-selectable">
|
||||
Client Version {VERSION} ({BUILD})
|
||||
Client Version {appconst.VERSION} ({appconst.BUILD})
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -55,7 +51,7 @@ class AboutModal extends React.Component<{}, {}> {
|
||||
<span>Up to Date</span>
|
||||
</div>
|
||||
<div className="selectable">
|
||||
Client Version {VERSION} ({BUILD})
|
||||
Client Version {appconst.VERSION} ({appconst.BUILD})
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -67,7 +63,7 @@ class AboutModal extends React.Component<{}, {}> {
|
||||
<span>Outdated Version</span>
|
||||
</div>
|
||||
<div className="selectable">
|
||||
Client Version {VERSION} ({BUILD})
|
||||
Client Version {appconst.VERSION} ({appconst.BUILD})
|
||||
</div>
|
||||
<div>
|
||||
<button onClick={this.updateApp} className="button color-green text-secondary">
|
||||
|
@ -6,7 +6,7 @@ import * as mobxReact from "mobx-react";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { If } from "tsx-control-statements/components";
|
||||
import { Markdown, Modal, Button, Checkbox } from "../elements";
|
||||
import { GlobalModel, GlobalCommandRunner } from "../../../model/model";
|
||||
import { GlobalModel, GlobalCommandRunner } from "../../../models";
|
||||
|
||||
import "./alert.less";
|
||||
|
||||
|
@ -5,7 +5,7 @@ import * as React from "react";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { If } from "tsx-control-statements/components";
|
||||
import { GlobalModel } from "../../../model/model";
|
||||
import { GlobalModel } from "../../../models";
|
||||
import { Modal, Button } from "../elements";
|
||||
|
||||
import "./clientstop.less";
|
||||
|
@ -6,7 +6,7 @@ import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { If } from "tsx-control-statements/components";
|
||||
import { GlobalModel, GlobalCommandRunner, RemotesModel } from "../../../model/model";
|
||||
import { GlobalModel, GlobalCommandRunner, RemotesModel } from "../../../models";
|
||||
import * as T from "../../../types/types";
|
||||
import {
|
||||
Modal,
|
||||
|
@ -5,7 +5,7 @@ import * as React from "react";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { GlobalModel } from "../../../model/model";
|
||||
import { GlobalModel } from "../../../models";
|
||||
import { Modal, Button } from "../elements";
|
||||
|
||||
import "./disconnected.less";
|
||||
|
@ -6,7 +6,7 @@ import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import { If } from "tsx-control-statements/components";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { GlobalModel, GlobalCommandRunner, RemotesModel } from "../../../model/model";
|
||||
import { GlobalModel, GlobalCommandRunner, RemotesModel } from "../../../models";
|
||||
import * as T from "../../../types/types";
|
||||
import { Modal, TextField, InputDecoration, Dropdown, PasswordField, Tooltip } from "../elements";
|
||||
import * as util from "../../../util/util";
|
||||
|
@ -5,7 +5,7 @@ import * as React from "react";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { GlobalModel, GlobalCommandRunner } from "../../../model/model";
|
||||
import { GlobalModel, GlobalCommandRunner } from "../../../models";
|
||||
import { SettingsError, Modal, Dropdown } from "../elements";
|
||||
import { LineType, RendererPluginType } from "../../../types/types";
|
||||
import { PluginModel } from "../../../plugins/plugins";
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
import * as React from "react";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import { GlobalModel } from "../../../model/model";
|
||||
import { GlobalModel } from "../../../models";
|
||||
import { TosModal } from "./tos";
|
||||
|
||||
@mobxReact.observer
|
||||
|
@ -7,7 +7,7 @@ import * as mobx from "mobx";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { If, For } from "tsx-control-statements/components";
|
||||
import cn from "classnames";
|
||||
import { GlobalModel, GlobalCommandRunner, TabColors, TabIcons, Screen } from "../../../model/model";
|
||||
import { GlobalModel, GlobalCommandRunner, Screen } from "../../../models";
|
||||
import { Toggle, InlineSettingsTextEdit, SettingsError, Modal, Dropdown, Tooltip } from "../elements";
|
||||
import { RemoteType } from "../../../types/types";
|
||||
import * as util from "../../../util/util";
|
||||
@ -15,6 +15,7 @@ import { commandRtnHandler } from "../../../util/util";
|
||||
import { ReactComponent as SquareIcon } from "../../assets/icons/tab/square.svg";
|
||||
import { ReactComponent as GlobeIcon } from "../../assets/icons/globe.svg";
|
||||
import { ReactComponent as StatusCircleIcon } from "../../assets/icons/statuscircle.svg";
|
||||
import * as appconst from "../../appconst";
|
||||
|
||||
import "./screensettings.less";
|
||||
|
||||
@ -281,7 +282,7 @@ class ScreenSettingsModal extends React.Component<{}, {}> {
|
||||
<span className="tab-color-name">{screen.getTabColor()}</span>
|
||||
</div>
|
||||
<div className="tab-color-sep">|</div>
|
||||
<For each="color" of={TabColors}>
|
||||
<For each="color" of={appconst.TabColors}>
|
||||
<div
|
||||
key={color}
|
||||
className="tab-color-select"
|
||||
@ -307,7 +308,7 @@ class ScreenSettingsModal extends React.Component<{}, {}> {
|
||||
<span className="tab-icon-name">{screen.getTabIcon()}</span>
|
||||
</div>
|
||||
<div className="tab-icon-sep">|</div>
|
||||
<For each="icon" index="index" of={TabIcons}>
|
||||
<For each="icon" index="index" of={appconst.TabIcons}>
|
||||
<div
|
||||
key={`${color}-${index}`}
|
||||
className="tab-icon-select"
|
||||
|
@ -5,7 +5,7 @@ import * as React from "react";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { GlobalModel, GlobalCommandRunner, Session } from "../../../model/model";
|
||||
import { GlobalModel, GlobalCommandRunner, Session } from "../../../models";
|
||||
import { Toggle, InlineSettingsTextEdit, SettingsError, Modal, Tooltip } from "../elements";
|
||||
import * as util from "../../../util/util";
|
||||
import { commandRtnHandler } from "../../../util/util";
|
||||
|
@ -7,10 +7,10 @@ import * as mobx from "mobx";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { For } from "tsx-control-statements/components";
|
||||
import cn from "classnames";
|
||||
import { GlobalModel, GlobalCommandRunner } from "../../../model/model";
|
||||
import { GlobalModel, GlobalCommandRunner } from "../../../models";
|
||||
import { Modal, TextField, InputDecoration, Tooltip } from "../elements";
|
||||
import * as util from "../../../util/util";
|
||||
import { Screen } from "../../../model/model";
|
||||
import { Screen } from "../../../models";
|
||||
import { ReactComponent as SquareIcon } from "../../assets/icons/tab/square.svg";
|
||||
|
||||
import "./tabswitcher.less";
|
||||
|
@ -4,7 +4,7 @@
|
||||
import * as React from "react";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { GlobalModel, GlobalCommandRunner } from "../../../model/model";
|
||||
import { GlobalModel, GlobalCommandRunner } from "../../../models";
|
||||
import { Toggle, Modal, Button } from "../elements";
|
||||
import * as util from "../../../util/util";
|
||||
import { ClientDataType } from "../../../types/types";
|
||||
|
@ -1,5 +1,5 @@
|
||||
import * as React from "react";
|
||||
import { GlobalModel } from "../../../model/model";
|
||||
import { GlobalModel } from "../../../models";
|
||||
import { Choose, When, If } from "tsx-control-statements";
|
||||
import { Modal, PasswordField, Markdown } from "../elements";
|
||||
import { UserInputRequest } from "../../../types/types";
|
||||
|
@ -7,7 +7,7 @@ import * as mobx from "mobx";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { If, For } from "tsx-control-statements/components";
|
||||
import cn from "classnames";
|
||||
import { GlobalModel, GlobalCommandRunner, RemotesModel } from "../../../model/model";
|
||||
import { GlobalModel, GlobalCommandRunner, RemotesModel } from "../../../models";
|
||||
import * as T from "../../../types/types";
|
||||
import { Modal, Tooltip, Button, Status } from "../elements";
|
||||
import * as util from "../../../util/util";
|
||||
|
@ -6,8 +6,14 @@ import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import dayjs from "dayjs";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import { GlobalModel, LineContainerModel } from "../../../model/model";
|
||||
import type { LineType, RemoteType, RemotePtrType, LineHeightChangeCallbackType } from "../../../types/types";
|
||||
import { GlobalModel } from "../../../models";
|
||||
import type {
|
||||
LineType,
|
||||
RemoteType,
|
||||
RemotePtrType,
|
||||
LineHeightChangeCallbackType,
|
||||
LineContainerType,
|
||||
} from "../../../types/types";
|
||||
import cn from "classnames";
|
||||
import { isBlank } from "../../../util/util";
|
||||
import { ReactComponent as FolderIcon } from "../../assets/icons/folder.svg";
|
||||
@ -21,7 +27,7 @@ type OArr<V> = mobx.IObservableArray<V>;
|
||||
type OMap<K, V> = mobx.ObservableMap<K, V>;
|
||||
|
||||
type RendererComponentProps = {
|
||||
screen: LineContainerModel;
|
||||
screen: LineContainerType;
|
||||
line: LineType;
|
||||
width: number;
|
||||
staticRender: boolean;
|
||||
|
@ -7,7 +7,7 @@ import * as mobx from "mobx";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { If, For } from "tsx-control-statements/components";
|
||||
import cn from "classnames";
|
||||
import { GlobalModel, RemotesModel, GlobalCommandRunner } from "../../model/model";
|
||||
import { GlobalModel, RemotesModel, GlobalCommandRunner } from "../../models";
|
||||
import { Button, Status, ShowWaveShellInstallPrompt } from "../common/elements";
|
||||
import * as T from "../../types/types";
|
||||
import * as util from "../../util/util";
|
||||
|
@ -8,7 +8,7 @@ import { If, For } from "tsx-control-statements/components";
|
||||
import { sprintf } from "sprintf-js";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import cn from "classnames";
|
||||
import { GlobalModel, GlobalCommandRunner, Cmd } from "../../model/model";
|
||||
import { GlobalModel, GlobalCommandRunner, Cmd } from "../../models";
|
||||
import { HistoryItem, RemotePtrType, LineType, CmdDataType } from "../../types/types";
|
||||
import dayjs from "dayjs";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
|
@ -9,7 +9,7 @@ import { boundMethod } from "autobind-decorator";
|
||||
import dayjs from "dayjs";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import { If } from "tsx-control-statements/components";
|
||||
import { GlobalModel, GlobalCommandRunner, Cmd, getTermPtyData } from "../../model/model";
|
||||
import { GlobalModel, GlobalCommandRunner, Cmd } from "../../models";
|
||||
import { termHeightFromRows } from "../../util/textmeasure";
|
||||
import type {
|
||||
LineType,
|
||||
@ -19,10 +19,11 @@ import type {
|
||||
LineHeightChangeCallbackType,
|
||||
RendererModelInitializeParams,
|
||||
RendererModel,
|
||||
LineContainerType,
|
||||
} from "../../types/types";
|
||||
import cn from "classnames";
|
||||
import { getTermPtyData } from "../../util/modelutil";
|
||||
|
||||
import type { LineContainerModel } from "../../model/model";
|
||||
import { renderCmdText } from "../common/elements";
|
||||
import { SimpleBlobRenderer } from "../../plugins/core/basicrenderer";
|
||||
import { IncrementalRenderer } from "../../plugins/core/incrementalrenderer";
|
||||
@ -101,7 +102,7 @@ class SmallLineAvatar extends React.Component<{ line: LineType; cmd: Cmd; onRigh
|
||||
@mobxReact.observer
|
||||
class LineCmd extends React.Component<
|
||||
{
|
||||
screen: LineContainerModel;
|
||||
screen: LineContainerType;
|
||||
line: LineType;
|
||||
width: number;
|
||||
staticRender: boolean;
|
||||
@ -799,7 +800,7 @@ class LineCmd extends React.Component<
|
||||
@mobxReact.observer
|
||||
class Line extends React.Component<
|
||||
{
|
||||
screen: LineContainerModel;
|
||||
screen: LineContainerType;
|
||||
line: LineType;
|
||||
width: number;
|
||||
staticRender: boolean;
|
||||
@ -830,7 +831,7 @@ class Line extends React.Component<
|
||||
@mobxReact.observer
|
||||
class LineText extends React.Component<
|
||||
{
|
||||
screen: LineContainerModel;
|
||||
screen: LineContainerType;
|
||||
line: LineType;
|
||||
renderMode: RenderModeType;
|
||||
noSelect?: boolean;
|
||||
|
@ -22,10 +22,11 @@ import type {
|
||||
LineType,
|
||||
TermContextUnion,
|
||||
RendererContainerType,
|
||||
ExtBlob,
|
||||
} from "../../../types/types";
|
||||
import { debounce } from "throttle-debounce";
|
||||
import * as util from "../../../util/util";
|
||||
import { GlobalModel } from "../../../model/model";
|
||||
import { GlobalModel } from "../../../models";
|
||||
|
||||
type OV<V> = mobx.IObservableValue<V>;
|
||||
type CV<V> = mobx.IComputedValue<V>;
|
||||
@ -277,7 +278,7 @@ class SimpleBlobRenderer extends React.Component<
|
||||
cwd={festate.cwd}
|
||||
cmdstr={cmdstr}
|
||||
exitcode={exitcode}
|
||||
data={model.dataBlob}
|
||||
data={model.dataBlob as ExtBlob}
|
||||
readOnly={model.readOnly}
|
||||
notFound={model.notFound}
|
||||
lineState={model.lineState}
|
||||
|
@ -5,7 +5,7 @@ import * as React from "react";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { GlobalModel } from "../../model/model";
|
||||
import { GlobalModel } from "../../models";
|
||||
import { PluginModel } from "../../plugins/plugins";
|
||||
import { Markdown } from "../common/elements";
|
||||
|
||||
|
@ -17,10 +17,10 @@ import { ReactComponent as WorkspacesIcon } from "../assets/icons/workspaces.svg
|
||||
import { ReactComponent as SettingsIcon } from "../assets/icons/settings.svg";
|
||||
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import { GlobalModel, GlobalCommandRunner, Session, VERSION } from "../../model/model";
|
||||
import { GlobalModel, GlobalCommandRunner, Session } from "../../models";
|
||||
import { isBlank, openLink } from "../../util/util";
|
||||
import { ResizableSidebar } from "../common/elements";
|
||||
import * as constants from "../appconst";
|
||||
import * as appconst from "../appconst";
|
||||
|
||||
import "./sidebar.less";
|
||||
import { ActionsIcon, CenteredIcon, FrontIcon, StatusIndicator } from "../common/icons/icons";
|
||||
@ -167,7 +167,7 @@ class MainSideBar extends React.Component<MainSideBarProps, {}> {
|
||||
mobx.action(() => {
|
||||
GlobalModel.sessionSettingsModal.set(session.sessionId);
|
||||
})();
|
||||
GlobalModel.modalsModel.pushModal(constants.SESSION_SETTINGS);
|
||||
GlobalModel.modalsModel.pushModal(appconst.SESSION_SETTINGS);
|
||||
}
|
||||
|
||||
getSessions() {
|
||||
@ -208,7 +208,7 @@ class MainSideBar extends React.Component<MainSideBarProps, {}> {
|
||||
let clientData = this.props.clientData;
|
||||
let needsUpdate = false;
|
||||
if (!clientData?.clientopts.noreleasecheck && !isBlank(clientData?.releaseinfo?.latestversion)) {
|
||||
needsUpdate = compareLoose(VERSION, clientData.releaseinfo.latestversion) < 0;
|
||||
needsUpdate = compareLoose(appconst.VERSION, clientData.releaseinfo.latestversion) < 0;
|
||||
}
|
||||
let mainSidebar = GlobalModel.mainSidebarModel;
|
||||
let isCollapsed = mainSidebar.getCollapsed();
|
||||
|
@ -4,7 +4,7 @@
|
||||
import * as React from "react";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import { GlobalModel } from "../../../model/model";
|
||||
import { GlobalModel } from "../../../models";
|
||||
import { isBlank } from "../../../util/util";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import cn from "classnames";
|
||||
|
@ -10,7 +10,7 @@ import cn from "classnames";
|
||||
import dayjs from "dayjs";
|
||||
import type { RemoteType, RemoteInstanceType, RemotePtrType } from "../../../types/types";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import { GlobalModel, GlobalCommandRunner, Screen } from "../../../model/model";
|
||||
import { GlobalModel, GlobalCommandRunner, Screen } from "../../../models";
|
||||
import { renderCmdText } from "../../common/elements";
|
||||
import { TextAreaInput } from "./textareainput";
|
||||
import { InfoMsg } from "./infomsg";
|
||||
|
@ -11,7 +11,7 @@ import cn from "classnames";
|
||||
import dayjs from "dayjs";
|
||||
import type { HistoryItem, HistoryQueryOpts } from "../../../types/types";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import { GlobalModel } from "../../../model/model";
|
||||
import { GlobalModel } from "../../../models";
|
||||
import { isBlank } from "../../../util/util";
|
||||
|
||||
dayjs.extend(localizedFormat);
|
||||
|
@ -7,7 +7,7 @@ import { If, For } from "tsx-control-statements/components";
|
||||
import cn from "classnames";
|
||||
import dayjs from "dayjs";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import { GlobalModel } from "../../../model/model";
|
||||
import { GlobalModel } from "../../../models";
|
||||
import { makeExternLink } from "../../../util/util";
|
||||
|
||||
dayjs.extend(localizedFormat);
|
||||
|
@ -9,7 +9,7 @@ import * as util from "../../../util/util";
|
||||
import { If } from "tsx-control-statements/components";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import cn from "classnames";
|
||||
import { GlobalModel, GlobalCommandRunner, Screen } from "../../../model/model";
|
||||
import { GlobalModel, GlobalCommandRunner, Screen } from "../../../models";
|
||||
import { getMonoFontSize } from "../../../util/textmeasure";
|
||||
import { isModKeyPress, hasNoModifiers } from "../../../util/util";
|
||||
import * as appconst from "../../appconst";
|
||||
|
@ -10,16 +10,7 @@ import { If, For } from "tsx-control-statements/components";
|
||||
import cn from "classnames";
|
||||
import { debounce } from "throttle-debounce";
|
||||
import dayjs from "dayjs";
|
||||
import {
|
||||
GlobalCommandRunner,
|
||||
TabColors,
|
||||
TabIcons,
|
||||
ForwardLineContainer,
|
||||
GlobalModel,
|
||||
ScreenLines,
|
||||
Screen,
|
||||
Session,
|
||||
} from "../../../model/model";
|
||||
import { GlobalCommandRunner, ForwardLineContainer, GlobalModel, ScreenLines, Screen, Session } from "../../../models";
|
||||
import type { LineType, RenderModeType, LineFactoryProps } from "../../../types/types";
|
||||
import * as T from "../../../types/types";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
@ -442,7 +433,7 @@ class NewTabSettings extends React.Component<{ screen: Screen }, {}> {
|
||||
<div key="square" className="icondiv" title="square" onClick={() => this.selectTabIcon("square")}>
|
||||
<SquareIcon className="icon square-icon" />
|
||||
</div>
|
||||
<For each="icon" of={TabIcons}>
|
||||
<For each="icon" of={appconst.TabIcons}>
|
||||
<div
|
||||
className="icondiv tabicon"
|
||||
key={icon}
|
||||
@ -469,7 +460,7 @@ class NewTabSettings extends React.Component<{ screen: Screen }, {}> {
|
||||
<>
|
||||
<div className="text-s1 unselectable">Select the color</div>
|
||||
<div className="control-iconlist">
|
||||
<For each="color" of={TabColors}>
|
||||
<For each="color" of={appconst.TabColors}>
|
||||
<div
|
||||
className="icondiv"
|
||||
key={color}
|
||||
|
@ -6,7 +6,7 @@ import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import cn from "classnames";
|
||||
import { GlobalModel, GlobalCommandRunner, Screen } from "../../../model/model";
|
||||
import { GlobalModel, GlobalCommandRunner, Screen } from "../../../models";
|
||||
import { ActionsIcon, StatusIndicator, CenteredIcon } from "../../common/icons/icons";
|
||||
import { renderCmdText } from "../../common/elements";
|
||||
import { ReactComponent as SquareIcon } from "../../assets/icons/tab/square.svg";
|
||||
|
@ -7,7 +7,7 @@ import * as mobx from "mobx";
|
||||
import { sprintf } from "sprintf-js";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { For } from "tsx-control-statements/components";
|
||||
import { GlobalModel, GlobalCommandRunner, Session, Screen } from "../../../model/model";
|
||||
import { GlobalModel, GlobalCommandRunner, Session, Screen } from "../../../models";
|
||||
import { ReactComponent as AddIcon } from "../../assets/icons/add.svg";
|
||||
import { Reorder } from "framer-motion";
|
||||
import { ScreenTab } from "./tab";
|
||||
|
@ -7,7 +7,7 @@ import * as mobx from "mobx";
|
||||
import cn from "classnames";
|
||||
import dayjs from "dayjs";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import { GlobalModel } from "../../model/model";
|
||||
import { GlobalModel } from "../../models";
|
||||
import { CmdInput } from "./cmdinput/cmdinput";
|
||||
import { ScreenView } from "./screen/screenview";
|
||||
import { ScreenTabs } from "./screen/tabs";
|
||||
|
287
src/models/bookmarks.ts
Normal file
287
src/models/bookmarks.ts
Normal file
@ -0,0 +1,287 @@
|
||||
// Copyright 2023, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import * as mobx from "mobx";
|
||||
import { sprintf } from "sprintf-js";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { genMergeSimpleData } from "../util/util";
|
||||
import { BookmarkType } from "../types/types";
|
||||
import { checkKeyPressed, adaptFromReactOrNativeKeyEvent } from "../util/keyutil";
|
||||
import { OV, OArr } from "../types/types";
|
||||
import { GlobalCommandRunner } from "./global";
|
||||
import { Model } from "./model";
|
||||
|
||||
class BookmarksModel {
|
||||
globalModel: Model;
|
||||
bookmarks: OArr<BookmarkType> = mobx.observable.array([], {
|
||||
name: "Bookmarks",
|
||||
});
|
||||
activeBookmark: OV<string> = mobx.observable.box(null, {
|
||||
name: "activeBookmark",
|
||||
});
|
||||
editingBookmark: OV<string> = mobx.observable.box(null, {
|
||||
name: "editingBookmark",
|
||||
});
|
||||
pendingDelete: OV<string> = mobx.observable.box(null, {
|
||||
name: "pendingDelete",
|
||||
});
|
||||
copiedIndicator: OV<string> = mobx.observable.box(null, {
|
||||
name: "copiedIndicator",
|
||||
});
|
||||
|
||||
tempDesc: OV<string> = mobx.observable.box("", {
|
||||
name: "bookmarkEdit-tempDesc",
|
||||
});
|
||||
tempCmd: OV<string> = mobx.observable.box("", {
|
||||
name: "bookmarkEdit-tempCmd",
|
||||
});
|
||||
|
||||
constructor(globalModel: Model) {
|
||||
this.globalModel = globalModel;
|
||||
}
|
||||
|
||||
showBookmarksView(bmArr: BookmarkType[], selectedBookmarkId: string): void {
|
||||
bmArr = bmArr ?? [];
|
||||
mobx.action(() => {
|
||||
this.reset();
|
||||
this.globalModel.activeMainView.set("bookmarks");
|
||||
this.bookmarks.replace(bmArr);
|
||||
if (selectedBookmarkId != null) {
|
||||
this.selectBookmark(selectedBookmarkId);
|
||||
}
|
||||
if (this.activeBookmark.get() == null && bmArr.length > 0) {
|
||||
this.activeBookmark.set(bmArr[0].bookmarkid);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
mobx.action(() => {
|
||||
this.activeBookmark.set(null);
|
||||
this.editingBookmark.set(null);
|
||||
this.pendingDelete.set(null);
|
||||
this.tempDesc.set("");
|
||||
this.tempCmd.set("");
|
||||
})();
|
||||
}
|
||||
|
||||
closeView(): void {
|
||||
this.globalModel.showSessionView();
|
||||
setTimeout(() => this.globalModel.inputModel.giveFocus(), 50);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
clearPendingDelete(): void {
|
||||
mobx.action(() => this.pendingDelete.set(null))();
|
||||
}
|
||||
|
||||
useBookmark(bookmarkId: string): void {
|
||||
let bm = this.getBookmark(bookmarkId);
|
||||
if (bm == null) {
|
||||
return;
|
||||
}
|
||||
mobx.action(() => {
|
||||
this.reset();
|
||||
this.globalModel.showSessionView();
|
||||
this.globalModel.inputModel.setCurLine(bm.cmdstr);
|
||||
setTimeout(() => this.globalModel.inputModel.giveFocus(), 50);
|
||||
})();
|
||||
}
|
||||
|
||||
selectBookmark(bookmarkId: string): void {
|
||||
let bm = this.getBookmark(bookmarkId);
|
||||
if (bm == null) {
|
||||
return;
|
||||
}
|
||||
if (this.activeBookmark.get() == bookmarkId) {
|
||||
return;
|
||||
}
|
||||
mobx.action(() => {
|
||||
this.cancelEdit();
|
||||
this.activeBookmark.set(bookmarkId);
|
||||
})();
|
||||
}
|
||||
|
||||
cancelEdit(): void {
|
||||
mobx.action(() => {
|
||||
this.pendingDelete.set(null);
|
||||
this.editingBookmark.set(null);
|
||||
this.tempDesc.set("");
|
||||
this.tempCmd.set("");
|
||||
})();
|
||||
}
|
||||
|
||||
confirmEdit(): void {
|
||||
if (this.editingBookmark.get() == null) {
|
||||
return;
|
||||
}
|
||||
let bm = this.getBookmark(this.editingBookmark.get());
|
||||
mobx.action(() => {
|
||||
this.editingBookmark.set(null);
|
||||
bm.description = this.tempDesc.get();
|
||||
bm.cmdstr = this.tempCmd.get();
|
||||
this.tempDesc.set("");
|
||||
this.tempCmd.set("");
|
||||
})();
|
||||
GlobalCommandRunner.editBookmark(bm.bookmarkid, bm.description, bm.cmdstr);
|
||||
}
|
||||
|
||||
handleDeleteBookmark(bookmarkId: string): void {
|
||||
if (this.pendingDelete.get() == null || this.pendingDelete.get() != this.activeBookmark.get()) {
|
||||
mobx.action(() => this.pendingDelete.set(this.activeBookmark.get()))();
|
||||
setTimeout(this.clearPendingDelete, 2000);
|
||||
return;
|
||||
}
|
||||
GlobalCommandRunner.deleteBookmark(bookmarkId);
|
||||
this.clearPendingDelete();
|
||||
}
|
||||
|
||||
getBookmark(bookmarkId: string): BookmarkType {
|
||||
if (bookmarkId == null) {
|
||||
return null;
|
||||
}
|
||||
for (const bm of this.bookmarks) {
|
||||
if (bm.bookmarkid == bookmarkId) {
|
||||
return bm;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getBookmarkPos(bookmarkId: string): number {
|
||||
if (bookmarkId == null) {
|
||||
return -1;
|
||||
}
|
||||
for (let i = 0; i < this.bookmarks.length; i++) {
|
||||
let bm = this.bookmarks[i];
|
||||
if (bm.bookmarkid == bookmarkId) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
getActiveBookmark(): BookmarkType {
|
||||
let activeBookmarkId = this.activeBookmark.get();
|
||||
return this.getBookmark(activeBookmarkId);
|
||||
}
|
||||
|
||||
handleEditBookmark(bookmarkId: string): void {
|
||||
let bm = this.getBookmark(bookmarkId);
|
||||
if (bm == null) {
|
||||
return;
|
||||
}
|
||||
mobx.action(() => {
|
||||
this.pendingDelete.set(null);
|
||||
this.activeBookmark.set(bookmarkId);
|
||||
this.editingBookmark.set(bookmarkId);
|
||||
this.tempDesc.set(bm.description ?? "");
|
||||
this.tempCmd.set(bm.cmdstr ?? "");
|
||||
})();
|
||||
}
|
||||
|
||||
handleCopyBookmark(bookmarkId: string): void {
|
||||
let bm = this.getBookmark(bookmarkId);
|
||||
if (bm == null) {
|
||||
return;
|
||||
}
|
||||
navigator.clipboard.writeText(bm.cmdstr);
|
||||
mobx.action(() => {
|
||||
this.copiedIndicator.set(bm.bookmarkid);
|
||||
})();
|
||||
setTimeout(() => {
|
||||
mobx.action(() => {
|
||||
this.copiedIndicator.set(null);
|
||||
})();
|
||||
}, 600);
|
||||
}
|
||||
|
||||
mergeBookmarks(bmArr: BookmarkType[]): void {
|
||||
mobx.action(() => {
|
||||
genMergeSimpleData(
|
||||
this.bookmarks,
|
||||
bmArr,
|
||||
(bm: BookmarkType) => bm.bookmarkid,
|
||||
(bm: BookmarkType) => sprintf("%05d", bm.orderidx)
|
||||
);
|
||||
})();
|
||||
}
|
||||
|
||||
handleDocKeyDown(e: any): void {
|
||||
let waveEvent = adaptFromReactOrNativeKeyEvent(e);
|
||||
if (checkKeyPressed(waveEvent, "Escape")) {
|
||||
e.preventDefault();
|
||||
if (this.editingBookmark.get() != null) {
|
||||
this.cancelEdit();
|
||||
return;
|
||||
}
|
||||
this.closeView();
|
||||
return;
|
||||
}
|
||||
if (this.editingBookmark.get() != null) {
|
||||
return;
|
||||
}
|
||||
if (checkKeyPressed(waveEvent, "Backspace") || checkKeyPressed(waveEvent, "Delete")) {
|
||||
if (this.activeBookmark.get() == null) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
this.handleDeleteBookmark(this.activeBookmark.get());
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
checkKeyPressed(waveEvent, "ArrowUp") ||
|
||||
checkKeyPressed(waveEvent, "ArrowDown") ||
|
||||
checkKeyPressed(waveEvent, "PageUp") ||
|
||||
checkKeyPressed(waveEvent, "PageDown")
|
||||
) {
|
||||
e.preventDefault();
|
||||
if (this.bookmarks.length == 0) {
|
||||
return;
|
||||
}
|
||||
let newPos = 0; // if active is null, then newPos will be 0 (select the first)
|
||||
if (this.activeBookmark.get() != null) {
|
||||
let amtMap = { ArrowUp: -1, ArrowDown: 1, PageUp: -10, PageDown: 10 };
|
||||
let amt = amtMap[e.code];
|
||||
let curIdx = this.getBookmarkPos(this.activeBookmark.get());
|
||||
newPos = curIdx + amt;
|
||||
if (newPos < 0) {
|
||||
newPos = 0;
|
||||
}
|
||||
if (newPos >= this.bookmarks.length) {
|
||||
newPos = this.bookmarks.length - 1;
|
||||
}
|
||||
}
|
||||
let bm = this.bookmarks[newPos];
|
||||
mobx.action(() => {
|
||||
this.activeBookmark.set(bm.bookmarkid);
|
||||
})();
|
||||
return;
|
||||
}
|
||||
if (checkKeyPressed(waveEvent, "Enter")) {
|
||||
if (this.activeBookmark.get() == null) {
|
||||
return;
|
||||
}
|
||||
this.useBookmark(this.activeBookmark.get());
|
||||
return;
|
||||
}
|
||||
if (checkKeyPressed(waveEvent, "e")) {
|
||||
if (this.activeBookmark.get() == null) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
this.handleEditBookmark(this.activeBookmark.get());
|
||||
return;
|
||||
}
|
||||
if (checkKeyPressed(waveEvent, "c")) {
|
||||
if (this.activeBookmark.get() == null) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
this.handleCopyBookmark(this.activeBookmark.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { BookmarksModel };
|
26
src/models/clientsettingsview.ts
Normal file
26
src/models/clientsettingsview.ts
Normal file
@ -0,0 +1,26 @@
|
||||
// Copyright 2023, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import * as mobx from "mobx";
|
||||
import { Model } from "./model";
|
||||
|
||||
class ClientSettingsViewModel {
|
||||
globalModel: Model;
|
||||
|
||||
constructor(globalModel: Model) {
|
||||
this.globalModel = globalModel;
|
||||
}
|
||||
|
||||
closeView(): void {
|
||||
this.globalModel.showSessionView();
|
||||
setTimeout(() => this.globalModel.inputModel.giveFocus(), 50);
|
||||
}
|
||||
|
||||
showClientSettingsView(): void {
|
||||
mobx.action(() => {
|
||||
this.globalModel.activeMainView.set("clientsettings");
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
export { ClientSettingsViewModel };
|
145
src/models/cmd.ts
Normal file
145
src/models/cmd.ts
Normal file
@ -0,0 +1,145 @@
|
||||
// Copyright 2023, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import * as mobx from "mobx";
|
||||
import { stringToBase64 } from "../util/util";
|
||||
import { TermWrap } from "../plugins/terminal/term";
|
||||
import {
|
||||
RemotePtrType,
|
||||
CmdDataType,
|
||||
TermOptsType,
|
||||
FeInputPacketType,
|
||||
RendererModel,
|
||||
WebCmd,
|
||||
WebRemote,
|
||||
} from "../types/types";
|
||||
import { cmdStatusIsRunning } from "../app/line/lineutil";
|
||||
import { Model } from "./model";
|
||||
import { OV } from "../types/types";
|
||||
|
||||
const InputChunkSize = 500;
|
||||
class Cmd {
|
||||
model: Model;
|
||||
screenId: string;
|
||||
remote: RemotePtrType;
|
||||
lineId: string;
|
||||
data: OV<CmdDataType>;
|
||||
|
||||
constructor(cmd: CmdDataType) {
|
||||
this.model = Model.getInstance();
|
||||
this.screenId = cmd.screenid;
|
||||
this.lineId = cmd.lineid;
|
||||
this.remote = cmd.remote;
|
||||
this.data = mobx.observable.box(cmd, { deep: false, name: "cmd-data" });
|
||||
}
|
||||
|
||||
setCmd(cmd: CmdDataType) {
|
||||
mobx.action(() => {
|
||||
let origData = this.data.get();
|
||||
this.data.set(cmd);
|
||||
if (origData != null && cmd != null && origData.status != cmd.status) {
|
||||
this.model.cmdStatusUpdate(this.screenId, this.lineId, origData.status, cmd.status);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
getRestartTs(): number {
|
||||
return this.data.get().restartts;
|
||||
}
|
||||
|
||||
getAsWebCmd(lineid: string): WebCmd {
|
||||
let cmd = this.data.get();
|
||||
let remote = this.model.getRemote(this.remote.remoteid);
|
||||
let webRemote: WebRemote = null;
|
||||
if (remote != null) {
|
||||
webRemote = {
|
||||
remoteid: cmd.remote.remoteid,
|
||||
alias: remote.remotealias,
|
||||
canonicalname: remote.remotecanonicalname,
|
||||
name: this.remote.name,
|
||||
homedir: remote.remotevars["home"],
|
||||
isroot: !!remote.remotevars["isroot"],
|
||||
};
|
||||
}
|
||||
let webCmd: WebCmd = {
|
||||
screenid: cmd.screenid,
|
||||
lineid: lineid,
|
||||
remote: webRemote,
|
||||
status: cmd.status,
|
||||
cmdstr: cmd.cmdstr,
|
||||
rawcmdstr: cmd.rawcmdstr,
|
||||
festate: cmd.festate,
|
||||
termopts: cmd.termopts,
|
||||
cmdpid: cmd.cmdpid,
|
||||
remotepid: cmd.remotepid,
|
||||
donets: cmd.donets,
|
||||
exitcode: cmd.exitcode,
|
||||
durationms: cmd.durationms,
|
||||
rtnstate: cmd.rtnstate,
|
||||
vts: 0,
|
||||
rtnstatestr: null,
|
||||
};
|
||||
return webCmd;
|
||||
}
|
||||
|
||||
getExitCode(): number {
|
||||
return this.data.get().exitcode;
|
||||
}
|
||||
|
||||
getRtnState(): boolean {
|
||||
return this.data.get().rtnstate;
|
||||
}
|
||||
|
||||
getStatus(): string {
|
||||
return this.data.get().status;
|
||||
}
|
||||
|
||||
getTermOpts(): TermOptsType {
|
||||
return this.data.get().termopts;
|
||||
}
|
||||
|
||||
getCmdStr(): string {
|
||||
return this.data.get().cmdstr;
|
||||
}
|
||||
|
||||
getRemoteFeState(): Record<string, string> {
|
||||
return this.data.get().festate;
|
||||
}
|
||||
|
||||
isRunning(): boolean {
|
||||
let data = this.data.get();
|
||||
return cmdStatusIsRunning(data.status);
|
||||
}
|
||||
|
||||
handleData(data: string, termWrap: TermWrap): void {
|
||||
if (!this.isRunning()) {
|
||||
return;
|
||||
}
|
||||
for (let pos = 0; pos < data.length; pos += InputChunkSize) {
|
||||
let dataChunk = data.slice(pos, pos + InputChunkSize);
|
||||
this.handleInputChunk(dataChunk);
|
||||
}
|
||||
}
|
||||
|
||||
handleDataFromRenderer(data: string, renderer: RendererModel): void {
|
||||
if (!this.isRunning()) {
|
||||
return;
|
||||
}
|
||||
for (let pos = 0; pos < data.length; pos += InputChunkSize) {
|
||||
let dataChunk = data.slice(pos, pos + InputChunkSize);
|
||||
this.handleInputChunk(dataChunk);
|
||||
}
|
||||
}
|
||||
|
||||
handleInputChunk(data: string): void {
|
||||
let inputPacket: FeInputPacketType = {
|
||||
type: "feinput",
|
||||
ck: this.screenId + "/" + this.lineId,
|
||||
remote: this.remote,
|
||||
inputdata64: stringToBase64(data),
|
||||
};
|
||||
this.model.sendInputPacket(inputPacket);
|
||||
}
|
||||
}
|
||||
|
||||
export { Cmd };
|
431
src/models/commandrunner.ts
Normal file
431
src/models/commandrunner.ts
Normal file
@ -0,0 +1,431 @@
|
||||
// Copyright 2023, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import * as mobx from "mobx";
|
||||
import { RendererContext, CommandRtnType, HistorySearchParams, LineStateType } from "../types/types";
|
||||
import { GlobalModel } from "./global";
|
||||
|
||||
class CommandRunner {
|
||||
private constructor() {}
|
||||
|
||||
static getInstance() {
|
||||
if (!(window as any).GlobalCommandRunner) {
|
||||
(window as any).GlobalCommandRunner = new CommandRunner();
|
||||
}
|
||||
return (window as any).GlobalCommandRunner;
|
||||
}
|
||||
|
||||
loadHistory(show: boolean, htype: string) {
|
||||
let kwargs = { nohist: "1" };
|
||||
if (!show) {
|
||||
kwargs["noshow"] = "1";
|
||||
}
|
||||
if (htype != null && htype != "screen") {
|
||||
kwargs["type"] = htype;
|
||||
}
|
||||
GlobalModel.submitCommand("history", null, null, kwargs, true);
|
||||
}
|
||||
|
||||
resetShellState() {
|
||||
GlobalModel.submitCommand("reset", null, null, null, true);
|
||||
}
|
||||
|
||||
historyPurgeLines(lines: string[]): Promise<CommandRtnType> {
|
||||
let prtn = GlobalModel.submitCommand("history", "purge", lines, { nohist: "1" }, false);
|
||||
return prtn;
|
||||
}
|
||||
|
||||
switchSession(session: string) {
|
||||
mobx.action(() => {
|
||||
GlobalModel.activeMainView.set("session");
|
||||
})();
|
||||
GlobalModel.submitCommand("session", null, [session], { nohist: "1" }, false);
|
||||
}
|
||||
|
||||
switchScreen(screen: string, session?: string) {
|
||||
mobx.action(() => {
|
||||
GlobalModel.activeMainView.set("session");
|
||||
})();
|
||||
let kwargs = { nohist: "1" };
|
||||
if (session != null) {
|
||||
kwargs["session"] = session;
|
||||
}
|
||||
GlobalModel.submitCommand("screen", null, [screen], kwargs, false);
|
||||
}
|
||||
|
||||
lineView(sessionId: string, screenId: string, lineNum?: number) {
|
||||
let screen = GlobalModel.getScreenById(sessionId, screenId);
|
||||
if (screen != null && lineNum != null) {
|
||||
screen.setAnchorFields(lineNum, 0, "line:view");
|
||||
}
|
||||
let lineNumStr = lineNum == null || lineNum == 0 ? "E" : String(lineNum);
|
||||
GlobalModel.submitCommand("line", "view", [sessionId, screenId, lineNumStr], { nohist: "1" }, false);
|
||||
}
|
||||
|
||||
lineArchive(lineArg: string, archive: boolean): Promise<CommandRtnType> {
|
||||
let kwargs = { nohist: "1" };
|
||||
let archiveStr = archive ? "1" : "0";
|
||||
return GlobalModel.submitCommand("line", "archive", [lineArg, archiveStr], kwargs, false);
|
||||
}
|
||||
|
||||
lineDelete(lineArg: string, interactive: boolean): Promise<CommandRtnType> {
|
||||
return GlobalModel.submitCommand("line", "delete", [lineArg], { nohist: "1" }, interactive);
|
||||
}
|
||||
|
||||
lineRestart(lineArg: string, interactive: boolean): Promise<CommandRtnType> {
|
||||
return GlobalModel.submitCommand("line", "restart", [lineArg], { nohist: "1" }, interactive);
|
||||
}
|
||||
|
||||
lineSet(lineArg: string, opts: { renderer?: string }): Promise<CommandRtnType> {
|
||||
let kwargs = { nohist: "1" };
|
||||
if ("renderer" in opts) {
|
||||
kwargs["renderer"] = opts.renderer ?? "";
|
||||
}
|
||||
return GlobalModel.submitCommand("line", "set", [lineArg], kwargs, false);
|
||||
}
|
||||
|
||||
createNewSession() {
|
||||
GlobalModel.submitCommand("session", "open", null, { nohist: "1" }, false);
|
||||
}
|
||||
|
||||
createNewScreen() {
|
||||
GlobalModel.submitCommand("screen", "open", null, { nohist: "1" }, false);
|
||||
}
|
||||
|
||||
closeScreen(screen: string) {
|
||||
GlobalModel.submitCommand("screen", "close", [screen], { nohist: "1" }, false);
|
||||
}
|
||||
|
||||
// include is lineIds to include, exclude is lineIds to exclude
|
||||
// if include is given then it *only* does those ids. if exclude is given (or not),
|
||||
// it does all running commands in the screen except for excluded.
|
||||
resizeScreen(screenId: string, rows: number, cols: number, opts?: { include?: string[]; exclude?: string[] }) {
|
||||
let kwargs: Record<string, string> = {
|
||||
nohist: "1",
|
||||
screen: screenId,
|
||||
cols: String(cols),
|
||||
rows: String(rows),
|
||||
};
|
||||
if (opts?.include != null && opts?.include.length > 0) {
|
||||
kwargs.include = opts.include.join(",");
|
||||
}
|
||||
if (opts?.exclude != null && opts?.exclude.length > 0) {
|
||||
kwargs.exclude = opts.exclude.join(",");
|
||||
}
|
||||
GlobalModel.submitCommand("screen", "resize", null, kwargs, false);
|
||||
}
|
||||
|
||||
screenArchive(screenId: string, shouldArchive: boolean): Promise<CommandRtnType> {
|
||||
return GlobalModel.submitCommand(
|
||||
"screen",
|
||||
"archive",
|
||||
[screenId, shouldArchive ? "1" : "0"],
|
||||
{ nohist: "1" },
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
screenDelete(screenId: string, interactive: boolean): Promise<CommandRtnType> {
|
||||
return GlobalModel.submitCommand("screen", "delete", [screenId], { nohist: "1" }, interactive);
|
||||
}
|
||||
|
||||
screenWebShare(screenId: string, shouldShare: boolean): Promise<CommandRtnType> {
|
||||
let kwargs: Record<string, string> = { nohist: "1" };
|
||||
kwargs["screen"] = screenId;
|
||||
return GlobalModel.submitCommand("screen", "webshare", [shouldShare ? "1" : "0"], kwargs, false);
|
||||
}
|
||||
|
||||
showRemote(remoteid: string) {
|
||||
GlobalModel.submitCommand("remote", "show", null, { nohist: "1", remote: remoteid }, true);
|
||||
}
|
||||
|
||||
showAllRemotes() {
|
||||
GlobalModel.submitCommand("remote", "showall", null, { nohist: "1" }, true);
|
||||
}
|
||||
|
||||
connectRemote(remoteid: string) {
|
||||
GlobalModel.submitCommand("remote", "connect", null, { nohist: "1", remote: remoteid }, true);
|
||||
}
|
||||
|
||||
disconnectRemote(remoteid: string) {
|
||||
GlobalModel.submitCommand("remote", "disconnect", null, { nohist: "1", remote: remoteid }, true);
|
||||
}
|
||||
|
||||
installRemote(remoteid: string) {
|
||||
GlobalModel.submitCommand("remote", "install", null, { nohist: "1", remote: remoteid }, true);
|
||||
}
|
||||
|
||||
installCancelRemote(remoteid: string) {
|
||||
GlobalModel.submitCommand("remote", "installcancel", null, { nohist: "1", remote: remoteid }, true);
|
||||
}
|
||||
|
||||
createRemote(cname: string, kwargsArg: Record<string, string>, interactive: boolean): Promise<CommandRtnType> {
|
||||
let kwargs = Object.assign({}, kwargsArg);
|
||||
kwargs["nohist"] = "1";
|
||||
return GlobalModel.submitCommand("remote", "new", [cname], kwargs, interactive);
|
||||
}
|
||||
|
||||
openCreateRemote(): void {
|
||||
GlobalModel.submitCommand("remote", "new", null, { nohist: "1", visual: "1" }, true);
|
||||
}
|
||||
|
||||
screenSetRemote(remoteArg: string, nohist: boolean, interactive: boolean): Promise<CommandRtnType> {
|
||||
let kwargs = {};
|
||||
if (nohist) {
|
||||
kwargs["nohist"] = "1";
|
||||
}
|
||||
return GlobalModel.submitCommand("connect", null, [remoteArg], kwargs, interactive);
|
||||
}
|
||||
|
||||
editRemote(remoteid: string, kwargsArg: Record<string, string>): void {
|
||||
let kwargs = Object.assign({}, kwargsArg);
|
||||
kwargs["nohist"] = "1";
|
||||
kwargs["remote"] = remoteid;
|
||||
GlobalModel.submitCommand("remote", "set", null, kwargs, true);
|
||||
}
|
||||
|
||||
openEditRemote(remoteid: string): void {
|
||||
GlobalModel.submitCommand("remote", "set", null, { remote: remoteid, nohist: "1", visual: "1" }, true);
|
||||
}
|
||||
|
||||
archiveRemote(remoteid: string) {
|
||||
GlobalModel.submitCommand("remote", "archive", null, { remote: remoteid, nohist: "1" }, true);
|
||||
}
|
||||
|
||||
importSshConfig() {
|
||||
GlobalModel.submitCommand("remote", "parse", null, { nohist: "1", visual: "1" }, true);
|
||||
}
|
||||
|
||||
screenSelectLine(lineArg: string, focusVal?: string) {
|
||||
let kwargs: Record<string, string> = {
|
||||
nohist: "1",
|
||||
line: lineArg,
|
||||
};
|
||||
if (focusVal != null) {
|
||||
kwargs["focus"] = focusVal;
|
||||
}
|
||||
GlobalModel.submitCommand("screen", "set", null, kwargs, false);
|
||||
}
|
||||
|
||||
screenReorder(screenId: string, index: string) {
|
||||
let kwargs: Record<string, string> = {
|
||||
nohist: "1",
|
||||
screenId: screenId,
|
||||
index: index,
|
||||
};
|
||||
GlobalModel.submitCommand("screen", "reorder", null, kwargs, false);
|
||||
}
|
||||
|
||||
setTermUsedRows(termContext: RendererContext, height: number) {
|
||||
let kwargs: Record<string, string> = {};
|
||||
kwargs["screen"] = termContext.screenId;
|
||||
kwargs["hohist"] = "1";
|
||||
let posargs = [String(termContext.lineNum), String(height)];
|
||||
GlobalModel.submitCommand("line", "setheight", posargs, kwargs, false);
|
||||
}
|
||||
|
||||
screenSetAnchor(sessionId: string, screenId: string, anchorVal: string): void {
|
||||
let kwargs = {
|
||||
nohist: "1",
|
||||
anchor: anchorVal,
|
||||
session: sessionId,
|
||||
screen: screenId,
|
||||
};
|
||||
GlobalModel.submitCommand("screen", "set", null, kwargs, false);
|
||||
}
|
||||
|
||||
screenSetFocus(focusVal: string): void {
|
||||
GlobalModel.submitCommand("screen", "set", null, { focus: focusVal, nohist: "1" }, false);
|
||||
}
|
||||
|
||||
screenSetSettings(
|
||||
screenId: string,
|
||||
settings: { tabcolor?: string; tabicon?: string; name?: string; sharename?: string },
|
||||
interactive: boolean
|
||||
): Promise<CommandRtnType> {
|
||||
let kwargs: { [key: string]: any } = Object.assign({}, settings);
|
||||
kwargs["nohist"] = "1";
|
||||
kwargs["screen"] = screenId;
|
||||
return GlobalModel.submitCommand("screen", "set", null, kwargs, interactive);
|
||||
}
|
||||
|
||||
sessionArchive(sessionId: string, shouldArchive: boolean): Promise<CommandRtnType> {
|
||||
return GlobalModel.submitCommand(
|
||||
"session",
|
||||
"archive",
|
||||
[sessionId, shouldArchive ? "1" : "0"],
|
||||
{ nohist: "1" },
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
sessionDelete(sessionId: string): Promise<CommandRtnType> {
|
||||
return GlobalModel.submitCommand("session", "delete", [sessionId], { nohist: "1" }, false);
|
||||
}
|
||||
|
||||
sessionSetSettings(sessionId: string, settings: { name?: string }, interactive: boolean): Promise<CommandRtnType> {
|
||||
let kwargs = Object.assign({}, settings);
|
||||
kwargs["nohist"] = "1";
|
||||
kwargs["session"] = sessionId;
|
||||
return GlobalModel.submitCommand("session", "set", null, kwargs, interactive);
|
||||
}
|
||||
|
||||
lineStar(lineId: string, starVal: number) {
|
||||
GlobalModel.submitCommand("line", "star", [lineId, String(starVal)], { nohist: "1" }, true);
|
||||
}
|
||||
|
||||
lineBookmark(lineId: string) {
|
||||
GlobalModel.submitCommand("line", "bookmark", [lineId], { nohist: "1" }, true);
|
||||
}
|
||||
|
||||
linePin(lineId: string, val: boolean) {
|
||||
GlobalModel.submitCommand("line", "pin", [lineId, val ? "1" : "0"], { nohist: "1" }, true);
|
||||
}
|
||||
|
||||
bookmarksView() {
|
||||
GlobalModel.submitCommand("bookmarks", "show", null, { nohist: "1" }, true);
|
||||
}
|
||||
|
||||
connectionsView() {
|
||||
GlobalModel.connectionViewModel.showConnectionsView();
|
||||
}
|
||||
|
||||
clientSettingsView() {
|
||||
GlobalModel.clientSettingsViewModel.showClientSettingsView();
|
||||
}
|
||||
|
||||
historyView(params: HistorySearchParams) {
|
||||
let kwargs = { nohist: "1" };
|
||||
kwargs["offset"] = String(params.offset);
|
||||
kwargs["rawoffset"] = String(params.rawOffset);
|
||||
if (params.searchText != null) {
|
||||
kwargs["text"] = params.searchText;
|
||||
}
|
||||
if (params.searchSessionId != null) {
|
||||
kwargs["searchsession"] = params.searchSessionId;
|
||||
}
|
||||
if (params.searchRemoteId != null) {
|
||||
kwargs["searchremote"] = params.searchRemoteId;
|
||||
}
|
||||
if (params.fromTs != null) {
|
||||
kwargs["fromts"] = String(params.fromTs);
|
||||
}
|
||||
if (params.noMeta) {
|
||||
kwargs["meta"] = "0";
|
||||
}
|
||||
if (params.filterCmds) {
|
||||
kwargs["filter"] = "1";
|
||||
}
|
||||
GlobalModel.submitCommand("history", "viewall", null, kwargs, true);
|
||||
}
|
||||
|
||||
telemetryOff(interactive: boolean): Promise<CommandRtnType> {
|
||||
return GlobalModel.submitCommand("telemetry", "off", null, { nohist: "1" }, interactive);
|
||||
}
|
||||
|
||||
telemetryOn(interactive: boolean): Promise<CommandRtnType> {
|
||||
return GlobalModel.submitCommand("telemetry", "on", null, { nohist: "1" }, interactive);
|
||||
}
|
||||
|
||||
releaseCheckAutoOff(interactive: boolean): Promise<CommandRtnType> {
|
||||
return GlobalModel.submitCommand("releasecheck", "autooff", null, { nohist: "1" }, interactive);
|
||||
}
|
||||
|
||||
releaseCheckAutoOn(interactive: boolean): Promise<CommandRtnType> {
|
||||
return GlobalModel.submitCommand("releasecheck", "autoon", null, { nohist: "1" }, interactive);
|
||||
}
|
||||
|
||||
setTermFontSize(fsize: number, interactive: boolean): Promise<CommandRtnType> {
|
||||
let kwargs = {
|
||||
nohist: "1",
|
||||
termfontsize: String(fsize),
|
||||
};
|
||||
return GlobalModel.submitCommand("client", "set", null, kwargs, interactive);
|
||||
}
|
||||
|
||||
setClientOpenAISettings(opts: { model?: string; apitoken?: string; maxtokens?: string }): Promise<CommandRtnType> {
|
||||
let kwargs = {
|
||||
nohist: "1",
|
||||
};
|
||||
if (opts.model != null) {
|
||||
kwargs["openaimodel"] = opts.model;
|
||||
}
|
||||
if (opts.apitoken != null) {
|
||||
kwargs["openaiapitoken"] = opts.apitoken;
|
||||
}
|
||||
if (opts.maxtokens != null) {
|
||||
kwargs["openaimaxtokens"] = opts.maxtokens;
|
||||
}
|
||||
return GlobalModel.submitCommand("client", "set", null, kwargs, false);
|
||||
}
|
||||
|
||||
clientAcceptTos(): void {
|
||||
GlobalModel.submitCommand("client", "accepttos", null, { nohist: "1" }, true);
|
||||
}
|
||||
|
||||
clientSetConfirmFlag(flag: string, value: boolean): Promise<CommandRtnType> {
|
||||
let kwargs = { nohist: "1" };
|
||||
let valueStr = value ? "1" : "0";
|
||||
return GlobalModel.submitCommand("client", "setconfirmflag", [flag, valueStr], kwargs, false);
|
||||
}
|
||||
|
||||
clientSetSidebar(width: number, collapsed: boolean): Promise<CommandRtnType> {
|
||||
let kwargs = { nohist: "1", width: `${width}`, collapsed: collapsed ? "1" : "0" };
|
||||
return GlobalModel.submitCommand("client", "setsidebar", null, kwargs, false);
|
||||
}
|
||||
|
||||
editBookmark(bookmarkId: string, desc: string, cmdstr: string) {
|
||||
let kwargs = {
|
||||
nohist: "1",
|
||||
desc: desc,
|
||||
cmdstr: cmdstr,
|
||||
};
|
||||
GlobalModel.submitCommand("bookmark", "set", [bookmarkId], kwargs, true);
|
||||
}
|
||||
|
||||
deleteBookmark(bookmarkId: string): void {
|
||||
GlobalModel.submitCommand("bookmark", "delete", [bookmarkId], { nohist: "1" }, true);
|
||||
}
|
||||
|
||||
openSharedSession(): void {
|
||||
GlobalModel.submitCommand("session", "openshared", null, { nohist: "1" }, true);
|
||||
}
|
||||
|
||||
setLineState(
|
||||
screenId: string,
|
||||
lineId: string,
|
||||
state: LineStateType,
|
||||
interactive: boolean
|
||||
): Promise<CommandRtnType> {
|
||||
let stateStr = JSON.stringify(state);
|
||||
return GlobalModel.submitCommand(
|
||||
"line",
|
||||
"set",
|
||||
[lineId],
|
||||
{ screen: screenId, nohist: "1", state: stateStr },
|
||||
interactive
|
||||
);
|
||||
}
|
||||
|
||||
screenSidebarAddLine(lineId: string) {
|
||||
GlobalModel.submitCommand("sidebar", "add", null, { nohist: "1", line: lineId }, false);
|
||||
}
|
||||
|
||||
screenSidebarRemove() {
|
||||
GlobalModel.submitCommand("sidebar", "remove", null, { nohist: "1" }, false);
|
||||
}
|
||||
|
||||
screenSidebarClose(): void {
|
||||
GlobalModel.submitCommand("sidebar", "close", null, { nohist: "1" }, false);
|
||||
}
|
||||
|
||||
screenSidebarOpen(width?: string): void {
|
||||
let kwargs: Record<string, string> = { nohist: "1" };
|
||||
if (width != null) {
|
||||
kwargs.width = width;
|
||||
}
|
||||
GlobalModel.submitCommand("sidebar", "open", null, kwargs, false);
|
||||
}
|
||||
}
|
||||
|
||||
export { CommandRunner };
|
26
src/models/connectionsview.ts
Normal file
26
src/models/connectionsview.ts
Normal file
@ -0,0 +1,26 @@
|
||||
// Copyright 2023, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import * as mobx from "mobx";
|
||||
import { Model } from "./model";
|
||||
|
||||
class ConnectionsViewModel {
|
||||
globalModel: Model;
|
||||
|
||||
constructor(globalModel: Model) {
|
||||
this.globalModel = globalModel;
|
||||
}
|
||||
|
||||
closeView(): void {
|
||||
this.globalModel.showSessionView();
|
||||
setTimeout(() => this.globalModel.inputModel.giveFocus(), 50);
|
||||
}
|
||||
|
||||
showConnectionsView(): void {
|
||||
mobx.action(() => {
|
||||
this.globalModel.activeMainView.set("connections");
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
export { ConnectionsViewModel };
|
115
src/models/forwardlinecontainer.ts
Normal file
115
src/models/forwardlinecontainer.ts
Normal file
@ -0,0 +1,115 @@
|
||||
// Copyright 2023, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { TermWrap } from "../plugins/terminal/term";
|
||||
import * as types from "../types/types";
|
||||
import { windowWidthToCols, windowHeightToRows } from "../util/textmeasure";
|
||||
import { MagicLayout } from "../app/magiclayout";
|
||||
import { Model } from "./model";
|
||||
import { GlobalCommandRunner } from "./global";
|
||||
import { Cmd } from "./cmd";
|
||||
import { Screen } from "./screen";
|
||||
|
||||
class ForwardLineContainer {
|
||||
globalModel: Model;
|
||||
winSize: types.WindowSize;
|
||||
screen: Screen;
|
||||
containerType: types.LineContainerStrs;
|
||||
lineId: string;
|
||||
|
||||
constructor(screen: Screen, winSize: types.WindowSize, containerType: types.LineContainerStrs, lineId: string) {
|
||||
this.globalModel = Model.getInstance();
|
||||
this.screen = screen;
|
||||
this.winSize = winSize;
|
||||
this.containerType = containerType;
|
||||
this.lineId = lineId;
|
||||
}
|
||||
|
||||
screenSizeCallback(winSize: types.WindowSize): void {
|
||||
this.winSize = winSize;
|
||||
let termWrap = this.getTermWrap(this.lineId);
|
||||
if (termWrap != null) {
|
||||
let fontSize = this.globalModel.termFontSize.get();
|
||||
let cols = windowWidthToCols(winSize.width, fontSize);
|
||||
let rows = windowHeightToRows(winSize.height, fontSize);
|
||||
termWrap.resizeCols(cols);
|
||||
GlobalCommandRunner.resizeScreen(this.screen.screenId, rows, cols, { include: [this.lineId] });
|
||||
}
|
||||
}
|
||||
|
||||
getContainerType(): types.LineContainerStrs {
|
||||
return this.containerType;
|
||||
}
|
||||
|
||||
getCmd(line: types.LineType): Cmd {
|
||||
return this.screen.getCmd(line);
|
||||
}
|
||||
|
||||
isSidebarOpen(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
isLineIdInSidebar(lineId: string): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
setLineFocus(lineNum: number, focus: boolean): void {
|
||||
this.screen.setLineFocus(lineNum, focus);
|
||||
}
|
||||
|
||||
setContentHeight(context: types.RendererContext, height: number): void {
|
||||
return;
|
||||
}
|
||||
|
||||
getMaxContentSize(): types.WindowSize {
|
||||
let rtn = { width: this.winSize.width, height: this.winSize.height };
|
||||
rtn.width = rtn.width - MagicLayout.ScreenMaxContentWidthBuffer;
|
||||
return rtn;
|
||||
}
|
||||
|
||||
getIdealContentSize(): types.WindowSize {
|
||||
return this.winSize;
|
||||
}
|
||||
|
||||
loadTerminalRenderer(elem: Element, line: types.LineType, cmd: Cmd, width: number): void {
|
||||
this.screen.loadTerminalRenderer(elem, line, cmd, width);
|
||||
}
|
||||
|
||||
registerRenderer(lineId: string, renderer: types.RendererModel): void {
|
||||
this.screen.registerRenderer(lineId, renderer);
|
||||
}
|
||||
|
||||
unloadRenderer(lineId: string): void {
|
||||
this.screen.unloadRenderer(lineId);
|
||||
}
|
||||
|
||||
getContentHeight(context: types.RendererContext): number {
|
||||
return this.screen.getContentHeight(context);
|
||||
}
|
||||
|
||||
getUsedRows(context: types.RendererContext, line: types.LineType, cmd: Cmd, width: number): number {
|
||||
return this.screen.getUsedRows(context, line, cmd, width);
|
||||
}
|
||||
|
||||
getIsFocused(lineNum: number): boolean {
|
||||
return this.screen.getIsFocused(lineNum);
|
||||
}
|
||||
|
||||
getRenderer(lineId: string): types.RendererModel {
|
||||
return this.screen.getRenderer(lineId);
|
||||
}
|
||||
|
||||
getTermWrap(lineId: string): TermWrap {
|
||||
return this.screen.getTermWrap(lineId);
|
||||
}
|
||||
|
||||
getFocusType(): types.FocusTypeStrs {
|
||||
return this.screen.getFocusType();
|
||||
}
|
||||
|
||||
getSelectedLine(): number {
|
||||
return this.screen.getSelectedLine();
|
||||
}
|
||||
}
|
||||
|
||||
export { ForwardLineContainer };
|
6
src/models/global.ts
Normal file
6
src/models/global.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { Model } from "./model";
|
||||
import { CommandRunner } from "./commandrunner";
|
||||
|
||||
const GlobalModel = Model.getInstance();
|
||||
const GlobalCommandRunner = CommandRunner.getInstance();
|
||||
export { GlobalModel, GlobalCommandRunner };
|
326
src/models/historyview.ts
Normal file
326
src/models/historyview.ts
Normal file
@ -0,0 +1,326 @@
|
||||
// Copyright 2023, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import * as mobx from "mobx";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { isBlank } from "../util/util";
|
||||
import {
|
||||
LineType,
|
||||
HistoryItem,
|
||||
CmdDataType,
|
||||
HistoryViewDataType,
|
||||
HistorySearchParams,
|
||||
CommandRtnType,
|
||||
} from "../types/types";
|
||||
import { termWidthFromCols, termHeightFromRows } from "../util/textmeasure";
|
||||
import dayjs from "dayjs";
|
||||
import * as appconst from "../app/appconst";
|
||||
import { checkKeyPressed, adaptFromReactOrNativeKeyEvent } from "../util/keyutil";
|
||||
import { OV, OArr, OMap } from "../types/types";
|
||||
import { GlobalCommandRunner } from "./global";
|
||||
import { Model } from "./model";
|
||||
import { Cmd } from "./cmd";
|
||||
import { SpecialLineContainer } from "./speciallinecontainer";
|
||||
|
||||
const HistoryPageSize = 50;
|
||||
|
||||
class HistoryViewModel {
|
||||
globalModel: Model;
|
||||
items: OArr<HistoryItem> = mobx.observable.array([], {
|
||||
name: "HistoryItems",
|
||||
});
|
||||
hasMore: OV<boolean> = mobx.observable.box(false, {
|
||||
name: "historyview-hasmore",
|
||||
});
|
||||
offset: OV<number> = mobx.observable.box(0, { name: "historyview-offset" });
|
||||
searchText: OV<string> = mobx.observable.box("", {
|
||||
name: "historyview-searchtext",
|
||||
});
|
||||
activeSearchText: string = null;
|
||||
selectedItems: OMap<string, boolean> = mobx.observable.map({}, { name: "historyview-selectedItems" });
|
||||
deleteActive: OV<boolean> = mobx.observable.box(false, {
|
||||
name: "historyview-deleteActive",
|
||||
});
|
||||
activeItem: OV<string> = mobx.observable.box(null, {
|
||||
name: "historyview-activeItem",
|
||||
});
|
||||
searchSessionId: OV<string> = mobx.observable.box(null, {
|
||||
name: "historyview-searchSessionId",
|
||||
});
|
||||
searchRemoteId: OV<string> = mobx.observable.box(null, {
|
||||
name: "historyview-searchRemoteId",
|
||||
});
|
||||
searchShowMeta: OV<boolean> = mobx.observable.box(true, {
|
||||
name: "historyview-searchShowMeta",
|
||||
});
|
||||
searchFromDate: OV<string> = mobx.observable.box(null, {
|
||||
name: "historyview-searchfromts",
|
||||
});
|
||||
searchFilterCmds: OV<boolean> = mobx.observable.box(true, {
|
||||
name: "historyview-filtercmds",
|
||||
});
|
||||
nextRawOffset: number = 0;
|
||||
curRawOffset: number = 0;
|
||||
|
||||
historyItemLines: LineType[] = [];
|
||||
historyItemCmds: CmdDataType[] = [];
|
||||
|
||||
specialLineContainer: SpecialLineContainer;
|
||||
|
||||
constructor(globalModel: Model) {
|
||||
this.globalModel = globalModel;
|
||||
}
|
||||
|
||||
closeView(): void {
|
||||
this.globalModel.showSessionView();
|
||||
setTimeout(() => this.globalModel.inputModel.giveFocus(), 50);
|
||||
}
|
||||
|
||||
getLineById(lineId: string): LineType {
|
||||
if (isBlank(lineId)) {
|
||||
return null;
|
||||
}
|
||||
for (const line of this.historyItemLines) {
|
||||
if (line.lineid == lineId) {
|
||||
return line;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getCmdById(lineId: string): Cmd {
|
||||
if (isBlank(lineId)) {
|
||||
return null;
|
||||
}
|
||||
for (const cmd of this.historyItemCmds) {
|
||||
if (cmd.lineid == lineId) {
|
||||
return new Cmd(cmd);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getHistoryItemById(historyId: string): HistoryItem {
|
||||
if (isBlank(historyId)) {
|
||||
return null;
|
||||
}
|
||||
for (const hitem of this.items) {
|
||||
if (hitem.historyid == historyId) {
|
||||
return hitem;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
setActiveItem(historyId: string) {
|
||||
if (this.activeItem.get() == historyId) {
|
||||
return;
|
||||
}
|
||||
let hitem = this.getHistoryItemById(historyId);
|
||||
mobx.action(() => {
|
||||
if (hitem == null) {
|
||||
this.activeItem.set(null);
|
||||
this.specialLineContainer = null;
|
||||
} else {
|
||||
this.activeItem.set(hitem.historyid);
|
||||
let width = termWidthFromCols(80, this.globalModel.termFontSize.get());
|
||||
let height = termHeightFromRows(25, this.globalModel.termFontSize.get());
|
||||
this.specialLineContainer = new SpecialLineContainer(
|
||||
this,
|
||||
{ width, height },
|
||||
false,
|
||||
appconst.LineContainer_History
|
||||
);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
doSelectedDelete(): void {
|
||||
if (!this.deleteActive.get()) {
|
||||
mobx.action(() => {
|
||||
this.deleteActive.set(true);
|
||||
})();
|
||||
setTimeout(this.clearActiveDelete, 2000);
|
||||
return;
|
||||
}
|
||||
let prtn = this.globalModel.showAlert({
|
||||
message: "Deleting lines from history also deletes their content from your workspaces.",
|
||||
confirm: true,
|
||||
});
|
||||
prtn.then((result) => {
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
if (result) {
|
||||
this._deleteSelected();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_deleteSelected(): void {
|
||||
let lineIds = Array.from(this.selectedItems.keys());
|
||||
let prtn = GlobalCommandRunner.historyPurgeLines(lineIds);
|
||||
prtn.then((result: CommandRtnType) => {
|
||||
if (!result.success) {
|
||||
this.globalModel.showAlert({ message: "Error removing history lines." });
|
||||
}
|
||||
});
|
||||
let params = this._getSearchParams();
|
||||
GlobalCommandRunner.historyView(params);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
clearActiveDelete(): void {
|
||||
mobx.action(() => {
|
||||
this.deleteActive.set(false);
|
||||
})();
|
||||
}
|
||||
|
||||
_getSearchParams(newOffset?: number, newRawOffset?: number): HistorySearchParams {
|
||||
let offset = newOffset ?? this.offset.get();
|
||||
let rawOffset = newRawOffset ?? this.curRawOffset;
|
||||
let opts: HistorySearchParams = {
|
||||
offset: offset,
|
||||
rawOffset: rawOffset,
|
||||
searchText: this.activeSearchText,
|
||||
searchSessionId: this.searchSessionId.get(),
|
||||
searchRemoteId: this.searchRemoteId.get(),
|
||||
};
|
||||
if (!this.searchShowMeta.get()) {
|
||||
opts.noMeta = true;
|
||||
}
|
||||
if (this.searchFromDate.get() != null) {
|
||||
let fromDate = this.searchFromDate.get();
|
||||
let fromTs = dayjs(fromDate, "YYYY-MM-DD").valueOf();
|
||||
let d = new Date(fromTs);
|
||||
d.setDate(d.getDate() + 1);
|
||||
let ts = d.getTime() - 1;
|
||||
opts.fromTs = ts;
|
||||
}
|
||||
if (this.searchFilterCmds.get()) {
|
||||
opts.filterCmds = true;
|
||||
}
|
||||
return opts;
|
||||
}
|
||||
|
||||
reSearch(): void {
|
||||
this.setActiveItem(null);
|
||||
GlobalCommandRunner.historyView(this._getSearchParams());
|
||||
}
|
||||
|
||||
resetAllFilters(): void {
|
||||
mobx.action(() => {
|
||||
this.activeSearchText = "";
|
||||
this.searchText.set("");
|
||||
this.searchSessionId.set(null);
|
||||
this.searchRemoteId.set(null);
|
||||
this.searchFromDate.set(null);
|
||||
this.searchShowMeta.set(true);
|
||||
this.searchFilterCmds.set(true);
|
||||
})();
|
||||
GlobalCommandRunner.historyView(this._getSearchParams(0, 0));
|
||||
}
|
||||
|
||||
setFromDate(fromDate: string): void {
|
||||
if (this.searchFromDate.get() == fromDate) {
|
||||
return;
|
||||
}
|
||||
mobx.action(() => {
|
||||
this.searchFromDate.set(fromDate);
|
||||
})();
|
||||
GlobalCommandRunner.historyView(this._getSearchParams(0, 0));
|
||||
}
|
||||
|
||||
setSearchFilterCmds(filter: boolean): void {
|
||||
if (this.searchFilterCmds.get() == filter) {
|
||||
return;
|
||||
}
|
||||
mobx.action(() => {
|
||||
this.searchFilterCmds.set(filter);
|
||||
})();
|
||||
GlobalCommandRunner.historyView(this._getSearchParams(0, 0));
|
||||
}
|
||||
|
||||
setSearchShowMeta(show: boolean): void {
|
||||
if (this.searchShowMeta.get() == show) {
|
||||
return;
|
||||
}
|
||||
mobx.action(() => {
|
||||
this.searchShowMeta.set(show);
|
||||
})();
|
||||
GlobalCommandRunner.historyView(this._getSearchParams(0, 0));
|
||||
}
|
||||
|
||||
setSearchSessionId(sessionId: string): void {
|
||||
if (this.searchSessionId.get() == sessionId) {
|
||||
return;
|
||||
}
|
||||
mobx.action(() => {
|
||||
this.searchSessionId.set(sessionId);
|
||||
})();
|
||||
GlobalCommandRunner.historyView(this._getSearchParams(0, 0));
|
||||
}
|
||||
|
||||
setSearchRemoteId(remoteId: string): void {
|
||||
if (this.searchRemoteId.get() == remoteId) {
|
||||
return;
|
||||
}
|
||||
mobx.action(() => {
|
||||
this.searchRemoteId.set(remoteId);
|
||||
})();
|
||||
GlobalCommandRunner.historyView(this._getSearchParams(0, 0));
|
||||
}
|
||||
|
||||
goPrev(): void {
|
||||
let offset = this.offset.get();
|
||||
offset = offset - HistoryPageSize;
|
||||
if (offset < 0) {
|
||||
offset = 0;
|
||||
}
|
||||
let params = this._getSearchParams(offset, 0);
|
||||
GlobalCommandRunner.historyView(params);
|
||||
}
|
||||
|
||||
goNext(): void {
|
||||
let offset = this.offset.get();
|
||||
offset += HistoryPageSize;
|
||||
let params = this._getSearchParams(offset, this.nextRawOffset ?? 0);
|
||||
GlobalCommandRunner.historyView(params);
|
||||
}
|
||||
|
||||
submitSearch(): void {
|
||||
mobx.action(() => {
|
||||
this.hasMore.set(false);
|
||||
this.items.replace([]);
|
||||
this.activeSearchText = this.searchText.get();
|
||||
this.historyItemLines = [];
|
||||
this.historyItemCmds = [];
|
||||
})();
|
||||
GlobalCommandRunner.historyView(this._getSearchParams(0, 0));
|
||||
}
|
||||
|
||||
handleDocKeyDown(e: any): void {
|
||||
let waveEvent = adaptFromReactOrNativeKeyEvent(e);
|
||||
if (checkKeyPressed(waveEvent, "Escape")) {
|
||||
e.preventDefault();
|
||||
this.closeView();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
showHistoryView(data: HistoryViewDataType): void {
|
||||
mobx.action(() => {
|
||||
this.globalModel.activeMainView.set("history");
|
||||
this.hasMore.set(data.hasmore);
|
||||
this.items.replace(data.items || []);
|
||||
this.offset.set(data.offset);
|
||||
this.nextRawOffset = data.nextrawoffset;
|
||||
this.curRawOffset = data.rawoffset;
|
||||
this.historyItemLines = data.lines ?? [];
|
||||
this.historyItemCmds = data.cmds ?? [];
|
||||
this.selectedItems.clear();
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
export { HistoryViewModel };
|
16
src/models/index.ts
Normal file
16
src/models/index.ts
Normal file
@ -0,0 +1,16 @@
|
||||
export * from "./global";
|
||||
export * from "./model";
|
||||
export { BookmarksModel } from "./bookmarks";
|
||||
export { ClientSettingsViewModel } from "./clientsettingsview";
|
||||
export { Cmd } from "./cmd";
|
||||
export { ConnectionsViewModel } from "./connectionsview";
|
||||
export { InputModel } from "./input";
|
||||
export { MainSidebarModel } from "./mainsidebar";
|
||||
export { ModalsModel } from "./modals";
|
||||
export { PluginsModel } from "./plugins";
|
||||
export { RemotesModel } from "./remotes";
|
||||
export { Screen } from "./screen";
|
||||
export { ScreenLines } from "./screenlines";
|
||||
export { Session } from "./session";
|
||||
export { SpecialLineContainer } from "./speciallinecontainer";
|
||||
export { ForwardLineContainer } from "./forwardlinecontainer";
|
803
src/models/input.ts
Normal file
803
src/models/input.ts
Normal file
@ -0,0 +1,803 @@
|
||||
// Copyright 2023, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type React from "react";
|
||||
import * as mobx from "mobx";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { isBlank } from "../util/util";
|
||||
import {
|
||||
HistoryItem,
|
||||
RemotePtrType,
|
||||
InfoType,
|
||||
HistoryInfoType,
|
||||
HistoryQueryOpts,
|
||||
HistoryTypeStrs,
|
||||
OpenAICmdInfoChatMessageType,
|
||||
} from "../types/types";
|
||||
import { StrWithPos } from "../types/types";
|
||||
import * as appconst from "../app/appconst";
|
||||
import { OV } from "../types/types";
|
||||
import { Model } from "./model";
|
||||
import { GlobalCommandRunner } from "./global";
|
||||
|
||||
function getDefaultHistoryQueryOpts(): HistoryQueryOpts {
|
||||
return {
|
||||
queryType: "screen",
|
||||
limitRemote: true,
|
||||
limitRemoteInstance: true,
|
||||
limitUser: true,
|
||||
queryStr: "",
|
||||
maxItems: 10000,
|
||||
includeMeta: true,
|
||||
fromTs: 0,
|
||||
};
|
||||
}
|
||||
|
||||
class InputModel {
|
||||
globalModel: Model;
|
||||
historyShow: OV<boolean> = mobx.observable.box(false);
|
||||
infoShow: OV<boolean> = mobx.observable.box(false);
|
||||
aIChatShow: OV<boolean> = mobx.observable.box(false);
|
||||
cmdInputHeight: OV<number> = mobx.observable.box(0);
|
||||
aiChatTextAreaRef: React.RefObject<HTMLTextAreaElement>;
|
||||
aiChatWindowRef: React.RefObject<HTMLDivElement>;
|
||||
codeSelectBlockRefArray: Array<React.RefObject<HTMLElement>>;
|
||||
codeSelectSelectedIndex: OV<number> = mobx.observable.box(-1);
|
||||
|
||||
AICmdInfoChatItems: mobx.IObservableArray<OpenAICmdInfoChatMessageType> = mobx.observable.array([], {
|
||||
name: "aicmdinfo-chat",
|
||||
});
|
||||
readonly codeSelectTop: number = -2;
|
||||
readonly codeSelectBottom: number = -1;
|
||||
|
||||
historyType: mobx.IObservableValue<HistoryTypeStrs> = mobx.observable.box("screen");
|
||||
historyLoading: mobx.IObservableValue<boolean> = mobx.observable.box(false);
|
||||
historyAfterLoadIndex: number = 0;
|
||||
historyItems: mobx.IObservableValue<HistoryItem[]> = mobx.observable.box(null, {
|
||||
name: "history-items",
|
||||
deep: false,
|
||||
}); // sorted in reverse (most recent is index 0)
|
||||
filteredHistoryItems: mobx.IComputedValue<HistoryItem[]> = null;
|
||||
historyIndex: mobx.IObservableValue<number> = mobx.observable.box(0, {
|
||||
name: "history-index",
|
||||
}); // 1-indexed (because 0 is current)
|
||||
modHistory: mobx.IObservableArray<string> = mobx.observable.array([""], {
|
||||
name: "mod-history",
|
||||
});
|
||||
historyQueryOpts: OV<HistoryQueryOpts> = mobx.observable.box(getDefaultHistoryQueryOpts());
|
||||
|
||||
infoMsg: OV<InfoType> = mobx.observable.box(null);
|
||||
infoTimeoutId: any = null;
|
||||
inputMode: OV<null | "comment" | "global"> = mobx.observable.box(null);
|
||||
inputExpanded: OV<boolean> = mobx.observable.box(false, {
|
||||
name: "inputExpanded",
|
||||
});
|
||||
|
||||
// cursor
|
||||
forceCursorPos: OV<number> = mobx.observable.box(null);
|
||||
|
||||
// focus
|
||||
inputFocused: OV<boolean> = mobx.observable.box(false);
|
||||
lineFocused: OV<boolean> = mobx.observable.box(false);
|
||||
physicalInputFocused: OV<boolean> = mobx.observable.box(false);
|
||||
forceInputFocus: boolean = false;
|
||||
|
||||
constructor(globalModel: Model) {
|
||||
this.globalModel = globalModel;
|
||||
this.filteredHistoryItems = mobx.computed(() => {
|
||||
return this._getFilteredHistoryItems();
|
||||
});
|
||||
mobx.action(() => {
|
||||
this.codeSelectSelectedIndex.set(-1);
|
||||
this.codeSelectBlockRefArray = [];
|
||||
})();
|
||||
}
|
||||
|
||||
setInputMode(inputMode: null | "comment" | "global"): void {
|
||||
mobx.action(() => {
|
||||
this.inputMode.set(inputMode);
|
||||
})();
|
||||
}
|
||||
|
||||
onInputFocus(isFocused: boolean): void {
|
||||
mobx.action(() => {
|
||||
if (isFocused) {
|
||||
this.inputFocused.set(true);
|
||||
this.lineFocused.set(false);
|
||||
} else if (this.inputFocused.get()) {
|
||||
this.inputFocused.set(false);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
onLineFocus(isFocused: boolean): void {
|
||||
mobx.action(() => {
|
||||
if (isFocused) {
|
||||
this.inputFocused.set(false);
|
||||
this.lineFocused.set(true);
|
||||
} else if (this.lineFocused.get()) {
|
||||
this.lineFocused.set(false);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
_focusCmdInput(): void {
|
||||
let elem = document.getElementById("main-cmd-input");
|
||||
if (elem != null) {
|
||||
elem.focus();
|
||||
}
|
||||
}
|
||||
|
||||
_focusHistoryInput(): void {
|
||||
let elem: HTMLElement = document.querySelector(".cmd-input input.history-input");
|
||||
if (elem != null) {
|
||||
elem.focus();
|
||||
}
|
||||
}
|
||||
|
||||
giveFocus(): void {
|
||||
if (this.historyShow.get()) {
|
||||
this._focusHistoryInput();
|
||||
} else {
|
||||
this._focusCmdInput();
|
||||
}
|
||||
}
|
||||
|
||||
setPhysicalInputFocused(isFocused: boolean): void {
|
||||
mobx.action(() => {
|
||||
this.physicalInputFocused.set(isFocused);
|
||||
})();
|
||||
if (isFocused) {
|
||||
let screen = this.globalModel.getActiveScreen();
|
||||
if (screen != null) {
|
||||
if (screen.focusType.get() != "input") {
|
||||
GlobalCommandRunner.screenSetFocus("input");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hasFocus(): boolean {
|
||||
let mainInputElem = document.getElementById("main-cmd-input");
|
||||
if (document.activeElement == mainInputElem) {
|
||||
return true;
|
||||
}
|
||||
let historyInputElem = document.querySelector(".cmd-input input.history-input");
|
||||
if (document.activeElement == historyInputElem) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
setHistoryType(htype: HistoryTypeStrs): void {
|
||||
if (this.historyQueryOpts.get().queryType == htype) {
|
||||
return;
|
||||
}
|
||||
this.loadHistory(true, -1, htype);
|
||||
}
|
||||
|
||||
findBestNewIndex(oldItem: HistoryItem): number {
|
||||
if (oldItem == null) {
|
||||
return 0;
|
||||
}
|
||||
let newItems = this.getFilteredHistoryItems();
|
||||
if (newItems.length == 0) {
|
||||
return 0;
|
||||
}
|
||||
let bestIdx = 0;
|
||||
for (let i = 0; i < newItems.length; i++) {
|
||||
// still start at i=0 to catch the historynum equality case
|
||||
let item = newItems[i];
|
||||
if (item.historynum == oldItem.historynum) {
|
||||
bestIdx = i;
|
||||
break;
|
||||
}
|
||||
let bestTsDiff = Math.abs(item.ts - newItems[bestIdx].ts);
|
||||
let curTsDiff = Math.abs(item.ts - oldItem.ts);
|
||||
if (curTsDiff < bestTsDiff) {
|
||||
bestIdx = i;
|
||||
}
|
||||
}
|
||||
return bestIdx + 1;
|
||||
}
|
||||
|
||||
setHistoryQueryOpts(opts: HistoryQueryOpts): void {
|
||||
mobx.action(() => {
|
||||
let oldItem = this.getHistorySelectedItem();
|
||||
this.historyQueryOpts.set(opts);
|
||||
let bestIndex = this.findBestNewIndex(oldItem);
|
||||
setTimeout(() => this.setHistoryIndex(bestIndex, true), 10);
|
||||
return;
|
||||
})();
|
||||
}
|
||||
|
||||
setOpenAICmdInfoChat(chat: OpenAICmdInfoChatMessageType[]): void {
|
||||
this.AICmdInfoChatItems.replace(chat);
|
||||
this.codeSelectBlockRefArray = [];
|
||||
}
|
||||
|
||||
setHistoryShow(show: boolean): void {
|
||||
if (this.historyShow.get() == show) {
|
||||
return;
|
||||
}
|
||||
mobx.action(() => {
|
||||
this.historyShow.set(show);
|
||||
if (this.hasFocus()) {
|
||||
this.giveFocus();
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
isHistoryLoaded(): boolean {
|
||||
if (this.historyLoading.get()) {
|
||||
return false;
|
||||
}
|
||||
let hitems = this.historyItems.get();
|
||||
return hitems != null;
|
||||
}
|
||||
|
||||
loadHistory(show: boolean, afterLoadIndex: number, htype: HistoryTypeStrs) {
|
||||
if (this.historyLoading.get()) {
|
||||
return;
|
||||
}
|
||||
if (this.isHistoryLoaded()) {
|
||||
if (this.historyQueryOpts.get().queryType == htype) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.historyAfterLoadIndex = afterLoadIndex;
|
||||
mobx.action(() => {
|
||||
this.historyLoading.set(true);
|
||||
})();
|
||||
GlobalCommandRunner.loadHistory(show, htype);
|
||||
}
|
||||
|
||||
openHistory(): void {
|
||||
if (this.historyLoading.get()) {
|
||||
return;
|
||||
}
|
||||
if (!this.isHistoryLoaded()) {
|
||||
this.loadHistory(true, 0, "screen");
|
||||
return;
|
||||
}
|
||||
if (!this.historyShow.get()) {
|
||||
mobx.action(() => {
|
||||
this.setHistoryShow(true);
|
||||
this.infoShow.set(false);
|
||||
this.dropModHistory(true);
|
||||
this.giveFocus();
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
updateCmdLine(cmdLine: StrWithPos): void {
|
||||
mobx.action(() => {
|
||||
this.setCurLine(cmdLine.str);
|
||||
if (cmdLine.pos != appconst.NoStrPos) {
|
||||
this.forceCursorPos.set(cmdLine.pos);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
getHistorySelectedItem(): HistoryItem {
|
||||
let hidx = this.historyIndex.get();
|
||||
if (hidx == 0) {
|
||||
return null;
|
||||
}
|
||||
let hitems = this.getFilteredHistoryItems();
|
||||
if (hidx > hitems.length) {
|
||||
return null;
|
||||
}
|
||||
return hitems[hidx - 1];
|
||||
}
|
||||
|
||||
getFirstHistoryItem(): HistoryItem {
|
||||
let hitems = this.getFilteredHistoryItems();
|
||||
if (hitems.length == 0) {
|
||||
return null;
|
||||
}
|
||||
return hitems[0];
|
||||
}
|
||||
|
||||
setHistorySelectionNum(hnum: string): void {
|
||||
let hitems = this.getFilteredHistoryItems();
|
||||
for (let i = 0; i < hitems.length; i++) {
|
||||
if (hitems[i].historynum == hnum) {
|
||||
this.setHistoryIndex(i + 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setHistoryInfo(hinfo: HistoryInfoType): void {
|
||||
mobx.action(() => {
|
||||
let oldItem = this.getHistorySelectedItem();
|
||||
let hitems: HistoryItem[] = hinfo.items ?? [];
|
||||
this.historyItems.set(hitems);
|
||||
this.historyLoading.set(false);
|
||||
this.historyQueryOpts.get().queryType = hinfo.historytype;
|
||||
if (hinfo.historytype == "session" || hinfo.historytype == "global") {
|
||||
this.historyQueryOpts.get().limitRemote = false;
|
||||
this.historyQueryOpts.get().limitRemoteInstance = false;
|
||||
}
|
||||
if (this.historyAfterLoadIndex == -1) {
|
||||
let bestIndex = this.findBestNewIndex(oldItem);
|
||||
setTimeout(() => this.setHistoryIndex(bestIndex, true), 100);
|
||||
} else if (this.historyAfterLoadIndex) {
|
||||
if (hitems.length >= this.historyAfterLoadIndex) {
|
||||
this.setHistoryIndex(this.historyAfterLoadIndex);
|
||||
}
|
||||
}
|
||||
this.historyAfterLoadIndex = 0;
|
||||
if (hinfo.show) {
|
||||
this.openHistory();
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
getFilteredHistoryItems(): HistoryItem[] {
|
||||
return this.filteredHistoryItems.get();
|
||||
}
|
||||
|
||||
_getFilteredHistoryItems(): HistoryItem[] {
|
||||
let hitems: HistoryItem[] = this.historyItems.get() ?? [];
|
||||
let rtn: HistoryItem[] = [];
|
||||
let opts = mobx.toJS(this.historyQueryOpts.get());
|
||||
let ctx = this.globalModel.getUIContext();
|
||||
let curRemote: RemotePtrType = ctx.remote;
|
||||
if (curRemote == null) {
|
||||
curRemote = { ownerid: "", name: "", remoteid: "" };
|
||||
}
|
||||
curRemote = mobx.toJS(curRemote);
|
||||
for (const hitem of hitems) {
|
||||
if (hitem.ismetacmd) {
|
||||
if (!opts.includeMeta) {
|
||||
continue;
|
||||
}
|
||||
} else if (opts.limitRemoteInstance) {
|
||||
if (hitem.remote == null || isBlank(hitem.remote.remoteid)) {
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
(curRemote.ownerid ?? "") != (hitem.remote.ownerid ?? "") ||
|
||||
(curRemote.remoteid ?? "") != (hitem.remote.remoteid ?? "") ||
|
||||
(curRemote.name ?? "") != (hitem.remote.name ?? "")
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
} else if (opts.limitRemote) {
|
||||
if (hitem.remote == null || isBlank(hitem.remote.remoteid)) {
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
(curRemote.ownerid ?? "") != (hitem.remote.ownerid ?? "") ||
|
||||
(curRemote.remoteid ?? "") != (hitem.remote.remoteid ?? "")
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (!isBlank(opts.queryStr)) {
|
||||
if (isBlank(hitem.cmdstr)) {
|
||||
continue;
|
||||
}
|
||||
let idx = hitem.cmdstr.indexOf(opts.queryStr);
|
||||
if (idx == -1) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
rtn.push(hitem);
|
||||
}
|
||||
return rtn;
|
||||
}
|
||||
|
||||
scrollHistoryItemIntoView(hnum: string): void {
|
||||
let elem: HTMLElement = document.querySelector(".cmd-history .hnum-" + hnum);
|
||||
if (elem == null) {
|
||||
return;
|
||||
}
|
||||
let historyDiv = elem.closest(".cmd-history");
|
||||
if (historyDiv == null) {
|
||||
return;
|
||||
}
|
||||
let buffer = 15;
|
||||
let titleHeight = 24;
|
||||
let titleDiv: HTMLElement = document.querySelector(".cmd-history .history-title");
|
||||
if (titleDiv != null) {
|
||||
titleHeight = titleDiv.offsetHeight + 2;
|
||||
}
|
||||
let elemOffset = elem.offsetTop;
|
||||
let elemHeight = elem.clientHeight;
|
||||
let topPos = historyDiv.scrollTop;
|
||||
let endPos = topPos + historyDiv.clientHeight;
|
||||
if (elemOffset + elemHeight + buffer > endPos) {
|
||||
if (elemHeight + buffer > historyDiv.clientHeight - titleHeight) {
|
||||
historyDiv.scrollTop = elemOffset - titleHeight;
|
||||
return;
|
||||
}
|
||||
historyDiv.scrollTop = elemOffset - historyDiv.clientHeight + elemHeight + buffer;
|
||||
return;
|
||||
}
|
||||
if (elemOffset < topPos + titleHeight) {
|
||||
if (elemHeight + buffer > historyDiv.clientHeight - titleHeight) {
|
||||
historyDiv.scrollTop = elemOffset - titleHeight;
|
||||
return;
|
||||
}
|
||||
historyDiv.scrollTop = elemOffset - titleHeight - buffer;
|
||||
}
|
||||
}
|
||||
|
||||
grabSelectedHistoryItem(): void {
|
||||
let hitem = this.getHistorySelectedItem();
|
||||
if (hitem == null) {
|
||||
this.resetHistory();
|
||||
return;
|
||||
}
|
||||
mobx.action(() => {
|
||||
this.resetInput();
|
||||
this.setCurLine(hitem.cmdstr);
|
||||
})();
|
||||
}
|
||||
|
||||
setHistoryIndex(hidx: number, force?: boolean): void {
|
||||
if (hidx < 0) {
|
||||
return;
|
||||
}
|
||||
if (!force && this.historyIndex.get() == hidx) {
|
||||
return;
|
||||
}
|
||||
mobx.action(() => {
|
||||
this.historyIndex.set(hidx);
|
||||
if (this.historyShow.get()) {
|
||||
let hitem = this.getHistorySelectedItem();
|
||||
if (hitem == null) {
|
||||
hitem = this.getFirstHistoryItem();
|
||||
}
|
||||
if (hitem != null) {
|
||||
this.scrollHistoryItemIntoView(hitem.historynum);
|
||||
}
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
moveHistorySelection(amt: number): void {
|
||||
if (amt == 0) {
|
||||
return;
|
||||
}
|
||||
if (!this.isHistoryLoaded()) {
|
||||
return;
|
||||
}
|
||||
let hitems = this.getFilteredHistoryItems();
|
||||
let idx = this.historyIndex.get();
|
||||
idx += amt;
|
||||
if (idx < 0) {
|
||||
idx = 0;
|
||||
}
|
||||
if (idx > hitems.length) {
|
||||
idx = hitems.length;
|
||||
}
|
||||
this.setHistoryIndex(idx);
|
||||
}
|
||||
|
||||
flashInfoMsg(info: InfoType, timeoutMs: number): void {
|
||||
this._clearInfoTimeout();
|
||||
mobx.action(() => {
|
||||
this.infoMsg.set(info);
|
||||
if (info == null) {
|
||||
this.infoShow.set(false);
|
||||
} else {
|
||||
this.infoShow.set(true);
|
||||
this.setHistoryShow(false);
|
||||
}
|
||||
})();
|
||||
if (info != null && timeoutMs) {
|
||||
this.infoTimeoutId = setTimeout(() => {
|
||||
if (this.historyShow.get()) {
|
||||
return;
|
||||
}
|
||||
this.clearInfoMsg(false);
|
||||
}, timeoutMs);
|
||||
}
|
||||
}
|
||||
|
||||
setCmdInfoChatRefs(
|
||||
textAreaRef: React.RefObject<HTMLTextAreaElement>,
|
||||
chatWindowRef: React.RefObject<HTMLDivElement>
|
||||
) {
|
||||
this.aiChatTextAreaRef = textAreaRef;
|
||||
this.aiChatWindowRef = chatWindowRef;
|
||||
}
|
||||
|
||||
setAIChatFocus() {
|
||||
if (this.aiChatTextAreaRef?.current != null) {
|
||||
this.aiChatTextAreaRef.current.focus();
|
||||
}
|
||||
}
|
||||
|
||||
grabCodeSelectSelection() {
|
||||
if (
|
||||
this.codeSelectSelectedIndex.get() >= 0 &&
|
||||
this.codeSelectSelectedIndex.get() < this.codeSelectBlockRefArray.length
|
||||
) {
|
||||
let curBlockRef = this.codeSelectBlockRefArray[this.codeSelectSelectedIndex.get()];
|
||||
let codeText = curBlockRef.current.innerText;
|
||||
codeText = codeText.replace(/\n$/, ""); // remove trailing newline
|
||||
this.setCurLine(codeText);
|
||||
this.giveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
addCodeBlockToCodeSelect(blockRef: React.RefObject<HTMLElement>): number {
|
||||
let rtn = -1;
|
||||
rtn = this.codeSelectBlockRefArray.length;
|
||||
this.codeSelectBlockRefArray.push(blockRef);
|
||||
return rtn;
|
||||
}
|
||||
|
||||
setCodeSelectSelectedCodeBlock(blockIndex: number) {
|
||||
mobx.action(() => {
|
||||
if (blockIndex >= 0 && blockIndex < this.codeSelectBlockRefArray.length) {
|
||||
this.codeSelectSelectedIndex.set(blockIndex);
|
||||
let currentRef = this.codeSelectBlockRefArray[blockIndex].current;
|
||||
if (currentRef != null) {
|
||||
if (this.aiChatWindowRef?.current != null) {
|
||||
let chatWindowTop = this.aiChatWindowRef.current.scrollTop;
|
||||
let chatWindowBottom = chatWindowTop + this.aiChatWindowRef.current.clientHeight - 100;
|
||||
let elemTop = currentRef.offsetTop;
|
||||
let elemBottom = elemTop - currentRef.offsetHeight;
|
||||
let elementIsInView = elemBottom < chatWindowBottom && elemTop > chatWindowTop;
|
||||
if (!elementIsInView) {
|
||||
this.aiChatWindowRef.current.scrollTop =
|
||||
elemBottom - this.aiChatWindowRef.current.clientHeight / 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.codeSelectBlockRefArray = [];
|
||||
this.setAIChatFocus();
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
codeSelectSelectNextNewestCodeBlock() {
|
||||
// oldest code block = index 0 in array
|
||||
// this decrements codeSelectSelected index
|
||||
mobx.action(() => {
|
||||
if (this.codeSelectSelectedIndex.get() == this.codeSelectTop) {
|
||||
this.codeSelectSelectedIndex.set(this.codeSelectBottom);
|
||||
} else if (this.codeSelectSelectedIndex.get() == this.codeSelectBottom) {
|
||||
return;
|
||||
}
|
||||
let incBlockIndex = this.codeSelectSelectedIndex.get() + 1;
|
||||
if (this.codeSelectSelectedIndex.get() == this.codeSelectBlockRefArray.length - 1) {
|
||||
this.codeSelectDeselectAll();
|
||||
if (this.aiChatWindowRef?.current != null) {
|
||||
this.aiChatWindowRef.current.scrollTop = this.aiChatWindowRef.current.scrollHeight;
|
||||
}
|
||||
}
|
||||
if (incBlockIndex >= 0 && incBlockIndex < this.codeSelectBlockRefArray.length) {
|
||||
this.setCodeSelectSelectedCodeBlock(incBlockIndex);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
codeSelectSelectNextOldestCodeBlock() {
|
||||
mobx.action(() => {
|
||||
if (this.codeSelectSelectedIndex.get() == this.codeSelectBottom) {
|
||||
if (this.codeSelectBlockRefArray.length > 0) {
|
||||
this.codeSelectSelectedIndex.set(this.codeSelectBlockRefArray.length);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else if (this.codeSelectSelectedIndex.get() == this.codeSelectTop) {
|
||||
return;
|
||||
}
|
||||
let decBlockIndex = this.codeSelectSelectedIndex.get() - 1;
|
||||
if (decBlockIndex < 0) {
|
||||
this.codeSelectDeselectAll(this.codeSelectTop);
|
||||
if (this.aiChatWindowRef?.current != null) {
|
||||
this.aiChatWindowRef.current.scrollTop = 0;
|
||||
}
|
||||
}
|
||||
if (decBlockIndex >= 0 && decBlockIndex < this.codeSelectBlockRefArray.length) {
|
||||
this.setCodeSelectSelectedCodeBlock(decBlockIndex);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
getCodeSelectSelectedIndex() {
|
||||
return this.codeSelectSelectedIndex.get();
|
||||
}
|
||||
|
||||
getCodeSelectRefArrayLength() {
|
||||
return this.codeSelectBlockRefArray.length;
|
||||
}
|
||||
|
||||
codeBlockIsSelected(blockIndex: number): boolean {
|
||||
return blockIndex == this.codeSelectSelectedIndex.get();
|
||||
}
|
||||
|
||||
codeSelectDeselectAll(direction: number = this.codeSelectBottom) {
|
||||
mobx.action(() => {
|
||||
this.codeSelectSelectedIndex.set(direction);
|
||||
this.codeSelectBlockRefArray = [];
|
||||
})();
|
||||
}
|
||||
|
||||
openAIAssistantChat(): void {
|
||||
this.aIChatShow.set(true);
|
||||
this.setAIChatFocus();
|
||||
}
|
||||
|
||||
closeAIAssistantChat(): void {
|
||||
this.aIChatShow.set(false);
|
||||
this.giveFocus();
|
||||
}
|
||||
|
||||
clearAIAssistantChat(): void {
|
||||
let prtn = this.globalModel.submitChatInfoCommand("", "", true);
|
||||
prtn.then((rtn) => {
|
||||
if (!rtn.success) {
|
||||
console.log("submit chat command error: " + rtn.error);
|
||||
}
|
||||
}).catch((error) => {
|
||||
console.log("submit chat command error: ", error);
|
||||
});
|
||||
}
|
||||
|
||||
hasScrollingInfoMsg(): boolean {
|
||||
if (!this.infoShow.get()) {
|
||||
return false;
|
||||
}
|
||||
let info = this.infoMsg.get();
|
||||
if (info == null) {
|
||||
return false;
|
||||
}
|
||||
let div = document.querySelector(".cmd-input-info");
|
||||
if (div == null) {
|
||||
return false;
|
||||
}
|
||||
return div.scrollHeight > div.clientHeight;
|
||||
}
|
||||
|
||||
_clearInfoTimeout(): void {
|
||||
if (this.infoTimeoutId != null) {
|
||||
clearTimeout(this.infoTimeoutId);
|
||||
this.infoTimeoutId = null;
|
||||
}
|
||||
}
|
||||
|
||||
clearInfoMsg(setNull: boolean): void {
|
||||
this._clearInfoTimeout();
|
||||
mobx.action(() => {
|
||||
this.setHistoryShow(false);
|
||||
this.infoShow.set(false);
|
||||
if (setNull) {
|
||||
this.infoMsg.set(null);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
toggleInfoMsg(): void {
|
||||
this._clearInfoTimeout();
|
||||
mobx.action(() => {
|
||||
if (this.historyShow.get()) {
|
||||
this.setHistoryShow(false);
|
||||
return;
|
||||
}
|
||||
let isShowing = this.infoShow.get();
|
||||
if (isShowing) {
|
||||
this.infoShow.set(false);
|
||||
} else {
|
||||
if (this.infoMsg.get() != null) {
|
||||
this.infoShow.set(true);
|
||||
}
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
uiSubmitCommand(): void {
|
||||
mobx.action(() => {
|
||||
let commandStr = this.getCurLine();
|
||||
if (commandStr.trim() == "") {
|
||||
return;
|
||||
}
|
||||
this.resetInput();
|
||||
this.globalModel.submitRawCommand(commandStr, true, true);
|
||||
})();
|
||||
}
|
||||
|
||||
isEmpty(): boolean {
|
||||
return this.getCurLine().trim() == "";
|
||||
}
|
||||
|
||||
resetInputMode(): void {
|
||||
mobx.action(() => {
|
||||
this.setInputMode(null);
|
||||
this.setCurLine("");
|
||||
})();
|
||||
}
|
||||
|
||||
setCurLine(val: string): void {
|
||||
let hidx = this.historyIndex.get();
|
||||
mobx.action(() => {
|
||||
// if (val == "\" ") {
|
||||
// this.setInputMode("comment");
|
||||
// val = "";
|
||||
// }
|
||||
// if (val == "//") {
|
||||
// this.setInputMode("global");
|
||||
// val = "";
|
||||
// }
|
||||
if (this.modHistory.length <= hidx) {
|
||||
this.modHistory.length = hidx + 1;
|
||||
}
|
||||
this.modHistory[hidx] = val;
|
||||
})();
|
||||
}
|
||||
|
||||
resetInput(): void {
|
||||
mobx.action(() => {
|
||||
this.setHistoryShow(false);
|
||||
this.closeAIAssistantChat();
|
||||
this.infoShow.set(false);
|
||||
this.inputMode.set(null);
|
||||
this.resetHistory();
|
||||
this.dropModHistory(false);
|
||||
this.infoMsg.set(null);
|
||||
this.inputExpanded.set(false);
|
||||
this._clearInfoTimeout();
|
||||
})();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
toggleExpandInput(): void {
|
||||
mobx.action(() => {
|
||||
this.inputExpanded.set(!this.inputExpanded.get());
|
||||
this.forceInputFocus = true;
|
||||
})();
|
||||
}
|
||||
|
||||
getCurLine(): string {
|
||||
let hidx = this.historyIndex.get();
|
||||
if (hidx < this.modHistory.length && this.modHistory[hidx] != null) {
|
||||
return this.modHistory[hidx];
|
||||
}
|
||||
let hitems = this.getFilteredHistoryItems();
|
||||
if (hidx == 0 || hitems == null || hidx > hitems.length) {
|
||||
return "";
|
||||
}
|
||||
let hitem = hitems[hidx - 1];
|
||||
if (hitem == null) {
|
||||
return "";
|
||||
}
|
||||
return hitem.cmdstr;
|
||||
}
|
||||
|
||||
dropModHistory(keepLine0: boolean): void {
|
||||
mobx.action(() => {
|
||||
if (keepLine0) {
|
||||
if (this.modHistory.length > 1) {
|
||||
this.modHistory.splice(1, this.modHistory.length - 1);
|
||||
}
|
||||
} else {
|
||||
this.modHistory.replace([""]);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
resetHistory(): void {
|
||||
mobx.action(() => {
|
||||
this.setHistoryShow(false);
|
||||
this.historyLoading.set(false);
|
||||
this.historyType.set("screen");
|
||||
this.historyItems.set(null);
|
||||
this.historyIndex.set(0);
|
||||
this.historyQueryOpts.set(getDefaultHistoryQueryOpts());
|
||||
this.historyAfterLoadIndex = 0;
|
||||
this.dropModHistory(true);
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
export { InputModel };
|
85
src/models/mainsidebar.ts
Normal file
85
src/models/mainsidebar.ts
Normal file
@ -0,0 +1,85 @@
|
||||
// Copyright 2023, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import * as mobx from "mobx";
|
||||
import { MagicLayout } from "../app/magiclayout";
|
||||
import { OV } from "../types/types";
|
||||
import { Model } from "./model";
|
||||
class MainSidebarModel {
|
||||
globalModel: Model = null;
|
||||
tempWidth: OV<number> = mobx.observable.box(null, {
|
||||
name: "MainSidebarModel-tempWidth",
|
||||
});
|
||||
tempCollapsed: OV<boolean> = mobx.observable.box(null, {
|
||||
name: "MainSidebarModel-tempCollapsed",
|
||||
});
|
||||
isDragging: OV<boolean> = mobx.observable.box(false, {
|
||||
name: "MainSidebarModel-isDragging",
|
||||
});
|
||||
|
||||
constructor(globalModel: Model) {
|
||||
this.globalModel = globalModel;
|
||||
}
|
||||
|
||||
setTempWidthAndTempCollapsed(newWidth: number, newCollapsed: boolean): void {
|
||||
const width = Math.max(MagicLayout.MainSidebarMinWidth, Math.min(newWidth, MagicLayout.MainSidebarMaxWidth));
|
||||
|
||||
mobx.action(() => {
|
||||
this.tempWidth.set(width);
|
||||
this.tempCollapsed.set(newCollapsed);
|
||||
})();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the intended width for the sidebar. If the sidebar is being dragged, returns the tempWidth. If the sidebar is collapsed, returns the default width.
|
||||
* @param ignoreCollapse If true, returns the persisted width even if the sidebar is collapsed.
|
||||
* @returns The intended width for the sidebar or the default width if the sidebar is collapsed. Can be overridden using ignoreCollapse.
|
||||
*/
|
||||
getWidth(ignoreCollapse: boolean = false): number {
|
||||
const clientData = this.globalModel.clientData.get();
|
||||
let width = clientData?.clientopts?.mainsidebar?.width ?? MagicLayout.MainSidebarDefaultWidth;
|
||||
if (this.isDragging.get()) {
|
||||
if (this.tempWidth.get() == null && width == null) {
|
||||
return MagicLayout.MainSidebarDefaultWidth;
|
||||
}
|
||||
if (this.tempWidth.get() == null) {
|
||||
return width;
|
||||
}
|
||||
return this.tempWidth.get();
|
||||
}
|
||||
// Set by CLI and collapsed
|
||||
if (this.getCollapsed()) {
|
||||
if (ignoreCollapse) {
|
||||
return width;
|
||||
} else {
|
||||
return MagicLayout.MainSidebarMinWidth;
|
||||
}
|
||||
} else {
|
||||
if (width <= MagicLayout.MainSidebarMinWidth) {
|
||||
width = MagicLayout.MainSidebarDefaultWidth;
|
||||
}
|
||||
const snapPoint = MagicLayout.MainSidebarMinWidth + MagicLayout.MainSidebarSnapThreshold;
|
||||
if (width < snapPoint || width > MagicLayout.MainSidebarMaxWidth) {
|
||||
width = MagicLayout.MainSidebarDefaultWidth;
|
||||
}
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
getCollapsed(): boolean {
|
||||
const clientData = this.globalModel.clientData.get();
|
||||
const collapsed = clientData?.clientopts?.mainsidebar?.collapsed;
|
||||
if (this.isDragging.get()) {
|
||||
if (this.tempCollapsed.get() == null && collapsed == null) {
|
||||
return false;
|
||||
}
|
||||
if (this.tempCollapsed.get() == null) {
|
||||
return collapsed;
|
||||
}
|
||||
return this.tempCollapsed.get();
|
||||
}
|
||||
return collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
export { MainSidebarModel };
|
30
src/models/modals.ts
Normal file
30
src/models/modals.ts
Normal file
@ -0,0 +1,30 @@
|
||||
// Copyright 2023, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import * as mobx from "mobx";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { ModalStoreEntry } from "../types/types";
|
||||
import { modalsRegistry } from "../app/common/modals/registry";
|
||||
import { OArr } from "../types/types";
|
||||
|
||||
class ModalsModel {
|
||||
store: OArr<ModalStoreEntry> = mobx.observable.array([], { name: "ModalsModel-store", deep: false });
|
||||
|
||||
pushModal(modalId: string, props?: any) {
|
||||
const modalFactory = modalsRegistry[modalId];
|
||||
|
||||
if (modalFactory && !this.store.some((modal) => modal.id === modalId)) {
|
||||
mobx.action(() => {
|
||||
this.store.push({ id: modalId, component: modalFactory, uniqueKey: uuidv4(), props });
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
popModal() {
|
||||
mobx.action(() => {
|
||||
this.store.pop();
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
export { ModalsModel };
|
1430
src/models/model.ts
Normal file
1430
src/models/model.ts
Normal file
File diff suppressed because it is too large
Load Diff
47
src/models/plugins.ts
Normal file
47
src/models/plugins.ts
Normal file
@ -0,0 +1,47 @@
|
||||
// Copyright 2023, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import * as mobx from "mobx";
|
||||
import { PluginModel } from "../plugins/plugins";
|
||||
import { RendererPluginType } from "../types/types";
|
||||
import { OV } from "../types/types";
|
||||
import { GlobalCommandRunner } from "./global";
|
||||
import { Model } from "./model";
|
||||
|
||||
class PluginsModel {
|
||||
globalModel: Model = null;
|
||||
selectedPlugin: OV<RendererPluginType> = mobx.observable.box(null, { name: "selectedPlugin" });
|
||||
|
||||
constructor(globalModel: Model) {
|
||||
this.globalModel = globalModel;
|
||||
}
|
||||
|
||||
showPluginsView(): void {
|
||||
PluginModel.loadAllPluginResources();
|
||||
mobx.action(() => {
|
||||
this.reset();
|
||||
this.globalModel.activeMainView.set("plugins");
|
||||
const allPlugins = PluginModel.allPlugins();
|
||||
this.selectedPlugin.set(allPlugins.length > 0 ? allPlugins[0] : null);
|
||||
})();
|
||||
}
|
||||
|
||||
setSelectedPlugin(plugin: RendererPluginType): void {
|
||||
mobx.action(() => {
|
||||
this.selectedPlugin.set(plugin);
|
||||
})();
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
mobx.action(() => {
|
||||
this.selectedPlugin.set(null);
|
||||
})();
|
||||
}
|
||||
|
||||
closeView(): void {
|
||||
this.globalModel.showSessionView();
|
||||
setTimeout(() => this.globalModel.inputModel.giveFocus(), 50);
|
||||
}
|
||||
}
|
||||
|
||||
export { PluginsModel };
|
206
src/models/remotes.ts
Normal file
206
src/models/remotes.ts
Normal file
@ -0,0 +1,206 @@
|
||||
// Copyright 2023, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import * as mobx from "mobx";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { stringToBase64 } from "../util/util";
|
||||
import { TermWrap } from "../plugins/terminal/term";
|
||||
import { RemoteInputPacketType, RemoteEditType } from "../types/types";
|
||||
import * as appconst from "../app/appconst";
|
||||
import { OV } from "../types/types";
|
||||
import { GlobalCommandRunner } from "./global";
|
||||
import { Model } from "./model";
|
||||
import { getTermPtyData } from "../util/modelutil";
|
||||
|
||||
const RemotePtyRows = 8; // also in main.tsx
|
||||
const RemotePtyCols = 80;
|
||||
|
||||
class RemotesModel {
|
||||
globalModel: Model;
|
||||
selectedRemoteId: OV<string> = mobx.observable.box(null, {
|
||||
name: "RemotesModel-selectedRemoteId",
|
||||
});
|
||||
remoteTermWrap: TermWrap = null;
|
||||
remoteTermWrapFocus: OV<boolean> = mobx.observable.box(false, {
|
||||
name: "RemotesModel-remoteTermWrapFocus",
|
||||
});
|
||||
showNoInputMsg: OV<boolean> = mobx.observable.box(false, {
|
||||
name: "RemotesModel-showNoInputMg",
|
||||
});
|
||||
showNoInputTimeoutId: any = null;
|
||||
remoteEdit: OV<RemoteEditType> = mobx.observable.box(null, {
|
||||
name: "RemotesModel-remoteEdit",
|
||||
});
|
||||
recentConnAddedState: OV<boolean> = mobx.observable.box(false, {
|
||||
name: "RemotesModel-recentlyAdded",
|
||||
});
|
||||
|
||||
constructor(globalModel: Model) {
|
||||
this.globalModel = globalModel;
|
||||
}
|
||||
|
||||
get recentConnAdded(): boolean {
|
||||
return this.recentConnAddedState.get();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
setRecentConnAdded(value: boolean) {
|
||||
mobx.action(() => {
|
||||
this.recentConnAddedState.set(value);
|
||||
})();
|
||||
}
|
||||
|
||||
deSelectRemote(): void {
|
||||
mobx.action(() => {
|
||||
this.selectedRemoteId.set(null);
|
||||
this.remoteEdit.set(null);
|
||||
})();
|
||||
}
|
||||
|
||||
openReadModal(remoteId: string): void {
|
||||
mobx.action(() => {
|
||||
this.setRecentConnAdded(false);
|
||||
this.selectedRemoteId.set(remoteId);
|
||||
this.remoteEdit.set(null);
|
||||
this.globalModel.modalsModel.pushModal(appconst.VIEW_REMOTE);
|
||||
})();
|
||||
}
|
||||
|
||||
openAddModal(redit: RemoteEditType): void {
|
||||
mobx.action(() => {
|
||||
this.remoteEdit.set(redit);
|
||||
this.globalModel.modalsModel.pushModal(appconst.CREATE_REMOTE);
|
||||
})();
|
||||
}
|
||||
|
||||
openEditModal(redit?: RemoteEditType): void {
|
||||
mobx.action(() => {
|
||||
this.selectedRemoteId.set(redit?.remoteid);
|
||||
this.remoteEdit.set(redit);
|
||||
this.globalModel.modalsModel.pushModal(appconst.EDIT_REMOTE);
|
||||
})();
|
||||
}
|
||||
|
||||
selectRemote(remoteId: string): void {
|
||||
if (this.selectedRemoteId.get() == remoteId) {
|
||||
return;
|
||||
}
|
||||
mobx.action(() => {
|
||||
this.selectedRemoteId.set(remoteId);
|
||||
this.remoteEdit.set(null);
|
||||
})();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
startEditAuth(): void {
|
||||
let remoteId = this.selectedRemoteId.get();
|
||||
if (remoteId != null) {
|
||||
GlobalCommandRunner.openEditRemote(remoteId);
|
||||
}
|
||||
}
|
||||
|
||||
isAuthEditMode(): boolean {
|
||||
return this.remoteEdit.get() != null;
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
closeModal(): void {
|
||||
mobx.action(() => {
|
||||
this.globalModel.modalsModel.popModal();
|
||||
})();
|
||||
setTimeout(() => this.globalModel.refocus(), 10);
|
||||
}
|
||||
|
||||
disposeTerm(): void {
|
||||
if (this.remoteTermWrap == null) {
|
||||
return;
|
||||
}
|
||||
this.remoteTermWrap.dispose();
|
||||
this.remoteTermWrap = null;
|
||||
mobx.action(() => {
|
||||
this.remoteTermWrapFocus.set(false);
|
||||
})();
|
||||
}
|
||||
|
||||
receiveData(remoteId: string, ptyPos: number, ptyData: Uint8Array, reason?: string) {
|
||||
if (this.remoteTermWrap == null) {
|
||||
return;
|
||||
}
|
||||
if (this.remoteTermWrap.getContextRemoteId() != remoteId) {
|
||||
return;
|
||||
}
|
||||
this.remoteTermWrap.receiveData(ptyPos, ptyData);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
setRemoteTermWrapFocus(focus: boolean): void {
|
||||
mobx.action(() => {
|
||||
this.remoteTermWrapFocus.set(focus);
|
||||
})();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
setShowNoInputMsg(val: boolean) {
|
||||
mobx.action(() => {
|
||||
if (this.showNoInputTimeoutId != null) {
|
||||
clearTimeout(this.showNoInputTimeoutId);
|
||||
this.showNoInputTimeoutId = null;
|
||||
}
|
||||
if (val) {
|
||||
this.showNoInputMsg.set(true);
|
||||
this.showNoInputTimeoutId = setTimeout(() => this.setShowNoInputMsg(false), 2000);
|
||||
} else {
|
||||
this.showNoInputMsg.set(false);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
termKeyHandler(remoteId: string, event: any, termWrap: TermWrap): void {
|
||||
let remote = this.globalModel.getRemote(remoteId);
|
||||
if (remote == null) {
|
||||
return;
|
||||
}
|
||||
if (remote.status != "connecting" && remote.installstatus != "connecting") {
|
||||
this.setShowNoInputMsg(true);
|
||||
return;
|
||||
}
|
||||
let inputPacket: RemoteInputPacketType = {
|
||||
type: "remoteinput",
|
||||
remoteid: remoteId,
|
||||
inputdata64: stringToBase64(event.key),
|
||||
};
|
||||
this.globalModel.sendInputPacket(inputPacket);
|
||||
}
|
||||
|
||||
createTermWrap(elem: HTMLElement): void {
|
||||
this.disposeTerm();
|
||||
let remoteId = this.selectedRemoteId.get();
|
||||
if (remoteId == null) {
|
||||
return;
|
||||
}
|
||||
let termOpts = {
|
||||
rows: RemotePtyRows,
|
||||
cols: RemotePtyCols,
|
||||
flexrows: false,
|
||||
maxptysize: 64 * 1024,
|
||||
};
|
||||
let termWrap = new TermWrap(elem, {
|
||||
termContext: { remoteId: remoteId },
|
||||
usedRows: RemotePtyRows,
|
||||
termOpts: termOpts,
|
||||
winSize: null,
|
||||
keyHandler: (e, termWrap) => {
|
||||
this.termKeyHandler(remoteId, e, termWrap);
|
||||
},
|
||||
focusHandler: this.setRemoteTermWrapFocus.bind(this),
|
||||
isRunning: true,
|
||||
fontSize: this.globalModel.termFontSize.get(),
|
||||
ptyDataSource: getTermPtyData,
|
||||
onUpdateContentHeight: null,
|
||||
});
|
||||
this.remoteTermWrap = termWrap;
|
||||
}
|
||||
}
|
||||
|
||||
export { RemotesModel };
|
697
src/models/screen.ts
Normal file
697
src/models/screen.ts
Normal file
@ -0,0 +1,697 @@
|
||||
// Copyright 2023, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import * as mobx from "mobx";
|
||||
import { sprintf } from "sprintf-js";
|
||||
import { debounce } from "throttle-debounce";
|
||||
import { base64ToArray, boundInt, isModKeyPress, isBlank } from "../util/util";
|
||||
import { TermWrap } from "../plugins/terminal/term";
|
||||
import {
|
||||
LineType,
|
||||
RemoteInstanceType,
|
||||
RemotePtrType,
|
||||
ScreenDataType,
|
||||
ScreenOptsType,
|
||||
PtyDataUpdateType,
|
||||
RendererContext,
|
||||
RendererModel,
|
||||
FocusTypeStrs,
|
||||
WindowSize,
|
||||
WebShareOpts,
|
||||
StatusIndicatorLevel,
|
||||
LineContainerStrs,
|
||||
ScreenViewOptsType,
|
||||
} from "../types/types";
|
||||
import { windowWidthToCols, windowHeightToRows, termWidthFromCols, termHeightFromRows } from "../util/textmeasure";
|
||||
import { getRendererContext } from "../app/line/lineutil";
|
||||
import { MagicLayout } from "../app/magiclayout";
|
||||
import * as appconst from "../app/appconst";
|
||||
import { checkKeyPressed, adaptFromReactOrNativeKeyEvent } from "../util/keyutil";
|
||||
import { OV } from "../types/types";
|
||||
import { Model } from "./model";
|
||||
import { GlobalCommandRunner } from "./global";
|
||||
import { Cmd } from "./cmd";
|
||||
import { ScreenLines } from "./screenlines";
|
||||
import { getTermPtyData } from "../util/modelutil";
|
||||
|
||||
class Screen {
|
||||
globalModel: Model;
|
||||
sessionId: string;
|
||||
screenId: string;
|
||||
screenIdx: OV<number>;
|
||||
opts: OV<ScreenOptsType>;
|
||||
viewOpts: OV<ScreenViewOptsType>;
|
||||
name: OV<string>;
|
||||
archived: OV<boolean>;
|
||||
curRemote: OV<RemotePtrType>;
|
||||
nextLineNum: OV<number>;
|
||||
lastScreenSize: WindowSize;
|
||||
lastCols: number;
|
||||
lastRows: number;
|
||||
selectedLine: OV<number>;
|
||||
focusType: OV<FocusTypeStrs>;
|
||||
anchor: OV<{ anchorLine: number; anchorOffset: number }>;
|
||||
termLineNumFocus: OV<number>;
|
||||
setAnchor_debounced: (anchorLine: number, anchorOffset: number) => void;
|
||||
terminals: Record<string, TermWrap> = {}; // lineid => TermWrap
|
||||
renderers: Record<string, RendererModel> = {}; // lineid => RendererModel
|
||||
shareMode: OV<string>;
|
||||
webShareOpts: OV<WebShareOpts>;
|
||||
filterRunning: OV<boolean>;
|
||||
statusIndicator: OV<StatusIndicatorLevel>;
|
||||
numRunningCmds: OV<number>;
|
||||
|
||||
constructor(sdata: ScreenDataType, globalModel: Model) {
|
||||
this.globalModel = globalModel;
|
||||
this.sessionId = sdata.sessionid;
|
||||
this.screenId = sdata.screenid;
|
||||
this.name = mobx.observable.box(sdata.name, { name: "screen-name" });
|
||||
this.nextLineNum = mobx.observable.box(sdata.nextlinenum, { name: "screen-nextlinenum" });
|
||||
this.screenIdx = mobx.observable.box(sdata.screenidx, {
|
||||
name: "screen-screenidx",
|
||||
});
|
||||
this.opts = mobx.observable.box(sdata.screenopts, { name: "screen-opts" });
|
||||
this.viewOpts = mobx.observable.box(sdata.screenviewopts, { name: "viewOpts" });
|
||||
this.archived = mobx.observable.box(!!sdata.archived, {
|
||||
name: "screen-archived",
|
||||
});
|
||||
this.focusType = mobx.observable.box(sdata.focustype, {
|
||||
name: "focusType",
|
||||
});
|
||||
this.selectedLine = mobx.observable.box(sdata.selectedline == 0 ? null : sdata.selectedline, {
|
||||
name: "selectedLine",
|
||||
});
|
||||
this.setAnchor_debounced = debounce(1000, this.setAnchor.bind(this));
|
||||
this.anchor = mobx.observable.box(
|
||||
{ anchorLine: sdata.selectedline, anchorOffset: 0 },
|
||||
{ name: "screen-anchor" }
|
||||
);
|
||||
this.termLineNumFocus = mobx.observable.box(0, {
|
||||
name: "termLineNumFocus",
|
||||
});
|
||||
this.curRemote = mobx.observable.box(sdata.curremote, {
|
||||
name: "screen-curRemote",
|
||||
});
|
||||
this.shareMode = mobx.observable.box(sdata.sharemode, {
|
||||
name: "screen-shareMode",
|
||||
});
|
||||
this.webShareOpts = mobx.observable.box(sdata.webshareopts, {
|
||||
name: "screen-webShareOpts",
|
||||
});
|
||||
this.filterRunning = mobx.observable.box(false, {
|
||||
name: "screen-filter-running",
|
||||
});
|
||||
this.statusIndicator = mobx.observable.box(StatusIndicatorLevel.None, {
|
||||
name: "screen-status-indicator",
|
||||
});
|
||||
this.numRunningCmds = mobx.observable.box(0, {
|
||||
name: "screen-num-running-cmds",
|
||||
});
|
||||
}
|
||||
|
||||
dispose() {}
|
||||
|
||||
isWebShared(): boolean {
|
||||
return this.shareMode.get() == "web" && this.webShareOpts.get() != null;
|
||||
}
|
||||
|
||||
isSidebarOpen(): boolean {
|
||||
let viewOpts = this.viewOpts.get();
|
||||
if (viewOpts == null) {
|
||||
return false;
|
||||
}
|
||||
return viewOpts.sidebar?.open;
|
||||
}
|
||||
|
||||
isLineIdInSidebar(lineId: string): boolean {
|
||||
let viewOpts = this.viewOpts.get();
|
||||
if (viewOpts == null) {
|
||||
return false;
|
||||
}
|
||||
if (!viewOpts.sidebar?.open) {
|
||||
return false;
|
||||
}
|
||||
return viewOpts?.sidebar?.sidebarlineid == lineId;
|
||||
}
|
||||
|
||||
getContainerType(): LineContainerStrs {
|
||||
return appconst.LineContainer_Main;
|
||||
}
|
||||
|
||||
getShareName(): string {
|
||||
if (!this.isWebShared()) {
|
||||
return null;
|
||||
}
|
||||
let opts = this.webShareOpts.get();
|
||||
if (opts == null) {
|
||||
return null;
|
||||
}
|
||||
return opts.sharename;
|
||||
}
|
||||
|
||||
getWebShareUrl(): string {
|
||||
let viewKey: string = null;
|
||||
if (this.webShareOpts.get() != null) {
|
||||
viewKey = this.webShareOpts.get().viewkey;
|
||||
}
|
||||
if (viewKey == null) {
|
||||
return null;
|
||||
}
|
||||
if (this.globalModel.isDev) {
|
||||
return sprintf(
|
||||
"http://devtest.getprompt.com:9001/static/index-dev.html?screenid=%s&viewkey=%s",
|
||||
this.screenId,
|
||||
viewKey
|
||||
);
|
||||
}
|
||||
return sprintf("https://share.getprompt.dev/share/%s?viewkey=%s", this.screenId, viewKey);
|
||||
}
|
||||
|
||||
mergeData(data: ScreenDataType) {
|
||||
if (data.sessionid != this.sessionId || data.screenid != this.screenId) {
|
||||
throw new Error("invalid screen update, ids don't match");
|
||||
}
|
||||
mobx.action(() => {
|
||||
this.screenIdx.set(data.screenidx);
|
||||
this.opts.set(data.screenopts);
|
||||
this.viewOpts.set(data.screenviewopts);
|
||||
this.name.set(data.name);
|
||||
this.nextLineNum.set(data.nextlinenum);
|
||||
this.archived.set(!!data.archived);
|
||||
let oldSelectedLine = this.selectedLine.get();
|
||||
let oldFocusType = this.focusType.get();
|
||||
this.selectedLine.set(data.selectedline);
|
||||
this.curRemote.set(data.curremote);
|
||||
this.focusType.set(data.focustype);
|
||||
this.refocusLine(data, oldFocusType, oldSelectedLine);
|
||||
this.shareMode.set(data.sharemode);
|
||||
this.webShareOpts.set(data.webshareopts);
|
||||
// do not update anchorLine/anchorOffset (only stored)
|
||||
})();
|
||||
}
|
||||
|
||||
getContentHeight(context: RendererContext): number {
|
||||
return this.globalModel.getContentHeight(context);
|
||||
}
|
||||
|
||||
setContentHeight(context: RendererContext, height: number): void {
|
||||
this.globalModel.setContentHeight(context, height);
|
||||
}
|
||||
|
||||
getCmd(line: LineType): Cmd {
|
||||
return this.globalModel.getCmd(line);
|
||||
}
|
||||
|
||||
getCmdById(lineId: string): Cmd {
|
||||
return this.globalModel.getCmdByScreenLine(this.screenId, lineId);
|
||||
}
|
||||
|
||||
getAnchorStr(): string {
|
||||
let anchor = this.anchor.get();
|
||||
if (anchor.anchorLine == null || anchor.anchorLine == 0) {
|
||||
return "0";
|
||||
}
|
||||
return sprintf("%d:%d", anchor.anchorLine, anchor.anchorOffset);
|
||||
}
|
||||
|
||||
getTabColor(): string {
|
||||
let tabColor = "default";
|
||||
let screenOpts = this.opts.get();
|
||||
if (screenOpts != null && !isBlank(screenOpts.tabcolor)) {
|
||||
tabColor = screenOpts.tabcolor;
|
||||
}
|
||||
return tabColor;
|
||||
}
|
||||
|
||||
getTabIcon(): string {
|
||||
let tabIcon = "default";
|
||||
let screenOpts = this.opts.get();
|
||||
if (screenOpts != null && !isBlank(screenOpts.tabicon)) {
|
||||
tabIcon = screenOpts.tabicon;
|
||||
}
|
||||
return tabIcon;
|
||||
}
|
||||
|
||||
getCurRemoteInstance(): RemoteInstanceType {
|
||||
let session = this.globalModel.getSessionById(this.sessionId);
|
||||
let rptr = this.curRemote.get();
|
||||
if (rptr == null) {
|
||||
return null;
|
||||
}
|
||||
return session.getRemoteInstance(this.screenId, rptr);
|
||||
}
|
||||
|
||||
setAnchorFields(anchorLine: number, anchorOffset: number, reason: string): void {
|
||||
mobx.action(() => {
|
||||
this.anchor.set({ anchorLine: anchorLine, anchorOffset: anchorOffset });
|
||||
})();
|
||||
}
|
||||
|
||||
refocusLine(sdata: ScreenDataType, oldFocusType: string, oldSelectedLine: number): void {
|
||||
let isCmdFocus = sdata.focustype == "cmd";
|
||||
if (!isCmdFocus) {
|
||||
return;
|
||||
}
|
||||
let curLineFocus = this.globalModel.getFocusedLine();
|
||||
let sline: LineType = null;
|
||||
if (sdata.selectedline != 0) {
|
||||
sline = this.getLineByNum(sdata.selectedline);
|
||||
}
|
||||
if (
|
||||
curLineFocus.cmdInputFocus ||
|
||||
(curLineFocus.linenum != null && curLineFocus.linenum != sdata.selectedline)
|
||||
) {
|
||||
(document.activeElement as HTMLElement).blur();
|
||||
}
|
||||
if (sline != null) {
|
||||
let renderer = this.getRenderer(sline.lineid);
|
||||
if (renderer != null) {
|
||||
renderer.giveFocus();
|
||||
}
|
||||
let termWrap = this.getTermWrap(sline.lineid);
|
||||
if (termWrap != null) {
|
||||
termWrap.giveFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setFocusType(ftype: FocusTypeStrs): void {
|
||||
mobx.action(() => {
|
||||
this.focusType.set(ftype);
|
||||
})();
|
||||
}
|
||||
|
||||
setAnchor(anchorLine: number, anchorOffset: number): void {
|
||||
let setVal = anchorLine == null || anchorLine == 0 ? "0" : sprintf("%d:%d", anchorLine, anchorOffset);
|
||||
GlobalCommandRunner.screenSetAnchor(this.sessionId, this.screenId, setVal);
|
||||
}
|
||||
|
||||
getAnchor(): { anchorLine: number; anchorOffset: number } {
|
||||
let anchor = this.anchor.get();
|
||||
if (anchor.anchorLine == null || anchor.anchorLine == 0) {
|
||||
return { anchorLine: this.selectedLine.get(), anchorOffset: 0 };
|
||||
}
|
||||
return anchor;
|
||||
}
|
||||
|
||||
getMaxLineNum(): number {
|
||||
let win = this.getScreenLines();
|
||||
if (win == null) {
|
||||
return null;
|
||||
}
|
||||
let lines = win.lines;
|
||||
if (lines == null || lines.length == 0) {
|
||||
return null;
|
||||
}
|
||||
return lines[lines.length - 1].linenum;
|
||||
}
|
||||
|
||||
getLineByNum(lineNum: number): LineType {
|
||||
if (lineNum == null) {
|
||||
return null;
|
||||
}
|
||||
let win = this.getScreenLines();
|
||||
if (win == null) {
|
||||
return null;
|
||||
}
|
||||
let lines = win.lines;
|
||||
if (lines == null || lines.length == 0) {
|
||||
return null;
|
||||
}
|
||||
for (const line of lines) {
|
||||
if (line.linenum == lineNum) {
|
||||
return line;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getLineById(lineId: string): LineType {
|
||||
if (lineId == null) {
|
||||
return null;
|
||||
}
|
||||
let win = this.getScreenLines();
|
||||
if (win == null) {
|
||||
return null;
|
||||
}
|
||||
let lines = win.lines;
|
||||
if (lines == null || lines.length == 0) {
|
||||
return null;
|
||||
}
|
||||
for (const line of lines) {
|
||||
if (line.lineid == lineId) {
|
||||
return line;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getPresentLineNum(lineNum: number): number {
|
||||
let win = this.getScreenLines();
|
||||
if (win == null || !win.loaded.get()) {
|
||||
return lineNum;
|
||||
}
|
||||
let lines = win.lines;
|
||||
if (lines == null || lines.length == 0) {
|
||||
return null;
|
||||
}
|
||||
if (lineNum == 0) {
|
||||
return null;
|
||||
}
|
||||
for (const line of lines) {
|
||||
if (line.linenum == lineNum) {
|
||||
return lineNum;
|
||||
}
|
||||
if (line.linenum > lineNum) {
|
||||
return line.linenum;
|
||||
}
|
||||
}
|
||||
return lines[lines.length - 1].linenum;
|
||||
}
|
||||
|
||||
setSelectedLine(lineNum: number): void {
|
||||
mobx.action(() => {
|
||||
let pln = this.getPresentLineNum(lineNum);
|
||||
if (pln != this.selectedLine.get()) {
|
||||
this.selectedLine.set(pln);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
checkSelectedLine(): void {
|
||||
let pln = this.getPresentLineNum(this.selectedLine.get());
|
||||
if (pln != this.selectedLine.get()) {
|
||||
this.setSelectedLine(pln);
|
||||
}
|
||||
}
|
||||
|
||||
updatePtyData(ptyMsg: PtyDataUpdateType) {
|
||||
let lineId = ptyMsg.lineid;
|
||||
let renderer = this.renderers[lineId];
|
||||
if (renderer != null) {
|
||||
let data = base64ToArray(ptyMsg.ptydata64);
|
||||
renderer.receiveData(ptyMsg.ptypos, data, "from-sw");
|
||||
}
|
||||
let term = this.terminals[lineId];
|
||||
if (term != null) {
|
||||
let data = base64ToArray(ptyMsg.ptydata64);
|
||||
term.receiveData(ptyMsg.ptypos, data, "from-sw");
|
||||
}
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
let activeScreen = this.globalModel.getActiveScreen();
|
||||
if (activeScreen == null) {
|
||||
return false;
|
||||
}
|
||||
return this.sessionId == activeScreen.sessionId && this.screenId == activeScreen.screenId;
|
||||
}
|
||||
|
||||
screenSizeCallback(winSize: WindowSize): void {
|
||||
if (winSize.height == 0 || winSize.width == 0) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
this.lastScreenSize != null &&
|
||||
this.lastScreenSize.height == winSize.height &&
|
||||
this.lastScreenSize.width == winSize.width
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this.lastScreenSize = winSize;
|
||||
let cols = windowWidthToCols(winSize.width, this.globalModel.termFontSize.get());
|
||||
let rows = windowHeightToRows(winSize.height, this.globalModel.termFontSize.get());
|
||||
this._termSizeCallback(rows, cols);
|
||||
}
|
||||
|
||||
getMaxContentSize(): WindowSize {
|
||||
if (this.lastScreenSize == null) {
|
||||
let width = termWidthFromCols(80, this.globalModel.termFontSize.get());
|
||||
let height = termHeightFromRows(25, this.globalModel.termFontSize.get());
|
||||
return { width, height };
|
||||
}
|
||||
let winSize = this.lastScreenSize;
|
||||
let minSize = MagicLayout.ScreenMinContentSize;
|
||||
let maxSize = MagicLayout.ScreenMaxContentSize;
|
||||
let width = boundInt(winSize.width - MagicLayout.ScreenMaxContentWidthBuffer, minSize, maxSize);
|
||||
let height = boundInt(winSize.height - MagicLayout.ScreenMaxContentHeightBuffer, minSize, maxSize);
|
||||
return { width, height };
|
||||
}
|
||||
|
||||
getIdealContentSize(): WindowSize {
|
||||
if (this.lastScreenSize == null) {
|
||||
let width = termWidthFromCols(80, this.globalModel.termFontSize.get());
|
||||
let height = termHeightFromRows(25, this.globalModel.termFontSize.get());
|
||||
return { width, height };
|
||||
}
|
||||
let winSize = this.lastScreenSize;
|
||||
let width = boundInt(Math.ceil((winSize.width - 50) * 0.7), 100, 5000);
|
||||
let height = boundInt(Math.ceil((winSize.height - 100) * 0.5), 100, 5000);
|
||||
return { width, height };
|
||||
}
|
||||
|
||||
_termSizeCallback(rows: number, cols: number): void {
|
||||
if (cols == 0 || rows == 0) {
|
||||
return;
|
||||
}
|
||||
if (rows == this.lastRows && cols == this.lastCols) {
|
||||
return;
|
||||
}
|
||||
this.lastRows = rows;
|
||||
this.lastCols = cols;
|
||||
let exclude = [];
|
||||
for (let lineid in this.terminals) {
|
||||
let inSidebar = this.isLineIdInSidebar(lineid);
|
||||
if (!inSidebar) {
|
||||
this.terminals[lineid].resizeCols(cols);
|
||||
} else {
|
||||
exclude.push(lineid);
|
||||
}
|
||||
}
|
||||
GlobalCommandRunner.resizeScreen(this.screenId, rows, cols, { exclude });
|
||||
}
|
||||
|
||||
getTermWrap(lineId: string): TermWrap {
|
||||
return this.terminals[lineId];
|
||||
}
|
||||
|
||||
getRenderer(lineId: string): RendererModel {
|
||||
return this.renderers[lineId];
|
||||
}
|
||||
|
||||
registerRenderer(lineId: string, renderer: RendererModel) {
|
||||
this.renderers[lineId] = renderer;
|
||||
}
|
||||
|
||||
setLineFocus(lineNum: number, focus: boolean): void {
|
||||
mobx.action(() => this.termLineNumFocus.set(focus ? lineNum : 0))();
|
||||
if (focus && this.selectedLine.get() != lineNum) {
|
||||
GlobalCommandRunner.screenSelectLine(String(lineNum), "cmd");
|
||||
} else if (focus && this.focusType.get() == "input") {
|
||||
GlobalCommandRunner.screenSetFocus("cmd");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the status indicator for the screen.
|
||||
* @param indicator The value of the status indicator. One of "none", "error", "success", "output".
|
||||
*/
|
||||
setStatusIndicator(indicator: StatusIndicatorLevel): void {
|
||||
mobx.action(() => {
|
||||
this.statusIndicator.set(indicator);
|
||||
})();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the number of running commands for the screen.
|
||||
* @param numRunning The number of running commands.
|
||||
*/
|
||||
setNumRunningCmds(numRunning: number): void {
|
||||
mobx.action(() => {
|
||||
this.numRunningCmds.set(numRunning);
|
||||
})();
|
||||
}
|
||||
|
||||
termCustomKeyHandlerInternal(e: any, termWrap: TermWrap): void {
|
||||
let waveEvent = adaptFromReactOrNativeKeyEvent(e);
|
||||
if (checkKeyPressed(waveEvent, "ArrowUp")) {
|
||||
termWrap.terminal.scrollLines(-1);
|
||||
return;
|
||||
}
|
||||
if (checkKeyPressed(waveEvent, "ArrowDown")) {
|
||||
termWrap.terminal.scrollLines(1);
|
||||
return;
|
||||
}
|
||||
if (checkKeyPressed(waveEvent, "PageUp")) {
|
||||
termWrap.terminal.scrollPages(-1);
|
||||
return;
|
||||
}
|
||||
if (checkKeyPressed(waveEvent, "PageDown")) {
|
||||
termWrap.terminal.scrollPages(1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
isTermCapturedKey(e: any): boolean {
|
||||
let waveEvent = adaptFromReactOrNativeKeyEvent(e);
|
||||
if (
|
||||
checkKeyPressed(waveEvent, "ArrowUp") ||
|
||||
checkKeyPressed(waveEvent, "ArrowDown") ||
|
||||
checkKeyPressed(waveEvent, "PageUp") ||
|
||||
checkKeyPressed(waveEvent, "PageDown")
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
termCustomKeyHandler(e: any, termWrap: TermWrap): boolean {
|
||||
let waveEvent = adaptFromReactOrNativeKeyEvent(e);
|
||||
if (e.type == "keypress" && checkKeyPressed(waveEvent, "Ctrl:Shift:c")) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
let sel = termWrap.terminal.getSelection();
|
||||
navigator.clipboard.writeText(sel);
|
||||
return false;
|
||||
}
|
||||
if (e.type == "keypress" && checkKeyPressed(waveEvent, "Ctrl:Shift:v")) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
let p = navigator.clipboard.readText();
|
||||
p.then((text) => {
|
||||
termWrap.dataHandler?.(text, termWrap);
|
||||
});
|
||||
return false;
|
||||
}
|
||||
if (termWrap.isRunning) {
|
||||
return true;
|
||||
}
|
||||
let isCaptured = this.isTermCapturedKey(e);
|
||||
if (!isCaptured) {
|
||||
return true;
|
||||
}
|
||||
if (e.type != "keydown" || isModKeyPress(e)) {
|
||||
return false;
|
||||
}
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
this.termCustomKeyHandlerInternal(e, termWrap);
|
||||
return false;
|
||||
}
|
||||
|
||||
loadTerminalRenderer(elem: Element, line: LineType, cmd: Cmd, width: number) {
|
||||
let lineId = cmd.lineId;
|
||||
let termWrap = this.getTermWrap(lineId);
|
||||
if (termWrap != null) {
|
||||
console.log("term-wrap already exists for", this.screenId, lineId);
|
||||
return;
|
||||
}
|
||||
let usedRows = this.globalModel.getContentHeight(getRendererContext(line));
|
||||
if (line.contentheight != null && line.contentheight != -1) {
|
||||
usedRows = line.contentheight;
|
||||
}
|
||||
let termContext = {
|
||||
sessionId: this.sessionId,
|
||||
screenId: this.screenId,
|
||||
lineId: line.lineid,
|
||||
lineNum: line.linenum,
|
||||
};
|
||||
termWrap = new TermWrap(elem, {
|
||||
termContext: termContext,
|
||||
usedRows: usedRows,
|
||||
termOpts: cmd.getTermOpts(),
|
||||
winSize: { height: 0, width: width },
|
||||
dataHandler: cmd.handleData.bind(cmd),
|
||||
focusHandler: (focus: boolean) => this.setLineFocus(line.linenum, focus),
|
||||
isRunning: cmd.isRunning(),
|
||||
customKeyHandler: this.termCustomKeyHandler.bind(this),
|
||||
fontSize: this.globalModel.termFontSize.get(),
|
||||
ptyDataSource: getTermPtyData,
|
||||
onUpdateContentHeight: (termContext: RendererContext, height: number) => {
|
||||
this.globalModel.setContentHeight(termContext, height);
|
||||
},
|
||||
});
|
||||
this.terminals[lineId] = termWrap;
|
||||
if (this.focusType.get() == "cmd" && this.selectedLine.get() == line.linenum) {
|
||||
termWrap.giveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
unloadRenderer(lineId: string) {
|
||||
let rmodel = this.renderers[lineId];
|
||||
if (rmodel != null) {
|
||||
rmodel.dispose();
|
||||
delete this.renderers[lineId];
|
||||
}
|
||||
let term = this.terminals[lineId];
|
||||
if (term != null) {
|
||||
term.dispose();
|
||||
delete this.terminals[lineId];
|
||||
}
|
||||
}
|
||||
|
||||
getUsedRows(context: RendererContext, line: LineType, cmd: Cmd, width: number): number {
|
||||
if (cmd == null) {
|
||||
return 0;
|
||||
}
|
||||
let termOpts = cmd.getTermOpts();
|
||||
if (!termOpts.flexrows) {
|
||||
return termOpts.rows;
|
||||
}
|
||||
let termWrap = this.getTermWrap(cmd.lineId);
|
||||
if (termWrap == null) {
|
||||
let usedRows = this.globalModel.getContentHeight(context);
|
||||
if (usedRows != null) {
|
||||
return usedRows;
|
||||
}
|
||||
if (line.contentheight != null && line.contentheight != -1) {
|
||||
return line.contentheight;
|
||||
}
|
||||
return cmd.isRunning() ? 1 : 0;
|
||||
}
|
||||
return termWrap.getUsedRows();
|
||||
}
|
||||
|
||||
getIsFocused(lineNum: number): boolean {
|
||||
return this.termLineNumFocus.get() == lineNum;
|
||||
}
|
||||
|
||||
getSelectedLine(): number {
|
||||
return this.selectedLine.get();
|
||||
}
|
||||
|
||||
getScreenLines(): ScreenLines {
|
||||
return this.globalModel.getScreenLinesById(this.screenId);
|
||||
}
|
||||
|
||||
getFocusType(): FocusTypeStrs {
|
||||
return this.focusType.get();
|
||||
}
|
||||
|
||||
giveFocus(): void {
|
||||
if (!this.isActive()) {
|
||||
return;
|
||||
}
|
||||
let ftype = this.focusType.get();
|
||||
if (ftype == "input") {
|
||||
this.globalModel.inputModel.giveFocus();
|
||||
} else {
|
||||
let sline: LineType = null;
|
||||
if (this.selectedLine.get() != 0) {
|
||||
sline = this.getLineByNum(this.selectedLine.get());
|
||||
}
|
||||
if (sline != null) {
|
||||
let renderer = this.getRenderer(sline.lineid);
|
||||
if (renderer != null) {
|
||||
renderer.giveFocus();
|
||||
}
|
||||
let termWrap = this.getTermWrap(sline.lineid);
|
||||
if (termWrap != null) {
|
||||
termWrap.giveFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { Screen };
|
163
src/models/screenlines.ts
Normal file
163
src/models/screenlines.ts
Normal file
@ -0,0 +1,163 @@
|
||||
// Copyright 2023, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import * as mobx from "mobx";
|
||||
import { sprintf } from "sprintf-js";
|
||||
import { genMergeSimpleData } from "../util/util";
|
||||
import { LineType, CmdDataType, ScreenLinesType } from "../types/types";
|
||||
import { cmdStatusIsRunning } from "../app/line/lineutil";
|
||||
import { OV, OArr } from "../types/types";
|
||||
import { Cmd } from "./cmd";
|
||||
|
||||
class ScreenLines {
|
||||
screenId: string;
|
||||
loaded: OV<boolean> = mobx.observable.box(false, { name: "slines-loaded" });
|
||||
loadError: OV<string> = mobx.observable.box(null);
|
||||
lines: OArr<LineType> = mobx.observable.array([], {
|
||||
name: "slines-lines",
|
||||
deep: false,
|
||||
});
|
||||
cmds: Record<string, Cmd> = {}; // lineid => Cmd
|
||||
|
||||
constructor(screenId: string) {
|
||||
this.screenId = screenId;
|
||||
}
|
||||
|
||||
getNonArchivedLines(): LineType[] {
|
||||
let rtn: LineType[] = [];
|
||||
for (const line of this.lines) {
|
||||
if (line.archived) {
|
||||
continue;
|
||||
}
|
||||
rtn.push(line);
|
||||
}
|
||||
return rtn;
|
||||
}
|
||||
|
||||
updateData(slines: ScreenLinesType, load: boolean) {
|
||||
mobx.action(() => {
|
||||
if (load) {
|
||||
this.loaded.set(true);
|
||||
}
|
||||
genMergeSimpleData(
|
||||
this.lines,
|
||||
slines.lines,
|
||||
(l: LineType) => String(l.lineid),
|
||||
(l: LineType) => sprintf("%013d:%s", l.ts, l.lineid)
|
||||
);
|
||||
let cmds = slines.cmds || [];
|
||||
for (const cmd of cmds) {
|
||||
this.cmds[cmd.lineid] = new Cmd(cmd);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
setLoadError(errStr: string) {
|
||||
mobx.action(() => {
|
||||
this.loaded.set(true);
|
||||
this.loadError.set(errStr);
|
||||
})();
|
||||
}
|
||||
|
||||
dispose() {}
|
||||
|
||||
getCmd(lineId: string): Cmd {
|
||||
return this.cmds[lineId];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all running cmds in the screen.
|
||||
* @param returnFirst If true, return the first running cmd found.
|
||||
* @returns An array of running cmds, or the first running cmd if returnFirst is true.
|
||||
*/
|
||||
getRunningCmdLines(returnFirst?: boolean): LineType[] {
|
||||
let rtn: LineType[] = [];
|
||||
for (const line of this.lines) {
|
||||
const cmd = this.getCmd(line.lineid);
|
||||
if (cmd == null) {
|
||||
continue;
|
||||
}
|
||||
const status = cmd.getStatus();
|
||||
if (cmdStatusIsRunning(status)) {
|
||||
if (returnFirst) {
|
||||
return [line];
|
||||
}
|
||||
rtn.push(line);
|
||||
}
|
||||
}
|
||||
return rtn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there are any running cmds in the screen.
|
||||
* @returns True if there are any running cmds.
|
||||
*/
|
||||
hasRunningCmdLines(): boolean {
|
||||
return this.getRunningCmdLines(true).length > 0;
|
||||
}
|
||||
|
||||
updateCmd(cmd: CmdDataType): void {
|
||||
if (cmd.remove) {
|
||||
throw new Error("cannot remove cmd with updateCmd call [" + cmd.lineid + "]");
|
||||
}
|
||||
let origCmd = this.cmds[cmd.lineid];
|
||||
if (origCmd != null) {
|
||||
origCmd.setCmd(cmd);
|
||||
}
|
||||
}
|
||||
|
||||
mergeCmd(cmd: CmdDataType): void {
|
||||
if (cmd.remove) {
|
||||
delete this.cmds[cmd.lineid];
|
||||
return;
|
||||
}
|
||||
let origCmd = this.cmds[cmd.lineid];
|
||||
if (origCmd == null) {
|
||||
this.cmds[cmd.lineid] = new Cmd(cmd);
|
||||
return;
|
||||
}
|
||||
origCmd.setCmd(cmd);
|
||||
}
|
||||
|
||||
addLineCmd(line: LineType, cmd: CmdDataType, interactive: boolean) {
|
||||
if (!this.loaded.get()) {
|
||||
return;
|
||||
}
|
||||
mobx.action(() => {
|
||||
if (cmd != null) {
|
||||
this.mergeCmd(cmd);
|
||||
}
|
||||
if (line != null) {
|
||||
let lines = this.lines;
|
||||
if (line.remove) {
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
if (lines[i].lineid == line.lineid) {
|
||||
this.lines.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
let lineIdx = 0;
|
||||
for (lineIdx = 0; lineIdx < lines.length; lineIdx++) {
|
||||
let lineId = lines[lineIdx].lineid;
|
||||
let curTs = lines[lineIdx].ts;
|
||||
if (lineId == line.lineid) {
|
||||
this.lines[lineIdx] = line;
|
||||
return;
|
||||
}
|
||||
if (curTs > line.ts || (curTs == line.ts && lineId > line.lineid)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (lineIdx == lines.length) {
|
||||
this.lines.push(line);
|
||||
return;
|
||||
}
|
||||
this.lines.splice(lineIdx, 0, line);
|
||||
}
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
export { ScreenLines };
|
117
src/models/session.ts
Normal file
117
src/models/session.ts
Normal file
@ -0,0 +1,117 @@
|
||||
// Copyright 2023, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import * as mobx from "mobx";
|
||||
import { sprintf } from "sprintf-js";
|
||||
import { genMergeSimpleData, isBlank, ces } from "../util/util";
|
||||
import { SessionDataType, RemoteInstanceType, RemotePtrType } from "../types/types";
|
||||
import { OV, OArr } from "../types/types";
|
||||
import { Model } from "./model";
|
||||
import { Screen } from "./screen";
|
||||
|
||||
class Session {
|
||||
sessionId: string;
|
||||
name: OV<string>;
|
||||
activeScreenId: OV<string>;
|
||||
sessionIdx: OV<number>;
|
||||
notifyNum: OV<number> = mobx.observable.box(0);
|
||||
remoteInstances: OArr<RemoteInstanceType>;
|
||||
archived: OV<boolean>;
|
||||
globalModel: Model;
|
||||
|
||||
constructor(sdata: SessionDataType, globalModel: Model) {
|
||||
this.globalModel = globalModel;
|
||||
this.sessionId = sdata.sessionid;
|
||||
this.name = mobx.observable.box(sdata.name);
|
||||
this.sessionIdx = mobx.observable.box(sdata.sessionidx);
|
||||
this.archived = mobx.observable.box(!!sdata.archived);
|
||||
this.activeScreenId = mobx.observable.box(ces(sdata.activescreenid));
|
||||
let remotes = sdata.remotes || [];
|
||||
this.remoteInstances = mobx.observable.array(remotes);
|
||||
}
|
||||
|
||||
dispose(): void {}
|
||||
|
||||
// session updates only contain screens (no windows)
|
||||
mergeData(sdata: SessionDataType) {
|
||||
if (sdata.sessionid != this.sessionId) {
|
||||
throw new Error(
|
||||
sprintf(
|
||||
"cannot merge session data, sessionids don't match sid=%s, data-sid=%s",
|
||||
this.sessionId,
|
||||
sdata.sessionid
|
||||
)
|
||||
);
|
||||
}
|
||||
mobx.action(() => {
|
||||
if (!isBlank(sdata.name)) {
|
||||
this.name.set(sdata.name);
|
||||
}
|
||||
if (sdata.sessionidx > 0) {
|
||||
this.sessionIdx.set(sdata.sessionidx);
|
||||
}
|
||||
if (sdata.notifynum >= 0) {
|
||||
this.notifyNum.set(sdata.notifynum);
|
||||
}
|
||||
this.archived.set(!!sdata.archived);
|
||||
if (!isBlank(sdata.activescreenid)) {
|
||||
let screen = this.getScreenById(sdata.activescreenid);
|
||||
if (screen == null) {
|
||||
console.log(
|
||||
sprintf("got session update, activescreenid=%s, screen not found", sdata.activescreenid)
|
||||
);
|
||||
} else {
|
||||
this.activeScreenId.set(sdata.activescreenid);
|
||||
}
|
||||
}
|
||||
genMergeSimpleData(this.remoteInstances, sdata.remotes, (r) => r.riid, null);
|
||||
})();
|
||||
}
|
||||
|
||||
getActiveScreen(): Screen {
|
||||
return this.getScreenById(this.activeScreenId.get());
|
||||
}
|
||||
|
||||
setActiveScreenId(screenId: string) {
|
||||
this.activeScreenId.set(screenId);
|
||||
}
|
||||
|
||||
getScreenById(screenId: string): Screen {
|
||||
if (screenId == null) {
|
||||
return null;
|
||||
}
|
||||
return this.globalModel.getScreenById(this.sessionId, screenId);
|
||||
}
|
||||
|
||||
getRemoteInstance(screenId: string, rptr: RemotePtrType): RemoteInstanceType {
|
||||
if (rptr.name.startsWith("*")) {
|
||||
screenId = "";
|
||||
}
|
||||
for (const rdata of this.remoteInstances) {
|
||||
if (
|
||||
rdata.screenid == screenId &&
|
||||
rdata.remoteid == rptr.remoteid &&
|
||||
rdata.remoteownerid == rptr.ownerid &&
|
||||
rdata.name == rptr.name
|
||||
) {
|
||||
return rdata;
|
||||
}
|
||||
}
|
||||
let remote = this.globalModel.getRemote(rptr.remoteid);
|
||||
if (remote != null) {
|
||||
return {
|
||||
riid: "",
|
||||
sessionid: this.sessionId,
|
||||
screenid: screenId,
|
||||
remoteownerid: rptr.ownerid,
|
||||
remoteid: rptr.remoteid,
|
||||
name: rptr.name,
|
||||
festate: remote.defaultfestate,
|
||||
shelltype: remote.defaultshelltype,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export { Session };
|
164
src/models/speciallinecontainer.ts
Normal file
164
src/models/speciallinecontainer.ts
Normal file
@ -0,0 +1,164 @@
|
||||
// Copyright 2023, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { TermWrap } from "../plugins/terminal/term";
|
||||
import { LineType, RendererContext, RendererModel, FocusTypeStrs, WindowSize, LineContainerStrs } from "../types/types";
|
||||
import { windowWidthToCols } from "../util/textmeasure";
|
||||
import { getRendererContext } from "../app/line/lineutil";
|
||||
import { getTermPtyData } from "../util/modelutil";
|
||||
import { Cmd } from "./cmd";
|
||||
import { Model } from "./model";
|
||||
|
||||
type CmdFinder = {
|
||||
getCmdById(cmdId: string): Cmd;
|
||||
};
|
||||
|
||||
class SpecialLineContainer {
|
||||
globalModel: Model;
|
||||
wsize: WindowSize;
|
||||
allowInput: boolean;
|
||||
terminal: TermWrap;
|
||||
renderer: RendererModel;
|
||||
cmd: Cmd;
|
||||
cmdFinder: CmdFinder;
|
||||
containerType: LineContainerStrs;
|
||||
|
||||
constructor(cmdFinder: CmdFinder, wsize: WindowSize, allowInput: boolean, containerType: LineContainerStrs) {
|
||||
this.globalModel = Model.getInstance();
|
||||
this.cmdFinder = cmdFinder;
|
||||
this.wsize = wsize;
|
||||
this.allowInput = allowInput;
|
||||
}
|
||||
|
||||
getCmd(line: LineType): Cmd {
|
||||
if (this.cmd == null) {
|
||||
this.cmd = this.cmdFinder.getCmdById(line.lineid);
|
||||
}
|
||||
return this.cmd;
|
||||
}
|
||||
|
||||
getContainerType(): LineContainerStrs {
|
||||
return this.containerType;
|
||||
}
|
||||
|
||||
isSidebarOpen(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
isLineIdInSidebar(lineId: string): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
setLineFocus(lineNum: number, focus: boolean): void {
|
||||
return;
|
||||
}
|
||||
|
||||
setContentHeight(context: RendererContext, height: number): void {
|
||||
return;
|
||||
}
|
||||
|
||||
getMaxContentSize(): WindowSize {
|
||||
return this.wsize;
|
||||
}
|
||||
|
||||
getIdealContentSize(): WindowSize {
|
||||
return this.wsize;
|
||||
}
|
||||
|
||||
loadTerminalRenderer(elem: Element, line: LineType, cmd: Cmd, width: number): void {
|
||||
this.unloadRenderer(null);
|
||||
let lineId = cmd.lineId;
|
||||
let termWrap = this.getTermWrap(lineId);
|
||||
if (termWrap != null) {
|
||||
console.log("term-wrap already exists for", line.screenid, lineId);
|
||||
return;
|
||||
}
|
||||
let usedRows = this.globalModel.getContentHeight(getRendererContext(line));
|
||||
if (line.contentheight != null && line.contentheight != -1) {
|
||||
usedRows = line.contentheight;
|
||||
}
|
||||
let termContext = {
|
||||
screenId: line.screenid,
|
||||
lineId: line.lineid,
|
||||
lineNum: line.linenum,
|
||||
};
|
||||
termWrap = new TermWrap(elem, {
|
||||
termContext: termContext,
|
||||
usedRows: usedRows,
|
||||
termOpts: cmd.getTermOpts(),
|
||||
winSize: { height: 0, width: width },
|
||||
dataHandler: null,
|
||||
focusHandler: null,
|
||||
isRunning: cmd.isRunning(),
|
||||
customKeyHandler: null,
|
||||
fontSize: this.globalModel.termFontSize.get(),
|
||||
ptyDataSource: getTermPtyData,
|
||||
onUpdateContentHeight: null,
|
||||
});
|
||||
this.terminal = termWrap;
|
||||
}
|
||||
|
||||
registerRenderer(lineId: string, renderer: RendererModel): void {
|
||||
this.renderer = renderer;
|
||||
}
|
||||
|
||||
unloadRenderer(lineId: string): void {
|
||||
if (this.renderer != null) {
|
||||
this.renderer.dispose();
|
||||
this.renderer = null;
|
||||
}
|
||||
if (this.terminal != null) {
|
||||
this.terminal.dispose();
|
||||
this.terminal = null;
|
||||
}
|
||||
}
|
||||
|
||||
getContentHeight(context: RendererContext): number {
|
||||
return this.globalModel.getContentHeight(context);
|
||||
}
|
||||
|
||||
getUsedRows(context: RendererContext, line: LineType, cmd: Cmd, width: number): number {
|
||||
if (cmd == null) {
|
||||
return 0;
|
||||
}
|
||||
let termOpts = cmd.getTermOpts();
|
||||
if (!termOpts.flexrows) {
|
||||
return termOpts.rows;
|
||||
}
|
||||
let termWrap = this.getTermWrap(cmd.lineId);
|
||||
if (termWrap == null) {
|
||||
let cols = windowWidthToCols(width, this.globalModel.termFontSize.get());
|
||||
let usedRows = this.globalModel.getContentHeight(context);
|
||||
if (usedRows != null) {
|
||||
return usedRows;
|
||||
}
|
||||
if (line.contentheight != null && line.contentheight != -1) {
|
||||
return line.contentheight;
|
||||
}
|
||||
return cmd.isRunning() ? 1 : 0;
|
||||
}
|
||||
return termWrap.getUsedRows();
|
||||
}
|
||||
|
||||
getIsFocused(lineNum: number): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
getRenderer(lineId: string): RendererModel {
|
||||
return this.renderer;
|
||||
}
|
||||
|
||||
getTermWrap(lineId: string): TermWrap {
|
||||
return this.terminal;
|
||||
}
|
||||
|
||||
getFocusType(): FocusTypeStrs {
|
||||
return "input";
|
||||
}
|
||||
|
||||
getSelectedLine(): number {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export { SpecialLineContainer };
|
@ -6,7 +6,7 @@ import * as T from "../../types/types";
|
||||
import Editor, { Monaco } from "@monaco-editor/react";
|
||||
import type * as MonacoTypes from "monaco-editor/esm/vs/editor/editor.api";
|
||||
import { Markdown } from "../../app/common/elements";
|
||||
import { GlobalModel, GlobalCommandRunner } from "../../model/model";
|
||||
import { GlobalModel, GlobalCommandRunner } from "../../models";
|
||||
import Split from "react-split-it";
|
||||
import loader from "@monaco-editor/loader";
|
||||
loader.config({ paths: { vs: "./node_modules/monaco-editor/min/vs" } });
|
||||
|
@ -20,7 +20,7 @@ import type {
|
||||
import * as T from "../../types/types";
|
||||
import { debounce, throttle } from "throttle-debounce";
|
||||
import * as util from "../../util/util";
|
||||
import { GlobalModel } from "../../model/model";
|
||||
import { GlobalModel } from "../../models";
|
||||
|
||||
type OV<V> = mobx.IObservableValue<V>;
|
||||
type CV<V> = mobx.IComputedValue<V>;
|
||||
@ -56,7 +56,7 @@ class SimpleBlobRendererModel {
|
||||
this.savedHeight = params.savedHeight;
|
||||
this.ptyDataSource = params.ptyDataSource;
|
||||
if (this.isClosed) {
|
||||
this.dataBlob = (new Blob() as T.ExtBlob);
|
||||
this.dataBlob = new Blob() as T.ExtBlob;
|
||||
this.dataBlob.notFound = false; // TODO
|
||||
} else {
|
||||
if (this.isDone.get()) {
|
||||
|
@ -4,7 +4,7 @@
|
||||
import React, { FC, useEffect, useState, useRef, useMemo } from "react";
|
||||
import { RendererContext, RendererOpts, LineStateType, RendererModelContainerApi } from "../../types/types";
|
||||
import * as T from "../../types/types";
|
||||
import { GlobalModel } from "../../model/model";
|
||||
import { GlobalModel } from "../../models";
|
||||
import Papa from "papaparse";
|
||||
import {
|
||||
createColumnHelper,
|
||||
|
@ -9,7 +9,7 @@ import * as T from "../../types/types";
|
||||
import { isBlank } from "../../util/util";
|
||||
import mustache from "mustache";
|
||||
import * as DOMPurify from "dompurify";
|
||||
import { GlobalModel } from "../../model/model";
|
||||
import { GlobalModel } from "../../models";
|
||||
|
||||
import "./mustache.less";
|
||||
|
||||
@ -17,7 +17,13 @@ type OV<V> = mobx.IObservableValue<V>;
|
||||
|
||||
@mobxReact.observer
|
||||
class SimpleMustacheRenderer extends React.Component<
|
||||
{ data: T.ExtBlob; context: T.RendererContext; opts: T.RendererOpts; savedHeight: number; lineState: T.LineStateType },
|
||||
{
|
||||
data: T.ExtBlob;
|
||||
context: T.RendererContext;
|
||||
opts: T.RendererOpts;
|
||||
savedHeight: number;
|
||||
lineState: T.LineStateType;
|
||||
},
|
||||
{}
|
||||
> {
|
||||
templateLoading: OV<boolean> = mobx.observable.box(true, { name: "templateLoading" });
|
||||
|
@ -9,7 +9,8 @@ import { sprintf } from "sprintf-js";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { windowWidthToCols, windowHeightToRows } from "../../util/textmeasure";
|
||||
import { boundInt } from "../../util/util";
|
||||
import { GlobalModel } from "../../model/model"
|
||||
import { GlobalModel } from "../../models";
|
||||
import { Model } from "../../models/model";
|
||||
import type {
|
||||
TermContextUnion,
|
||||
TermOptsType,
|
||||
@ -99,21 +100,23 @@ class TermWrap {
|
||||
fontFamily: "JetBrains Mono",
|
||||
theme: { foreground: terminal.foreground, background: terminal.background },
|
||||
});
|
||||
this.terminal.loadAddon(new WebLinksAddon((e, uri) => {
|
||||
e.preventDefault();
|
||||
switch (GlobalModel.platform) {
|
||||
case "darwin":
|
||||
if (e.metaKey) {
|
||||
GlobalModel.openExternalLink(uri);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (e.ctrlKey) {
|
||||
GlobalModel.openExternalLink(uri);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}));
|
||||
this.terminal.loadAddon(
|
||||
new WebLinksAddon((e, uri) => {
|
||||
e.preventDefault();
|
||||
switch (GlobalModel.platform) {
|
||||
case "darwin":
|
||||
if (e.metaKey) {
|
||||
GlobalModel.openExternalLink(uri);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (e.ctrlKey) {
|
||||
GlobalModel.openExternalLink(uri);
|
||||
}
|
||||
break;
|
||||
}
|
||||
})
|
||||
);
|
||||
this.terminal._core._inputHandler._parser.setErrorHandler((state) => {
|
||||
this.numParseErrors++;
|
||||
return state;
|
||||
|
@ -8,9 +8,9 @@ import { boundMethod } from "autobind-decorator";
|
||||
import dayjs from "dayjs";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import { If } from "tsx-control-statements/components";
|
||||
import { GlobalModel, LineContainerModel } from "../../model/model";
|
||||
import { GlobalModel } from "../../models";
|
||||
import { termHeightFromRows } from "../../util/textmeasure";
|
||||
import type { LineType } from "../../types/types";
|
||||
import type { LineType, LineContainerType } from "../../types/types";
|
||||
import cn from "classnames";
|
||||
import * as lineutil from "../../app/line/lineutil";
|
||||
|
||||
@ -25,7 +25,7 @@ type OMap<K, V> = mobx.ObservableMap<K, V>;
|
||||
@mobxReact.observer
|
||||
class TerminalRenderer extends React.Component<
|
||||
{
|
||||
screen: LineContainerModel;
|
||||
screen: LineContainerType;
|
||||
line: LineType;
|
||||
width: number;
|
||||
staticRender: boolean;
|
||||
|
@ -3,6 +3,8 @@
|
||||
|
||||
import * as React from "react";
|
||||
import * as mobx from "mobx";
|
||||
import { TermWrap } from "../plugins/terminal/term";
|
||||
import { Cmd, Model } from "../models";
|
||||
|
||||
type ShareModeType = "local" | "web";
|
||||
type FocusTypeStrs = "input" | "cmd";
|
||||
@ -754,10 +756,39 @@ type StrWithPos = {
|
||||
pos: number;
|
||||
};
|
||||
|
||||
type LineFocusType = {
|
||||
cmdInputFocus: boolean;
|
||||
lineid?: string;
|
||||
linenum?: number;
|
||||
screenid?: string;
|
||||
};
|
||||
|
||||
type LineContainerType = {
|
||||
loadTerminalRenderer: (elem: Element, line: LineType, cmd: Cmd, width: number) => void;
|
||||
registerRenderer: (lineId: string, renderer: RendererModel) => void;
|
||||
unloadRenderer: (lineId: string) => void;
|
||||
getIsFocused: (lineNum: number) => boolean;
|
||||
getTermWrap: (lineId: string) => TermWrap;
|
||||
getRenderer: (lineId: string) => RendererModel;
|
||||
getFocusType: () => FocusTypeStrs;
|
||||
getSelectedLine: () => number;
|
||||
getCmd: (line: LineType) => Cmd;
|
||||
setLineFocus: (lineNum: number, focus: boolean) => void;
|
||||
getUsedRows: (context: RendererContext, line: LineType, cmd: Cmd, width: number) => number;
|
||||
getContentHeight: (context: RendererContext) => number;
|
||||
setContentHeight: (context: RendererContext, height: number) => void;
|
||||
getMaxContentSize(): WindowSize;
|
||||
getIdealContentSize(): WindowSize;
|
||||
isSidebarOpen(): boolean;
|
||||
isLineIdInSidebar(lineId: string): boolean;
|
||||
getContainerType(): LineContainerStrs;
|
||||
};
|
||||
|
||||
export type {
|
||||
SessionDataType,
|
||||
LineStateType,
|
||||
LineType,
|
||||
LineFocusType,
|
||||
RemoteType,
|
||||
RemoteStateType,
|
||||
RemoteInstanceType,
|
||||
@ -836,6 +867,10 @@ export type {
|
||||
OpenAICmdInfoChatMessageType,
|
||||
ScreenStatusIndicatorUpdateType,
|
||||
OV,
|
||||
OArr,
|
||||
OMap,
|
||||
CV,
|
||||
LineContainerType,
|
||||
};
|
||||
|
||||
export { StatusIndicatorLevel };
|
||||
|
80
src/util/modelutil.ts
Normal file
80
src/util/modelutil.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import { sprintf } from "sprintf-js";
|
||||
import { GlobalModel } from "../models";
|
||||
import { RemotePtrType, FeCmdPacketType, PtyDataType, TermContextUnion } from "../types/types";
|
||||
import { isBlank } from "./util";
|
||||
|
||||
function getTermPtyData(termContext: TermContextUnion): Promise<PtyDataType> {
|
||||
if ("remoteId" in termContext) {
|
||||
return getRemotePtyData(termContext.remoteId);
|
||||
}
|
||||
return getPtyData(termContext.screenId, termContext.lineId, termContext.lineNum);
|
||||
}
|
||||
|
||||
function getPtyData(screenId: string, lineId: string, lineNum: number): Promise<PtyDataType> {
|
||||
let url = sprintf(
|
||||
GlobalModel.getBaseHostPort() + "/api/ptyout?linenum=%d&screenid=%s&lineid=%s",
|
||||
lineNum,
|
||||
screenId,
|
||||
lineId
|
||||
);
|
||||
return getPtyDataFromUrl(url);
|
||||
}
|
||||
|
||||
function getRemotePtyData(remoteId: string): Promise<PtyDataType> {
|
||||
let url = sprintf(GlobalModel.getBaseHostPort() + "/api/remote-pty?remoteid=%s", remoteId);
|
||||
return getPtyDataFromUrl(url);
|
||||
}
|
||||
|
||||
function getPtyDataFromUrl(url: string): Promise<PtyDataType> {
|
||||
let ptyOffset = 0;
|
||||
let fetchHeaders = GlobalModel.getFetchHeaders();
|
||||
return fetch(url, { headers: fetchHeaders })
|
||||
.then((resp) => {
|
||||
if (!resp.ok) {
|
||||
throw new Error(sprintf("Bad fetch response for /api/ptyout: %d %s", resp.status, resp.statusText));
|
||||
}
|
||||
let ptyOffsetStr = resp.headers.get("X-PtyDataOffset");
|
||||
if (ptyOffsetStr != null && !isNaN(parseInt(ptyOffsetStr))) {
|
||||
ptyOffset = parseInt(ptyOffsetStr);
|
||||
}
|
||||
return resp.arrayBuffer();
|
||||
})
|
||||
.then((buf) => {
|
||||
return { pos: ptyOffset, data: new Uint8Array(buf) };
|
||||
});
|
||||
}
|
||||
|
||||
function remotePtrToString(rptr: RemotePtrType): string {
|
||||
if (rptr == null || isBlank(rptr.remoteid)) {
|
||||
return null;
|
||||
}
|
||||
if (isBlank(rptr.ownerid) && isBlank(rptr.name)) {
|
||||
return rptr.remoteid;
|
||||
}
|
||||
if (!isBlank(rptr.ownerid) && isBlank(rptr.name)) {
|
||||
return sprintf("@%s:%s", rptr.ownerid, rptr.remoteid);
|
||||
}
|
||||
if (isBlank(rptr.ownerid) && !isBlank(rptr.name)) {
|
||||
return sprintf("%s:%s", rptr.remoteid, rptr.name);
|
||||
}
|
||||
return sprintf("@%s:%s:%s", rptr.ownerid, rptr.remoteid, rptr.name);
|
||||
}
|
||||
|
||||
function cmdPacketString(pk: FeCmdPacketType): string {
|
||||
let cmd = pk.metacmd;
|
||||
if (pk.metasubcmd != null) {
|
||||
cmd += ":" + pk.metasubcmd;
|
||||
}
|
||||
let parts = [cmd];
|
||||
if (pk.kwargs != null) {
|
||||
for (let key in pk.kwargs) {
|
||||
parts.push(sprintf("%s=%s", key, pk.kwargs[key]));
|
||||
}
|
||||
}
|
||||
if (pk.args != null) {
|
||||
parts.push(...pk.args);
|
||||
}
|
||||
return parts.join(" ");
|
||||
}
|
||||
|
||||
export { getTermPtyData, remotePtrToString, cmdPacketString };
|
@ -72,13 +72,13 @@ function handleJsonFetchResponse(url: URL, resp: any): Promise<any> {
|
||||
}
|
||||
|
||||
function base64ToString(b64: string): string {
|
||||
let stringBytes = base64.toByteArray(b64)
|
||||
return new TextDecoder().decode(stringBytes)
|
||||
let stringBytes = base64.toByteArray(b64);
|
||||
return new TextDecoder().decode(stringBytes);
|
||||
}
|
||||
|
||||
function stringToBase64(input: string): string {
|
||||
let stringBytes = new TextEncoder().encode(input)
|
||||
return base64.fromByteArray(stringBytes)
|
||||
let stringBytes = new TextEncoder().encode(input);
|
||||
return base64.fromByteArray(stringBytes);
|
||||
}
|
||||
|
||||
function base64ToArray(b64: string): Uint8Array {
|
||||
@ -403,6 +403,14 @@ function getRemoteName(remote: RemoteType): string {
|
||||
return remotealias ? `${remotealias} [${remotecanonicalname}]` : remotecanonicalname;
|
||||
}
|
||||
|
||||
// clean empty string
|
||||
function ces(s: string) {
|
||||
if (s == "") {
|
||||
return null;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
export {
|
||||
handleJsonFetchResponse,
|
||||
base64ToString,
|
||||
@ -429,4 +437,5 @@ export {
|
||||
commandRtnHandler,
|
||||
getRemoteConnVal,
|
||||
getRemoteName,
|
||||
ces,
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user