mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-03-11 13:23:06 +01:00
prettier all the files
This commit is contained in:
parent
8b473607c7
commit
c8cae274bc
@ -1,9 +1,9 @@
|
||||
var AllowedFirstParts = {
|
||||
"package.json": true,
|
||||
"dist": true,
|
||||
"static": true,
|
||||
"node_modules": true,
|
||||
"bin": true,
|
||||
dist: true,
|
||||
static: true,
|
||||
node_modules: true,
|
||||
bin: true,
|
||||
};
|
||||
|
||||
var AllowedNodeModules = {
|
||||
@ -39,7 +39,6 @@ function ignoreFn(path) {
|
||||
if (!AllowedNodeModules[nodeModule]) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -75,19 +74,19 @@ module.exports = {
|
||||
rebuildConfig: {},
|
||||
makers: [
|
||||
{
|
||||
name: '@electron-forge/maker-squirrel',
|
||||
name: "@electron-forge/maker-squirrel",
|
||||
config: {},
|
||||
},
|
||||
{
|
||||
name: '@electron-forge/maker-zip',
|
||||
platforms: ['darwin'],
|
||||
name: "@electron-forge/maker-zip",
|
||||
platforms: ["darwin"],
|
||||
},
|
||||
{
|
||||
name: '@electron-forge/maker-deb',
|
||||
name: "@electron-forge/maker-deb",
|
||||
config: {},
|
||||
},
|
||||
{
|
||||
name: '@electron-forge/maker-rpm',
|
||||
name: "@electron-forge/maker-rpm",
|
||||
config: {},
|
||||
},
|
||||
],
|
||||
|
@ -1,48 +1,48 @@
|
||||
import * as React from "react";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import {sprintf} from "sprintf-js";
|
||||
import {boundMethod} from "autobind-decorator";
|
||||
import {v4 as uuidv4} from "uuid";
|
||||
import { sprintf } from "sprintf-js";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import dayjs from "dayjs";
|
||||
import {If, For, When, Otherwise, Choose} from "tsx-control-statements/components";
|
||||
import { If, For, When, Otherwise, Choose } from "tsx-control-statements/components";
|
||||
import cn from "classnames";
|
||||
import type {BookmarkType} from "./types";
|
||||
import {GlobalModel, GlobalCommandRunner} from "./model";
|
||||
import {CmdStrCode, Markdown} from "./elements";
|
||||
import type { BookmarkType } from "./types";
|
||||
import { GlobalModel, GlobalCommandRunner } from "./model";
|
||||
import { CmdStrCode, Markdown } from "./elements";
|
||||
|
||||
@mobxReact.observer
|
||||
class Bookmark extends React.Component<{bookmark : BookmarkType}, {}> {
|
||||
class Bookmark extends React.Component<{ bookmark: BookmarkType }, {}> {
|
||||
@boundMethod
|
||||
handleDeleteClick() : void {
|
||||
let {bookmark} = this.props;
|
||||
handleDeleteClick(): void {
|
||||
let { bookmark } = this.props;
|
||||
let model = GlobalModel.bookmarksModel;
|
||||
model.handleDeleteBookmark(bookmark.bookmarkid);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleEditClick() : void {
|
||||
let {bookmark} = this.props;
|
||||
handleEditClick(): void {
|
||||
let { bookmark } = this.props;
|
||||
let model = GlobalModel.bookmarksModel;
|
||||
model.handleEditBookmark(bookmark.bookmarkid);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleEditCancel() : void {
|
||||
handleEditCancel(): void {
|
||||
let model = GlobalModel.bookmarksModel;
|
||||
model.cancelEdit();
|
||||
return;
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleEditUpdate() : void {
|
||||
handleEditUpdate(): void {
|
||||
let model = GlobalModel.bookmarksModel;
|
||||
model.confirmEdit();
|
||||
return;
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleDescChange(e : any) : void {
|
||||
handleDescChange(e: any): void {
|
||||
let model = GlobalModel.bookmarksModel;
|
||||
mobx.action(() => {
|
||||
model.tempDesc.set(e.target.value);
|
||||
@ -50,7 +50,7 @@ class Bookmark extends React.Component<{bookmark : BookmarkType}, {}> {
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleCmdChange(e : any) : void {
|
||||
handleCmdChange(e: any): void {
|
||||
let model = GlobalModel.bookmarksModel;
|
||||
mobx.action(() => {
|
||||
model.tempCmd.set(e.target.value);
|
||||
@ -58,57 +58,76 @@ class Bookmark extends React.Component<{bookmark : BookmarkType}, {}> {
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleClick() : void {
|
||||
let {bookmark} = this.props;
|
||||
handleClick(): void {
|
||||
let { bookmark } = this.props;
|
||||
let model = GlobalModel.bookmarksModel;
|
||||
model.selectBookmark(bookmark.bookmarkid);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleUse() : void {
|
||||
let {bookmark} = this.props;
|
||||
handleUse(): void {
|
||||
let { bookmark } = this.props;
|
||||
let model = GlobalModel.bookmarksModel;
|
||||
model.useBookmark(bookmark.bookmarkid);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
clickCopy() : void {
|
||||
clickCopy(): void {
|
||||
let bm = this.props.bookmark;
|
||||
let model = GlobalModel.bookmarksModel;
|
||||
model.handleCopyBookmark(bm.bookmarkid);
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
let bm = this.props.bookmark;
|
||||
let model = GlobalModel.bookmarksModel;
|
||||
let isSelected = (model.activeBookmark.get() == bm.bookmarkid);
|
||||
let isSelected = model.activeBookmark.get() == bm.bookmarkid;
|
||||
let markdown = bm.description ?? "";
|
||||
let hasDesc = markdown != "";
|
||||
let isEditing = (model.editingBookmark.get() == bm.bookmarkid);
|
||||
let isCopied = mobx.computed(() => (model.copiedIndicator.get() == bm.bookmarkid)).get();
|
||||
let isEditing = model.editingBookmark.get() == bm.bookmarkid;
|
||||
let isCopied = mobx.computed(() => model.copiedIndicator.get() == bm.bookmarkid).get();
|
||||
if (isEditing) {
|
||||
return (
|
||||
<div data-bookmarkid={bm.bookmarkid} className={cn("bookmark focus-parent is-editing", {"pending-delete": model.pendingDelete.get() == bm.bookmarkid})}>
|
||||
<div className={cn("focus-indicator", {"active": isSelected})}/>
|
||||
<div
|
||||
data-bookmarkid={bm.bookmarkid}
|
||||
className={cn("bookmark focus-parent is-editing", {
|
||||
"pending-delete": model.pendingDelete.get() == bm.bookmarkid,
|
||||
})}
|
||||
>
|
||||
<div className={cn("focus-indicator", { active: isSelected })} />
|
||||
<div className="bookmark-edit">
|
||||
<div className="field">
|
||||
<label className="label">Description (markdown)</label>
|
||||
<div className="control">
|
||||
<textarea className="textarea mono-font" rows={6} value={model.tempDesc.get()} onChange={this.handleDescChange}/>
|
||||
<textarea
|
||||
className="textarea mono-font"
|
||||
rows={6}
|
||||
value={model.tempDesc.get()}
|
||||
onChange={this.handleDescChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label className="label">Command</label>
|
||||
<div className="control">
|
||||
<textarea className="textarea mono-font" rows={3} value={model.tempCmd.get()} onChange={this.handleCmdChange}/>
|
||||
<textarea
|
||||
className="textarea mono-font"
|
||||
rows={3}
|
||||
value={model.tempCmd.get()}
|
||||
onChange={this.handleCmdChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="field is-grouped">
|
||||
<div className="control">
|
||||
<button className="button is-link" onClick={this.handleEditUpdate}>Update</button>
|
||||
<button className="button is-link" onClick={this.handleEditUpdate}>
|
||||
Update
|
||||
</button>
|
||||
</div>
|
||||
<div className="control">
|
||||
<button className="button" onClick={this.handleEditCancel}>Cancel</button>
|
||||
<button className="button" onClick={this.handleEditCancel}>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -116,18 +135,34 @@ class Bookmark extends React.Component<{bookmark : BookmarkType}, {}> {
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className={cn("bookmark focus-parent", {"pending-delete": model.pendingDelete.get() == bm.bookmarkid})} onClick={this.handleClick}>
|
||||
<div className={cn("focus-indicator", {"active": isSelected})}/>
|
||||
<div
|
||||
className={cn("bookmark focus-parent", {
|
||||
"pending-delete": model.pendingDelete.get() == bm.bookmarkid,
|
||||
})}
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
<div className={cn("focus-indicator", { active: isSelected })} />
|
||||
<div className="bookmark-id-div">{bm.bookmarkid.substr(0, 8)}</div>
|
||||
<div className="bookmark-content">
|
||||
<If condition={hasDesc}>
|
||||
<Markdown text={markdown}/>
|
||||
<Markdown text={markdown} />
|
||||
</If>
|
||||
<CmdStrCode cmdstr={bm.cmdstr} onUse={this.handleUse} onCopy={this.clickCopy} isCopied={isCopied} fontSize="large" limitHeight={false}/>
|
||||
<CmdStrCode
|
||||
cmdstr={bm.cmdstr}
|
||||
onUse={this.handleUse}
|
||||
onCopy={this.clickCopy}
|
||||
isCopied={isCopied}
|
||||
fontSize="large"
|
||||
limitHeight={false}
|
||||
/>
|
||||
</div>
|
||||
<div className="bookmark-controls">
|
||||
<div className="bookmark-control" onClick={this.handleEditClick}><i className="fa-sharp fa-solid fa-pen"/></div>
|
||||
<div className="bookmark-control" onClick={this.handleDeleteClick}><i className="fa-sharp fa-solid fa-trash"/></div>
|
||||
<div className="bookmark-control" onClick={this.handleEditClick}>
|
||||
<i className="fa-sharp fa-solid fa-pen" />
|
||||
</div>
|
||||
<div className="bookmark-control" onClick={this.handleDeleteClick}>
|
||||
<i className="fa-sharp fa-solid fa-trash" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -137,44 +172,53 @@ class Bookmark extends React.Component<{bookmark : BookmarkType}, {}> {
|
||||
@mobxReact.observer
|
||||
class BookmarksView extends React.Component<{}, {}> {
|
||||
@boundMethod
|
||||
closeView() : void {
|
||||
closeView(): void {
|
||||
GlobalModel.bookmarksModel.closeView();
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
let isHidden = (GlobalModel.activeMainView.get() != "bookmarks");
|
||||
let isHidden = GlobalModel.activeMainView.get() != "bookmarks";
|
||||
if (isHidden) {
|
||||
return null;
|
||||
}
|
||||
let bookmarks = GlobalModel.bookmarksModel.bookmarks;
|
||||
let idx : number = 0;
|
||||
let bookmark : BookmarkType = null;
|
||||
let idx: number = 0;
|
||||
let bookmark: BookmarkType = null;
|
||||
return (
|
||||
<div className={cn("bookmarks-view", "alt-view", {"is-hidden": isHidden})}>
|
||||
<div className="close-button" onClick={this.closeView}><i className="fa-sharp fa-solid fa-xmark"></i></div>
|
||||
<div className={cn("bookmarks-view", "alt-view", { "is-hidden": isHidden })}>
|
||||
<div className="close-button" onClick={this.closeView}>
|
||||
<i className="fa-sharp fa-solid fa-xmark"></i>
|
||||
</div>
|
||||
<div className="alt-title">
|
||||
<i className="fa-sharp fa-solid fa-bookmark" style={{marginRight: 10}}/>
|
||||
<i className="fa-sharp fa-solid fa-bookmark" style={{ marginRight: 10 }} />
|
||||
BOOKMARKS
|
||||
</div>
|
||||
<div className="bookmarks-list">
|
||||
<For index="idx" each="bookmark" of={bookmarks}>
|
||||
<Bookmark key={bookmark.bookmarkid} bookmark={bookmark}/>
|
||||
<Bookmark key={bookmark.bookmarkid} bookmark={bookmark} />
|
||||
</For>
|
||||
<If condition={bookmarks.length == 0}>
|
||||
<div className="no-content">
|
||||
No Bookmarks.<br/>
|
||||
Use the <i className="fa-sharp fa-solid fa-bookmark"/> icon on commands to add your first bookmark.
|
||||
No Bookmarks.
|
||||
<br />
|
||||
Use the <i className="fa-sharp fa-solid fa-bookmark" /> icon on commands to add your first
|
||||
bookmark.
|
||||
</div>
|
||||
</If>
|
||||
</div>
|
||||
<If condition={bookmarks.length > 0}>
|
||||
<div className="alt-help">
|
||||
<div className="help-entry">
|
||||
[Enter] to Use Bookmark<br/>
|
||||
[Backspace/Delete]x2 or <i className="fa-sharp fa-solid fa-trash"/> to Delete<br/>
|
||||
[Arrow Up]/[Arrow Down]/[PageUp]/[PageDown] to Move in List<br/>
|
||||
[e] or <i className="fa-sharp fa-solid fa-pen"/> to Edit<br/>
|
||||
[c] or <i className="fa-sharp fa-regular fa-copy"/> to Copy<br/>
|
||||
[Enter] to Use Bookmark
|
||||
<br />
|
||||
[Backspace/Delete]x2 or <i className="fa-sharp fa-solid fa-trash" /> to Delete
|
||||
<br />
|
||||
[Arrow Up]/[Arrow Down]/[PageUp]/[PageDown] to Move in List
|
||||
<br />
|
||||
[e] or <i className="fa-sharp fa-solid fa-pen" /> to Edit
|
||||
<br />
|
||||
[c] or <i className="fa-sharp fa-regular fa-copy" /> to Copy
|
||||
<br />
|
||||
</div>
|
||||
</div>
|
||||
</If>
|
||||
@ -183,4 +227,4 @@ class BookmarksView extends React.Component<{}, {}> {
|
||||
}
|
||||
}
|
||||
|
||||
export {BookmarksView};
|
||||
export { BookmarksView };
|
||||
|
@ -1,4 +1,5 @@
|
||||
.cmd-input-info, .cmd-history {
|
||||
.cmd-input-info,
|
||||
.cmd-history {
|
||||
&::-webkit-scrollbar {
|
||||
background-color: #777;
|
||||
width: 5px;
|
||||
@ -15,7 +16,7 @@
|
||||
font-weight: normal;
|
||||
font-size: 12px;
|
||||
color: @term-white;
|
||||
|
||||
|
||||
.message-content {
|
||||
position: absolute;
|
||||
display: none;
|
||||
@ -41,7 +42,7 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&:hover {
|
||||
.message-content {
|
||||
display: flex;
|
||||
@ -95,7 +96,7 @@
|
||||
}
|
||||
|
||||
.remote-name {
|
||||
.mono-font(14px)
|
||||
.mono-font(14px);
|
||||
}
|
||||
}
|
||||
|
||||
@ -121,7 +122,7 @@
|
||||
|
||||
.cmd-input-field {
|
||||
position: relative;
|
||||
|
||||
|
||||
.cmd-input-control {
|
||||
line-height: 0;
|
||||
}
|
||||
@ -141,7 +142,8 @@
|
||||
resize: none;
|
||||
overflow-wrap: anywhere;
|
||||
|
||||
&:active, &:focus {
|
||||
&:active,
|
||||
&:focus {
|
||||
border-color: white !important;
|
||||
}
|
||||
|
||||
@ -259,7 +261,7 @@
|
||||
flex-shrink: 1;
|
||||
overflow-y: auto;
|
||||
margin-bottom: 5px;
|
||||
|
||||
|
||||
.info-msg {
|
||||
.mono-font(14px, 400);
|
||||
color: @soft-blue;
|
||||
@ -289,7 +291,7 @@
|
||||
flex-wrap: wrap;
|
||||
padding-bottom: 5px;
|
||||
.mono-font(14px, 400);
|
||||
|
||||
|
||||
.info-comp {
|
||||
min-width: 200px;
|
||||
color: @term-white;
|
||||
@ -317,8 +319,9 @@
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
th, td {
|
||||
|
||||
th,
|
||||
td {
|
||||
padding: 3px 8px 3px 8px;
|
||||
}
|
||||
|
||||
@ -341,7 +344,8 @@
|
||||
color: @term-cyan;
|
||||
}
|
||||
|
||||
.info-error, .info-msg {
|
||||
.info-error,
|
||||
.info-msg {
|
||||
margin-top: 5px;
|
||||
padding: 5px;
|
||||
}
|
||||
@ -354,7 +358,7 @@
|
||||
white-space: pre;
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
|
||||
.remote-field-val {
|
||||
white-space: pre;
|
||||
display: flex;
|
||||
@ -386,7 +390,9 @@
|
||||
}
|
||||
|
||||
&.text-input {
|
||||
input[type=text], input[type=number], input[type=password] {
|
||||
input[type="text"],
|
||||
input[type="number"],
|
||||
input[type="password"] {
|
||||
background-color: black;
|
||||
color: white;
|
||||
width: 200px;
|
||||
@ -394,7 +400,7 @@
|
||||
}
|
||||
|
||||
&.checkbox-input {
|
||||
input[type=checkbox] {
|
||||
input[type="checkbox"] {
|
||||
position: relative;
|
||||
top: 3px;
|
||||
}
|
||||
|
@ -49,7 +49,7 @@
|
||||
min-width: 100px;
|
||||
overflow: auto;
|
||||
border-left: 1px solid #777;
|
||||
|
||||
|
||||
code {
|
||||
flex-shrink: 0;
|
||||
min-width: 100px;
|
||||
@ -66,7 +66,7 @@
|
||||
position: relative;
|
||||
display: block;
|
||||
visibility: hidden;
|
||||
|
||||
|
||||
.inner-copy {
|
||||
position: absolute;
|
||||
bottom: -1px;
|
||||
@ -92,7 +92,7 @@
|
||||
|
||||
.terminal-wrapper {
|
||||
position: relative;
|
||||
|
||||
|
||||
.term-block {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@ -126,7 +126,7 @@
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
}
|
||||
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: white;
|
||||
}
|
||||
@ -138,7 +138,7 @@
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
}
|
||||
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #555;
|
||||
}
|
||||
@ -206,7 +206,8 @@ body .xterm .xterm-viewport {
|
||||
}
|
||||
}
|
||||
|
||||
.button.is-plain, .button.is-prompt-cancel {
|
||||
.button.is-plain,
|
||||
.button.is-prompt-cancel {
|
||||
background-color: #222;
|
||||
color: @term-white;
|
||||
|
||||
@ -238,7 +239,7 @@ body .xterm .xterm-viewport {
|
||||
.mono-font(10px);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
|
||||
.hint-item {
|
||||
padding: 0px 5px 0px 5px;
|
||||
border-radius: 0 0 3px 3px;
|
||||
@ -252,7 +253,7 @@ body .xterm .xterm-viewport {
|
||||
.hint-item.color-green {
|
||||
color: black;
|
||||
background-color: @tab-green;
|
||||
|
||||
|
||||
&:hover {
|
||||
color: white;
|
||||
}
|
||||
@ -263,11 +264,11 @@ body .xterm .xterm-viewport {
|
||||
background-color: @tab-green;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
|
||||
.hint-item.color-white {
|
||||
color: black;
|
||||
background-color: @term-white;
|
||||
|
||||
|
||||
&:hover {
|
||||
background-color: @term-bright-white;
|
||||
}
|
||||
@ -282,7 +283,7 @@ body .xterm .xterm-viewport {
|
||||
.hint-item.color-blue {
|
||||
color: black;
|
||||
background-color: @tab-blue;
|
||||
|
||||
|
||||
&:hover {
|
||||
color: white;
|
||||
}
|
||||
@ -426,11 +427,11 @@ body .xterm .xterm-viewport {
|
||||
div:nth-child(1) {
|
||||
animation-delay: -0.45s;
|
||||
}
|
||||
|
||||
|
||||
div:nth-child(2) {
|
||||
animation-delay: -0.3s;
|
||||
}
|
||||
|
||||
|
||||
div:nth-child(3) {
|
||||
animation-delay: -0.15s;
|
||||
}
|
||||
@ -457,19 +458,20 @@ body .xterm .xterm-viewport {
|
||||
background-color: #171717;
|
||||
outline: 2px solid #171717;
|
||||
|
||||
&:hover, &:focus {
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: @term-white;
|
||||
background-color: #333;
|
||||
outline: 2px solid #333;
|
||||
}
|
||||
|
||||
|
||||
&.connect-button {
|
||||
color: @term-green;
|
||||
&:hover {
|
||||
color: @term-green;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&.disconnect-button {
|
||||
color: @term-red;
|
||||
&:hover {
|
||||
@ -499,7 +501,8 @@ body .xterm .xterm-viewport {
|
||||
}
|
||||
|
||||
&.disabled-button {
|
||||
&:hover, &:focus {
|
||||
&:hover,
|
||||
&:focus {
|
||||
outline: none;
|
||||
background-color: #171717;
|
||||
}
|
||||
@ -521,12 +524,13 @@ body .xterm .xterm-viewport {
|
||||
display: block;
|
||||
background-color: #666 !important;
|
||||
}
|
||||
|
||||
&.active, &.active.selected {
|
||||
|
||||
&.active,
|
||||
&.active.selected {
|
||||
display: block;
|
||||
background-color: @tab-blue !important;
|
||||
}
|
||||
|
||||
|
||||
&.active.selected.fg-focus {
|
||||
display: block;
|
||||
background-color: @tab-green !important;
|
||||
@ -540,7 +544,7 @@ body .xterm .xterm-viewport {
|
||||
|
||||
#main .term-prompt {
|
||||
font-size: 14px;
|
||||
|
||||
|
||||
i {
|
||||
margin-right: 3px;
|
||||
}
|
||||
@ -561,7 +565,7 @@ body .xterm .xterm-viewport {
|
||||
|
||||
.term-prompt-remote {
|
||||
color: @term-bright-green;
|
||||
|
||||
|
||||
&.color-green {
|
||||
color: @term-bright-green;
|
||||
}
|
||||
@ -611,18 +615,19 @@ body .xterm .xterm-viewport {
|
||||
top: -3px;
|
||||
color: #c4a000;
|
||||
|
||||
&.status-init, &.status-disconnected {
|
||||
&.status-init,
|
||||
&.status-disconnected {
|
||||
color: #c4a000;
|
||||
}
|
||||
|
||||
&.status-connecting {
|
||||
color: #c4a000;
|
||||
}
|
||||
|
||||
|
||||
&.status-connected {
|
||||
color: #4e9a06;
|
||||
}
|
||||
|
||||
|
||||
&.status-error {
|
||||
color: #cc0000;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {sprintf} from "sprintf-js";
|
||||
import {Database} from "better-sqlite3";
|
||||
import { sprintf } from "sprintf-js";
|
||||
import { Database } from "better-sqlite3";
|
||||
|
||||
let DB = new Database();
|
||||
|
193
src/elements.tsx
193
src/elements.tsx
@ -1,31 +1,41 @@
|
||||
import * as React from "react";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import {sprintf} from "sprintf-js";
|
||||
import {boundMethod} from "autobind-decorator";
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
import { sprintf } from "sprintf-js";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import cn from "classnames";
|
||||
import {If, For, When, Otherwise, Choose} from "tsx-control-statements/components";
|
||||
import type {RemoteType} from "./types";
|
||||
import { If, For, When, Otherwise, Choose } from "tsx-control-statements/components";
|
||||
import type { RemoteType } from "./types";
|
||||
|
||||
type OV<V> = mobx.IObservableValue<V>;
|
||||
|
||||
function renderCmdText(text : string) : any {
|
||||
function renderCmdText(text: string): any {
|
||||
return <span>⌘{text}</span>;
|
||||
}
|
||||
|
||||
class CmdStrCode extends React.Component<{cmdstr : string, onUse : () => void, onCopy : () => void, isCopied : boolean, fontSize : "normal" | "large", limitHeight : boolean}, {}> {
|
||||
class CmdStrCode extends React.Component<
|
||||
{
|
||||
cmdstr: string;
|
||||
onUse: () => void;
|
||||
onCopy: () => void;
|
||||
isCopied: boolean;
|
||||
fontSize: "normal" | "large";
|
||||
limitHeight: boolean;
|
||||
},
|
||||
{}
|
||||
> {
|
||||
@boundMethod
|
||||
handleUse(e : any) {
|
||||
handleUse(e: any) {
|
||||
e.stopPropagation();
|
||||
if (this.props.onUse != null) {
|
||||
this.props.onUse()
|
||||
this.props.onUse();
|
||||
}
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleCopy(e : any) {
|
||||
handleCopy(e: any) {
|
||||
e.stopPropagation();
|
||||
if (this.props.onCopy != null) {
|
||||
this.props.onCopy();
|
||||
@ -33,21 +43,23 @@ class CmdStrCode extends React.Component<{cmdstr : string, onUse : () => void, o
|
||||
}
|
||||
|
||||
render() {
|
||||
let {isCopied, cmdstr, fontSize, limitHeight} = this.props;
|
||||
let { isCopied, cmdstr, fontSize, limitHeight } = this.props;
|
||||
return (
|
||||
<div className={cn("cmdstr-code", {"is-large": (fontSize == "large")}, {"limit-height": limitHeight})}>
|
||||
<div className={cn("cmdstr-code", { "is-large": fontSize == "large" }, { "limit-height": limitHeight })}>
|
||||
<If condition={isCopied}>
|
||||
<div key="copied" className="copied-indicator">
|
||||
<div>copied</div>
|
||||
</div>
|
||||
</If>
|
||||
<div key="use" className="use-button" title="Use Command" onClick={this.handleUse}><i className="fa-sharp fa-solid fa-check"/></div>
|
||||
<div key="use" className="use-button" title="Use Command" onClick={this.handleUse}>
|
||||
<i className="fa-sharp fa-solid fa-check" />
|
||||
</div>
|
||||
<div key="code" className="code-div">
|
||||
<code>{cmdstr}</code>
|
||||
</div>
|
||||
<div key="copy" className="copy-control">
|
||||
<div className="inner-copy" onClick={this.handleCopy}>
|
||||
<i title="copy" className="fa-sharp fa-regular fa-copy"/>
|
||||
<i title="copy" className="fa-sharp fa-regular fa-copy" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -55,27 +67,27 @@ class CmdStrCode extends React.Component<{cmdstr : string, onUse : () => void, o
|
||||
}
|
||||
}
|
||||
|
||||
class Toggle extends React.Component<{checked : boolean, onChange : (value : boolean) => void}, {}> {
|
||||
class Toggle extends React.Component<{ checked: boolean; onChange: (value: boolean) => void }, {}> {
|
||||
@boundMethod
|
||||
handleChange(e : any) : void {
|
||||
let {onChange} = this.props;
|
||||
handleChange(e: any): void {
|
||||
let { onChange } = this.props;
|
||||
if (onChange != null) {
|
||||
onChange(e.target.checked);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
return (
|
||||
<label className="checkbox-toggle">
|
||||
<input type="checkbox" checked={this.props.checked} onChange={this.handleChange}/>
|
||||
<span className="slider"/>
|
||||
<input type="checkbox" checked={this.props.checked} onChange={this.handleChange} />
|
||||
<span className="slider" />
|
||||
</label>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class RemoteStatusLight extends React.Component<{remote : RemoteType}, {}> {
|
||||
class RemoteStatusLight extends React.Component<{ remote: RemoteType }, {}> {
|
||||
render() {
|
||||
let remote = this.props.remote;
|
||||
let status = "error";
|
||||
@ -84,24 +96,32 @@ class RemoteStatusLight extends React.Component<{remote : RemoteType}, {}> {
|
||||
status = remote.status;
|
||||
wfp = remote.waitingforpassword;
|
||||
}
|
||||
let icon = "fa-sharp fa-solid fa-circle"
|
||||
let icon = "fa-sharp fa-solid fa-circle";
|
||||
if (status == "connecting") {
|
||||
icon = (wfp ? "fa-sharp fa-solid fa-key" : "fa-sharp fa-solid fa-rotate");
|
||||
icon = wfp ? "fa-sharp fa-solid fa-key" : "fa-sharp fa-solid fa-rotate";
|
||||
}
|
||||
return (
|
||||
<i className={cn("remote-status", icon, "status-" + status)}/>
|
||||
);
|
||||
return <i className={cn("remote-status", icon, "status-" + status)} />;
|
||||
}
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class InlineSettingsTextEdit extends React.Component<{text : string, value : string, onChange : (val : string) => void, maxLength : number, placeholder : string, showIcon? : boolean}, {}> {
|
||||
isEditing : OV<boolean> = mobx.observable.box(false, {name: "inlineedit-isEditing"});
|
||||
tempText : OV<string>;
|
||||
shouldFocus : boolean = false;
|
||||
inputRef : React.RefObject<any> = React.createRef();
|
||||
class InlineSettingsTextEdit extends React.Component<
|
||||
{
|
||||
text: string;
|
||||
value: string;
|
||||
onChange: (val: string) => void;
|
||||
maxLength: number;
|
||||
placeholder: string;
|
||||
showIcon?: boolean;
|
||||
},
|
||||
{}
|
||||
> {
|
||||
isEditing: OV<boolean> = mobx.observable.box(false, { name: "inlineedit-isEditing" });
|
||||
tempText: OV<string>;
|
||||
shouldFocus: boolean = false;
|
||||
inputRef: React.RefObject<any> = React.createRef();
|
||||
|
||||
componentDidUpdate() : void {
|
||||
componentDidUpdate(): void {
|
||||
if (this.shouldFocus) {
|
||||
this.shouldFocus = false;
|
||||
if (this.inputRef.current != null) {
|
||||
@ -111,14 +131,14 @@ class InlineSettingsTextEdit extends React.Component<{text : string, value : str
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleChangeText(e : any) : void {
|
||||
handleChangeText(e: any): void {
|
||||
mobx.action(() => {
|
||||
this.tempText.set(e.target.value);
|
||||
})();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
confirmChange() : void {
|
||||
confirmChange(): void {
|
||||
mobx.action(() => {
|
||||
let newText = this.tempText.get();
|
||||
this.isEditing.set(false);
|
||||
@ -128,7 +148,7 @@ class InlineSettingsTextEdit extends React.Component<{text : string, value : str
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
cancelChange() : void {
|
||||
cancelChange(): void {
|
||||
mobx.action(() => {
|
||||
this.isEditing.set(false);
|
||||
this.tempText = null;
|
||||
@ -136,7 +156,7 @@ class InlineSettingsTextEdit extends React.Component<{text : string, value : str
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleKeyDown(e : any) : void {
|
||||
handleKeyDown(e: any): void {
|
||||
if (e.code == "Enter") {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
@ -153,36 +173,64 @@ class InlineSettingsTextEdit extends React.Component<{text : string, value : str
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
clickEdit() : void {
|
||||
clickEdit(): void {
|
||||
mobx.action(() => {
|
||||
this.isEditing.set(true);
|
||||
this.shouldFocus = true;
|
||||
this.tempText = mobx.observable.box(this.props.value, {name: "inlineedit-tempText"});
|
||||
this.tempText = mobx.observable.box(this.props.value, { name: "inlineedit-tempText" });
|
||||
})();
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
if (this.isEditing.get()) {
|
||||
return (
|
||||
<div className={cn("settings-input inline-edit", "edit-active")}>
|
||||
<div className="field has-addons">
|
||||
<div className="control">
|
||||
<input ref={this.inputRef} className="input" type="text" onKeyDown={this.handleKeyDown} placeholder={this.props.placeholder} onChange={this.handleChangeText} value={this.tempText.get()} maxLength={this.props.maxLength}/>
|
||||
<input
|
||||
ref={this.inputRef}
|
||||
className="input"
|
||||
type="text"
|
||||
onKeyDown={this.handleKeyDown}
|
||||
placeholder={this.props.placeholder}
|
||||
onChange={this.handleChangeText}
|
||||
value={this.tempText.get()}
|
||||
maxLength={this.props.maxLength}
|
||||
/>
|
||||
</div>
|
||||
<div className="control">
|
||||
<div onClick={this.cancelChange} title="Cancel (Esc)" className="button is-prompt-danger is-outlined is-small"><span className="icon is-small"><i className="fa-sharp fa-solid fa-xmark"/></span></div>
|
||||
<div
|
||||
onClick={this.cancelChange}
|
||||
title="Cancel (Esc)"
|
||||
className="button is-prompt-danger is-outlined is-small"
|
||||
>
|
||||
<span className="icon is-small">
|
||||
<i className="fa-sharp fa-solid fa-xmark" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="control">
|
||||
<div onClick={this.confirmChange} title="Confirm (Enter)" className="button is-prompt-green is-outlined is-small"><span className="icon is-small"><i className="fa-sharp fa-solid fa-check"/></span></div>
|
||||
<div
|
||||
onClick={this.confirmChange}
|
||||
title="Confirm (Enter)"
|
||||
className="button is-prompt-green is-outlined is-small"
|
||||
>
|
||||
<span className="icon is-small">
|
||||
<i className="fa-sharp fa-solid fa-check" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return (
|
||||
<div onClick={this.clickEdit} className={cn("settings-input inline-edit", "edit-not-active")}>
|
||||
{this.props.text}<If condition={this.props.showIcon}> <i className="fa-sharp fa-solid fa-pen"/></If>
|
||||
{this.props.text}
|
||||
<If condition={this.props.showIcon}>
|
||||
{" "}
|
||||
<i className="fa-sharp fa-solid fa-pen" />
|
||||
</If>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -190,16 +238,16 @@ class InlineSettingsTextEdit extends React.Component<{text : string, value : str
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class InfoMessage extends React.Component<{width : number, children : React.ReactNode}> {
|
||||
class InfoMessage extends React.Component<{ width: number; children: React.ReactNode }> {
|
||||
render() {
|
||||
return (
|
||||
<div className="info-message">
|
||||
<div className="message-icon">
|
||||
<i className="fa-sharp fa-solid fa-circle-info"/>
|
||||
<i className="fa-sharp fa-solid fa-circle-info" />
|
||||
</div>
|
||||
<div className="message-content" style={{width: this.props.width}}>
|
||||
<div className="message-content" style={{ width: this.props.width }}>
|
||||
<div className="info-icon">
|
||||
<i className="fa-sharp fa-solid fa-circle-info"/>
|
||||
<i className="fa-sharp fa-solid fa-circle-info" />
|
||||
</div>
|
||||
<div className="info-children">{this.props.children}</div>
|
||||
</div>
|
||||
@ -208,25 +256,25 @@ class InfoMessage extends React.Component<{width : number, children : React.Reac
|
||||
}
|
||||
}
|
||||
|
||||
function LinkRenderer(props : any) : any {
|
||||
function LinkRenderer(props: any): any {
|
||||
let newUrl = "https://extern?" + encodeURIComponent(props.href);
|
||||
return <a href={newUrl} target="_blank">{props.children}</a>
|
||||
}
|
||||
|
||||
function HeaderRenderer(props : any, hnum : number) : any {
|
||||
return (
|
||||
<div className={cn("title", "is-" + hnum)}>{props.children}</div>
|
||||
<a href={newUrl} target="_blank">
|
||||
{props.children}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
function CodeRenderer(props : any) : any {
|
||||
return (
|
||||
<code className={cn({"inline": props.inline})}>{props.children}</code>
|
||||
);
|
||||
function HeaderRenderer(props: any, hnum: number): any {
|
||||
return <div className={cn("title", "is-" + hnum)}>{props.children}</div>;
|
||||
}
|
||||
|
||||
function CodeRenderer(props: any): any {
|
||||
return <code className={cn({ inline: props.inline })}>{props.children}</code>;
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class Markdown extends React.Component<{text : string, style? : any, extraClassName? : string}, {}> {
|
||||
class Markdown extends React.Component<{ text: string; style?: any; extraClassName?: string }, {}> {
|
||||
render() {
|
||||
let text = this.props.text;
|
||||
let markdownComponents = {
|
||||
@ -241,16 +289,16 @@ class Markdown extends React.Component<{text : string, style? : any, extraClassN
|
||||
};
|
||||
return (
|
||||
<div className={cn("markdown content", this.props.extraClassName)} style={this.props.style}>
|
||||
<ReactMarkdown children={text} remarkPlugins={[remarkGfm]} components={markdownComponents}/>
|
||||
<ReactMarkdown children={text} remarkPlugins={[remarkGfm]} components={markdownComponents} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class SettingsError extends React.Component<{errorMessage : OV<string>}, {}> {
|
||||
class SettingsError extends React.Component<{ errorMessage: OV<string> }, {}> {
|
||||
@boundMethod
|
||||
dismissError() : void {
|
||||
dismissError(): void {
|
||||
mobx.action(() => {
|
||||
this.props.errorMessage.set(null);
|
||||
})();
|
||||
@ -263,11 +311,22 @@ class SettingsError extends React.Component<{errorMessage : OV<string>}, {}> {
|
||||
return (
|
||||
<div className="settings-field settings-error">
|
||||
<div>Error: {this.props.errorMessage.get()}</div>
|
||||
<div className="flex-spacer"/>
|
||||
<div onClick={this.dismissError} className="error-dismiss"><i className="fa-sharp fa-solid fa-xmark"/></div>
|
||||
<div className="flex-spacer" />
|
||||
<div onClick={this.dismissError} className="error-dismiss">
|
||||
<i className="fa-sharp fa-solid fa-xmark" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export {CmdStrCode, Toggle, renderCmdText, RemoteStatusLight, InlineSettingsTextEdit, InfoMessage, Markdown, SettingsError};
|
||||
export {
|
||||
CmdStrCode,
|
||||
Toggle,
|
||||
renderCmdText,
|
||||
RemoteStatusLight,
|
||||
InlineSettingsTextEdit,
|
||||
InfoMessage,
|
||||
Markdown,
|
||||
SettingsError,
|
||||
};
|
||||
|
190
src/emain.ts
190
src/emain.ts
@ -3,12 +3,12 @@ import * as path from "path";
|
||||
import * as fs from "fs";
|
||||
import fetch from "node-fetch";
|
||||
import * as child_process from "node:child_process";
|
||||
import {debounce} from "throttle-debounce";
|
||||
import {handleJsonFetchResponse} from "./util";
|
||||
import { debounce } from "throttle-debounce";
|
||||
import { handleJsonFetchResponse } from "./util";
|
||||
import * as winston from "winston";
|
||||
import * as util from "util";
|
||||
import {sprintf} from "sprintf-js";
|
||||
import {v4 as uuidv4} from "uuid";
|
||||
import { sprintf } from "sprintf-js";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
const PromptAppPathVarName = "PROMPT_APP_PATH";
|
||||
const PromptDevVarName = "PROMPT_DEV";
|
||||
@ -16,10 +16,10 @@ const AuthKeyFile = "prompt.authkey";
|
||||
const DevServerEndpoint = "http://127.0.0.1:8090";
|
||||
const ProdServerEndpoint = "http://127.0.0.1:1619";
|
||||
|
||||
let isDev = (process.env[PromptDevVarName] != null);
|
||||
let isDev = process.env[PromptDevVarName] != null;
|
||||
let scHome = getPromptHomeDir();
|
||||
ensureDir(scHome);
|
||||
let DistDir = (isDev ? "dist-dev" : "dist");
|
||||
let DistDir = isDev ? "dist-dev" : "dist";
|
||||
let GlobalAuthKey = "";
|
||||
let instanceId = uuidv4();
|
||||
let oldConsoleLog = console.log;
|
||||
@ -31,18 +31,16 @@ let wasInFg = true;
|
||||
let unamePlatform = process.platform;
|
||||
let unameArch = process.arch;
|
||||
if (unameArch == "x64") {
|
||||
unameArch = "amd64"
|
||||
unameArch = "amd64";
|
||||
}
|
||||
let logger;
|
||||
let loggerConfig = {
|
||||
level: "info",
|
||||
format: winston.format.combine(
|
||||
winston.format.timestamp({format: "YYYY-MM-DD HH:mm:ss"}),
|
||||
winston.format.printf(info => `${info.timestamp} ${info.message}`),
|
||||
winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
|
||||
winston.format.printf((info) => `${info.timestamp} ${info.message}`)
|
||||
),
|
||||
transports: [
|
||||
new winston.transports.File({filename: path.join(scHome, "prompt-app.log"), level: "info"}),
|
||||
],
|
||||
transports: [new winston.transports.File({ filename: path.join(scHome, "prompt-app.log"), level: "info" })],
|
||||
};
|
||||
if (isDev) {
|
||||
loggerConfig.transports.push(new winston.transports.Console());
|
||||
@ -51,18 +49,25 @@ logger = winston.createLogger(loggerConfig);
|
||||
function log(...msg) {
|
||||
try {
|
||||
logger.info(util.format(...msg));
|
||||
}
|
||||
catch (e) {
|
||||
} catch (e) {
|
||||
oldConsoleLog(...msg);
|
||||
}
|
||||
}
|
||||
console.log = log;
|
||||
console.log(sprintf("prompt-app starting, PROMPT_HOME=%s, apppath=%s arch=%s/%s", scHome, getAppBasePath(), unamePlatform, unameArch));
|
||||
console.log(
|
||||
sprintf(
|
||||
"prompt-app starting, PROMPT_HOME=%s, apppath=%s arch=%s/%s",
|
||||
scHome,
|
||||
getAppBasePath(),
|
||||
unamePlatform,
|
||||
unameArch
|
||||
)
|
||||
);
|
||||
if (isDev) {
|
||||
console.log("prompt-app PROMPT_DEV set");
|
||||
}
|
||||
let app = electron.app;
|
||||
app.setName((isDev ? "Prompt (Dev)" : "Prompt"));
|
||||
app.setName(isDev ? "Prompt (Dev)" : "Prompt");
|
||||
let localServerProc = null;
|
||||
let localServerShouldRestart = false;
|
||||
|
||||
@ -78,7 +83,7 @@ function getPromptHomeDir() {
|
||||
if (homeDir == null) {
|
||||
homeDir = "/";
|
||||
}
|
||||
scHome = path.join(homeDir, (isDev ? "prompt-dev" : "prompt"));
|
||||
scHome = path.join(homeDir, isDev ? "prompt-dev" : "prompt");
|
||||
}
|
||||
return scHome;
|
||||
}
|
||||
@ -116,7 +121,7 @@ function getLocalServerCwd() {
|
||||
}
|
||||
|
||||
function ensureDir(dir) {
|
||||
fs.mkdirSync(dir, {recursive: true, mode: 0o700});
|
||||
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
||||
}
|
||||
|
||||
function readAuthKey() {
|
||||
@ -124,7 +129,7 @@ function readAuthKey() {
|
||||
let authKeyFileName = path.join(homeDir, AuthKeyFile);
|
||||
if (!fs.existsSync(authKeyFileName)) {
|
||||
let authKeyStr = String(uuidv4());
|
||||
fs.writeFileSync(authKeyFileName, authKeyStr, {mode: 0o600});
|
||||
fs.writeFileSync(authKeyFileName, authKeyStr, { mode: 0o600 });
|
||||
return authKeyStr;
|
||||
}
|
||||
let authKeyData = fs.readFileSync(authKeyFileName);
|
||||
@ -141,10 +146,7 @@ let menuTemplate = [
|
||||
},
|
||||
{
|
||||
label: "File",
|
||||
submenu: [
|
||||
{role: "close"},
|
||||
{role: "forceReload"},
|
||||
],
|
||||
submenu: [{ role: "close" }, { role: "forceReload" }],
|
||||
},
|
||||
{
|
||||
role: "editMenu",
|
||||
@ -162,11 +164,11 @@ electron.Menu.setApplicationMenu(menu);
|
||||
|
||||
let MainWindow = null;
|
||||
|
||||
function getMods(input : any) {
|
||||
return {meta: input.meta, shift: input.shift, ctrl: input.control, alt: input.alt};
|
||||
function getMods(input: any) {
|
||||
return { meta: input.meta, shift: input.shift, ctrl: input.control, alt: input.alt };
|
||||
}
|
||||
|
||||
function shNavHandler(event : any, url : any) {
|
||||
function shNavHandler(event: any, url: any) {
|
||||
console.log("navigation", url);
|
||||
event.preventDefault();
|
||||
}
|
||||
@ -181,9 +183,9 @@ function createMainWindow(clientData) {
|
||||
webPreferences: {
|
||||
preload: path.join(getAppBasePath(), DistDir, "preload.js"),
|
||||
},
|
||||
backgroundColor: '#000',
|
||||
backgroundColor: "#000",
|
||||
});
|
||||
let indexHtml = (isDev ? "index-dev.html" : "index.html");
|
||||
let indexHtml = isDev ? "index-dev.html" : "index.html";
|
||||
win.loadFile(path.join(getAppBasePath(), "static", indexHtml));
|
||||
win.webContents.on("before-input-event", (e, input) => {
|
||||
if (win.isFocused()) {
|
||||
@ -202,11 +204,10 @@ function createMainWindow(clientData) {
|
||||
e.preventDefault();
|
||||
if (!input.alt) {
|
||||
win.webContents.send("i-cmd", mods);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
win.webContents.toggleDevTools();
|
||||
}
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
if (input.code == "KeyR" && input.meta) {
|
||||
@ -255,18 +256,24 @@ function createMainWindow(clientData) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
win.webContents.send("digit-cmd", {digit: digitNum}, mods);
|
||||
win.webContents.send("digit-cmd", { digit: digitNum }, mods);
|
||||
}
|
||||
if ((input.code == "BracketRight" || input.code == "BracketLeft") && input.meta) {
|
||||
let rel = (input.code == "BracketRight" ? 1 : -1);
|
||||
win.webContents.send("bracket-cmd", {relative: rel}, mods);
|
||||
let rel = input.code == "BracketRight" ? 1 : -1;
|
||||
win.webContents.send("bracket-cmd", { relative: rel }, mods);
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
});
|
||||
win.webContents.on("will-navigate", shNavHandler);
|
||||
win.on("resized", debounce(400, (e) => mainResizeHandler(e, win)));
|
||||
win.on("moved", debounce(400, (e) => mainResizeHandler(e, win)));
|
||||
win.on(
|
||||
"resized",
|
||||
debounce(400, (e) => mainResizeHandler(e, win))
|
||||
);
|
||||
win.on(
|
||||
"moved",
|
||||
debounce(400, (e) => mainResizeHandler(e, win))
|
||||
);
|
||||
win.on("focus", () => {
|
||||
wasInFg = true;
|
||||
wasActive = true;
|
||||
@ -278,13 +285,11 @@ function createMainWindow(clientData) {
|
||||
e.preventDefault();
|
||||
if (url.startsWith("https://docs.getprompt.dev/")) {
|
||||
electron.shell.openExternal(url);
|
||||
}
|
||||
else if (url.startsWith("https://discord.gg/")) {
|
||||
} else if (url.startsWith("https://discord.gg/")) {
|
||||
electron.shell.openExternal(url);
|
||||
}
|
||||
else if (url.startsWith("https://extern/?")) {
|
||||
} else if (url.startsWith("https://extern/?")) {
|
||||
let qmark = url.indexOf("?");
|
||||
let param = url.substr(qmark+1);
|
||||
let param = url.substr(qmark + 1);
|
||||
let newUrl = decodeURIComponent(param);
|
||||
electron.shell.openExternal(newUrl);
|
||||
}
|
||||
@ -298,18 +303,20 @@ function mainResizeHandler(e, win) {
|
||||
}
|
||||
let bounds = win.getBounds();
|
||||
console.log("resize/move", win.getBounds());
|
||||
let winSize = {width: bounds.width, height: bounds.height, top: bounds.y, left: bounds.x};
|
||||
let winSize = { width: bounds.width, height: bounds.height, top: bounds.y, left: bounds.x };
|
||||
let url = getBaseHostPort() + "/api/set-winsize";
|
||||
let fetchHeaders = getFetchHeaders();
|
||||
fetch(url, {method: "post", body: JSON.stringify(winSize), headers: fetchHeaders}).then((resp) => handleJsonFetchResponse(url, resp)).catch((err) => {
|
||||
console.log("error setting winsize", err)
|
||||
});
|
||||
fetch(url, { method: "post", body: JSON.stringify(winSize), headers: fetchHeaders })
|
||||
.then((resp) => handleJsonFetchResponse(url, resp))
|
||||
.catch((err) => {
|
||||
console.log("error setting winsize", err);
|
||||
});
|
||||
}
|
||||
|
||||
function calcBounds(clientData) {
|
||||
let primaryDisplay = electron.screen.getPrimaryDisplay();
|
||||
let pdBounds = primaryDisplay.bounds;
|
||||
let size = {x: 50, y: 50, width: pdBounds.width-150, height: pdBounds.height-150};
|
||||
let size = { x: 50, y: 50, width: pdBounds.width - 150, height: pdBounds.height - 150 };
|
||||
if (clientData != null && clientData.winsize != null && clientData.winsize.width > 0) {
|
||||
let cwinSize = clientData.winsize;
|
||||
if (cwinSize.width > 0) {
|
||||
@ -346,8 +353,8 @@ function calcBounds(clientData) {
|
||||
return size;
|
||||
}
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') app.quit()
|
||||
app.on("window-all-closed", () => {
|
||||
if (process.platform !== "darwin") app.quit();
|
||||
});
|
||||
|
||||
electron.ipcMain.on("get-id", (event) => {
|
||||
@ -366,7 +373,7 @@ electron.ipcMain.on("get-authkey", (event) => {
|
||||
});
|
||||
|
||||
electron.ipcMain.on("local-server-status", (event) => {
|
||||
event.returnValue = (localServerProc != null);
|
||||
event.returnValue = localServerProc != null;
|
||||
return;
|
||||
});
|
||||
|
||||
@ -375,8 +382,7 @@ electron.ipcMain.on("restart-server", (event) => {
|
||||
localServerProc.kill();
|
||||
localServerShouldRestart = true;
|
||||
return;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
runLocalServer();
|
||||
}
|
||||
event.returnValue = true;
|
||||
@ -391,9 +397,9 @@ electron.ipcMain.on("reload-window", (event) => {
|
||||
return;
|
||||
});
|
||||
|
||||
function getContextMenu() : any {
|
||||
function getContextMenu(): any {
|
||||
let menu = new electron.Menu();
|
||||
let menuItem = new electron.MenuItem({label: "Testing", click: () => console.log("click testing!")});
|
||||
let menuItem = new electron.MenuItem({ label: "Testing", click: () => console.log("click testing!") });
|
||||
menu.append(menuItem);
|
||||
return menu;
|
||||
}
|
||||
@ -404,32 +410,35 @@ function getFetchHeaders() {
|
||||
};
|
||||
}
|
||||
|
||||
async function getClientDataPoll(loopNum : number) {
|
||||
let lastTime = (loopNum >= 6);
|
||||
async function getClientDataPoll(loopNum: number) {
|
||||
let lastTime = loopNum >= 6;
|
||||
let cdata = await getClientData(!lastTime, loopNum);
|
||||
if (lastTime || cdata != null) {
|
||||
return cdata;
|
||||
}
|
||||
await sleep(1000);
|
||||
return getClientDataPoll(loopNum+1);
|
||||
return getClientDataPoll(loopNum + 1);
|
||||
}
|
||||
|
||||
function getClientData(willRetry : boolean, retryNum : number) {
|
||||
function getClientData(willRetry: boolean, retryNum: number) {
|
||||
let url = getBaseHostPort() + "/api/get-client-data";
|
||||
let fetchHeaders = getFetchHeaders();
|
||||
return fetch(url, {headers: fetchHeaders}).then((resp) => handleJsonFetchResponse(url, resp)).then((data) => {
|
||||
if (data == null) {
|
||||
return fetch(url, { headers: fetchHeaders })
|
||||
.then((resp) => handleJsonFetchResponse(url, resp))
|
||||
.then((data) => {
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
return data.data;
|
||||
})
|
||||
.catch((err) => {
|
||||
if (willRetry) {
|
||||
console.log("error getting client-data from local-server, will retry", "(" + retryNum + ")");
|
||||
return null;
|
||||
}
|
||||
console.log("error getting client-data from local-server, failed: ", err);
|
||||
return null;
|
||||
}
|
||||
return data.data;
|
||||
}).catch((err) => {
|
||||
if (willRetry) {
|
||||
console.log("error getting client-data from local-server, will retry", "(" + retryNum + ")");
|
||||
return null;
|
||||
}
|
||||
console.log("error getting client-data from local-server, failed: ", err);
|
||||
return null;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function sendLSSC() {
|
||||
@ -479,23 +488,23 @@ function runLocalServer() {
|
||||
});
|
||||
proc.on("error", (e) => {
|
||||
console.log("error running local-server", e);
|
||||
})
|
||||
proc.stdout.on("data", output => {
|
||||
});
|
||||
proc.stdout.on("data", (output) => {
|
||||
return;
|
||||
});
|
||||
proc.stderr.on("data", output => {
|
||||
proc.stderr.on("data", (output) => {
|
||||
return;
|
||||
});
|
||||
return rtnPromise;
|
||||
}
|
||||
|
||||
electron.ipcMain.on("context-screen", (event, {screenId}, {x, y}) => {
|
||||
electron.ipcMain.on("context-screen", (event, { screenId }, { x, y }) => {
|
||||
console.log("context-screen", screenId);
|
||||
let menu = getContextMenu();
|
||||
menu.popup({x, y});
|
||||
menu.popup({ x, y });
|
||||
});
|
||||
|
||||
electron.ipcMain.on("context-editmenu", (event, {x, y}, opts) => {
|
||||
electron.ipcMain.on("context-editmenu", (event, { x, y }, opts) => {
|
||||
if (opts == null) {
|
||||
opts = {};
|
||||
}
|
||||
@ -503,22 +512,21 @@ electron.ipcMain.on("context-editmenu", (event, {x, y}, opts) => {
|
||||
let menu = new electron.Menu();
|
||||
let menuItem = null;
|
||||
if (opts.showCut) {
|
||||
menuItem = new electron.MenuItem({label: "Cut", role: "cut"});
|
||||
menuItem = new electron.MenuItem({ label: "Cut", role: "cut" });
|
||||
menu.append(menuItem);
|
||||
}
|
||||
menuItem = new electron.MenuItem({label: "Copy", role: "copy"});
|
||||
menuItem = new electron.MenuItem({ label: "Copy", role: "copy" });
|
||||
menu.append(menuItem);
|
||||
menuItem = new electron.MenuItem({label: "Paste", role: "paste"});
|
||||
menuItem = new electron.MenuItem({ label: "Paste", role: "paste" });
|
||||
menu.append(menuItem);
|
||||
menu.popup({x, y});
|
||||
menu.popup({ x, y });
|
||||
});
|
||||
|
||||
async function createMainWindowWrap() {
|
||||
let clientData = null;
|
||||
try {
|
||||
clientData = await getClientDataPoll(1);
|
||||
}
|
||||
catch (e) {
|
||||
} catch (e) {
|
||||
console.log("error getting local-server clientdata", e.toString());
|
||||
}
|
||||
MainWindow = createMainWindow(clientData);
|
||||
@ -532,14 +540,16 @@ async function sleep(ms) {
|
||||
}
|
||||
|
||||
function logActiveState() {
|
||||
let activeState = {fg: wasInFg, active: wasActive, open: true};
|
||||
let activeState = { fg: wasInFg, active: wasActive, open: true };
|
||||
let url = getBaseHostPort() + "/api/log-active-state";
|
||||
let fetchHeaders = getFetchHeaders();
|
||||
fetch(url, {method: "post", body: JSON.stringify(activeState), headers: fetchHeaders}).then((resp) => handleJsonFetchResponse(url, resp)).catch((err) => {
|
||||
console.log("error logging active state", err)
|
||||
});
|
||||
fetch(url, { method: "post", body: JSON.stringify(activeState), headers: fetchHeaders })
|
||||
.then((resp) => handleJsonFetchResponse(url, resp))
|
||||
.catch((err) => {
|
||||
console.log("error logging active state", err);
|
||||
});
|
||||
// for next iteration
|
||||
wasInFg = (MainWindow != null && MainWindow.isFocused());
|
||||
wasInFg = MainWindow != null && MainWindow.isFocused();
|
||||
wasActive = false;
|
||||
}
|
||||
|
||||
@ -561,17 +571,15 @@ function runActiveTimer() {
|
||||
GlobalAuthKey = readAuthKey();
|
||||
try {
|
||||
await runLocalServer();
|
||||
}
|
||||
catch (e) {
|
||||
} catch (e) {
|
||||
console.log(e.toString());
|
||||
}
|
||||
setTimeout(runActiveTimer, 5000); // start active timer, wait 5s just to be safe
|
||||
await app.whenReady();
|
||||
await createMainWindowWrap();
|
||||
app.on('activate', () => {
|
||||
app.on("activate", () => {
|
||||
if (electron.BrowserWindow.getAllWindows().length === 0) {
|
||||
createMainWindowWrap().then();
|
||||
}
|
||||
})
|
||||
});
|
||||
})();
|
||||
|
||||
|
@ -1,37 +1,60 @@
|
||||
import * as React from "react";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import {sprintf} from "sprintf-js";
|
||||
import {boundMethod} from "autobind-decorator";
|
||||
import {If, For, When, Otherwise, Choose} from "tsx-control-statements/components";
|
||||
import type {RendererModelInitializeParams, TermOptsType, RendererContext, RendererOpts, SimpleBlobRendererComponent, RendererModelContainerApi, RendererPluginType, PtyDataType, RendererModel, RendererOptsUpdate, LineType, TermContextUnion, RendererContainerType} from "./types";
|
||||
import {PacketDataBuffer} from "./ptydata";
|
||||
import {debounce, throttle} from "throttle-debounce";
|
||||
import { sprintf } from "sprintf-js";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { If, For, When, Otherwise, Choose } from "tsx-control-statements/components";
|
||||
import type {
|
||||
RendererModelInitializeParams,
|
||||
TermOptsType,
|
||||
RendererContext,
|
||||
RendererOpts,
|
||||
SimpleBlobRendererComponent,
|
||||
RendererModelContainerApi,
|
||||
RendererPluginType,
|
||||
PtyDataType,
|
||||
RendererModel,
|
||||
RendererOptsUpdate,
|
||||
LineType,
|
||||
TermContextUnion,
|
||||
RendererContainerType,
|
||||
} from "./types";
|
||||
import { PacketDataBuffer } from "./ptydata";
|
||||
import { debounce, throttle } from "throttle-debounce";
|
||||
|
||||
type OV<V> = mobx.IObservableValue<V>;
|
||||
type CV<V> = mobx.IComputedValue<V>;
|
||||
|
||||
@mobxReact.observer
|
||||
class FullRenderer extends React.Component<{rendererContainer : RendererContainerType, lineId : string, plugin : RendererPluginType, onHeightChange : () => void, initParams : RendererModelInitializeParams}, {}> {
|
||||
model : RendererModel;
|
||||
wrapperDivRef : React.RefObject<any> = React.createRef();
|
||||
rszObs : ResizeObserver;
|
||||
updateHeight_debounced : (newHeight : number) => void;
|
||||
class FullRenderer extends React.Component<
|
||||
{
|
||||
rendererContainer: RendererContainerType;
|
||||
lineId: string;
|
||||
plugin: RendererPluginType;
|
||||
onHeightChange: () => void;
|
||||
initParams: RendererModelInitializeParams;
|
||||
},
|
||||
{}
|
||||
> {
|
||||
model: RendererModel;
|
||||
wrapperDivRef: React.RefObject<any> = React.createRef();
|
||||
rszObs: ResizeObserver;
|
||||
updateHeight_debounced: (newHeight: number) => void;
|
||||
|
||||
constructor(props : any) {
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
let {rendererContainer, lineId, plugin, initParams} = this.props;
|
||||
let { rendererContainer, lineId, plugin, initParams } = this.props;
|
||||
this.model = plugin.modelCtor();
|
||||
this.model.initialize(initParams);
|
||||
rendererContainer.registerRenderer(lineId, this.model);
|
||||
this.updateHeight_debounced = debounce(1000, this.updateHeight.bind(this));
|
||||
}
|
||||
|
||||
updateHeight(newHeight : number) : void {
|
||||
updateHeight(newHeight: number): void {
|
||||
this.model.updateHeight(newHeight);
|
||||
}
|
||||
|
||||
handleResize(entries : ResizeObserverEntry[]) : void {
|
||||
handleResize(entries: ResizeObserverEntry[]): void {
|
||||
if (this.props.onHeightChange) {
|
||||
this.props.onHeightChange();
|
||||
}
|
||||
@ -51,13 +74,13 @@ class FullRenderer extends React.Component<{rendererContainer : RendererContaine
|
||||
this.rszObs = new ResizeObserver(this.handleResize.bind(this));
|
||||
this.rszObs.observe(this.wrapperDivRef.current);
|
||||
}
|
||||
|
||||
|
||||
componentDidMount() {
|
||||
this.checkRszObs();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
let {rendererContainer, lineId} = this.props;
|
||||
let { rendererContainer, lineId } = this.props;
|
||||
rendererContainer.unloadRenderer(lineId);
|
||||
if (this.rszObs != null) {
|
||||
this.rszObs.disconnect();
|
||||
@ -70,20 +93,17 @@ class FullRenderer extends React.Component<{rendererContainer : RendererContaine
|
||||
}
|
||||
|
||||
render() {
|
||||
let {plugin} = this.props;
|
||||
let { plugin } = this.props;
|
||||
let Comp = plugin.fullComponent;
|
||||
if (Comp == null) {
|
||||
<div ref={this.wrapperDivRef}>
|
||||
(no component found in plugin)
|
||||
</div>
|
||||
<div ref={this.wrapperDivRef}>(no component found in plugin)</div>;
|
||||
}
|
||||
return (
|
||||
<div ref={this.wrapperDivRef}>
|
||||
<Comp model={this.model}/>
|
||||
<Comp model={this.model} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export {FullRenderer};
|
||||
|
||||
export { FullRenderer };
|
||||
|
383
src/history.tsx
383
src/history.tsx
@ -1,44 +1,42 @@
|
||||
import * as React from "react";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import {If, For, When, Otherwise, Choose} from "tsx-control-statements/components";
|
||||
import {sprintf} from "sprintf-js";
|
||||
import {boundMethod} from "autobind-decorator";
|
||||
import { If, For, When, Otherwise, Choose } 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";
|
||||
import {HistoryItem, RemotePtrType, LineType, CmdDataType} from "./types";
|
||||
import { GlobalModel, GlobalCommandRunner, Cmd } from "./model";
|
||||
import { HistoryItem, RemotePtrType, LineType, CmdDataType } from "./types";
|
||||
import dayjs from "dayjs";
|
||||
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||
import customParseFormat from 'dayjs/plugin/customParseFormat';
|
||||
import {Line} from "./linecomps";
|
||||
import {CmdStrCode} from "./elements";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import customParseFormat from "dayjs/plugin/customParseFormat";
|
||||
import { Line } from "./linecomps";
|
||||
import { CmdStrCode } from "./elements";
|
||||
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(localizedFormat)
|
||||
dayjs.extend(customParseFormat);
|
||||
dayjs.extend(localizedFormat);
|
||||
|
||||
type OV<V> = mobx.IObservableValue<V>;
|
||||
type OArr<V> = mobx.IObservableArray<V>;
|
||||
type OMap<K,V> = mobx.ObservableMap<K,V>;
|
||||
type OMap<K, V> = mobx.ObservableMap<K, V>;
|
||||
type CV<V> = mobx.IComputedValue<V>;
|
||||
|
||||
function isBlank(s : string) {
|
||||
return (s == null || s == "");
|
||||
function isBlank(s: string) {
|
||||
return s == null || s == "";
|
||||
}
|
||||
|
||||
function getHistoryViewTs(nowDate : Date, ts : number) : string {
|
||||
function getHistoryViewTs(nowDate: Date, ts: number): string {
|
||||
let itemDate = new Date(ts);
|
||||
if (nowDate.getFullYear() != itemDate.getFullYear()) {
|
||||
return dayjs(itemDate).format("M/D/YY");
|
||||
}
|
||||
else if (nowDate.getMonth() != itemDate.getMonth() || nowDate.getDate() != itemDate.getDate()) {
|
||||
} else if (nowDate.getMonth() != itemDate.getMonth() || nowDate.getDate() != itemDate.getDate()) {
|
||||
return dayjs(itemDate).format("MMM D");
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return dayjs(itemDate).format("h:mm A");
|
||||
}
|
||||
}
|
||||
|
||||
function formatRemoteName(rnames : Record<string, string>, rptr : RemotePtrType) : string {
|
||||
function formatRemoteName(rnames: Record<string, string>, rptr: RemotePtrType): string {
|
||||
if (rptr == null || isBlank(rptr.remoteid)) {
|
||||
return "";
|
||||
}
|
||||
@ -52,7 +50,7 @@ function formatRemoteName(rnames : Record<string, string>, rptr : RemotePtrType)
|
||||
return "[" + rname + "]";
|
||||
}
|
||||
|
||||
function formatSSName(snames : Record<string, string>, scrnames : Record<string, string>, item : HistoryItem) : string {
|
||||
function formatSSName(snames: Record<string, string>, scrnames: Record<string, string>, item: HistoryItem): string {
|
||||
if (isBlank(item.sessionid)) {
|
||||
return "";
|
||||
}
|
||||
@ -65,7 +63,7 @@ function formatSSName(snames : Record<string, string>, scrnames : Record<string,
|
||||
return sessionName;
|
||||
}
|
||||
|
||||
function formatSessionName(snames : Record<string, string>, sessionId : string) : string {
|
||||
function formatSessionName(snames: Record<string, string>, sessionId: string): string {
|
||||
if (isBlank(sessionId)) {
|
||||
return "";
|
||||
}
|
||||
@ -78,15 +76,15 @@ function formatSessionName(snames : Record<string, string>, sessionId : string)
|
||||
|
||||
@mobxReact.observer
|
||||
class HistoryView extends React.Component<{}, {}> {
|
||||
tableRef : React.RefObject<any> = React.createRef();
|
||||
tableWidth : OV<number> = mobx.observable.box(0, {name: "tableWidth"});
|
||||
tableRszObs : ResizeObserver;
|
||||
sessionDropdownActive : OV<boolean> = mobx.observable.box(false, {name: "sessionDropdownActive"});
|
||||
remoteDropdownActive : OV<boolean> = mobx.observable.box(false, {name: "remoteDropdownActive"});
|
||||
copiedItemId : OV<string> = mobx.observable.box(null, {name: "copiedItemId"});
|
||||
|
||||
tableRef: React.RefObject<any> = React.createRef();
|
||||
tableWidth: OV<number> = mobx.observable.box(0, { name: "tableWidth" });
|
||||
tableRszObs: ResizeObserver;
|
||||
sessionDropdownActive: OV<boolean> = mobx.observable.box(false, { name: "sessionDropdownActive" });
|
||||
remoteDropdownActive: OV<boolean> = mobx.observable.box(false, { name: "remoteDropdownActive" });
|
||||
copiedItemId: OV<string> = mobx.observable.box(null, { name: "copiedItemId" });
|
||||
|
||||
@boundMethod
|
||||
clickCloseHandler() : void {
|
||||
clickCloseHandler(): void {
|
||||
GlobalModel.historyViewModel.closeView();
|
||||
}
|
||||
|
||||
@ -101,14 +99,14 @@ class HistoryView extends React.Component<{}, {}> {
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
changeSearchText(e : any) {
|
||||
changeSearchText(e: any) {
|
||||
mobx.action(() => {
|
||||
GlobalModel.historyViewModel.searchText.set(e.target.value);
|
||||
})();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
searchKeyDown(e : any) {
|
||||
searchKeyDown(e: any) {
|
||||
if (e.code == "Enter") {
|
||||
e.preventDefault();
|
||||
GlobalModel.historyViewModel.submitSearch();
|
||||
@ -117,13 +115,12 @@ class HistoryView extends React.Component<{}, {}> {
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleSelect(historyId : string) {
|
||||
handleSelect(historyId: string) {
|
||||
let hvm = GlobalModel.historyViewModel;
|
||||
mobx.action(() => {
|
||||
if (hvm.selectedItems.get(historyId)) {
|
||||
hvm.selectedItems.delete(historyId);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
hvm.selectedItems.set(historyId, true);
|
||||
}
|
||||
})();
|
||||
@ -137,9 +134,8 @@ class HistoryView extends React.Component<{}, {}> {
|
||||
if (numSelected > 0) {
|
||||
hvm.selectedItems.clear();
|
||||
return;
|
||||
}
|
||||
else {
|
||||
for (let i=0; i<hvm.items.length; i++) {
|
||||
} else {
|
||||
for (let i = 0; i < hvm.items.length; i++) {
|
||||
hvm.selectedItems.set(hvm.items[i].historyid, true);
|
||||
}
|
||||
}
|
||||
@ -152,11 +148,10 @@ class HistoryView extends React.Component<{}, {}> {
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
activateItem(historyId : string) {
|
||||
activateItem(historyId: string) {
|
||||
if (GlobalModel.historyViewModel.activeItem.get() == historyId) {
|
||||
GlobalModel.historyViewModel.setActiveItem(null);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
GlobalModel.historyViewModel.setActiveItem(historyId);
|
||||
}
|
||||
}
|
||||
@ -192,7 +187,7 @@ class HistoryView extends React.Component<{}, {}> {
|
||||
this.checkWidth();
|
||||
}
|
||||
|
||||
searchFromTsInputValue() : string {
|
||||
searchFromTsInputValue(): string {
|
||||
let hvm = GlobalModel.historyViewModel;
|
||||
let fromDate = hvm.searchFromDate.get();
|
||||
if (fromDate == null) {
|
||||
@ -202,7 +197,7 @@ class HistoryView extends React.Component<{}, {}> {
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleFromTsChange(e : any) : void {
|
||||
handleFromTsChange(e: any): void {
|
||||
let hvm = GlobalModel.historyViewModel;
|
||||
let newDate = e.target.value;
|
||||
let today = dayjs().format("YYYY-MM-DD");
|
||||
@ -215,7 +210,7 @@ class HistoryView extends React.Component<{}, {}> {
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
toggleSessionDropdown() : void {
|
||||
toggleSessionDropdown(): void {
|
||||
mobx.action(() => {
|
||||
this.sessionDropdownActive.set(!this.sessionDropdownActive.get());
|
||||
if (this.sessionDropdownActive.get()) {
|
||||
@ -225,7 +220,7 @@ class HistoryView extends React.Component<{}, {}> {
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
clickLimitSession(sessionId : string) : void {
|
||||
clickLimitSession(sessionId: string): void {
|
||||
let hvm = GlobalModel.historyViewModel;
|
||||
mobx.action(() => {
|
||||
this.sessionDropdownActive.set(false);
|
||||
@ -234,7 +229,7 @@ class HistoryView extends React.Component<{}, {}> {
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
toggleRemoteDropdown() : void {
|
||||
toggleRemoteDropdown(): void {
|
||||
mobx.action(() => {
|
||||
this.remoteDropdownActive.set(!this.remoteDropdownActive.get());
|
||||
if (this.remoteDropdownActive.get()) {
|
||||
@ -244,7 +239,7 @@ class HistoryView extends React.Component<{}, {}> {
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
clickLimitRemote(remoteId : string) : void {
|
||||
clickLimitRemote(remoteId: string): void {
|
||||
let hvm = GlobalModel.historyViewModel;
|
||||
mobx.action(() => {
|
||||
this.remoteDropdownActive.set(false);
|
||||
@ -253,7 +248,7 @@ class HistoryView extends React.Component<{}, {}> {
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
toggleShowMeta() : void {
|
||||
toggleShowMeta(): void {
|
||||
let hvm = GlobalModel.historyViewModel;
|
||||
mobx.action(() => {
|
||||
hvm.setSearchShowMeta(!hvm.searchShowMeta.get());
|
||||
@ -261,7 +256,7 @@ class HistoryView extends React.Component<{}, {}> {
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
toggleFilterCmds() : void {
|
||||
toggleFilterCmds(): void {
|
||||
let hvm = GlobalModel.historyViewModel;
|
||||
mobx.action(() => {
|
||||
hvm.setSearchFilterCmds(!hvm.searchFilterCmds.get());
|
||||
@ -269,13 +264,13 @@ class HistoryView extends React.Component<{}, {}> {
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
resetAllFilters() : void {
|
||||
resetAllFilters(): void {
|
||||
let hvm = GlobalModel.historyViewModel;
|
||||
hvm.resetAllFilters();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleCopy(item : HistoryItem) : void {
|
||||
handleCopy(item: HistoryItem): void {
|
||||
if (isBlank(item.cmdstr)) {
|
||||
return;
|
||||
}
|
||||
@ -291,7 +286,7 @@ class HistoryView extends React.Component<{}, {}> {
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleUse(item : HistoryItem) : void {
|
||||
handleUse(item: HistoryItem): void {
|
||||
if (isBlank(item.cmdstr)) {
|
||||
return;
|
||||
}
|
||||
@ -301,15 +296,15 @@ class HistoryView extends React.Component<{}, {}> {
|
||||
setTimeout(() => GlobalModel.inputModel.giveFocus(), 50);
|
||||
})();
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
let isHidden = (GlobalModel.activeMainView.get() != "history");
|
||||
let isHidden = GlobalModel.activeMainView.get() != "history";
|
||||
if (isHidden) {
|
||||
return null;
|
||||
}
|
||||
let hvm = GlobalModel.historyViewModel;
|
||||
let idx : number = 0;
|
||||
let item : HistoryItem = null;
|
||||
let idx: number = 0;
|
||||
let item: HistoryItem = null;
|
||||
let items = hvm.items.slice();
|
||||
let nowDate = new Date();
|
||||
let snames = GlobalModel.getSessionNames();
|
||||
@ -327,34 +322,47 @@ class HistoryView extends React.Component<{}, {}> {
|
||||
}
|
||||
let activeItemId = hvm.activeItem.get();
|
||||
let activeItem = hvm.getHistoryItemById(activeItemId);
|
||||
let activeLine : LineType = null;
|
||||
let activeLine: LineType = null;
|
||||
if (activeItem != null) {
|
||||
activeLine = hvm.getLineById(activeItem.lineid);
|
||||
}
|
||||
let sessionIds = Object.keys(snames);
|
||||
let sessionId : string = null;
|
||||
let sessionId: string = null;
|
||||
let remoteIds = Object.keys(rnames);
|
||||
let remoteId : string = null;
|
||||
let remoteId: string = null;
|
||||
return (
|
||||
<div className={cn("history-view", "alt-view", {"is-hidden": isHidden})}>
|
||||
<div className={cn("history-view", "alt-view", { "is-hidden": isHidden })}>
|
||||
<div className="header">
|
||||
<div className="history-title">
|
||||
HISTORY
|
||||
</div>
|
||||
<div className="history-title">HISTORY</div>
|
||||
<div className="history-search">
|
||||
<div className="main-search field">
|
||||
<p className="control has-icons-left">
|
||||
<input className="input" type="text" placeholder="Exact String Search" value={hvm.searchText.get()} onChange={this.changeSearchText} onKeyDown={this.searchKeyDown}/>
|
||||
<input
|
||||
className="input"
|
||||
type="text"
|
||||
placeholder="Exact String Search"
|
||||
value={hvm.searchText.get()}
|
||||
onChange={this.changeSearchText}
|
||||
onKeyDown={this.searchKeyDown}
|
||||
/>
|
||||
<span className="icon is-small is-left">
|
||||
<i className="fa-sharp fa-solid fa-search"/>
|
||||
<i className="fa-sharp fa-solid fa-search" />
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="advanced-search">
|
||||
<div className={cn("dropdown", "session-dropdown", {"is-active": this.sessionDropdownActive.get()})}>
|
||||
<div
|
||||
className={cn("dropdown", "session-dropdown", {
|
||||
"is-active": this.sessionDropdownActive.get(),
|
||||
})}
|
||||
>
|
||||
<div className="dropdown-trigger">
|
||||
<button onClick={this.toggleSessionDropdown} className="button is-small is-dark">
|
||||
<span>{hvm.searchSessionId.get() == null ? "Limit Session" : formatSessionName(snames, hvm.searchSessionId.get())}</span>
|
||||
<span>
|
||||
{hvm.searchSessionId.get() == null
|
||||
? "Limit Session"
|
||||
: formatSessionName(snames, hvm.searchSessionId.get())}
|
||||
</span>
|
||||
<span className="icon is-small">
|
||||
<i className="fa-sharp fa-regular fa-angle-down" aria-hidden="true"></i>
|
||||
</span>
|
||||
@ -362,17 +370,37 @@ class HistoryView extends React.Component<{}, {}> {
|
||||
</div>
|
||||
<div className="dropdown-menu" role="menu">
|
||||
<div className="dropdown-content has-background-black-ter">
|
||||
<div onClick={() => this.clickLimitSession(null) } key="all" className="dropdown-item">(all sessions)</div>
|
||||
<div
|
||||
onClick={() => this.clickLimitSession(null)}
|
||||
key="all"
|
||||
className="dropdown-item"
|
||||
>
|
||||
(all sessions)
|
||||
</div>
|
||||
<For each="sessionId" of={sessionIds}>
|
||||
<div onClick={() => this.clickLimitSession(sessionId) } key={sessionId} className="dropdown-item">#{snames[sessionId]}</div>
|
||||
<div
|
||||
onClick={() => this.clickLimitSession(sessionId)}
|
||||
key={sessionId}
|
||||
className="dropdown-item"
|
||||
>
|
||||
#{snames[sessionId]}
|
||||
</div>
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={cn("dropdown", "remote-dropdown", {"is-active": this.remoteDropdownActive.get()})}>
|
||||
<div
|
||||
className={cn("dropdown", "remote-dropdown", {
|
||||
"is-active": this.remoteDropdownActive.get(),
|
||||
})}
|
||||
>
|
||||
<div className="dropdown-trigger">
|
||||
<button onClick={this.toggleRemoteDropdown} className="button is-small is-dark">
|
||||
<span>{hvm.searchRemoteId.get() == null ? "Limit Remote" : formatRemoteName(rnames, {remoteid: hvm.searchRemoteId.get()})}</span>
|
||||
<span>
|
||||
{hvm.searchRemoteId.get() == null
|
||||
? "Limit Remote"
|
||||
: formatRemoteName(rnames, { remoteid: hvm.searchRemoteId.get() })}
|
||||
</span>
|
||||
<span className="icon is-small">
|
||||
<i className="fa-sharp fa-regular fa-angle-down" aria-hidden="true"></i>
|
||||
</span>
|
||||
@ -380,26 +408,64 @@ class HistoryView extends React.Component<{}, {}> {
|
||||
</div>
|
||||
<div className="dropdown-menu" role="menu">
|
||||
<div className="dropdown-content has-background-black-ter">
|
||||
<div onClick={() => this.clickLimitRemote(null) } key="all" className="dropdown-item">(all remotes)</div>
|
||||
<div
|
||||
onClick={() => this.clickLimitRemote(null)}
|
||||
key="all"
|
||||
className="dropdown-item"
|
||||
>
|
||||
(all remotes)
|
||||
</div>
|
||||
<For each="remoteId" of={remoteIds}>
|
||||
<div onClick={() => this.clickLimitRemote(remoteId) } key={remoteId} className="dropdown-item">[{rnames[remoteId]}]</div>
|
||||
<div
|
||||
onClick={() => this.clickLimitRemote(remoteId)}
|
||||
key={remoteId}
|
||||
className="dropdown-item"
|
||||
>
|
||||
[{rnames[remoteId]}]
|
||||
</div>
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="allow-meta search-checkbox">
|
||||
<div className="checkbox-container"><input onChange={this.toggleShowMeta} type="checkbox" checked={hvm.searchShowMeta.get()}/></div>
|
||||
<div onClick={this.toggleShowMeta} className="checkbox-text">Show MetaCmds</div>
|
||||
</div>
|
||||
<div className="fromts">
|
||||
<div onClick={this.toggleShowMeta} className="fromts-text">From: </div>
|
||||
<div>
|
||||
<input type="date" onChange={this.handleFromTsChange} value={this.searchFromTsInputValue()} className="input is-small"/>
|
||||
<div className="checkbox-container">
|
||||
<input
|
||||
onChange={this.toggleShowMeta}
|
||||
type="checkbox"
|
||||
checked={hvm.searchShowMeta.get()}
|
||||
/>
|
||||
</div>
|
||||
<div onClick={this.toggleShowMeta} className="checkbox-text">
|
||||
Show MetaCmds
|
||||
</div>
|
||||
</div>
|
||||
<div className="filter-cmds search-checkbox" title="Filter common commands like 'ls' and 'cd' from the results">
|
||||
<div className="checkbox-container"><input onChange={this.toggleFilterCmds} type="checkbox" checked={hvm.searchFilterCmds.get()}/></div>
|
||||
<div onClick={this.toggleFilterCmds} className="checkbox-text">Filter Cmds</div>
|
||||
<div className="fromts">
|
||||
<div onClick={this.toggleShowMeta} className="fromts-text">
|
||||
From:
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
type="date"
|
||||
onChange={this.handleFromTsChange}
|
||||
value={this.searchFromTsInputValue()}
|
||||
className="input is-small"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="filter-cmds search-checkbox"
|
||||
title="Filter common commands like 'ls' and 'cd' from the results"
|
||||
>
|
||||
<div className="checkbox-container">
|
||||
<input
|
||||
onChange={this.toggleFilterCmds}
|
||||
type="checkbox"
|
||||
checked={hvm.searchFilterCmds.get()}
|
||||
/>
|
||||
</div>
|
||||
<div onClick={this.toggleFilterCmds} className="checkbox-text">
|
||||
Filter Cmds
|
||||
</div>
|
||||
</div>
|
||||
<div onClick={this.resetAllFilters} className="reset-button">
|
||||
Reset All
|
||||
@ -407,26 +473,49 @@ class HistoryView extends React.Component<{}, {}> {
|
||||
</div>
|
||||
</div>
|
||||
<div className="close-div">
|
||||
<i onClick={this.clickCloseHandler} className="fa-sharp fa-solid fa-xmark"/>
|
||||
<i onClick={this.clickCloseHandler} className="fa-sharp fa-solid fa-xmark" />
|
||||
</div>
|
||||
</div>
|
||||
<div className={cn("control-bar", "is-top", {"is-hidden": (items.length == 0)})}>
|
||||
<div className={cn("control-bar", "is-top", { "is-hidden": items.length == 0 })}>
|
||||
<div className="control-checkbox" onClick={this.handleControlCheckbox}>
|
||||
<i className={controlCheckboxIcon} title="Toggle Selection"/>
|
||||
<i className={controlCheckboxIcon} title="Toggle Selection" />
|
||||
</div>
|
||||
<div className={cn("control-button delete-button", {"is-disabled": (numSelected == 0)}, {"is-active": hvm.deleteActive.get()})} onClick={this.handleClickDelete}>
|
||||
<i className="fa-sharp fa-solid fa-trash" title="Purge Selected Items"/> <span>Delete Items</span>
|
||||
<div
|
||||
className={cn(
|
||||
"control-button delete-button",
|
||||
{ "is-disabled": numSelected == 0 },
|
||||
{ "is-active": hvm.deleteActive.get() }
|
||||
)}
|
||||
onClick={this.handleClickDelete}
|
||||
>
|
||||
<i className="fa-sharp fa-solid fa-trash" title="Purge Selected Items" />{" "}
|
||||
<span>Delete Items</span>
|
||||
</div>
|
||||
<div className="spacer" />
|
||||
<div className="showing-text">
|
||||
Showing {offset + 1}-{offset + items.length}
|
||||
</div>
|
||||
<div
|
||||
className={cn("showing-btn", { "is-disabled": offset == 0 })}
|
||||
onClick={offset != 0 ? this.handlePrev : null}
|
||||
>
|
||||
<i className="fa-sharp fa-solid fa-chevron-left" />
|
||||
</div>
|
||||
<div className="btn-spacer" />
|
||||
<div
|
||||
className={cn("showing-btn", { "is-disabled": !hasMore })}
|
||||
onClick={hasMore ? this.handleNext : null}
|
||||
>
|
||||
<i className="fa-sharp fa-solid fa-chevron-right" />
|
||||
</div>
|
||||
<div className="spacer"/>
|
||||
<div className="showing-text">Showing {offset+1}-{offset+items.length}</div>
|
||||
<div className={cn("showing-btn", {"is-disabled": (offset == 0)})} onClick={(offset != 0 ? this.handlePrev : null)}><i className="fa-sharp fa-solid fa-chevron-left"/></div>
|
||||
<div className="btn-spacer"/>
|
||||
<div className={cn("showing-btn", {"is-disabled": !hasMore})} onClick={hasMore ? this.handleNext : null}><i className="fa-sharp fa-solid fa-chevron-right"/></div>
|
||||
</div>
|
||||
<table className="history-table" cellSpacing="0" cellPadding="0" border={0} ref={this.tableRef}>
|
||||
<tbody>
|
||||
<For index="idx" each="item" of={items}>
|
||||
<tr key={item.historyid} className={cn("history-item", {"is-selected": hvm.selectedItems.get(item.historyid)})}>
|
||||
<tr
|
||||
key={item.historyid}
|
||||
className={cn("history-item", { "is-selected": hvm.selectedItems.get(item.historyid) })}
|
||||
>
|
||||
<td className="selectbox" onClick={() => this.handleSelect(item.historyid)}>
|
||||
<If condition={hvm.selectedItems.get(item.historyid)}>
|
||||
<i className="fa-sharp fa-regular fa-square-check"></i>
|
||||
@ -435,38 +524,55 @@ class HistoryView extends React.Component<{}, {}> {
|
||||
<i className="fa-sharp fa-regular fa-square"></i>
|
||||
</If>
|
||||
</td>
|
||||
<td className="bookmark" style={{display: "none"}}>
|
||||
<i className="fa-sharp fa-regular fa-bookmark"/>
|
||||
</td>
|
||||
<td className="ts">
|
||||
{getHistoryViewTs(nowDate, item.ts)}
|
||||
</td>
|
||||
<td className="session">
|
||||
{formatSSName(snames, scrnames, item)}
|
||||
</td>
|
||||
<td className="remote">
|
||||
{formatRemoteName(rnames, item.remote)}
|
||||
<td className="bookmark" style={{ display: "none" }}>
|
||||
<i className="fa-sharp fa-regular fa-bookmark" />
|
||||
</td>
|
||||
<td className="ts">{getHistoryViewTs(nowDate, item.ts)}</td>
|
||||
<td className="session">{formatSSName(snames, scrnames, item)}</td>
|
||||
<td className="remote">{formatRemoteName(rnames, item.remote)}</td>
|
||||
<td className="cmdstr" onClick={() => this.activateItem(item.historyid)}>
|
||||
<CmdStrCode cmdstr={item.cmdstr} onUse={() => this.handleUse(item)} onCopy={() => this.handleCopy(item)} isCopied={this.copiedItemId.get() == item.historyid} fontSize="normal" limitHeight={true}/>
|
||||
<CmdStrCode
|
||||
cmdstr={item.cmdstr}
|
||||
onUse={() => this.handleUse(item)}
|
||||
onCopy={() => this.handleCopy(item)}
|
||||
isCopied={this.copiedItemId.get() == item.historyid}
|
||||
fontSize="normal"
|
||||
limitHeight={true}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<If condition={activeItemId == item.historyid}>
|
||||
<tr className="active-history-item">
|
||||
<td colSpan={6}>
|
||||
<LineContainer key={activeItemId} historyId={activeItemId} width={this.tableWidth.get()}/>
|
||||
<LineContainer
|
||||
key={activeItemId}
|
||||
historyId={activeItemId}
|
||||
width={this.tableWidth.get()}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</If>
|
||||
</For>
|
||||
</tbody>
|
||||
</table>
|
||||
<div className={cn("control-bar", {"is-hidden": (items.length == 0 || !hasMore)})}>
|
||||
<div className="spacer"/>
|
||||
<div className="showing-text">Showing {offset+1}-{offset+items.length}</div>
|
||||
<div className={cn("showing-btn", {"is-disabled": (offset == 0)})} onClick={(offset != 0 ? this.handlePrev : null)}><i className="fa-sharp fa-solid fa-chevron-left"/></div>
|
||||
<div className="btn-spacer"/>
|
||||
<div className={cn("showing-btn", {"is-disabled": !hasMore})} onClick={hasMore ? this.handleNext : null}><i className="fa-sharp fa-solid fa-chevron-right"/></div>
|
||||
<div className={cn("control-bar", { "is-hidden": items.length == 0 || !hasMore })}>
|
||||
<div className="spacer" />
|
||||
<div className="showing-text">
|
||||
Showing {offset + 1}-{offset + items.length}
|
||||
</div>
|
||||
<div
|
||||
className={cn("showing-btn", { "is-disabled": offset == 0 })}
|
||||
onClick={offset != 0 ? this.handlePrev : null}
|
||||
>
|
||||
<i className="fa-sharp fa-solid fa-chevron-left" />
|
||||
</div>
|
||||
<div className="btn-spacer" />
|
||||
<div
|
||||
className={cn("showing-btn", { "is-disabled": !hasMore })}
|
||||
onClick={hasMore ? this.handleNext : null}
|
||||
>
|
||||
<i className="fa-sharp fa-solid fa-chevron-right" />
|
||||
</div>
|
||||
</div>
|
||||
<If condition={items.length == 0}>
|
||||
<div className="no-items">
|
||||
@ -475,7 +581,8 @@ class HistoryView extends React.Component<{}, {}> {
|
||||
</If>
|
||||
<div className="alt-help">
|
||||
<div className="help-entry">
|
||||
[Esc] to Close<br/>
|
||||
[Esc] to Close
|
||||
<br />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -483,14 +590,14 @@ class HistoryView extends React.Component<{}, {}> {
|
||||
}
|
||||
}
|
||||
|
||||
class LineContainer extends React.Component<{historyId : string, width : number}, {}> {
|
||||
line : LineType;
|
||||
cmd : Cmd;
|
||||
historyItem : HistoryItem;
|
||||
visible : OV<boolean> = mobx.observable.box(true);
|
||||
overrideCollapsed : OV<boolean> = mobx.observable.box(false);
|
||||
|
||||
constructor(props : any) {
|
||||
class LineContainer extends React.Component<{ historyId: string; width: number }, {}> {
|
||||
line: LineType;
|
||||
cmd: Cmd;
|
||||
historyItem: HistoryItem;
|
||||
visible: OV<boolean> = mobx.observable.box(true);
|
||||
overrideCollapsed: OV<boolean> = mobx.observable.box(false);
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
let hvm = GlobalModel.historyViewModel;
|
||||
this.historyItem = hvm.getHistoryItemById(props.historyId);
|
||||
@ -502,7 +609,7 @@ class LineContainer extends React.Component<{historyId : string, width : number}
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleHeightChange(lineNum : number, newHeight : number, oldHeight : number) : void {
|
||||
handleHeightChange(lineNum: number, newHeight: number, oldHeight: number): void {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -515,14 +622,18 @@ class LineContainer extends React.Component<{historyId : string, width : number}
|
||||
GlobalModel.historyViewModel.closeView();
|
||||
GlobalCommandRunner.lineView(screen.sessionId, screen.screenId, this.line.linenum);
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
let hvm = GlobalModel.historyViewModel;
|
||||
if (this.historyItem == null || this.props.width == 0) {
|
||||
return null;
|
||||
}
|
||||
if (this.line == null) {
|
||||
return <div className="line-container no-line"><div>[no line data]</div></div>;
|
||||
return (
|
||||
<div className="line-container no-line">
|
||||
<div>[no line data]</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
let width = this.props.width;
|
||||
width = width - 50;
|
||||
@ -541,17 +652,29 @@ class LineContainer extends React.Component<{historyId : string, width : number}
|
||||
<div className="line-container">
|
||||
<If condition={canViewInContext}>
|
||||
<div className="line-context">
|
||||
<div title="View in Context" className="vic-btn" onClick={this.viewInContext}><i className="fa-sharp fa-solid fa-right"/> {ssStr}</div>
|
||||
<div title="View in Context" className="vic-btn" onClick={this.viewInContext}>
|
||||
<i className="fa-sharp fa-solid fa-right" /> {ssStr}
|
||||
</div>
|
||||
</div>
|
||||
</If>
|
||||
<If condition={session == null}>
|
||||
<div className="no-line-context"/>
|
||||
<div className="no-line-context" />
|
||||
</If>
|
||||
<Line screen={hvm.specialLineContainer} line={this.line} width={width} staticRender={false} visible={this.visible} onHeightChange={this.handleHeightChange} overrideCollapsed={this.overrideCollapsed} topBorder={false} renderMode="normal" noSelect={true}/>
|
||||
<Line
|
||||
screen={hvm.specialLineContainer}
|
||||
line={this.line}
|
||||
width={width}
|
||||
staticRender={false}
|
||||
visible={this.visible}
|
||||
onHeightChange={this.handleHeightChange}
|
||||
overrideCollapsed={this.overrideCollapsed}
|
||||
topBorder={false}
|
||||
renderMode="normal"
|
||||
noSelect={true}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export {HistoryView};
|
||||
export { HistoryView };
|
||||
|
2172
src/linecomps.tsx
2172
src/linecomps.tsx
File diff suppressed because it is too large
Load Diff
@ -60,19 +60,20 @@
|
||||
.line-minimise:hover {
|
||||
color: white;
|
||||
}
|
||||
|
||||
|
||||
.line-icon + .line-icon {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
|
||||
.line-icon.active {
|
||||
visibility: visible;
|
||||
display: block;
|
||||
|
||||
i.fa-star, i.fa-thumb-tack {
|
||||
|
||||
i.fa-star,
|
||||
i.fa-thumb-tack {
|
||||
color: @term-bright-yellow;
|
||||
}
|
||||
|
||||
|
||||
i.fa-bookmark {
|
||||
color: @term-bright-blue;
|
||||
}
|
||||
@ -251,7 +252,7 @@
|
||||
&.rtnstate {
|
||||
border: 1px solid white;
|
||||
}
|
||||
|
||||
|
||||
.status-icon {
|
||||
font-size: 8px;
|
||||
position: absolute;
|
||||
@ -341,8 +342,10 @@
|
||||
margin-top: 1px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.ts, .termopts, .renderer {
|
||||
|
||||
.ts,
|
||||
.termopts,
|
||||
.renderer {
|
||||
display: flex;
|
||||
color: #aaa;
|
||||
margin-top: 5px;
|
||||
@ -383,7 +386,7 @@
|
||||
padding-right: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.metapart-mono {
|
||||
color: #ddd;
|
||||
margin-left: 8px;
|
||||
@ -391,7 +394,7 @@
|
||||
white-space: nowrap;
|
||||
.mono-font(14px);
|
||||
}
|
||||
|
||||
|
||||
.cmdtext {
|
||||
color: #fff;
|
||||
font-size: 16px;
|
||||
@ -471,8 +474,9 @@
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.line-sep::before, .line-sep::after {
|
||||
content: '';
|
||||
.line-sep::before,
|
||||
.line-sep::after {
|
||||
content: "";
|
||||
height: 2px;
|
||||
background-color: #ccc;
|
||||
flex-grow: 1;
|
||||
|
@ -1,13 +1,13 @@
|
||||
import * as React from "react";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import {sprintf} from "sprintf-js";
|
||||
import {boundMethod} from "autobind-decorator";
|
||||
import {If, For, When, Otherwise, Choose} from "tsx-control-statements/components";
|
||||
import { sprintf } from "sprintf-js";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { If, For, When, Otherwise, Choose } from "tsx-control-statements/components";
|
||||
import cn from "classnames";
|
||||
import dayjs from "dayjs";
|
||||
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||
import {debounce, throttle} from "throttle-debounce";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import { debounce, throttle } from "throttle-debounce";
|
||||
import * as T from "./types";
|
||||
import * as util from "./util";
|
||||
import * as lineutil from "./lineutil";
|
||||
@ -15,45 +15,57 @@ import * as lineutil from "./lineutil";
|
||||
dayjs.extend(localizedFormat);
|
||||
type OV<V> = mobx.IObservableValue<V>;
|
||||
type OArr<V> = mobx.IObservableArray<V>;
|
||||
type OMap<K,V> = mobx.ObservableMap<K,V>;
|
||||
type OMap<K, V> = mobx.ObservableMap<K, V>;
|
||||
|
||||
const LinesVisiblePadding = 500;
|
||||
|
||||
type ScreenInterface = {
|
||||
setAnchorFields(anchorLine : number, anchorOffset : number, reason : string) : void,
|
||||
getSelectedLine() : number,
|
||||
getAnchor() : {anchorLine : number, anchorOffset : number},
|
||||
}
|
||||
setAnchorFields(anchorLine: number, anchorOffset: number, reason: string): void;
|
||||
getSelectedLine(): number;
|
||||
getAnchor(): { anchorLine: number; anchorOffset: number };
|
||||
};
|
||||
|
||||
// <Line key={line.lineid} line={line} screen={screen} width={width} visible={this.visibleMap.get(lineNumStr)} staticRender={this.staticRender.get()} onHeightChange={this.onHeightChange} overrideCollapsed={this.collapsedMap.get(lineNumStr)} topBorder={topBorder} renderMode={renderMode}/>;
|
||||
|
||||
type LineCompFactory = (props : T.LineFactoryProps) => JSX.Element;
|
||||
type LineCompFactory = (props: T.LineFactoryProps) => JSX.Element;
|
||||
|
||||
@mobxReact.observer
|
||||
class LinesView extends React.Component<{screen : ScreenInterface, width : number, lines : T.LineInterface[], renderMode : T.RenderModeType, lineFactory : LineCompFactory}, {}> {
|
||||
rszObs : ResizeObserver;
|
||||
linesRef : React.RefObject<any>;
|
||||
staticRender : OV<boolean> = mobx.observable.box(true, {name: "static-render"});
|
||||
lastOffsetHeight : number = 0;
|
||||
lastOffsetWidth : number = 0;
|
||||
ignoreNextScroll : boolean = false;
|
||||
visibleMap : Map<string, OV<boolean>>; // linenum => OV<vis>
|
||||
collapsedMap : Map<string, OV<boolean>>; // linenum => OV<collapsed>
|
||||
lastLinesLength : number = 0;
|
||||
lastSelectedLine : number = 0;
|
||||
class LinesView extends React.Component<
|
||||
{
|
||||
screen: ScreenInterface;
|
||||
width: number;
|
||||
lines: T.LineInterface[];
|
||||
renderMode: T.RenderModeType;
|
||||
lineFactory: LineCompFactory;
|
||||
},
|
||||
{}
|
||||
> {
|
||||
rszObs: ResizeObserver;
|
||||
linesRef: React.RefObject<any>;
|
||||
staticRender: OV<boolean> = mobx.observable.box(true, { name: "static-render" });
|
||||
lastOffsetHeight: number = 0;
|
||||
lastOffsetWidth: number = 0;
|
||||
ignoreNextScroll: boolean = false;
|
||||
visibleMap: Map<string, OV<boolean>>; // linenum => OV<vis>
|
||||
collapsedMap: Map<string, OV<boolean>>; // linenum => OV<collapsed>
|
||||
lastLinesLength: number = 0;
|
||||
lastSelectedLine: number = 0;
|
||||
|
||||
computeAnchorLine_throttled : () => void;
|
||||
computeVisibleMap_debounced : () => void;
|
||||
computeAnchorLine_throttled: () => void;
|
||||
computeVisibleMap_debounced: () => void;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.linesRef = React.createRef();
|
||||
this.computeAnchorLine_throttled = throttle(100, this.computeAnchorLine.bind(this), {noLeading: true, noTrailing: false});
|
||||
this.computeAnchorLine_throttled = throttle(100, this.computeAnchorLine.bind(this), {
|
||||
noLeading: true,
|
||||
noTrailing: false,
|
||||
});
|
||||
this.visibleMap = new Map();
|
||||
this.collapsedMap = new Map();
|
||||
this.computeVisibleMap_debounced = debounce(1000, this.computeVisibleMap.bind(this));
|
||||
}
|
||||
|
||||
|
||||
@boundMethod
|
||||
scrollHandler() {
|
||||
let linesElem = this.linesRef.current;
|
||||
@ -74,8 +86,8 @@ class LinesView extends React.Component<{screen : ScreenInterface, width : numbe
|
||||
this.computeAnchorLine_throttled(); // only do this when we're not ignoring the scroll
|
||||
}
|
||||
|
||||
computeAnchorLine() : void {
|
||||
let {screen} = this.props;
|
||||
computeAnchorLine(): void {
|
||||
let { screen } = this.props;
|
||||
let linesElem = this.linesRef.current;
|
||||
if (linesElem == null) {
|
||||
screen.setAnchorFields(null, 0, "no-lines");
|
||||
@ -89,8 +101,8 @@ class LinesView extends React.Component<{screen : ScreenInterface, width : numbe
|
||||
let scrollTop = linesElem.scrollTop;
|
||||
let height = linesElem.clientHeight;
|
||||
let containerBottom = scrollTop + height;
|
||||
let anchorElem : HTMLElement = null;
|
||||
for (let i=lineElemArr.length-1; i >= 0; i--) {
|
||||
let anchorElem: HTMLElement = null;
|
||||
for (let i = lineElemArr.length - 1; i >= 0; i--) {
|
||||
let lineElem = lineElemArr[i];
|
||||
let bottomPos = lineElem.offsetTop + lineElem.offsetHeight;
|
||||
if (anchorElem == null && (bottomPos <= containerBottom || lineElem.offsetTop <= scrollTop)) {
|
||||
@ -106,26 +118,26 @@ class LinesView extends React.Component<{screen : ScreenInterface, width : numbe
|
||||
screen.setAnchorFields(anchorLineNum, anchorOffset, "computeAnchorLine");
|
||||
}
|
||||
|
||||
computeVisibleMap() : void {
|
||||
computeVisibleMap(): void {
|
||||
let linesElem = this.linesRef.current;
|
||||
if (linesElem == null) {
|
||||
return;
|
||||
}
|
||||
if (linesElem.offsetParent == null) {
|
||||
return; // handles when parent is set to display:none (is-hidden)
|
||||
return; // handles when parent is set to display:none (is-hidden)
|
||||
}
|
||||
let lineElemArr = linesElem.querySelectorAll(".line");
|
||||
if (lineElemArr == null) {
|
||||
return;
|
||||
}
|
||||
if (linesElem.clientHeight == 0) {
|
||||
return; // when linesElem is collapsed (or display:none)
|
||||
return; // when linesElem is collapsed (or display:none)
|
||||
}
|
||||
let containerTop = linesElem.scrollTop - LinesVisiblePadding;
|
||||
let containerBot = linesElem.scrollTop + linesElem.clientHeight + LinesVisiblePadding;
|
||||
let newMap = new Map<string, boolean>();
|
||||
// console.log("computevismap", linesElem.scrollTop, linesElem.clientHeight, containerTop + "-" + containerBot);
|
||||
for (let i=0; i<lineElemArr.length; i++) {
|
||||
for (let i = 0; i < lineElemArr.length; i++) {
|
||||
let lineElem = lineElemArr[i];
|
||||
let lineTop = lineElem.offsetTop;
|
||||
let lineBot = lineElem.offsetTop + lineElem.offsetHeight;
|
||||
@ -134,7 +146,7 @@ class LinesView extends React.Component<{screen : ScreenInterface, width : numbe
|
||||
isVis = true;
|
||||
}
|
||||
if (lineBot >= containerTop && lineBot <= containerBot) {
|
||||
isVis = true
|
||||
isVis = true;
|
||||
}
|
||||
// console.log("line", lineElem.dataset.linenum, "top=" + lineTop, "bot=" + lineTop, isVis);
|
||||
let lineNumInt = parseInt(lineElem.dataset.linenum);
|
||||
@ -146,7 +158,7 @@ class LinesView extends React.Component<{screen : ScreenInterface, width : numbe
|
||||
for (let [k, v] of newMap) {
|
||||
let oldVal = this.visibleMap.get(k);
|
||||
if (oldVal == null) {
|
||||
oldVal = mobx.observable.box(v, {name: "lines-vis-map"});
|
||||
oldVal = mobx.observable.box(v, { name: "lines-vis-map" });
|
||||
this.visibleMap.set(k, oldVal);
|
||||
}
|
||||
if (oldVal.get() != v) {
|
||||
@ -161,11 +173,11 @@ class LinesView extends React.Component<{screen : ScreenInterface, width : numbe
|
||||
})();
|
||||
}
|
||||
|
||||
printVisMap() : void {
|
||||
printVisMap(): void {
|
||||
let visMap = this.visibleMap;
|
||||
let lines = this.props.lines;
|
||||
let visLines : string[] = [];
|
||||
for (let i=0; i<lines.length; i++) {
|
||||
let visLines: string[] = [];
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
let linenum = String(lines[i].linenum);
|
||||
if (visMap.get(linenum).get()) {
|
||||
visLines.push(linenum);
|
||||
@ -174,18 +186,18 @@ class LinesView extends React.Component<{screen : ScreenInterface, width : numbe
|
||||
console.log("vislines", visLines);
|
||||
}
|
||||
|
||||
restoreAnchorOffset(reason : string) : void {
|
||||
let {lines} = this.props;
|
||||
restoreAnchorOffset(reason: string): void {
|
||||
let { lines } = this.props;
|
||||
let linesElem = this.linesRef.current;
|
||||
if (linesElem == null) {
|
||||
return;
|
||||
}
|
||||
let anchor = this.getAnchor();
|
||||
let anchorElem = linesElem.querySelector(sprintf(".line[data-linenum=\"%d\"]", anchor.anchorLine));
|
||||
let anchorElem = linesElem.querySelector(sprintf('.line[data-linenum="%d"]', anchor.anchorLine));
|
||||
if (anchorElem == null) {
|
||||
return;
|
||||
}
|
||||
let isLastLine = (anchor.anchorIndex == lines.length-1);
|
||||
let isLastLine = anchor.anchorIndex == lines.length - 1;
|
||||
let scrollTop = linesElem.scrollTop;
|
||||
let height = linesElem.clientHeight;
|
||||
let containerBottom = scrollTop + height;
|
||||
@ -203,17 +215,16 @@ class LinesView extends React.Component<{screen : ScreenInterface, width : numbe
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() : void {
|
||||
let {screen, lines} = this.props;
|
||||
componentDidMount(): void {
|
||||
let { screen, lines } = this.props;
|
||||
let linesElem = this.linesRef.current;
|
||||
let anchor = this.getAnchor();
|
||||
if (anchor.anchorIndex == lines.length-1) {
|
||||
if (anchor.anchorIndex == lines.length - 1) {
|
||||
if (linesElem != null) {
|
||||
linesElem.scrollTop = linesElem.scrollHeight;
|
||||
}
|
||||
this.computeAnchorLine();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
this.restoreAnchorOffset("re-mount");
|
||||
}
|
||||
this.lastSelectedLine = screen.getSelectedLine();
|
||||
@ -225,21 +236,21 @@ class LinesView extends React.Component<{screen : ScreenInterface, width : numbe
|
||||
this.rszObs.observe(linesElem);
|
||||
}
|
||||
mobx.action(() => {
|
||||
this.staticRender.set(false)
|
||||
this.staticRender.set(false);
|
||||
this.computeVisibleMap();
|
||||
})();
|
||||
}
|
||||
|
||||
getLineElem(lineNum : number) : HTMLElement {
|
||||
getLineElem(lineNum: number): HTMLElement {
|
||||
let linesElem = this.linesRef.current;
|
||||
if (linesElem == null) {
|
||||
return null;
|
||||
}
|
||||
let elem = linesElem.querySelector(sprintf(".line[data-linenum=\"%d\"]", lineNum));
|
||||
let elem = linesElem.querySelector(sprintf('.line[data-linenum="%d"]', lineNum));
|
||||
return elem;
|
||||
}
|
||||
|
||||
getLineViewInfo(lineNum : number) : {height: number, topOffset: number, botOffset: number, anchorOffset: number} {
|
||||
getLineViewInfo(lineNum: number): { height: number; topOffset: number; botOffset: number; anchorOffset: number } {
|
||||
let linesElem = this.linesRef.current;
|
||||
if (linesElem == null) {
|
||||
return null;
|
||||
@ -260,22 +271,20 @@ class LinesView extends React.Component<{screen : ScreenInterface, width : numbe
|
||||
let lineBot = lineElem.offsetTop + lineElem.offsetHeight;
|
||||
if (lineTop < containerTop) {
|
||||
rtn.topOffset = lineTop - containerTop;
|
||||
}
|
||||
else if (lineTop > containerBot) {
|
||||
} else if (lineTop > containerBot) {
|
||||
rtn.topOffset = lineTop - containerBot;
|
||||
}
|
||||
if (lineBot < containerTop) {
|
||||
rtn.botOffset = lineBot - containerTop;
|
||||
}
|
||||
else if (lineBot > containerBot) {
|
||||
} else if (lineBot > containerBot) {
|
||||
rtn.botOffset = lineBot - containerBot;
|
||||
}
|
||||
rtn.anchorOffset = containerBot - lineBot;
|
||||
return rtn;
|
||||
}
|
||||
|
||||
updateSelectedLine() : void {
|
||||
let {screen, lines} = this.props;
|
||||
updateSelectedLine(): void {
|
||||
let { screen, lines } = this.props;
|
||||
let linesElem = this.linesRef.current;
|
||||
if (linesElem == null) {
|
||||
return null;
|
||||
@ -288,45 +297,41 @@ class LinesView extends React.Component<{screen : ScreenInterface, width : numbe
|
||||
this.setLineVisible(newLine, true);
|
||||
// console.log("update selected line", this.lastSelectedLine, "=>", newLine, sprintf("anchor=%d:%d", screen.anchorLine, screen.anchorOffset));
|
||||
let viewInfo = this.getLineViewInfo(newLine);
|
||||
let isFirst = (lidx.index == 0);
|
||||
let isLast = (lidx.index == lines.length-1);
|
||||
let offsetDelta = (isLast ? 10 : (isFirst ? -28 : 0));
|
||||
let isFirst = lidx.index == 0;
|
||||
let isLast = lidx.index == lines.length - 1;
|
||||
let offsetDelta = isLast ? 10 : isFirst ? -28 : 0;
|
||||
if (viewInfo == null) {
|
||||
screen.setAnchorFields(newLine, 0+offsetDelta, "updateSelectedLine");
|
||||
}
|
||||
else if (viewInfo.botOffset > 0) {
|
||||
screen.setAnchorFields(newLine, 0 + offsetDelta, "updateSelectedLine");
|
||||
} else if (viewInfo.botOffset > 0) {
|
||||
linesElem.scrollTop = linesElem.scrollTop + viewInfo.botOffset + offsetDelta;
|
||||
this.ignoreNextScroll = true;
|
||||
screen.setAnchorFields(newLine, offsetDelta, "updateSelectedLine");
|
||||
}
|
||||
else if (viewInfo.topOffset < 0) {
|
||||
} else if (viewInfo.topOffset < 0) {
|
||||
linesElem.scrollTop = linesElem.scrollTop + viewInfo.topOffset + offsetDelta;
|
||||
this.ignoreNextScroll = true;
|
||||
let newOffset = linesElem.clientHeight - viewInfo.height;
|
||||
screen.setAnchorFields(newLine, newOffset, "updateSelectedLine");
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
screen.setAnchorFields(newLine, viewInfo.anchorOffset, "updateSelectedLine");
|
||||
}
|
||||
// console.log("new anchor", screen.getAnchorStr());
|
||||
}
|
||||
|
||||
setLineVisible(lineNum : number, vis : boolean) : void {
|
||||
setLineVisible(lineNum: number, vis: boolean): void {
|
||||
mobx.action(() => {
|
||||
let key = String(lineNum);
|
||||
let visObj = this.visibleMap.get(key);
|
||||
if (visObj == null) {
|
||||
visObj = mobx.observable.box(true, {name: "lines-vis-map"});
|
||||
visObj = mobx.observable.box(true, { name: "lines-vis-map" });
|
||||
this.visibleMap.set(key, visObj);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
visObj.set(true);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState, snapshot) : void {
|
||||
let {screen, lines} = this.props;
|
||||
componentDidUpdate(prevProps, prevState, snapshot): void {
|
||||
let { screen, lines } = this.props;
|
||||
if (screen.getSelectedLine() != this.lastSelectedLine) {
|
||||
this.updateSelectedLine();
|
||||
this.lastSelectedLine = screen.getSelectedLine();
|
||||
@ -335,13 +340,13 @@ class LinesView extends React.Component<{screen : ScreenInterface, width : numbe
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() : void {
|
||||
componentWillUnmount(): void {
|
||||
if (this.rszObs != null) {
|
||||
this.rszObs.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
handleResize(entries : any) {
|
||||
handleResize(entries: any) {
|
||||
let linesElem = this.linesRef.current;
|
||||
if (linesElem == null) {
|
||||
return;
|
||||
@ -359,7 +364,7 @@ class LinesView extends React.Component<{screen : ScreenInterface, width : numbe
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
onHeightChange(lineNum : number, newHeight : number, oldHeight : number) : void {
|
||||
onHeightChange(lineNum: number, newHeight: number, oldHeight: number): void {
|
||||
if (oldHeight == null) {
|
||||
return;
|
||||
}
|
||||
@ -368,81 +373,87 @@ class LinesView extends React.Component<{screen : ScreenInterface, width : numbe
|
||||
this.computeVisibleMap_debounced();
|
||||
}
|
||||
|
||||
hasTopBorder(lines : T.LineInterface[], idx : number) : boolean {
|
||||
hasTopBorder(lines: T.LineInterface[], idx: number): boolean {
|
||||
if (idx == 0) {
|
||||
return false;
|
||||
}
|
||||
let curLineNumStr = String(lines[idx].linenum);
|
||||
let prevLineNumStr = String(lines[idx-1].linenum);
|
||||
let prevLineNumStr = String(lines[idx - 1].linenum);
|
||||
return !this.collapsedMap.get(curLineNumStr).get() || !this.collapsedMap.get(prevLineNumStr).get();
|
||||
}
|
||||
|
||||
getDateSepStr(lines : T.LineInterface[], idx : number, prevStr : string, todayStr : string, yesterdayStr : string) : string {
|
||||
getDateSepStr(
|
||||
lines: T.LineInterface[],
|
||||
idx: number,
|
||||
prevStr: string,
|
||||
todayStr: string,
|
||||
yesterdayStr: string
|
||||
): string {
|
||||
let curLineDate = new Date(lines[idx].ts);
|
||||
let curLineFormat = dayjs(curLineDate).format("ddd YYYY-MM-DD");
|
||||
if (idx == 0) {
|
||||
return ;
|
||||
return;
|
||||
}
|
||||
let prevLineDate = new Date(lines[idx].ts);
|
||||
let prevLineFormat = dayjs(prevLineDate).format("YYYY-MM-DD");
|
||||
return null;
|
||||
}
|
||||
|
||||
findClosestLineIndex(lineNum : number) : {line : T.LineInterface, index : number} {
|
||||
let {lines} = this.props;
|
||||
findClosestLineIndex(lineNum: number): { line: T.LineInterface; index: number } {
|
||||
let { lines } = this.props;
|
||||
if (lines.length == 0) {
|
||||
throw new Error("invalid lines, cannot have 0 length in LinesView");
|
||||
}
|
||||
if (lineNum == null || lineNum == 0) {
|
||||
return {line: lines[lines.length-1], index: lines.length-1};
|
||||
return { line: lines[lines.length - 1], index: lines.length - 1 };
|
||||
}
|
||||
// todo: bsearch
|
||||
// lines is sorted by linenum
|
||||
for (let idx=0; idx<lines.length; idx++) {
|
||||
for (let idx = 0; idx < lines.length; idx++) {
|
||||
let line = lines[idx];
|
||||
if (line.linenum >= lineNum) {
|
||||
return {line: line, index: idx};
|
||||
return { line: line, index: idx };
|
||||
}
|
||||
}
|
||||
return {line: lines[lines.length-1], index: lines.length-1};
|
||||
return { line: lines[lines.length - 1], index: lines.length - 1 };
|
||||
}
|
||||
|
||||
getAnchor() : {anchorLine : number, anchorOffset : number, anchorIndex : number} {
|
||||
let {screen, lines} = this.props;
|
||||
getAnchor(): { anchorLine: number; anchorOffset: number; anchorIndex: number } {
|
||||
let { screen, lines } = this.props;
|
||||
let anchor = screen.getAnchor();
|
||||
if (anchor.anchorLine == null || anchor.anchorLine == 0) {
|
||||
return {anchorLine: lines[lines.length-1].linenum, anchorOffset: 0, anchorIndex: lines.length-1};
|
||||
return { anchorLine: lines[lines.length - 1].linenum, anchorOffset: 0, anchorIndex: lines.length - 1 };
|
||||
}
|
||||
let lidx = this.findClosestLineIndex(anchor.anchorLine);
|
||||
if (lidx.line.linenum == anchor.anchorLine) {
|
||||
return {anchorLine: anchor.anchorLine, anchorOffset: anchor.anchorOffset, anchorIndex: lidx.index};
|
||||
return { anchorLine: anchor.anchorLine, anchorOffset: anchor.anchorOffset, anchorIndex: lidx.index };
|
||||
}
|
||||
return {anchorLine: lidx.line.linenum, anchorOffset: 0, anchorIndex: lidx.index};
|
||||
return { anchorLine: lidx.line.linenum, anchorOffset: 0, anchorIndex: lidx.index };
|
||||
}
|
||||
render() {
|
||||
let {screen, width, lines, renderMode} = this.props;
|
||||
let selectedLine = screen.getSelectedLine(); // for re-rendering
|
||||
let line : T.LineInterface = null;
|
||||
for (let i=0; i<lines.length; i++) {
|
||||
let { screen, width, lines, renderMode } = this.props;
|
||||
let selectedLine = screen.getSelectedLine(); // for re-rendering
|
||||
let line: T.LineInterface = null;
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
let key = String(lines[i].linenum);
|
||||
let visObs = this.visibleMap.get(key);
|
||||
if (visObs == null) {
|
||||
this.visibleMap.set(key, mobx.observable.box(false, {name: "lines-vis-map"}));
|
||||
this.visibleMap.set(key, mobx.observable.box(false, { name: "lines-vis-map" }));
|
||||
}
|
||||
let collObs = this.collapsedMap.get(key);
|
||||
if (collObs == null) {
|
||||
this.collapsedMap.set(key, mobx.observable.box(false, {name: "lines-collapsed-map"}));
|
||||
this.collapsedMap.set(key, mobx.observable.box(false, { name: "lines-collapsed-map" }));
|
||||
}
|
||||
}
|
||||
let lineElements : any = [];
|
||||
let lineElements: any = [];
|
||||
let todayStr = util.getTodayStr();
|
||||
let yesterdayStr = util.getYesterdayStr();
|
||||
let prevDateStr : string = null;
|
||||
let prevDateStr: string = null;
|
||||
let anchor = this.getAnchor();
|
||||
let startIdx = util.boundInt(anchor.anchorIndex-50, 0, lines.length-1);
|
||||
let endIdx = util.boundInt(anchor.anchorIndex+50, 0, lines.length-1);
|
||||
let startIdx = util.boundInt(anchor.anchorIndex - 50, 0, lines.length - 1);
|
||||
let endIdx = util.boundInt(anchor.anchorIndex + 50, 0, lines.length - 1);
|
||||
// console.log("render", anchor, "[" + startIdx + "," + endIdx + "]");
|
||||
for (let idx=startIdx; idx <= endIdx; idx++) {
|
||||
for (let idx = startIdx; idx <= endIdx; idx++) {
|
||||
let line = lines[idx];
|
||||
let lineNumStr = String(line.linenum);
|
||||
let dateSepStr = null;
|
||||
@ -452,10 +463,14 @@ class LinesView extends React.Component<{screen : ScreenInterface, width : numbe
|
||||
}
|
||||
prevDateStr = curDateStr;
|
||||
if (dateSepStr != null) {
|
||||
let sepElem = (<div key={"sep-" + line.lineid} className="line-sep">{dateSepStr}</div>);
|
||||
let sepElem = (
|
||||
<div key={"sep-" + line.lineid} className="line-sep">
|
||||
{dateSepStr}
|
||||
</div>
|
||||
);
|
||||
lineElements.push(sepElem);
|
||||
}
|
||||
let topBorder = (dateSepStr == null) && this.hasTopBorder(lines, idx);
|
||||
let topBorder = dateSepStr == null && this.hasTopBorder(lines, idx);
|
||||
let lineProps = {
|
||||
key: line.lineid,
|
||||
line: line,
|
||||
@ -471,10 +486,7 @@ class LinesView extends React.Component<{screen : ScreenInterface, width : numbe
|
||||
// let lineElem = <Line key={line.lineid} line={line} screen={screen} width={width} visible={this.visibleMap.get(lineNumStr)} staticRender={this.staticRender.get()} onHeightChange={this.onHeightChange} overrideCollapsed={this.collapsedMap.get(lineNumStr)} topBorder={topBorder} renderMode={renderMode}/>;
|
||||
lineElements.push(lineElem);
|
||||
}
|
||||
let linesClass = cn(
|
||||
"lines",
|
||||
(renderMode == "normal" ? "lines-expanded" : "lines-collapsed"),
|
||||
);
|
||||
let linesClass = cn("lines", renderMode == "normal" ? "lines-expanded" : "lines-collapsed");
|
||||
return (
|
||||
<div key="lines" className={linesClass} onScroll={this.scrollHandler} ref={this.linesRef}>
|
||||
<div className="lines-spacer"></div>
|
||||
@ -484,4 +496,4 @@ class LinesView extends React.Component<{screen : ScreenInterface, width : numbe
|
||||
}
|
||||
}
|
||||
|
||||
export {LinesView};
|
||||
export { LinesView };
|
||||
|
@ -1,18 +1,18 @@
|
||||
import dayjs from "dayjs";
|
||||
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||
import {isBlank, getDateStr} from "./util";
|
||||
import {LineType, WebLine, RendererContext} from "./types";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import { isBlank, getDateStr } from "./util";
|
||||
import { LineType, WebLine, RendererContext } from "./types";
|
||||
|
||||
dayjs.extend(localizedFormat)
|
||||
dayjs.extend(localizedFormat);
|
||||
|
||||
function getRendererType(line : LineType|WebLine) : "terminal" | "plugin" {
|
||||
function getRendererType(line: LineType | WebLine): "terminal" | "plugin" {
|
||||
if (isBlank(line.renderer) || line.renderer == "terminal") {
|
||||
return "terminal";
|
||||
}
|
||||
return "plugin";
|
||||
}
|
||||
|
||||
function getLineDateStr(todayDate : string, yesterdayDate : string, ts : number) : string {
|
||||
function getLineDateStr(todayDate: string, yesterdayDate: string, ts: number): string {
|
||||
let lineDate = new Date(ts);
|
||||
let dateStr = getDateStr(lineDate);
|
||||
if (dateStr == todayDate) {
|
||||
@ -24,36 +24,34 @@ function getLineDateStr(todayDate : string, yesterdayDate : string, ts : number)
|
||||
return dateStr;
|
||||
}
|
||||
|
||||
function getLineDateTimeStr(ts : number) : string {
|
||||
function getLineDateTimeStr(ts: number): string {
|
||||
let lineDate = new Date(ts);
|
||||
let nowDate = new Date();
|
||||
|
||||
|
||||
if (nowDate.getFullYear() != lineDate.getFullYear()) {
|
||||
return dayjs(lineDate).format("ddd L LTS");
|
||||
}
|
||||
else if (nowDate.getMonth() != lineDate.getMonth() || nowDate.getDate() != lineDate.getDate()) {
|
||||
let yesterdayDate = (new Date());
|
||||
yesterdayDate.setDate(yesterdayDate.getDate()-1);
|
||||
} else if (nowDate.getMonth() != lineDate.getMonth() || nowDate.getDate() != lineDate.getDate()) {
|
||||
let yesterdayDate = new Date();
|
||||
yesterdayDate.setDate(yesterdayDate.getDate() - 1);
|
||||
if (yesterdayDate.getMonth() == lineDate.getMonth() && yesterdayDate.getDate() == lineDate.getDate()) {
|
||||
return "Yesterday " + dayjs(lineDate).format("LTS");;
|
||||
return "Yesterday " + dayjs(lineDate).format("LTS");
|
||||
}
|
||||
return dayjs(lineDate).format("ddd L LTS");
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return dayjs(lineDate).format("LTS");
|
||||
}
|
||||
}
|
||||
|
||||
function isMultiLineCmdText(cmdText : string) : boolean {
|
||||
function isMultiLineCmdText(cmdText: string): boolean {
|
||||
if (cmdText == null) {
|
||||
return false;
|
||||
}
|
||||
cmdText = cmdText.trim();
|
||||
let nlIdx = cmdText.indexOf("\n");
|
||||
return (nlIdx != -1);
|
||||
return nlIdx != -1;
|
||||
}
|
||||
|
||||
function getFullCmdText(cmdText : string) {
|
||||
function getFullCmdText(cmdText: string) {
|
||||
if (cmdText == null) {
|
||||
return "(none)";
|
||||
}
|
||||
@ -61,7 +59,7 @@ function getFullCmdText(cmdText : string) {
|
||||
return cmdText;
|
||||
}
|
||||
|
||||
function getSingleLineCmdText(cmdText : string) {
|
||||
function getSingleLineCmdText(cmdText: string) {
|
||||
if (cmdText == null) {
|
||||
return "(none)";
|
||||
}
|
||||
@ -73,7 +71,7 @@ function getSingleLineCmdText(cmdText : string) {
|
||||
return cmdText;
|
||||
}
|
||||
|
||||
function getRendererContext(line : LineType) : RendererContext {
|
||||
function getRendererContext(line: LineType): RendererContext {
|
||||
return {
|
||||
screenId: line.screenid,
|
||||
lineId: line.lineid,
|
||||
@ -81,7 +79,7 @@ function getRendererContext(line : LineType) : RendererContext {
|
||||
};
|
||||
}
|
||||
|
||||
function getWebRendererContext(line : WebLine) : RendererContext {
|
||||
function getWebRendererContext(line: WebLine): RendererContext {
|
||||
return {
|
||||
screenId: line.screenid,
|
||||
lineId: line.lineid,
|
||||
@ -89,8 +87,18 @@ function getWebRendererContext(line : WebLine) : RendererContext {
|
||||
};
|
||||
}
|
||||
|
||||
function cmdStatusIsRunning(status : string) : boolean {
|
||||
function cmdStatusIsRunning(status: string): boolean {
|
||||
return status == "running" || status == "detached";
|
||||
}
|
||||
|
||||
export {getRendererType, getLineDateStr, getLineDateTimeStr, isMultiLineCmdText, getFullCmdText, getSingleLineCmdText, getRendererContext, getWebRendererContext, cmdStatusIsRunning};
|
||||
export {
|
||||
getRendererType,
|
||||
getLineDateStr,
|
||||
getLineDateTimeStr,
|
||||
isMultiLineCmdText,
|
||||
getFullCmdText,
|
||||
getSingleLineCmdText,
|
||||
getRendererContext,
|
||||
getWebRendererContext,
|
||||
cmdStatusIsRunning,
|
||||
};
|
||||
|
1092
src/main.tsx
1092
src/main.tsx
File diff suppressed because it is too large
Load Diff
@ -4,7 +4,7 @@
|
||||
z-index: 105;
|
||||
|
||||
.modal-background {
|
||||
background-color: rgba(10,10,10,.6);
|
||||
background-color: rgba(10, 10, 10, 0.6);
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,7 +35,7 @@
|
||||
background-color: black;
|
||||
height: 250px;
|
||||
overflow: auto;
|
||||
|
||||
|
||||
.ws-logline {
|
||||
.mono-font(12px);
|
||||
color: @term-white;
|
||||
@ -66,7 +66,7 @@
|
||||
|
||||
.modal.alert-modal {
|
||||
z-index: 205;
|
||||
|
||||
|
||||
footer {
|
||||
justify-content: center;
|
||||
|
||||
@ -171,7 +171,7 @@
|
||||
.modal-content {
|
||||
min-width: 850px;
|
||||
}
|
||||
|
||||
|
||||
.inner-content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@ -209,7 +209,7 @@
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&:first-child {
|
||||
border-top: 0;
|
||||
}
|
||||
@ -291,9 +291,8 @@
|
||||
i.fa-check {
|
||||
color: @term-green;
|
||||
}
|
||||
|
||||
|
||||
&.is-ok {
|
||||
|
||||
}
|
||||
|
||||
.message-row {
|
||||
@ -316,7 +315,7 @@
|
||||
.update-auth-button {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
|
||||
&:hover {
|
||||
.update-auth-button {
|
||||
visibility: visible;
|
||||
@ -328,7 +327,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.auth-editing, &.create-remote {
|
||||
&.auth-editing,
|
||||
&.create-remote {
|
||||
.settings-field.align-top {
|
||||
align-items: flex-start;
|
||||
|
||||
@ -366,11 +366,11 @@
|
||||
.settings-input input {
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
|
||||
.dropdown .dropdown-trigger button {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
|
||||
.dropdown .dropdown-item {
|
||||
font-size: 12px;
|
||||
padding: 5px 5px 5px 12px;
|
||||
@ -525,7 +525,7 @@
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
input {
|
||||
padding: 4px;
|
||||
border-radius: 3px;
|
||||
@ -534,7 +534,7 @@
|
||||
.control {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
|
||||
.tab-color-icon.color-green {
|
||||
color: desaturate(@tab-green, 30%);
|
||||
}
|
||||
|
@ -1,25 +1,25 @@
|
||||
import * as React from "react";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import {sprintf} from "sprintf-js";
|
||||
import {boundMethod} from "autobind-decorator";
|
||||
import {If, For, When, Otherwise, Choose} from "tsx-control-statements/components";
|
||||
import { sprintf } from "sprintf-js";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { If, For, When, Otherwise, Choose } from "tsx-control-statements/components";
|
||||
import cn from "classnames";
|
||||
import {GlobalModel, GlobalCommandRunner} from "./model";
|
||||
import { GlobalModel, GlobalCommandRunner } from "./model";
|
||||
import * as util from "./util";
|
||||
|
||||
type OV<V> = mobx.IObservableValue<V>;
|
||||
|
||||
class TosModal extends React.Component<{}, {}> {
|
||||
@boundMethod
|
||||
acceptTos() : void {
|
||||
acceptTos(): void {
|
||||
GlobalCommandRunner.clientAcceptTos();
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={cn("modal tos-modal prompt-modal is-active")}>
|
||||
<div className="modal-background"/>
|
||||
<div className="modal-background" />
|
||||
<div className="modal-content">
|
||||
<header>
|
||||
<div className="modal-title">Welcome to [prompt]</div>
|
||||
@ -29,20 +29,29 @@ class TosModal extends React.Component<{}, {}> {
|
||||
<p>Thank you for downloading Prompt!</p>
|
||||
<p>
|
||||
Prompt is a new terminal designed to help you save time and organize your command life.
|
||||
Prompt is currently in beta. If you'd like to give feedback, run into problems, have questions,
|
||||
or need help, please join the Prompt <a target="_blank" href={util.makeExternLink("https://discord.gg/XfvZ334gwU")}>discord server</a>.
|
||||
Prompt is currently in beta. If you'd like to give feedback, run into problems, have
|
||||
questions, or need help, please join the Prompt{" "}
|
||||
<a target="_blank" href={util.makeExternLink("https://discord.gg/XfvZ334gwU")}>
|
||||
discord server
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
<p>
|
||||
Prompt is free to use, no email or registration required (unless you're using the cloud features).
|
||||
Prompt is free to use, no email or registration required (unless you're using the cloud
|
||||
features).
|
||||
</p>
|
||||
<p>
|
||||
<a target="_blank" href={util.makeExternLink("https://www.getprompt.dev/tos.html")}>Full Terms of Service</a>
|
||||
<a target="_blank" href={util.makeExternLink("https://www.getprompt.dev/tos.html")}>
|
||||
Full Terms of Service
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
<div className="flex-spacer"/>
|
||||
<div onClick={this.acceptTos} className="button is-prompt-green is-outlined is-small">Accept Terms of Service</div>
|
||||
<div className="flex-spacer" />
|
||||
<div onClick={this.acceptTos} className="button is-prompt-green is-outlined is-small">
|
||||
Accept Terms of Service
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
@ -50,4 +59,4 @@ class TosModal extends React.Component<{}, {}> {
|
||||
}
|
||||
}
|
||||
|
||||
export {TosModal};
|
||||
export { TosModal };
|
||||
|
7680
src/model.ts
7680
src/model.ts
File diff suppressed because it is too large
Load Diff
144
src/plugins.ts
144
src/plugins.ts
@ -8,103 +8,99 @@ import { isBlank } from "./util";
|
||||
import { sprintf } from "sprintf-js";
|
||||
|
||||
const ImagePlugin: RendererPluginType = {
|
||||
name: "image",
|
||||
rendererType: "simple",
|
||||
heightType: "pixels",
|
||||
dataType: "blob",
|
||||
collapseType: "hide",
|
||||
globalCss: null,
|
||||
mimeTypes: ["image/*"],
|
||||
simpleComponent: SimpleImageRenderer,
|
||||
name: "image",
|
||||
rendererType: "simple",
|
||||
heightType: "pixels",
|
||||
dataType: "blob",
|
||||
collapseType: "hide",
|
||||
globalCss: null,
|
||||
mimeTypes: ["image/*"],
|
||||
simpleComponent: SimpleImageRenderer,
|
||||
};
|
||||
|
||||
const MarkdownPlugin: RendererPluginType = {
|
||||
name: "markdown",
|
||||
rendererType: "simple",
|
||||
heightType: "pixels",
|
||||
dataType: "blob",
|
||||
collapseType: "hide",
|
||||
globalCss: null,
|
||||
mimeTypes: ["text/markdown"],
|
||||
simpleComponent: SimpleMarkdownRenderer,
|
||||
name: "markdown",
|
||||
rendererType: "simple",
|
||||
heightType: "pixels",
|
||||
dataType: "blob",
|
||||
collapseType: "hide",
|
||||
globalCss: null,
|
||||
mimeTypes: ["text/markdown"],
|
||||
simpleComponent: SimpleMarkdownRenderer,
|
||||
};
|
||||
|
||||
const JsonPlugin: RendererPluginType = {
|
||||
name: "json",
|
||||
rendererType: "simple",
|
||||
heightType: "pixels",
|
||||
dataType: "blob",
|
||||
collapseType: "hide",
|
||||
globalCss: null,
|
||||
mimeTypes: ["application/json"],
|
||||
simpleComponent: SimpleJsonRenderer,
|
||||
name: "json",
|
||||
rendererType: "simple",
|
||||
heightType: "pixels",
|
||||
dataType: "blob",
|
||||
collapseType: "hide",
|
||||
globalCss: null,
|
||||
mimeTypes: ["application/json"],
|
||||
simpleComponent: SimpleJsonRenderer,
|
||||
};
|
||||
|
||||
const CodePlugin: RendererPluginType = {
|
||||
name: "code",
|
||||
rendererType: "simple",
|
||||
heightType: "pixels",
|
||||
dataType: "blob",
|
||||
collapseType: "hide",
|
||||
globalCss: null,
|
||||
mimeTypes: ["text/plain"],
|
||||
simpleComponent: SourceCodeRenderer,
|
||||
name: "code",
|
||||
rendererType: "simple",
|
||||
heightType: "pixels",
|
||||
dataType: "blob",
|
||||
collapseType: "hide",
|
||||
globalCss: null,
|
||||
mimeTypes: ["text/plain"],
|
||||
simpleComponent: SourceCodeRenderer,
|
||||
};
|
||||
|
||||
const OpenAIPlugin: RendererPluginType = {
|
||||
name: "openai",
|
||||
rendererType: "full",
|
||||
heightType: "pixels",
|
||||
dataType: "model",
|
||||
collapseType: "remove",
|
||||
hidePrompt: true,
|
||||
globalCss: null,
|
||||
mimeTypes: ["application/json"],
|
||||
fullComponent: OpenAIRenderer,
|
||||
modelCtor: () => new OpenAIRendererModel(),
|
||||
name: "openai",
|
||||
rendererType: "full",
|
||||
heightType: "pixels",
|
||||
dataType: "model",
|
||||
collapseType: "remove",
|
||||
hidePrompt: true,
|
||||
globalCss: null,
|
||||
mimeTypes: ["application/json"],
|
||||
fullComponent: OpenAIRenderer,
|
||||
modelCtor: () => new OpenAIRendererModel(),
|
||||
};
|
||||
|
||||
class PluginModelClass {
|
||||
rendererPlugins: RendererPluginType[] = [];
|
||||
rendererPlugins: RendererPluginType[] = [];
|
||||
|
||||
registerRendererPlugin(plugin: RendererPluginType) {
|
||||
if (isBlank(plugin.name)) {
|
||||
throw new Error("invalid plugin, no name");
|
||||
registerRendererPlugin(plugin: RendererPluginType) {
|
||||
if (isBlank(plugin.name)) {
|
||||
throw new Error("invalid plugin, no name");
|
||||
}
|
||||
if (plugin.name == "terminal" || plugin.name == "none") {
|
||||
throw new Error(sprintf("invalid plugin, name '%s' is reserved", plugin.name));
|
||||
}
|
||||
let existingPlugin = this.getRendererPluginByName(plugin.name);
|
||||
if (existingPlugin != null) {
|
||||
throw new Error(sprintf("plugin with name %s already registered", plugin.name));
|
||||
}
|
||||
this.rendererPlugins.push(plugin);
|
||||
}
|
||||
if (plugin.name == "terminal" || plugin.name == "none") {
|
||||
throw new Error(
|
||||
sprintf("invalid plugin, name '%s' is reserved", plugin.name)
|
||||
);
|
||||
}
|
||||
let existingPlugin = this.getRendererPluginByName(plugin.name);
|
||||
if (existingPlugin != null) {
|
||||
throw new Error(
|
||||
sprintf("plugin with name %s already registered", plugin.name)
|
||||
);
|
||||
}
|
||||
this.rendererPlugins.push(plugin);
|
||||
}
|
||||
|
||||
getRendererPluginByName(name: string): RendererPluginType {
|
||||
for (let i = 0; i < this.rendererPlugins.length; i++) {
|
||||
let plugin = this.rendererPlugins[i];
|
||||
if (plugin.name == name) {
|
||||
return plugin;
|
||||
}
|
||||
getRendererPluginByName(name: string): RendererPluginType {
|
||||
for (let i = 0; i < this.rendererPlugins.length; i++) {
|
||||
let plugin = this.rendererPlugins[i];
|
||||
if (plugin.name == name) {
|
||||
return plugin;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
let PluginModel: PluginModelClass = null;
|
||||
if ((window as any).PluginModel == null) {
|
||||
PluginModel = new PluginModelClass();
|
||||
PluginModel.registerRendererPlugin(ImagePlugin);
|
||||
PluginModel.registerRendererPlugin(MarkdownPlugin);
|
||||
PluginModel.registerRendererPlugin(JsonPlugin);
|
||||
PluginModel.registerRendererPlugin(CodePlugin);
|
||||
PluginModel.registerRendererPlugin(OpenAIPlugin);
|
||||
(window as any).PluginModel = PluginModel;
|
||||
PluginModel = new PluginModelClass();
|
||||
PluginModel.registerRendererPlugin(ImagePlugin);
|
||||
PluginModel.registerRendererPlugin(MarkdownPlugin);
|
||||
PluginModel.registerRendererPlugin(JsonPlugin);
|
||||
PluginModel.registerRendererPlugin(CodePlugin);
|
||||
PluginModel.registerRendererPlugin(OpenAIPlugin);
|
||||
(window as any).PluginModel = PluginModel;
|
||||
}
|
||||
|
||||
export { PluginModel };
|
||||
|
@ -1,4 +1,4 @@
|
||||
let {contextBridge, ipcRenderer} = require("electron");
|
||||
let { contextBridge, ipcRenderer } = require("electron");
|
||||
|
||||
contextBridge.exposeInMainWorld("api", {
|
||||
getId: () => ipcRenderer.sendSync("get-id"),
|
||||
|
372
src/prompt.less
372
src/prompt.less
@ -47,259 +47,259 @@
|
||||
// global settings / overrides
|
||||
|
||||
:root {
|
||||
--fa-style-family: "Font Awesome 6 Sharp";
|
||||
--fa-style-family: "Font Awesome 6 Sharp";
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#app,
|
||||
#main {
|
||||
background-color: #000;
|
||||
height: 100vh;
|
||||
background-color: #000;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.content {
|
||||
a:hover {
|
||||
color: #485fc7;
|
||||
}
|
||||
a:hover {
|
||||
color: #485fc7;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
overflow: hidden;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body::-webkit-scrollbar {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar {
|
||||
background-color: #777;
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
background-color: #777;
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-thumb {
|
||||
background: white;
|
||||
background: white;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
cursor: pointer;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
// main layout
|
||||
#main {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.main-content {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background-color: black;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
|
||||
.session-view,
|
||||
.history-view,
|
||||
.bookmarks-view {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 300px;
|
||||
position: relative;
|
||||
|
||||
&.is-hidden {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.screen-view {
|
||||
flex-grow: 1;
|
||||
border-right: 1px solid #ccc;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.window-view {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
|
||||
.rendermode-tag {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
background-color: rgba(78, 154, 6, 0.65);
|
||||
color: black;
|
||||
padding: 2px 8px 2px 4px;
|
||||
border-bottom-left-radius: 5px;
|
||||
z-index: 10;
|
||||
font-size: 12px;
|
||||
|
||||
&.is-active {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.render-mode {
|
||||
padding-top: 2px;
|
||||
font-size: 16px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
color: #ccc;
|
||||
|
||||
&:hover {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.share-tag {
|
||||
color: #ccc;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 40%;
|
||||
background-color: darken(rgb(0, 177, 10), 20%);
|
||||
padding: 2px 8px 2px 4px;
|
||||
z-index: 11;
|
||||
font-size: 12px;
|
||||
/* border-radius: 0 0 5px 5px; */
|
||||
opacity: 0.8;
|
||||
.main-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.share-tag-link {
|
||||
margin-top: 10px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.share-tag-title {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
opacity: 1;
|
||||
padding: 20px;
|
||||
width: 250px;
|
||||
border: 1px solid #ccc;
|
||||
border-top: 0;
|
||||
|
||||
.share-tag-link {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.window-empty {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
flex-direction: row;
|
||||
background-color: black;
|
||||
height: 100%;
|
||||
color: #ccc;
|
||||
.mono-font();
|
||||
|
||||
code {
|
||||
background-color: black;
|
||||
color: #4e9a06;
|
||||
.session-view,
|
||||
.history-view,
|
||||
.bookmarks-view {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 300px;
|
||||
position: relative;
|
||||
|
||||
&.is-hidden {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.should-fade {
|
||||
opacity: 1;
|
||||
animation: fade-in 2.5s;
|
||||
.screen-view {
|
||||
flex-grow: 1;
|
||||
border-right: 1px solid #ccc;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.window-view {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
|
||||
.rendermode-tag {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
background-color: rgba(78, 154, 6, 0.65);
|
||||
color: black;
|
||||
padding: 2px 8px 2px 4px;
|
||||
border-bottom-left-radius: 5px;
|
||||
z-index: 10;
|
||||
font-size: 12px;
|
||||
|
||||
&.is-active {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.render-mode {
|
||||
padding-top: 2px;
|
||||
font-size: 16px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
color: #ccc;
|
||||
|
||||
&:hover {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.share-tag {
|
||||
color: #ccc;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 40%;
|
||||
background-color: darken(rgb(0, 177, 10), 20%);
|
||||
padding: 2px 8px 2px 4px;
|
||||
z-index: 11;
|
||||
font-size: 12px;
|
||||
/* border-radius: 0 0 5px 5px; */
|
||||
opacity: 0.8;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.share-tag-link {
|
||||
margin-top: 10px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.share-tag-title {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
opacity: 1;
|
||||
padding: 20px;
|
||||
width: 250px;
|
||||
border: 1px solid #ccc;
|
||||
border-top: 0;
|
||||
|
||||
.share-tag-link {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.window-empty {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
height: 100%;
|
||||
color: #ccc;
|
||||
.mono-font();
|
||||
|
||||
code {
|
||||
background-color: black;
|
||||
color: #4e9a06;
|
||||
}
|
||||
|
||||
&.should-fade {
|
||||
opacity: 1;
|
||||
animation: fade-in 2.5s;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.remote-field .remote-status {
|
||||
top: 4px;
|
||||
top: 4px;
|
||||
}
|
||||
|
||||
.image-renderer {
|
||||
padding: 10px;
|
||||
img {
|
||||
display: block;
|
||||
}
|
||||
padding: 10px;
|
||||
img {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.renderer-container {
|
||||
.error-container {
|
||||
color: @term-red;
|
||||
font-size: 14px;
|
||||
padding: 5px;
|
||||
}
|
||||
.error-container {
|
||||
color: @term-red;
|
||||
font-size: 14px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.scroller {
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.scroller {
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
.renderer-container.code-renderer {
|
||||
margin-top: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.renderer-container.json-renderer {
|
||||
padding: 10px;
|
||||
font-size: 12px;
|
||||
padding: 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.markdown-renderer .markdown {
|
||||
padding: 5px;
|
||||
line-height: 1.5;
|
||||
width: fit-content;
|
||||
padding: 5px;
|
||||
line-height: 1.5;
|
||||
width: fit-content;
|
||||
|
||||
blockquote {
|
||||
background-color: #222;
|
||||
}
|
||||
blockquote {
|
||||
background-color: #222;
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: #222;
|
||||
font-size: 14px;
|
||||
}
|
||||
code {
|
||||
background-color: #222;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
pre {
|
||||
background-color: #222;
|
||||
margin: 2px 10px 6px 10px;
|
||||
padding: 4px 4px 4px 6px;
|
||||
}
|
||||
pre {
|
||||
background-color: #222;
|
||||
margin: 2px 10px 6px 10px;
|
||||
padding: 4px 4px 4px 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.openai-renderer {
|
||||
.openai-message {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
.openai-message {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
|
||||
.openai-role {
|
||||
color: @term-bright-green;
|
||||
font-weight: bold;
|
||||
width: 100px;
|
||||
}
|
||||
.openai-role {
|
||||
color: @term-bright-green;
|
||||
font-weight: bold;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.openai-role.openai-role-assistant {
|
||||
color: @term-bright-white;
|
||||
}
|
||||
.openai-role.openai-role-assistant {
|
||||
color: @term-bright-white;
|
||||
}
|
||||
|
||||
.openai-content-user {
|
||||
white-space: pre;
|
||||
color: white;
|
||||
}
|
||||
.openai-content-user {
|
||||
white-space: pre;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.openai-content-assistant {
|
||||
color: white;
|
||||
}
|
||||
.openai-content-assistant {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.openai-role-error {
|
||||
color: @term-bright-red;
|
||||
}
|
||||
.openai-role-error {
|
||||
color: @term-bright-red;
|
||||
}
|
||||
|
||||
.openai-content-error {
|
||||
color: @term-bright-red;
|
||||
.openai-content-error {
|
||||
color: @term-bright-red;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
import * as mobx from "mobx";
|
||||
import * as React from "react";
|
||||
import {createRoot} from 'react-dom/client';
|
||||
import {sprintf} from "sprintf-js";
|
||||
import {Terminal} from 'xterm';
|
||||
import {Main} from "./main";
|
||||
import {GlobalModel} from "./model";
|
||||
import {v4 as uuidv4} from "uuid";
|
||||
import {loadFonts} from "./util";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { sprintf } from "sprintf-js";
|
||||
import { Terminal } from "xterm";
|
||||
import { Main } from "./main";
|
||||
import { GlobalModel } from "./model";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { loadFonts } from "./util";
|
||||
|
||||
// @ts-ignore
|
||||
let VERSION = __PROMPT_VERSION__;
|
||||
@ -22,8 +22,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
let isFontLoaded = document.fonts.check("12px 'JetBrains Mono'");
|
||||
if (isFontLoaded) {
|
||||
root.render(reactElem);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
document.fonts.ready.then(() => {
|
||||
root.render(reactElem);
|
||||
});
|
||||
@ -33,4 +32,4 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
(window as any).mobx = mobx;
|
||||
(window as any).sprintf = sprintf;
|
||||
|
||||
console.log("PROMPT", VERSION, BUILD)
|
||||
console.log("PROMPT", VERSION, BUILD);
|
||||
|
@ -1,23 +1,23 @@
|
||||
import * as mobx from "mobx";
|
||||
import {incObs} from "./util";
|
||||
import { incObs } from "./util";
|
||||
|
||||
type OV<V> = mobx.IObservableValue<V>;
|
||||
type OArr<V> = mobx.IObservableArray<V>;
|
||||
type OMap<K,V> = mobx.ObservableMap<K,V>;
|
||||
type OMap<K, V> = mobx.ObservableMap<K, V>;
|
||||
|
||||
const InitialSize = 10*1024;
|
||||
const InitialSize = 10 * 1024;
|
||||
const IncreaseFactor = 1.5;
|
||||
|
||||
class PtyDataBuffer {
|
||||
ptyPos : number;
|
||||
dataVersion : mobx.IObservableValue<number>;
|
||||
brokenData : boolean;
|
||||
rawData : Uint8Array;
|
||||
dataSize : number;
|
||||
ptyPos: number;
|
||||
dataVersion: mobx.IObservableValue<number>;
|
||||
brokenData: boolean;
|
||||
rawData: Uint8Array;
|
||||
dataSize: number;
|
||||
|
||||
constructor() {
|
||||
this.ptyPos = 0;
|
||||
this.dataVersion = mobx.observable.box(0, {name: "dataVersion"});
|
||||
this.dataVersion = mobx.observable.box(0, { name: "dataVersion" });
|
||||
this._resetData();
|
||||
}
|
||||
|
||||
@ -27,15 +27,15 @@ class PtyDataBuffer {
|
||||
this.brokenData = false;
|
||||
}
|
||||
|
||||
reset() : void {
|
||||
reset(): void {
|
||||
this._resetData();
|
||||
}
|
||||
|
||||
getData() : Uint8Array {
|
||||
getData(): Uint8Array {
|
||||
return this.rawData.slice(0, this.dataSize);
|
||||
}
|
||||
|
||||
_growArray(minSize : number) : void {
|
||||
_growArray(minSize: number): void {
|
||||
let newSize = Math.round(this.rawData.length * IncreaseFactor);
|
||||
if (newSize < minSize) {
|
||||
newSize = minSize;
|
||||
@ -45,7 +45,7 @@ class PtyDataBuffer {
|
||||
this.rawData = newData;
|
||||
}
|
||||
|
||||
receiveData(pos : number, data : Uint8Array, reason? : string) : void {
|
||||
receiveData(pos: number, data: Uint8Array, reason?: string): void {
|
||||
if (pos != this.dataSize) {
|
||||
this.brokenData = true;
|
||||
return;
|
||||
@ -62,21 +62,21 @@ class PtyDataBuffer {
|
||||
const NewLineCharCode = "\n".charCodeAt(0);
|
||||
|
||||
class PacketDataBuffer extends PtyDataBuffer {
|
||||
parsePos : number;
|
||||
callback : (any) => void;
|
||||
parsePos: number;
|
||||
callback: (any) => void;
|
||||
|
||||
constructor(callback : (any) => void) {
|
||||
constructor(callback: (any) => void) {
|
||||
super();
|
||||
this.parsePos = 0;
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
reset() : void {
|
||||
reset(): void {
|
||||
super.reset();
|
||||
this.parsePos = 0;
|
||||
}
|
||||
|
||||
processLine(line : string) {
|
||||
processLine(line: string) {
|
||||
if (line.length == 0) {
|
||||
return;
|
||||
}
|
||||
@ -97,11 +97,10 @@ class PacketDataBuffer extends PtyDataBuffer {
|
||||
console.log("invalid line packet", line);
|
||||
}
|
||||
}
|
||||
let packet : any = null;
|
||||
let packet: any = null;
|
||||
try {
|
||||
packet = JSON.parse(packetStr);
|
||||
}
|
||||
catch (e) {
|
||||
} catch (e) {
|
||||
console.log("invalid line packet (bad json)", line, e);
|
||||
return;
|
||||
}
|
||||
@ -111,22 +110,24 @@ class PacketDataBuffer extends PtyDataBuffer {
|
||||
}
|
||||
|
||||
parseData() {
|
||||
for (let i=this.parsePos; i<this.dataSize; i++) {
|
||||
for (let i = this.parsePos; i < this.dataSize; i++) {
|
||||
let ch = this.rawData[i];
|
||||
if (ch == NewLineCharCode) {
|
||||
// line does *not* include the newline
|
||||
let line = (new TextDecoder()).decode(new Uint8Array(this.rawData.buffer, this.parsePos, i-this.parsePos));
|
||||
this.parsePos = i+1;
|
||||
let line = new TextDecoder().decode(
|
||||
new Uint8Array(this.rawData.buffer, this.parsePos, i - this.parsePos)
|
||||
);
|
||||
this.parsePos = i + 1;
|
||||
this.processLine(line);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
receiveData(pos : number, data : Uint8Array, reason? : string) : void {
|
||||
|
||||
receiveData(pos: number, data: Uint8Array, reason?: string): void {
|
||||
super.receiveData(pos, data, reason);
|
||||
this.parseData();
|
||||
}
|
||||
}
|
||||
|
||||
export {PtyDataBuffer, PacketDataBuffer};
|
||||
export { PtyDataBuffer, PacketDataBuffer };
|
||||
|
782
src/remotes.tsx
782
src/remotes.tsx
File diff suppressed because it is too large
Load Diff
487
src/settings.tsx
487
src/settings.tsx
@ -1,19 +1,19 @@
|
||||
import * as React from "react";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import {sprintf} from "sprintf-js";
|
||||
import {boundMethod} from "autobind-decorator";
|
||||
import {If, For, When, Otherwise, Choose} from "tsx-control-statements/components";
|
||||
import { sprintf } from "sprintf-js";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { If, For, When, Otherwise, Choose } from "tsx-control-statements/components";
|
||||
import cn from "classnames";
|
||||
import {GlobalModel, GlobalCommandRunner, TabColors} from "./model";
|
||||
import {Toggle, RemoteStatusLight, InlineSettingsTextEdit, SettingsError, InfoMessage} from "./elements";
|
||||
import {LineType, RendererPluginType, ClientDataType, CommandRtnType} from "./types";
|
||||
import {PluginModel} from "./plugins";
|
||||
import { GlobalModel, GlobalCommandRunner, TabColors } from "./model";
|
||||
import { Toggle, RemoteStatusLight, InlineSettingsTextEdit, SettingsError, InfoMessage } from "./elements";
|
||||
import { LineType, RendererPluginType, ClientDataType, CommandRtnType } from "./types";
|
||||
import { PluginModel } from "./plugins";
|
||||
import * as util from "./util";
|
||||
|
||||
type OV<V> = mobx.IObservableValue<V>;
|
||||
type OArr<V> = mobx.IObservableArray<V>;
|
||||
type OMap<K,V> = mobx.ObservableMap<K,V>;
|
||||
type OMap<K, V> = mobx.ObservableMap<K, V>;
|
||||
type CV<V> = mobx.IComputedValue<V>;
|
||||
|
||||
const RemotePtyRows = 8;
|
||||
@ -25,7 +25,6 @@ const VERSION = __PROMPT_VERSION__;
|
||||
// @ts-ignore
|
||||
const BUILD = __PROMPT_BUILD__;
|
||||
|
||||
|
||||
const ScreenDeleteMessage = `
|
||||
Are you sure you want to delete this screen/tab?
|
||||
|
||||
@ -48,7 +47,7 @@ const WebStopShareConfirmMarkdown = `
|
||||
Are you sure you want to stop web-sharing this screen?
|
||||
`.trim();
|
||||
|
||||
function commandRtnHandler(prtn : Promise<CommandRtnType>, errorMessage : OV<string>) {
|
||||
function commandRtnHandler(prtn: Promise<CommandRtnType>, errorMessage: OV<string>) {
|
||||
prtn.then((crtn) => {
|
||||
if (crtn.success) {
|
||||
return;
|
||||
@ -60,29 +59,29 @@ function commandRtnHandler(prtn : Promise<CommandRtnType>, errorMessage : OV<str
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class ScreenSettingsModal extends React.Component<{sessionId : string, screenId : string}, {}> {
|
||||
shareCopied : OV<boolean> = mobx.observable.box(false, {name: "ScreenSettings-shareCopied"});
|
||||
errorMessage : OV<string> = mobx.observable.box(null, {name: "ScreenSettings-errorMessage"});
|
||||
class ScreenSettingsModal extends React.Component<{ sessionId: string; screenId: string }, {}> {
|
||||
shareCopied: OV<boolean> = mobx.observable.box(false, { name: "ScreenSettings-shareCopied" });
|
||||
errorMessage: OV<string> = mobx.observable.box(null, { name: "ScreenSettings-errorMessage" });
|
||||
|
||||
constructor(props : any) {
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
let {sessionId, screenId} = props;
|
||||
let { sessionId, screenId } = props;
|
||||
let screen = GlobalModel.getScreenById(sessionId, screenId);
|
||||
if (screen == null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@boundMethod
|
||||
closeModal() : void {
|
||||
closeModal(): void {
|
||||
mobx.action(() => {
|
||||
GlobalModel.screenSettingsModal.set(null);
|
||||
})();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
selectTabColor(color : string) : void {
|
||||
let {sessionId, screenId} = this.props;
|
||||
selectTabColor(color: string): void {
|
||||
let { sessionId, screenId } = this.props;
|
||||
let screen = GlobalModel.getScreenById(sessionId, screenId);
|
||||
if (screen == null) {
|
||||
return;
|
||||
@ -90,13 +89,13 @@ class ScreenSettingsModal extends React.Component<{sessionId : string, screenId
|
||||
if (screen.getTabColor() == color) {
|
||||
return;
|
||||
}
|
||||
let prtn = GlobalCommandRunner.screenSetSettings(this.props.screenId, {tabcolor: color}, false);
|
||||
let prtn = GlobalCommandRunner.screenSetSettings(this.props.screenId, { tabcolor: color }, false);
|
||||
commandRtnHandler(prtn, this.errorMessage);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleChangeArchived(val : boolean) : void {
|
||||
let {sessionId, screenId} = this.props;
|
||||
handleChangeArchived(val: boolean): void {
|
||||
let { sessionId, screenId } = this.props;
|
||||
let screen = GlobalModel.getScreenById(sessionId, screenId);
|
||||
if (screen == null) {
|
||||
return;
|
||||
@ -109,8 +108,8 @@ class ScreenSettingsModal extends React.Component<{sessionId : string, screenId
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleChangeWebShare(val : boolean) : void {
|
||||
let {sessionId, screenId} = this.props;
|
||||
handleChangeWebShare(val: boolean): void {
|
||||
let { sessionId, screenId } = this.props;
|
||||
let screen = GlobalModel.getScreenById(sessionId, screenId);
|
||||
if (screen == null) {
|
||||
return;
|
||||
@ -118,8 +117,8 @@ class ScreenSettingsModal extends React.Component<{sessionId : string, screenId
|
||||
if (screen.isWebShared() == val) {
|
||||
return;
|
||||
}
|
||||
let message = (val ? WebShareConfirmMarkdown : WebStopShareConfirmMarkdown);
|
||||
let alertRtn = GlobalModel.showAlert({message: message, confirm: true, markdown: true});
|
||||
let message = val ? WebShareConfirmMarkdown : WebStopShareConfirmMarkdown;
|
||||
let alertRtn = GlobalModel.showAlert({ message: message, confirm: true, markdown: true });
|
||||
alertRtn.then((result) => {
|
||||
if (!result) {
|
||||
return;
|
||||
@ -130,8 +129,8 @@ class ScreenSettingsModal extends React.Component<{sessionId : string, screenId
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
copyShareLink() : void {
|
||||
let {sessionId, screenId} = this.props;
|
||||
copyShareLink(): void {
|
||||
let { sessionId, screenId } = this.props;
|
||||
let screen = GlobalModel.getScreenById(sessionId, screenId);
|
||||
if (screen == null) {
|
||||
return null;
|
||||
@ -148,12 +147,12 @@ class ScreenSettingsModal extends React.Component<{sessionId : string, screenId
|
||||
mobx.action(() => {
|
||||
this.shareCopied.set(false);
|
||||
})();
|
||||
}, 600)
|
||||
}, 600);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
inlineUpdateName(val : string) : void {
|
||||
let {sessionId, screenId} = this.props;
|
||||
inlineUpdateName(val: string): void {
|
||||
let { sessionId, screenId } = this.props;
|
||||
let screen = GlobalModel.getScreenById(sessionId, screenId);
|
||||
if (screen == null) {
|
||||
return;
|
||||
@ -161,13 +160,13 @@ class ScreenSettingsModal extends React.Component<{sessionId : string, screenId
|
||||
if (util.isStrEq(val, screen.name.get())) {
|
||||
return;
|
||||
}
|
||||
let prtn = GlobalCommandRunner.screenSetSettings(this.props.screenId, {name: val}, false);
|
||||
let prtn = GlobalCommandRunner.screenSetSettings(this.props.screenId, { name: val }, false);
|
||||
commandRtnHandler(prtn, this.errorMessage);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
inlineUpdateShareName(val : string) : void {
|
||||
let {sessionId, screenId} = this.props;
|
||||
inlineUpdateShareName(val: string): void {
|
||||
let { sessionId, screenId } = this.props;
|
||||
let screen = GlobalModel.getScreenById(sessionId, screenId);
|
||||
if (screen == null) {
|
||||
return;
|
||||
@ -175,26 +174,26 @@ class ScreenSettingsModal extends React.Component<{sessionId : string, screenId
|
||||
if (util.isStrEq(val, screen.getShareName())) {
|
||||
return;
|
||||
}
|
||||
let prtn = GlobalCommandRunner.screenSetSettings(this.props.screenId, {sharename: val}, false);
|
||||
let prtn = GlobalCommandRunner.screenSetSettings(this.props.screenId, { sharename: val }, false);
|
||||
commandRtnHandler(prtn, this.errorMessage);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
dismissError() : void {
|
||||
dismissError(): void {
|
||||
mobx.action(() => {
|
||||
this.errorMessage.set(null);
|
||||
})();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleDeleteScreen() : void {
|
||||
let {sessionId, screenId} = this.props;
|
||||
handleDeleteScreen(): void {
|
||||
let { sessionId, screenId } = this.props;
|
||||
let screen = GlobalModel.getScreenById(sessionId, screenId);
|
||||
if (screen == null) {
|
||||
return;
|
||||
}
|
||||
let message = ScreenDeleteMessage;
|
||||
let alertRtn = GlobalModel.showAlert({message: message, confirm: true, markdown: true});
|
||||
let alertRtn = GlobalModel.showAlert({ message: message, confirm: true, markdown: true });
|
||||
alertRtn.then((result) => {
|
||||
if (!result) {
|
||||
return;
|
||||
@ -205,59 +204,62 @@ class ScreenSettingsModal extends React.Component<{sessionId : string, screenId
|
||||
}
|
||||
|
||||
render() {
|
||||
let {sessionId, screenId} = this.props;
|
||||
let { sessionId, screenId } = this.props;
|
||||
let screen = GlobalModel.getScreenById(sessionId, screenId);
|
||||
if (screen == null) {
|
||||
return null;
|
||||
}
|
||||
let color : string = null;
|
||||
let color: string = null;
|
||||
return (
|
||||
<div className={cn("modal screen-settings-modal settings-modal prompt-modal is-active")}>
|
||||
<div className="modal-background"/>
|
||||
<div className="modal-background" />
|
||||
<div className="modal-content">
|
||||
<If condition={this.shareCopied.get()}>
|
||||
<div className="copied-indicator"/>
|
||||
<div className="copied-indicator" />
|
||||
</If>
|
||||
<header>
|
||||
<div className="modal-title">screen settings ({screen.name.get()})</div>
|
||||
<div className="close-icon">
|
||||
<i onClick={this.closeModal} className="fa-sharp fa-solid fa-times"/>
|
||||
<i onClick={this.closeModal} className="fa-sharp fa-solid fa-times" />
|
||||
</div>
|
||||
</header>
|
||||
<div className="inner-content">
|
||||
<div className="settings-field">
|
||||
<div className="settings-label">
|
||||
Screen Id
|
||||
</div>
|
||||
<div className="settings-label">Screen Id</div>
|
||||
<div className="settings-input">{screen.screenId}</div>
|
||||
</div>
|
||||
<div className="settings-field">
|
||||
<div className="settings-label">Name</div>
|
||||
<div className="settings-input">
|
||||
{screen.screenId}
|
||||
<InlineSettingsTextEdit
|
||||
placeholder="name"
|
||||
text={screen.name.get() ?? "(none)"}
|
||||
value={screen.name.get() ?? ""}
|
||||
onChange={this.inlineUpdateName}
|
||||
maxLength={50}
|
||||
showIcon={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="settings-field">
|
||||
<div className="settings-label">
|
||||
Name
|
||||
</div>
|
||||
<div className="settings-input">
|
||||
<InlineSettingsTextEdit placeholder="name" text={screen.name.get() ?? "(none)"} value={screen.name.get() ?? ""} onChange={this.inlineUpdateName} maxLength={50} showIcon={true}/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="settings-field">
|
||||
<div className="settings-label">
|
||||
Tab Color
|
||||
</div>
|
||||
<div className="settings-label">Tab Color</div>
|
||||
<div className="settings-input">
|
||||
<div className="tab-colors">
|
||||
<div className="tab-color-cur">
|
||||
<span className={cn("icon tab-color-icon", "color-" + screen.getTabColor())}>
|
||||
<i className="fa-sharp fa-solid fa-square"/>
|
||||
<i className="fa-sharp fa-solid fa-square" />
|
||||
</span>
|
||||
<span>{screen.getTabColor()}</span>
|
||||
</div>
|
||||
<div className="tab-color-sep">|</div>
|
||||
<For each="color" of={TabColors}>
|
||||
<div key={color} className="tab-color-select" onClick={() => this.selectTabColor(color)}>
|
||||
<div
|
||||
key={color}
|
||||
className="tab-color-select"
|
||||
onClick={() => this.selectTabColor(color)}
|
||||
>
|
||||
<span className={cn("tab-color-icon", "color-" + color)}>
|
||||
<i className="fa-sharp fa-solid fa-square"/>
|
||||
<i className="fa-sharp fa-solid fa-square" />
|
||||
</span>
|
||||
</div>
|
||||
</For>
|
||||
@ -268,51 +270,58 @@ class ScreenSettingsModal extends React.Component<{sessionId : string, screenId
|
||||
<div className="settings-label">
|
||||
<div>Archived</div>
|
||||
<InfoMessage width={400}>
|
||||
Archive will hide the screen tab. Commands and output will be retained in history.
|
||||
Archive will hide the screen tab. Commands and output will be retained in history.
|
||||
</InfoMessage>
|
||||
</div>
|
||||
<div className="settings-input">
|
||||
<Toggle checked={screen.archived.get()} onChange={this.handleChangeArchived}/>
|
||||
<Toggle checked={screen.archived.get()} onChange={this.handleChangeArchived} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="settings-field">
|
||||
<div className="settings-label">
|
||||
Web Sharing
|
||||
</div>
|
||||
<div className="settings-label">Web Sharing</div>
|
||||
<div className="settings-input">
|
||||
<Toggle checked={screen.isWebShared()} onChange={this.handleChangeWebShare}/>
|
||||
<Toggle checked={screen.isWebShared()} onChange={this.handleChangeWebShare} />
|
||||
</div>
|
||||
</div>
|
||||
<If condition={screen.isWebShared()}>
|
||||
<div className="settings-field sub-field">
|
||||
<div className="settings-label">
|
||||
Share Link
|
||||
</div>
|
||||
<div className="settings-label">Share Link</div>
|
||||
<div className="settings-input">
|
||||
<a href={util.makeExternLink(screen.getWebShareUrl())} target="_blank" className="button is-prompt-green is-outlined is-small a-block">
|
||||
<a
|
||||
href={util.makeExternLink(screen.getWebShareUrl())}
|
||||
target="_blank"
|
||||
className="button is-prompt-green is-outlined is-small a-block"
|
||||
>
|
||||
<span>open in browser</span>
|
||||
<span className="icon">
|
||||
<i className="fa-sharp fa-solid fa-up-right-from-square"/>
|
||||
<i className="fa-sharp fa-solid fa-up-right-from-square" />
|
||||
</span>
|
||||
</a>
|
||||
<div className="button is-prompt-green is-outlined is-small ml-4" onClick={this.copyShareLink}>
|
||||
<div
|
||||
className="button is-prompt-green is-outlined is-small ml-4"
|
||||
onClick={this.copyShareLink}
|
||||
>
|
||||
<span>copy link</span>
|
||||
<span className="icon">
|
||||
<i className="fa-sharp fa-solid fa-copy"/>
|
||||
<i className="fa-sharp fa-solid fa-copy" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="settings-field sub-field">
|
||||
<div className="settings-label">
|
||||
Share Name
|
||||
</div>
|
||||
<div className="settings-label">Share Name</div>
|
||||
<div className="settings-input">
|
||||
<div className="settings-input">
|
||||
<InlineSettingsTextEdit placeholder="name"
|
||||
text={util.isBlank(screen.getShareName()) ? "(none)" : screen.getShareName()}
|
||||
value={screen.getShareName() ?? ""}
|
||||
onChange={this.inlineUpdateShareName} maxLength={150} showIcon={true}/>
|
||||
<InlineSettingsTextEdit
|
||||
placeholder="name"
|
||||
text={
|
||||
util.isBlank(screen.getShareName()) ? "(none)" : screen.getShareName()
|
||||
}
|
||||
value={screen.getShareName() ?? ""}
|
||||
onChange={this.inlineUpdateShareName}
|
||||
maxLength={150}
|
||||
showIcon={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -325,13 +334,20 @@ class ScreenSettingsModal extends React.Component<{sessionId : string, screenId
|
||||
</InfoMessage>
|
||||
</div>
|
||||
<div className="settings-input">
|
||||
<div onClick={this.handleDeleteScreen} className="button is-prompt-danger is-outlined is-small">Delete Screen</div>
|
||||
<div
|
||||
onClick={this.handleDeleteScreen}
|
||||
className="button is-prompt-danger is-outlined is-small"
|
||||
>
|
||||
Delete Screen
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<SettingsError errorMessage={this.errorMessage}/>
|
||||
<SettingsError errorMessage={this.errorMessage} />
|
||||
</div>
|
||||
<footer>
|
||||
<div onClick={this.closeModal} className="button is-prompt-green is-outlined is-small">Close</div>
|
||||
<div onClick={this.closeModal} className="button is-prompt-green is-outlined is-small">
|
||||
Close
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
@ -340,28 +356,28 @@ class ScreenSettingsModal extends React.Component<{sessionId : string, screenId
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class SessionSettingsModal extends React.Component<{sessionId : string}, {}> {
|
||||
errorMessage : OV<string> = mobx.observable.box(null, {name: "ScreenSettings-errorMessage"});
|
||||
|
||||
constructor(props : any) {
|
||||
class SessionSettingsModal extends React.Component<{ sessionId: string }, {}> {
|
||||
errorMessage: OV<string> = mobx.observable.box(null, { name: "ScreenSettings-errorMessage" });
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
let {sessionId} = props;
|
||||
let { sessionId } = props;
|
||||
let session = GlobalModel.getSessionById(sessionId);
|
||||
if (session == null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@boundMethod
|
||||
closeModal() : void {
|
||||
closeModal(): void {
|
||||
mobx.action(() => {
|
||||
GlobalModel.sessionSettingsModal.set(null);
|
||||
})();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleInlineChangeName(newVal : string) : void {
|
||||
let {sessionId} = this.props;
|
||||
handleInlineChangeName(newVal: string): void {
|
||||
let { sessionId } = this.props;
|
||||
let session = GlobalModel.getSessionById(sessionId);
|
||||
if (session == null) {
|
||||
return;
|
||||
@ -369,13 +385,13 @@ class SessionSettingsModal extends React.Component<{sessionId : string}, {}> {
|
||||
if (util.isStrEq(newVal, session.name.get())) {
|
||||
return;
|
||||
}
|
||||
let prtn = GlobalCommandRunner.sessionSetSettings(this.props.sessionId, {"name": newVal}, false);
|
||||
let prtn = GlobalCommandRunner.sessionSetSettings(this.props.sessionId, { name: newVal }, false);
|
||||
commandRtnHandler(prtn, this.errorMessage);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleChangeArchived(val : boolean) : void {
|
||||
let {sessionId} = this.props;
|
||||
handleChangeArchived(val: boolean): void {
|
||||
let { sessionId } = this.props;
|
||||
let session = GlobalModel.getSessionById(sessionId);
|
||||
if (session == null) {
|
||||
return;
|
||||
@ -388,10 +404,10 @@ class SessionSettingsModal extends React.Component<{sessionId : string}, {}> {
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleDeleteSession() : void {
|
||||
let {sessionId} = this.props;
|
||||
handleDeleteSession(): void {
|
||||
let { sessionId } = this.props;
|
||||
let message = SessionDeleteMessage;
|
||||
let alertRtn = GlobalModel.showAlert({message: message, confirm: true, markdown: true});
|
||||
let alertRtn = GlobalModel.showAlert({ message: message, confirm: true, markdown: true });
|
||||
alertRtn.then((result) => {
|
||||
if (!result) {
|
||||
return;
|
||||
@ -402,46 +418,52 @@ class SessionSettingsModal extends React.Component<{sessionId : string}, {}> {
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
dismissError() : void {
|
||||
dismissError(): void {
|
||||
mobx.action(() => {
|
||||
this.errorMessage.set(null);
|
||||
})();
|
||||
}
|
||||
|
||||
render() {
|
||||
let {sessionId} = this.props;
|
||||
let { sessionId } = this.props;
|
||||
let session = GlobalModel.getSessionById(sessionId);
|
||||
if (session == null) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div className={cn("modal session-settings-modal settings-modal prompt-modal is-active")}>
|
||||
<div className="modal-background"/>
|
||||
<div className="modal-background" />
|
||||
<div className="modal-content">
|
||||
<header>
|
||||
<div className="modal-title">session settings ({session.name.get()})</div>
|
||||
<div className="close-icon">
|
||||
<i onClick={this.closeModal} className="fa-sharp fa-solid fa-times"/>
|
||||
<i onClick={this.closeModal} className="fa-sharp fa-solid fa-times" />
|
||||
</div>
|
||||
</header>
|
||||
<div className="inner-content">
|
||||
<div className="settings-field">
|
||||
<div className="settings-label">
|
||||
Name
|
||||
</div>
|
||||
<div className="settings-label">Name</div>
|
||||
<div className="settings-input">
|
||||
<InlineSettingsTextEdit placeholder="name" text={session.name.get() ?? "(none)"} value={session.name.get() ?? ""} onChange={this.handleInlineChangeName} maxLength={50} showIcon={true}/>
|
||||
<InlineSettingsTextEdit
|
||||
placeholder="name"
|
||||
text={session.name.get() ?? "(none)"}
|
||||
value={session.name.get() ?? ""}
|
||||
onChange={this.handleInlineChangeName}
|
||||
maxLength={50}
|
||||
showIcon={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="settings-field">
|
||||
<div className="settings-label">
|
||||
<div>Archived</div>
|
||||
<InfoMessage width={400}>
|
||||
Archive will hide the session from the active menu. Commands and output will be retained in history.
|
||||
Archive will hide the session from the active menu. Commands and output will be
|
||||
retained in history.
|
||||
</InfoMessage>
|
||||
</div>
|
||||
<div className="settings-input">
|
||||
<Toggle checked={session.archived.get()} onChange={this.handleChangeArchived}/>
|
||||
<Toggle checked={session.archived.get()} onChange={this.handleChangeArchived} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="settings-field">
|
||||
@ -452,13 +474,20 @@ class SessionSettingsModal extends React.Component<{sessionId : string}, {}> {
|
||||
</InfoMessage>
|
||||
</div>
|
||||
<div className="settings-input">
|
||||
<div onClick={this.handleDeleteSession} className="button is-prompt-danger is-outlined is-small">Delete Session</div>
|
||||
<div
|
||||
onClick={this.handleDeleteSession}
|
||||
className="button is-prompt-danger is-outlined is-small"
|
||||
>
|
||||
Delete Session
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<SettingsError errorMessage={this.errorMessage}/>
|
||||
<SettingsError errorMessage={this.errorMessage} />
|
||||
</div>
|
||||
<footer>
|
||||
<div onClick={this.closeModal} className="button is-prompt-green is-outlined is-small">Close</div>
|
||||
<div onClick={this.closeModal} className="button is-prompt-green is-outlined is-small">
|
||||
Close
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
@ -467,19 +496,19 @@ class SessionSettingsModal extends React.Component<{sessionId : string}, {}> {
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class LineSettingsModal extends React.Component<{linenum : number}, {}> {
|
||||
rendererDropdownActive : OV<boolean> = mobx.observable.box(false, {name: "lineSettings-rendererDropdownActive"});
|
||||
errorMessage : OV<string> = mobx.observable.box(null, {name: "ScreenSettings-errorMessage"});
|
||||
|
||||
class LineSettingsModal extends React.Component<{ linenum: number }, {}> {
|
||||
rendererDropdownActive: OV<boolean> = mobx.observable.box(false, { name: "lineSettings-rendererDropdownActive" });
|
||||
errorMessage: OV<string> = mobx.observable.box(null, { name: "ScreenSettings-errorMessage" });
|
||||
|
||||
@boundMethod
|
||||
closeModal() : void {
|
||||
closeModal(): void {
|
||||
mobx.action(() => {
|
||||
GlobalModel.lineSettingsModal.set(null);
|
||||
})();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleChangeArchived(val : boolean) : void {
|
||||
handleChangeArchived(val: boolean): void {
|
||||
let line = this.getLine();
|
||||
if (line == null) {
|
||||
return;
|
||||
@ -489,13 +518,13 @@ class LineSettingsModal extends React.Component<{linenum : number}, {}> {
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
toggleRendererDropdown() : void {
|
||||
toggleRendererDropdown(): void {
|
||||
mobx.action(() => {
|
||||
this.rendererDropdownActive.set(!this.rendererDropdownActive.get());
|
||||
})();
|
||||
}
|
||||
|
||||
getLine() : LineType {
|
||||
getLine(): LineType {
|
||||
let screen = GlobalModel.getActiveScreen();
|
||||
if (screen == null) {
|
||||
return;
|
||||
@ -504,31 +533,33 @@ class LineSettingsModal extends React.Component<{linenum : number}, {}> {
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
clickSetRenderer(renderer : string) : void {
|
||||
clickSetRenderer(renderer: string): void {
|
||||
let line = this.getLine();
|
||||
if (line == null) {
|
||||
return;
|
||||
}
|
||||
let prtn = GlobalCommandRunner.lineSet(line.lineid, {renderer: renderer});
|
||||
let prtn = GlobalCommandRunner.lineSet(line.lineid, { renderer: renderer });
|
||||
commandRtnHandler(prtn, this.errorMessage);
|
||||
mobx.action(() => {
|
||||
this.rendererDropdownActive.set(false);
|
||||
})();
|
||||
}
|
||||
|
||||
renderRendererDropdown() : any {
|
||||
renderRendererDropdown(): any {
|
||||
let line = this.getLine();
|
||||
if (line == null) {
|
||||
return null;
|
||||
}
|
||||
let plugins = PluginModel.rendererPlugins;
|
||||
let plugin : RendererPluginType = null;
|
||||
let plugin: RendererPluginType = null;
|
||||
let renderer = line.renderer ?? "terminal";
|
||||
return (
|
||||
<div className={cn("dropdown", "renderer-dropdown", {"is-active": this.rendererDropdownActive.get()})}>
|
||||
<div className={cn("dropdown", "renderer-dropdown", { "is-active": this.rendererDropdownActive.get() })}>
|
||||
<div className="dropdown-trigger">
|
||||
<button onClick={this.toggleRendererDropdown} className="button is-small is-dark">
|
||||
<span><i className="fa-sharp fa-solid fa-fill"/> {renderer}</span>
|
||||
<span>
|
||||
<i className="fa-sharp fa-solid fa-fill" /> {renderer}
|
||||
</span>
|
||||
<span className="icon is-small">
|
||||
<i className="fa-sharp fa-regular fa-angle-down" aria-hidden="true"></i>
|
||||
</span>
|
||||
@ -536,11 +567,21 @@ class LineSettingsModal extends React.Component<{linenum : number}, {}> {
|
||||
</div>
|
||||
<div className="dropdown-menu" role="menu">
|
||||
<div className="dropdown-content has-background-black">
|
||||
<div onClick={() => this.clickSetRenderer(null) } key="terminal" className="dropdown-item">terminal</div>
|
||||
<div onClick={() => this.clickSetRenderer(null)} key="terminal" className="dropdown-item">
|
||||
terminal
|
||||
</div>
|
||||
<For each="plugin" of={plugins}>
|
||||
<div onClick={() => this.clickSetRenderer(plugin.name) } key={plugin.name} className="dropdown-item">{plugin.name}</div>
|
||||
<div
|
||||
onClick={() => this.clickSetRenderer(plugin.name)}
|
||||
key={plugin.name}
|
||||
className="dropdown-item"
|
||||
>
|
||||
{plugin.name}
|
||||
</div>
|
||||
</For>
|
||||
<div onClick={() => this.clickSetRenderer("none") } key="none" className="dropdown-item">none</div>
|
||||
<div onClick={() => this.clickSetRenderer("none")} key="none" className="dropdown-item">
|
||||
none
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -557,36 +598,32 @@ class LineSettingsModal extends React.Component<{linenum : number}, {}> {
|
||||
}
|
||||
return (
|
||||
<div className={cn("modal line-settings-modal settings-modal prompt-modal is-active")}>
|
||||
<div className="modal-background"/>
|
||||
<div className="modal-background" />
|
||||
<div className="modal-content">
|
||||
<header>
|
||||
<div className="modal-title">line settings ({line.linenum})</div>
|
||||
<div className="close-icon">
|
||||
<i onClick={this.closeModal} className="fa-sharp fa-solid fa-times"/>
|
||||
<i onClick={this.closeModal} className="fa-sharp fa-solid fa-times" />
|
||||
</div>
|
||||
</header>
|
||||
<div className="inner-content">
|
||||
<div className="settings-field">
|
||||
<div className="settings-label">
|
||||
Renderer
|
||||
</div>
|
||||
<div className="settings-input">
|
||||
{this.renderRendererDropdown()}
|
||||
</div>
|
||||
<div className="settings-label">Renderer</div>
|
||||
<div className="settings-input">{this.renderRendererDropdown()}</div>
|
||||
</div>
|
||||
<div className="settings-field">
|
||||
<div className="settings-label">
|
||||
Archived
|
||||
</div>
|
||||
<div className="settings-label">Archived</div>
|
||||
<div className="settings-input">
|
||||
<Toggle checked={!!line.archived} onChange={this.handleChangeArchived}/>
|
||||
<Toggle checked={!!line.archived} onChange={this.handleChangeArchived} />
|
||||
</div>
|
||||
</div>
|
||||
<SettingsError errorMessage={this.errorMessage}/>
|
||||
<div style={{height: 50}}/>
|
||||
<SettingsError errorMessage={this.errorMessage} />
|
||||
<div style={{ height: 50 }} />
|
||||
</div>
|
||||
<footer>
|
||||
<div onClick={this.closeModal} className="button is-prompt-green is-outlined is-small">Close</div>
|
||||
<div onClick={this.closeModal} className="button is-prompt-green is-outlined is-small">
|
||||
Close
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
@ -596,32 +633,34 @@ class LineSettingsModal extends React.Component<{linenum : number}, {}> {
|
||||
|
||||
@mobxReact.observer
|
||||
class ClientSettingsModal extends React.Component<{}, {}> {
|
||||
tempFontSize : OV<number>;
|
||||
fontSizeDropdownActive : OV<boolean> = mobx.observable.box(false, {name: "clientSettings-fontSizeDropdownActive"});
|
||||
errorMessage : OV<string> = mobx.observable.box(null, {name: "ClientSettings-errorMessage"});
|
||||
tempFontSize: OV<number>;
|
||||
fontSizeDropdownActive: OV<boolean> = mobx.observable.box(false, { name: "clientSettings-fontSizeDropdownActive" });
|
||||
errorMessage: OV<string> = mobx.observable.box(null, { name: "ClientSettings-errorMessage" });
|
||||
|
||||
constructor(props : any) {
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
let cdata = GlobalModel.clientData.get();
|
||||
this.tempFontSize = mobx.observable.box(GlobalModel.termFontSize.get(), {name: "clientSettings-tempFontSize"});
|
||||
this.tempFontSize = mobx.observable.box(GlobalModel.termFontSize.get(), {
|
||||
name: "clientSettings-tempFontSize",
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@boundMethod
|
||||
closeModal() : void {
|
||||
closeModal(): void {
|
||||
mobx.action(() => {
|
||||
GlobalModel.clientSettingsModal.set(false);
|
||||
})();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
dismissError() : void {
|
||||
dismissError(): void {
|
||||
mobx.action(() => {
|
||||
this.errorMessage.set(null);
|
||||
})();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleChangeFontSize(newFontSize : number) : void {
|
||||
handleChangeFontSize(newFontSize: number): void {
|
||||
if (GlobalModel.termFontSize.get() == newFontSize) {
|
||||
return;
|
||||
}
|
||||
@ -630,29 +669,28 @@ class ClientSettingsModal extends React.Component<{}, {}> {
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
togglefontSizeDropdown() : void {
|
||||
togglefontSizeDropdown(): void {
|
||||
mobx.action(() => {
|
||||
this.fontSizeDropdownActive.set(!this.fontSizeDropdownActive.get());
|
||||
})();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleChangeTelemetry(val : boolean) : void {
|
||||
let prtn : Promise<CommandRtnType> = null;
|
||||
handleChangeTelemetry(val: boolean): void {
|
||||
let prtn: Promise<CommandRtnType> = null;
|
||||
if (val) {
|
||||
prtn = GlobalCommandRunner.telemetryOn(false);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
prtn = GlobalCommandRunner.telemetryOff(false);
|
||||
}
|
||||
commandRtnHandler(prtn, this.errorMessage);
|
||||
}
|
||||
|
||||
renderFontSizeDropdown() : any {
|
||||
renderFontSizeDropdown(): any {
|
||||
let availableFontSizes = [8, 9, 10, 11, 12, 13, 14, 15];
|
||||
let fsize : number = 0;
|
||||
let fsize: number = 0;
|
||||
return (
|
||||
<div className={cn("dropdown", "font-size-dropdown", {"is-active": this.fontSizeDropdownActive.get()})}>
|
||||
<div className={cn("dropdown", "font-size-dropdown", { "is-active": this.fontSizeDropdownActive.get() })}>
|
||||
<div className="dropdown-trigger">
|
||||
<button onClick={this.togglefontSizeDropdown} className="button is-small is-dark">
|
||||
<span>{this.tempFontSize.get()}px</span>
|
||||
@ -664,7 +702,13 @@ class ClientSettingsModal extends React.Component<{}, {}> {
|
||||
<div className="dropdown-menu" role="menu">
|
||||
<div className="dropdown-content has-background-black">
|
||||
<For each="fsize" of={availableFontSizes}>
|
||||
<div onClick={() => this.handleChangeFontSize(fsize) } key={fsize + "px"} className="dropdown-item">{fsize}px</div>
|
||||
<div
|
||||
onClick={() => this.handleChangeFontSize(fsize)}
|
||||
key={fsize + "px"}
|
||||
className="dropdown-item"
|
||||
>
|
||||
{fsize}px
|
||||
</div>
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
@ -673,114 +717,117 @@ class ClientSettingsModal extends React.Component<{}, {}> {
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
inlineUpdateOpenAIModel(newModel : string) : void {
|
||||
let prtn = GlobalCommandRunner.setClientOpenAISettings({model: newModel});
|
||||
inlineUpdateOpenAIModel(newModel: string): void {
|
||||
let prtn = GlobalCommandRunner.setClientOpenAISettings({ model: newModel });
|
||||
commandRtnHandler(prtn, this.errorMessage);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
inlineUpdateOpenAIToken(newToken : string) : void {
|
||||
let prtn = GlobalCommandRunner.setClientOpenAISettings({apitoken: newToken});
|
||||
inlineUpdateOpenAIToken(newToken: string): void {
|
||||
let prtn = GlobalCommandRunner.setClientOpenAISettings({ apitoken: newToken });
|
||||
commandRtnHandler(prtn, this.errorMessage);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
inlineUpdateOpenAIMaxTokens(newMaxTokensStr : string) : void {
|
||||
let prtn = GlobalCommandRunner.setClientOpenAISettings({maxtokens: newMaxTokensStr});
|
||||
inlineUpdateOpenAIMaxTokens(newMaxTokensStr: string): void {
|
||||
let prtn = GlobalCommandRunner.setClientOpenAISettings({ maxtokens: newMaxTokensStr });
|
||||
commandRtnHandler(prtn, this.errorMessage);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
setErrorMessage(msg : string) : void {
|
||||
setErrorMessage(msg: string): void {
|
||||
mobx.action(() => {
|
||||
this.errorMessage.set(msg);
|
||||
})();
|
||||
}
|
||||
|
||||
render() {
|
||||
let cdata : ClientDataType = GlobalModel.clientData.get();
|
||||
let cdata: ClientDataType = GlobalModel.clientData.get();
|
||||
let openAIOpts = cdata.openaiopts ?? {};
|
||||
let apiTokenStr = (util.isBlank(openAIOpts.apitoken) ? "(not set)" : "********");
|
||||
let maxTokensStr = String(openAIOpts.maxtokens == null || openAIOpts.maxtokens == 0 ? 1000 : openAIOpts.maxtokens);
|
||||
let apiTokenStr = util.isBlank(openAIOpts.apitoken) ? "(not set)" : "********";
|
||||
let maxTokensStr = String(
|
||||
openAIOpts.maxtokens == null || openAIOpts.maxtokens == 0 ? 1000 : openAIOpts.maxtokens
|
||||
);
|
||||
return (
|
||||
<div className={cn("modal client-settings-modal settings-modal prompt-modal is-active")}>
|
||||
<div className="modal-background"/>
|
||||
<div className="modal-background" />
|
||||
<div className="modal-content">
|
||||
<header>
|
||||
<div className="modal-title">client settings</div>
|
||||
<div className="close-icon">
|
||||
<i onClick={this.closeModal} className="fa-sharp fa-solid fa-times"/>
|
||||
<i onClick={this.closeModal} className="fa-sharp fa-solid fa-times" />
|
||||
</div>
|
||||
</header>
|
||||
<div className="inner-content">
|
||||
<div className="settings-field">
|
||||
<div className="settings-label">
|
||||
Term Font Size
|
||||
</div>
|
||||
<div className="settings-input">
|
||||
{this.renderFontSizeDropdown()}
|
||||
</div>
|
||||
<div className="settings-label">Term Font Size</div>
|
||||
<div className="settings-input">{this.renderFontSizeDropdown()}</div>
|
||||
</div>
|
||||
<div className="settings-field">
|
||||
<div className="settings-label">
|
||||
Client ID
|
||||
</div>
|
||||
<div className="settings-input">
|
||||
{cdata.clientid}
|
||||
</div>
|
||||
<div className="settings-label">Client ID</div>
|
||||
<div className="settings-input">{cdata.clientid}</div>
|
||||
</div>
|
||||
<div className="settings-field">
|
||||
<div className="settings-label">
|
||||
Client Version
|
||||
</div>
|
||||
<div className="settings-label">Client Version</div>
|
||||
<div className="settings-input">
|
||||
{VERSION} {BUILD}
|
||||
</div>
|
||||
</div>
|
||||
<div className="settings-field">
|
||||
<div className="settings-label">
|
||||
DB Version
|
||||
</div>
|
||||
<div className="settings-label">DB Version</div>
|
||||
<div className="settings-input">{cdata.dbversion}</div>
|
||||
</div>
|
||||
<div className="settings-field">
|
||||
<div className="settings-label">Basic Telemetry</div>
|
||||
<div className="settings-input">
|
||||
{cdata.dbversion}
|
||||
<Toggle checked={!cdata.clientopts.notelemetry} onChange={this.handleChangeTelemetry} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="settings-field">
|
||||
<div className="settings-label">
|
||||
Basic Telemetry
|
||||
</div>
|
||||
<div className="settings-label">OpenAI Token</div>
|
||||
<div className="settings-input">
|
||||
<Toggle checked={!cdata.clientopts.notelemetry} onChange={this.handleChangeTelemetry}/>
|
||||
<InlineSettingsTextEdit
|
||||
placeholder=""
|
||||
text={apiTokenStr}
|
||||
value={""}
|
||||
onChange={this.inlineUpdateOpenAIToken}
|
||||
maxLength={100}
|
||||
showIcon={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="settings-field">
|
||||
<div className="settings-label">
|
||||
OpenAI Token
|
||||
</div>
|
||||
<div className="settings-label">OpenAI Model</div>
|
||||
<div className="settings-input">
|
||||
<InlineSettingsTextEdit placeholder="" text={apiTokenStr} value={""} onChange={this.inlineUpdateOpenAIToken} maxLength={100} showIcon={true}/>
|
||||
<InlineSettingsTextEdit
|
||||
placeholder="gpt-3.5-turbo"
|
||||
text={util.isBlank(openAIOpts.model) ? "gpt-3.5-turbo" : openAIOpts.model}
|
||||
value={openAIOpts.model ?? ""}
|
||||
onChange={this.inlineUpdateOpenAIModel}
|
||||
maxLength={100}
|
||||
showIcon={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="settings-field">
|
||||
<div className="settings-label">
|
||||
OpenAI Model
|
||||
</div>
|
||||
<div className="settings-label">OpenAI MaxTokens</div>
|
||||
<div className="settings-input">
|
||||
<InlineSettingsTextEdit placeholder="gpt-3.5-turbo" text={util.isBlank(openAIOpts.model) ? "gpt-3.5-turbo" : openAIOpts.model} value={openAIOpts.model ?? ""} onChange={this.inlineUpdateOpenAIModel} maxLength={100} showIcon={true}/>
|
||||
<InlineSettingsTextEdit
|
||||
placeholder=""
|
||||
text={maxTokensStr}
|
||||
value={maxTokensStr}
|
||||
onChange={this.inlineUpdateOpenAIMaxTokens}
|
||||
maxLength={10}
|
||||
showIcon={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="settings-field">
|
||||
<div className="settings-label">
|
||||
OpenAI MaxTokens
|
||||
</div>
|
||||
<div className="settings-input">
|
||||
<InlineSettingsTextEdit placeholder="" text={maxTokensStr} value={maxTokensStr} onChange={this.inlineUpdateOpenAIMaxTokens} maxLength={10} showIcon={true}/>
|
||||
</div>
|
||||
</div>
|
||||
<SettingsError errorMessage={this.errorMessage}/>
|
||||
<SettingsError errorMessage={this.errorMessage} />
|
||||
</div>
|
||||
<footer>
|
||||
<div onClick={this.closeModal} className="button is-prompt-green is-outlined is-small">Close</div>
|
||||
<div onClick={this.closeModal} className="button is-prompt-green is-outlined is-small">
|
||||
Close
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
@ -788,4 +835,10 @@ class ClientSettingsModal extends React.Component<{}, {}> {
|
||||
}
|
||||
}
|
||||
|
||||
export {ScreenSettingsModal, SessionSettingsModal, LineSettingsModal, ClientSettingsModal, WebStopShareConfirmMarkdown};
|
||||
export {
|
||||
ScreenSettingsModal,
|
||||
SessionSettingsModal,
|
||||
LineSettingsModal,
|
||||
ClientSettingsModal,
|
||||
WebStopShareConfirmMarkdown,
|
||||
};
|
||||
|
@ -5,7 +5,7 @@
|
||||
border-bottom: 2px solid #ddd;
|
||||
margin-left: -4px;
|
||||
margin-right: -5px;
|
||||
|
||||
|
||||
.title.prompt-logo-small {
|
||||
padding-left: 5px;
|
||||
padding-top: 8px;
|
||||
@ -18,15 +18,15 @@
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
|
||||
|
||||
&.is-dev {
|
||||
background-color: darken(rgb(177, 0, 10), 30%);
|
||||
}
|
||||
|
||||
|
||||
&.collapsed {
|
||||
padding-left: 1px;
|
||||
}
|
||||
|
||||
|
||||
.title-cursor {
|
||||
position: relative;
|
||||
bottom: 3px;
|
||||
@ -84,12 +84,13 @@
|
||||
}
|
||||
|
||||
.menu-list {
|
||||
li.new-session, li.add-remote {
|
||||
li.new-session,
|
||||
li.add-remote {
|
||||
a {
|
||||
color: #666;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
|
||||
.fa {
|
||||
font-size: 10px;
|
||||
}
|
||||
@ -103,7 +104,10 @@
|
||||
.mono-font();
|
||||
}
|
||||
|
||||
li.menu-history, li.menu-bookmarks, li.menu-settings, li.menu-websharing {
|
||||
li.menu-history,
|
||||
li.menu-bookmarks,
|
||||
li.menu-settings,
|
||||
li.menu-websharing {
|
||||
margin-left: -10px;
|
||||
}
|
||||
|
||||
|
@ -3,27 +3,21 @@ import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import { sprintf } from "sprintf-js";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import {
|
||||
If,
|
||||
For,
|
||||
When,
|
||||
Otherwise,
|
||||
Choose,
|
||||
} from "tsx-control-statements/components";
|
||||
import { If, For, When, Otherwise, Choose } from "tsx-control-statements/components";
|
||||
import type {
|
||||
RendererModelInitializeParams,
|
||||
TermOptsType,
|
||||
RendererContext,
|
||||
RendererOpts,
|
||||
SimpleBlobRendererComponent,
|
||||
RendererModelContainerApi,
|
||||
RendererPluginType,
|
||||
PtyDataType,
|
||||
RendererModel,
|
||||
RendererOptsUpdate,
|
||||
LineType,
|
||||
TermContextUnion,
|
||||
RendererContainerType,
|
||||
RendererModelInitializeParams,
|
||||
TermOptsType,
|
||||
RendererContext,
|
||||
RendererOpts,
|
||||
SimpleBlobRendererComponent,
|
||||
RendererModelContainerApi,
|
||||
RendererPluginType,
|
||||
PtyDataType,
|
||||
RendererModel,
|
||||
RendererOptsUpdate,
|
||||
LineType,
|
||||
TermContextUnion,
|
||||
RendererContainerType,
|
||||
} from "./types";
|
||||
import { PacketDataBuffer } from "./ptydata";
|
||||
import { debounce, throttle } from "throttle-debounce";
|
||||
@ -32,198 +26,193 @@ type OV<V> = mobx.IObservableValue<V>;
|
||||
type CV<V> = mobx.IComputedValue<V>;
|
||||
|
||||
class SimpleBlobRendererModel {
|
||||
context: RendererContext;
|
||||
opts: RendererOpts;
|
||||
isDone: OV<boolean>;
|
||||
api: RendererModelContainerApi;
|
||||
savedHeight: number;
|
||||
loading: OV<boolean>;
|
||||
loadError: OV<string> = mobx.observable.box(null, {
|
||||
name: "renderer-loadError",
|
||||
});
|
||||
ptyData: PtyDataType;
|
||||
ptyDataSource: (termContext: TermContextUnion) => Promise<PtyDataType>;
|
||||
|
||||
initialize(params: RendererModelInitializeParams): void {
|
||||
this.loading = mobx.observable.box(true, { name: "renderer-loading" });
|
||||
this.isDone = mobx.observable.box(params.isDone, {
|
||||
name: "renderer-isDone",
|
||||
context: RendererContext;
|
||||
opts: RendererOpts;
|
||||
isDone: OV<boolean>;
|
||||
api: RendererModelContainerApi;
|
||||
savedHeight: number;
|
||||
loading: OV<boolean>;
|
||||
loadError: OV<string> = mobx.observable.box(null, {
|
||||
name: "renderer-loadError",
|
||||
});
|
||||
this.context = params.context;
|
||||
this.opts = params.opts;
|
||||
this.api = params.api;
|
||||
this.savedHeight = params.savedHeight;
|
||||
this.ptyDataSource = params.ptyDataSource;
|
||||
if (this.isDone.get()) {
|
||||
setTimeout(() => this.reload(0), 10);
|
||||
ptyData: PtyDataType;
|
||||
ptyDataSource: (termContext: TermContextUnion) => Promise<PtyDataType>;
|
||||
|
||||
initialize(params: RendererModelInitializeParams): void {
|
||||
this.loading = mobx.observable.box(true, { name: "renderer-loading" });
|
||||
this.isDone = mobx.observable.box(params.isDone, {
|
||||
name: "renderer-isDone",
|
||||
});
|
||||
this.context = params.context;
|
||||
this.opts = params.opts;
|
||||
this.api = params.api;
|
||||
this.savedHeight = params.savedHeight;
|
||||
this.ptyDataSource = params.ptyDataSource;
|
||||
if (this.isDone.get()) {
|
||||
setTimeout(() => this.reload(0), 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
return;
|
||||
}
|
||||
|
||||
giveFocus(): void {
|
||||
return;
|
||||
}
|
||||
|
||||
updateOpts(update: RendererOptsUpdate): void {
|
||||
Object.assign(this.opts, update);
|
||||
}
|
||||
|
||||
updateHeight(newHeight: number): void {
|
||||
if (this.savedHeight != newHeight) {
|
||||
this.savedHeight = newHeight;
|
||||
this.api.saveHeight(newHeight);
|
||||
dispose(): void {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setIsDone(): void {
|
||||
if (this.isDone.get()) {
|
||||
return;
|
||||
giveFocus(): void {
|
||||
return;
|
||||
}
|
||||
mobx.action(() => {
|
||||
this.isDone.set(true);
|
||||
})();
|
||||
this.reload(0);
|
||||
}
|
||||
|
||||
reload(delayMs: number): void {
|
||||
mobx.action(() => {
|
||||
this.loading.set(true);
|
||||
})();
|
||||
let rtnp = this.ptyDataSource(this.context);
|
||||
if (rtnp == null) {
|
||||
console.log(
|
||||
"no promise returned from ptyDataSource (simplerenderer)",
|
||||
this.context
|
||||
);
|
||||
return;
|
||||
updateOpts(update: RendererOptsUpdate): void {
|
||||
Object.assign(this.opts, update);
|
||||
}
|
||||
rtnp
|
||||
.then((ptydata) => {
|
||||
setTimeout(() => {
|
||||
this.ptyData = ptydata;
|
||||
mobx.action(() => {
|
||||
this.loading.set(false);
|
||||
this.loadError.set(null);
|
||||
})();
|
||||
}, delayMs);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log("error loading data", e);
|
||||
|
||||
updateHeight(newHeight: number): void {
|
||||
if (this.savedHeight != newHeight) {
|
||||
this.savedHeight = newHeight;
|
||||
this.api.saveHeight(newHeight);
|
||||
}
|
||||
}
|
||||
|
||||
setIsDone(): void {
|
||||
if (this.isDone.get()) {
|
||||
return;
|
||||
}
|
||||
mobx.action(() => {
|
||||
this.loadError.set("error loading data: " + e);
|
||||
this.isDone.set(true);
|
||||
})();
|
||||
});
|
||||
}
|
||||
this.reload(0);
|
||||
}
|
||||
|
||||
receiveData(pos: number, data: Uint8Array, reason?: string): void {
|
||||
// this.dataBuf.receiveData(pos, data, reason);
|
||||
}
|
||||
reload(delayMs: number): void {
|
||||
mobx.action(() => {
|
||||
this.loading.set(true);
|
||||
})();
|
||||
let rtnp = this.ptyDataSource(this.context);
|
||||
if (rtnp == null) {
|
||||
console.log("no promise returned from ptyDataSource (simplerenderer)", this.context);
|
||||
return;
|
||||
}
|
||||
rtnp.then((ptydata) => {
|
||||
setTimeout(() => {
|
||||
this.ptyData = ptydata;
|
||||
mobx.action(() => {
|
||||
this.loading.set(false);
|
||||
this.loadError.set(null);
|
||||
})();
|
||||
}, delayMs);
|
||||
}).catch((e) => {
|
||||
console.log("error loading data", e);
|
||||
mobx.action(() => {
|
||||
this.loadError.set("error loading data: " + e);
|
||||
})();
|
||||
});
|
||||
}
|
||||
|
||||
receiveData(pos: number, data: Uint8Array, reason?: string): void {
|
||||
// this.dataBuf.receiveData(pos, data, reason);
|
||||
}
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class SimpleBlobRenderer extends React.Component<
|
||||
{
|
||||
rendererContainer: RendererContainerType;
|
||||
lineId: string;
|
||||
plugin: RendererPluginType;
|
||||
onHeightChange: () => void;
|
||||
initParams: RendererModelInitializeParams;
|
||||
},
|
||||
{}
|
||||
{
|
||||
rendererContainer: RendererContainerType;
|
||||
lineId: string;
|
||||
plugin: RendererPluginType;
|
||||
onHeightChange: () => void;
|
||||
initParams: RendererModelInitializeParams;
|
||||
},
|
||||
{}
|
||||
> {
|
||||
model: SimpleBlobRendererModel;
|
||||
wrapperDivRef: React.RefObject<any> = React.createRef();
|
||||
rszObs: ResizeObserver;
|
||||
updateHeight_debounced: (newHeight: number) => void;
|
||||
model: SimpleBlobRendererModel;
|
||||
wrapperDivRef: React.RefObject<any> = React.createRef();
|
||||
rszObs: ResizeObserver;
|
||||
updateHeight_debounced: (newHeight: number) => void;
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
let { rendererContainer, lineId, plugin, initParams } = this.props;
|
||||
this.model = new SimpleBlobRendererModel();
|
||||
this.model.initialize(initParams);
|
||||
rendererContainer.registerRenderer(lineId, this.model);
|
||||
this.updateHeight_debounced = debounce(1000, this.updateHeight.bind(this));
|
||||
}
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
let { rendererContainer, lineId, plugin, initParams } = this.props;
|
||||
this.model = new SimpleBlobRendererModel();
|
||||
this.model.initialize(initParams);
|
||||
rendererContainer.registerRenderer(lineId, this.model);
|
||||
this.updateHeight_debounced = debounce(1000, this.updateHeight.bind(this));
|
||||
}
|
||||
|
||||
updateHeight(newHeight: number): void {
|
||||
this.model.updateHeight(newHeight);
|
||||
}
|
||||
updateHeight(newHeight: number): void {
|
||||
this.model.updateHeight(newHeight);
|
||||
}
|
||||
|
||||
handleResize(entries: ResizeObserverEntry[]): void {
|
||||
if (this.model.loading.get()) {
|
||||
return;
|
||||
handleResize(entries: ResizeObserverEntry[]): void {
|
||||
if (this.model.loading.get()) {
|
||||
return;
|
||||
}
|
||||
if (this.props.onHeightChange) {
|
||||
this.props.onHeightChange();
|
||||
}
|
||||
if (!this.model.loading.get() && this.wrapperDivRef.current != null) {
|
||||
let height = this.wrapperDivRef.current.offsetHeight;
|
||||
this.updateHeight_debounced(height);
|
||||
}
|
||||
}
|
||||
if (this.props.onHeightChange) {
|
||||
this.props.onHeightChange();
|
||||
}
|
||||
if (!this.model.loading.get() && this.wrapperDivRef.current != null) {
|
||||
let height = this.wrapperDivRef.current.offsetHeight;
|
||||
this.updateHeight_debounced(height);
|
||||
}
|
||||
}
|
||||
|
||||
checkRszObs() {
|
||||
if (this.rszObs != null) {
|
||||
return;
|
||||
checkRszObs() {
|
||||
if (this.rszObs != null) {
|
||||
return;
|
||||
}
|
||||
if (this.wrapperDivRef.current == null) {
|
||||
return;
|
||||
}
|
||||
this.rszObs = new ResizeObserver(this.handleResize.bind(this));
|
||||
this.rszObs.observe(this.wrapperDivRef.current);
|
||||
}
|
||||
if (this.wrapperDivRef.current == null) {
|
||||
return;
|
||||
}
|
||||
this.rszObs = new ResizeObserver(this.handleResize.bind(this));
|
||||
this.rszObs.observe(this.wrapperDivRef.current);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.checkRszObs();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
let { rendererContainer, lineId } = this.props;
|
||||
rendererContainer.unloadRenderer(lineId);
|
||||
if (this.rszObs != null) {
|
||||
this.rszObs.disconnect();
|
||||
this.rszObs = null;
|
||||
componentDidMount() {
|
||||
this.checkRszObs();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.checkRszObs();
|
||||
}
|
||||
componentWillUnmount() {
|
||||
let { rendererContainer, lineId } = this.props;
|
||||
rendererContainer.unloadRenderer(lineId);
|
||||
if (this.rszObs != null) {
|
||||
this.rszObs.disconnect();
|
||||
this.rszObs = null;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let { plugin } = this.props;
|
||||
let model = this.model;
|
||||
if (model.loading.get()) {
|
||||
let height = this.model.savedHeight;
|
||||
return (
|
||||
<div ref={this.wrapperDivRef} style={{ minHeight: height }}>
|
||||
...
|
||||
</div>
|
||||
);
|
||||
componentDidUpdate() {
|
||||
this.checkRszObs();
|
||||
}
|
||||
let Comp = plugin.simpleComponent;
|
||||
if (Comp == null) {
|
||||
<div ref={this.wrapperDivRef}>(no component found in plugin)</div>;
|
||||
|
||||
render() {
|
||||
let { plugin } = this.props;
|
||||
let model = this.model;
|
||||
if (model.loading.get()) {
|
||||
let height = this.model.savedHeight;
|
||||
return (
|
||||
<div ref={this.wrapperDivRef} style={{ minHeight: height }}>
|
||||
...
|
||||
</div>
|
||||
);
|
||||
}
|
||||
let Comp = plugin.simpleComponent;
|
||||
if (Comp == null) {
|
||||
<div ref={this.wrapperDivRef}>(no component found in plugin)</div>;
|
||||
}
|
||||
let dataBlob = new Blob([model.ptyData.data]);
|
||||
let simpleModel = model as SimpleBlobRendererModel;
|
||||
let { festate, cmdstr } = this.props.initParams.rawCmd;
|
||||
return (
|
||||
<div ref={this.wrapperDivRef}>
|
||||
<Comp
|
||||
cwd={festate.cwd}
|
||||
cmdstr={cmdstr}
|
||||
data={dataBlob}
|
||||
context={simpleModel.context}
|
||||
opts={simpleModel.opts}
|
||||
savedHeight={simpleModel.savedHeight}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
let dataBlob = new Blob([model.ptyData.data]);
|
||||
let simpleModel = model as SimpleBlobRendererModel;
|
||||
let { festate, cmdstr } = this.props.initParams.rawCmd;
|
||||
return (
|
||||
<div ref={this.wrapperDivRef}>
|
||||
<Comp
|
||||
cwd={festate.cwd}
|
||||
cmdstr={cmdstr}
|
||||
data={dataBlob}
|
||||
context={simpleModel.context}
|
||||
opts={simpleModel.opts}
|
||||
savedHeight={simpleModel.savedHeight}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { SimpleBlobRendererModel, SimpleBlobRenderer };
|
||||
|
@ -44,9 +44,7 @@
|
||||
&.is-active {
|
||||
background-color: @tab-yellow;
|
||||
color: black;
|
||||
box-shadow:
|
||||
0 3px 0 #fff inset,
|
||||
0 4px 0 #000 inset;
|
||||
box-shadow: 0 3px 0 #fff inset, 0 4px 0 #000 inset;
|
||||
}
|
||||
}
|
||||
|
||||
@ -95,9 +93,7 @@
|
||||
&.is-active {
|
||||
color: black;
|
||||
background-color: @tab-white;
|
||||
box-shadow:
|
||||
0 3px 0 #fff inset,
|
||||
0 4px 0 #000 inset;
|
||||
box-shadow: 0 3px 0 #fff inset, 0 4px 0 #000 inset;
|
||||
}
|
||||
}
|
||||
|
||||
@ -122,7 +118,7 @@
|
||||
|
||||
.screen-tabs-container {
|
||||
position: relative;
|
||||
|
||||
|
||||
&:hover {
|
||||
z-index: 200;
|
||||
}
|
||||
@ -169,7 +165,7 @@
|
||||
height: 30px;
|
||||
min-width: 80px;
|
||||
width: 150px;
|
||||
line-height: 1.0;
|
||||
line-height: 1;
|
||||
flex-shrink: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@ -193,7 +189,7 @@
|
||||
&.is-active {
|
||||
box-shadow: 0 3px 0 #fff inset;
|
||||
border-top: none;
|
||||
opacity: 1.0;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.is-archived {
|
||||
|
164
src/term.ts
164
src/term.ts
@ -1,58 +1,58 @@
|
||||
import * as mobx from "mobx";
|
||||
import {Terminal} from 'xterm';
|
||||
import {sprintf} from "sprintf-js";
|
||||
import {boundMethod} from "autobind-decorator";
|
||||
import {v4 as uuidv4} from "uuid";
|
||||
import {termHeightFromRows, windowWidthToCols, windowHeightToRows} from "./textmeasure";
|
||||
import {boundInt} from "./util";
|
||||
import type {TermContextUnion, TermOptsType, TermWinSize, RendererContext, WindowSize, PtyDataType} from "./types";
|
||||
import { Terminal } from "xterm";
|
||||
import { sprintf } from "sprintf-js";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { termHeightFromRows, windowWidthToCols, windowHeightToRows } from "./textmeasure";
|
||||
import { boundInt } from "./util";
|
||||
import type { TermContextUnion, TermOptsType, TermWinSize, RendererContext, WindowSize, PtyDataType } from "./types";
|
||||
|
||||
type DataUpdate = {
|
||||
data : Uint8Array,
|
||||
pos : number,
|
||||
}
|
||||
data: Uint8Array;
|
||||
pos: number;
|
||||
};
|
||||
|
||||
const MinTermCols = 10;
|
||||
const MaxTermCols = 1024;
|
||||
|
||||
type TermWrapOpts = {
|
||||
termContext : TermContextUnion,
|
||||
usedRows? : number,
|
||||
termOpts : TermOptsType,
|
||||
winSize : WindowSize,
|
||||
keyHandler? : (event : any, termWrap : TermWrap) => void,
|
||||
focusHandler? : (focus : boolean) => void,
|
||||
dataHandler? : (data : string, termWrap : TermWrap) => void,
|
||||
isRunning : boolean,
|
||||
customKeyHandler? : (event : any, termWrap : TermWrap) => boolean,
|
||||
fontSize : number,
|
||||
ptyDataSource : (termContext : TermContextUnion) => Promise<PtyDataType>,
|
||||
onUpdateContentHeight : (termContext : RendererContext, height : number) => void,
|
||||
termContext: TermContextUnion;
|
||||
usedRows?: number;
|
||||
termOpts: TermOptsType;
|
||||
winSize: WindowSize;
|
||||
keyHandler?: (event: any, termWrap: TermWrap) => void;
|
||||
focusHandler?: (focus: boolean) => void;
|
||||
dataHandler?: (data: string, termWrap: TermWrap) => void;
|
||||
isRunning: boolean;
|
||||
customKeyHandler?: (event: any, termWrap: TermWrap) => boolean;
|
||||
fontSize: number;
|
||||
ptyDataSource: (termContext: TermContextUnion) => Promise<PtyDataType>;
|
||||
onUpdateContentHeight: (termContext: RendererContext, height: number) => void;
|
||||
};
|
||||
|
||||
// cmd-instance
|
||||
class TermWrap {
|
||||
terminal : any;
|
||||
termContext : TermContextUnion;
|
||||
atRowMax : boolean;
|
||||
usedRows : mobx.IObservableValue<number>;
|
||||
flexRows : boolean;
|
||||
connectedElem : Element;
|
||||
ptyPos : number = 0;
|
||||
reloading : boolean = false;
|
||||
dataUpdates : DataUpdate[] = [];
|
||||
loadError : mobx.IObservableValue<boolean> = mobx.observable.box(false, {name: "term-loaderror"});
|
||||
winSize : WindowSize;
|
||||
numParseErrors : number = 0;
|
||||
termSize : TermWinSize;
|
||||
focusHandler : (focus : boolean) => void;
|
||||
isRunning : boolean;
|
||||
fontSize : number;
|
||||
onUpdateContentHeight : (termContext : RendererContext, height : number) => void;
|
||||
ptyDataSource : (termContext : TermContextUnion) => Promise<PtyDataType>;
|
||||
initializing : boolean;
|
||||
terminal: any;
|
||||
termContext: TermContextUnion;
|
||||
atRowMax: boolean;
|
||||
usedRows: mobx.IObservableValue<number>;
|
||||
flexRows: boolean;
|
||||
connectedElem: Element;
|
||||
ptyPos: number = 0;
|
||||
reloading: boolean = false;
|
||||
dataUpdates: DataUpdate[] = [];
|
||||
loadError: mobx.IObservableValue<boolean> = mobx.observable.box(false, { name: "term-loaderror" });
|
||||
winSize: WindowSize;
|
||||
numParseErrors: number = 0;
|
||||
termSize: TermWinSize;
|
||||
focusHandler: (focus: boolean) => void;
|
||||
isRunning: boolean;
|
||||
fontSize: number;
|
||||
onUpdateContentHeight: (termContext: RendererContext, height: number) => void;
|
||||
ptyDataSource: (termContext: TermContextUnion) => Promise<PtyDataType>;
|
||||
initializing: boolean;
|
||||
|
||||
constructor(elem : Element, opts : TermWrapOpts) {
|
||||
constructor(elem: Element, opts: TermWrapOpts) {
|
||||
opts = opts ?? ({} as any);
|
||||
this.termContext = opts.termContext;
|
||||
this.connectedElem = elem;
|
||||
@ -66,23 +66,27 @@ class TermWrap {
|
||||
this.initializing = true;
|
||||
if (this.flexRows) {
|
||||
this.atRowMax = false;
|
||||
this.usedRows = mobx.observable.box(opts.usedRows ?? (opts.isRunning ? 1 : 0), {name: "term-usedrows"});
|
||||
}
|
||||
else {
|
||||
this.usedRows = mobx.observable.box(opts.usedRows ?? (opts.isRunning ? 1 : 0), { name: "term-usedrows" });
|
||||
} else {
|
||||
this.atRowMax = true;
|
||||
this.usedRows = mobx.observable.box(opts.termOpts.rows, {name: "term-usedrows"});
|
||||
this.usedRows = mobx.observable.box(opts.termOpts.rows, { name: "term-usedrows" });
|
||||
}
|
||||
if (opts.winSize == null) {
|
||||
this.termSize = {rows: opts.termOpts.rows, cols: opts.termOpts.cols};
|
||||
}
|
||||
else {
|
||||
this.termSize = { rows: opts.termOpts.rows, cols: opts.termOpts.cols };
|
||||
} else {
|
||||
let cols = windowWidthToCols(opts.winSize.width, opts.fontSize);
|
||||
this.termSize = {rows: opts.termOpts.rows, cols: cols};
|
||||
this.termSize = { rows: opts.termOpts.rows, cols: cols };
|
||||
}
|
||||
let theme = {
|
||||
foreground: "#d3d7cf",
|
||||
};
|
||||
this.terminal = new Terminal({rows: this.termSize.rows, cols: this.termSize.cols, fontSize: opts.fontSize, fontFamily: "JetBrains Mono", theme: theme});
|
||||
this.terminal = new Terminal({
|
||||
rows: this.termSize.rows,
|
||||
cols: this.termSize.cols,
|
||||
fontSize: opts.fontSize,
|
||||
fontFamily: "JetBrains Mono",
|
||||
theme: theme,
|
||||
});
|
||||
this.terminal._core._inputHandler._parser.setErrorHandler((state) => {
|
||||
this.numParseErrors++;
|
||||
return state;
|
||||
@ -99,7 +103,7 @@ class TermWrap {
|
||||
this.focusHandler(true);
|
||||
}
|
||||
});
|
||||
this.terminal.textarea.addEventListener("blur", (e : any) => {
|
||||
this.terminal.textarea.addEventListener("blur", (e: any) => {
|
||||
if (document.activeElement == this.terminal.textarea) {
|
||||
return;
|
||||
}
|
||||
@ -114,12 +118,12 @@ class TermWrap {
|
||||
setTimeout(() => this.reload(0), 10);
|
||||
}
|
||||
|
||||
getUsedRows() : number {
|
||||
getUsedRows(): number {
|
||||
return this.usedRows.get();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
elemScrollHandler(e : any) {
|
||||
elemScrollHandler(e: any) {
|
||||
// this stops a weird behavior in the terminal
|
||||
// xterm.js renders a textarea that handles focus. when it focuses and a space is typed the browser
|
||||
// will scroll to make it visible (even though our terminal element has overflow hidden)
|
||||
@ -130,21 +134,21 @@ class TermWrap {
|
||||
e.target.scrollTop = 0;
|
||||
}
|
||||
|
||||
getContextRemoteId() : string {
|
||||
getContextRemoteId(): string {
|
||||
if ("remoteId" in this.termContext) {
|
||||
return this.termContext.remoteId;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getRendererContext() : RendererContext {
|
||||
getRendererContext(): RendererContext {
|
||||
if ("remoteId" in this.termContext) {
|
||||
return null;
|
||||
}
|
||||
return this.termContext;
|
||||
}
|
||||
|
||||
getFontHeight() : number {
|
||||
getFontHeight(): number {
|
||||
return this.terminal._core.viewport._currentRowHeight;
|
||||
}
|
||||
|
||||
@ -160,14 +164,14 @@ class TermWrap {
|
||||
return;
|
||||
}
|
||||
this.terminal.focus();
|
||||
setTimeout(() => this.terminal._core.viewport.syncScrollArea(true), 0)
|
||||
setTimeout(() => this.terminal._core.viewport.syncScrollArea(true), 0);
|
||||
}
|
||||
|
||||
disconnectElem() {
|
||||
this.connectedElem = null;
|
||||
}
|
||||
|
||||
getTermUsedRows() : number {
|
||||
getTermUsedRows(): number {
|
||||
let term = this.terminal;
|
||||
if (term == null) {
|
||||
return 0;
|
||||
@ -178,21 +182,21 @@ class TermWrap {
|
||||
if (termNumLines > term.rows) {
|
||||
return term.rows;
|
||||
}
|
||||
let usedRows = (this.isRunning ? 1 : 0);
|
||||
let usedRows = this.isRunning ? 1 : 0;
|
||||
if (this.isRunning && termYPos >= usedRows) {
|
||||
usedRows = termYPos + 1;
|
||||
}
|
||||
for (let i=term.rows-1; i>=usedRows; i--) {
|
||||
for (let i = term.rows - 1; i >= usedRows; i--) {
|
||||
let line = termBuf.translateBufferLineToString(i, true);
|
||||
if (line != null && line.trim() != "") {
|
||||
usedRows = i+1;
|
||||
usedRows = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return usedRows;
|
||||
}
|
||||
|
||||
updateUsedRows(forceFull : boolean, reason : string) {
|
||||
updateUsedRows(forceFull: boolean, reason: string) {
|
||||
if (this.terminal == null) {
|
||||
return;
|
||||
}
|
||||
@ -228,15 +232,15 @@ class TermWrap {
|
||||
})();
|
||||
}
|
||||
|
||||
resizeCols(cols : number) : void {
|
||||
this.resize({rows: this.termSize.rows, cols: cols});
|
||||
resizeCols(cols: number): void {
|
||||
this.resize({ rows: this.termSize.rows, cols: cols });
|
||||
}
|
||||
|
||||
resize(size : TermWinSize) : void {
|
||||
resize(size: TermWinSize): void {
|
||||
if (this.terminal == null) {
|
||||
return;
|
||||
}
|
||||
let newSize = {rows: size.rows, cols: size.cols};
|
||||
let newSize = { rows: size.rows, cols: size.cols };
|
||||
newSize.cols = boundInt(newSize.cols, MinTermCols, MaxTermCols);
|
||||
if (newSize.rows == this.termSize.rows && newSize.cols == this.termSize.cols) {
|
||||
return;
|
||||
@ -246,17 +250,17 @@ class TermWrap {
|
||||
this.updateUsedRows(true, "resize");
|
||||
}
|
||||
|
||||
resizeWindow(size : WindowSize) : void {
|
||||
resizeWindow(size: WindowSize): void {
|
||||
let cols = windowWidthToCols(size.width, this.fontSize);
|
||||
let rows = windowHeightToRows(size.height, this.fontSize);
|
||||
this.resize({rows, cols});
|
||||
this.resize({ rows, cols });
|
||||
}
|
||||
|
||||
_reloadThenHandler(ptydata : PtyDataType) {
|
||||
_reloadThenHandler(ptydata: PtyDataType) {
|
||||
this.reloading = false;
|
||||
this.ptyPos = ptydata.pos;
|
||||
this.receiveData(ptydata.pos, ptydata.data, "reload-main");
|
||||
for (let i=0; i<this.dataUpdates.length; i++) {
|
||||
for (let i = 0; i < this.dataUpdates.length; i++) {
|
||||
this.receiveData(this.dataUpdates[i].pos, this.dataUpdates[i].data, "reload-update-" + i);
|
||||
}
|
||||
this.dataUpdates = [];
|
||||
@ -267,7 +271,7 @@ class TermWrap {
|
||||
}
|
||||
}
|
||||
|
||||
getLineNum() : number {
|
||||
getLineNum(): number {
|
||||
let context = this.getRendererContext();
|
||||
if (context == null) {
|
||||
return 0;
|
||||
@ -275,7 +279,7 @@ class TermWrap {
|
||||
return context.lineNum;
|
||||
}
|
||||
|
||||
hardResetTerminal() : void {
|
||||
hardResetTerminal(): void {
|
||||
if (this.terminal == null) {
|
||||
return;
|
||||
}
|
||||
@ -286,7 +290,7 @@ class TermWrap {
|
||||
this.numParseErrors = 0;
|
||||
}
|
||||
|
||||
reload(delayMs : number) {
|
||||
reload(delayMs: number) {
|
||||
if (this.terminal == null) {
|
||||
return;
|
||||
}
|
||||
@ -306,14 +310,16 @@ class TermWrap {
|
||||
this._reloadThenHandler(ptydata);
|
||||
}, delayMs);
|
||||
}).catch((e) => {
|
||||
mobx.action(() => { this.loadError.set(true); })();
|
||||
mobx.action(() => {
|
||||
this.loadError.set(true);
|
||||
})();
|
||||
this.dataUpdates = [];
|
||||
this.reloading = false;
|
||||
console.log("error reloading terminal", this.termContext, e);
|
||||
});
|
||||
}
|
||||
|
||||
receiveData(pos : number, data : Uint8Array, reason? : string) {
|
||||
receiveData(pos: number, data: Uint8Array, reason?: string) {
|
||||
// console.log("update-pty-data", reason, "line:" + this.getLineNum(), pos, data.length, "=>", pos + data.length);
|
||||
if (this.initializing) {
|
||||
return;
|
||||
@ -325,7 +331,7 @@ class TermWrap {
|
||||
return;
|
||||
}
|
||||
if (this.reloading) {
|
||||
this.dataUpdates.push({data: data, pos: pos});
|
||||
this.dataUpdates.push({ data: data, pos: pos });
|
||||
return;
|
||||
}
|
||||
if (pos > this.ptyPos) {
|
||||
@ -347,10 +353,10 @@ class TermWrap {
|
||||
});
|
||||
}
|
||||
|
||||
cmdDone() : void {
|
||||
cmdDone(): void {
|
||||
this.isRunning = false;
|
||||
this.updateUsedRows(true, "cmd-done");
|
||||
}
|
||||
}
|
||||
|
||||
export {TermWrap};
|
||||
export { TermWrap };
|
||||
|
@ -1,9 +1,9 @@
|
||||
import {boundInt} from "./util";
|
||||
import { boundInt } from "./util";
|
||||
|
||||
const MinTermCols = 10;
|
||||
const MaxTermCols = 1024;
|
||||
|
||||
let MonoFontSizes : {height : number, width : number}[] = [];
|
||||
let MonoFontSizes: { height: number; width: number }[] = [];
|
||||
|
||||
// MonoFontSizes[8] = {height: 11, width: 4.797};
|
||||
// MonoFontSizes[9] = {height: 12, width: 5.398};
|
||||
@ -15,18 +15,21 @@ let MonoFontSizes : {height : number, width : number}[] = [];
|
||||
// MonoFontSizes[15] = {height: 20, width: 9};
|
||||
// MonoFontSizes[16] = {height: 22, width: 9.594};
|
||||
|
||||
function getMonoFontSize(fontSize : number) : {height : number, width : number} {
|
||||
function getMonoFontSize(fontSize: number): { height: number; width: number } {
|
||||
if (MonoFontSizes[fontSize] != null) {
|
||||
return MonoFontSizes[fontSize];
|
||||
}
|
||||
let size = measureText("W", {pre: true, mono: true, fontSize: fontSize});
|
||||
let size = measureText("W", { pre: true, mono: true, fontSize: fontSize });
|
||||
if (size.height != 0 && size.width != 0) {
|
||||
MonoFontSizes[fontSize] = size;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
function measureText(text : string, textOpts? : {pre? : boolean, mono? : boolean, fontSize? : number|string}) : {height : number, width : number} {
|
||||
function measureText(
|
||||
text: string,
|
||||
textOpts?: { pre?: boolean; mono?: boolean; fontSize?: number | string }
|
||||
): { height: number; width: number } {
|
||||
if (textOpts == null) {
|
||||
textOpts = {};
|
||||
}
|
||||
@ -38,10 +41,9 @@ function measureText(text : string, textOpts? : {pre? : boolean, mono? : boolean
|
||||
textElem.classList.add("mono");
|
||||
}
|
||||
if (textOpts.fontSize != null) {
|
||||
if (typeof(textOpts.fontSize) == "number") {
|
||||
if (typeof textOpts.fontSize == "number") {
|
||||
textElem.style.fontSize = textOpts.fontSize + "px";
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
textElem.style.fontSize = textOpts.fontSize;
|
||||
}
|
||||
}
|
||||
@ -51,18 +53,18 @@ function measureText(text : string, textOpts? : {pre? : boolean, mono? : boolean
|
||||
throw new Error("cannot measure text, no #measure div");
|
||||
}
|
||||
measureDiv.replaceChildren(textElem);
|
||||
let rect = textElem.getBoundingClientRect()
|
||||
return {width: rect.width, height: Math.ceil(rect.height)};
|
||||
let rect = textElem.getBoundingClientRect();
|
||||
return { width: rect.width, height: Math.ceil(rect.height) };
|
||||
}
|
||||
|
||||
function windowWidthToCols(width : number, fontSize : number) : number {
|
||||
function windowWidthToCols(width: number, fontSize: number): number {
|
||||
let dr = getMonoFontSize(fontSize);
|
||||
let cols = Math.trunc((width - 50) / dr.width) - 1;
|
||||
cols = boundInt(cols, MinTermCols, MaxTermCols);
|
||||
return cols;
|
||||
}
|
||||
|
||||
function windowHeightToRows(height : number, fontSize : number) : number {
|
||||
function windowHeightToRows(height: number, fontSize: number): number {
|
||||
let dr = getMonoFontSize(fontSize);
|
||||
let rows = Math.floor((height - 80) / dr.height) - 1;
|
||||
if (rows <= 0) {
|
||||
@ -71,14 +73,14 @@ function windowHeightToRows(height : number, fontSize : number) : number {
|
||||
return rows;
|
||||
}
|
||||
|
||||
function termWidthFromCols(cols : number, fontSize : number) : number {
|
||||
function termWidthFromCols(cols: number, fontSize: number): number {
|
||||
let dr = getMonoFontSize(fontSize);
|
||||
return Math.ceil(dr.width*cols) + 15;
|
||||
return Math.ceil(dr.width * cols) + 15;
|
||||
}
|
||||
|
||||
function termHeightFromRows(rows : number, fontSize : number) : number {
|
||||
function termHeightFromRows(rows: number, fontSize: number): number {
|
||||
let dr = getMonoFontSize(fontSize);
|
||||
return Math.ceil(dr.height*rows);
|
||||
return Math.ceil(dr.height * rows);
|
||||
}
|
||||
|
||||
export {measureText, getMonoFontSize, windowWidthToCols, windowHeightToRows, termWidthFromCols, termHeightFromRows};
|
||||
export { measureText, getMonoFontSize, windowWidthToCols, windowHeightToRows, termWidthFromCols, termHeightFromRows };
|
||||
|
197
src/util.ts
197
src/util.ts
@ -1,44 +1,47 @@
|
||||
import * as mobx from "mobx";
|
||||
import {sprintf} from "sprintf-js";
|
||||
import { sprintf } from "sprintf-js";
|
||||
import dayjs from "dayjs";
|
||||
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||
import type {RemoteType} from "./types";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import type { RemoteType } from "./types";
|
||||
|
||||
dayjs.extend(localizedFormat)
|
||||
dayjs.extend(localizedFormat);
|
||||
|
||||
function isBlank(s : string) : boolean {
|
||||
return (s == null || s == "");
|
||||
function isBlank(s: string): boolean {
|
||||
return s == null || s == "";
|
||||
}
|
||||
|
||||
function handleNotOkResp(resp : any, url : URL) : Promise<any> {
|
||||
let errMsg = sprintf("Bad status code response from fetch '%s': code=%d %s", url.toString(), resp.status, resp.statusText);
|
||||
function handleNotOkResp(resp: any, url: URL): Promise<any> {
|
||||
let errMsg = sprintf(
|
||||
"Bad status code response from fetch '%s': code=%d %s",
|
||||
url.toString(),
|
||||
resp.status,
|
||||
resp.statusText
|
||||
);
|
||||
return resp.text().then((textData) => {
|
||||
if (textData == null || textData == "") {
|
||||
throw new Error(errMsg);
|
||||
}
|
||||
let rtnData : any = null;
|
||||
let rtnData: any = null;
|
||||
try {
|
||||
rtnData = JSON.parse(textData);
|
||||
}
|
||||
catch (err) {
|
||||
} catch (err) {
|
||||
// nothing (rtnData will be null)
|
||||
}
|
||||
if (rtnData != null && typeof(rtnData) == "object" && rtnData["error"] != null) {
|
||||
if (rtnData != null && typeof rtnData == "object" && rtnData["error"] != null) {
|
||||
throw new Error(rtnData["error"]);
|
||||
}
|
||||
throw new Error(errMsg + "\n" + textData);
|
||||
});
|
||||
}
|
||||
|
||||
function fetchJsonData(resp : any, ctErr : boolean) : Promise<any> {
|
||||
function fetchJsonData(resp: any, ctErr: boolean): Promise<any> {
|
||||
let contentType = resp.headers.get("Content-Type");
|
||||
if (contentType != null && contentType.startsWith("application/json")) {
|
||||
return resp.text().then((textData) => {
|
||||
let rtnData : any = null;
|
||||
let rtnData: any = null;
|
||||
try {
|
||||
rtnData = JSON.parse(textData);
|
||||
}
|
||||
catch (err) {
|
||||
} catch (err) {
|
||||
let errMsg = sprintf("Unparseable JSON: " + err.message);
|
||||
let rtnErr = new Error(errMsg);
|
||||
throw rtnErr;
|
||||
@ -54,7 +57,7 @@ function fetchJsonData(resp : any, ctErr : boolean) : Promise<any> {
|
||||
}
|
||||
}
|
||||
|
||||
function handleJsonFetchResponse(url : URL, resp : any) : Promise<any> {
|
||||
function handleJsonFetchResponse(url: URL, resp: any): Promise<any> {
|
||||
if (!resp.ok) {
|
||||
return handleNotOkResp(resp, url);
|
||||
}
|
||||
@ -62,40 +65,45 @@ function handleJsonFetchResponse(url : URL, resp : any) : Promise<any> {
|
||||
return rtnData;
|
||||
}
|
||||
|
||||
function base64ToArray(b64 : string) : Uint8Array {
|
||||
function base64ToArray(b64: string): Uint8Array {
|
||||
let rawStr = atob(b64);
|
||||
let rtnArr = new Uint8Array(new ArrayBuffer(rawStr.length));
|
||||
for (let i=0; i<rawStr.length; i++) {
|
||||
for (let i = 0; i < rawStr.length; i++) {
|
||||
rtnArr[i] = rawStr.charCodeAt(i);
|
||||
}
|
||||
return rtnArr;
|
||||
}
|
||||
|
||||
interface IDataType {
|
||||
remove? : boolean;
|
||||
full? : boolean;
|
||||
remove?: boolean;
|
||||
full?: boolean;
|
||||
}
|
||||
|
||||
interface IObjType<DataType> {
|
||||
dispose : () => void;
|
||||
mergeData : (data : DataType) => void,
|
||||
dispose: () => void;
|
||||
mergeData: (data: DataType) => void;
|
||||
}
|
||||
|
||||
interface ISimpleDataType {
|
||||
remove? : boolean;
|
||||
remove?: boolean;
|
||||
}
|
||||
|
||||
function genMergeSimpleData<T extends ISimpleDataType>(objs : mobx.IObservableArray<T>, dataArr : T[], idFn : (obj : T) => string, sortIdxFn : (obj : T) => string) {
|
||||
function genMergeSimpleData<T extends ISimpleDataType>(
|
||||
objs: mobx.IObservableArray<T>,
|
||||
dataArr: T[],
|
||||
idFn: (obj: T) => string,
|
||||
sortIdxFn: (obj: T) => string
|
||||
) {
|
||||
if (dataArr == null || dataArr.length == 0) {
|
||||
return;
|
||||
}
|
||||
let objMap : Record<string, T> = {};
|
||||
for (let i=0; i<objs.length; i++) {
|
||||
let objMap: Record<string, T> = {};
|
||||
for (let i = 0; i < objs.length; i++) {
|
||||
let obj = objs[i];
|
||||
let id = idFn(obj);
|
||||
objMap[id] = obj;
|
||||
}
|
||||
for (let i=0; i<dataArr.length; i++) {
|
||||
for (let i = 0; i < dataArr.length; i++) {
|
||||
let dataItem = dataArr[i];
|
||||
if (dataItem == null) {
|
||||
console.log("genMergeSimpleData, null item");
|
||||
@ -105,8 +113,7 @@ function genMergeSimpleData<T extends ISimpleDataType>(objs : mobx.IObservableAr
|
||||
if (dataItem.remove) {
|
||||
delete objMap[id];
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
objMap[id] = dataItem;
|
||||
}
|
||||
}
|
||||
@ -122,23 +129,23 @@ function genMergeSimpleData<T extends ISimpleDataType>(objs : mobx.IObservableAr
|
||||
}
|
||||
|
||||
function genMergeData<ObjType extends IObjType<DataType>, DataType extends IDataType>(
|
||||
objs : mobx.IObservableArray<ObjType>,
|
||||
dataArr : DataType[],
|
||||
objIdFn : (obj : ObjType) => string,
|
||||
dataIdFn : (data : DataType) => string,
|
||||
ctorFn : (data : DataType) => ObjType,
|
||||
sortIdxFn : (obj : ObjType) => number,
|
||||
objs: mobx.IObservableArray<ObjType>,
|
||||
dataArr: DataType[],
|
||||
objIdFn: (obj: ObjType) => string,
|
||||
dataIdFn: (data: DataType) => string,
|
||||
ctorFn: (data: DataType) => ObjType,
|
||||
sortIdxFn: (obj: ObjType) => number
|
||||
) {
|
||||
if (dataArr == null || dataArr.length == 0) {
|
||||
return;
|
||||
}
|
||||
let objMap : Record<string, ObjType> = {};
|
||||
for (let i=0; i<objs.length; i++) {
|
||||
let objMap: Record<string, ObjType> = {};
|
||||
for (let i = 0; i < objs.length; i++) {
|
||||
let obj = objs[i];
|
||||
let id = objIdFn(obj);
|
||||
objMap[id] = obj;
|
||||
}
|
||||
for (let i=0; i<dataArr.length; i++) {
|
||||
for (let i = 0; i < dataArr.length; i++) {
|
||||
let dataItem = dataArr[i];
|
||||
if (dataItem == null) {
|
||||
console.log("genMergeData, null item");
|
||||
@ -157,7 +164,7 @@ function genMergeData<ObjType extends IObjType<DataType>, DataType extends IData
|
||||
if (obj == null) {
|
||||
if (!dataItem.full) {
|
||||
console.log("cannot create object, dataitem is not full", objs, dataItem);
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
obj = ctorFn(dataItem);
|
||||
objMap[id] = obj;
|
||||
@ -175,18 +182,17 @@ function genMergeData<ObjType extends IObjType<DataType>, DataType extends IData
|
||||
}
|
||||
|
||||
function genMergeDataMap<ObjType extends IObjType<DataType>, DataType extends IDataType>(
|
||||
objMap : mobx.ObservableMap<string, ObjType>,
|
||||
dataArr : DataType[],
|
||||
objIdFn : (obj : ObjType) => string,
|
||||
dataIdFn : (data : DataType) => string,
|
||||
ctorFn : (data : DataType) => ObjType,
|
||||
) : {added : string[], removed : string[]}
|
||||
{
|
||||
let rtn : {added : string[], removed : string[]} = {added: [], removed: []};
|
||||
objMap: mobx.ObservableMap<string, ObjType>,
|
||||
dataArr: DataType[],
|
||||
objIdFn: (obj: ObjType) => string,
|
||||
dataIdFn: (data: DataType) => string,
|
||||
ctorFn: (data: DataType) => ObjType
|
||||
): { added: string[]; removed: string[] } {
|
||||
let rtn: { added: string[]; removed: string[] } = { added: [], removed: [] };
|
||||
if (dataArr == null || dataArr.length == 0) {
|
||||
return rtn;
|
||||
}
|
||||
for (let i=0; i<dataArr.length; i++) {
|
||||
for (let i = 0; i < dataArr.length; i++) {
|
||||
let dataItem = dataArr[i];
|
||||
if (dataItem == null) {
|
||||
console.log("genMergeDataMap, null item");
|
||||
@ -206,7 +212,7 @@ function genMergeDataMap<ObjType extends IObjType<DataType>, DataType extends ID
|
||||
if (obj == null) {
|
||||
if (!dataItem.full) {
|
||||
console.log("cannot create object, dataitem is not full", dataItem);
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
obj = ctorFn(dataItem);
|
||||
objMap.set(id, obj);
|
||||
@ -218,11 +224,11 @@ function genMergeDataMap<ObjType extends IObjType<DataType>, DataType extends ID
|
||||
return rtn;
|
||||
}
|
||||
|
||||
function parseEnv0(envStr64 : string) : Map<string, string> {
|
||||
function parseEnv0(envStr64: string): Map<string, string> {
|
||||
let envStr = atob(envStr64);
|
||||
let parts = envStr.split("\x00");
|
||||
let rtn : Map<string, string> = new Map();
|
||||
for (let i=0; i<parts.length; i++) {
|
||||
let rtn: Map<string, string> = new Map();
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
let part = parts[i];
|
||||
if (part == "") {
|
||||
continue;
|
||||
@ -232,13 +238,13 @@ function parseEnv0(envStr64 : string) : Map<string, string> {
|
||||
continue;
|
||||
}
|
||||
let varName = part.substr(0, eqIdx);
|
||||
let varVal = part.substr(eqIdx+1)
|
||||
rtn.set(varName, varVal)
|
||||
let varVal = part.substr(eqIdx + 1);
|
||||
rtn.set(varName, varVal);
|
||||
}
|
||||
return rtn;
|
||||
}
|
||||
|
||||
function boundInt(ival : number, minVal : number, maxVal : number) : number {
|
||||
function boundInt(ival: number, minVal: number, maxVal: number): number {
|
||||
if (ival < minVal) {
|
||||
return minVal;
|
||||
}
|
||||
@ -248,22 +254,34 @@ function boundInt(ival : number, minVal : number, maxVal : number) : number {
|
||||
return ival;
|
||||
}
|
||||
|
||||
function isModKeyPress(e : any) {
|
||||
function isModKeyPress(e: any) {
|
||||
return e.code.match(/^(Control|Meta|Alt|Shift)(Left|Right)$/);
|
||||
}
|
||||
|
||||
function incObs(inum : mobx.IObservableValue<number>) {
|
||||
function incObs(inum: mobx.IObservableValue<number>) {
|
||||
mobx.action(() => {
|
||||
inum.set(inum.get() + 1);
|
||||
})();
|
||||
}
|
||||
|
||||
function loadFonts() {
|
||||
let jbmFontNormal = new FontFace("JetBrains Mono", "url('static/fonts/jetbrains-mono-v13-latin-regular.woff2')", {style: "normal", weight: "400"});
|
||||
let jbmFont200 = new FontFace("JetBrains Mono", "url('static/fonts/jetbrains-mono-v13-latin-200.woff2')", {style: "normal", weight: "200"});
|
||||
let jbmFont700 = new FontFace("JetBrains Mono", "url('static/fonts/jetbrains-mono-v13-latin-700.woff2')", {style: "normal", weight: "700"});
|
||||
let faFont = new FontFace("FontAwesome", "url(static/fonts/fontawesome-webfont-4.7.woff2)", {style: "normal", weight: "normal"});
|
||||
let docFonts : any = document.fonts; // work around ts typing issue
|
||||
let jbmFontNormal = new FontFace("JetBrains Mono", "url('static/fonts/jetbrains-mono-v13-latin-regular.woff2')", {
|
||||
style: "normal",
|
||||
weight: "400",
|
||||
});
|
||||
let jbmFont200 = new FontFace("JetBrains Mono", "url('static/fonts/jetbrains-mono-v13-latin-200.woff2')", {
|
||||
style: "normal",
|
||||
weight: "200",
|
||||
});
|
||||
let jbmFont700 = new FontFace("JetBrains Mono", "url('static/fonts/jetbrains-mono-v13-latin-700.woff2')", {
|
||||
style: "normal",
|
||||
weight: "700",
|
||||
});
|
||||
let faFont = new FontFace("FontAwesome", "url(static/fonts/fontawesome-webfont-4.7.woff2)", {
|
||||
style: "normal",
|
||||
weight: "normal",
|
||||
});
|
||||
let docFonts: any = document.fonts; // work around ts typing issue
|
||||
docFonts.add(jbmFontNormal);
|
||||
docFonts.add(jbmFont200);
|
||||
docFonts.add(jbmFont700);
|
||||
@ -276,19 +294,19 @@ function loadFonts() {
|
||||
|
||||
const DOW_STRS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
||||
|
||||
function getTodayStr() : string {
|
||||
function getTodayStr(): string {
|
||||
return getDateStr(new Date());
|
||||
}
|
||||
|
||||
function getYesterdayStr() : string {
|
||||
function getYesterdayStr(): string {
|
||||
let d = new Date();
|
||||
d.setDate(d.getDate()-1);
|
||||
d.setDate(d.getDate() - 1);
|
||||
return getDateStr(d);
|
||||
}
|
||||
|
||||
function getDateStr(d : Date) : string {
|
||||
function getDateStr(d: Date): string {
|
||||
let yearStr = String(d.getFullYear());
|
||||
let monthStr = String(d.getMonth()+1);
|
||||
let monthStr = String(d.getMonth() + 1);
|
||||
if (monthStr.length == 1) {
|
||||
monthStr = "0" + monthStr;
|
||||
}
|
||||
@ -300,7 +318,7 @@ function getDateStr(d : Date) : string {
|
||||
return dowStr + " " + yearStr + "-" + monthStr + "-" + dayStr;
|
||||
}
|
||||
|
||||
function getRemoteConnVal(r : RemoteType) : number {
|
||||
function getRemoteConnVal(r: RemoteType): number {
|
||||
if (r.status == "connected") {
|
||||
return 1;
|
||||
}
|
||||
@ -316,7 +334,7 @@ function getRemoteConnVal(r : RemoteType) : number {
|
||||
return 5;
|
||||
}
|
||||
|
||||
function sortAndFilterRemotes(origRemotes : RemoteType[]) : RemoteType[] {
|
||||
function sortAndFilterRemotes(origRemotes: RemoteType[]): RemoteType[] {
|
||||
let remotes = origRemotes.filter((r) => !r.archived);
|
||||
remotes.sort((a, b) => {
|
||||
let connValA = getRemoteConnVal(a);
|
||||
@ -329,23 +347,48 @@ function sortAndFilterRemotes(origRemotes : RemoteType[]) : RemoteType[] {
|
||||
return remotes;
|
||||
}
|
||||
|
||||
function makeExternLink(url : string) : string {
|
||||
function makeExternLink(url: string): string {
|
||||
return "https://extern?" + encodeURIComponent(url);
|
||||
}
|
||||
|
||||
function isStrEq(s1 : string, s2 : string) {
|
||||
function isStrEq(s1: string, s2: string) {
|
||||
if (isBlank(s1) && isBlank(s2)) {
|
||||
return true;
|
||||
}
|
||||
return s1 == s2;
|
||||
}
|
||||
|
||||
function isBoolEq(b1 : boolean, b2 : boolean) {
|
||||
return (!!b1) == (!!b2);
|
||||
function isBoolEq(b1: boolean, b2: boolean) {
|
||||
return !!b1 == !!b2;
|
||||
}
|
||||
|
||||
function hasNoModifiers(e : any) : boolean {
|
||||
return !e.getModifierState("Shift") && !e.getModifierState("Control") && !e.getModifierState("Meta") && !e.getModifierState("Alt");
|
||||
function hasNoModifiers(e: any): boolean {
|
||||
return (
|
||||
!e.getModifierState("Shift") &&
|
||||
!e.getModifierState("Control") &&
|
||||
!e.getModifierState("Meta") &&
|
||||
!e.getModifierState("Alt")
|
||||
);
|
||||
}
|
||||
|
||||
export {handleJsonFetchResponse, base64ToArray, genMergeData, genMergeDataMap, genMergeSimpleData, parseEnv0, boundInt, isModKeyPress, incObs, isBlank, loadFonts, getTodayStr, getYesterdayStr, getDateStr, sortAndFilterRemotes, makeExternLink, isStrEq, isBoolEq, hasNoModifiers};
|
||||
export {
|
||||
handleJsonFetchResponse,
|
||||
base64ToArray,
|
||||
genMergeData,
|
||||
genMergeDataMap,
|
||||
genMergeSimpleData,
|
||||
parseEnv0,
|
||||
boundInt,
|
||||
isModKeyPress,
|
||||
incObs,
|
||||
isBlank,
|
||||
loadFonts,
|
||||
getTodayStr,
|
||||
getYesterdayStr,
|
||||
getDateStr,
|
||||
sortAndFilterRemotes,
|
||||
makeExternLink,
|
||||
isStrEq,
|
||||
isBoolEq,
|
||||
hasNoModifiers,
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
.mono-font(@size: inherit, @weight: inherit) {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-family: "JetBrains Mono", monospace;
|
||||
font-size: @size;
|
||||
font-weight: @weight;
|
||||
}
|
||||
|
@ -16,80 +16,78 @@ const MaxJsonSize = 50000;
|
||||
|
||||
@mobxReact.observer
|
||||
class SourceCodeRenderer extends React.Component<
|
||||
{
|
||||
data: Blob;
|
||||
path: String;
|
||||
context: RendererContext;
|
||||
opts: RendererOpts;
|
||||
savedHeight: number;
|
||||
},
|
||||
{}
|
||||
{
|
||||
data: Blob;
|
||||
path: String;
|
||||
context: RendererContext;
|
||||
opts: RendererOpts;
|
||||
savedHeight: number;
|
||||
},
|
||||
{}
|
||||
> {
|
||||
code: OV<any> = mobx.observable.box(null, {
|
||||
name: "code",
|
||||
deep: false,
|
||||
});
|
||||
language: OV<any> = mobx.observable.box(null, {
|
||||
name: "language",
|
||||
deep: false,
|
||||
});
|
||||
code: OV<any> = mobx.observable.box(null, {
|
||||
name: "code",
|
||||
deep: false,
|
||||
});
|
||||
language: OV<any> = mobx.observable.box(null, {
|
||||
name: "language",
|
||||
deep: false,
|
||||
});
|
||||
|
||||
editorRef;
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.editorRef = React.createRef();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
let prtn = this.props.data.text();
|
||||
prtn.then((text) => this.code.set(text));
|
||||
}
|
||||
|
||||
handleEditorDidMount = (editor, monaco) => {
|
||||
const extension = this.props.cmdstr.split(".").pop();
|
||||
const detectedLanguage = monaco.languages
|
||||
.getLanguages()
|
||||
.find(
|
||||
(lang) => lang.extensions && lang.extensions.includes("." + extension)
|
||||
);
|
||||
if (detectedLanguage) {
|
||||
this.editorRef.current = editor;
|
||||
const model = editor.getModel();
|
||||
if (model) {
|
||||
monaco.editor.setModelLanguage(model, detectedLanguage.id);
|
||||
this.language.set(detectedLanguage.id);
|
||||
}
|
||||
editorRef;
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.editorRef = React.createRef();
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
let opts = this.props.opts;
|
||||
let maxWidth = opts.maxSize.width;
|
||||
let minWidth = opts.maxSize.width;
|
||||
if (minWidth > 1000) {
|
||||
minWidth = 1000;
|
||||
componentDidMount() {
|
||||
let prtn = this.props.data.text();
|
||||
prtn.then((text) => this.code.set(text));
|
||||
}
|
||||
|
||||
handleEditorDidMount = (editor, monaco) => {
|
||||
const extension = this.props.cmdstr.split(".").pop();
|
||||
const detectedLanguage = monaco.languages
|
||||
.getLanguages()
|
||||
.find((lang) => lang.extensions && lang.extensions.includes("." + extension));
|
||||
if (detectedLanguage) {
|
||||
this.editorRef.current = editor;
|
||||
const model = editor.getModel();
|
||||
if (model) {
|
||||
monaco.editor.setModelLanguage(model, detectedLanguage.id);
|
||||
this.language.set(detectedLanguage.id);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
let opts = this.props.opts;
|
||||
let maxWidth = opts.maxSize.width;
|
||||
let minWidth = opts.maxSize.width;
|
||||
if (minWidth > 1000) {
|
||||
minWidth = 1000;
|
||||
}
|
||||
let lang = this.language.get();
|
||||
let code = this.code.get();
|
||||
if (!code) return <></>;
|
||||
return (
|
||||
<div className="renderer-container code-renderer">
|
||||
<div className="scroller" style={{ maxHeight: opts.maxSize.height }}>
|
||||
<Editor
|
||||
height="30vh"
|
||||
theme="hc-black"
|
||||
defaultLanguage={lang}
|
||||
defaultValue={code}
|
||||
onMount={this.handleEditorDidMount}
|
||||
options={{
|
||||
scrollBeyondLastLine: false,
|
||||
fontSize: "14px",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
let lang = this.language.get();
|
||||
let code = this.code.get();
|
||||
if (!code) return <></>;
|
||||
return (
|
||||
<div className="renderer-container code-renderer">
|
||||
<div className="scroller" style={{ maxHeight: opts.maxSize.height }}>
|
||||
<Editor
|
||||
height="30vh"
|
||||
theme="hc-black"
|
||||
defaultLanguage={lang}
|
||||
defaultValue={code}
|
||||
onMount={this.handleEditorDidMount}
|
||||
options={{
|
||||
scrollBeyondLastLine: false,
|
||||
fontSize: "14px",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { SourceCodeRenderer };
|
||||
|
@ -2,8 +2,8 @@ import * as React from "react";
|
||||
import * as mobx from "mobx";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import cn from "classnames";
|
||||
import {If, For, When, Otherwise, Choose} from "tsx-control-statements/components";
|
||||
import {WindowSize, RendererContext, TermOptsType, LineType, RendererOpts} from "../types";
|
||||
import { If, For, When, Otherwise, Choose } from "tsx-control-statements/components";
|
||||
import { WindowSize, RendererContext, TermOptsType, LineType, RendererOpts } from "../types";
|
||||
|
||||
type OV<V> = mobx.IObservableValue<V>;
|
||||
type CV<V> = mobx.IComputedValue<V>;
|
||||
@ -26,10 +26,13 @@ type CV<V> = mobx.IComputedValue<V>;
|
||||
//
|
||||
|
||||
@mobxReact.observer
|
||||
class SimpleImageRenderer extends React.Component<{data : Blob, context : RendererContext, opts : RendererOpts, savedHeight : number}, {}> {
|
||||
objUrl : string = null;
|
||||
imageRef : React.RefObject<any> = React.createRef();
|
||||
imageLoaded : OV<boolean> = mobx.observable.box(false, {name: "imageLoaded"});
|
||||
class SimpleImageRenderer extends React.Component<
|
||||
{ data: Blob; context: RendererContext; opts: RendererOpts; savedHeight: number },
|
||||
{}
|
||||
> {
|
||||
objUrl: string = null;
|
||||
imageRef: React.RefObject<any> = React.createRef();
|
||||
imageLoaded: OV<boolean> = mobx.observable.box(false, { name: "imageLoaded" });
|
||||
|
||||
componentDidMount() {
|
||||
let img = this.imageRef.current;
|
||||
@ -56,24 +59,27 @@ class SimpleImageRenderer extends React.Component<{data : Blob, context : Render
|
||||
URL.revokeObjectURL(this.objUrl);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
if (this.objUrl == null) {
|
||||
let dataBlob = this.props.data;
|
||||
this.objUrl = URL.createObjectURL(dataBlob);
|
||||
}
|
||||
let opts = this.props.opts;
|
||||
let forceHeight : number = null;
|
||||
let forceHeight: number = null;
|
||||
if (!this.imageLoaded.get() && this.props.savedHeight >= 0) {
|
||||
forceHeight = this.props.savedHeight;
|
||||
}
|
||||
return (
|
||||
<div className="renderer-container image-renderer" style={{height: forceHeight}}>
|
||||
<img ref={this.imageRef} style={{maxHeight: opts.idealSize.height, maxWidth: opts.idealSize.width}} src={this.objUrl}/>
|
||||
<div className="renderer-container image-renderer" style={{ height: forceHeight }}>
|
||||
<img
|
||||
ref={this.imageRef}
|
||||
style={{ maxHeight: opts.idealSize.height, maxWidth: opts.idealSize.width }}
|
||||
src={this.objUrl}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export {SimpleImageRenderer};
|
||||
|
||||
export { SimpleImageRenderer };
|
||||
|
@ -2,10 +2,10 @@ import * as React from "react";
|
||||
import * as mobx from "mobx";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import cn from "classnames";
|
||||
import {If, For, When, Otherwise, Choose} from "tsx-control-statements/components";
|
||||
import {WindowSize, RendererContext, TermOptsType, LineType, RendererOpts} from "../types";
|
||||
import {sprintf} from "sprintf-js";
|
||||
import {Markdown} from "../elements";
|
||||
import { If, For, When, Otherwise, Choose } from "tsx-control-statements/components";
|
||||
import { WindowSize, RendererContext, TermOptsType, LineType, RendererOpts } from "../types";
|
||||
import { sprintf } from "sprintf-js";
|
||||
import { Markdown } from "../elements";
|
||||
import ReactJson from "react-json-view";
|
||||
|
||||
type OV<V> = mobx.IObservableValue<V>;
|
||||
@ -13,11 +13,14 @@ type OV<V> = mobx.IObservableValue<V>;
|
||||
const MaxJsonSize = 50000;
|
||||
|
||||
@mobxReact.observer
|
||||
class SimpleJsonRenderer extends React.Component<{data : Blob, context : RendererContext, opts : RendererOpts, savedHeight : number}, {}> {
|
||||
jsonObj : OV<any> = mobx.observable.box(null, {name: "jsonObj", deep: false});
|
||||
jsonError : OV<string> = mobx.observable.box(null, {name: "jsonError"});
|
||||
class SimpleJsonRenderer extends React.Component<
|
||||
{ data: Blob; context: RendererContext; opts: RendererOpts; savedHeight: number },
|
||||
{}
|
||||
> {
|
||||
jsonObj: OV<any> = mobx.observable.box(null, { name: "jsonObj", deep: false });
|
||||
jsonError: OV<string> = mobx.observable.box(null, { name: "jsonError" });
|
||||
|
||||
setJsonError(err : string) {
|
||||
setJsonError(err: string) {
|
||||
mobx.action(() => {
|
||||
this.jsonError.set(err);
|
||||
})();
|
||||
@ -29,7 +32,7 @@ class SimpleJsonRenderer extends React.Component<{data : Blob, context : Rendere
|
||||
this.setJsonError(sprintf("error: json too large to render size=%d", dataBlob.size));
|
||||
return;
|
||||
}
|
||||
let prtn = dataBlob.text()
|
||||
let prtn = dataBlob.text();
|
||||
prtn.then((text) => {
|
||||
if (/[\x00-\x08]/.test(text)) {
|
||||
this.setJsonError(sprintf("error: not rendering json, binary characters detected"));
|
||||
@ -40,19 +43,22 @@ class SimpleJsonRenderer extends React.Component<{data : Blob, context : Rendere
|
||||
mobx.action(() => {
|
||||
this.jsonObj.set(obj);
|
||||
})();
|
||||
}
|
||||
catch (e) {
|
||||
} catch (e) {
|
||||
this.setJsonError(sprintf("error: JSON parse error: %s", e.message));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
if (this.jsonError.get() != null) {
|
||||
return <div className="renderer-container json-renderer"><div className="error-container">{this.jsonError.get()}</div></div>;
|
||||
return (
|
||||
<div className="renderer-container json-renderer">
|
||||
<div className="error-container">{this.jsonError.get()}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (this.jsonObj.get() == null) {
|
||||
return <div className="renderer-container json-renderer" style={{height: this.props.savedHeight}}/>
|
||||
return <div className="renderer-container json-renderer" style={{ height: this.props.savedHeight }} />;
|
||||
}
|
||||
let opts = this.props.opts;
|
||||
let maxWidth = opts.maxSize.width;
|
||||
@ -62,12 +68,19 @@ class SimpleJsonRenderer extends React.Component<{data : Blob, context : Rendere
|
||||
}
|
||||
return (
|
||||
<div className="renderer-container json-renderer">
|
||||
<div className="scroller" style={{maxHeight: opts.maxSize.height}}>
|
||||
<ReactJson src={this.jsonObj.get()} theme="monokai" style={{backgroundColor: "black"}} displayDataTypes={false} quotesOnKeys={false} sortKeys={true}/>
|
||||
<div className="scroller" style={{ maxHeight: opts.maxSize.height }}>
|
||||
<ReactJson
|
||||
src={this.jsonObj.get()}
|
||||
theme="monokai"
|
||||
style={{ backgroundColor: "black" }}
|
||||
displayDataTypes={false}
|
||||
quotesOnKeys={false}
|
||||
sortKeys={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export {SimpleJsonRenderer};
|
||||
export { SimpleJsonRenderer };
|
||||
|
@ -2,19 +2,22 @@ import * as React from "react";
|
||||
import * as mobx from "mobx";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import cn from "classnames";
|
||||
import {If, For, When, Otherwise, Choose} from "tsx-control-statements/components";
|
||||
import {WindowSize, RendererContext, TermOptsType, LineType, RendererOpts} from "../types";
|
||||
import {sprintf} from "sprintf-js";
|
||||
import {Markdown} from "../elements";
|
||||
import { If, For, When, Otherwise, Choose } from "tsx-control-statements/components";
|
||||
import { WindowSize, RendererContext, TermOptsType, LineType, RendererOpts } from "../types";
|
||||
import { sprintf } from "sprintf-js";
|
||||
import { Markdown } from "../elements";
|
||||
|
||||
type OV<V> = mobx.IObservableValue<V>;
|
||||
|
||||
const MaxMarkdownSize = 50000;
|
||||
|
||||
@mobxReact.observer
|
||||
class SimpleMarkdownRenderer extends React.Component<{data : Blob, context : RendererContext, opts : RendererOpts, savedHeight : number}, {}> {
|
||||
markdownText : OV<string> = mobx.observable.box(null, {name: "markdownText"});
|
||||
markdownError : OV<string> = mobx.observable.box(null, {name: "markdownError"});
|
||||
class SimpleMarkdownRenderer extends React.Component<
|
||||
{ data: Blob; context: RendererContext; opts: RendererOpts; savedHeight: number },
|
||||
{}
|
||||
> {
|
||||
markdownText: OV<string> = mobx.observable.box(null, { name: "markdownText" });
|
||||
markdownError: OV<string> = mobx.observable.box(null, { name: "markdownError" });
|
||||
|
||||
componentDidMount() {
|
||||
let dataBlob = this.props.data;
|
||||
@ -22,7 +25,7 @@ class SimpleMarkdownRenderer extends React.Component<{data : Blob, context : Ren
|
||||
this.markdownError.set(sprintf("error: markdown too large to render size=%d", dataBlob.size));
|
||||
return;
|
||||
}
|
||||
let prtn = dataBlob.text()
|
||||
let prtn = dataBlob.text();
|
||||
prtn.then((text) => {
|
||||
if (/[\x00-\x08]/.test(text)) {
|
||||
this.markdownError.set(sprintf("error: not rendering markdown, binary characters detected"));
|
||||
@ -33,13 +36,17 @@ class SimpleMarkdownRenderer extends React.Component<{data : Blob, context : Ren
|
||||
})();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
if (this.markdownError.get() != null) {
|
||||
return <div className="renderer-container markdown-renderer"><div className="error-container">{this.markdownError.get()}</div></div>;
|
||||
return (
|
||||
<div className="renderer-container markdown-renderer">
|
||||
<div className="error-container">{this.markdownError.get()}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (this.markdownText.get() == null) {
|
||||
return <div className="renderer-container markdown-renderer" style={{height: this.props.savedHeight}}/>
|
||||
return <div className="renderer-container markdown-renderer" style={{ height: this.props.savedHeight }} />;
|
||||
}
|
||||
let opts = this.props.opts;
|
||||
let markdownText = this.markdownText.get();
|
||||
@ -50,12 +57,20 @@ class SimpleMarkdownRenderer extends React.Component<{data : Blob, context : Ren
|
||||
}
|
||||
return (
|
||||
<div className="renderer-container markdown-renderer">
|
||||
<div className="scroller" style={{maxHeight: opts.maxSize.height, minWidth: minWidth, width: "min-content", maxWidth: maxWidth}}>
|
||||
<Markdown text={this.markdownText.get()} style={{maxHeight: opts.maxSize.height}}/>
|
||||
<div
|
||||
className="scroller"
|
||||
style={{
|
||||
maxHeight: opts.maxSize.height,
|
||||
minWidth: minWidth,
|
||||
width: "min-content",
|
||||
maxWidth: maxWidth,
|
||||
}}
|
||||
>
|
||||
<Markdown text={this.markdownText.get()} style={{ maxHeight: opts.maxSize.height }} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export {SimpleMarkdownRenderer};
|
||||
export { SimpleMarkdownRenderer };
|
||||
|
@ -2,50 +2,50 @@ import * as React from "react";
|
||||
import * as mobx from "mobx";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import cn from "classnames";
|
||||
import {If, For, When, Otherwise, Choose} from "tsx-control-statements/components";
|
||||
import { If, For, When, Otherwise, Choose } from "tsx-control-statements/components";
|
||||
import * as T from "../types";
|
||||
import {debounce, throttle} from "throttle-debounce";
|
||||
import {boundMethod} from "autobind-decorator";
|
||||
import {sprintf} from "sprintf-js";
|
||||
import {PacketDataBuffer} from "../ptydata";
|
||||
import {Markdown} from "../elements";
|
||||
import { debounce, throttle } from "throttle-debounce";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { sprintf } from "sprintf-js";
|
||||
import { PacketDataBuffer } from "../ptydata";
|
||||
import { Markdown } from "../elements";
|
||||
|
||||
type OV<V> = mobx.IObservableValue<V>;
|
||||
type OArr<V> = mobx.IObservableArray<V>;
|
||||
type OMap<K,V> = mobx.ObservableMap<K,V>;
|
||||
type OMap<K, V> = mobx.ObservableMap<K, V>;
|
||||
|
||||
type OpenAIOutputType = {
|
||||
model : string,
|
||||
created : number,
|
||||
finish_reason : string,
|
||||
message : string,
|
||||
model: string;
|
||||
created: number;
|
||||
finish_reason: string;
|
||||
message: string;
|
||||
};
|
||||
|
||||
class OpenAIRendererModel {
|
||||
context : T.RendererContext;
|
||||
opts : T.RendererOpts;
|
||||
isDone : OV<boolean>;
|
||||
api : T.RendererModelContainerApi;
|
||||
savedHeight : number;
|
||||
loading : OV<boolean>;
|
||||
loadError : OV<string> = mobx.observable.box(null, {name: "renderer-loadError"});
|
||||
updateHeight_debounced : (newHeight : number) => void;
|
||||
ptyDataSource : (termContext : T.TermContextUnion) => Promise<T.PtyDataType>;
|
||||
packetData : PacketDataBuffer;
|
||||
rawCmd : T.WebCmd;
|
||||
output : OV<OpenAIOutputType>;
|
||||
version : OV<number>;
|
||||
|
||||
context: T.RendererContext;
|
||||
opts: T.RendererOpts;
|
||||
isDone: OV<boolean>;
|
||||
api: T.RendererModelContainerApi;
|
||||
savedHeight: number;
|
||||
loading: OV<boolean>;
|
||||
loadError: OV<string> = mobx.observable.box(null, { name: "renderer-loadError" });
|
||||
updateHeight_debounced: (newHeight: number) => void;
|
||||
ptyDataSource: (termContext: T.TermContextUnion) => Promise<T.PtyDataType>;
|
||||
packetData: PacketDataBuffer;
|
||||
rawCmd: T.WebCmd;
|
||||
output: OV<OpenAIOutputType>;
|
||||
version: OV<number>;
|
||||
|
||||
constructor() {
|
||||
this.updateHeight_debounced = debounce(1000, this.updateHeight.bind(this));
|
||||
this.packetData = new PacketDataBuffer(this.packetCallback);
|
||||
this.output = mobx.observable.box(null, {name: "openai-output"});
|
||||
this.output = mobx.observable.box(null, { name: "openai-output" });
|
||||
this.version = mobx.observable.box(0);
|
||||
}
|
||||
|
||||
initialize(params : T.RendererModelInitializeParams) : void {
|
||||
this.loading = mobx.observable.box(true, {name: "renderer-loading"});
|
||||
this.isDone = mobx.observable.box(params.isDone, {name: "renderer-isDone"});
|
||||
initialize(params: T.RendererModelInitializeParams): void {
|
||||
this.loading = mobx.observable.box(true, { name: "renderer-loading" });
|
||||
this.isDone = mobx.observable.box(params.isDone, { name: "renderer-isDone" });
|
||||
this.context = params.context;
|
||||
this.opts = params.opts;
|
||||
this.api = params.api;
|
||||
@ -56,8 +56,8 @@ class OpenAIRendererModel {
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
packetCallback(packetAny : any) {
|
||||
let packet : T.OpenAIPacketType = packetAny
|
||||
packetCallback(packetAny: any) {
|
||||
let packet: T.OpenAIPacketType = packetAny;
|
||||
if (packet == null) {
|
||||
return;
|
||||
}
|
||||
@ -65,7 +65,7 @@ class OpenAIRendererModel {
|
||||
if (packet.error != null) {
|
||||
mobx.action(() => {
|
||||
this.loadError.set(packet.error);
|
||||
this.version.set(this.version.get()+1);
|
||||
this.version.set(this.version.get() + 1);
|
||||
})();
|
||||
return;
|
||||
}
|
||||
@ -74,7 +74,7 @@ class OpenAIRendererModel {
|
||||
model: packet.model,
|
||||
created: packet.created,
|
||||
finish_reason: packet.finish_reason,
|
||||
message: (packet.text ?? ""),
|
||||
message: packet.text ?? "",
|
||||
};
|
||||
mobx.action(() => {
|
||||
this.output.set(output);
|
||||
@ -93,31 +93,31 @@ class OpenAIRendererModel {
|
||||
if (packet.text != null) {
|
||||
this.output.get().message += packet.text;
|
||||
}
|
||||
this.version.set(this.version.get()+1);
|
||||
this.version.set(this.version.get() + 1);
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
dispose() : void {
|
||||
return;
|
||||
}
|
||||
|
||||
giveFocus() : void {
|
||||
dispose(): void {
|
||||
return;
|
||||
}
|
||||
|
||||
updateOpts(update : T.RendererOptsUpdate) : void {
|
||||
giveFocus(): void {
|
||||
return;
|
||||
}
|
||||
|
||||
updateOpts(update: T.RendererOptsUpdate): void {
|
||||
Object.assign(this.opts, update);
|
||||
}
|
||||
|
||||
updateHeight(newHeight : number) : void {
|
||||
updateHeight(newHeight: number): void {
|
||||
if (this.savedHeight != newHeight) {
|
||||
this.savedHeight = newHeight;
|
||||
this.api.saveHeight(newHeight);
|
||||
}
|
||||
}
|
||||
|
||||
setIsDone() : void {
|
||||
|
||||
setIsDone(): void {
|
||||
if (this.isDone.get()) {
|
||||
return;
|
||||
}
|
||||
@ -127,7 +127,7 @@ class OpenAIRendererModel {
|
||||
// this.reload(0);
|
||||
}
|
||||
|
||||
reload(delayMs : number) : void {
|
||||
reload(delayMs: number): void {
|
||||
mobx.action(() => {
|
||||
this.loading.set(true);
|
||||
this.loadError.set(null);
|
||||
@ -152,45 +152,41 @@ class OpenAIRendererModel {
|
||||
})();
|
||||
});
|
||||
}
|
||||
|
||||
receiveData(pos : number, data : Uint8Array, reason? : string) : void {
|
||||
|
||||
receiveData(pos: number, data: Uint8Array, reason?: string): void {
|
||||
this.packetData.receiveData(pos, data, reason);
|
||||
}
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class OpenAIRenderer extends React.Component<{model : OpenAIRendererModel}> {
|
||||
renderPrompt(cmd : T.WebCmd) {
|
||||
class OpenAIRenderer extends React.Component<{ model: OpenAIRendererModel }> {
|
||||
renderPrompt(cmd: T.WebCmd) {
|
||||
let cmdStr = cmd.cmdstr.trim();
|
||||
if (cmdStr.startsWith("/openai")) {
|
||||
let spaceIdx = cmdStr.indexOf(" ");
|
||||
if (spaceIdx > 0) {
|
||||
cmdStr = cmdStr.substr(spaceIdx+1).trim();
|
||||
cmdStr = cmdStr.substr(spaceIdx + 1).trim();
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div className="openai-message">
|
||||
<span className="openai-role openai-role-user">[user]</span>
|
||||
<div className="openai-content-user">
|
||||
{cmdStr}
|
||||
</div>
|
||||
<div className="openai-content-user">{cmdStr}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderError() {
|
||||
let model : OpenAIRendererModel = this.props.model;
|
||||
let model: OpenAIRendererModel = this.props.model;
|
||||
return (
|
||||
<div className="openai-message">
|
||||
<span className="openai-role openai-role-error">[error]</span>
|
||||
<div className="openai-content-error">
|
||||
{model.loadError.get()}
|
||||
</div>
|
||||
<div className="openai-content-error">{model.loadError.get()}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderOutput(cmd : T.WebCmd) {
|
||||
renderOutput(cmd: T.WebCmd) {
|
||||
let output = this.props.model.output.get();
|
||||
let message = "";
|
||||
if (output != null) {
|
||||
@ -207,20 +203,28 @@ class OpenAIRenderer extends React.Component<{model : OpenAIRendererModel}> {
|
||||
<div className="openai-message">
|
||||
<div className="openai-role openai-role-assistant">[assistant]</div>
|
||||
<div className="openai-content-assistant">
|
||||
<div className="scroller" style={{maxHeight: opts.maxSize.height, minWidth: minWidth, width: "min-content", maxWidth: maxWidth}}>
|
||||
<Markdown text={message} style={{maxHeight: opts.maxSize.height}}/>
|
||||
<div
|
||||
className="scroller"
|
||||
style={{
|
||||
maxHeight: opts.maxSize.height,
|
||||
minWidth: minWidth,
|
||||
width: "min-content",
|
||||
maxWidth: maxWidth,
|
||||
}}
|
||||
>
|
||||
<Markdown text={message} style={{ maxHeight: opts.maxSize.height }} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
let model : OpenAIRendererModel = this.props.model;
|
||||
let model: OpenAIRendererModel = this.props.model;
|
||||
let cmd = model.rawCmd;
|
||||
let styleVal : Record<string, any> = null;
|
||||
let styleVal: Record<string, any> = null;
|
||||
if (model.loading.get() && model.savedHeight >= 0 && model.isDone) {
|
||||
styleVal = {height: model.savedHeight};
|
||||
styleVal = { height: model.savedHeight };
|
||||
}
|
||||
let version = model.version.get();
|
||||
let loadError = model.loadError.get();
|
||||
@ -241,4 +245,4 @@ class OpenAIRenderer extends React.Component<{model : OpenAIRendererModel}> {
|
||||
}
|
||||
}
|
||||
|
||||
export {OpenAIRenderer, OpenAIRendererModel};
|
||||
export { OpenAIRenderer, OpenAIRendererModel };
|
||||
|
@ -1,9 +1,8 @@
|
||||
|
||||
.alt-view {
|
||||
background-color: #111;
|
||||
overflow-y: auto;
|
||||
flex-grow: 1;
|
||||
|
||||
|
||||
.alt-title {
|
||||
margin: 20px 10px 0px 5px;
|
||||
padding-left: 10px;
|
||||
@ -113,8 +112,9 @@
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
.session-dropdown, .remote-dropdown {
|
||||
|
||||
.session-dropdown,
|
||||
.remote-dropdown {
|
||||
.dropdown-item {
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
@ -163,7 +163,7 @@
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-left: 15px;
|
||||
|
||||
|
||||
.fromts-text {
|
||||
}
|
||||
}
|
||||
@ -174,7 +174,7 @@
|
||||
font-size: 12px;
|
||||
border: 1px solid #777;
|
||||
padding: 5px 10px 5px 10px;
|
||||
|
||||
|
||||
&:hover {
|
||||
background-color: #666;
|
||||
}
|
||||
@ -220,7 +220,8 @@
|
||||
|
||||
&.is-disabled {
|
||||
cursor: default;
|
||||
i, span {
|
||||
i,
|
||||
span {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@ -271,7 +272,7 @@
|
||||
border-radius: 3px;
|
||||
margin: 20px 50px 20px 20px;
|
||||
}
|
||||
|
||||
|
||||
.history-table {
|
||||
margin: 0px 10px 10px 10px;
|
||||
table-layout: fixed;
|
||||
@ -280,7 +281,7 @@
|
||||
tr.active-history-item {
|
||||
td {
|
||||
padding-right: 10px;
|
||||
|
||||
|
||||
.line-container {
|
||||
padding: 0px 10px 10px 10px;
|
||||
overflow-x: auto;
|
||||
@ -418,7 +419,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.webshare-view {
|
||||
.webshare-item {
|
||||
padding: 4px 5px 8px 15px;
|
||||
@ -439,7 +439,7 @@
|
||||
|
||||
.webshare-vic-link {
|
||||
cursor: pointer;
|
||||
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
@ -492,8 +492,9 @@
|
||||
&.pending-delete {
|
||||
background-color: #522;
|
||||
}
|
||||
|
||||
.bookmark-content, .bookmark-edit {
|
||||
|
||||
.bookmark-content,
|
||||
.bookmark-edit {
|
||||
flex-grow: 1;
|
||||
max-width: calc(100% - 50px);
|
||||
}
|
||||
|
@ -1,43 +1,42 @@
|
||||
import * as React from "react";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import {sprintf} from "sprintf-js";
|
||||
import {boundMethod} from "autobind-decorator";
|
||||
import {v4 as uuidv4} from "uuid";
|
||||
import { sprintf } from "sprintf-js";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import dayjs from "dayjs";
|
||||
import {If, For, When, Otherwise, Choose} from "tsx-control-statements/components";
|
||||
import { If, For, When, Otherwise, Choose } from "tsx-control-statements/components";
|
||||
import cn from "classnames";
|
||||
import {GlobalModel, GlobalCommandRunner, Screen} from "./model";
|
||||
import {WebStopShareConfirmMarkdown} from "./settings";
|
||||
import { GlobalModel, GlobalCommandRunner, Screen } from "./model";
|
||||
import { WebStopShareConfirmMarkdown } from "./settings";
|
||||
import * as util from "./util";
|
||||
|
||||
type OV<V> = mobx.IObservableValue<V>;
|
||||
type OArr<V> = mobx.IObservableArray<V>;
|
||||
type OMap<K,V> = mobx.ObservableMap<K,V>;
|
||||
|
||||
type OMap<K, V> = mobx.ObservableMap<K, V>;
|
||||
|
||||
@mobxReact.observer
|
||||
class WebShareView extends React.Component<{}, {}> {
|
||||
shareCopied : OV<string> = mobx.observable.box(null, {name: "sw-shareCopied"});
|
||||
|
||||
shareCopied: OV<string> = mobx.observable.box(null, { name: "sw-shareCopied" });
|
||||
|
||||
@boundMethod
|
||||
closeView() : void {
|
||||
closeView(): void {
|
||||
GlobalModel.showSessionView();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
viewInContext(screen : Screen) {
|
||||
viewInContext(screen: Screen) {
|
||||
GlobalModel.historyViewModel.closeView();
|
||||
GlobalCommandRunner.lineView(screen.sessionId, screen.screenId, screen.selectedLine.get());
|
||||
}
|
||||
|
||||
getSSName(screen : Screen, snames : Record<string, string>) : string {
|
||||
getSSName(screen: Screen, snames: Record<string, string>): string {
|
||||
let sessionName = snames[screen.sessionId] ?? "unknown";
|
||||
return sprintf("#%s[%s]", sessionName, screen.name.get())
|
||||
return sprintf("#%s[%s]", sessionName, screen.name.get());
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
copyShareLink(screen : Screen) : void {
|
||||
copyShareLink(screen: Screen): void {
|
||||
let shareLink = screen.getWebShareUrl();
|
||||
if (shareLink == null) {
|
||||
return;
|
||||
@ -50,20 +49,20 @@ class WebShareView extends React.Component<{}, {}> {
|
||||
mobx.action(() => {
|
||||
this.shareCopied.set(null);
|
||||
})();
|
||||
}, 600)
|
||||
}, 600);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
openScreenSettings(screen : Screen) : void {
|
||||
openScreenSettings(screen: Screen): void {
|
||||
mobx.action(() => {
|
||||
GlobalModel.screenSettingsModal.set({sessionId: screen.sessionId, screenId: screen.screenId});
|
||||
GlobalModel.screenSettingsModal.set({ sessionId: screen.sessionId, screenId: screen.screenId });
|
||||
})();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
stopSharing(screen : Screen) : void {
|
||||
stopSharing(screen: Screen): void {
|
||||
let message = WebStopShareConfirmMarkdown;
|
||||
let alertRtn = GlobalModel.showAlert({message: message, confirm: true, markdown: true});
|
||||
let alertRtn = GlobalModel.showAlert({ message: message, confirm: true, markdown: true });
|
||||
alertRtn.then((result) => {
|
||||
if (!result) {
|
||||
return;
|
||||
@ -73,30 +72,34 @@ class WebShareView extends React.Component<{}, {}> {
|
||||
if (crtn.success) {
|
||||
return;
|
||||
}
|
||||
GlobalModel.showAlert({message: crtn.error});
|
||||
GlobalModel.showAlert({ message: crtn.error });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
let isHidden = (GlobalModel.activeMainView.get() != "webshare");
|
||||
let isHidden = GlobalModel.activeMainView.get() != "webshare";
|
||||
if (isHidden) {
|
||||
return null;
|
||||
}
|
||||
let snames = GlobalModel.getSessionNames();
|
||||
let screenList = GlobalModel.getWebSharedScreens();
|
||||
let screen : Screen = null;
|
||||
let screen: Screen = null;
|
||||
return (
|
||||
<div className={cn("webshare-view", "alt-view")}>
|
||||
<div className="close-button" onClick={this.closeView}><i className="fa-sharp fa-solid fa-xmark"></i></div>
|
||||
<div className="close-button" onClick={this.closeView}>
|
||||
<i className="fa-sharp fa-solid fa-xmark"></i>
|
||||
</div>
|
||||
<div className="alt-title">
|
||||
<i className="fa-sharp fa-solid fa-share-nodes" style={{marginRight: 10}}/>
|
||||
<i className="fa-sharp fa-solid fa-share-nodes" style={{ marginRight: 10 }} />
|
||||
WEB SHARING<If condition={screenList.length > 0}> ({screenList.length})</If>
|
||||
</div>
|
||||
<If condition={screenList.length == 0}>
|
||||
<div className="no-content">
|
||||
No Active Web Shares.<br/>
|
||||
Share a screen using the "web share" toggle in screen/tab settings <i className="fa-sharp fa-solid fa-gear"/>.
|
||||
No Active Web Shares.
|
||||
<br />
|
||||
Share a screen using the "web share" toggle in screen/tab settings{" "}
|
||||
<i className="fa-sharp fa-solid fa-gear" />.
|
||||
</div>
|
||||
</If>
|
||||
<If condition={screenList.length > 0}>
|
||||
@ -104,7 +107,7 @@ class WebShareView extends React.Component<{}, {}> {
|
||||
<For each="screen" of={screenList}>
|
||||
<div key={screen.screenId} className="webshare-item">
|
||||
<If condition={this.shareCopied.get() == screen.screenId}>
|
||||
<div className="copied-indicator"/>
|
||||
<div className="copied-indicator" />
|
||||
</If>
|
||||
<div className="webshare-vic">
|
||||
<span className="webshare-vic-link" onClick={() => this.viewInContext(screen)}>
|
||||
@ -112,28 +115,41 @@ class WebShareView extends React.Component<{}, {}> {
|
||||
</span>
|
||||
</div>
|
||||
<div className="actions">
|
||||
<a href={util.makeExternLink(screen.getWebShareUrl())} target="_blank" className="button is-prompt-green is-outlined is-small a-block">
|
||||
<a
|
||||
href={util.makeExternLink(screen.getWebShareUrl())}
|
||||
target="_blank"
|
||||
className="button is-prompt-green is-outlined is-small a-block"
|
||||
>
|
||||
<span>open in browser</span>
|
||||
<span className="icon">
|
||||
<i className="fa-sharp fa-solid fa-up-right-from-square"/>
|
||||
<i className="fa-sharp fa-solid fa-up-right-from-square" />
|
||||
</span>
|
||||
</a>
|
||||
<div className="button is-prompt-green is-outlined is-small" onClick={() => this.copyShareLink(screen)}>
|
||||
<div
|
||||
className="button is-prompt-green is-outlined is-small"
|
||||
onClick={() => this.copyShareLink(screen)}
|
||||
>
|
||||
<span>copy link</span>
|
||||
<span className="icon">
|
||||
<i className="fa-sharp fa-solid fa-copy"/>
|
||||
<i className="fa-sharp fa-solid fa-copy" />
|
||||
</span>
|
||||
</div>
|
||||
<div className="button is-prompt-green is-outlined is-small" onClick={() => this.openScreenSettings(screen)}>
|
||||
<div
|
||||
className="button is-prompt-green is-outlined is-small"
|
||||
onClick={() => this.openScreenSettings(screen)}
|
||||
>
|
||||
<span>open settings</span>
|
||||
<span className="icon">
|
||||
<i className="fa-sharp fa-solid fa-cog"/>
|
||||
<i className="fa-sharp fa-solid fa-cog" />
|
||||
</span>
|
||||
</div>
|
||||
<div className="button is-prompt-danger is-outlined is-small ml-4" onClick={() => this.stopSharing(screen)}>
|
||||
<div
|
||||
className="button is-prompt-danger is-outlined is-small ml-4"
|
||||
onClick={() => this.stopSharing(screen)}
|
||||
>
|
||||
<span>stop sharing</span>
|
||||
<span className="icon">
|
||||
<i className="fa-sharp fa-solid fa-trash"/>
|
||||
<i className="fa-sharp fa-solid fa-trash" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -143,8 +159,13 @@ class WebShareView extends React.Component<{}, {}> {
|
||||
</If>
|
||||
<div className="alt-help">
|
||||
<div className="help-entry">
|
||||
Currently limited to a maximum of 3 screens, each with up to 50 commands.<br/>
|
||||
Contact us on <a target="_blank" href="https://discord.gg/XfvZ334gwU"><i className="fa-brands fa-discord"/> Discord</a> to get a higher limit.
|
||||
Currently limited to a maximum of 3 screens, each with up to 50 commands.
|
||||
<br />
|
||||
Contact us on{" "}
|
||||
<a target="_blank" href="https://discord.gg/XfvZ334gwU">
|
||||
<i className="fa-brands fa-discord" /> Discord
|
||||
</a>{" "}
|
||||
to get a higher limit.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -152,5 +173,4 @@ class WebShareView extends React.Component<{}, {}> {
|
||||
}
|
||||
}
|
||||
|
||||
export {WebShareView};
|
||||
|
||||
export { WebShareView };
|
||||
|
@ -1,32 +1,32 @@
|
||||
import * as React from "react";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import {sprintf} from "sprintf-js";
|
||||
import {boundMethod} from "autobind-decorator";
|
||||
import {If, For, When, Otherwise, Choose} from "tsx-control-statements/components";
|
||||
import { sprintf } from "sprintf-js";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { If, For, When, Otherwise, Choose } from "tsx-control-statements/components";
|
||||
import cn from "classnames";
|
||||
import {WebShareModel, getTermPtyData} from "./webshare-model";
|
||||
import { WebShareModel, getTermPtyData } from "./webshare-model";
|
||||
import * as T from "./types";
|
||||
import {isBlank} from "./util";
|
||||
import {PluginModel} from "./plugins";
|
||||
import { isBlank } from "./util";
|
||||
import { PluginModel } from "./plugins";
|
||||
import * as lineutil from "./lineutil";
|
||||
import * as util from "./util";
|
||||
import {windowWidthToCols, windowHeightToRows, termHeightFromRows, termWidthFromCols} from "./textmeasure";
|
||||
import {debounce, throttle} from "throttle-debounce";
|
||||
import {LinesView} from "./linesview";
|
||||
import {Toggle} from "./elements";
|
||||
import {SimpleBlobRendererModel, SimpleBlobRenderer} from "./simplerenderer";
|
||||
import { windowWidthToCols, windowHeightToRows, termHeightFromRows, termWidthFromCols } from "./textmeasure";
|
||||
import { debounce, throttle } from "throttle-debounce";
|
||||
import { LinesView } from "./linesview";
|
||||
import { Toggle } from "./elements";
|
||||
import { SimpleBlobRendererModel, SimpleBlobRenderer } from "./simplerenderer";
|
||||
|
||||
type OV<V> = mobx.IObservableValue<V>;
|
||||
type OArr<V> = mobx.IObservableArray<V>;
|
||||
type OMap<K,V> = mobx.ObservableMap<K,V>;
|
||||
type OMap<K, V> = mobx.ObservableMap<K, V>;
|
||||
|
||||
let foo = LinesView;
|
||||
|
||||
// TODO reshare
|
||||
// TODO document.visibility API to disconnect websocket: document.addEventListener("visibilitychange", () => { document.hidden });
|
||||
|
||||
function makeFullRemoteRef(ownerName : string, remoteRef : string, name : string) : string {
|
||||
function makeFullRemoteRef(ownerName: string, remoteRef: string, name: string): string {
|
||||
if (isBlank(ownerName) && isBlank(name)) {
|
||||
return remoteRef;
|
||||
}
|
||||
@ -39,7 +39,7 @@ function makeFullRemoteRef(ownerName : string, remoteRef : string, name : string
|
||||
return ownerName + ":" + remoteRef + ":" + name;
|
||||
}
|
||||
|
||||
function getShortVEnv(venvDir : string) : string {
|
||||
function getShortVEnv(venvDir: string): string {
|
||||
if (isBlank(venvDir)) {
|
||||
return "";
|
||||
}
|
||||
@ -47,10 +47,10 @@ function getShortVEnv(venvDir : string) : string {
|
||||
if (lastSlash == -1) {
|
||||
return venvDir;
|
||||
}
|
||||
return venvDir.substr(lastSlash+1);
|
||||
return venvDir.substr(lastSlash + 1);
|
||||
}
|
||||
|
||||
function replaceHomePath(path : string, homeDir : string) : string {
|
||||
function replaceHomePath(path: string, homeDir: string): string {
|
||||
if (path == homeDir) {
|
||||
return "~";
|
||||
}
|
||||
@ -60,77 +60,100 @@ function replaceHomePath(path : string, homeDir : string) : string {
|
||||
return path;
|
||||
}
|
||||
|
||||
function getCwdStr(remote : T.WebRemote, state : Record<string, string>) : string {
|
||||
function getCwdStr(remote: T.WebRemote, state: Record<string, string>): string {
|
||||
if (state == null || isBlank(state.cwd)) {
|
||||
return "~";
|
||||
}
|
||||
let cwd = state.cwd;
|
||||
if (remote && remote.homedir) {
|
||||
cwd = replaceHomePath(cwd, remote.homedir)
|
||||
cwd = replaceHomePath(cwd, remote.homedir);
|
||||
}
|
||||
return cwd;
|
||||
}
|
||||
|
||||
function getRemoteStr(remote : T.WebRemote) : string {
|
||||
function getRemoteStr(remote: T.WebRemote): string {
|
||||
if (remote == null) {
|
||||
return "(invalid remote)";
|
||||
}
|
||||
let remoteRef = (!isBlank(remote.alias) ? remote.alias : remote.canonicalname);
|
||||
let remoteRef = !isBlank(remote.alias) ? remote.alias : remote.canonicalname;
|
||||
let fullRef = makeFullRemoteRef(null, remoteRef, remote.name);
|
||||
return fullRef;
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class Prompt extends React.Component<{remote : T.WebRemote, festate : Record<string, string>}, {}> {
|
||||
class Prompt extends React.Component<{ remote: T.WebRemote; festate: Record<string, string> }, {}> {
|
||||
render() {
|
||||
let {remote, festate} = this.props;
|
||||
let { remote, festate } = this.props;
|
||||
let remoteStr = getRemoteStr(remote);
|
||||
let cwd = getCwdStr(remote, festate);
|
||||
let isRoot = !!remote.isroot;
|
||||
let remoteColorClass = (isRoot ? "color-red" : "color-green");
|
||||
let remoteTitle : string = null;
|
||||
let remoteColorClass = isRoot ? "color-red" : "color-green";
|
||||
let remoteTitle: string = null;
|
||||
if (remote && remote.canonicalname) {
|
||||
remoteTitle = remote.canonicalname;
|
||||
}
|
||||
let cwdElem = (<span title="current directory" className="term-prompt-cwd"><i className="fa-solid fa-sharp fa-folder-open"/>{cwd}</span>);
|
||||
let remoteElem = (<span title={remoteTitle} className={cn("term-prompt-remote", remoteColorClass)}>[{remoteStr}] </span>);
|
||||
let rootIndicatorElem = (<span className="term-prompt-end">{isRoot ? "#" : "$"}</span>);
|
||||
let cwdElem = (
|
||||
<span title="current directory" className="term-prompt-cwd">
|
||||
<i className="fa-solid fa-sharp fa-folder-open" />
|
||||
{cwd}
|
||||
</span>
|
||||
);
|
||||
let remoteElem = (
|
||||
<span title={remoteTitle} className={cn("term-prompt-remote", remoteColorClass)}>
|
||||
[{remoteStr}]{" "}
|
||||
</span>
|
||||
);
|
||||
let rootIndicatorElem = <span className="term-prompt-end">{isRoot ? "#" : "$"}</span>;
|
||||
let branchElem = null;
|
||||
let pythonElem = null;
|
||||
if (!isBlank(festate["PROMPTVAR_GITBRANCH"])) {
|
||||
let branchName = festate["PROMPTVAR_GITBRANCH"];
|
||||
branchElem = (<span title="current git branch" className="term-prompt-branch"><i className="fa-sharp fa-solid fa-code-branch"/>{branchName} </span>);
|
||||
branchElem = (
|
||||
<span title="current git branch" className="term-prompt-branch">
|
||||
<i className="fa-sharp fa-solid fa-code-branch" />
|
||||
{branchName}{" "}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
if (!isBlank(festate["VIRTUAL_ENV"])) {
|
||||
let venvDir = festate["VIRTUAL_ENV"];
|
||||
let venv = getShortVEnv(venvDir);
|
||||
pythonElem = (<span title="python venv" className="term-prompt-python"><i className="fa-brands fa-python"/>{venv} </span>);
|
||||
pythonElem = (
|
||||
<span title="python venv" className="term-prompt-python">
|
||||
<i className="fa-brands fa-python" />
|
||||
{venv}{" "}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<span className="term-prompt">{remoteElem} {pythonElem}{branchElem}{cwdElem} {rootIndicatorElem}</span>
|
||||
<span className="term-prompt">
|
||||
{remoteElem} {pythonElem}
|
||||
{branchElem}
|
||||
{cwdElem} {rootIndicatorElem}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class LineAvatar extends React.Component<{line : T.WebLine, cmd : T.WebCmd}, {}> {
|
||||
class LineAvatar extends React.Component<{ line: T.WebLine; cmd: T.WebCmd }, {}> {
|
||||
render() {
|
||||
let {line, cmd} = this.props;
|
||||
let { line, cmd } = this.props;
|
||||
let lineNumStr = String(line.linenum);
|
||||
let status = (cmd != null ? cmd.status : "done");
|
||||
let rtnstate = (cmd != null ? cmd.rtnstate : false);
|
||||
let isComment = (line.linetype == "text");
|
||||
let status = cmd != null ? cmd.status : "done";
|
||||
let rtnstate = cmd != null ? cmd.rtnstate : false;
|
||||
let isComment = line.linetype == "text";
|
||||
return (
|
||||
<div className={cn("avatar", "num-"+lineNumStr.length, "status-" + status, {"rtnstate": rtnstate})}>
|
||||
<div className={cn("avatar", "num-" + lineNumStr.length, "status-" + status, { rtnstate: rtnstate })}>
|
||||
{lineNumStr}
|
||||
<If condition={status == "hangup" || status == "error"}>
|
||||
<i className="fa-sharp fa-solid fa-triangle-exclamation status-icon"/>
|
||||
<i className="fa-sharp fa-solid fa-triangle-exclamation status-icon" />
|
||||
</If>
|
||||
<If condition={status == "detached"}>
|
||||
<i className="fa-sharp fa-solid fa-rotate status-icon"/>
|
||||
<i className="fa-sharp fa-solid fa-rotate status-icon" />
|
||||
</If>
|
||||
<If condition={isComment}>
|
||||
<i className="fa-sharp fa-solid fa-comment comment-icon"/>
|
||||
<i className="fa-sharp fa-solid fa-comment comment-icon" />
|
||||
</If>
|
||||
</div>
|
||||
);
|
||||
@ -138,48 +161,60 @@ class LineAvatar extends React.Component<{line : T.WebLine, cmd : T.WebCmd}, {}>
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class WebLineCmdView extends React.Component<{line : T.WebLine, cmd : T.WebCmd, topBorder : boolean, width: number, onHeightChange : T.LineHeightChangeCallbackType, staticRender : boolean, visible : OV<boolean>}, {}> {
|
||||
lineRef : React.RefObject<any> = React.createRef();
|
||||
isCmdExpanded : OV<boolean> = mobx.observable.box(false, {name: "cmd-expanded"});
|
||||
isOverflow : OV<boolean> = mobx.observable.box(false, {name: "line-overflow"});
|
||||
cmdTextRef : React.RefObject<any> = React.createRef();
|
||||
copiedIndicator : OV<boolean> = mobx.observable.box(false, {name: "copiedIndicator"});
|
||||
lastHeight : number;
|
||||
class WebLineCmdView extends React.Component<
|
||||
{
|
||||
line: T.WebLine;
|
||||
cmd: T.WebCmd;
|
||||
topBorder: boolean;
|
||||
width: number;
|
||||
onHeightChange: T.LineHeightChangeCallbackType;
|
||||
staticRender: boolean;
|
||||
visible: OV<boolean>;
|
||||
},
|
||||
{}
|
||||
> {
|
||||
lineRef: React.RefObject<any> = React.createRef();
|
||||
isCmdExpanded: OV<boolean> = mobx.observable.box(false, { name: "cmd-expanded" });
|
||||
isOverflow: OV<boolean> = mobx.observable.box(false, { name: "line-overflow" });
|
||||
cmdTextRef: React.RefObject<any> = React.createRef();
|
||||
copiedIndicator: OV<boolean> = mobx.observable.box(false, { name: "copiedIndicator" });
|
||||
lastHeight: number;
|
||||
|
||||
componentDidMount() : void {
|
||||
componentDidMount(): void {
|
||||
this.checkCmdText();
|
||||
this.componentDidUpdate();
|
||||
}
|
||||
|
||||
componentDidUpdate() : void {
|
||||
componentDidUpdate(): void {
|
||||
this.handleHeightChange();
|
||||
}
|
||||
|
||||
|
||||
renderSimple() {
|
||||
let {line, cmd, topBorder} = this.props;
|
||||
let height : number = 0;
|
||||
let { line, cmd, topBorder } = this.props;
|
||||
let height: number = 0;
|
||||
if (isBlank(line.renderer) || line.renderer == "terminal") {
|
||||
height = this.getTerminalRendererHeight(cmd);
|
||||
}
|
||||
else {
|
||||
let {line, width} = this.props;
|
||||
} else {
|
||||
let { line, width } = this.props;
|
||||
let usedRows = WebShareModel.getUsedRows(lineutil.getWebRendererContext(line), line, cmd, width);
|
||||
height = 36 + usedRows;
|
||||
}
|
||||
let mainCn = cn(
|
||||
"line",
|
||||
"line-cmd",
|
||||
{"top-border": topBorder},
|
||||
);
|
||||
let mainCn = cn("line", "line-cmd", { "top-border": topBorder });
|
||||
return (
|
||||
<div ref={this.lineRef} className={mainCn} data-lineid={line.lineid} data-linenum={line.linenum} style={{height: height}}>
|
||||
<LineAvatar line={line} cmd={null}/>
|
||||
<div
|
||||
ref={this.lineRef}
|
||||
className={mainCn}
|
||||
data-lineid={line.lineid}
|
||||
data-linenum={line.linenum}
|
||||
style={{ height: height }}
|
||||
>
|
||||
<LineAvatar line={line} cmd={null} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
getTerminalRendererHeight(cmd : T.WebCmd) : number {
|
||||
let {line, width} = this.props;
|
||||
getTerminalRendererHeight(cmd: T.WebCmd): number {
|
||||
let { line, width } = this.props;
|
||||
let height = 42; // height of zero height terminal
|
||||
let usedRows = WebShareModel.getUsedRows(lineutil.getWebRendererContext(line), line, cmd, width);
|
||||
if (usedRows > 0) {
|
||||
@ -189,13 +224,13 @@ class WebLineCmdView extends React.Component<{line : T.WebLine, cmd : T.WebCmd,
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleExpandCmd() : void {
|
||||
handleExpandCmd(): void {
|
||||
mobx.action(() => {
|
||||
this.isCmdExpanded.set(true);
|
||||
})();
|
||||
}
|
||||
|
||||
renderCmdText(cmd : T.WebCmd, remote : T.WebRemote) : any {
|
||||
renderCmdText(cmd: T.WebCmd, remote: T.WebRemote): any {
|
||||
if (cmd == null) {
|
||||
return (
|
||||
<div className="metapart-mono cmdtext">
|
||||
@ -208,7 +243,7 @@ class WebLineCmdView extends React.Component<{line : T.WebLine, cmd : T.WebCmd,
|
||||
<React.Fragment>
|
||||
<div key="meta2" className="meta meta-line2">
|
||||
<div className="metapart-mono cmdtext">
|
||||
<Prompt remote={cmd.remote} festate={cmd.festate}/>
|
||||
<Prompt remote={cmd.remote} festate={cmd.festate} />
|
||||
</div>
|
||||
</div>
|
||||
<div key="meta3" className="meta meta-line3 cmdtext-expanded-wrapper">
|
||||
@ -221,12 +256,14 @@ class WebLineCmdView extends React.Component<{line : T.WebLine, cmd : T.WebCmd,
|
||||
return (
|
||||
<div key="meta2" className="meta meta-line2" ref={this.cmdTextRef}>
|
||||
<div className="metapart-mono cmdtext">
|
||||
<Prompt remote={cmd.remote} festate={cmd.festate}/>
|
||||
<Prompt remote={cmd.remote} festate={cmd.festate} />
|
||||
<span> </span>
|
||||
<span>{lineutil.getSingleLineCmdText(cmd.cmdstr)}</span>
|
||||
</div>
|
||||
<If condition={this.isOverflow.get() || isMultiLine}>
|
||||
<div className="cmdtext-overflow" onClick={this.handleExpandCmd}>...▼</div>
|
||||
<div className="cmdtext-overflow" onClick={this.handleExpandCmd}>
|
||||
...▼
|
||||
</div>
|
||||
</If>
|
||||
</div>
|
||||
);
|
||||
@ -241,11 +278,11 @@ class WebLineCmdView extends React.Component<{line : T.WebLine, cmd : T.WebCmd,
|
||||
let metaChild = metaElem.firstChild;
|
||||
let children = metaChild.childNodes;
|
||||
let childWidth = 0;
|
||||
for (let i=0; i<children.length; i++) {
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
let ch = children[i];
|
||||
childWidth += ch.offsetWidth;
|
||||
}
|
||||
let isOverflow = (childWidth > metaElemWidth);
|
||||
let isOverflow = childWidth > metaElemWidth;
|
||||
if (isOverflow != this.isOverflow.get()) {
|
||||
mobx.action(() => {
|
||||
this.isOverflow.set(isOverflow);
|
||||
@ -254,8 +291,8 @@ class WebLineCmdView extends React.Component<{line : T.WebLine, cmd : T.WebCmd,
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleHeightChange() : void {
|
||||
let {line} = this.props;
|
||||
handleHeightChange(): void {
|
||||
let { line } = this.props;
|
||||
let curHeight = 0;
|
||||
let curWidth = 0;
|
||||
let elem = this.lineRef.current;
|
||||
@ -273,12 +310,12 @@ class WebLineCmdView extends React.Component<{line : T.WebLine, cmd : T.WebCmd,
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleClick() : void {
|
||||
handleClick(): void {
|
||||
WebShareModel.setSelectedLine(this.props.line.linenum);
|
||||
}
|
||||
|
||||
renderMetaWrap() {
|
||||
let {line, cmd} = this.props;
|
||||
let { line, cmd } = this.props;
|
||||
let formattedTime = lineutil.getLineDateTimeStr(line.ts);
|
||||
let termOpts = cmd.termopts;
|
||||
let remote = cmd.remote;
|
||||
@ -289,7 +326,10 @@ class WebLineCmdView extends React.Component<{line : T.WebLine, cmd : T.WebCmd,
|
||||
<div className="ts">{formattedTime}</div>
|
||||
<div> </div>
|
||||
<If condition={!isBlank(renderer) && renderer != "terminal"}>
|
||||
<div className="renderer"><i className="fa-sharp fa-solid fa-fill"/>{renderer} </div>
|
||||
<div className="renderer">
|
||||
<i className="fa-sharp fa-solid fa-fill" />
|
||||
{renderer}
|
||||
</div>
|
||||
</If>
|
||||
<div className="termopts">
|
||||
({termOpts.rows}x{termOpts.cols})
|
||||
@ -300,14 +340,14 @@ class WebLineCmdView extends React.Component<{line : T.WebLine, cmd : T.WebCmd,
|
||||
);
|
||||
}
|
||||
|
||||
copyAllowed() : boolean {
|
||||
return (navigator.clipboard != null);
|
||||
copyAllowed(): boolean {
|
||||
return navigator.clipboard != null;
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
clickCopy() : void {
|
||||
clickCopy(): void {
|
||||
if (this.copyAllowed()) {
|
||||
let {cmd} = this.props;
|
||||
let { cmd } = this.props;
|
||||
navigator.clipboard.writeText(cmd.cmdstr);
|
||||
}
|
||||
mobx.action(() => {
|
||||
@ -320,7 +360,7 @@ class WebLineCmdView extends React.Component<{line : T.WebLine, cmd : T.WebCmd,
|
||||
}, 600);
|
||||
}
|
||||
|
||||
getRendererOpts(cmd : T.WebCmd) : T.RendererOpts {
|
||||
getRendererOpts(cmd: T.WebCmd): T.RendererOpts {
|
||||
return {
|
||||
maxSize: WebShareModel.getMaxContentSize(),
|
||||
idealSize: WebShareModel.getIdealContentSize(),
|
||||
@ -329,26 +369,25 @@ class WebLineCmdView extends React.Component<{line : T.WebLine, cmd : T.WebCmd,
|
||||
};
|
||||
}
|
||||
|
||||
makeRendererModelInitializeParams() : T.RendererModelInitializeParams {
|
||||
let {line, cmd} = this.props;
|
||||
makeRendererModelInitializeParams(): T.RendererModelInitializeParams {
|
||||
let { line, cmd } = this.props;
|
||||
let context = lineutil.getWebRendererContext(line);
|
||||
let savedHeight = WebShareModel.getContentHeight(context);
|
||||
if (savedHeight == null) {
|
||||
if (line.contentheight != null && line.contentheight != -1) {
|
||||
savedHeight = line.contentheight;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
savedHeight = 0;
|
||||
}
|
||||
}
|
||||
let api = {
|
||||
saveHeight: (height : number) => {
|
||||
saveHeight: (height: number) => {
|
||||
WebShareModel.setContentHeight(lineutil.getWebRendererContext(line), height);
|
||||
},
|
||||
onFocusChanged: (focus : boolean) => {
|
||||
onFocusChanged: (focus: boolean) => {
|
||||
// nothing
|
||||
},
|
||||
dataHandler: (data : string, model : T.RendererModel) => {
|
||||
dataHandler: (data: string, model: T.RendererModel) => {
|
||||
// nothing
|
||||
},
|
||||
};
|
||||
@ -361,54 +400,94 @@ class WebLineCmdView extends React.Component<{line : T.WebLine, cmd : T.WebCmd,
|
||||
api: api,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
let {line, cmd, topBorder, staticRender, visible} = this.props;
|
||||
let { line, cmd, topBorder, staticRender, visible } = this.props;
|
||||
let isVisible = visible.get();
|
||||
if (staticRender || !isVisible) {
|
||||
return this.renderSimple();
|
||||
}
|
||||
let model = WebShareModel;
|
||||
let isSelected = mobx.computed(() => (model.getSelectedLine() == line.linenum), {name: "computed-isSelected"}).get();
|
||||
let isServerSelected = mobx.computed(() => (model.getServerSelectedLine() == line.linenum), {name: "computed-isServerSelected"}).get();
|
||||
let rendererPlugin : T.RendererPluginType = null;
|
||||
let isNoneRenderer = (line.renderer == "none");
|
||||
let isSelected = mobx
|
||||
.computed(() => model.getSelectedLine() == line.linenum, { name: "computed-isSelected" })
|
||||
.get();
|
||||
let isServerSelected = mobx
|
||||
.computed(() => model.getServerSelectedLine() == line.linenum, { name: "computed-isServerSelected" })
|
||||
.get();
|
||||
let rendererPlugin: T.RendererPluginType = null;
|
||||
let isNoneRenderer = line.renderer == "none";
|
||||
if (!isBlank(line.renderer) && line.renderer != "terminal" && !isNoneRenderer) {
|
||||
rendererPlugin = PluginModel.getRendererPluginByName(line.renderer);
|
||||
}
|
||||
let rendererType = lineutil.getRendererType(line);
|
||||
let mainCn = cn("web-line line line-cmd", {"top-border": topBorder});
|
||||
let visObs = mobx.observable.box(true, {name: "visObs"});
|
||||
let mainCn = cn("web-line line line-cmd", { "top-border": topBorder });
|
||||
let visObs = mobx.observable.box(true, { name: "visObs" });
|
||||
let width = this.props.width;
|
||||
if (width == 0) {
|
||||
width = 1024;
|
||||
}
|
||||
let isExpanded = this.isCmdExpanded.get();
|
||||
return (
|
||||
<div ref={this.lineRef} className={mainCn} data-lineid={line.lineid} data-linenum={line.linenum} onClick={this.handleClick}>
|
||||
<div
|
||||
ref={this.lineRef}
|
||||
className={mainCn}
|
||||
data-lineid={line.lineid}
|
||||
data-linenum={line.linenum}
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
<If condition={this.copiedIndicator.get()}>
|
||||
<div key="copied" className="copied-indicator">
|
||||
<div>copied</div>
|
||||
</div>
|
||||
</If>
|
||||
<div key="focus" className={cn("focus-indicator", {"selected": isSelected || isServerSelected}, {"active": isSelected})}/>
|
||||
<div className={cn("line-header", {"is-expanded": isExpanded})}>
|
||||
<LineAvatar line={line} cmd={cmd}/>
|
||||
<div
|
||||
key="focus"
|
||||
className={cn(
|
||||
"focus-indicator",
|
||||
{ selected: isSelected || isServerSelected },
|
||||
{ active: isSelected }
|
||||
)}
|
||||
/>
|
||||
<div className={cn("line-header", { "is-expanded": isExpanded })}>
|
||||
<LineAvatar line={line} cmd={cmd} />
|
||||
{this.renderMetaWrap()}
|
||||
<If condition={this.copyAllowed()}>
|
||||
<div key="copy" title="Copy Command" className={cn("line-icon copy-icon")} onClick={this.clickCopy} style={{marginLeft: 5}}>
|
||||
<i className="fa-sharp fa-solid fa-copy"/>
|
||||
<div
|
||||
key="copy"
|
||||
title="Copy Command"
|
||||
className={cn("line-icon copy-icon")}
|
||||
onClick={this.clickCopy}
|
||||
style={{ marginLeft: 5 }}
|
||||
>
|
||||
<i className="fa-sharp fa-solid fa-copy" />
|
||||
</div>
|
||||
</If>
|
||||
</div>
|
||||
<If condition={rendererPlugin == null && !isNoneRenderer}>
|
||||
<TerminalRenderer line={line} cmd={cmd} width={width} staticRender={staticRender} visible={visible} onHeightChange={this.handleHeightChange}/>
|
||||
<TerminalRenderer
|
||||
line={line}
|
||||
cmd={cmd}
|
||||
width={width}
|
||||
staticRender={staticRender}
|
||||
visible={visible}
|
||||
onHeightChange={this.handleHeightChange}
|
||||
/>
|
||||
</If>
|
||||
<If condition={rendererPlugin != null}>
|
||||
<SimpleBlobRenderer rendererContainer={WebShareModel} lineId={line.lineid} plugin={rendererPlugin} onHeightChange={this.handleHeightChange} initParams={this.makeRendererModelInitializeParams()}/>
|
||||
<SimpleBlobRenderer
|
||||
rendererContainer={WebShareModel}
|
||||
lineId={line.lineid}
|
||||
plugin={rendererPlugin}
|
||||
onHeightChange={this.handleHeightChange}
|
||||
initParams={this.makeRendererModelInitializeParams()}
|
||||
/>
|
||||
</If>
|
||||
<If condition={cmd && cmd.rtnstate}>
|
||||
<div key="rtnstate" className="cmd-rtnstate" style={{visibility: ((cmd.status == "done") ? "visible" : "hidden")}}>
|
||||
<div
|
||||
key="rtnstate"
|
||||
className="cmd-rtnstate"
|
||||
style={{ visibility: cmd.status == "done" ? "visible" : "hidden" }}
|
||||
>
|
||||
<If condition={isBlank(cmd.rtnstatestr)}>
|
||||
<div className="cmd-rtnstate-label">state unchanged</div>
|
||||
<div className="cmd-rtnstate-sep"></div>
|
||||
@ -426,17 +505,19 @@ class WebLineCmdView extends React.Component<{line : T.WebLine, cmd : T.WebCmd,
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class WebLineTextView extends React.Component<{line : T.WebLine, cmd : T.WebCmd, topBorder : boolean}, {}> {
|
||||
class WebLineTextView extends React.Component<{ line: T.WebLine; cmd: T.WebCmd; topBorder: boolean }, {}> {
|
||||
render() {
|
||||
let {line, topBorder} = this.props;
|
||||
let { line, topBorder } = this.props;
|
||||
let model = WebShareModel;
|
||||
let isSelected = mobx.computed(() => (model.getSelectedLine() == line.linenum), {name: "computed-isSelected"}).get();
|
||||
let mainCn = cn("web-line line line-text", {"top-border": topBorder});
|
||||
let isSelected = mobx
|
||||
.computed(() => model.getSelectedLine() == line.linenum, { name: "computed-isSelected" })
|
||||
.get();
|
||||
let mainCn = cn("web-line line line-text", { "top-border": topBorder });
|
||||
return (
|
||||
<div className={mainCn} data-lineid={line.lineid} data-linenum={line.linenum}>
|
||||
<div key="focus" className={cn("focus-indicator", {"selected active": isSelected})}/>
|
||||
<div key="focus" className={cn("focus-indicator", { "selected active": isSelected })} />
|
||||
<div className="line-header">
|
||||
<LineAvatar line={line} cmd={null}/>
|
||||
<LineAvatar line={line} cmd={null} />
|
||||
</div>
|
||||
<div>
|
||||
<div>{line.text}</div>
|
||||
@ -447,10 +528,20 @@ class WebLineTextView extends React.Component<{line : T.WebLine, cmd : T.WebCmd,
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class TerminalRenderer extends React.Component<{line : T.WebLine, cmd : T.WebCmd, width : number, staticRender : boolean, visible : OV<boolean>, onHeightChange : () => void}, {}> {
|
||||
termLoaded : mobx.IObservableValue<boolean> = mobx.observable.box(false, {name: "termrenderer-termLoaded"});
|
||||
elemRef : React.RefObject<any> = React.createRef();
|
||||
termRef : React.RefObject<any> = React.createRef();
|
||||
class TerminalRenderer extends React.Component<
|
||||
{
|
||||
line: T.WebLine;
|
||||
cmd: T.WebCmd;
|
||||
width: number;
|
||||
staticRender: boolean;
|
||||
visible: OV<boolean>;
|
||||
onHeightChange: () => void;
|
||||
},
|
||||
{}
|
||||
> {
|
||||
termLoaded: mobx.IObservableValue<boolean> = mobx.observable.box(false, { name: "termrenderer-termLoaded" });
|
||||
elemRef: React.RefObject<any> = React.createRef();
|
||||
termRef: React.RefObject<any> = React.createRef();
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@ -466,26 +557,26 @@ class TerminalRenderer extends React.Component<{line : T.WebLine, cmd : T.WebCmd
|
||||
}
|
||||
}
|
||||
|
||||
getSnapshotBeforeUpdate(prevProps, prevState) : {height : number} {
|
||||
getSnapshotBeforeUpdate(prevProps, prevState): { height: number } {
|
||||
let elem = this.elemRef.current;
|
||||
if (elem == null) {
|
||||
return {height: 0};
|
||||
return { height: 0 };
|
||||
}
|
||||
return {height: elem.offsetHeight};
|
||||
return { height: elem.offsetHeight };
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState, snapshot : {height : number}) : void {
|
||||
componentDidUpdate(prevProps, prevState, snapshot: { height: number }): void {
|
||||
if (this.props.onHeightChange == null) {
|
||||
return;
|
||||
}
|
||||
let {line} = this.props;
|
||||
let { line } = this.props;
|
||||
let curHeight = 0;
|
||||
let elem = this.elemRef.current;
|
||||
if (elem != null) {
|
||||
curHeight = elem.offsetHeight;
|
||||
}
|
||||
if (snapshot == null) {
|
||||
snapshot = {height: 0};
|
||||
snapshot = { height: 0 };
|
||||
}
|
||||
if (snapshot.height != curHeight) {
|
||||
this.props.onHeightChange();
|
||||
@ -494,8 +585,8 @@ class TerminalRenderer extends React.Component<{line : T.WebLine, cmd : T.WebCmd
|
||||
this.checkLoad();
|
||||
}
|
||||
|
||||
checkLoad() : void {
|
||||
let {line, staticRender, visible} = this.props;
|
||||
checkLoad(): void {
|
||||
let { line, staticRender, visible } = this.props;
|
||||
if (staticRender) {
|
||||
return;
|
||||
}
|
||||
@ -503,14 +594,13 @@ class TerminalRenderer extends React.Component<{line : T.WebLine, cmd : T.WebCmd
|
||||
let curVis = this.termLoaded.get();
|
||||
if (vis && !curVis) {
|
||||
this.loadTerminal();
|
||||
}
|
||||
else if (!vis && curVis) {
|
||||
} else if (!vis && curVis) {
|
||||
this.unloadTerminal(false);
|
||||
}
|
||||
}
|
||||
|
||||
loadTerminal() : void {
|
||||
let {line, cmd} = this.props;
|
||||
loadTerminal(): void {
|
||||
let { line, cmd } = this.props;
|
||||
if (cmd == null) {
|
||||
return;
|
||||
}
|
||||
@ -523,8 +613,8 @@ class TerminalRenderer extends React.Component<{line : T.WebLine, cmd : T.WebCmd
|
||||
mobx.action(() => this.termLoaded.set(true))();
|
||||
}
|
||||
|
||||
unloadTerminal(unmount : boolean) : void {
|
||||
let {line} = this.props;
|
||||
unloadTerminal(unmount: boolean): void {
|
||||
let { line } = this.props;
|
||||
WebShareModel.unloadRenderer(line.lineid);
|
||||
if (!unmount) {
|
||||
mobx.action(() => this.termLoaded.set(false))();
|
||||
@ -534,10 +624,10 @@ class TerminalRenderer extends React.Component<{line : T.WebLine, cmd : T.WebCmd
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@boundMethod
|
||||
clickTermBlock(e : any) {
|
||||
let {line} = this.props;
|
||||
clickTermBlock(e: any) {
|
||||
let { line } = this.props;
|
||||
let termWrap = WebShareModel.getTermWrap(line.lineid);
|
||||
if (termWrap != null) {
|
||||
termWrap.giveFocus();
|
||||
@ -546,56 +636,82 @@ class TerminalRenderer extends React.Component<{line : T.WebLine, cmd : T.WebCmd
|
||||
WebShareModel.setSelectedLine(line.linenum);
|
||||
})();
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
let {cmd, line, width, staticRender, visible} = this.props;
|
||||
let { cmd, line, width, staticRender, visible } = this.props;
|
||||
let isVisible = visible.get(); // for reaction
|
||||
let usedRows = WebShareModel.getUsedRows(lineutil.getWebRendererContext(line), line, cmd, width);
|
||||
let termHeight = termHeightFromRows(usedRows, WebShareModel.getTermFontSize());
|
||||
let termLoaded = this.termLoaded.get();
|
||||
let isFocused = (WebShareModel.getSelectedLine() == line.linenum);
|
||||
let isFocused = WebShareModel.getSelectedLine() == line.linenum;
|
||||
return (
|
||||
<div ref={this.elemRef} key="term-wrap" className={cn("terminal-wrapper", {"cmd-done": !lineutil.cmdStatusIsRunning(cmd.status)}, {"zero-height": (termHeight == 0)})}>
|
||||
<div
|
||||
ref={this.elemRef}
|
||||
key="term-wrap"
|
||||
className={cn(
|
||||
"terminal-wrapper",
|
||||
{ "cmd-done": !lineutil.cmdStatusIsRunning(cmd.status) },
|
||||
{ "zero-height": termHeight == 0 }
|
||||
)}
|
||||
>
|
||||
<If condition={!isFocused}>
|
||||
<div key="term-block" className="term-block" onClick={this.clickTermBlock}></div>
|
||||
</If>
|
||||
<div key="term-connectelem" className="terminal-connectelem" ref={this.termRef} data-lineid={line.lineid} style={{height: termHeight}}></div>
|
||||
<If condition={!termLoaded}><div key="term-loading" className="terminal-loading-message">...</div></If>
|
||||
|
||||
<div
|
||||
key="term-connectelem"
|
||||
className="terminal-connectelem"
|
||||
ref={this.termRef}
|
||||
data-lineid={line.lineid}
|
||||
style={{ height: termHeight }}
|
||||
></div>
|
||||
<If condition={!termLoaded}>
|
||||
<div key="term-loading" className="terminal-loading-message">
|
||||
...
|
||||
</div>
|
||||
</If>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class WebLineView extends React.Component<{line : T.WebLine, cmd : T.WebCmd, topBorder : boolean, width : number, onHeightChange : T.LineHeightChangeCallbackType, staticRender : boolean, visible : OV<boolean>}, {}> {
|
||||
class WebLineView extends React.Component<
|
||||
{
|
||||
line: T.WebLine;
|
||||
cmd: T.WebCmd;
|
||||
topBorder: boolean;
|
||||
width: number;
|
||||
onHeightChange: T.LineHeightChangeCallbackType;
|
||||
staticRender: boolean;
|
||||
visible: OV<boolean>;
|
||||
},
|
||||
{}
|
||||
> {
|
||||
render() {
|
||||
let {line} = this.props;
|
||||
let { line } = this.props;
|
||||
if (line.linetype == "text") {
|
||||
return <WebLineTextView {...this.props}/>
|
||||
return <WebLineTextView {...this.props} />;
|
||||
}
|
||||
if (line.linetype == "cmd") {
|
||||
return <WebLineCmdView {...this.props}/>
|
||||
return <WebLineCmdView {...this.props} />;
|
||||
}
|
||||
return (
|
||||
<div className="web-line line">invalid linetype "{line.linetype}"</div>
|
||||
);
|
||||
return <div className="web-line line">invalid linetype "{line.linetype}"</div>;
|
||||
}
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class WebScreenView extends React.Component<{}, {}> {
|
||||
viewRef : React.RefObject<any> = React.createRef();
|
||||
width : OV<number> = mobx.observable.box(0, {name: "WebScreenView-width"});
|
||||
handleResize_debounced : () => void;
|
||||
rszObs : ResizeObserver;
|
||||
viewRef: React.RefObject<any> = React.createRef();
|
||||
width: OV<number> = mobx.observable.box(0, { name: "WebScreenView-width" });
|
||||
handleResize_debounced: () => void;
|
||||
rszObs: ResizeObserver;
|
||||
|
||||
constructor(props : any) {
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
this.handleResize_debounced = debounce(1000, this.handleResize.bind(this));
|
||||
}
|
||||
|
||||
componentDidMount() : void {
|
||||
componentDidMount(): void {
|
||||
if (this.viewRef.current != null) {
|
||||
let viewElem = this.viewRef.current;
|
||||
this.rszObs = new ResizeObserver(this.handleResize_debounced.bind(this));
|
||||
@ -610,16 +726,16 @@ class WebScreenView extends React.Component<{}, {}> {
|
||||
}
|
||||
}
|
||||
|
||||
handleResize() : void {
|
||||
handleResize(): void {
|
||||
let viewElem = this.viewRef.current;
|
||||
if (viewElem == null) {
|
||||
return;
|
||||
}
|
||||
let width = viewElem.offsetWidth;
|
||||
let height = viewElem.offsetHeight;
|
||||
WebShareModel.setLastScreenSize({width, height});
|
||||
WebShareModel.setLastScreenSize({ width, height });
|
||||
if (width != this.width.get()) {
|
||||
WebShareModel.resizeWindow({width: width, height: height});
|
||||
WebShareModel.resizeWindow({ width: width, height: height });
|
||||
mobx.action(() => {
|
||||
this.width.set(width);
|
||||
})();
|
||||
@ -627,15 +743,24 @@ class WebScreenView extends React.Component<{}, {}> {
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
buildLineComponent(lineProps : T.LineFactoryProps) : JSX.Element {
|
||||
let line : T.WebLine = (lineProps.line as T.WebLine);
|
||||
buildLineComponent(lineProps: T.LineFactoryProps): JSX.Element {
|
||||
let line: T.WebLine = lineProps.line as T.WebLine;
|
||||
let cmd = WebShareModel.getCmdById(lineProps.line.lineid);
|
||||
return (
|
||||
<WebLineView key={line.lineid} line={line} cmd={cmd} topBorder={lineProps.topBorder} width={lineProps.width} onHeightChange={lineProps.onHeightChange} staticRender={lineProps.staticRender} visible={lineProps.visible}/>
|
||||
<WebLineView
|
||||
key={line.lineid}
|
||||
line={line}
|
||||
cmd={cmd}
|
||||
topBorder={lineProps.topBorder}
|
||||
width={lineProps.width}
|
||||
onHeightChange={lineProps.onHeightChange}
|
||||
staticRender={lineProps.staticRender}
|
||||
visible={lineProps.visible}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderEmpty() : any {
|
||||
renderEmpty(): any {
|
||||
return (
|
||||
<div className="web-screen-view" ref={this.viewRef}>
|
||||
<div className="web-lines lines">
|
||||
@ -644,7 +769,7 @@ class WebScreenView extends React.Component<{}, {}> {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
let fullScreen = WebShareModel.fullScreen.get();
|
||||
if (fullScreen == null || fullScreen.lines.length == 0) {
|
||||
@ -652,7 +777,13 @@ class WebScreenView extends React.Component<{}, {}> {
|
||||
}
|
||||
return (
|
||||
<div className="web-screen-view" ref={this.viewRef}>
|
||||
<LinesView screen={WebShareModel} width={this.width.get()} lines={fullScreen.lines} renderMode="normal" lineFactory={this.buildLineComponent}/>
|
||||
<LinesView
|
||||
screen={WebShareModel}
|
||||
width={this.width.get()}
|
||||
lines={fullScreen.lines}
|
||||
renderMode="normal"
|
||||
lineFactory={this.buildLineComponent}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -661,9 +792,9 @@ class WebScreenView extends React.Component<{}, {}> {
|
||||
@mobxReact.observer
|
||||
class WebShareMain extends React.Component<{}, {}> {
|
||||
renderCopy() {
|
||||
return (<div className="footer-copy">© 2023 Dashborg Inc</div>);
|
||||
return <div className="footer-copy">© 2023 Dashborg Inc</div>;
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
let screen = WebShareModel.fullScreen.get();
|
||||
let errMessage = WebShareModel.errMessage.get();
|
||||
@ -675,27 +806,36 @@ class WebShareMain extends React.Component<{}, {}> {
|
||||
<div id="main">
|
||||
<div className="logo-header">
|
||||
<div className="logo-text">
|
||||
<a target="_blank" href="https://www.getprompt.dev">[prompt]</a>
|
||||
<a target="_blank" href="https://www.getprompt.dev">
|
||||
[prompt]
|
||||
</a>
|
||||
</div>
|
||||
<div className="flex-spacer"/>
|
||||
<a href="https://getprompt.dev/download/" target="_blank" className="download-button button is-link">
|
||||
<div className="flex-spacer" />
|
||||
<a
|
||||
href="https://getprompt.dev/download/"
|
||||
target="_blank"
|
||||
className="download-button button is-link"
|
||||
>
|
||||
<span>Download Prompt</span>
|
||||
<span className="icon is-small">
|
||||
<i className="fa-sharp fa-solid fa-cloud-arrow-down"/>
|
||||
<i className="fa-sharp fa-solid fa-cloud-arrow-down" />
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div className="webshare-controls">
|
||||
<div className="screen-sharename">{shareName}</div>
|
||||
<div className="flex-spacer"/>
|
||||
<div className="flex-spacer" />
|
||||
<div className="sync-control">
|
||||
<div>Sync Selection</div>
|
||||
<Toggle checked={WebShareModel.syncSelectedLine.get()} onChange={(val) => WebShareModel.setSyncSelectedLine(val)}/>
|
||||
<Toggle
|
||||
checked={WebShareModel.syncSelectedLine.get()}
|
||||
onChange={(val) => WebShareModel.setSyncSelectedLine(val)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="prompt-content">
|
||||
<If condition={screen != null}>
|
||||
<WebScreenView/>
|
||||
<WebScreenView />
|
||||
</If>
|
||||
<If condition={errMessage != null}>
|
||||
<div className="err-message">{WebShareModel.errMessage.get()}</div>
|
||||
@ -703,10 +843,10 @@ class WebShareMain extends React.Component<{}, {}> {
|
||||
</div>
|
||||
<div className="prompt-footer">
|
||||
{this.renderCopy()}
|
||||
<div className="flex-spacer"/>
|
||||
<div className="flex-spacer" />
|
||||
<a target="_blank" href="https://discord.gg/XfvZ334gwU" className="button is-link is-small">
|
||||
<span className="icon is-small">
|
||||
<i className="fa-brands fa-discord"/>
|
||||
<i className="fa-brands fa-discord" />
|
||||
</span>
|
||||
<span>Discord</span>
|
||||
</a>
|
||||
@ -716,4 +856,4 @@ class WebShareMain extends React.Component<{}, {}> {
|
||||
}
|
||||
}
|
||||
|
||||
export {WebShareMain};
|
||||
export { WebShareMain };
|
||||
|
@ -1,13 +1,13 @@
|
||||
import * as mobx from "mobx";
|
||||
import {sprintf} from "sprintf-js";
|
||||
import {boundMethod} from "autobind-decorator";
|
||||
import {handleJsonFetchResponse, isModKeyPress, base64ToArray} from "./util";
|
||||
import { sprintf } from "sprintf-js";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { handleJsonFetchResponse, isModKeyPress, base64ToArray } from "./util";
|
||||
import * as T from "./types";
|
||||
import {TermWrap} from "./term";
|
||||
import { TermWrap } from "./term";
|
||||
import * as lineutil from "./lineutil";
|
||||
import * as util from "./util";
|
||||
import {windowWidthToCols, windowHeightToRows, termWidthFromCols, termHeightFromRows} from "./textmeasure";
|
||||
import {WebShareWSControl} from "./webshare-ws";
|
||||
import { windowWidthToCols, windowHeightToRows, termWidthFromCols, termHeightFromRows } from "./textmeasure";
|
||||
import { WebShareWSControl } from "./webshare-ws";
|
||||
|
||||
// @ts-ignore
|
||||
let PROMPT_DEV = __PROMPT_DEV__;
|
||||
@ -22,15 +22,15 @@ let PROMPT_WSAPI_ENDPOINT = __PROMPT_WSAPI_ENDPOINT__;
|
||||
|
||||
type OV<V> = mobx.IObservableValue<V>;
|
||||
type OArr<V> = mobx.IObservableArray<V>;
|
||||
type OMap<K,V> = mobx.ObservableMap<K,V>;
|
||||
type OMap<K, V> = mobx.ObservableMap<K, V>;
|
||||
type CV<V> = mobx.IComputedValue<V>;
|
||||
|
||||
type PtyListener = {
|
||||
receiveData(ptyPos : number, data : Uint8Array, reason? : string);
|
||||
receiveData(ptyPos: number, data: Uint8Array, reason?: string);
|
||||
};
|
||||
|
||||
function isBlank(s : string) {
|
||||
return (s == null || s == "");
|
||||
function isBlank(s: string) {
|
||||
return s == null || s == "";
|
||||
}
|
||||
|
||||
function getBaseUrl() {
|
||||
@ -42,25 +42,25 @@ function getBaseWSUrl() {
|
||||
}
|
||||
|
||||
class WebShareModelClass {
|
||||
viewKey : string;
|
||||
screenId : string;
|
||||
errMessage : OV<string> = mobx.observable.box(null, {name: "errMessage"});
|
||||
fullScreen : OV<T.WebFullScreen> = mobx.observable.box(null, {name: "webScreen"});
|
||||
terminals : Record<string, TermWrap> = {}; // lineid => TermWrap
|
||||
renderers : Record<string, T.RendererModel> = {}; // lineid => RendererModel
|
||||
contentHeightCache : Record<string, number> = {}; // lineid => height
|
||||
wsControl : WebShareWSControl;
|
||||
anchor : {anchorLine : number, anchorOffset : number} = {anchorLine: 0, anchorOffset: 0};
|
||||
selectedLine : OV<number> = mobx.observable.box(0, {name: "selectedLine"});
|
||||
syncSelectedLine : OV<boolean> = mobx.observable.box(true, {name: "syncSelectedLine"});
|
||||
lastScreenSize : T.WindowSize = null;
|
||||
activePtyFetch : Record<string, boolean> = {}; // lineid -> active
|
||||
localPtyOffsetMap : Record<string, number> = {};
|
||||
remotePtyOffsetMap : Record<string, number> = {};
|
||||
activeUpdateFetch : boolean = false;
|
||||
remoteScreenVts : number = 0;
|
||||
isDev : boolean = PROMPT_DEV;
|
||||
|
||||
viewKey: string;
|
||||
screenId: string;
|
||||
errMessage: OV<string> = mobx.observable.box(null, { name: "errMessage" });
|
||||
fullScreen: OV<T.WebFullScreen> = mobx.observable.box(null, { name: "webScreen" });
|
||||
terminals: Record<string, TermWrap> = {}; // lineid => TermWrap
|
||||
renderers: Record<string, T.RendererModel> = {}; // lineid => RendererModel
|
||||
contentHeightCache: Record<string, number> = {}; // lineid => height
|
||||
wsControl: WebShareWSControl;
|
||||
anchor: { anchorLine: number; anchorOffset: number } = { anchorLine: 0, anchorOffset: 0 };
|
||||
selectedLine: OV<number> = mobx.observable.box(0, { name: "selectedLine" });
|
||||
syncSelectedLine: OV<boolean> = mobx.observable.box(true, { name: "syncSelectedLine" });
|
||||
lastScreenSize: T.WindowSize = null;
|
||||
activePtyFetch: Record<string, boolean> = {}; // lineid -> active
|
||||
localPtyOffsetMap: Record<string, number> = {};
|
||||
remotePtyOffsetMap: Record<string, number> = {};
|
||||
activeUpdateFetch: boolean = false;
|
||||
remoteScreenVts: number = 0;
|
||||
isDev: boolean = PROMPT_DEV;
|
||||
|
||||
constructor() {
|
||||
let pathName = window.location.pathname;
|
||||
let screenMatch = pathName.match(/\/share\/([a-f0-9-]+)/);
|
||||
@ -73,17 +73,22 @@ class WebShareModelClass {
|
||||
this.screenId = urlParams.get("screenid");
|
||||
}
|
||||
setTimeout(() => this.loadFullScreenData(false), 10);
|
||||
this.wsControl = new WebShareWSControl(getBaseWSUrl(), this.screenId, this.viewKey, this.wsMessageCallback.bind(this));
|
||||
this.wsControl = new WebShareWSControl(
|
||||
getBaseWSUrl(),
|
||||
this.screenId,
|
||||
this.viewKey,
|
||||
this.wsMessageCallback.bind(this)
|
||||
);
|
||||
document.addEventListener("keydown", this.docKeyDownHandler.bind(this));
|
||||
}
|
||||
|
||||
setErrMessage(msg : string) : void {
|
||||
setErrMessage(msg: string): void {
|
||||
mobx.action(() => {
|
||||
this.errMessage.set(msg);
|
||||
})();
|
||||
}
|
||||
|
||||
setSyncSelectedLine(val : boolean) : void {
|
||||
setSyncSelectedLine(val: boolean): void {
|
||||
mobx.action(() => {
|
||||
this.syncSelectedLine.set(val);
|
||||
if (val) {
|
||||
@ -95,55 +100,55 @@ class WebShareModelClass {
|
||||
})();
|
||||
}
|
||||
|
||||
setLastScreenSize(winSize : T.WindowSize) {
|
||||
setLastScreenSize(winSize: T.WindowSize) {
|
||||
if (winSize == null || winSize.height == 0 || winSize.width == 0) {
|
||||
return;
|
||||
}
|
||||
this.lastScreenSize = winSize;
|
||||
}
|
||||
|
||||
getMaxContentSize() : T.WindowSize {
|
||||
getMaxContentSize(): T.WindowSize {
|
||||
if (this.lastScreenSize == null) {
|
||||
let width = termWidthFromCols(80, WebShareModel.getTermFontSize());
|
||||
let height = termHeightFromRows(25, WebShareModel.getTermFontSize());
|
||||
return {width, height};
|
||||
return { width, height };
|
||||
}
|
||||
let winSize = this.lastScreenSize;
|
||||
let width = util.boundInt(winSize.width-50, 100, 5000);
|
||||
let height = util.boundInt(winSize.height-100, 100, 5000);
|
||||
return {width, height};
|
||||
}
|
||||
|
||||
getIdealContentSize() : T.WindowSize {
|
||||
if (this.lastScreenSize == null) {
|
||||
let width = termWidthFromCols(80, WebShareModel.getTermFontSize());
|
||||
let height = termHeightFromRows(25, WebShareModel.getTermFontSize());
|
||||
return {width, height};
|
||||
}
|
||||
let winSize = this.lastScreenSize;
|
||||
let width = util.boundInt(Math.ceil((winSize.width-50)*0.7), 100, 5000);
|
||||
let height = util.boundInt(Math.ceil((winSize.height-100)*0.5), 100, 5000);
|
||||
return {width, height};
|
||||
let width = util.boundInt(winSize.width - 50, 100, 5000);
|
||||
let height = util.boundInt(winSize.height - 100, 100, 5000);
|
||||
return { width, height };
|
||||
}
|
||||
|
||||
getSelectedLine() : number {
|
||||
getIdealContentSize(): T.WindowSize {
|
||||
if (this.lastScreenSize == null) {
|
||||
let width = termWidthFromCols(80, WebShareModel.getTermFontSize());
|
||||
let height = termHeightFromRows(25, WebShareModel.getTermFontSize());
|
||||
return { width, height };
|
||||
}
|
||||
let winSize = this.lastScreenSize;
|
||||
let width = util.boundInt(Math.ceil((winSize.width - 50) * 0.7), 100, 5000);
|
||||
let height = util.boundInt(Math.ceil((winSize.height - 100) * 0.5), 100, 5000);
|
||||
return { width, height };
|
||||
}
|
||||
|
||||
getSelectedLine(): number {
|
||||
return this.selectedLine.get();
|
||||
}
|
||||
|
||||
getServerSelectedLine() : number {
|
||||
getServerSelectedLine(): number {
|
||||
let fullScreen = this.fullScreen.get();
|
||||
if (fullScreen != null) {
|
||||
return fullScreen.screen.selectedline;
|
||||
}
|
||||
}
|
||||
|
||||
setSelectedLine(lineNum : number) : void {
|
||||
setSelectedLine(lineNum: number): void {
|
||||
mobx.action(() => {
|
||||
this.selectedLine.set(lineNum);
|
||||
})();
|
||||
}
|
||||
|
||||
updateSelectedLineIndex(delta : number) : void {
|
||||
updateSelectedLineIndex(delta: number): void {
|
||||
let fullScreen = this.fullScreen.get();
|
||||
if (fullScreen == null) {
|
||||
return;
|
||||
@ -160,20 +165,20 @@ class WebShareModelClass {
|
||||
this.setSelectedLine(lines[lineIndex].linenum);
|
||||
}
|
||||
|
||||
setAnchorFields(anchorLine : number, anchorOffset : number, reason : string) : void {
|
||||
setAnchorFields(anchorLine: number, anchorOffset: number, reason: string): void {
|
||||
this.anchor.anchorLine = anchorLine;
|
||||
this.anchor.anchorOffset = anchorOffset;
|
||||
}
|
||||
|
||||
getAnchor() : {anchorLine : number, anchorOffset : number} {
|
||||
getAnchor(): { anchorLine: number; anchorOffset: number } {
|
||||
return this.anchor;
|
||||
}
|
||||
|
||||
getTermFontSize() : number {
|
||||
getTermFontSize(): number {
|
||||
return 12;
|
||||
}
|
||||
|
||||
resizeWindow(winSize : T.WindowSize) : void {
|
||||
resizeWindow(winSize: T.WindowSize): void {
|
||||
let cols = windowWidthToCols(winSize.width, this.getTermFontSize());
|
||||
for (let lineId in this.terminals) {
|
||||
let termWrap = this.terminals[lineId];
|
||||
@ -181,8 +186,8 @@ class WebShareModelClass {
|
||||
}
|
||||
}
|
||||
|
||||
mergeLine(fullScreen : T.WebFullScreen, newLine : T.WebLine) {
|
||||
for (let i=0; i<fullScreen.lines.length; i++) {
|
||||
mergeLine(fullScreen: T.WebFullScreen, newLine: T.WebLine) {
|
||||
for (let i = 0; i < fullScreen.lines.length; i++) {
|
||||
let line = fullScreen.lines[i];
|
||||
if (line.lineid == newLine.lineid) {
|
||||
fullScreen.lines[i] = newLine;
|
||||
@ -196,15 +201,15 @@ class WebShareModelClass {
|
||||
fullScreen.lines.push(newLine);
|
||||
}
|
||||
|
||||
removeLine(fullScreen : T.WebFullScreen, lineId : string) {
|
||||
for (let i=0; i<fullScreen.lines.length; i++) {
|
||||
removeLine(fullScreen: T.WebFullScreen, lineId: string) {
|
||||
for (let i = 0; i < fullScreen.lines.length; i++) {
|
||||
let line = fullScreen.lines[i];
|
||||
if (line.lineid == lineId) {
|
||||
fullScreen.lines.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (let i=0; i<fullScreen.cmds.length; i++) {
|
||||
for (let i = 0; i < fullScreen.cmds.length; i++) {
|
||||
let cmd = fullScreen.cmds[i];
|
||||
if (cmd.lineid == lineId) {
|
||||
fullScreen.cmds.splice(i, 1);
|
||||
@ -214,15 +219,15 @@ class WebShareModelClass {
|
||||
this.unloadRenderer(lineId);
|
||||
}
|
||||
|
||||
setCmdDone(lineId : string) : void {
|
||||
setCmdDone(lineId: string): void {
|
||||
let termWrap = this.getTermWrap(lineId);
|
||||
if (termWrap != null) {
|
||||
termWrap.cmdDone();
|
||||
}
|
||||
}
|
||||
|
||||
mergeCmd(fullScreen : T.WebFullScreen, newCmd : T.WebCmd) {
|
||||
for (let i=0; i<fullScreen.cmds.length; i++) {
|
||||
mergeCmd(fullScreen: T.WebFullScreen, newCmd: T.WebCmd) {
|
||||
for (let i = 0; i < fullScreen.cmds.length; i++) {
|
||||
let cmd = fullScreen.cmds[i];
|
||||
if (cmd.lineid == newCmd.lineid) {
|
||||
let wasRunning = lineutil.cmdStatusIsRunning(cmd.status);
|
||||
@ -237,7 +242,7 @@ class WebShareModelClass {
|
||||
fullScreen.cmds.push(newCmd);
|
||||
}
|
||||
|
||||
mergeUpdate(msg : T.WebFullScreen) {
|
||||
mergeUpdate(msg: T.WebFullScreen) {
|
||||
if (msg.screenid != this.screenId) {
|
||||
console.log("bad WebFullScreen update, wrong screenid", msg.screenid);
|
||||
return;
|
||||
@ -272,10 +277,10 @@ class WebShareModelClass {
|
||||
}
|
||||
}
|
||||
this.handleCmdPtyMap(msg.cmdptymap);
|
||||
})();
|
||||
})();
|
||||
}
|
||||
|
||||
handleCmdPtyMap(ptyMap : Record<string, number>) {
|
||||
handleCmdPtyMap(ptyMap: Record<string, number>) {
|
||||
if (ptyMap == null) {
|
||||
return;
|
||||
}
|
||||
@ -289,7 +294,7 @@ class WebShareModelClass {
|
||||
}
|
||||
}
|
||||
|
||||
runPtyFetch(lineId : string) {
|
||||
runPtyFetch(lineId: string) {
|
||||
let prtn = this.checkFetchPtyData(lineId, false);
|
||||
let ptyListener = this.getPtyListener(lineId);
|
||||
if (ptyListener != null) {
|
||||
@ -302,7 +307,7 @@ class WebShareModelClass {
|
||||
}
|
||||
}
|
||||
|
||||
getPtyListener(lineId : string) {
|
||||
getPtyListener(lineId: string) {
|
||||
let termWrap = this.getTermWrap(lineId);
|
||||
if (termWrap != null) {
|
||||
return termWrap;
|
||||
@ -314,7 +319,7 @@ class WebShareModelClass {
|
||||
return null;
|
||||
}
|
||||
|
||||
receivePtyData(lineId : string, ptyPos : number, data : Uint8Array, reason? : string) : void {
|
||||
receivePtyData(lineId: string, ptyPos: number, data: Uint8Array, reason?: string): void {
|
||||
let termWrap = this.getTermWrap(lineId);
|
||||
if (termWrap != null) {
|
||||
termWrap.receiveData(ptyPos, data, reason);
|
||||
@ -325,7 +330,7 @@ class WebShareModelClass {
|
||||
}
|
||||
}
|
||||
|
||||
checkFetchPtyData(lineId : string, reload : boolean) : Promise<T.PtyDataType> {
|
||||
checkFetchPtyData(lineId: string, reload: boolean): Promise<T.PtyDataType> {
|
||||
let lineNum = this.getLineNumFromId(lineId);
|
||||
if (this.activePtyFetch[lineId]) {
|
||||
// console.log("check-fetch", lineNum, "already running");
|
||||
@ -342,34 +347,44 @@ class WebShareModelClass {
|
||||
let remotePtyOffset = this.remotePtyOffsetMap[lineId];
|
||||
if (ptyOffset >= remotePtyOffset) {
|
||||
// up to date
|
||||
return Promise.resolve({pos: ptyOffset, data: new Uint8Array(0)});
|
||||
return Promise.resolve({ pos: ptyOffset, data: new Uint8Array(0) });
|
||||
}
|
||||
this.activePtyFetch[lineId] = true;
|
||||
let viewKey = WebShareModel.viewKey;
|
||||
// console.log("fetch pty", lineNum, "pos=" + ptyOffset);
|
||||
let usp = new URLSearchParams({screenid: this.screenId, viewkey: viewKey, lineid: lineId, pos: String(ptyOffset)});
|
||||
let url = new URL(getBaseUrl() + "/webshare/ptydata?" + usp.toString());
|
||||
return fetch(url, {method: "GET", mode: "cors", cache: "no-cache"}).then((resp) => {
|
||||
if (!resp.ok) {
|
||||
throw new Error(sprintf("Bad fetch response for /webshare/ptydata: %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) => {
|
||||
let dataArr = new Uint8Array(buf);
|
||||
let newOffset = ptyOffset + dataArr.length;
|
||||
// console.log("fetch pty success", lineNum, "len=" + dataArr.length, "pos => " + newOffset);
|
||||
this.localPtyOffsetMap[lineId] = newOffset;
|
||||
return {pos: ptyOffset, data: dataArr};
|
||||
}).finally(() => {
|
||||
this.activePtyFetch[lineId] = false;
|
||||
let usp = new URLSearchParams({
|
||||
screenid: this.screenId,
|
||||
viewkey: viewKey,
|
||||
lineid: lineId,
|
||||
pos: String(ptyOffset),
|
||||
});
|
||||
let url = new URL(getBaseUrl() + "/webshare/ptydata?" + usp.toString());
|
||||
return fetch(url, { method: "GET", mode: "cors", cache: "no-cache" })
|
||||
.then((resp) => {
|
||||
if (!resp.ok) {
|
||||
throw new Error(
|
||||
sprintf("Bad fetch response for /webshare/ptydata: %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) => {
|
||||
let dataArr = new Uint8Array(buf);
|
||||
let newOffset = ptyOffset + dataArr.length;
|
||||
// console.log("fetch pty success", lineNum, "len=" + dataArr.length, "pos => " + newOffset);
|
||||
this.localPtyOffsetMap[lineId] = newOffset;
|
||||
return { pos: ptyOffset, data: dataArr };
|
||||
})
|
||||
.finally(() => {
|
||||
this.activePtyFetch[lineId] = false;
|
||||
});
|
||||
}
|
||||
|
||||
wsMessageCallback(msg : any) {
|
||||
wsMessageCallback(msg: any) {
|
||||
if (msg.type == "webscreen:update") {
|
||||
// console.log("[ws] update vts", msg.vts);
|
||||
if (msg.vts > this.remoteScreenVts) {
|
||||
@ -384,7 +399,7 @@ class WebShareModelClass {
|
||||
console.log("[ws] unhandled message", msg);
|
||||
}
|
||||
|
||||
setWebFullScreen(screen : T.WebFullScreen) {
|
||||
setWebFullScreen(screen: T.WebFullScreen) {
|
||||
// console.log("got initial screen", "vts=" + screen.vts);
|
||||
mobx.action(() => {
|
||||
if (screen.lines == null) {
|
||||
@ -398,13 +413,12 @@ class WebShareModelClass {
|
||||
this.fullScreen.set(screen);
|
||||
this.wsControl.reconnect(true);
|
||||
if (this.syncSelectedLine.get()) {
|
||||
this.selectedLine.set(screen.screen.selectedline);
|
||||
this.selectedLine.set(screen.screen.selectedline);
|
||||
}
|
||||
})();
|
||||
|
||||
}
|
||||
|
||||
loadTerminalRenderer(elem : Element, line : T.WebLine, cmd : T.WebCmd, width : number) : void {
|
||||
loadTerminalRenderer(elem: Element, line: T.WebLine, cmd: T.WebCmd, width: number): void {
|
||||
let lineId = cmd.lineid;
|
||||
let termWrap = this.getTermWrap(lineId);
|
||||
if (termWrap != null) {
|
||||
@ -421,14 +435,16 @@ class WebShareModelClass {
|
||||
termContext: termContext,
|
||||
usedRows: usedRows,
|
||||
termOpts: cmd.termopts,
|
||||
winSize: {height: 0, width: width},
|
||||
winSize: { height: 0, width: width },
|
||||
dataHandler: null,
|
||||
focusHandler: (focus : boolean) => this.setTermFocus(line.linenum, focus),
|
||||
focusHandler: (focus: boolean) => this.setTermFocus(line.linenum, focus),
|
||||
isRunning: lineutil.cmdStatusIsRunning(cmd.status),
|
||||
customKeyHandler: this.termCustomKeyHandler.bind(this),
|
||||
fontSize: this.getTermFontSize(),
|
||||
ptyDataSource: getTermPtyData,
|
||||
onUpdateContentHeight: (termContext : T.RendererContext, height : number) => { this.setContentHeight(termContext, height); },
|
||||
onUpdateContentHeight: (termContext: T.RendererContext, height: number) => {
|
||||
this.setContentHeight(termContext, height);
|
||||
},
|
||||
});
|
||||
this.terminals[lineId] = termWrap;
|
||||
if (this.localPtyOffsetMap[lineId] == null) {
|
||||
@ -441,7 +457,7 @@ class WebShareModelClass {
|
||||
return;
|
||||
}
|
||||
|
||||
termCustomKeyHandler(e : any, termWrap : TermWrap) : boolean {
|
||||
termCustomKeyHandler(e: any, termWrap: TermWrap): boolean {
|
||||
if (e.type != "keydown" || isModKeyPress(e)) {
|
||||
return false;
|
||||
}
|
||||
@ -466,20 +482,19 @@ class WebShareModelClass {
|
||||
return false;
|
||||
}
|
||||
|
||||
setTermFocus(lineNum : number, focus : boolean) : void {
|
||||
}
|
||||
setTermFocus(lineNum: number, focus: boolean): void {}
|
||||
|
||||
getContentHeight(context : T.RendererContext) : number {
|
||||
getContentHeight(context: T.RendererContext): number {
|
||||
let key = context.lineId;
|
||||
return this.contentHeightCache[key];
|
||||
}
|
||||
|
||||
setContentHeight(context : T.RendererContext, height : number) : void {
|
||||
setContentHeight(context: T.RendererContext, height: number): void {
|
||||
let key = context.lineId;
|
||||
this.contentHeightCache[key] = height;
|
||||
}
|
||||
|
||||
unloadRenderer(lineId : string) : void {
|
||||
unloadRenderer(lineId: string): void {
|
||||
let rmodel = this.renderers[lineId];
|
||||
if (rmodel != null) {
|
||||
rmodel.dispose();
|
||||
@ -493,7 +508,7 @@ class WebShareModelClass {
|
||||
delete this.localPtyOffsetMap[lineId];
|
||||
}
|
||||
|
||||
getUsedRows(context : T.RendererContext, line : T.WebLine, cmd : T.WebCmd, width : number) : number {
|
||||
getUsedRows(context: T.RendererContext, line: T.WebLine, cmd: T.WebCmd, width: number): number {
|
||||
if (cmd == null) {
|
||||
return 0;
|
||||
}
|
||||
@ -511,27 +526,27 @@ class WebShareModelClass {
|
||||
if (line.contentheight != null && line.contentheight != -1) {
|
||||
return line.contentheight;
|
||||
}
|
||||
return (lineutil.cmdStatusIsRunning(cmd.status) ? 1 : 0);
|
||||
return lineutil.cmdStatusIsRunning(cmd.status) ? 1 : 0;
|
||||
}
|
||||
return termWrap.getUsedRows();
|
||||
}
|
||||
|
||||
getTermWrap(lineId : string) : TermWrap {
|
||||
getTermWrap(lineId: string): TermWrap {
|
||||
return this.terminals[lineId];
|
||||
}
|
||||
|
||||
getRenderer(lineId : string) : T.RendererModel {
|
||||
getRenderer(lineId: string): T.RendererModel {
|
||||
return this.renderers[lineId];
|
||||
}
|
||||
|
||||
registerRenderer(lineId : string, renderer : T.RendererModel) {
|
||||
registerRenderer(lineId: string, renderer: T.RendererModel) {
|
||||
this.renderers[lineId] = renderer;
|
||||
if (this.localPtyOffsetMap[lineId] == null) {
|
||||
this.localPtyOffsetMap[lineId] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
checkUpdateScreenData() : void {
|
||||
checkUpdateScreenData(): void {
|
||||
let fullScreen = this.fullScreen.get();
|
||||
if (fullScreen == null) {
|
||||
return;
|
||||
@ -543,7 +558,7 @@ class WebShareModelClass {
|
||||
this.loadFullScreenData(true);
|
||||
}
|
||||
|
||||
loadFullScreenData(update : boolean) : void {
|
||||
loadFullScreenData(update: boolean): void {
|
||||
if (isBlank(this.screenId)) {
|
||||
this.setErrMessage("No ScreenId Specified, Cannot Load.");
|
||||
return;
|
||||
@ -558,7 +573,7 @@ class WebShareModelClass {
|
||||
}
|
||||
// console.log("running screen-data update");
|
||||
this.activeUpdateFetch = true;
|
||||
let urlParams : Record<string, string> = {screenid: this.screenId, viewkey: this.viewKey};
|
||||
let urlParams: Record<string, string> = { screenid: this.screenId, viewkey: this.viewKey };
|
||||
if (update) {
|
||||
let fullScreen = this.fullScreen.get();
|
||||
if (fullScreen != null) {
|
||||
@ -567,28 +582,31 @@ class WebShareModelClass {
|
||||
}
|
||||
let usp = new URLSearchParams(urlParams);
|
||||
let url = new URL(getBaseUrl() + "/webshare/screen?" + usp.toString());
|
||||
fetch(url, {method: "GET", mode: "cors", cache: "no-cache"}).then((resp) => handleJsonFetchResponse(url, resp)).then((data) => {
|
||||
let screen : T.WebFullScreen = data;
|
||||
if (update) {
|
||||
this.mergeUpdate(screen);
|
||||
}
|
||||
else {
|
||||
this.setWebFullScreen(screen);
|
||||
}
|
||||
setTimeout(() => this.checkUpdateScreenData(), 300);
|
||||
}).catch((err) => {
|
||||
this.errMessage.set("Cannot get screen: " + err.message);
|
||||
}).finally(() => {
|
||||
this.activeUpdateFetch = false;
|
||||
});
|
||||
fetch(url, { method: "GET", mode: "cors", cache: "no-cache" })
|
||||
.then((resp) => handleJsonFetchResponse(url, resp))
|
||||
.then((data) => {
|
||||
let screen: T.WebFullScreen = data;
|
||||
if (update) {
|
||||
this.mergeUpdate(screen);
|
||||
} else {
|
||||
this.setWebFullScreen(screen);
|
||||
}
|
||||
setTimeout(() => this.checkUpdateScreenData(), 300);
|
||||
})
|
||||
.catch((err) => {
|
||||
this.errMessage.set("Cannot get screen: " + err.message);
|
||||
})
|
||||
.finally(() => {
|
||||
this.activeUpdateFetch = false;
|
||||
});
|
||||
}
|
||||
|
||||
getLineNumFromId(lineId : string) : number {
|
||||
getLineNumFromId(lineId: string): number {
|
||||
let fullScreen = this.fullScreen.get();
|
||||
if (fullScreen == null) {
|
||||
return -1;
|
||||
}
|
||||
for (let i=0; i<fullScreen.lines.length; i++) {
|
||||
for (let i = 0; i < fullScreen.lines.length; i++) {
|
||||
let line = fullScreen.lines[i];
|
||||
if (line.lineid == lineId) {
|
||||
return line.linenum;
|
||||
@ -597,12 +615,12 @@ class WebShareModelClass {
|
||||
return -1;
|
||||
}
|
||||
|
||||
getLineIndex(lineNum : number) : number {
|
||||
getLineIndex(lineNum: number): number {
|
||||
let fullScreen = this.fullScreen.get();
|
||||
if (fullScreen == null) {
|
||||
return -1;
|
||||
}
|
||||
for (let i=0; i<fullScreen.lines.length; i++) {
|
||||
for (let i = 0; i < fullScreen.lines.length; i++) {
|
||||
let line = fullScreen.lines[i];
|
||||
if (line.linenum == lineNum) {
|
||||
return i;
|
||||
@ -611,7 +629,7 @@ class WebShareModelClass {
|
||||
return -1;
|
||||
}
|
||||
|
||||
getNumLines() : number {
|
||||
getNumLines(): number {
|
||||
let fullScreen = this.fullScreen.get();
|
||||
if (fullScreen == null) {
|
||||
return 0;
|
||||
@ -619,7 +637,7 @@ class WebShareModelClass {
|
||||
return fullScreen.lines.length;
|
||||
}
|
||||
|
||||
getCmdById(lineId : string) : T.WebCmd {
|
||||
getCmdById(lineId: string): T.WebCmd {
|
||||
let fullScreen = this.fullScreen.get();
|
||||
if (fullScreen == null) {
|
||||
return null;
|
||||
@ -632,7 +650,7 @@ class WebShareModelClass {
|
||||
return null;
|
||||
}
|
||||
|
||||
docKeyDownHandler(e : any) : void {
|
||||
docKeyDownHandler(e: any): void {
|
||||
if (isModKeyPress(e)) {
|
||||
return;
|
||||
}
|
||||
@ -645,17 +663,17 @@ class WebShareModelClass {
|
||||
}
|
||||
}
|
||||
|
||||
function getTermPtyData(termContext : T.TermContextUnion) : Promise<T.PtyDataType> {
|
||||
function getTermPtyData(termContext: T.TermContextUnion): Promise<T.PtyDataType> {
|
||||
if ("remoteId" in termContext) {
|
||||
throw new Error("remote term ptydata is not supported in webshare");
|
||||
}
|
||||
return WebShareModel.checkFetchPtyData(termContext.lineId, true);
|
||||
}
|
||||
|
||||
let WebShareModel : WebShareModelClass = null;
|
||||
let WebShareModel: WebShareModelClass = null;
|
||||
if ((window as any).WebShareModel == null) {
|
||||
WebShareModel = new WebShareModelClass();
|
||||
(window as any).WebShareModel = WebShareModel;
|
||||
}
|
||||
|
||||
export {WebShareModel, getTermPtyData};
|
||||
export { WebShareModel, getTermPtyData };
|
||||
|
@ -1,50 +1,50 @@
|
||||
import * as mobx from "mobx";
|
||||
import {sprintf} from "sprintf-js";
|
||||
import {boundMethod} from "autobind-decorator";
|
||||
import {WebShareWSMessage} from "./types";
|
||||
import { sprintf } from "sprintf-js";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { WebShareWSMessage } from "./types";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
class WebShareWSControl {
|
||||
wsConn : any;
|
||||
open : mobx.IObservableValue<boolean>;
|
||||
opening : boolean = false;
|
||||
reconnectTimes : number = 0;
|
||||
msgQueue : any[] = [];
|
||||
messageCallback : (any) => void = null;
|
||||
screenId : string = null;
|
||||
viewKey : string = null;
|
||||
wsUrl : string;
|
||||
closed : boolean;
|
||||
|
||||
constructor(wsUrl : string, screenId : string, viewKey : string, messageCallback : (any) => void) {
|
||||
wsConn: any;
|
||||
open: mobx.IObservableValue<boolean>;
|
||||
opening: boolean = false;
|
||||
reconnectTimes: number = 0;
|
||||
msgQueue: any[] = [];
|
||||
messageCallback: (any) => void = null;
|
||||
screenId: string = null;
|
||||
viewKey: string = null;
|
||||
wsUrl: string;
|
||||
closed: boolean;
|
||||
|
||||
constructor(wsUrl: string, screenId: string, viewKey: string, messageCallback: (any) => void) {
|
||||
this.wsUrl = wsUrl;
|
||||
this.messageCallback = messageCallback;
|
||||
this.screenId = screenId;
|
||||
this.viewKey = viewKey;
|
||||
this.open = mobx.observable.box(false, {name: "WSOpen"});
|
||||
this.open = mobx.observable.box(false, { name: "WSOpen" });
|
||||
this.closed = true;
|
||||
setInterval(this.sendPing, 20000);
|
||||
}
|
||||
|
||||
close() : void {
|
||||
close(): void {
|
||||
this.closed = true;
|
||||
if (this.wsConn != null) {
|
||||
this.wsConn.close();
|
||||
}
|
||||
}
|
||||
|
||||
log(str : string) {
|
||||
log(str: string) {
|
||||
console.log("[wscontrol]", str);
|
||||
}
|
||||
|
||||
@mobx.action
|
||||
setOpen(val : boolean) {
|
||||
setOpen(val: boolean) {
|
||||
mobx.action(() => {
|
||||
this.open.set(val);
|
||||
})();
|
||||
}
|
||||
|
||||
connectNow(desc : string) {
|
||||
connectNow(desc: string) {
|
||||
this.closed = false;
|
||||
if (this.open.get()) {
|
||||
return;
|
||||
@ -59,7 +59,7 @@ class WebShareWSControl {
|
||||
// this.wsConn.onerror = this.onerror;
|
||||
}
|
||||
|
||||
reconnect(forceClose? : boolean) {
|
||||
reconnect(forceClose?: boolean) {
|
||||
this.closed = false;
|
||||
if (this.open.get()) {
|
||||
if (forceClose) {
|
||||
@ -73,7 +73,7 @@ class WebShareWSControl {
|
||||
return;
|
||||
}
|
||||
let timeoutArr = [0, 0, 5, 5, 15, 30, 60, 300, 3600];
|
||||
let timeout = timeoutArr[timeoutArr.length-1];
|
||||
let timeout = timeoutArr[timeoutArr.length - 1];
|
||||
if (this.reconnectTimes < timeoutArr.length) {
|
||||
timeout = timeoutArr[this.reconnectTimes];
|
||||
}
|
||||
@ -82,16 +82,15 @@ class WebShareWSControl {
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.connectNow(String(this.reconnectTimes));
|
||||
}, timeout*1000);
|
||||
}, timeout * 1000);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
onclose(event : any) {
|
||||
onclose(event: any) {
|
||||
// console.log("close", event);
|
||||
if (event.wasClean) {
|
||||
this.log("connection closed");
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
this.log("connection error/disconnected");
|
||||
}
|
||||
if (this.open.get() || this.opening) {
|
||||
@ -128,7 +127,7 @@ class WebShareWSControl {
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
onmessage(event : any) {
|
||||
onmessage(event: any) {
|
||||
let eventData = null;
|
||||
if (event.data != null) {
|
||||
eventData = JSON.parse(event.data);
|
||||
@ -137,7 +136,7 @@ class WebShareWSControl {
|
||||
return;
|
||||
}
|
||||
if (eventData.type == "ping") {
|
||||
this.wsConn.send(JSON.stringify({type: "pong", stime: Date.now()}));
|
||||
this.wsConn.send(JSON.stringify({ type: "pong", stime: Date.now() }));
|
||||
return;
|
||||
}
|
||||
if (eventData.type == "pong") {
|
||||
@ -151,8 +150,7 @@ class WebShareWSControl {
|
||||
if (this.messageCallback) {
|
||||
try {
|
||||
this.messageCallback(eventData);
|
||||
}
|
||||
catch (e) {
|
||||
} catch (e) {
|
||||
this.log("[error] messageCallback " + e);
|
||||
}
|
||||
}
|
||||
@ -163,17 +161,17 @@ class WebShareWSControl {
|
||||
if (!this.open.get()) {
|
||||
return;
|
||||
}
|
||||
this.wsConn.send(JSON.stringify({type: "ping", stime: Date.now()}));
|
||||
this.wsConn.send(JSON.stringify({ type: "ping", stime: Date.now() }));
|
||||
}
|
||||
|
||||
sendMessage(data : any) {
|
||||
sendMessage(data: any) {
|
||||
if (!this.open.get()) {
|
||||
return;
|
||||
}
|
||||
this.wsConn.send(JSON.stringify(data));
|
||||
}
|
||||
|
||||
pushMessage(data : any) {
|
||||
pushMessage(data: any) {
|
||||
if (!this.open.get()) {
|
||||
this.msgQueue.push(data);
|
||||
return;
|
||||
@ -182,9 +180,9 @@ class WebShareWSControl {
|
||||
}
|
||||
|
||||
sendWebShareInit() {
|
||||
let pk : WebShareWSMessage = {"type": "webshare", screenid: this.screenId, viewkey: this.viewKey};
|
||||
let pk: WebShareWSMessage = { type: "webshare", screenid: this.screenId, viewkey: this.viewKey };
|
||||
this.pushMessage(pk);
|
||||
}
|
||||
}
|
||||
|
||||
export {WebShareWSControl};
|
||||
export { WebShareWSControl };
|
||||
|
@ -1,7 +1,7 @@
|
||||
body.prompt-webshare #main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
|
||||
.logo-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@ -97,9 +97,9 @@ body.prompt-webshare #main {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
color: white;
|
||||
|
||||
|
||||
#app {
|
||||
color: white;
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
import * as mobx from "mobx";
|
||||
import * as React from "react";
|
||||
import {createRoot} from 'react-dom/client';
|
||||
import {sprintf} from "sprintf-js";
|
||||
import {WebShareMain} from "./webshare-elems";
|
||||
import {loadFonts} from "./util";
|
||||
import {WebShareModel} from "./webshare-model";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { sprintf } from "sprintf-js";
|
||||
import { WebShareMain } from "./webshare-elems";
|
||||
import { loadFonts } from "./util";
|
||||
import { WebShareModel } from "./webshare-model";
|
||||
import * as textmeasure from "./textmeasure";
|
||||
|
||||
// @ts-ignore
|
||||
@ -23,8 +23,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
let isFontLoaded = document.fonts.check("12px 'JetBrains Mono'");
|
||||
if (isFontLoaded) {
|
||||
root.render(reactElem);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
document.fonts.ready.then(() => {
|
||||
root.render(reactElem);
|
||||
});
|
||||
|
80
src/ws.ts
80
src/ws.ts
@ -1,50 +1,50 @@
|
||||
import * as mobx from "mobx";
|
||||
import {sprintf} from "sprintf-js";
|
||||
import {boundMethod} from "autobind-decorator";
|
||||
import {WatchScreenPacketType} from "./types";
|
||||
import { sprintf } from "sprintf-js";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { WatchScreenPacketType } from "./types";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
class WSControl {
|
||||
wsConn : any;
|
||||
open : mobx.IObservableValue<boolean>;
|
||||
opening : boolean = false;
|
||||
reconnectTimes : number = 0;
|
||||
msgQueue : any[] = [];
|
||||
clientId : string;
|
||||
messageCallback : (any) => void = null;
|
||||
watchSessionId : string = null;
|
||||
watchScreenId : string = null;
|
||||
wsLog : mobx.IObservableArray<string> = mobx.observable.array([], {name: "wsLog"})
|
||||
authKey : string;
|
||||
baseHostPort : string;
|
||||
|
||||
constructor(baseHostPort : string, clientId : string, authKey : string, messageCallback : (any) => void) {
|
||||
wsConn: any;
|
||||
open: mobx.IObservableValue<boolean>;
|
||||
opening: boolean = false;
|
||||
reconnectTimes: number = 0;
|
||||
msgQueue: any[] = [];
|
||||
clientId: string;
|
||||
messageCallback: (any) => void = null;
|
||||
watchSessionId: string = null;
|
||||
watchScreenId: string = null;
|
||||
wsLog: mobx.IObservableArray<string> = mobx.observable.array([], { name: "wsLog" });
|
||||
authKey: string;
|
||||
baseHostPort: string;
|
||||
|
||||
constructor(baseHostPort: string, clientId: string, authKey: string, messageCallback: (any) => void) {
|
||||
this.baseHostPort = baseHostPort;
|
||||
this.messageCallback = messageCallback;
|
||||
this.clientId = clientId;
|
||||
this.authKey = authKey;
|
||||
this.open = mobx.observable.box(false, {name: "WSOpen"});
|
||||
this.open = mobx.observable.box(false, { name: "WSOpen" });
|
||||
setInterval(this.sendPing, 5000);
|
||||
}
|
||||
|
||||
log(str : string) {
|
||||
log(str: string) {
|
||||
mobx.action(() => {
|
||||
let ts = dayjs().format("YYYY-MM-DD HH:mm:ss");
|
||||
this.wsLog.push("[" + ts + "] " + str);
|
||||
if (this.wsLog.length > 50) {
|
||||
this.wsLog.splice(0, this.wsLog.length-50);
|
||||
this.wsLog.splice(0, this.wsLog.length - 50);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
@mobx.action
|
||||
setOpen(val : boolean) {
|
||||
setOpen(val: boolean) {
|
||||
mobx.action(() => {
|
||||
this.open.set(val);
|
||||
})();
|
||||
}
|
||||
|
||||
connectNow(desc : string) {
|
||||
connectNow(desc: string) {
|
||||
if (this.open.get()) {
|
||||
return;
|
||||
}
|
||||
@ -58,7 +58,7 @@ class WSControl {
|
||||
// this.wsConn.onerror = this.onerror;
|
||||
}
|
||||
|
||||
reconnect(forceClose? : boolean) {
|
||||
reconnect(forceClose?: boolean) {
|
||||
if (this.open.get()) {
|
||||
if (forceClose) {
|
||||
this.wsConn.close(); // this will force a reconnect
|
||||
@ -80,16 +80,15 @@ class WSControl {
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.connectNow(String(this.reconnectTimes));
|
||||
}, timeout*1000);
|
||||
}, timeout * 1000);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
onclose(event : any) {
|
||||
onclose(event: any) {
|
||||
// console.log("close", event);
|
||||
if (event.wasClean) {
|
||||
this.log("connection closed");
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
this.log("connection error/disconnected");
|
||||
}
|
||||
if (this.open.get() || this.opening) {
|
||||
@ -124,7 +123,7 @@ class WSControl {
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
onmessage(event : any) {
|
||||
onmessage(event: any) {
|
||||
let eventData = null;
|
||||
if (event.data != null) {
|
||||
eventData = JSON.parse(event.data);
|
||||
@ -133,7 +132,7 @@ class WSControl {
|
||||
return;
|
||||
}
|
||||
if (eventData.type == "ping") {
|
||||
this.wsConn.send(JSON.stringify({type: "pong", stime: Date.now()}));
|
||||
this.wsConn.send(JSON.stringify({ type: "pong", stime: Date.now() }));
|
||||
return;
|
||||
}
|
||||
if (eventData.type == "pong") {
|
||||
@ -147,8 +146,7 @@ class WSControl {
|
||||
if (this.messageCallback) {
|
||||
try {
|
||||
this.messageCallback(eventData);
|
||||
}
|
||||
catch (e) {
|
||||
} catch (e) {
|
||||
console.log("[error] messageCallback", e);
|
||||
}
|
||||
}
|
||||
@ -159,17 +157,17 @@ class WSControl {
|
||||
if (!this.open.get()) {
|
||||
return;
|
||||
}
|
||||
this.wsConn.send(JSON.stringify({type: "ping", stime: Date.now()}));
|
||||
this.wsConn.send(JSON.stringify({ type: "ping", stime: Date.now() }));
|
||||
}
|
||||
|
||||
sendMessage(data : any) {
|
||||
sendMessage(data: any) {
|
||||
if (!this.open.get()) {
|
||||
return;
|
||||
}
|
||||
this.wsConn.send(JSON.stringify(data));
|
||||
}
|
||||
|
||||
pushMessage(data : any) {
|
||||
pushMessage(data: any) {
|
||||
if (!this.open.get()) {
|
||||
this.msgQueue.push(data);
|
||||
return;
|
||||
@ -177,8 +175,14 @@ class WSControl {
|
||||
this.sendMessage(data);
|
||||
}
|
||||
|
||||
sendWatchScreenPacket(connect : boolean) {
|
||||
let pk : WatchScreenPacketType = {"type": "watchscreen", connect: connect, sessionid: null, screenid: null, authkey: this.authKey};
|
||||
sendWatchScreenPacket(connect: boolean) {
|
||||
let pk: WatchScreenPacketType = {
|
||||
type: "watchscreen",
|
||||
connect: connect,
|
||||
sessionid: null,
|
||||
screenid: null,
|
||||
authkey: this.authKey,
|
||||
};
|
||||
if (this.watchSessionId != null) {
|
||||
pk.sessionid = this.watchSessionId;
|
||||
}
|
||||
@ -189,11 +193,11 @@ class WSControl {
|
||||
}
|
||||
|
||||
// these params can be null. (null, null) means stop watching
|
||||
watchScreen(sessionId : string, screenId : string) {
|
||||
watchScreen(sessionId: string, screenId: string) {
|
||||
this.watchSessionId = sessionId;
|
||||
this.watchScreenId = screenId;
|
||||
this.sendWatchScreenPacket(false);
|
||||
}
|
||||
}
|
||||
|
||||
export {WSControl};
|
||||
export { WSControl };
|
||||
|
@ -1,5 +1,5 @@
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const LodashModuleReplacementPlugin = require('lodash-webpack-plugin');
|
||||
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
||||
const LodashModuleReplacementPlugin = require("lodash-webpack-plugin");
|
||||
const path = require("path");
|
||||
|
||||
module.exports = {
|
||||
@ -23,18 +23,20 @@ module.exports = {
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
targets: "defaults and not ie > 0 and not op_mini all and not op_mob > 0 and not kaios > 0 and not and_qq > 0 and not and_uc > 0 and not baidu > 0",
|
||||
targets:
|
||||
"defaults and not ie > 0 and not op_mini all and not op_mob > 0 and not kaios > 0 and not and_qq > 0 and not and_uc > 0 and not baidu > 0",
|
||||
},
|
||||
],
|
||||
"@babel/preset-react",
|
||||
"@babel/preset-typescript"],
|
||||
"@babel/preset-typescript",
|
||||
],
|
||||
plugins: [
|
||||
["@babel/transform-runtime", {"regenerator": true}],
|
||||
["@babel/transform-runtime", { regenerator: true }],
|
||||
"@babel/plugin-transform-react-jsx",
|
||||
["@babel/plugin-proposal-decorators", { "legacy": true }],
|
||||
["@babel/plugin-proposal-class-properties", { "loose": true }],
|
||||
["@babel/plugin-proposal-private-methods", { "loose": true }],
|
||||
["@babel/plugin-proposal-private-property-in-object", { "loose": true }],
|
||||
["@babel/plugin-proposal-decorators", { legacy: true }],
|
||||
["@babel/plugin-proposal-class-properties", { loose: true }],
|
||||
["@babel/plugin-proposal-private-methods", { loose: true }],
|
||||
["@babel/plugin-proposal-private-property-in-object", { loose: true }],
|
||||
"babel-plugin-jsx-control-statements",
|
||||
],
|
||||
},
|
||||
@ -42,26 +44,19 @@ module.exports = {
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [
|
||||
"style-loader",
|
||||
"css-loader",
|
||||
],
|
||||
use: ["style-loader", "css-loader"],
|
||||
},
|
||||
{
|
||||
test: /\.less$/,
|
||||
use: [
|
||||
{loader: MiniCssExtractPlugin.loader},
|
||||
"css-loader",
|
||||
"less-loader"
|
||||
]
|
||||
use: [{ loader: MiniCssExtractPlugin.loader }, "css-loader", "less-loader"],
|
||||
},
|
||||
]
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new MiniCssExtractPlugin({filename: "[name].css", ignoreOrder: true}),
|
||||
new MiniCssExtractPlugin({ filename: "[name].css", ignoreOrder: true }),
|
||||
new LodashModuleReplacementPlugin(),
|
||||
],
|
||||
resolve: {
|
||||
extensions: ['.ts', '.tsx', '.js', '.mjs', '.cjs', '.wasm', '.json', '.less', '.css']
|
||||
extensions: [".ts", ".tsx", ".js", ".mjs", ".cjs", ".wasm", ".json", ".less", ".css"],
|
||||
},
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
const webpack = require('webpack');
|
||||
const merge = require('webpack-merge');
|
||||
const common = require('./webpack.common.js');
|
||||
const path = require('path');
|
||||
const webpack = require("webpack");
|
||||
const merge = require("webpack-merge");
|
||||
const common = require("./webpack.common.js");
|
||||
const path = require("path");
|
||||
const CopyPlugin = require("copy-webpack-plugin");
|
||||
const VERSION = require("./version.js");
|
||||
|
||||
@ -18,7 +18,7 @@ var merged = merge.merge(common, {
|
||||
},
|
||||
port: 9000,
|
||||
headers: {
|
||||
'Cache-Control': 'no-store',
|
||||
"Cache-Control": "no-store",
|
||||
},
|
||||
},
|
||||
watchOptions: {
|
||||
@ -34,4 +34,3 @@ var definePlugin = new webpack.DefinePlugin({
|
||||
merged.plugins.push(definePlugin);
|
||||
|
||||
module.exports = merged;
|
||||
|
||||
|
@ -9,10 +9,10 @@ module.exports = {
|
||||
target: "electron-main",
|
||||
output: {
|
||||
path: path.resolve(__dirname, "dist-dev"),
|
||||
filename: "[name].js"
|
||||
filename: "[name].js",
|
||||
},
|
||||
externals: {
|
||||
"fs": "require('fs')",
|
||||
fs: "require('fs')",
|
||||
"fs-ext": "require('fs-ext')",
|
||||
},
|
||||
module: {
|
||||
@ -27,31 +27,33 @@ module.exports = {
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
targets: "defaults and not ie > 0 and not op_mini all and not op_mob > 0 and not kaios > 0 and not and_qq > 0 and not and_uc > 0 and not baidu > 0",
|
||||
targets:
|
||||
"defaults and not ie > 0 and not op_mini all and not op_mob > 0 and not kaios > 0 and not and_qq > 0 and not and_uc > 0 and not baidu > 0",
|
||||
},
|
||||
],
|
||||
"@babel/preset-react",
|
||||
"@babel/preset-typescript"],
|
||||
"@babel/preset-typescript",
|
||||
],
|
||||
plugins: [
|
||||
["@babel/transform-runtime", {"regenerator": true}],
|
||||
["@babel/transform-runtime", { regenerator: true }],
|
||||
"@babel/plugin-transform-react-jsx",
|
||||
["@babel/plugin-proposal-decorators", { "legacy": true }],
|
||||
["@babel/plugin-proposal-class-properties", { "loose": true }],
|
||||
["@babel/plugin-proposal-private-methods", { "loose": true }],
|
||||
["@babel/plugin-proposal-private-property-in-object", { "loose": true }],
|
||||
["@babel/plugin-proposal-decorators", { legacy: true }],
|
||||
["@babel/plugin-proposal-class-properties", { loose: true }],
|
||||
["@babel/plugin-proposal-private-methods", { loose: true }],
|
||||
["@babel/plugin-proposal-private-property-in-object", { loose: true }],
|
||||
"babel-plugin-jsx-control-statements",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new CopyPlugin({
|
||||
patterns: [{from: "src/preload.js", to: "preload.js"}],
|
||||
patterns: [{ from: "src/preload.js", to: "preload.js" }],
|
||||
}),
|
||||
],
|
||||
resolve: {
|
||||
extensions: ['.ts', '.tsx', '.js']
|
||||
extensions: [".ts", ".tsx", ".js"],
|
||||
},
|
||||
}
|
||||
};
|
||||
|
@ -1,8 +1,8 @@
|
||||
const webpack = require('webpack');
|
||||
const merge = require('webpack-merge');
|
||||
const common = require('./webpack.electron.js');
|
||||
const webpack = require("webpack");
|
||||
const merge = require("webpack-merge");
|
||||
const common = require("./webpack.electron.js");
|
||||
const moment = require("dayjs");
|
||||
const VERSION = require('./version.js');
|
||||
const VERSION = require("./version.js");
|
||||
const path = require("path");
|
||||
|
||||
function makeBuildStr() {
|
||||
@ -17,7 +17,7 @@ let merged = merge.merge(common, {
|
||||
mode: "production",
|
||||
output: {
|
||||
path: path.resolve(__dirname, "dist"),
|
||||
filename: "[name].js"
|
||||
filename: "[name].js",
|
||||
},
|
||||
devtool: "source-map",
|
||||
optimization: {
|
||||
@ -25,10 +25,12 @@ let merged = merge.merge(common, {
|
||||
},
|
||||
});
|
||||
|
||||
merged.plugins.push(new webpack.DefinePlugin({
|
||||
__PROMPT_DEV__: "false",
|
||||
__PROMPT_VERSION__: JSON.stringify(VERSION),
|
||||
__PROMPT_BUILD__: JSON.stringify(BUILD),
|
||||
}));
|
||||
merged.plugins.push(
|
||||
new webpack.DefinePlugin({
|
||||
__PROMPT_DEV__: "false",
|
||||
__PROMPT_VERSION__: JSON.stringify(VERSION),
|
||||
__PROMPT_BUILD__: JSON.stringify(BUILD),
|
||||
})
|
||||
);
|
||||
|
||||
module.exports = merged;
|
||||
|
@ -1,6 +1,6 @@
|
||||
const webpack = require('webpack');
|
||||
const merge = require('webpack-merge');
|
||||
const common = require('./webpack.common.js');
|
||||
const webpack = require("webpack");
|
||||
const merge = require("webpack-merge");
|
||||
const common = require("./webpack.common.js");
|
||||
const moment = require("dayjs");
|
||||
const path = require("path");
|
||||
const VERSION = require("./version.js");
|
||||
@ -15,7 +15,7 @@ const BUILD = makeBuildStr();
|
||||
|
||||
let BundleAnalyzerPlugin = null;
|
||||
if (process.env.WEBPACK_ANALYZE) {
|
||||
BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
|
||||
BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
|
||||
}
|
||||
|
||||
let merged = merge.merge(common, {
|
||||
@ -33,13 +33,12 @@ let merged = merge.merge(common, {
|
||||
if (BundleAnalyzerPlugin != null) {
|
||||
merged.plugins.push(new BundleAnalyzerPlugin());
|
||||
}
|
||||
merged.plugins.push(new webpack.DefinePlugin({
|
||||
__PROMPT_DEV__: "false",
|
||||
__PROMPT_VERSION__: JSON.stringify(VERSION),
|
||||
__PROMPT_BUILD__: JSON.stringify(BUILD),
|
||||
}));
|
||||
merged.plugins.push(
|
||||
new webpack.DefinePlugin({
|
||||
__PROMPT_DEV__: "false",
|
||||
__PROMPT_VERSION__: JSON.stringify(VERSION),
|
||||
__PROMPT_BUILD__: JSON.stringify(BUILD),
|
||||
})
|
||||
);
|
||||
|
||||
module.exports = merged;
|
||||
|
||||
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
const webpack = require('webpack');
|
||||
const merge = require('webpack-merge');
|
||||
const common = require('./webpack.share.js');
|
||||
const path = require('path');
|
||||
const webpack = require("webpack");
|
||||
const merge = require("webpack-merge");
|
||||
const common = require("./webpack.share.js");
|
||||
const path = require("path");
|
||||
const CopyPlugin = require("copy-webpack-plugin");
|
||||
const VERSION = require("./version.js");
|
||||
|
||||
@ -18,10 +18,10 @@ var merged = merge.merge(common, {
|
||||
},
|
||||
port: 9001,
|
||||
headers: {
|
||||
'Cache-Control': 'no-store',
|
||||
"Cache-Control": "no-store",
|
||||
},
|
||||
devMiddleware: {
|
||||
publicPath: '/dist-dev/'
|
||||
publicPath: "/dist-dev/",
|
||||
},
|
||||
allowedHosts: "all",
|
||||
hot: false,
|
||||
@ -42,4 +42,3 @@ var definePlugin = new webpack.DefinePlugin({
|
||||
merged.plugins.push(definePlugin);
|
||||
|
||||
module.exports = merged;
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const LodashModuleReplacementPlugin = require('lodash-webpack-plugin');
|
||||
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
||||
const LodashModuleReplacementPlugin = require("lodash-webpack-plugin");
|
||||
const path = require("path");
|
||||
|
||||
module.exports = {
|
||||
@ -23,18 +23,20 @@ module.exports = {
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
targets: "defaults and not ie > 0 and not op_mini all and not op_mob > 0 and not kaios > 0 and not and_qq > 0 and not and_uc > 0 and not baidu > 0",
|
||||
targets:
|
||||
"defaults and not ie > 0 and not op_mini all and not op_mob > 0 and not kaios > 0 and not and_qq > 0 and not and_uc > 0 and not baidu > 0",
|
||||
},
|
||||
],
|
||||
"@babel/preset-react",
|
||||
"@babel/preset-typescript"],
|
||||
"@babel/preset-typescript",
|
||||
],
|
||||
plugins: [
|
||||
["@babel/transform-runtime", {"regenerator": true}],
|
||||
["@babel/transform-runtime", { regenerator: true }],
|
||||
"@babel/plugin-transform-react-jsx",
|
||||
["@babel/plugin-proposal-decorators", { "legacy": true }],
|
||||
["@babel/plugin-proposal-class-properties", { "loose": true }],
|
||||
["@babel/plugin-proposal-private-methods", { "loose": true }],
|
||||
["@babel/plugin-proposal-private-property-in-object", { "loose": true }],
|
||||
["@babel/plugin-proposal-decorators", { legacy: true }],
|
||||
["@babel/plugin-proposal-class-properties", { loose: true }],
|
||||
["@babel/plugin-proposal-private-methods", { loose: true }],
|
||||
["@babel/plugin-proposal-private-property-in-object", { loose: true }],
|
||||
"babel-plugin-jsx-control-statements",
|
||||
],
|
||||
},
|
||||
@ -42,26 +44,19 @@ module.exports = {
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [
|
||||
"style-loader",
|
||||
"css-loader",
|
||||
],
|
||||
use: ["style-loader", "css-loader"],
|
||||
},
|
||||
{
|
||||
test: /\.less$/,
|
||||
use: [
|
||||
{loader: MiniCssExtractPlugin.loader},
|
||||
"css-loader",
|
||||
"less-loader"
|
||||
]
|
||||
use: [{ loader: MiniCssExtractPlugin.loader }, "css-loader", "less-loader"],
|
||||
},
|
||||
]
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new MiniCssExtractPlugin({filename: "[name].css", ignoreOrder: true}),
|
||||
new MiniCssExtractPlugin({ filename: "[name].css", ignoreOrder: true }),
|
||||
new LodashModuleReplacementPlugin(),
|
||||
],
|
||||
resolve: {
|
||||
extensions: ['.ts', '.tsx', '.js', '.mjs', '.cjs', '.wasm', '.json', '.less', '.css']
|
||||
extensions: [".ts", ".tsx", ".js", ".mjs", ".cjs", ".wasm", ".json", ".less", ".css"],
|
||||
},
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
const webpack = require('webpack');
|
||||
const merge = require('webpack-merge');
|
||||
const common = require('./webpack.share.js');
|
||||
const webpack = require("webpack");
|
||||
const merge = require("webpack-merge");
|
||||
const common = require("./webpack.share.js");
|
||||
const moment = require("dayjs");
|
||||
const path = require("path");
|
||||
const VERSION = require("./version.js");
|
||||
@ -25,12 +25,14 @@ let merged = merge.merge(common, {
|
||||
},
|
||||
});
|
||||
|
||||
merged.plugins.push(new webpack.DefinePlugin({
|
||||
__PROMPT_DEV__: "false",
|
||||
__PROMPT_VERSION__: JSON.stringify(VERSION),
|
||||
__PROMPT_BUILD__: JSON.stringify(BUILD),
|
||||
__PROMPT_API_ENDPOINT__: JSON.stringify("https://share.getprompt.dev/api"),
|
||||
__PROMPT_WSAPI_ENDPOINT__: JSON.stringify("wss://wsapi.getprompt.dev"),
|
||||
}));
|
||||
merged.plugins.push(
|
||||
new webpack.DefinePlugin({
|
||||
__PROMPT_DEV__: "false",
|
||||
__PROMPT_VERSION__: JSON.stringify(VERSION),
|
||||
__PROMPT_BUILD__: JSON.stringify(BUILD),
|
||||
__PROMPT_API_ENDPOINT__: JSON.stringify("https://share.getprompt.dev/api"),
|
||||
__PROMPT_WSAPI_ENDPOINT__: JSON.stringify("wss://wsapi.getprompt.dev"),
|
||||
})
|
||||
);
|
||||
|
||||
module.exports = merged;
|
||||
|
Loading…
Reference in New Issue
Block a user