prettier all the files

This commit is contained in:
sawka
2023-08-21 21:37:04 -07:00
parent 8b473607c7
commit c8cae274bc
52 changed files with 8628 additions and 8083 deletions
+9 -10
View File
@@ -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: {},
},
],
+98 -54
View File
@@ -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 };
+20 -14
View File
@@ -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;
}
+30 -25
View File
@@ -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;
}
+2 -2
View File
@@ -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();
+126 -67
View File
@@ -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>&#x2318;{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,
};
+99 -91
View File
@@ -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();
}
})
});
})();
+44 -24
View File
@@ -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 };
+253 -130
View File
File diff suppressed because it is too large Load Diff
+1013 -1147
View File
File diff suppressed because it is too large Load Diff
+16 -12
View File
@@ -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;
+122 -110
View File
@@ -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 };
+31 -23
View File
@@ -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,
};
+695 -397
View File
File diff suppressed because it is too large Load Diff
+13 -13
View File
@@ -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%);
}
+23 -14
View File
@@ -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&nbsp;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&nbsp;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 };
+3635 -4043
View File
File diff suppressed because it is too large Load Diff
+70 -74
View File
@@ -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 -1
View File
@@ -1,4 +1,4 @@
let {contextBridge, ipcRenderer} = require("electron");
let { contextBridge, ipcRenderer } = require("electron");
contextBridge.exposeInMainWorld("api", {
getId: () => ipcRenderer.sendSync("get-id"),
+186 -186
View File
@@ -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;
}
}
}
}

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