From a25c7b42863f60bc75957630f6bf25fabb55f90d Mon Sep 17 00:00:00 2001 From: anandamarsh Date: Sat, 23 Sep 2023 21:05:06 -0700 Subject: [PATCH] Pe 179 refactoring UI code (#25) * refactored main JS files * modals have been refactored * lines and renderer refactored * all refactoring done. ready for reskinning --- scripthaus.md | 3 +- src/{comps.less => common/common.less} | 41 + src/{elements.tsx => common/common.tsx} | 7 +- src/dbops.ts | 4 - src/index.less | 641 +++++ src/{prompt.ts => index.ts} | 5 +- src/keyframes.less | 36 - src/main.tsx | 2298 ----------------- src/main/Main.tsx | 140 + src/main/bookmarks/bookmarks.less | 99 + src/{ => main/bookmarks}/bookmarks.tsx | 13 +- src/{views.less => main/history/history.less} | 192 +- src/{ => main/history}/history.tsx | 10 +- src/{ => main/line}/linecomps.tsx | 24 +- src/{ => main/line}/lines.less | 6 +- src/{ => main/line}/linesview.tsx | 6 +- src/{ => main/line}/lineutil.ts | 4 +- src/{ => main/line/renderer}/fullrenderer.tsx | 15 +- .../line/renderer}/simplerenderer.tsx | 10 +- src/main/modals/Modals.tsx | 357 +++ src/{ => main/modals}/modals.less | 249 +- src/{ => main/modals}/settings.tsx | 22 +- src/main/sessionview/CmdInput.tsx | 192 ++ src/main/sessionview/HistoryInfo.tsx | 234 ++ src/main/sessionview/InfoMsg.tsx | 113 + src/main/sessionview/SessionView.tsx | 457 ++++ src/main/sessionview/TextareaInput.tsx | 573 ++++ .../sessionview/sessionview.less} | 41 +- src/{ => main/sessionview}/tabs.less | 2 + src/main/sidebar/MainSideBar.tsx | 346 +++ src/{ => main/sidebar}/sidebar.less | 2 + src/modals.tsx | 62 - src/model.ts | 2 +- src/{view => plugins}/code/index.tsx | 6 +- src/{view => plugins}/code/split.less | 0 src/{view => plugins}/image.tsx | 6 +- src/{view => plugins}/markdown.tsx | 14 +- src/{view => plugins}/mustache.tsx | 4 +- src/{view => plugins}/openai.tsx | 10 +- src/plugins/plugins.less | 337 +++ src/{ => plugins}/plugins.ts | 14 +- src/prompt.less | 416 --- src/remotes/remotes.less | 248 ++ src/{ => remotes}/remotes.tsx | 16 +- src/{ => webshare}/webshare-client-view.tsx | 8 +- src/{ => webshare}/webshare-elems.tsx | 0 src/{ => webshare}/webshare-model.ts | 0 src/{ => webshare}/webshare-ws.ts | 0 src/{ => webshare}/webshare.less | 41 + src/{ => webshare}/webshare.ts | 0 webpack.common.js | 2 +- webpack.share.js | 2 +- 52 files changed, 3927 insertions(+), 3403 deletions(-) rename src/{comps.less => common/common.less} (93%) rename src/{elements.tsx => common/common.tsx} (98%) delete mode 100644 src/dbops.ts create mode 100644 src/index.less rename src/{prompt.ts => index.ts} (86%) delete mode 100644 src/keyframes.less delete mode 100644 src/main.tsx create mode 100644 src/main/Main.tsx create mode 100644 src/main/bookmarks/bookmarks.less rename src/{ => main/bookmarks}/bookmarks.tsx (96%) rename src/{views.less => main/history/history.less} (68%) rename src/{ => main/history}/history.tsx (99%) rename src/{ => main/line}/linecomps.tsx (98%) rename src/{ => main/line}/lines.less (99%) rename src/{ => main/line}/linesview.tsx (99%) rename src/{ => main/line}/lineutil.ts (95%) rename src/{ => main/line/renderer}/fullrenderer.tsx (86%) rename src/{ => main/line/renderer}/simplerenderer.tsx (97%) create mode 100644 src/main/modals/Modals.tsx rename src/{ => main/modals}/modals.less (53%) rename src/{ => main/modals}/settings.tsx (98%) create mode 100644 src/main/sessionview/CmdInput.tsx create mode 100644 src/main/sessionview/HistoryInfo.tsx create mode 100644 src/main/sessionview/InfoMsg.tsx create mode 100644 src/main/sessionview/SessionView.tsx create mode 100644 src/main/sessionview/TextareaInput.tsx rename src/{cmdinput.less => main/sessionview/sessionview.less} (92%) rename src/{ => main/sessionview}/tabs.less (99%) create mode 100644 src/main/sidebar/MainSideBar.tsx rename src/{ => main/sidebar}/sidebar.less (99%) delete mode 100644 src/modals.tsx rename src/{view => plugins}/code/index.tsx (99%) rename src/{view => plugins}/code/split.less (100%) rename src/{view => plugins}/image.tsx (92%) rename src/{view => plugins}/markdown.tsx (87%) rename src/{view => plugins}/mustache.tsx (99%) rename src/{view => plugins}/openai.tsx (96%) create mode 100644 src/plugins/plugins.less rename src/{ => plugins}/plugins.ts (88%) delete mode 100644 src/prompt.less create mode 100644 src/remotes/remotes.less rename src/{ => remotes}/remotes.tsx (98%) rename src/{ => webshare}/webshare-client-view.tsx (97%) rename src/{ => webshare}/webshare-elems.tsx (100%) rename src/{ => webshare}/webshare-model.ts (100%) rename src/{ => webshare}/webshare-ws.ts (100%) rename src/{ => webshare}/webshare.less (76%) rename src/{ => webshare}/webshare.ts (100%) diff --git a/scripthaus.md b/scripthaus.md index 4c718aea..ded1b922 100644 --- a/scripthaus.md +++ b/scripthaus.md @@ -12,7 +12,6 @@ node_modules/.bin/webpack --watch --config webpack.dev.js node_modules/.bin/webpack --config webpack.dev.js ``` - ```bash # @scripthaus command webpack-electron-watch # @scripthaus cd :playbook @@ -64,7 +63,7 @@ node_modules/.bin/webpack --config webpack.share.prod.js ```bash # @scripthaus command typecheck # @scripthaus cd :playbook -node_modules/.bin/tsc --jsx preserve --noEmit --esModuleInterop --target ES5 --experimentalDecorators --downlevelIteration src/prompt.ts +node_modules/.bin/tsc --jsx preserve --noEmit --esModuleInterop --target ES5 --experimentalDecorators --downlevelIteration src/index.ts ``` ```bash diff --git a/src/comps.less b/src/common/common.less similarity index 93% rename from src/comps.less rename to src/common/common.less index 3416f2c8..01e28097 100644 --- a/src/comps.less +++ b/src/common/common.less @@ -1,3 +1,44 @@ +@import "../index.less"; + +.info-message { + position: relative; + font-weight: normal; + font-size: 12px; + color: @term-white; + + .message-content { + position: absolute; + display: none; + flex-direction: row; + align-items: flex-start; + top: -6px; + left: -6px; + padding: 5px; + border: 1px solid #777; + background-color: #444; + border-radius: 5px; + z-index: 5; + overflow: hidden; + + .info-icon { + margin-right: 5px; + width: 12px; + flex-shrink: 0; + } + + .info-children { + flex: 1 0 0; + overflow: hidden; + } + } + + &:hover { + .message-content { + display: flex; + } + } +} + .cmdstr-code { position: relative; display: flex; diff --git a/src/elements.tsx b/src/common/common.tsx similarity index 98% rename from src/elements.tsx rename to src/common/common.tsx index 7beb658d..c405a2b6 100644 --- a/src/elements.tsx +++ b/src/common/common.tsx @@ -1,13 +1,14 @@ 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 cn from "classnames"; -import { If, For, When, Otherwise, Choose } from "tsx-control-statements/components"; -import type { RemoteType } from "./types"; +import { If } from "tsx-control-statements/components"; +import type { RemoteType } from "../types"; + +import "./common.less"; type OV = mobx.IObservableValue; diff --git a/src/dbops.ts b/src/dbops.ts deleted file mode 100644 index 50d2819f..00000000 --- a/src/dbops.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { sprintf } from "sprintf-js"; -import { Database } from "better-sqlite3"; - -let DB = new Database(); diff --git a/src/index.less b/src/index.less new file mode 100644 index 00000000..63ba387d --- /dev/null +++ b/src/index.less @@ -0,0 +1,641 @@ +@term-black: #000000; +@term-red: #cc0000; +@term-green: #4e9a06; +@term-yellow: #c4a000; +@term-blue: #3465a4; +@term-magenta: #75507b; +@term-cyan: #06989a; +@term-white: #d3d7cf; +@term-bright-black: #555753; +@term-bright-red: #ef2929; +@term-bright-green: #8ae234; +@term-bright-yellow: #fce94f; +@term-bright-blue: #32afff; +@term-bright-magenta: #ad7fa8; +@term-bright-cyan: #34e2e2; +@term-bright-white: #ffffff; + +@tab-black: rgb(0, 0, 0); +@tab-red: rgb(205, 49, 49); +@tab-green: rgb(0, 128, 0); +@tab-orange: rgb(255, 199, 6); +@tab-yellow: rgb(229, 229, 16); +@tab-blue: rgb(0, 71, 171); +@tab-magenta: rgb(188, 63, 188); +@tab-cyan: rgb(17, 168, 205); +@tab-white: rgb(249, 249, 249); + +@tab-black-text: #333; +@tab-white-text: #d7d7d7; + +@prompt-green: rgb(0, 177, 10); + +@soft-blue: #729fcf; + +@active-menu-color: rgb(0, 71, 171); + +@import "utils.less"; + +// global settings / overrides + +:root { + --fa-style-family: "Font Awesome 6 Sharp"; +} + +html, +body, +#app, +#main { + background-color: #000; + height: 100vh; +} + +.content { + a:hover { + color: #485fc7; + } +} + +body { + overflow: hidden; +} + +body::-webkit-scrollbar { + display: none; +} + +*::-webkit-scrollbar { + background-color: #777; + width: 5px; + height: 5px; +} + +*::-webkit-scrollbar-thumb { + background: white; +} + +input[type="checkbox"] { + cursor: pointer; +} + +// main layout +#main { + height: 100vh; + display: flex; + flex-direction: column; + + .main-content { + display: flex; + flex-direction: row; + background-color: black; + height: 100%; + + .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; + 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; +} + +.terminal-wrapper { + position: relative; + + .term-block { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: transparent; + z-index: 10; + } + + .xterm-screen { + &::-webkit-scrollbar { + display: none; + } + } + + &.focus .xterm { + .xterm-screen { + overflow-y: scroll; + overscroll-behavior: contain; + } + + .xterm-viewport { + overscroll-behavior: contain; + } + } + + &.focus .xterm-viewport { + &::-webkit-scrollbar { + background-color: #777; + width: 5px; + height: 5px; + } + + &::-webkit-scrollbar-thumb { + background: white; + } + } + + .xterm-viewport { + &::-webkit-scrollbar { + background-color: #222; + width: 5px; + height: 5px; + } + + &::-webkit-scrollbar-thumb { + background: #555; + } + } +} + +body .xterm .xterm-viewport { + overflow-y: auto; + width: calc(100% + 5px); +} + +.checkbox-toggle { + position: relative; + display: inline-block; + width: 40px; + height: 23px; + + input { + opacity: 0; + width: 0; + height: 0; + } + + .slider { + position: absolute; + content: ""; + cursor: pointer; + top: 0; + bottom: 0; + left: 0; + right: 0; + background-color: #333; + transition: 0.5s; + border-radius: 33px; + } + + .slider:before { + position: absolute; + content: ""; + height: 18px; + width: 18px; + left: 3px; + bottom: 3px; + background-color: white; + transition: 0.5s; + border-radius: 50%; + } + + input:checked + .slider { + background-color: @term-green; + } + + input:checked + .slider:before { + transform: translateX(18px); + } +} + +.button.is-prompt-green { + background-color: #222; + color: @term-white; + + &:hover { + background-color: @term-green; + color: @term-bright-white; + } +} + +.button.is-plain, +.button.is-prompt-cancel { + background-color: #222; + color: @term-white; + + &:hover { + background-color: #666; + color: @term-bright-white; + } +} + +.button.is-prompt-danger { + background-color: #222; + color: @term-white; + + &:hover { + background-color: @tab-red; + color: @term-bright-white; + } +} + +.button.is-inline-height { + height: 22px; +} + +.button input.confirm-checkbox { + margin-right: 5px; +} + +.copied-indicator { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: white; + opacity: 0; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + pointer-events: none; + .mono-font(12px); + animation-name: fade-in-out; + animation-duration: 0.3s; +} + +.loading-spinner { + display: inline-block; + position: absolute; + top: calc(40% - 8px); + left: 30px; + width: 20px; + height: 20px; + + div { + box-sizing: border-box; + display: block; + position: absolute; + width: 16px; + height: 16px; + margin: 2px; + border: 2px solid #777; + border-radius: 50%; + animation: loader-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; + border-color: #777 transparent transparent transparent; + } + + div:nth-child(1) { + animation-delay: -0.45s; + } + + div:nth-child(2) { + animation-delay: -0.3s; + } + + div:nth-child(3) { + animation-delay: -0.15s; + } +} + +#measure { + position: absolute; + z-index: -1; + top: -5000px; + + .mono { + .mono-font(); + } + + .pre { + white-space: pre; + } +} + +.text-button { + .mono-font(12px, 700); + color: @term-white; + cursor: pointer; + background-color: #171717; + outline: 2px solid #171717; + + &: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 { + color: @term-red; + } + } + + &.success-button { + color: @term-green; + &:hover { + color: @term-green; + } + } + + &.error-button { + color: @term-red; + &:hover { + color: @term-red; + } + } + + &.grey-button { + color: #666; + &:hover { + color: #666; + } + } + + &.disabled-button { + &:hover, + &:focus { + outline: none; + background-color: #171717; + } + cursor: default; + } +} + +.focus-indicator { + position: absolute; + display: none; + width: 5px; + border-radius: 3px; + height: calc(100% - 20px); + top: 10px; + left: 0; + z-index: 8; + + &.selected { + display: block; + background-color: #666 !important; + } + + &.active, + &.active.selected { + display: block; + background-color: @tab-blue !important; + } + + &.active.selected.fg-focus { + display: block; + background-color: @tab-green !important; + } +} + +.focus-parent:hover .focus-indicator { + display: block; + background-color: #222; +} + +#main .term-prompt { + font-size: 14px; + + i { + margin-right: 3px; + } + + .term-prompt-branch { + color: @term-white; + } + + .term-prompt-python { + color: @term-bright-magenta; + } + + .term-prompt-remote { + i { + margin-right: 0; + } + } + + .term-prompt-remote { + color: @term-bright-green; + + &.color-green { + color: @term-bright-green; + } + + &.color-red { + color: @term-bright-red; + } + + &.color-blue { + color: @term-bright-blue; + } + + &.color-yellow { + color: @term-bright-yellow; + } + + &.color-magenta { + color: @term-bright-magenta; + } + + &.color-cyan { + color: @term-bright-cyan; + } + + &.color-white { + color: @term-bright-white; + } + + &.color-orange { + color: @tab-orange; + } + } + + .term-prompt-cwd { + color: @term-bright-green; + } + + .term-prompt-end { + color: @term-bright-green; + } +} + +.remote-status { + font-size: 8px; + margin-right: 5px; + position: relative; + top: -3px; + color: #c4a000; + + &.status-init, + &.status-disconnected { + color: #c4a000; + } + + &.status-connecting { + color: #c4a000; + } + + &.status-connected { + color: #4e9a06; + } + + &.status-error { + color: #cc0000; + } +} + +@keyframes loader-ring { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +@keyframes fade-in { + 0% { + opacity: 0; + } + + 80% { + opacity: 0; + } + + 100% { + opacity: 1; + } +} + +@keyframes fade-in-out { + 0% { + opacity: 0; + } + + 50% { + opacity: 0.5; + } + + 100% { + opacity: 0; + } +} diff --git a/src/prompt.ts b/src/index.ts similarity index 86% rename from src/prompt.ts rename to src/index.ts index cb9d4c37..3716ac26 100644 --- a/src/prompt.ts +++ b/src/index.ts @@ -2,10 +2,7 @@ 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 { Main } from "./main/Main"; import { loadFonts } from "./util"; import * as DOMPurify from "dompurify"; diff --git a/src/keyframes.less b/src/keyframes.less deleted file mode 100644 index 7f237ea9..00000000 --- a/src/keyframes.less +++ /dev/null @@ -1,36 +0,0 @@ -@keyframes loader-ring { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } -} - -@keyframes fade-in { - 0% { - opacity: 0; - } - - 80% { - opacity: 0; - } - - 100% { - opacity: 1; - } -} - -@keyframes fade-in-out { - 0% { - opacity: 0; - } - - 50% { - opacity: 0.5; - } - - 100% { - opacity: 0; - } -} diff --git a/src/main.tsx b/src/main.tsx deleted file mode 100644 index 59ae2f09..00000000 --- a/src/main.tsx +++ /dev/null @@ -1,2298 +0,0 @@ -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 cn from "classnames"; -import { debounce, throttle } from "throttle-debounce"; -import { v4 as uuidv4 } from "uuid"; -import dayjs from "dayjs"; -import type { - SessionDataType, - LineType, - CmdDataType, - RemoteType, - RemoteStateType, - RemoteInstanceType, - RemotePtrType, - HistoryItem, - HistoryQueryOpts, - RemoteEditType, - ContextMenuOpts, - BookmarkType, - RenderModeType, - LineFactoryProps, -} from "./types"; -import type * as T from "./types"; -import localizedFormat from "dayjs/plugin/localizedFormat"; -import { - GlobalModel, - GlobalCommandRunner, - Session, - Cmd, - ScreenLines, - Screen, - riToRPtr, - TabColors, - RemoteColors, -} from "./model"; -import { - windowWidthToCols, - windowHeightToRows, - termHeightFromRows, - termWidthFromCols, - getMonoFontSize, -} from "./textmeasure"; -import { isModKeyPress, boundInt, sortAndFilterRemotes, makeExternLink, isBlank, hasNoModifiers } from "./util"; -import { BookmarksView } from "./bookmarks"; -import { WebShareView } from "./webshare-client-view"; -import { HistoryView } from "./history"; -import { Line, Prompt } from "./linecomps"; -import { ScreenSettingsModal, SessionSettingsModal, LineSettingsModal, ClientSettingsModal } from "./settings"; -import { RemotesModal } from "./remotes"; -import { renderCmdText, RemoteStatusLight, Markdown } from "./elements"; -import { LinesView } from "./linesview"; -import { TosModal } from "./modals"; - -dayjs.extend(localizedFormat); - -const RemotePtyRows = 8; -const RemotePtyCols = 80; -const PasswordUnchangedSentinel = "--unchanged--"; -const LinesVisiblePadding = 500; -const TDots = "⋮"; - -type OV = mobx.IObservableValue; -type OArr = mobx.IObservableArray; -type OMap = mobx.ObservableMap; - -type VisType = "visible" | ""; - -type InterObsValue = { - sessionid: string; - lineid: string; - visible: mobx.IObservableValue; - timeoutid?: any; -}; - -function scrollDiv(div: any, amt: number) { - if (div == null) { - return; - } - let newScrollTop = div.scrollTop + amt; - if (newScrollTop < 0) { - newScrollTop = 0; - } - div.scrollTo({ top: newScrollTop, behavior: "smooth" }); -} - -function truncateWithTDots(str: string, maxLen: number): string { - if (str == null) { - return null; - } - if (str.length <= maxLen) { - return str; - } - return str.slice(0, maxLen - 1) + TDots; -} - -function pageSize(div: any): number { - if (div == null) { - return 300; - } - let size = div.clientHeight; - if (size > 500) { - size = size - 100; - } else if (size > 200) { - size = size - 30; - } - return size; -} - -@mobxReact.observer -class TextAreaInput extends React.Component<{ onHeightChange: () => void }, {}> { - lastTab: boolean = false; - lastHistoryUpDown: boolean = false; - lastTabCurLine: mobx.IObservableValue = mobx.observable.box(null); - lastFocusType: string = null; - mainInputRef: React.RefObject; - historyInputRef: React.RefObject; - controlRef: React.RefObject; - lastHeight: number = 0; - - constructor(props) { - super(props); - this.mainInputRef = React.createRef(); - this.historyInputRef = React.createRef(); - this.controlRef = React.createRef(); - } - - setFocus(): void { - let inputModel = GlobalModel.inputModel; - if (inputModel.historyShow.get()) { - this.historyInputRef.current.focus(); - } else { - this.mainInputRef.current.focus(); - } - } - - getTextAreaMaxCols(): number { - let taElem = this.mainInputRef.current; - if (taElem == null) { - return 0; - } - let cs = window.getComputedStyle(taElem); - let padding = parseFloat(cs.paddingLeft) + parseFloat(cs.paddingRight); - let borders = parseFloat(cs.borderLeft) + parseFloat(cs.borderRight); - let contentWidth = taElem.clientWidth - padding - borders; - let fontSize = getMonoFontSize(parseInt(cs.fontSize)); - let maxCols = Math.floor(contentWidth / fontSize.width); - return maxCols; - } - - checkHeight(shouldFire: boolean): void { - let elem = this.controlRef.current; - if (elem == null) { - return; - } - let curHeight = elem.offsetHeight; - if (this.lastHeight == curHeight) { - return; - } - this.lastHeight = curHeight; - if (shouldFire && this.props.onHeightChange != null) { - this.props.onHeightChange(); - } - } - - componentDidMount() { - let activeScreen = GlobalModel.getActiveScreen(); - if (activeScreen != null) { - let focusType = activeScreen.focusType.get(); - if (focusType == "input") { - this.setFocus(); - } - this.lastFocusType = focusType; - } - this.checkHeight(false); - } - - componentDidUpdate() { - let activeScreen = GlobalModel.getActiveScreen(); - if (activeScreen != null) { - let focusType = activeScreen.focusType.get(); - if (this.lastFocusType != focusType && focusType == "input") { - this.setFocus(); - } - this.lastFocusType = focusType; - } - let inputModel = GlobalModel.inputModel; - if (inputModel.forceCursorPos.get() != null) { - if (this.mainInputRef.current != null) { - this.mainInputRef.current.selectionStart = inputModel.forceCursorPos.get(); - this.mainInputRef.current.selectionEnd = inputModel.forceCursorPos.get(); - } - mobx.action(() => inputModel.forceCursorPos.set(null))(); - } - if (inputModel.forceInputFocus) { - inputModel.forceInputFocus = false; - this.setFocus(); - } - this.checkHeight(true); - } - - getLinePos(elem: any): { numLines: number; linePos: number } { - let numLines = elem.value.split("\n").length; - let linePos = elem.value.substr(0, elem.selectionStart).split("\n").length; - return { numLines, linePos }; - } - - @mobx.action - @boundMethod - onKeyDown(e: any) { - mobx.action(() => { - if (isModKeyPress(e)) { - return; - } - let model = GlobalModel; - let inputModel = model.inputModel; - let win = model.getScreenLinesForActiveScreen(); - let ctrlMod = e.getModifierState("Control") || e.getModifierState("Meta") || e.getModifierState("Shift"); - let curLine = inputModel.getCurLine(); - - let lastTab = this.lastTab; - this.lastTab = e.code == "Tab"; - let lastHist = this.lastHistoryUpDown; - this.lastHistoryUpDown = false; - - if (e.code == "Tab") { - e.preventDefault(); - if (lastTab) { - GlobalModel.submitCommand( - "_compgen", - null, - [curLine], - { comppos: String(curLine.length), compshow: "1", nohist: "1" }, - true - ); - return; - } else { - GlobalModel.submitCommand( - "_compgen", - null, - [curLine], - { comppos: String(curLine.length), nohist: "1" }, - true - ); - return; - } - } - if (e.code == "Enter") { - e.preventDefault(); - if (!ctrlMod) { - if (GlobalModel.inputModel.isEmpty()) { - let activeWindow = GlobalModel.getScreenLinesForActiveScreen(); - let activeScreen = GlobalModel.getActiveScreen(); - if (activeScreen != null && activeWindow != null && activeWindow.lines.length > 0) { - activeScreen.setSelectedLine(0); - GlobalCommandRunner.screenSelectLine("E"); - } - return; - } else { - setTimeout(() => GlobalModel.inputModel.uiSubmitCommand(), 0); - return; - } - } - e.target.setRangeText("\n", e.target.selectionStart, e.target.selectionEnd, "end"); - GlobalModel.inputModel.setCurLine(e.target.value); - return; - } - if (e.code == "Escape") { - e.preventDefault(); - e.stopPropagation(); - let inputModel = GlobalModel.inputModel; - inputModel.toggleInfoMsg(); - if (inputModel.inputMode.get() != null) { - inputModel.resetInputMode(); - } - return; - } - if (e.code == "KeyE" && e.getModifierState("Meta")) { - e.preventDefault(); - e.stopPropagation(); - let inputModel = GlobalModel.inputModel; - inputModel.toggleExpandInput(); - } - if (e.code == "KeyC" && e.getModifierState("Control")) { - e.preventDefault(); - inputModel.resetInput(); - return; - } - if (e.code == "KeyU" && e.getModifierState("Control")) { - e.preventDefault(); - this.controlU(); - return; - } - if (e.code == "KeyP" && e.getModifierState("Control")) { - e.preventDefault(); - this.controlP(); - return; - } - if (e.code == "KeyN" && e.getModifierState("Control")) { - e.preventDefault(); - this.controlN(); - return; - } - if (e.code == "KeyW" && e.getModifierState("Control")) { - e.preventDefault(); - this.controlW(); - return; - } - if (e.code == "KeyY" && e.getModifierState("Control")) { - e.preventDefault(); - this.controlY(); - return; - } - if (e.code == "KeyR" && e.getModifierState("Control")) { - e.preventDefault(); - inputModel.openHistory(); - return; - } - if ((e.code == "ArrowUp" || e.code == "ArrowDown") && hasNoModifiers(e)) { - if (!inputModel.isHistoryLoaded()) { - if (e.code == "ArrowUp") { - this.lastHistoryUpDown = true; - inputModel.loadHistory(false, 1, "screen"); - } - return; - } - // invisible history movement - let linePos = this.getLinePos(e.target); - if (e.code == "ArrowUp") { - if (!lastHist && linePos.linePos > 1) { - // regular arrow - return; - } - e.preventDefault(); - inputModel.moveHistorySelection(1); - this.lastHistoryUpDown = true; - return; - } - if (e.code == "ArrowDown") { - if (!lastHist && linePos.linePos < linePos.numLines) { - // regular arrow - return; - } - e.preventDefault(); - inputModel.moveHistorySelection(-1); - this.lastHistoryUpDown = true; - return; - } - } - if (e.code == "PageUp" || e.code == "PageDown") { - e.preventDefault(); - let infoScroll = inputModel.hasScrollingInfoMsg(); - if (infoScroll) { - let div = document.querySelector(".cmd-input-info"); - let amt = pageSize(div); - scrollDiv(div, e.code == "PageUp" ? -amt : amt); - } - } - // console.log(e.code, e.keyCode, e.key, event.which, ctrlMod, e); - })(); - } - - @boundMethod - onChange(e: any) { - mobx.action(() => { - GlobalModel.inputModel.setCurLine(e.target.value); - })(); - } - - @boundMethod - onHistoryKeyDown(e: any) { - let inputModel = GlobalModel.inputModel; - if (e.code == "Escape") { - e.preventDefault(); - inputModel.resetHistory(); - return; - } - if (e.code == "Enter") { - e.preventDefault(); - inputModel.grabSelectedHistoryItem(); - return; - } - if (e.code == "KeyG" && e.getModifierState("Control")) { - e.preventDefault(); - inputModel.resetInput(); - return; - } - if (e.code == "KeyC" && e.getModifierState("Control")) { - e.preventDefault(); - inputModel.resetInput(); - return; - } - if ( - e.code == "KeyR" && - (e.getModifierState("Meta") || e.getModifierState("Control")) && - !e.getModifierState("Shift") - ) { - e.preventDefault(); - let opts = mobx.toJS(inputModel.historyQueryOpts.get()); - if (opts.limitRemote) { - opts.limitRemote = false; - opts.limitRemoteInstance = false; - } else { - opts.limitRemote = true; - opts.limitRemoteInstance = true; - } - inputModel.setHistoryQueryOpts(opts); - return; - } - if (e.code == "KeyS" && (e.getModifierState("Meta") || e.getModifierState("Control"))) { - e.preventDefault(); - let opts = mobx.toJS(inputModel.historyQueryOpts.get()); - let htype = opts.queryType; - if (htype == "screen") { - htype = "session"; - } else if (htype == "session") { - htype = "global"; - } else { - htype = "screen"; - } - inputModel.setHistoryType(htype); - return; - } - if (e.code == "Tab") { - e.preventDefault(); - return; - } - if (e.code == "ArrowUp" || e.code == "ArrowDown") { - e.preventDefault(); - inputModel.moveHistorySelection(e.code == "ArrowUp" ? 1 : -1); - return; - } - if (e.code == "PageUp" || e.code == "PageDown") { - e.preventDefault(); - inputModel.moveHistorySelection(e.code == "PageUp" ? 10 : -10); - return; - } - if (e.code == "KeyP" && e.getModifierState("Control")) { - e.preventDefault(); - inputModel.moveHistorySelection(1); - return; - } - if (e.code == "KeyN" && e.getModifierState("Control")) { - e.preventDefault(); - inputModel.moveHistorySelection(-1); - return; - } - } - - @boundMethod - controlU() { - if (this.mainInputRef.current == null) { - return; - } - let selStart = this.mainInputRef.current.selectionStart; - let value = this.mainInputRef.current.value; - if (selStart > value.length) { - return; - } - let cutValue = value.substr(0, selStart); - let restValue = value.substr(selStart); - let cmdLineUpdate = { cmdline: restValue, cursorpos: 0 }; - console.log("ss", selStart, value, "[" + cutValue + "]", "[" + restValue + "]"); - navigator.clipboard.writeText(cutValue); - GlobalModel.inputModel.updateCmdLine(cmdLineUpdate); - } - - @boundMethod - controlP() { - let inputModel = GlobalModel.inputModel; - if (!inputModel.isHistoryLoaded()) { - this.lastHistoryUpDown = true; - inputModel.loadHistory(false, 1, "screen"); - return; - } - inputModel.moveHistorySelection(1); - this.lastHistoryUpDown = true; - } - - @boundMethod - controlN() { - let inputModel = GlobalModel.inputModel; - inputModel.moveHistorySelection(-1); - this.lastHistoryUpDown = true; - } - - @boundMethod - controlW() { - if (this.mainInputRef.current == null) { - return; - } - let selStart = this.mainInputRef.current.selectionStart; - let value = this.mainInputRef.current.value; - if (selStart > value.length) { - return; - } - let cutSpot = selStart - 1; - let initial = true; - for (; cutSpot >= 0; cutSpot--) { - let ch = value[cutSpot]; - console.log(cutSpot, "[" + ch + "]"); - if (ch == " " && initial) { - continue; - } - initial = false; - if (ch == " ") { - cutSpot++; - break; - } - } - let cutValue = value.slice(cutSpot, selStart); - let prevValue = value.slice(0, cutSpot); - let restValue = value.slice(selStart); - let cmdLineUpdate = { cmdline: prevValue + restValue, cursorpos: prevValue.length }; - console.log( - "ss", - selStart, - value, - "prev[" + prevValue + "]", - "cut[" + cutValue + "]", - "rest[" + restValue + "]" - ); - console.log(" ", cmdLineUpdate); - navigator.clipboard.writeText(cutValue); - GlobalModel.inputModel.updateCmdLine(cmdLineUpdate); - } - - @boundMethod - controlY() { - if (this.mainInputRef.current == null) { - return; - } - let pastePromise = navigator.clipboard.readText(); - pastePromise.then((clipText) => { - clipText = clipText ?? ""; - let selStart = this.mainInputRef.current.selectionStart; - let selEnd = this.mainInputRef.current.selectionEnd; - let value = this.mainInputRef.current.value; - if (selStart > value.length || selEnd > value.length) { - return; - } - let newValue = value.substr(0, selStart) + clipText + value.substr(selEnd); - let cmdLineUpdate = { cmdline: newValue, cursorpos: selStart + clipText.length }; - GlobalModel.inputModel.updateCmdLine(cmdLineUpdate); - }); - } - - @boundMethod - handleHistoryInput(e: any) { - let inputModel = GlobalModel.inputModel; - mobx.action(() => { - let opts = mobx.toJS(inputModel.historyQueryOpts.get()); - opts.queryStr = e.target.value; - inputModel.setHistoryQueryOpts(opts); - })(); - } - - @boundMethod - handleMainFocus(e: any) { - let inputModel = GlobalModel.inputModel; - if (inputModel.historyShow.get()) { - e.preventDefault(); - if (this.historyInputRef.current != null) { - this.historyInputRef.current.focus(); - } - return; - } - inputModel.setPhysicalInputFocused(true); - } - - @boundMethod - handleMainBlur(e: any) { - if (document.activeElement == this.mainInputRef.current) { - return; - } - GlobalModel.inputModel.setPhysicalInputFocused(false); - } - - @boundMethod - handleHistoryFocus(e: any) { - let inputModel = GlobalModel.inputModel; - if (!inputModel.historyShow.get()) { - e.preventDefault(); - if (this.mainInputRef.current != null) { - this.mainInputRef.current.focus(); - } - return; - } - inputModel.setPhysicalInputFocused(true); - } - - @boundMethod - handleHistoryBlur(e: any) { - if (document.activeElement == this.historyInputRef.current) { - return; - } - GlobalModel.inputModel.setPhysicalInputFocused(false); - } - - render() { - let model = GlobalModel; - let inputModel = model.inputModel; - let curLine = inputModel.getCurLine(); - let fcp = inputModel.forceCursorPos.get(); // for reaction - let displayLines = 1; - let numLines = curLine.split("\n").length; - let maxCols = this.getTextAreaMaxCols(); - let longLine = false; - if (maxCols != 0 && curLine.length >= maxCols - 4) { - longLine = true; - } - if (numLines > 1 || longLine || inputModel.inputExpanded.get()) { - displayLines = 5; - } - let disabled = inputModel.historyShow.get(); - if (disabled) { - displayLines = 1; - } - let activeScreen = GlobalModel.getActiveScreen(); - if (activeScreen != null) { - activeScreen.focusType.get(); // for reaction - } - let computedHeight = displayLines * 24 + 14 + 2; // 24 = height of line, 14 = padding, 2 = border - return ( -
- - -
- ); - } -} - -@mobxReact.observer -class InfoMsg extends React.Component<{}, {}> { - getAfterSlash(s: string): string { - if (s.startsWith("^/")) { - return s.substr(1); - } - let slashIdx = s.lastIndexOf("/"); - if (slashIdx == s.length - 1) { - slashIdx = s.lastIndexOf("/", slashIdx - 1); - } - if (slashIdx == -1) { - return s; - } - return s.substr(slashIdx + 1); - } - - hasSpace(s: string): boolean { - return s.indexOf(" ") != -1; - } - - handleCompClick(s: string): void { - // TODO -> complete to this completion - } - - render() { - let model = GlobalModel; - let inputModel = model.inputModel; - let infoMsg = inputModel.infoMsg.get(); - let infoShow = inputModel.infoShow.get(); - let line: string = null; - let istr: string = null; - let idx: number = 0; - let titleStr = null; - let remoteEditKey = "inforemoteedit"; - if (infoMsg != null) { - titleStr = infoMsg.infotitle; - } - let activeScreen = model.getActiveScreen(); - return ( -
- -
- {titleStr} -
-
- -
- - - - {infoMsg.infomsg} -
-
- -
- started sharing screen at{" "} - - [link] - -
-
- -
- -
{line == "" ? " " : line}
-
-
-
- 0}> -
- -
this.handleCompClick(istr)} - key={idx} - className={cn( - "info-comp", - { "has-space": this.hasSpace(istr) }, - { "metacmd-comp": istr.startsWith("^") } - )} - > - {this.getAfterSlash(istr)} -
-
- -
- ... -
-
-
-
- -
- [error] {infoMsg.infoerror} -
-
-
- ); - } -} - -@mobxReact.observer -class HistoryInfo extends React.Component<{}, {}> { - lastClickHNum: string = null; - lastClickTs: number = 0; - containingText: mobx.IObservableValue = mobx.observable.box(""); - - componentDidMount() { - let inputModel = GlobalModel.inputModel; - let hitem = inputModel.getHistorySelectedItem(); - if (hitem == null) { - hitem = inputModel.getFirstHistoryItem(); - } - if (hitem != null) { - inputModel.scrollHistoryItemIntoView(hitem.historynum); - } - } - - @boundMethod - handleItemClick(hitem: HistoryItem) { - let inputModel = GlobalModel.inputModel; - let selItem = inputModel.getHistorySelectedItem(); - if (this.lastClickHNum == hitem.historynum && selItem != null && selItem.historynum == hitem.historynum) { - inputModel.grabSelectedHistoryItem(); - return; - } - inputModel.giveFocus(); - inputModel.setHistorySelectionNum(hitem.historynum); - let now = Date.now(); - this.lastClickHNum = hitem.historynum; - this.lastClickTs = now; - setTimeout(() => { - if (this.lastClickTs == now) { - this.lastClickHNum = null; - this.lastClickTs = 0; - } - }, 3000); - } - - renderRemote(hitem: HistoryItem): any { - if (hitem.remote == null || isBlank(hitem.remote.remoteid)) { - return sprintf("%-15s ", ""); - } - let r = GlobalModel.getRemote(hitem.remote.remoteid); - if (r == null) { - return sprintf("%-15s ", "???"); - } - let rname = ""; - if (!isBlank(r.remotealias)) { - rname = r.remotealias; - } else { - rname = r.remotecanonicalname; - } - if (!isBlank(hitem.remote.name)) { - rname = rname + ":" + hitem.remote.name; - } - let rtn = sprintf("%-15s ", "[" + truncateWithTDots(rname, 13) + "]"); - return rtn; - } - - renderHInfoText( - hitem: HistoryItem, - opts: HistoryQueryOpts, - isSelected: boolean, - snames: Record, - scrNames: Record - ): string { - let remoteStr = ""; - if (!opts.limitRemote) { - remoteStr = this.renderRemote(hitem); - } - let selectedStr = isSelected ? "*" : " "; - let lineNumStr = hitem.linenum > 0 ? "(" + hitem.linenum + ")" : ""; - if (isBlank(opts.queryType) || opts.queryType == "screen") { - return selectedStr + sprintf("%7s", lineNumStr) + " " + remoteStr; - } - if (opts.queryType == "session") { - let screenStr = ""; - if (!isBlank(hitem.screenid)) { - let scrName = scrNames[hitem.screenid]; - if (scrName != null) { - screenStr = "[" + truncateWithTDots(scrName, 15) + "]"; - } - } - return selectedStr + sprintf("%17s", screenStr) + sprintf("%7s", lineNumStr) + " " + remoteStr; - } - if (opts.queryType == "global") { - let sessionStr = ""; - if (!isBlank(hitem.sessionid)) { - let sessionName = snames[hitem.sessionid]; - if (sessionName != null) { - sessionStr = "#" + truncateWithTDots(sessionName, 15); - } - } - let screenStr = ""; - if (!isBlank(hitem.screenid)) { - let scrName = scrNames[hitem.screenid]; - if (scrName != null) { - screenStr = "[" + truncateWithTDots(scrName, 13) + "]"; - } - } - let ssStr = sessionStr + screenStr; - return ( - selectedStr + - sprintf("%15s ", sessionStr) + - " " + - sprintf("%15s", screenStr) + - sprintf("%7s", lineNumStr) + - " " + - remoteStr - ); - } - return "-"; - } - - renderHItem( - hitem: HistoryItem, - opts: HistoryQueryOpts, - isSelected: boolean, - snames: Record, - scrNames: Record - ): any { - let lines = hitem.cmdstr.split("\n"); - let line: string = ""; - let idx = 0; - let infoText = this.renderHInfoText(hitem, opts, isSelected, snames, scrNames); - let infoTextSpacer = sprintf("%" + infoText.length + "s", ""); - return ( -
this.handleItemClick(hitem)} - > -
- {infoText} {lines[0]} -
- -
- {infoTextSpacer} {line} -
-
-
- ); - } - - @boundMethod - handleClose() { - GlobalModel.inputModel.toggleInfoMsg(); - } - - render() { - let inputModel = GlobalModel.inputModel; - let idx: number = 0; - let selItem = inputModel.getHistorySelectedItem(); - let hitems = inputModel.getFilteredHistoryItems(); - hitems = hitems.slice().reverse(); - let hitem: HistoryItem = null; - let opts = inputModel.historyQueryOpts.get(); - let snames: Record = {}; - let scrNames: Record = {}; - if (opts.queryType == "global") { - scrNames = GlobalModel.getScreenNames(); - snames = GlobalModel.getSessionNames(); - } else if (opts.queryType == "session") { - scrNames = GlobalModel.getScreenNames(); - } - return ( -
-
-
history
-
-
[for {opts.queryType} ⌘S]
-
-
[containing '{opts.queryStr}']
-
-
[{opts.limitRemote ? "this" : "any"} remote ⌘R]
-
-
- (ESC) -
-
-
-
- [no history] - 0}> - - {this.renderHItem(hitem, opts, hitem == selItem, snames, scrNames)} - - -
-
- ); - } -} - -@mobxReact.observer -class CmdInput extends React.Component<{}, {}> { - cmdInputRef: React.RefObject = React.createRef(); - - @boundMethod - onInfoToggle(): void { - GlobalModel.inputModel.toggleInfoMsg(); - return; - } - - componentDidMount() { - this.updateCmdInputHeight(); - } - - updateCmdInputHeight() { - let elem = this.cmdInputRef.current; - if (elem == null) { - return; - } - let height = elem.offsetHeight; - if (height == GlobalModel.inputModel.cmdInputHeight) { - return; - } - mobx.action(() => { - GlobalModel.inputModel.cmdInputHeight.set(height); - })(); - } - - componentDidUpdate(prevProps, prevState, snapshot: {}): void { - this.updateCmdInputHeight(); - } - - @boundMethod - handleInnerHeightUpdate(): void { - this.updateCmdInputHeight(); - } - - @boundMethod - clickFocusInputHint(): void { - GlobalModel.inputModel.giveFocus(); - } - - @boundMethod - clickHistoryHint(e: any): void { - e.preventDefault(); - e.stopPropagation(); - - let inputModel = GlobalModel.inputModel; - if (inputModel.historyShow.get()) { - inputModel.resetHistory(); - } else { - inputModel.openHistory(); - } - } - - @boundMethod - clickConnectRemote(remoteId: string): void { - GlobalCommandRunner.connectRemote(remoteId); - } - - render() { - let model = GlobalModel; - let inputModel = model.inputModel; - let screen = GlobalModel.getActiveScreen(); - let ri: RemoteInstanceType = null; - let rptr: RemotePtrType = null; - if (screen != null) { - ri = screen.getCurRemoteInstance(); - rptr = screen.curRemote.get(); - } - let remote: RemoteType = null; - let feState: Record = null; - if (ri != null) { - remote = GlobalModel.getRemote(ri.remoteid); - feState = ri.festate; - } - let infoShow = inputModel.infoShow.get(); - let historyShow = !infoShow && inputModel.historyShow.get(); - let infoMsg = inputModel.infoMsg.get(); - let hasInfo = infoMsg != null; - let focusVal = inputModel.physicalInputFocused.get(); - let inputMode: string = inputModel.inputMode.get(); - let textAreaInputKey = screen == null ? "null" : screen.screenId; - return ( -
-
-
- - - - - - -
- -
- -
- - -
- WARNING:  - [{GlobalModel.resolveRemoteIdToFullRef(remote.remoteid)}] -  is {remote.status} - -
this.clickConnectRemote(remote.remoteid)} - > - connect now -
-
-
-
-
-
- -
-
-
- -
-
{inputMode}
-
-
- -
-
- - - -
-
-
-
- {inputModel.inputExpanded.get() ? "shrink" : "expand"} input ({renderCmdText("E")}) -
- -
- focus input ({renderCmdText("I")}) -
-
- -
- {" "} - {historyShow ? "close history (esc)" : "show history (ctrl-r)"} -
-
-
-
-
- ); - } -} - -// screen is not null -@mobxReact.observer -class ScreenWindowView extends React.Component<{ screen: Screen }, {}> { - rszObs: any; - windowViewRef: React.RefObject; - - width: mobx.IObservableValue = mobx.observable.box(0, { name: "sw-view-width" }); - height: mobx.IObservableValue = mobx.observable.box(0, { name: "sw-view-height" }); - setSize_debounced: (width: number, height: number) => void; - - renderMode: OV = mobx.observable.box("normal", { name: "renderMode" }); - shareCopied: OV = mobx.observable.box(false, { name: "sw-shareCopied" }); - - constructor(props: any) { - super(props); - this.setSize_debounced = debounce(1000, this.setSize.bind(this)); - this.windowViewRef = React.createRef(); - } - - setSize(width: number, height: number): void { - let { screen } = this.props; - if (screen == null) { - return; - } - if (width == null || height == null || width == 0 || height == 0) { - return; - } - mobx.action(() => { - this.width.set(width); - this.height.set(height); - screen.screenSizeCallback({ height: height, width: width }); - })(); - } - - componentDidMount() { - let wvElem = this.windowViewRef.current; - if (wvElem != null) { - let width = wvElem.offsetWidth; - let height = wvElem.offsetHeight; - this.setSize(width, height); - this.rszObs = new ResizeObserver(this.handleResize.bind(this)); - this.rszObs.observe(wvElem); - } - } - - componentWillUnmount() { - if (this.rszObs) { - this.rszObs.disconnect(); - } - } - - handleResize(entries: any) { - if (entries.length == 0) { - return; - } - let entry = entries[0]; - let width = entry.target.offsetWidth; - let height = entry.target.offsetHeight; - mobx.action(() => { - this.setSize_debounced(width, height); - })(); - } - - getScreenLines(): ScreenLines { - let { screen } = this.props; - let win = GlobalModel.getScreenLinesById(screen.screenId); - if (win == null) { - win = GlobalModel.loadScreenLines(screen.screenId); - } - return win; - } - - getWindowViewStyle(): any { - return { position: "absolute", width: "100%", height: "100%", overflowX: "hidden" }; - } - - @boundMethod - toggleRenderMode() { - let renderMode = this.renderMode.get(); - mobx.action(() => { - this.renderMode.set(renderMode == "normal" ? "collapsed" : "normal"); - })(); - } - - renderError(message: string, fade: boolean) { - let { screen } = this.props; - return ( -
-
-
-
{message}
-
-
- ); - } - - @boundMethod - copyShareLink(): void { - let { screen } = this.props; - let shareLink = screen.getWebShareUrl(); - if (shareLink == null) { - return; - } - navigator.clipboard.writeText(shareLink); - mobx.action(() => { - this.shareCopied.set(true); - })(); - setTimeout(() => { - mobx.action(() => { - this.shareCopied.set(false); - })(); - }, 600); - } - - @boundMethod - openScreenSettings(): void { - let { screen } = this.props; - mobx.action(() => { - GlobalModel.screenSettingsModal.set({ sessionId: screen.sessionId, screenId: screen.screenId }); - })(); - } - - @boundMethod - buildLineComponent(lineProps: LineFactoryProps): JSX.Element { - let { screen } = this.props; - let { line, ...restProps } = lineProps; - let realLine: LineType = line as LineType; - return ; - } - - render() { - let { screen } = this.props; - let win = this.getScreenLines(); - if (win == null || !win.loaded.get()) { - return this.renderError("...", true); - } - if (win.loadError.get() != null) { - return this.renderError(sprintf("(%s)", win.loadError.get()), false); - } - if (this.width.get() == 0) { - return this.renderError("", false); - } - let cdata = GlobalModel.clientData.get(); - if (cdata == null) { - return this.renderError("loading client data", true); - } - let idx = 0; - let line: LineType = null; - let session = GlobalModel.getSessionById(screen.sessionId); - let isActive = screen.isActive(); - let selectedLine = screen.getSelectedLine(); - let lines = win.getNonArchivedLines(); - let renderMode = this.renderMode.get(); - return ( -
-
-
- - - - - - -
-
- -
- -
- -
- web shared -
-
-
- copy link - - - -
-
- open settings - - - -
-
-
-
- 0}> - - - -
-
- - [session="{session.name.get()}" screen="{screen.name.get()}"] - -
-
-
-
- ); - } -} - -@mobxReact.observer -class ScreenView extends React.Component<{ screen: Screen }, {}> { - render() { - let { screen } = this.props; - if (screen == null) { - return
(no screen found)
; - } - let fontSize = GlobalModel.termFontSize.get(); - return ( -
- -
- ); - } -} - -@mobxReact.observer -class ScreenTabs extends React.Component<{ session: Session }, {}> { - tabsRef: React.RefObject = React.createRef(); - lastActiveScreenId: string = null; - scrolling: OV = mobx.observable.box(false, { name: "screentabs-scrolling" }); - - stopScrolling_debounced: () => void; - - constructor(props: any) { - super(props); - this.stopScrolling_debounced = debounce(1500, this.stopScrolling.bind(this)); - } - - @boundMethod - handleNewScreen() { - let { session } = this.props; - GlobalCommandRunner.createNewScreen(); - } - - @boundMethod - handleSwitchScreen(screenId: string) { - let { session } = this.props; - if (session == null) { - return; - } - if (session.activeScreenId.get() == screenId) { - return; - } - let screen = session.getScreenById(screenId); - if (screen == null) { - return; - } - GlobalCommandRunner.switchScreen(screenId); - } - - componentDidMount(): void { - this.componentDidUpdate(); - } - - componentDidUpdate(): void { - let { session } = this.props; - let activeScreenId = session.activeScreenId.get(); - if (activeScreenId != this.lastActiveScreenId && this.tabsRef.current) { - let tabElem = this.tabsRef.current.querySelector( - sprintf('.screen-tab[data-screenid="%s"]', activeScreenId) - ); - if (tabElem != null) { - tabElem.scrollIntoView(); - } - } - this.lastActiveScreenId = activeScreenId; - } - - stopScrolling(): void { - mobx.action(() => { - this.scrolling.set(false); - })(); - } - - @boundMethod - handleScroll() { - if (!this.scrolling.get()) { - mobx.action(() => { - this.scrolling.set(true); - })(); - } - this.stopScrolling_debounced(); - } - - @boundMethod - openScreenSettings(e: any, screen: Screen): void { - e.preventDefault(); - e.stopPropagation(); - mobx.action(() => { - GlobalModel.screenSettingsModal.set({ sessionId: screen.sessionId, screenId: screen.screenId }); - })(); - } - - renderTab(screen: Screen, activeScreenId: string, index: number): any { - let tabIndex = null; - if (index + 1 <= 9) { - tabIndex =
{renderCmdText(String(index + 1))}
; - } - let settings = ( -
this.openScreenSettings(e, screen)} title="Settings" className="tab-gear"> - -
- ); - let archived = screen.archived.get() ? ( - - ) : null; - - let webShared = screen.isWebShared() ? ( - - ) : null; - return ( -
this.handleSwitchScreen(screen.screenId)} - onContextMenu={(event) => this.openScreenSettings(event, screen)} - > -
- {archived} - {webShared} - {screen.name.get()} -
- {tabIndex} - {settings} -
- ); - } - - render() { - let { session } = this.props; - if (session == null) { - return null; - } - let screen: Screen = null; - let index = 0; - let showingScreens = []; - let activeScreenId = session.activeScreenId.get(); - let screens = GlobalModel.getSessionScreens(session.sessionId); - for (let screen of screens) { - if (!screen.archived.get() || activeScreenId == screen.screenId) { - showingScreens.push(screen); - } - } - showingScreens.sort((a, b) => { - let aidx = a.screenIdx.get(); - let bidx = b.screenIdx.get(); - if (aidx < bidx) { - return -1; - } - if (aidx > bidx) { - return 1; - } - return 0; - }); - return ( -
-
- - {this.renderTab(screen, activeScreenId, index)} - -
- -
-
-
-
move left {renderCmdText("[")}
-
move right {renderCmdText("]")}
-
new tab {renderCmdText("T")}
-
-
- ); - } -} - -@mobxReact.observer -class SessionView extends React.Component<{}, {}> { - render() { - let model = GlobalModel; - let session = model.getActiveSession(); - if (session == null) { - return
(no active session)
; - } - let activeScreen = session.getActiveScreen(); - let cmdInputHeight = model.inputModel.cmdInputHeight.get(); - if (cmdInputHeight == 0) { - cmdInputHeight = 110; - } - let isHidden = GlobalModel.activeMainView.get() != "session"; - return ( -
- - -
- -
- ); - } -} - -@mobxReact.observer -class MainSideBar extends React.Component<{}, {}> { - collapsed: mobx.IObservableValue = mobx.observable.box(false); - - @boundMethod - toggleCollapsed() { - mobx.action(() => { - this.collapsed.set(!this.collapsed.get()); - })(); - } - - handleSessionClick(sessionId: string) { - GlobalCommandRunner.switchSession(sessionId); - } - - handleNewSession() { - GlobalCommandRunner.createNewSession(); - } - - handleNewSharedSession() { - GlobalCommandRunner.openSharedSession(); - } - - clickLinks() { - mobx.action(() => { - GlobalModel.showLinks.set(!GlobalModel.showLinks.get()); - })(); - } - - remoteDisplayName(remote: RemoteType): any { - if (!isBlank(remote.remotealias)) { - return ( - <> - {remote.remotealias} - {remote.remotecanonicalname} - - ); - } - return {remote.remotecanonicalname}; - } - - clickRemote(remote: RemoteType) { - GlobalCommandRunner.showRemote(remote.remoteid); - } - - @boundMethod - handleAddRemote(): void { - GlobalCommandRunner.openCreateRemote(); - } - - @boundMethod - handleHistoryClick(): void { - if (GlobalModel.activeMainView.get() == "history") { - mobx.action(() => { - GlobalModel.activeMainView.set("session"); - })(); - return; - } - GlobalModel.historyViewModel.reSearch(); - } - - @boundMethod - handlePlaybookClick(): void { - console.log("playbook click"); - return; - } - - @boundMethod - handleBookmarksClick(): void { - if (GlobalModel.activeMainView.get() == "bookmarks") { - GlobalModel.showSessionView(); - return; - } - GlobalCommandRunner.bookmarksView(); - } - - @boundMethod - handleWebSharingClick(): void { - if (GlobalModel.activeMainView.get() == "webshare") { - GlobalModel.showSessionView(); - return; - } - GlobalModel.showWebShareView(); - } - - @boundMethod - handleWelcomeClick(): void { - mobx.action(() => { - GlobalModel.welcomeModalOpen.set(true); - })(); - } - - @boundMethod - handleSettingsClick(): void { - mobx.action(() => { - GlobalModel.clientSettingsModal.set(true); - })(); - } - - @boundMethod - handleConnectionsClick(): void { - GlobalModel.remotesModalModel.openModal(); - } - - @boundMethod - openSessionSettings(e: any, session: Session): void { - e.preventDefault(); - e.stopPropagation(); - mobx.action(() => { - GlobalModel.sessionSettingsModal.set(session.sessionId); - })(); - } - - render() { - let model = GlobalModel; - let activeSessionId = model.activeSessionId.get(); - let activeWindow = model.getScreenLinesForActiveScreen(); - let activeScreen = model.getActiveScreen(); - let activeRemoteId: string = null; - if (activeScreen != null) { - let rptr = activeScreen.curRemote.get(); - if (rptr != null && !isBlank(rptr.remoteid)) { - activeRemoteId = rptr.remoteid; - } - } - let session: Session = null; - let remotes = model.remotes ?? []; - let remote: RemoteType = null; - let idx: number = 0; - remotes = sortAndFilterRemotes(remotes); - let sessionList = []; - for (let session of model.sessionList) { - if (!session.archived.get() || session.sessionId == activeSessionId) { - sessionList.push(session); - } - } - let isCollapsed = this.collapsed.get(); - let mainView = GlobalModel.activeMainView.get(); - let activePlaybookId: string = null; - return ( -
-
-

- {isCollapsed ? "[p]" : "[prompt]"} -

-
-
-
- - - - - - -
-
-
-

Sessions

- - - -

Playbooks

- -
- -
- focus={activeScreen.focusType.get()} -
- sline={activeScreen.getSelectedLine()} -
- termfocus={activeScreen.termLineNumFocus.get()} -
-
-
- - -

- this.clickLinks()}> - LINKS{" "} - - -

- -

- Connections -

- -
-
-
- ); - } -} - -@mobxReact.observer -class DisconnectedModal extends React.Component<{}, {}> { - logRef: any = React.createRef(); - showLog: mobx.IObservableValue = mobx.observable.box(false); - - @boundMethod - restartServer() { - GlobalModel.restartLocalServer(); - } - - @boundMethod - tryReconnect() { - GlobalModel.ws.connectNow("manual"); - } - - componentDidMount() { - if (this.logRef.current != null) { - this.logRef.current.scrollTop = this.logRef.current.scrollHeight; - } - } - - componentDidUpdate() { - if (this.logRef.current != null) { - this.logRef.current.scrollTop = this.logRef.current.scrollHeight; - } - } - - @boundMethod - handleShowLog(): void { - mobx.action(() => { - this.showLog.set(!this.showLog.get()); - })(); - } - - render() { - let model = GlobalModel; - let logLine: string = null; - let idx: number = 0; - return ( -
-
-
-
-
Prompt Client Disconnected
-
- -
-
- -
- {logLine} -
-
-
-
-
-
-
- - Show Log - - - Hide Log - -
-
- - -
-
-
- ); - } -} - -@mobxReact.observer -class ClientStopModal extends React.Component<{}, {}> { - @boundMethod - refreshClient() { - GlobalModel.refreshClient(); - } - - render() { - let model = GlobalModel; - let cdata = model.clientData.get(); - let title = "Client Not Ready"; - return ( -
-
-
-
-
[prompt] {title}
-
-
- -
Cannot get client data.
-
-
-
- -
-
-
- ); - } -} - -@mobxReact.observer -class LoadingSpinner extends React.Component<{}, {}> { - render() { - return ( -
-
-
-
-
-
- ); - } -} - -@mobxReact.observer -class AlertModal extends React.Component<{}, {}> { - @boundMethod - closeModal(): void { - GlobalModel.cancelAlert(); - } - - @boundMethod - handleOK(): void { - GlobalModel.confirmAlert(); - } - - render() { - let message = GlobalModel.alertMessage.get(); - if (message == null) { - return null; - } - let title = message.title ?? (message.confirm ? "Confirm" : "Alert"); - let isConfirm = message.confirm; - return ( -
-
-
-
-

- {title} -

-
- -
-
- - - - -
-

{message.message}

-
-
-
- -
- Cancel -
-
- OK -
-
- -
- OK -
-
-
-
-
- ); - } -} - -@mobxReact.observer -class WelcomeModal extends React.Component<{}, {}> { - totalPages: number = 3; - pageNum: OV = mobx.observable.box(1, { name: "welcome-pagenum" }); - - @boundMethod - closeModal(): void { - mobx.action(() => { - GlobalModel.welcomeModalOpen.set(false); - })(); - } - - @boundMethod - goNext(): void { - mobx.action(() => { - this.pageNum.set(this.pageNum.get() + 1); - })(); - } - - @boundMethod - goPrev(): void { - mobx.action(() => { - this.pageNum.set(this.pageNum.get() - 1); - })(); - } - - renderDot(num: number): any { - if (num == this.pageNum.get()) { - return ; - } - return ; - } - - renderDots(): any { - let elems: any = []; - for (let i = 1; i <= this.totalPages; i++) { - let elem = this.renderDot(i); - elems.push(elem); - } - return elems; - } - - render() { - let pageNum = this.pageNum.get(); - return ( -
-
-
-
-
welcome to [prompt]
-
- -
-
-
-

- Prompt is a new terminal to help save you time and keep your command-line life organized. - Here's a couple quick tips to get your started! -

-
-
- 1}> - - - -
- -
-
{this.renderDots()}
-
- - - - - - -
-
-
- ); - } -} - -@mobxReact.observer -class Main extends React.Component<{}, {}> { - dcWait: OV = mobx.observable.box(false, { name: "dcWait" }); - - constructor(props: any) { - super(props); - } - - @boundMethod - handleContextMenu(e: any) { - let isInNonTermInput = false; - let activeElem = document.activeElement; - if (activeElem != null && activeElem.nodeName == "TEXTAREA") { - if (!activeElem.classList.contains("xterm-helper-textarea")) { - isInNonTermInput = true; - } - } - if (activeElem != null && activeElem.nodeName == "INPUT" && activeElem.getAttribute("type") == "text") { - isInNonTermInput = true; - } - let opts: ContextMenuOpts = {}; - if (isInNonTermInput) { - opts.showCut = true; - } - let sel = window.getSelection(); - if (!isBlank(sel.toString())) { - GlobalModel.contextEditMenu(e, opts); - } else { - if (isInNonTermInput) { - GlobalModel.contextEditMenu(e, opts); - } - } - } - - @boundMethod - updateDcWait(val: boolean): void { - mobx.action(() => { - this.dcWait.set(val); - })(); - } - - render() { - let screenSettingsModal = GlobalModel.screenSettingsModal.get(); - let sessionSettingsModal = GlobalModel.sessionSettingsModal.get(); - let lineSettingsModal = GlobalModel.lineSettingsModal.get(); - let clientSettingsModal = GlobalModel.clientSettingsModal.get(); - let remotesModal = GlobalModel.remotesModalModel.isOpen(); - let disconnected = !GlobalModel.ws.open.get() || !GlobalModel.localServerRunning.get(); - let hasClientStop = GlobalModel.getHasClientStop(); - let dcWait = this.dcWait.get(); - if (disconnected || hasClientStop) { - if (!dcWait) { - setTimeout(() => this.updateDcWait(true), 1500); - } - return ( -
-
- -
-
- - - - - - - - -
- ); - } - if (dcWait) { - setTimeout(() => this.updateDcWait(false), 0); - } - return ( -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - -
- ); - } -} - -export { Main }; diff --git a/src/main/Main.tsx b/src/main/Main.tsx new file mode 100644 index 00000000..9fb43c25 --- /dev/null +++ b/src/main/Main.tsx @@ -0,0 +1,140 @@ +import * as React from "react"; +import * as mobxReact from "mobx-react"; +import * as mobx from "mobx"; +import { boundMethod } from "autobind-decorator"; +import { If } from "tsx-control-statements/components"; +import dayjs from "dayjs"; +import type { ContextMenuOpts } from "../types"; +import localizedFormat from "dayjs/plugin/localizedFormat"; +import { GlobalModel } from "../model"; +import { isBlank } from "../util"; +import { BookmarksView } from "./bookmarks/bookmarks"; +import { WebShareView } from "../webshare/webshare-client-view"; +import { HistoryView } from "./history/history"; +import { ScreenSettingsModal, SessionSettingsModal, LineSettingsModal, ClientSettingsModal } from "./modals/settings"; +import { RemotesModal } from "../remotes/remotes"; +import { TosModal } from "./modals/Modals"; + +import { SessionView } from "./sessionview/SessionView"; +import { MainSideBar } from "./sidebar/MainSideBar"; +import { DisconnectedModal, ClientStopModal, AlertModal, WelcomeModal } from "./modals/Modals"; + +dayjs.extend(localizedFormat); + +type OV = mobx.IObservableValue; + +@mobxReact.observer +class Main extends React.Component<{}, {}> { + dcWait: OV = mobx.observable.box(false, { name: "dcWait" }); + + constructor(props: any) { + super(props); + } + + @boundMethod + handleContextMenu(e: any) { + let isInNonTermInput = false; + let activeElem = document.activeElement; + if (activeElem != null && activeElem.nodeName == "TEXTAREA") { + if (!activeElem.classList.contains("xterm-helper-textarea")) { + isInNonTermInput = true; + } + } + if (activeElem != null && activeElem.nodeName == "INPUT" && activeElem.getAttribute("type") == "text") { + isInNonTermInput = true; + } + let opts: ContextMenuOpts = {}; + if (isInNonTermInput) { + opts.showCut = true; + } + let sel = window.getSelection(); + if (!isBlank(sel.toString())) { + GlobalModel.contextEditMenu(e, opts); + } else { + if (isInNonTermInput) { + GlobalModel.contextEditMenu(e, opts); + } + } + } + + @boundMethod + updateDcWait(val: boolean): void { + mobx.action(() => { + this.dcWait.set(val); + })(); + } + + render() { + let screenSettingsModal = GlobalModel.screenSettingsModal.get(); + let sessionSettingsModal = GlobalModel.sessionSettingsModal.get(); + let lineSettingsModal = GlobalModel.lineSettingsModal.get(); + let clientSettingsModal = GlobalModel.clientSettingsModal.get(); + let remotesModal = GlobalModel.remotesModalModel.isOpen(); + let disconnected = !GlobalModel.ws.open.get() || !GlobalModel.localServerRunning.get(); + let hasClientStop = GlobalModel.getHasClientStop(); + let dcWait = this.dcWait.get(); + if (disconnected || hasClientStop) { + if (!dcWait) { + setTimeout(() => this.updateDcWait(true), 1500); + } + return ( +
+
+ +
+
+ + + + + + + + +
+ ); + } + if (dcWait) { + setTimeout(() => this.updateDcWait(false), 0); + } + return ( +
+
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ ); + } +} + +export { Main }; diff --git a/src/main/bookmarks/bookmarks.less b/src/main/bookmarks/bookmarks.less new file mode 100644 index 00000000..a9546ebb --- /dev/null +++ b/src/main/bookmarks/bookmarks.less @@ -0,0 +1,99 @@ +@import "../../index.less"; + +.bookmarks-view { + .bookmarks-list { + color: white; + margin: 4px 10px 5px 5px; + + .no-bookmarks { + color: @term-white; + padding: 30px 10px 35px 10px; + border-bottom: 1px solid white; + } + } + + .bookmark { + border-bottom: 1px solid #777; + padding: 4px 5px 8px 15px; + margin-bottom: 4px; + position: relative; + display: flex; + flex-direction: row; + max-width: 100%; + + .focus-indicator { + top: 0; + height: calc(100% - 4px); + } + + &.is-editing { + padding-top: 8px; + padding-bottom: 12px; + } + + &.pending-delete { + background-color: #522; + } + + .bookmark-content, + .bookmark-edit { + flex-grow: 1; + max-width: calc(100% - 50px); + } + + label { + color: white; + margin-bottom: 4px; + } + + textarea { + width: 80%; + min-width: 50%; + color: white; + background-color: black; + + &.mono-font { + .mono-font(12px); + } + } + + .bookmark-id-div { + display: none; + position: absolute; + color: #666; + right: 5px; + bottom: 2px; + .mono-font(8px); + } + + &:hover .bookmark-id-div { + display: block; + } + + .bookmark-controls { + display: flex; + flex-direction: row; + font-size: 16px; + visibility: hidden; + color: @term-white; + + .bookmark-control:first-child { + margin-left: 0; + } + + .bookmark-control { + margin-left: 10px; + cursor: pointer; + padding: 2px; + + &:hover { + color: @term-bright-white; + } + } + } + + &:hover .bookmark-controls { + visibility: visible; + } + } +} diff --git a/src/bookmarks.tsx b/src/main/bookmarks/bookmarks.tsx similarity index 96% rename from src/bookmarks.tsx rename to src/main/bookmarks/bookmarks.tsx index af7b2ae2..a8b09780 100644 --- a/src/bookmarks.tsx +++ b/src/main/bookmarks/bookmarks.tsx @@ -1,15 +1,14 @@ 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 dayjs from "dayjs"; -import { If, For, When, Otherwise, Choose } from "tsx-control-statements/components"; +import { If, For } 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 } from "../../model"; +import { CmdStrCode, Markdown } from "../../common/common"; + +import "./bookmarks.less"; @mobxReact.observer class Bookmark extends React.Component<{ bookmark: BookmarkType }, {}> { diff --git a/src/views.less b/src/main/history/history.less similarity index 68% rename from src/views.less rename to src/main/history/history.less index 48ce27ee..316136f0 100644 --- a/src/views.less +++ b/src/main/history/history.less @@ -1,57 +1,4 @@ -.alt-view { - background-color: #111; - overflow-y: auto; - flex-grow: 1; - - .alt-title { - margin: 20px 10px 0px 5px; - padding-left: 10px; - padding-bottom: 12px; - .mono-font(1.5rem); - color: @term-bright-white; - border-bottom: 1px solid white; - } - - .alt-list { - color: white; - margin: 4px 10px 5px 5px; - border-bottom: 1px solid white; - } - - .no-content { - color: @term-white; - padding: 30px 10px 35px 10px; - border-bottom: 1px solid white; - } - - .close-button { - position: absolute; - padding: 4px; - font-size: 24px; - color: #aaa; - right: 15px; - top: 18px; - cursor: pointer; - - &:hover { - color: #fff; - } - } - - .alt-help { - color: @term-white; - margin-top: 20px; - display: flex; - flex-direction: row; - align-items: center; - font-size: 12px; - margin-bottom: 10px; - - .help-entry { - margin-left: 20px; - } - } -} +@import "../../index.less"; .history-view { color: #ccc; @@ -418,140 +365,3 @@ } } } - -.webshare-view { - .webshare-item { - padding: 4px 5px 8px 15px; - margin-bottom: 4px; - border-top: 1px solid white; - display: flex; - flex-direction: row; - position: relative; - min-height: 55px; - align-items: center; - - &:first-child { - border-top: 0; - } - - .webshare-vic { - width: 200px; - - .webshare-vic-link { - cursor: pointer; - - &:hover { - text-decoration: underline; - } - } - } - - .actions { - display: flex; - flex-direction: row; - gap: 10px; - } - - &:hover { - background-color: #222; - } - } -} - -.bookmarks-view { - .bookmarks-list { - color: white; - margin: 4px 10px 5px 5px; - - .no-bookmarks { - color: @term-white; - padding: 30px 10px 35px 10px; - border-bottom: 1px solid white; - } - } - - .bookmark { - border-bottom: 1px solid #777; - padding: 4px 5px 8px 15px; - margin-bottom: 4px; - position: relative; - display: flex; - flex-direction: row; - max-width: 100%; - - .focus-indicator { - top: 0; - height: calc(100% - 4px); - } - - &.is-editing { - padding-top: 8px; - padding-bottom: 12px; - } - - &.pending-delete { - background-color: #522; - } - - .bookmark-content, - .bookmark-edit { - flex-grow: 1; - max-width: calc(100% - 50px); - } - - label { - color: white; - margin-bottom: 4px; - } - - textarea { - width: 80%; - min-width: 50%; - color: white; - background-color: black; - - &.mono-font { - .mono-font(12px); - } - } - - .bookmark-id-div { - display: none; - position: absolute; - color: #666; - right: 5px; - bottom: 2px; - .mono-font(8px); - } - - &:hover .bookmark-id-div { - display: block; - } - - .bookmark-controls { - display: flex; - flex-direction: row; - font-size: 16px; - visibility: hidden; - color: @term-white; - - .bookmark-control:first-child { - margin-left: 0; - } - - .bookmark-control { - margin-left: 10px; - cursor: pointer; - padding: 2px; - - &:hover { - color: @term-bright-white; - } - } - } - - &:hover .bookmark-controls { - visibility: visible; - } - } -} diff --git a/src/history.tsx b/src/main/history/history.tsx similarity index 99% rename from src/history.tsx rename to src/main/history/history.tsx index e4216c11..f31b4a8f 100644 --- a/src/history.tsx +++ b/src/main/history/history.tsx @@ -5,13 +5,15 @@ import { If, For, When, Otherwise, Choose } from "tsx-control-statements/compone 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 { Line } from "../line/linecomps"; +import { CmdStrCode } from "../../common/common"; + +import "./history.less"; dayjs.extend(customParseFormat); dayjs.extend(localizedFormat); diff --git a/src/linecomps.tsx b/src/main/line/linecomps.tsx similarity index 98% rename from src/linecomps.tsx rename to src/main/line/linecomps.tsx index e0e7a56b..20584928 100644 --- a/src/linecomps.tsx +++ b/src/main/line/linecomps.tsx @@ -5,9 +5,9 @@ import { sprintf } from "sprintf-js"; import { boundMethod } from "autobind-decorator"; import dayjs from "dayjs"; import localizedFormat from "dayjs/plugin/localizedFormat"; -import { If, For, When, Otherwise, Choose } from "tsx-control-statements/components"; -import { GlobalModel, GlobalCommandRunner, Session, Cmd, ScreenLines, Screen, getTermPtyData } from "./model"; -import { windowWidthToCols, windowHeightToRows, termHeightFromRows, termWidthFromCols } from "./textmeasure"; +import { If } from "tsx-control-statements/components"; +import { GlobalModel, GlobalCommandRunner, Cmd, getTermPtyData } from "../../model"; +import { termHeightFromRows } from "../../textmeasure"; import type { LineType, CmdDataType, @@ -21,18 +21,18 @@ import type { LineHeightChangeCallbackType, RendererModelInitializeParams, RendererModel, -} from "./types"; +} from "../../types"; import cn from "classnames"; -import { TermWrap } from "./term"; -import type { LineContainerModel } from "./model"; -import { renderCmdText } from "./elements"; -import { SimpleBlobRendererModel, SimpleBlobRenderer } from "./simplerenderer"; -import { FullRenderer } from "./fullrenderer"; -import { isBlank } from "./util"; -import { PluginModel } from "./plugins"; -import { PtyDataBuffer } from "./ptydata"; +import type { LineContainerModel } from "../../model"; +import { renderCmdText } from "../../common/common"; +import { SimpleBlobRenderer } from "./renderer/simplerenderer"; +import { FullRenderer } from "./renderer/fullrenderer"; +import { isBlank } from "../../util"; +import { PluginModel } from "../../plugins/plugins"; import * as lineutil from "./lineutil"; +import "./lines.less"; + dayjs.extend(localizedFormat); type OV = mobx.IObservableValue; diff --git a/src/lines.less b/src/main/line/lines.less similarity index 99% rename from src/lines.less rename to src/main/line/lines.less index c30caff1..b707f008 100644 --- a/src/lines.less +++ b/src/main/line/lines.less @@ -1,3 +1,5 @@ +@import "../../index.less"; + .line.line-text { flex-direction: row; padding-top: 5px; @@ -465,10 +467,6 @@ border-radius: 3px; } } - - &:hover .cmd-hints { - display: flex; - } } .line.line-invalid { diff --git a/src/linesview.tsx b/src/main/line/linesview.tsx similarity index 99% rename from src/linesview.tsx rename to src/main/line/linesview.tsx index cbcec6f6..51900cfd 100644 --- a/src/linesview.tsx +++ b/src/main/line/linesview.tsx @@ -8,10 +8,12 @@ import cn from "classnames"; import dayjs from "dayjs"; import localizedFormat from "dayjs/plugin/localizedFormat"; import { debounce, throttle } from "throttle-debounce"; -import * as T from "./types"; -import * as util from "./util"; +import * as T from "../../types"; +import * as util from "../../util"; import * as lineutil from "./lineutil"; +import "./lines.less"; + dayjs.extend(localizedFormat); type OV = mobx.IObservableValue; type OArr = mobx.IObservableArray; diff --git a/src/lineutil.ts b/src/main/line/lineutil.ts similarity index 95% rename from src/lineutil.ts rename to src/main/line/lineutil.ts index e7a669ba..3ebf699b 100644 --- a/src/lineutil.ts +++ b/src/main/line/lineutil.ts @@ -1,7 +1,7 @@ import dayjs from "dayjs"; import localizedFormat from "dayjs/plugin/localizedFormat"; -import { isBlank, getDateStr } from "./util"; -import { LineType, WebLine, RendererContext } from "./types"; +import { isBlank, getDateStr } from "../../util"; +import { LineType, WebLine, RendererContext } from "../../types"; dayjs.extend(localizedFormat); diff --git a/src/fullrenderer.tsx b/src/main/line/renderer/fullrenderer.tsx similarity index 86% rename from src/fullrenderer.tsx rename to src/main/line/renderer/fullrenderer.tsx index a4fb510a..f3ffcb38 100644 --- a/src/fullrenderer.tsx +++ b/src/main/line/renderer/fullrenderer.tsx @@ -1,25 +1,12 @@ 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"; +} from "../../../types"; import { debounce, throttle } from "throttle-debounce"; type OV = mobx.IObservableValue; diff --git a/src/simplerenderer.tsx b/src/main/line/renderer/simplerenderer.tsx similarity index 97% rename from src/simplerenderer.tsx rename to src/main/line/renderer/simplerenderer.tsx index 70b9bc21..4efe2337 100644 --- a/src/simplerenderer.tsx +++ b/src/main/line/renderer/simplerenderer.tsx @@ -19,12 +19,12 @@ import type { LineType, TermContextUnion, RendererContainerType, -} from "./types"; -import * as T from "./types"; -import { PacketDataBuffer } from "./ptydata"; +} from "../../../types"; +import * as T from "../../../types"; +import { PacketDataBuffer } from "../../../ptydata"; import { debounce, throttle } from "throttle-debounce"; -import * as util from "./util"; -import { GlobalModel } from "./model"; +import * as util from "../../../util"; +import { GlobalModel } from "../../../model"; type OV = mobx.IObservableValue; type CV = mobx.IComputedValue; diff --git a/src/main/modals/Modals.tsx b/src/main/modals/Modals.tsx new file mode 100644 index 00000000..609b6564 --- /dev/null +++ b/src/main/modals/Modals.tsx @@ -0,0 +1,357 @@ +import * as React from "react"; +import * as mobxReact from "mobx-react"; +import * as mobx from "mobx"; +import { boundMethod } from "autobind-decorator"; +import { If, For } from "tsx-control-statements/components"; +import cn from "classnames"; +import dayjs from "dayjs"; +import localizedFormat from "dayjs/plugin/localizedFormat"; +import { GlobalModel } from "../../model"; +import { Markdown } from "../../common/common"; + +dayjs.extend(localizedFormat); + +type OV = mobx.IObservableValue; + +@mobxReact.observer +class DisconnectedModal extends React.Component<{}, {}> { + logRef: any = React.createRef(); + showLog: mobx.IObservableValue = mobx.observable.box(false); + + @boundMethod + restartServer() { + GlobalModel.restartLocalServer(); + } + + @boundMethod + tryReconnect() { + GlobalModel.ws.connectNow("manual"); + } + + componentDidMount() { + if (this.logRef.current != null) { + this.logRef.current.scrollTop = this.logRef.current.scrollHeight; + } + } + + componentDidUpdate() { + if (this.logRef.current != null) { + this.logRef.current.scrollTop = this.logRef.current.scrollHeight; + } + } + + @boundMethod + handleShowLog(): void { + mobx.action(() => { + this.showLog.set(!this.showLog.get()); + })(); + } + + render() { + let model = GlobalModel; + let logLine: string = null; + let idx: number = 0; + return ( +
+
+
+
+
Prompt Client Disconnected
+
+ +
+
+ +
+ {logLine} +
+
+
+
+
+
+
+ + Show Log + + + Hide Log + +
+
+ + +
+
+
+ ); + } +} + +@mobxReact.observer +class ClientStopModal extends React.Component<{}, {}> { + @boundMethod + refreshClient() { + GlobalModel.refreshClient(); + } + + render() { + let model = GlobalModel; + let cdata = model.clientData.get(); + let title = "Client Not Ready"; + return ( +
+
+
+
+
[prompt] {title}
+
+
+ +
Cannot get client data.
+
+
+
+ +
+
+
+ ); + } +} + +@mobxReact.observer +class LoadingSpinner extends React.Component<{}, {}> { + render() { + return ( +
+
+
+
+
+
+ ); + } +} + +@mobxReact.observer +class AlertModal extends React.Component<{}, {}> { + @boundMethod + closeModal(): void { + GlobalModel.cancelAlert(); + } + + @boundMethod + handleOK(): void { + GlobalModel.confirmAlert(); + } + + render() { + let message = GlobalModel.alertMessage.get(); + if (message == null) { + return null; + } + let title = message.title ?? (message.confirm ? "Confirm" : "Alert"); + let isConfirm = message.confirm; + return ( +
+
+
+
+

+ {title} +

+
+ +
+
+ + + + +
+

{message.message}

+
+
+
+ +
+ Cancel +
+
+ OK +
+
+ +
+ OK +
+
+
+
+
+ ); + } +} + +@mobxReact.observer +class WelcomeModal extends React.Component<{}, {}> { + totalPages: number = 3; + pageNum: OV = mobx.observable.box(1, { name: "welcome-pagenum" }); + + @boundMethod + closeModal(): void { + mobx.action(() => { + GlobalModel.welcomeModalOpen.set(false); + })(); + } + + @boundMethod + goNext(): void { + mobx.action(() => { + this.pageNum.set(this.pageNum.get() + 1); + })(); + } + + @boundMethod + goPrev(): void { + mobx.action(() => { + this.pageNum.set(this.pageNum.get() - 1); + })(); + } + + renderDot(num: number): any { + if (num == this.pageNum.get()) { + return ; + } + return ; + } + + renderDots(): any { + let elems: any = []; + for (let i = 1; i <= this.totalPages; i++) { + let elem = this.renderDot(i); + elems.push(elem); + } + return elems; + } + + render() { + let pageNum = this.pageNum.get(); + return ( +
+
+
+
+
welcome to [prompt]
+
+ +
+
+
+

+ Prompt is a new terminal to help save you time and keep your command-line life organized. + Here's a couple quick tips to get your started! +

+
+
+ 1}> + + + +
+ +
+
{this.renderDots()}
+
+ + + + + + +
+
+
+ ); + } +} + +@mobxReact.observer +class TosModal extends React.Component<{}, {}> { + @boundMethod + acceptTos(): void { + GlobalCommandRunner.clientAcceptTos(); + } + + render() { + return ( +
+
+
+
+
Welcome to [prompt]
+
+
+
+

Thank you for downloading Prompt!

+

+ 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{" "} + + discord server + + . +

+

+ Prompt is free to use, no email or registration required (unless you're using the cloud + features). +

+

+ + Full Terms of Service + +

+
+
+
+
+
+ Accept Terms of Service +
+
+
+
+ ); + } +} + +export { WelcomeModal, LoadingSpinner, ClientStopModal, AlertModal, DisconnectedModal, TosModal }; diff --git a/src/modals.less b/src/main/modals/modals.less similarity index 53% rename from src/modals.less rename to src/main/modals/modals.less index e8e277fa..617efbc0 100644 --- a/src/modals.less +++ b/src/main/modals/modals.less @@ -1,3 +1,5 @@ +@import "../../index.less"; + // modal css (also includes settings-field) .modal { @@ -167,253 +169,6 @@ } } -.modal.prompt-modal.remotes-modal { - .modal-content { - min-width: 850px; - } - - .inner-content { - display: flex; - flex-direction: row; - align-items: stretch; - padding: 0; - max-height: 80vh; - - .remotes-menu { - flex: 0 0 200px; - min-height: 450px; - border-right: 1px solid #666; - overflow-y: auto; - height: 100px; - - .remote-menu-item { - border-top: 1px solid #666; - padding: 5px; - display: flex; - flex-direction: row; - cursor: pointer; - - &.add-remote { - font-size: 13px; - padding: 10px 5px 10px 5px; - } - - &:hover { - background-color: #333; - } - - &.is-selected { - background-color: @active-menu-color; - - .remote-name .remote-name-secondary { - color: white; - } - } - - &:first-child { - border-top: 0; - } - - .remote-status-light { - width: 15px; - margin-top: -2px; - } - - .remote-name { - flex-grow: 1; - - .remote-name-primary { - font-size: 12px; - font-weight: bold; - } - - .remote-name-secondary { - font-size: 11px; - color: #777; - } - } - } - } - - .remote-detail { - padding: 10px; - flex-grow: 1; - font-size: 12px; - display: flex; - flex-direction: column; - - .settings-field { - margin-top: 5px; - } - - * { - flex-shrink: 0; - } - - .detail-subtitle { - font-size: 18px; - margin-bottom: 10px; - margin-top: 10px; - } - - .title { - color: white; - padding-bottom: 8px; - margin-bottom: 0; - border-bottom: 1px solid #777; - } - - .terminal-wrapper { - margin-left: 0; - margin-bottom: 0; - - &.has-message { - margin-top: 0; - } - - box-shadow: none; - border: 1px solid #777; - } - - .action-buttons { - display: flex; - flex-direction: row; - gap: 10px; - margin-top: 2px; - } - - .remote-message { - margin-top: 5px; - padding: 8px; - border-radius: 5px 5px 0 0; - background-color: #333; - - i.fa-check { - color: @term-green; - } - - &.is-ok { - } - - .message-row { - display: flex; - flex-direction: row; - align-items: center; - } - - .remote-status { - position: relative; - top: -1px; - } - - .button { - height: 22px; - } - } - - .settings-field { - .update-auth-button { - visibility: hidden; - } - - &:hover { - .update-auth-button { - visibility: visible; - } - - .hide-hover { - display: none; - } - } - } - - &.auth-editing, - &.create-remote { - .settings-field.align-top { - align-items: flex-start; - - .settings-label { - margin-top: 8px; - } - - .settings-input { - align-items: flex-start; - } - } - - .settings-label { - display: flex; - flex-direction: row; - align-items: center; - width: 135px; - } - - .settings-field .settings-input .undo-icon { - cursor: pointer; - font-size: 18px; - margin-left: 5px; - } - - .editremote-dropdown .dropdown-trigger button { - width: 120px; - justify-content: flex-start; - } - - .settings-field .raw-input { - width: 120px; - } - - .settings-input input { - width: 250px; - } - - .dropdown .dropdown-trigger button { - font-size: 12px; - } - - .dropdown .dropdown-item { - font-size: 12px; - padding: 5px 5px 5px 12px; - } - - .settings-input { - .info-message { - margin-left: 22px; - } - } - - .settings-label { - .info-message { - margin-right: 15px; - } - } - } - } - } - - .terminal-wrapper { - position: relative; - background-color: #000; - padding: 2px 10px 5px 4px; - margin: 5px 5px 10px 5px; - box-shadow: 0 0 1px 1px rgba(255, 255, 255, 0.3); - &.focus { - box-shadow: 0 0 3px 3px rgba(255, 255, 255, 0.3); - } - - .term-tag { - position: absolute; - top: 0; - right: 0; - background-color: @term-red; - color: white; - z-index: 110; - padding: 4px; - .mono-font(10px); - } - } -} - .modal.welcome-modal { footer { .prev-button { diff --git a/src/settings.tsx b/src/main/modals/settings.tsx similarity index 98% rename from src/settings.tsx rename to src/main/modals/settings.tsx index 0c33b99f..9058d96d 100644 --- a/src/settings.tsx +++ b/src/main/modals/settings.tsx @@ -1,24 +1,18 @@ 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 { If, For } 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 * as util from "./util"; +import { GlobalModel, GlobalCommandRunner, TabColors } from "../../model"; +import { Toggle, InlineSettingsTextEdit, SettingsError, InfoMessage } from "../../common/common"; +import { LineType, RendererPluginType, ClientDataType, CommandRtnType } from "../../types"; +import { PluginModel } from "../../plugins/plugins"; +import * as util from "../../util"; + +import "./modals.less"; type OV = mobx.IObservableValue; -type OArr = mobx.IObservableArray; -type OMap = mobx.ObservableMap; -type CV = mobx.IComputedValue; - -const RemotePtyRows = 8; -const RemotePtyCols = 80; -const APITokenSentinel = "--apitoken--"; // @ts-ignore const VERSION = __PROMPT_VERSION__; diff --git a/src/main/sessionview/CmdInput.tsx b/src/main/sessionview/CmdInput.tsx new file mode 100644 index 00000000..aadbfd2a --- /dev/null +++ b/src/main/sessionview/CmdInput.tsx @@ -0,0 +1,192 @@ +import * as React from "react"; +import * as mobxReact from "mobx-react"; +import * as mobx from "mobx"; +import { boundMethod } from "autobind-decorator"; +import { If } from "tsx-control-statements/components"; +import cn from "classnames"; +import dayjs from "dayjs"; +import type { RemoteType, RemoteInstanceType, RemotePtrType } from "../../types"; +import localizedFormat from "dayjs/plugin/localizedFormat"; +import { GlobalModel, GlobalCommandRunner } from "../../model"; +import { Prompt } from "../line/linecomps"; +import { renderCmdText } from "../../common/common"; +import { TextAreaInput } from "./TextareaInput"; +import { InfoMsg } from "./InfoMsg"; +import { HistoryInfo } from "./HistoryInfo"; +import "./sessionview.less"; + +dayjs.extend(localizedFormat); + +const TDots = "⋮"; + +type OV = mobx.IObservableValue; + +@mobxReact.observer +class CmdInput extends React.Component<{}, {}> { + cmdInputRef: React.RefObject = React.createRef(); + + @boundMethod + onInfoToggle(): void { + GlobalModel.inputModel.toggleInfoMsg(); + return; + } + + componentDidMount() { + this.updateCmdInputHeight(); + } + + updateCmdInputHeight() { + let elem = this.cmdInputRef.current; + if (elem == null) { + return; + } + let height = elem.offsetHeight; + if (height == GlobalModel.inputModel.cmdInputHeight) { + return; + } + mobx.action(() => { + GlobalModel.inputModel.cmdInputHeight.set(height); + })(); + } + + componentDidUpdate(prevProps, prevState, snapshot: {}): void { + this.updateCmdInputHeight(); + } + + @boundMethod + handleInnerHeightUpdate(): void { + this.updateCmdInputHeight(); + } + + @boundMethod + clickFocusInputHint(): void { + GlobalModel.inputModel.giveFocus(); + } + + @boundMethod + clickHistoryHint(e: any): void { + e.preventDefault(); + e.stopPropagation(); + + let inputModel = GlobalModel.inputModel; + if (inputModel.historyShow.get()) { + inputModel.resetHistory(); + } else { + inputModel.openHistory(); + } + } + + @boundMethod + clickConnectRemote(remoteId: string): void { + GlobalCommandRunner.connectRemote(remoteId); + } + + render() { + let model = GlobalModel; + let inputModel = model.inputModel; + let screen = GlobalModel.getActiveScreen(); + let ri: RemoteInstanceType = null; + let rptr: RemotePtrType = null; + if (screen != null) { + ri = screen.getCurRemoteInstance(); + rptr = screen.curRemote.get(); + } + let remote: RemoteType = null; + let feState: Record = null; + if (ri != null) { + remote = GlobalModel.getRemote(ri.remoteid); + feState = ri.festate; + } + let infoShow = inputModel.infoShow.get(); + let historyShow = !infoShow && inputModel.historyShow.get(); + let infoMsg = inputModel.infoMsg.get(); + let hasInfo = infoMsg != null; + let focusVal = inputModel.physicalInputFocused.get(); + let inputMode: string = inputModel.inputMode.get(); + let textAreaInputKey = screen == null ? "null" : screen.screenId; + return ( +
+
+
+ + + + + + +
+ +
+ +
+ + +
+ WARNING:  + [{GlobalModel.resolveRemoteIdToFullRef(remote.remoteid)}] +  is {remote.status} + +
this.clickConnectRemote(remote.remoteid)} + > + connect now +
+
+
+
+
+
+ +
+
+
+ +
+
{inputMode}
+
+
+ +
+
+ + + +
+
+
+
+ {inputModel.inputExpanded.get() ? "shrink" : "expand"} input ({renderCmdText("E")}) +
+ +
+ focus input ({renderCmdText("I")}) +
+
+ +
+ {" "} + {historyShow ? "close history (esc)" : "show history (ctrl-r)"} +
+
+
+
+
+ ); + } +} + +export { CmdInput }; diff --git a/src/main/sessionview/HistoryInfo.tsx b/src/main/sessionview/HistoryInfo.tsx new file mode 100644 index 00000000..8b7dc53d --- /dev/null +++ b/src/main/sessionview/HistoryInfo.tsx @@ -0,0 +1,234 @@ +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 } from "tsx-control-statements/components"; +import cn from "classnames"; +import dayjs from "dayjs"; +import type { HistoryItem, HistoryQueryOpts } from "../../types"; +import localizedFormat from "dayjs/plugin/localizedFormat"; +import { GlobalModel } from "../../model"; +import { isBlank } from "../../util"; +import "./sessionview.less"; + +dayjs.extend(localizedFormat); + +const TDots = "⋮"; + +function truncateWithTDots(str: string, maxLen: number): string { + if (str == null) { + return null; + } + if (str.length <= maxLen) { + return str; + } + return str.slice(0, maxLen - 1) + TDots; +} + +@mobxReact.observer +class HistoryInfo extends React.Component<{}, {}> { + lastClickHNum: string = null; + lastClickTs: number = 0; + containingText: mobx.IObservableValue = mobx.observable.box(""); + + componentDidMount() { + let inputModel = GlobalModel.inputModel; + let hitem = inputModel.getHistorySelectedItem(); + if (hitem == null) { + hitem = inputModel.getFirstHistoryItem(); + } + if (hitem != null) { + inputModel.scrollHistoryItemIntoView(hitem.historynum); + } + } + + @boundMethod + handleItemClick(hitem: HistoryItem) { + let inputModel = GlobalModel.inputModel; + let selItem = inputModel.getHistorySelectedItem(); + if (this.lastClickHNum == hitem.historynum && selItem != null && selItem.historynum == hitem.historynum) { + inputModel.grabSelectedHistoryItem(); + return; + } + inputModel.giveFocus(); + inputModel.setHistorySelectionNum(hitem.historynum); + let now = Date.now(); + this.lastClickHNum = hitem.historynum; + this.lastClickTs = now; + setTimeout(() => { + if (this.lastClickTs == now) { + this.lastClickHNum = null; + this.lastClickTs = 0; + } + }, 3000); + } + + renderRemote(hitem: HistoryItem): any { + if (hitem.remote == null || isBlank(hitem.remote.remoteid)) { + return sprintf("%-15s ", ""); + } + let r = GlobalModel.getRemote(hitem.remote.remoteid); + if (r == null) { + return sprintf("%-15s ", "???"); + } + let rname = ""; + if (!isBlank(r.remotealias)) { + rname = r.remotealias; + } else { + rname = r.remotecanonicalname; + } + if (!isBlank(hitem.remote.name)) { + rname = rname + ":" + hitem.remote.name; + } + let rtn = sprintf("%-15s ", "[" + truncateWithTDots(rname, 13) + "]"); + return rtn; + } + + renderHInfoText( + hitem: HistoryItem, + opts: HistoryQueryOpts, + isSelected: boolean, + snames: Record, + scrNames: Record + ): string { + let remoteStr = ""; + if (!opts.limitRemote) { + remoteStr = this.renderRemote(hitem); + } + let selectedStr = isSelected ? "*" : " "; + let lineNumStr = hitem.linenum > 0 ? "(" + hitem.linenum + ")" : ""; + if (isBlank(opts.queryType) || opts.queryType == "screen") { + return selectedStr + sprintf("%7s", lineNumStr) + " " + remoteStr; + } + if (opts.queryType == "session") { + let screenStr = ""; + if (!isBlank(hitem.screenid)) { + let scrName = scrNames[hitem.screenid]; + if (scrName != null) { + screenStr = "[" + truncateWithTDots(scrName, 15) + "]"; + } + } + return selectedStr + sprintf("%17s", screenStr) + sprintf("%7s", lineNumStr) + " " + remoteStr; + } + if (opts.queryType == "global") { + let sessionStr = ""; + if (!isBlank(hitem.sessionid)) { + let sessionName = snames[hitem.sessionid]; + if (sessionName != null) { + sessionStr = "#" + truncateWithTDots(sessionName, 15); + } + } + let screenStr = ""; + if (!isBlank(hitem.screenid)) { + let scrName = scrNames[hitem.screenid]; + if (scrName != null) { + screenStr = "[" + truncateWithTDots(scrName, 13) + "]"; + } + } + let ssStr = sessionStr + screenStr; + return ( + selectedStr + + sprintf("%15s ", sessionStr) + + " " + + sprintf("%15s", screenStr) + + sprintf("%7s", lineNumStr) + + " " + + remoteStr + ); + } + return "-"; + } + + renderHItem( + hitem: HistoryItem, + opts: HistoryQueryOpts, + isSelected: boolean, + snames: Record, + scrNames: Record + ): any { + let lines = hitem.cmdstr.split("\n"); + let line: string = ""; + let idx = 0; + let infoText = this.renderHInfoText(hitem, opts, isSelected, snames, scrNames); + let infoTextSpacer = sprintf("%" + infoText.length + "s", ""); + return ( +
this.handleItemClick(hitem)} + > +
+ {infoText} {lines[0]} +
+ +
+ {infoTextSpacer} {line} +
+
+
+ ); + } + + @boundMethod + handleClose() { + GlobalModel.inputModel.toggleInfoMsg(); + } + + render() { + let inputModel = GlobalModel.inputModel; + let idx: number = 0; + let selItem = inputModel.getHistorySelectedItem(); + let hitems = inputModel.getFilteredHistoryItems(); + hitems = hitems.slice().reverse(); + let hitem: HistoryItem = null; + let opts = inputModel.historyQueryOpts.get(); + let snames: Record = {}; + let scrNames: Record = {}; + if (opts.queryType == "global") { + scrNames = GlobalModel.getScreenNames(); + snames = GlobalModel.getSessionNames(); + } else if (opts.queryType == "session") { + scrNames = GlobalModel.getScreenNames(); + } + return ( +
+
+
history
+
+
[for {opts.queryType} ⌘S]
+
+
[containing '{opts.queryStr}']
+
+
[{opts.limitRemote ? "this" : "any"} remote ⌘R]
+
+
+ (ESC) +
+
+
+
+ [no history] + 0}> + + {this.renderHItem(hitem, opts, hitem == selItem, snames, scrNames)} + + +
+
+ ); + } +} + +export { HistoryInfo }; diff --git a/src/main/sessionview/InfoMsg.tsx b/src/main/sessionview/InfoMsg.tsx new file mode 100644 index 00000000..01ed0d6e --- /dev/null +++ b/src/main/sessionview/InfoMsg.tsx @@ -0,0 +1,113 @@ +import * as React from "react"; +import * as mobxReact from "mobx-react"; +import { If, For } from "tsx-control-statements/components"; +import cn from "classnames"; +import dayjs from "dayjs"; +import localizedFormat from "dayjs/plugin/localizedFormat"; +import { GlobalModel } from "../../model"; +import { makeExternLink } from "../../util"; +import "./sessionview.less"; + +dayjs.extend(localizedFormat); + +@mobxReact.observer +class InfoMsg extends React.Component<{}, {}> { + getAfterSlash(s: string): string { + if (s.startsWith("^/")) { + return s.substr(1); + } + let slashIdx = s.lastIndexOf("/"); + if (slashIdx == s.length - 1) { + slashIdx = s.lastIndexOf("/", slashIdx - 1); + } + if (slashIdx == -1) { + return s; + } + return s.substr(slashIdx + 1); + } + + hasSpace(s: string): boolean { + return s.indexOf(" ") != -1; + } + + handleCompClick(s: string): void { + // TODO -> complete to this completion + } + + render() { + let model = GlobalModel; + let inputModel = model.inputModel; + let infoMsg = inputModel.infoMsg.get(); + let infoShow = inputModel.infoShow.get(); + let line: string = null; + let istr: string = null; + let idx: number = 0; + let titleStr = null; + let remoteEditKey = "inforemoteedit"; + if (infoMsg != null) { + titleStr = infoMsg.infotitle; + } + let activeScreen = model.getActiveScreen(); + return ( +
+ +
+ {titleStr} +
+
+ +
+ + + + {infoMsg.infomsg} +
+
+ +
+ started sharing screen at{" "} + + [link] + +
+
+ +
+ +
{line == "" ? " " : line}
+
+
+
+ 0}> +
+ +
this.handleCompClick(istr)} + key={idx} + className={cn( + "info-comp", + { "has-space": this.hasSpace(istr) }, + { "metacmd-comp": istr.startsWith("^") } + )} + > + {this.getAfterSlash(istr)} +
+
+ +
+ ... +
+
+
+
+ +
+ [error] {infoMsg.infoerror} +
+
+
+ ); + } +} + +export { InfoMsg }; diff --git a/src/main/sessionview/SessionView.tsx b/src/main/sessionview/SessionView.tsx new file mode 100644 index 00000000..f805fd33 --- /dev/null +++ b/src/main/sessionview/SessionView.tsx @@ -0,0 +1,457 @@ +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 } from "tsx-control-statements/components"; +import cn from "classnames"; +import { debounce } from "throttle-debounce"; +import dayjs from "dayjs"; +import type { LineType, RenderModeType, LineFactoryProps } from "../../types"; +import localizedFormat from "dayjs/plugin/localizedFormat"; +import { GlobalModel, GlobalCommandRunner, Session, ScreenLines, Screen } from "../../model"; +import { Line } from "../line/linecomps"; +import { renderCmdText } from "../../common/common"; +import { LinesView } from "../line/linesview"; +import { CmdInput } from "./CmdInput"; +import "./sessionview.less"; +import "./tabs.less"; + +dayjs.extend(localizedFormat); + +type OV = mobx.IObservableValue; + +@mobxReact.observer +class SessionView extends React.Component<{}, {}> { + render() { + let model = GlobalModel; + let session = model.getActiveSession(); + if (session == null) { + return
(no active session)
; + } + let activeScreen = session.getActiveScreen(); + let cmdInputHeight = model.inputModel.cmdInputHeight.get(); + if (cmdInputHeight == 0) { + cmdInputHeight = 110; + } + let isHidden = GlobalModel.activeMainView.get() != "session"; + return ( +
+ + +
+ +
+ ); + } +} + +@mobxReact.observer +class ScreenView extends React.Component<{ screen: Screen }, {}> { + render() { + let { screen } = this.props; + if (screen == null) { + return
(no screen found)
; + } + let fontSize = GlobalModel.termFontSize.get(); + return ( +
+ +
+ ); + } +} + +// screen is not null +@mobxReact.observer +class ScreenWindowView extends React.Component<{ screen: Screen }, {}> { + rszObs: any; + windowViewRef: React.RefObject; + + width: mobx.IObservableValue = mobx.observable.box(0, { name: "sw-view-width" }); + height: mobx.IObservableValue = mobx.observable.box(0, { name: "sw-view-height" }); + setSize_debounced: (width: number, height: number) => void; + + renderMode: OV = mobx.observable.box("normal", { name: "renderMode" }); + shareCopied: OV = mobx.observable.box(false, { name: "sw-shareCopied" }); + + constructor(props: any) { + super(props); + this.setSize_debounced = debounce(1000, this.setSize.bind(this)); + this.windowViewRef = React.createRef(); + } + + setSize(width: number, height: number): void { + let { screen } = this.props; + if (screen == null) { + return; + } + if (width == null || height == null || width == 0 || height == 0) { + return; + } + mobx.action(() => { + this.width.set(width); + this.height.set(height); + screen.screenSizeCallback({ height: height, width: width }); + })(); + } + + componentDidMount() { + let wvElem = this.windowViewRef.current; + if (wvElem != null) { + let width = wvElem.offsetWidth; + let height = wvElem.offsetHeight; + this.setSize(width, height); + this.rszObs = new ResizeObserver(this.handleResize.bind(this)); + this.rszObs.observe(wvElem); + } + } + + componentWillUnmount() { + if (this.rszObs) { + this.rszObs.disconnect(); + } + } + + handleResize(entries: any) { + if (entries.length == 0) { + return; + } + let entry = entries[0]; + let width = entry.target.offsetWidth; + let height = entry.target.offsetHeight; + mobx.action(() => { + this.setSize_debounced(width, height); + })(); + } + + getScreenLines(): ScreenLines { + let { screen } = this.props; + let win = GlobalModel.getScreenLinesById(screen.screenId); + if (win == null) { + win = GlobalModel.loadScreenLines(screen.screenId); + } + return win; + } + + getWindowViewStyle(): any { + return { position: "absolute", width: "100%", height: "100%", overflowX: "hidden" }; + } + + @boundMethod + toggleRenderMode() { + let renderMode = this.renderMode.get(); + mobx.action(() => { + this.renderMode.set(renderMode == "normal" ? "collapsed" : "normal"); + })(); + } + + renderError(message: string, fade: boolean) { + let { screen } = this.props; + return ( +
+
+
+
{message}
+
+
+ ); + } + + @boundMethod + copyShareLink(): void { + let { screen } = this.props; + let shareLink = screen.getWebShareUrl(); + if (shareLink == null) { + return; + } + navigator.clipboard.writeText(shareLink); + mobx.action(() => { + this.shareCopied.set(true); + })(); + setTimeout(() => { + mobx.action(() => { + this.shareCopied.set(false); + })(); + }, 600); + } + + @boundMethod + openScreenSettings(): void { + let { screen } = this.props; + mobx.action(() => { + GlobalModel.screenSettingsModal.set({ sessionId: screen.sessionId, screenId: screen.screenId }); + })(); + } + + @boundMethod + buildLineComponent(lineProps: LineFactoryProps): JSX.Element { + let { screen } = this.props; + let { line, ...restProps } = lineProps; + let realLine: LineType = line as LineType; + return ; + } + + render() { + let { screen } = this.props; + let win = this.getScreenLines(); + if (win == null || !win.loaded.get()) { + return this.renderError("...", true); + } + if (win.loadError.get() != null) { + return this.renderError(sprintf("(%s)", win.loadError.get()), false); + } + if (this.width.get() == 0) { + return this.renderError("", false); + } + let cdata = GlobalModel.clientData.get(); + if (cdata == null) { + return this.renderError("loading client data", true); + } + let idx = 0; + let line: LineType = null; + let session = GlobalModel.getSessionById(screen.sessionId); + let isActive = screen.isActive(); + let selectedLine = screen.getSelectedLine(); + let lines = win.getNonArchivedLines(); + let renderMode = this.renderMode.get(); + return ( +
+
+
+ + + + + + +
+
+ +
+ +
+ +
+ web shared +
+
+
+ copy link + + + +
+
+ open settings + + + +
+
+
+
+ 0}> + + + +
+
+ + [session="{session.name.get()}" screen="{screen.name.get()}"] + +
+
+
+
+ ); + } +} + +@mobxReact.observer +class ScreenTabs extends React.Component<{ session: Session }, {}> { + tabsRef: React.RefObject = React.createRef(); + lastActiveScreenId: string = null; + scrolling: OV = mobx.observable.box(false, { name: "screentabs-scrolling" }); + + stopScrolling_debounced: () => void; + + constructor(props: any) { + super(props); + this.stopScrolling_debounced = debounce(1500, this.stopScrolling.bind(this)); + } + + @boundMethod + handleNewScreen() { + let { session } = this.props; + GlobalCommandRunner.createNewScreen(); + } + + @boundMethod + handleSwitchScreen(screenId: string) { + let { session } = this.props; + if (session == null) { + return; + } + if (session.activeScreenId.get() == screenId) { + return; + } + let screen = session.getScreenById(screenId); + if (screen == null) { + return; + } + GlobalCommandRunner.switchScreen(screenId); + } + + componentDidMount(): void { + this.componentDidUpdate(); + } + + componentDidUpdate(): void { + let { session } = this.props; + let activeScreenId = session.activeScreenId.get(); + if (activeScreenId != this.lastActiveScreenId && this.tabsRef.current) { + let tabElem = this.tabsRef.current.querySelector( + sprintf('.screen-tab[data-screenid="%s"]', activeScreenId) + ); + if (tabElem != null) { + tabElem.scrollIntoView(); + } + } + this.lastActiveScreenId = activeScreenId; + } + + stopScrolling(): void { + mobx.action(() => { + this.scrolling.set(false); + })(); + } + + @boundMethod + handleScroll() { + if (!this.scrolling.get()) { + mobx.action(() => { + this.scrolling.set(true); + })(); + } + this.stopScrolling_debounced(); + } + + @boundMethod + openScreenSettings(e: any, screen: Screen): void { + e.preventDefault(); + e.stopPropagation(); + mobx.action(() => { + GlobalModel.screenSettingsModal.set({ sessionId: screen.sessionId, screenId: screen.screenId }); + })(); + } + + renderTab(screen: Screen, activeScreenId: string, index: number): any { + let tabIndex = null; + if (index + 1 <= 9) { + tabIndex =
{renderCmdText(String(index + 1))}
; + } + let settings = ( +
this.openScreenSettings(e, screen)} title="Settings" className="tab-gear"> + +
+ ); + let archived = screen.archived.get() ? ( + + ) : null; + + let webShared = screen.isWebShared() ? ( + + ) : null; + return ( +
this.handleSwitchScreen(screen.screenId)} + onContextMenu={(event) => this.openScreenSettings(event, screen)} + > +
+ {archived} + {webShared} + {screen.name.get()} +
+ {tabIndex} + {settings} +
+ ); + } + + render() { + let { session } = this.props; + if (session == null) { + return null; + } + let screen: Screen = null; + let index = 0; + let showingScreens = []; + let activeScreenId = session.activeScreenId.get(); + let screens = GlobalModel.getSessionScreens(session.sessionId); + for (let screen of screens) { + if (!screen.archived.get() || activeScreenId == screen.screenId) { + showingScreens.push(screen); + } + } + showingScreens.sort((a, b) => { + let aidx = a.screenIdx.get(); + let bidx = b.screenIdx.get(); + if (aidx < bidx) { + return -1; + } + if (aidx > bidx) { + return 1; + } + return 0; + }); + return ( +
+
+ + {this.renderTab(screen, activeScreenId, index)} + +
+ +
+
+
+
move left {renderCmdText("[")}
+
move right {renderCmdText("]")}
+
new tab {renderCmdText("T")}
+
+
+ ); + } +} + +export { SessionView }; diff --git a/src/main/sessionview/TextareaInput.tsx b/src/main/sessionview/TextareaInput.tsx new file mode 100644 index 00000000..dfe89071 --- /dev/null +++ b/src/main/sessionview/TextareaInput.tsx @@ -0,0 +1,573 @@ +import * as React from "react"; +import * as mobxReact from "mobx-react"; +import * as mobx from "mobx"; +import { boundMethod } from "autobind-decorator"; +import cn from "classnames"; +import { GlobalModel, GlobalCommandRunner } from "../../model"; +import { getMonoFontSize } from "../../textmeasure"; +import { isModKeyPress, hasNoModifiers } from "../../util"; +import "./sessionview.less"; + +function pageSize(div: any): number { + if (div == null) { + return 300; + } + let size = div.clientHeight; + if (size > 500) { + size = size - 100; + } else if (size > 200) { + size = size - 30; + } + return size; +} + +@mobxReact.observer +class TextAreaInput extends React.Component<{ onHeightChange: () => void }, {}> { + lastTab: boolean = false; + lastHistoryUpDown: boolean = false; + lastTabCurLine: mobx.IObservableValue = mobx.observable.box(null); + lastFocusType: string = null; + mainInputRef: React.RefObject; + historyInputRef: React.RefObject; + controlRef: React.RefObject; + lastHeight: number = 0; + + constructor(props) { + super(props); + this.mainInputRef = React.createRef(); + this.historyInputRef = React.createRef(); + this.controlRef = React.createRef(); + } + + setFocus(): void { + let inputModel = GlobalModel.inputModel; + if (inputModel.historyShow.get()) { + this.historyInputRef.current.focus(); + } else { + this.mainInputRef.current.focus(); + } + } + + getTextAreaMaxCols(): number { + let taElem = this.mainInputRef.current; + if (taElem == null) { + return 0; + } + let cs = window.getComputedStyle(taElem); + let padding = parseFloat(cs.paddingLeft) + parseFloat(cs.paddingRight); + let borders = parseFloat(cs.borderLeft) + parseFloat(cs.borderRight); + let contentWidth = taElem.clientWidth - padding - borders; + let fontSize = getMonoFontSize(parseInt(cs.fontSize)); + let maxCols = Math.floor(contentWidth / fontSize.width); + return maxCols; + } + + checkHeight(shouldFire: boolean): void { + let elem = this.controlRef.current; + if (elem == null) { + return; + } + let curHeight = elem.offsetHeight; + if (this.lastHeight == curHeight) { + return; + } + this.lastHeight = curHeight; + if (shouldFire && this.props.onHeightChange != null) { + this.props.onHeightChange(); + } + } + + componentDidMount() { + let activeScreen = GlobalModel.getActiveScreen(); + if (activeScreen != null) { + let focusType = activeScreen.focusType.get(); + if (focusType == "input") { + this.setFocus(); + } + this.lastFocusType = focusType; + } + this.checkHeight(false); + } + + componentDidUpdate() { + let activeScreen = GlobalModel.getActiveScreen(); + if (activeScreen != null) { + let focusType = activeScreen.focusType.get(); + if (this.lastFocusType != focusType && focusType == "input") { + this.setFocus(); + } + this.lastFocusType = focusType; + } + let inputModel = GlobalModel.inputModel; + if (inputModel.forceCursorPos.get() != null) { + if (this.mainInputRef.current != null) { + this.mainInputRef.current.selectionStart = inputModel.forceCursorPos.get(); + this.mainInputRef.current.selectionEnd = inputModel.forceCursorPos.get(); + } + mobx.action(() => inputModel.forceCursorPos.set(null))(); + } + if (inputModel.forceInputFocus) { + inputModel.forceInputFocus = false; + this.setFocus(); + } + this.checkHeight(true); + } + + getLinePos(elem: any): { numLines: number; linePos: number } { + let numLines = elem.value.split("\n").length; + let linePos = elem.value.substr(0, elem.selectionStart).split("\n").length; + return { numLines, linePos }; + } + + @mobx.action + @boundMethod + onKeyDown(e: any) { + mobx.action(() => { + if (isModKeyPress(e)) { + return; + } + let model = GlobalModel; + let inputModel = model.inputModel; + let win = model.getScreenLinesForActiveScreen(); + let ctrlMod = e.getModifierState("Control") || e.getModifierState("Meta") || e.getModifierState("Shift"); + let curLine = inputModel.getCurLine(); + + let lastTab = this.lastTab; + this.lastTab = e.code == "Tab"; + let lastHist = this.lastHistoryUpDown; + this.lastHistoryUpDown = false; + + if (e.code == "Tab") { + e.preventDefault(); + if (lastTab) { + GlobalModel.submitCommand( + "_compgen", + null, + [curLine], + { comppos: String(curLine.length), compshow: "1", nohist: "1" }, + true + ); + return; + } else { + GlobalModel.submitCommand( + "_compgen", + null, + [curLine], + { comppos: String(curLine.length), nohist: "1" }, + true + ); + return; + } + } + if (e.code == "Enter") { + e.preventDefault(); + if (!ctrlMod) { + if (GlobalModel.inputModel.isEmpty()) { + let activeWindow = GlobalModel.getScreenLinesForActiveScreen(); + let activeScreen = GlobalModel.getActiveScreen(); + if (activeScreen != null && activeWindow != null && activeWindow.lines.length > 0) { + activeScreen.setSelectedLine(0); + GlobalCommandRunner.screenSelectLine("E"); + } + return; + } else { + setTimeout(() => GlobalModel.inputModel.uiSubmitCommand(), 0); + return; + } + } + e.target.setRangeText("\n", e.target.selectionStart, e.target.selectionEnd, "end"); + GlobalModel.inputModel.setCurLine(e.target.value); + return; + } + if (e.code == "Escape") { + e.preventDefault(); + e.stopPropagation(); + let inputModel = GlobalModel.inputModel; + inputModel.toggleInfoMsg(); + if (inputModel.inputMode.get() != null) { + inputModel.resetInputMode(); + } + return; + } + if (e.code == "KeyE" && e.getModifierState("Meta")) { + e.preventDefault(); + e.stopPropagation(); + let inputModel = GlobalModel.inputModel; + inputModel.toggleExpandInput(); + } + if (e.code == "KeyC" && e.getModifierState("Control")) { + e.preventDefault(); + inputModel.resetInput(); + return; + } + if (e.code == "KeyU" && e.getModifierState("Control")) { + e.preventDefault(); + this.controlU(); + return; + } + if (e.code == "KeyP" && e.getModifierState("Control")) { + e.preventDefault(); + this.controlP(); + return; + } + if (e.code == "KeyN" && e.getModifierState("Control")) { + e.preventDefault(); + this.controlN(); + return; + } + if (e.code == "KeyW" && e.getModifierState("Control")) { + e.preventDefault(); + this.controlW(); + return; + } + if (e.code == "KeyY" && e.getModifierState("Control")) { + e.preventDefault(); + this.controlY(); + return; + } + if (e.code == "KeyR" && e.getModifierState("Control")) { + e.preventDefault(); + inputModel.openHistory(); + return; + } + if ((e.code == "ArrowUp" || e.code == "ArrowDown") && hasNoModifiers(e)) { + if (!inputModel.isHistoryLoaded()) { + if (e.code == "ArrowUp") { + this.lastHistoryUpDown = true; + inputModel.loadHistory(false, 1, "screen"); + } + return; + } + // invisible history movement + let linePos = this.getLinePos(e.target); + if (e.code == "ArrowUp") { + if (!lastHist && linePos.linePos > 1) { + // regular arrow + return; + } + e.preventDefault(); + inputModel.moveHistorySelection(1); + this.lastHistoryUpDown = true; + return; + } + if (e.code == "ArrowDown") { + if (!lastHist && linePos.linePos < linePos.numLines) { + // regular arrow + return; + } + e.preventDefault(); + inputModel.moveHistorySelection(-1); + this.lastHistoryUpDown = true; + return; + } + } + if (e.code == "PageUp" || e.code == "PageDown") { + e.preventDefault(); + let infoScroll = inputModel.hasScrollingInfoMsg(); + if (infoScroll) { + let div = document.querySelector(".cmd-input-info"); + let amt = pageSize(div); + scrollDiv(div, e.code == "PageUp" ? -amt : amt); + } + } + // console.log(e.code, e.keyCode, e.key, event.which, ctrlMod, e); + })(); + } + + @boundMethod + onChange(e: any) { + mobx.action(() => { + GlobalModel.inputModel.setCurLine(e.target.value); + })(); + } + + @boundMethod + onHistoryKeyDown(e: any) { + let inputModel = GlobalModel.inputModel; + if (e.code == "Escape") { + e.preventDefault(); + inputModel.resetHistory(); + return; + } + if (e.code == "Enter") { + e.preventDefault(); + inputModel.grabSelectedHistoryItem(); + return; + } + if (e.code == "KeyG" && e.getModifierState("Control")) { + e.preventDefault(); + inputModel.resetInput(); + return; + } + if (e.code == "KeyC" && e.getModifierState("Control")) { + e.preventDefault(); + inputModel.resetInput(); + return; + } + if ( + e.code == "KeyR" && + (e.getModifierState("Meta") || e.getModifierState("Control")) && + !e.getModifierState("Shift") + ) { + e.preventDefault(); + let opts = mobx.toJS(inputModel.historyQueryOpts.get()); + if (opts.limitRemote) { + opts.limitRemote = false; + opts.limitRemoteInstance = false; + } else { + opts.limitRemote = true; + opts.limitRemoteInstance = true; + } + inputModel.setHistoryQueryOpts(opts); + return; + } + if (e.code == "KeyS" && (e.getModifierState("Meta") || e.getModifierState("Control"))) { + e.preventDefault(); + let opts = mobx.toJS(inputModel.historyQueryOpts.get()); + let htype = opts.queryType; + if (htype == "screen") { + htype = "session"; + } else if (htype == "session") { + htype = "global"; + } else { + htype = "screen"; + } + inputModel.setHistoryType(htype); + return; + } + if (e.code == "Tab") { + e.preventDefault(); + return; + } + if (e.code == "ArrowUp" || e.code == "ArrowDown") { + e.preventDefault(); + inputModel.moveHistorySelection(e.code == "ArrowUp" ? 1 : -1); + return; + } + if (e.code == "PageUp" || e.code == "PageDown") { + e.preventDefault(); + inputModel.moveHistorySelection(e.code == "PageUp" ? 10 : -10); + return; + } + if (e.code == "KeyP" && e.getModifierState("Control")) { + e.preventDefault(); + inputModel.moveHistorySelection(1); + return; + } + if (e.code == "KeyN" && e.getModifierState("Control")) { + e.preventDefault(); + inputModel.moveHistorySelection(-1); + return; + } + } + + @boundMethod + controlU() { + if (this.mainInputRef.current == null) { + return; + } + let selStart = this.mainInputRef.current.selectionStart; + let value = this.mainInputRef.current.value; + if (selStart > value.length) { + return; + } + let cutValue = value.substr(0, selStart); + let restValue = value.substr(selStart); + let cmdLineUpdate = { cmdline: restValue, cursorpos: 0 }; + console.log("ss", selStart, value, "[" + cutValue + "]", "[" + restValue + "]"); + navigator.clipboard.writeText(cutValue); + GlobalModel.inputModel.updateCmdLine(cmdLineUpdate); + } + + @boundMethod + controlP() { + let inputModel = GlobalModel.inputModel; + if (!inputModel.isHistoryLoaded()) { + this.lastHistoryUpDown = true; + inputModel.loadHistory(false, 1, "screen"); + return; + } + inputModel.moveHistorySelection(1); + this.lastHistoryUpDown = true; + } + + @boundMethod + controlN() { + let inputModel = GlobalModel.inputModel; + inputModel.moveHistorySelection(-1); + this.lastHistoryUpDown = true; + } + + @boundMethod + controlW() { + if (this.mainInputRef.current == null) { + return; + } + let selStart = this.mainInputRef.current.selectionStart; + let value = this.mainInputRef.current.value; + if (selStart > value.length) { + return; + } + let cutSpot = selStart - 1; + let initial = true; + for (; cutSpot >= 0; cutSpot--) { + let ch = value[cutSpot]; + console.log(cutSpot, "[" + ch + "]"); + if (ch == " " && initial) { + continue; + } + initial = false; + if (ch == " ") { + cutSpot++; + break; + } + } + let cutValue = value.slice(cutSpot, selStart); + let prevValue = value.slice(0, cutSpot); + let restValue = value.slice(selStart); + let cmdLineUpdate = { cmdline: prevValue + restValue, cursorpos: prevValue.length }; + console.log( + "ss", + selStart, + value, + "prev[" + prevValue + "]", + "cut[" + cutValue + "]", + "rest[" + restValue + "]" + ); + console.log(" ", cmdLineUpdate); + navigator.clipboard.writeText(cutValue); + GlobalModel.inputModel.updateCmdLine(cmdLineUpdate); + } + + @boundMethod + controlY() { + if (this.mainInputRef.current == null) { + return; + } + let pastePromise = navigator.clipboard.readText(); + pastePromise.then((clipText) => { + clipText = clipText ?? ""; + let selStart = this.mainInputRef.current.selectionStart; + let selEnd = this.mainInputRef.current.selectionEnd; + let value = this.mainInputRef.current.value; + if (selStart > value.length || selEnd > value.length) { + return; + } + let newValue = value.substr(0, selStart) + clipText + value.substr(selEnd); + let cmdLineUpdate = { cmdline: newValue, cursorpos: selStart + clipText.length }; + GlobalModel.inputModel.updateCmdLine(cmdLineUpdate); + }); + } + + @boundMethod + handleHistoryInput(e: any) { + let inputModel = GlobalModel.inputModel; + mobx.action(() => { + let opts = mobx.toJS(inputModel.historyQueryOpts.get()); + opts.queryStr = e.target.value; + inputModel.setHistoryQueryOpts(opts); + })(); + } + + @boundMethod + handleMainFocus(e: any) { + let inputModel = GlobalModel.inputModel; + if (inputModel.historyShow.get()) { + e.preventDefault(); + if (this.historyInputRef.current != null) { + this.historyInputRef.current.focus(); + } + return; + } + inputModel.setPhysicalInputFocused(true); + } + + @boundMethod + handleMainBlur(e: any) { + if (document.activeElement == this.mainInputRef.current) { + return; + } + GlobalModel.inputModel.setPhysicalInputFocused(false); + } + + @boundMethod + handleHistoryFocus(e: any) { + let inputModel = GlobalModel.inputModel; + if (!inputModel.historyShow.get()) { + e.preventDefault(); + if (this.mainInputRef.current != null) { + this.mainInputRef.current.focus(); + } + return; + } + inputModel.setPhysicalInputFocused(true); + } + + @boundMethod + handleHistoryBlur(e: any) { + if (document.activeElement == this.historyInputRef.current) { + return; + } + GlobalModel.inputModel.setPhysicalInputFocused(false); + } + + render() { + let model = GlobalModel; + let inputModel = model.inputModel; + let curLine = inputModel.getCurLine(); + let fcp = inputModel.forceCursorPos.get(); // for reaction + let displayLines = 1; + let numLines = curLine.split("\n").length; + let maxCols = this.getTextAreaMaxCols(); + let longLine = false; + if (maxCols != 0 && curLine.length >= maxCols - 4) { + longLine = true; + } + if (numLines > 1 || longLine || inputModel.inputExpanded.get()) { + displayLines = 5; + } + let disabled = inputModel.historyShow.get(); + if (disabled) { + displayLines = 1; + } + let activeScreen = GlobalModel.getActiveScreen(); + if (activeScreen != null) { + activeScreen.focusType.get(); // for reaction + } + let computedHeight = displayLines * 24 + 14 + 2; // 24 = height of line, 14 = padding, 2 = border + return ( +
+ + +
+ ); + } +} + +export { TextAreaInput }; diff --git a/src/cmdinput.less b/src/main/sessionview/sessionview.less similarity index 92% rename from src/cmdinput.less rename to src/main/sessionview/sessionview.less index 89239b80..7d18559b 100644 --- a/src/cmdinput.less +++ b/src/main/sessionview/sessionview.less @@ -1,3 +1,5 @@ +@import "../../index.less"; + .cmd-input-info, .cmd-history { &::-webkit-scrollbar { @@ -11,45 +13,6 @@ } } -.info-message { - position: relative; - font-weight: normal; - font-size: 12px; - color: @term-white; - - .message-content { - position: absolute; - display: none; - flex-direction: row; - align-items: flex-start; - top: -6px; - left: -6px; - padding: 5px; - border: 1px solid #777; - background-color: #444; - border-radius: 5px; - z-index: 5; - overflow: hidden; - - .info-icon { - margin-right: 5px; - width: 12px; - flex-shrink: 0; - } - - .info-children { - flex: 1 0 0; - overflow: hidden; - } - } - - &:hover { - .message-content { - display: flex; - } - } -} - .cmd-input { border-radius: 0; border-top: 4px solid #ccc; diff --git a/src/tabs.less b/src/main/sessionview/tabs.less similarity index 99% rename from src/tabs.less rename to src/main/sessionview/tabs.less index 6d7d5985..efc3eb77 100644 --- a/src/tabs.less +++ b/src/main/sessionview/tabs.less @@ -1,3 +1,5 @@ +@import "../../index.less"; + #main .screen-tabs .screen-tab { &.color-green { color: @tab-white-text; diff --git a/src/main/sidebar/MainSideBar.tsx b/src/main/sidebar/MainSideBar.tsx new file mode 100644 index 00000000..f49b2275 --- /dev/null +++ b/src/main/sidebar/MainSideBar.tsx @@ -0,0 +1,346 @@ +import * as React from "react"; +import * as mobxReact from "mobx-react"; +import * as mobx from "mobx"; +import { boundMethod } from "autobind-decorator"; +import { If, For } from "tsx-control-statements/components"; +import cn from "classnames"; +import dayjs from "dayjs"; +import type { RemoteType } from "../../types"; +import type * as T from "../../types"; +import localizedFormat from "dayjs/plugin/localizedFormat"; +import { GlobalModel, GlobalCommandRunner, Session } from "../../model"; + +import { sortAndFilterRemotes, isBlank } from "../../util"; + +import { RemoteStatusLight } from "../../common/common"; + +import "./sidebar.less"; + +dayjs.extend(localizedFormat); + +type OV = mobx.IObservableValue; + +@mobxReact.observer +class MainSideBar extends React.Component<{}, {}> { + collapsed: mobx.IObservableValue = mobx.observable.box(false); + + @boundMethod + toggleCollapsed() { + mobx.action(() => { + this.collapsed.set(!this.collapsed.get()); + })(); + } + + handleSessionClick(sessionId: string) { + GlobalCommandRunner.switchSession(sessionId); + } + + handleNewSession() { + GlobalCommandRunner.createNewSession(); + } + + handleNewSharedSession() { + GlobalCommandRunner.openSharedSession(); + } + + clickLinks() { + mobx.action(() => { + GlobalModel.showLinks.set(!GlobalModel.showLinks.get()); + })(); + } + + remoteDisplayName(remote: RemoteType): any { + if (!isBlank(remote.remotealias)) { + return ( + <> + {remote.remotealias} + {remote.remotecanonicalname} + + ); + } + return {remote.remotecanonicalname}; + } + + clickRemote(remote: RemoteType) { + GlobalCommandRunner.showRemote(remote.remoteid); + } + + @boundMethod + handleAddRemote(): void { + GlobalCommandRunner.openCreateRemote(); + } + + @boundMethod + handleHistoryClick(): void { + if (GlobalModel.activeMainView.get() == "history") { + mobx.action(() => { + GlobalModel.activeMainView.set("session"); + })(); + return; + } + GlobalModel.historyViewModel.reSearch(); + } + + @boundMethod + handlePlaybookClick(): void { + console.log("playbook click"); + return; + } + + @boundMethod + handleBookmarksClick(): void { + if (GlobalModel.activeMainView.get() == "bookmarks") { + GlobalModel.showSessionView(); + return; + } + GlobalCommandRunner.bookmarksView(); + } + + @boundMethod + handleWebSharingClick(): void { + if (GlobalModel.activeMainView.get() == "webshare") { + GlobalModel.showSessionView(); + return; + } + GlobalModel.showWebShareView(); + } + + @boundMethod + handleWelcomeClick(): void { + mobx.action(() => { + GlobalModel.welcomeModalOpen.set(true); + })(); + } + + @boundMethod + handleSettingsClick(): void { + mobx.action(() => { + GlobalModel.clientSettingsModal.set(true); + })(); + } + + @boundMethod + handleConnectionsClick(): void { + GlobalModel.remotesModalModel.openModal(); + } + + @boundMethod + openSessionSettings(e: any, session: Session): void { + e.preventDefault(); + e.stopPropagation(); + mobx.action(() => { + GlobalModel.sessionSettingsModal.set(session.sessionId); + })(); + } + + render() { + let model = GlobalModel; + let activeSessionId = model.activeSessionId.get(); + let activeScreen = model.getActiveScreen(); + let activeRemoteId: string = null; + if (activeScreen != null) { + let rptr = activeScreen.curRemote.get(); + if (rptr != null && !isBlank(rptr.remoteid)) { + activeRemoteId = rptr.remoteid; + } + } + let session: Session = null; + let remotes = model.remotes ?? []; + let remote: RemoteType = null; + let idx: number = 0; + remotes = sortAndFilterRemotes(remotes); + let sessionList = []; + for (let session of model.sessionList) { + if (!session.archived.get() || session.sessionId == activeSessionId) { + sessionList.push(session); + } + } + let isCollapsed = this.collapsed.get(); + let mainView = GlobalModel.activeMainView.get(); + return ( +
+
+

+ {isCollapsed ? "[p]" : "[prompt]"} +

+
+
+
+ + + + + + +
+
+
+

Sessions

+ + + +

Playbooks

+ +
+ +
+ focus={activeScreen.focusType.get()} +
+ sline={activeScreen.getSelectedLine()} +
+ termfocus={activeScreen.termLineNumFocus.get()} +
+
+
+ + +

+ this.clickLinks()}> + LINKS{" "} + + +

+ +

+ Connections +

+ +
+
+
+ ); + } +} + +export { MainSideBar }; diff --git a/src/sidebar.less b/src/main/sidebar/sidebar.less similarity index 99% rename from src/sidebar.less rename to src/main/sidebar/sidebar.less index 97ba302e..255015fa 100644 --- a/src/sidebar.less +++ b/src/main/sidebar/sidebar.less @@ -1,3 +1,5 @@ +@import "../../index.less"; + .main-sidebar .logo-header { display: flex; flex-direction: row; diff --git a/src/modals.tsx b/src/modals.tsx deleted file mode 100644 index b2366283..00000000 --- a/src/modals.tsx +++ /dev/null @@ -1,62 +0,0 @@ -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 cn from "classnames"; -import { GlobalModel, GlobalCommandRunner } from "./model"; -import * as util from "./util"; - -type OV = mobx.IObservableValue; - -class TosModal extends React.Component<{}, {}> { - @boundMethod - acceptTos(): void { - GlobalCommandRunner.clientAcceptTos(); - } - - render() { - return ( -
-
-
-
-
Welcome to [prompt]
-
-
-
-

Thank you for downloading Prompt!

-

- 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{" "} - - discord server - - . -

-

- Prompt is free to use, no email or registration required (unless you're using the cloud - features). -

-

- - Full Terms of Service - -

-
-
-
-
-
- Accept Terms of Service -
-
-
-
- ); - } -} - -export { TosModal }; diff --git a/src/model.ts b/src/model.ts index ed0dd6cd..cff499ec 100644 --- a/src/model.ts +++ b/src/model.ts @@ -72,7 +72,7 @@ import { import dayjs from "dayjs"; import localizedFormat from "dayjs/plugin/localizedFormat"; import customParseFormat from "dayjs/plugin/customParseFormat"; -import { getRendererContext, cmdStatusIsRunning } from "./lineutil"; +import { getRendererContext, cmdStatusIsRunning } from "./main/line/lineutil"; dayjs.extend(customParseFormat); dayjs.extend(localizedFormat); diff --git a/src/view/code/index.tsx b/src/plugins/code/index.tsx similarity index 99% rename from src/view/code/index.tsx rename to src/plugins/code/index.tsx index 9212e1d8..ba85dfbf 100644 --- a/src/view/code/index.tsx +++ b/src/plugins/code/index.tsx @@ -1,13 +1,15 @@ import * as React from "react"; import { RendererContext, RendererOpts, LineStateType, RendererModelContainerApi } from "../../types"; import Editor from "@monaco-editor/react"; -import { Markdown } from "../../elements"; +import { Markdown } from "../../common/common"; import { GlobalModel, GlobalCommandRunner } from "../../model"; import Split from "react-split-it"; -import "./split.less"; import loader from "@monaco-editor/loader"; loader.config({ paths: { vs: "./node_modules/monaco-editor/min/vs" } }); +import "./split.less"; +import "../plugins.less"; + function renderCmdText(text: string): any { return ⌘{text}; } diff --git a/src/view/code/split.less b/src/plugins/code/split.less similarity index 100% rename from src/view/code/split.less rename to src/plugins/code/split.less diff --git a/src/view/image.tsx b/src/plugins/image.tsx similarity index 92% rename from src/view/image.tsx rename to src/plugins/image.tsx index 1df5e3b2..5328f2ad 100644 --- a/src/view/image.tsx +++ b/src/plugins/image.tsx @@ -1,9 +1,9 @@ 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 { RendererContext, RendererOpts } from "../types"; + +import "./plugins.less"; type OV = mobx.IObservableValue; type CV = mobx.IComputedValue; diff --git a/src/view/markdown.tsx b/src/plugins/markdown.tsx similarity index 87% rename from src/view/markdown.tsx rename to src/plugins/markdown.tsx index 8b1fceee..42c9ec58 100644 --- a/src/view/markdown.tsx +++ b/src/plugins/markdown.tsx @@ -5,7 +5,9 @@ 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 { Markdown } from "../common/common"; + +import "./plugins.less"; type OV = mobx.IObservableValue; @@ -44,7 +46,10 @@ class SimpleMarkdownRenderer extends React.Component< let dataBlob = this.props.data; if (dataBlob == null || dataBlob.notFound) { return ( -
+
ERROR: file {dataBlob && dataBlob.name ? JSON.stringify(dataBlob.name) : ""} not found
@@ -53,7 +58,10 @@ class SimpleMarkdownRenderer extends React.Component< } if (this.markdownError.get() != null) { return ( -
+
{this.markdownError.get()}
); diff --git a/src/view/mustache.tsx b/src/plugins/mustache.tsx similarity index 99% rename from src/view/mustache.tsx rename to src/plugins/mustache.tsx index c20fe36b..d0f17cd8 100644 --- a/src/view/mustache.tsx +++ b/src/plugins/mustache.tsx @@ -11,9 +11,9 @@ import mustache from "mustache"; import * as DOMPurify from "dompurify"; import { GlobalModel } from "../model"; -type OV = mobx.IObservableValue; +import "./plugins.less"; -const MaxMustacheSize = 200000; +type OV = mobx.IObservableValue; @mobxReact.observer class SimpleMustacheRenderer extends React.Component< diff --git a/src/view/openai.tsx b/src/plugins/openai.tsx similarity index 96% rename from src/view/openai.tsx rename to src/plugins/openai.tsx index 19fcd421..19c5d03c 100644 --- a/src/view/openai.tsx +++ b/src/plugins/openai.tsx @@ -1,18 +1,16 @@ 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 * as T from "../types"; -import { debounce, throttle } from "throttle-debounce"; +import { debounce } from "throttle-debounce"; import { boundMethod } from "autobind-decorator"; import { sprintf } from "sprintf-js"; import { PacketDataBuffer } from "../ptydata"; -import { Markdown } from "../elements"; +import { Markdown } from "../common/common"; + +import "./plugins.less"; type OV = mobx.IObservableValue; -type OArr = mobx.IObservableArray; -type OMap = mobx.ObservableMap; type OpenAIOutputType = { model: string; diff --git a/src/plugins/plugins.less b/src/plugins/plugins.less new file mode 100644 index 00000000..ceb28681 --- /dev/null +++ b/src/plugins/plugins.less @@ -0,0 +1,337 @@ +@import "../index.less"; + +.alt-view { + background-color: #111; + overflow-y: auto; + flex-grow: 1; + + .alt-title { + margin: 20px 10px 0px 5px; + padding-left: 10px; + padding-bottom: 12px; + .mono-font(1.5rem); + color: @term-bright-white; + border-bottom: 1px solid white; + } + + .alt-list { + color: white; + margin: 4px 10px 5px 5px; + border-bottom: 1px solid white; + } + + .no-content { + color: @term-white; + padding: 30px 10px 35px 10px; + border-bottom: 1px solid white; + } + + .close-button { + position: absolute; + padding: 4px; + font-size: 24px; + color: #aaa; + right: 15px; + top: 18px; + cursor: pointer; + + &:hover { + color: #fff; + } + } + + .alt-help { + color: @term-white; + margin-top: 20px; + display: flex; + flex-direction: row; + align-items: center; + font-size: 12px; + margin-bottom: 10px; + + .help-entry { + margin-left: 20px; + } + } +} + +.image-renderer { + padding: 10px; + img { + display: block; + } +} + +.renderer-container { + .error-container { + color: @term-red; + font-size: 14px; + padding: 5px; + } + + .scroller { + overflow: auto; + display: flex; + flex-direction: row; + overscroll-behavior: contain; + } + + .dropdown { + background: #dbdbdb; + color: black; + border-radius: 6px 6px 0 0; + font-size: 10px; + padding: 2px 0 5px 5px; + outline: none; + } +} + +.renderer-container.code-renderer { + .scroller { + padding-top: 10px; + padding-bottom: 15px; + } + + .monaco-editor .monaco-editor-background { + background-color: rgba(255, 255, 255, 0.075) !important; + } + .monaco-editor .scrollbar { + height: 4px !important; + width: 4px !important; + } + .monaco-editor .scrollbar .slider { + background-color: rgba(255, 255, 255) !important; + } + + .cmd-hints, + .dropdown { + display: inline-block; + position: relative; + min-width: 6rem; + max-width: 6rem; + margin-right: 8px; + } + .hint-item { + border-radius: 4px 4px 0 0; + padding: 3px 9px 2px 8px; + line-height: 19px; + text-align: center; + } + section { + transition: height 0.3s ease-in-out; + } + .preview { + color: #000; + background-color: rgb(200, 200, 200); + } + .preview:hover { + background-color: white !important; + } + .save-enabled { + color: white; + background-color: #4e9a06; + } + .save-disabled { + color: rgb(52, 52, 52); + background-color: #aaaea7; + cursor: default !important; + } + .save-disabled:hover { + background-color: #aaaea7; + } + .close { + color: white; + background-color: #9e0000; + } + .message { + color: white; + border-radius: 6px; + margin-bottom: 1rem; + padding: 4px 1rem; + max-width: 80vw; + } + .readonly { + .mono-font(12px); + position: absolute; + top: calc(1.5rem + 3px); + right: 10rem; + border-radius: 5px; + background-color: @term-bright-red; + color: white; + z-index: 1; + padding: 0 6px 2px; + } +} + +.renderer-container.mustache-renderer { + color: @term-white; + .cmd-hints { + display: inline-block !important; + position: relative; + margin-right: 26px; + } + .hint-item { + border-radius: 4px 4px 0 0; + padding: 3px 9px 2px 8px; + line-height: 15px; + text-align: center; + } + .refresh-button { + color: rgb(52, 52, 52); + background-color: @term-white; + } +} + +.renderer-container .content { + padding: 5px; + line-height: 1.5; + width: fit-content; + + blockquote { + background-color: #222; + } + + code { + background-color: #222; + font-size: 14px; + } + + pre { + background-color: #222; + margin: 2px 10px 6px 10px; + padding: 4px 4px 4px 6px; + } + + h1, + h2, + h3, + h4, + h5, + h6 { + color: @term-white; + } +} + +.openai-renderer { + .openai-message { + display: flex; + flex-direction: row; + justify-content: flex-start; + + .openai-role { + color: @term-bright-green; + font-weight: bold; + width: 100px; + } + + .openai-role.openai-role-assistant { + color: @term-bright-white; + } + + .openai-content-user { + white-space: pre; + color: white; + } + + .openai-content-assistant { + color: white; + } + + .openai-role-error { + color: @term-bright-red; + } + + .openai-content-error { + color: @term-bright-red; + } + } +} + +.markdown { + color: @term-white; + margin-bottom: 10px; + + code { + background-color: black; + color: white; + .mono-font(); + padding: 5px; + } + + code.inline { + padding-top: 0; + padding-bottom: 0; + } + + .title { + color: white; + margin-top: 16px; + line-height: 1.25; + margin-bottom: 8px; + } + + strong { + color: white; + } + + a { + color: #32afff; + } + + table { + tr th { + color: white; + } + } + + ul { + list-style-type: disc; + list-style-position: outside; + margin-left: 16px; + } + + ol { + list-style-position: outside; + margin-left: 19px; + } + + blockquote { + margin: 4px 10px 4px 10px; + border-radius: 3px; + background-color: #444; + padding: 2px 4px 2px 6px; + } + + pre { + background-color: inherit; + margin: 4px 10px 4px 10px; + padding: 2px 4px 2px 6px; + } + + .title.is-1 { + font-size: 32px; + border-bottom: 1px solid #777; + padding-bottom: 6px; + } + .title.is-2 { + font-size: 24px; + border-bottom: 1px solid #777; + padding-bottom: 6px; + } + .title.is-3 { + font-size: 20px; + } + .title.is-4 { + font-size: 16px; + } + .title.is-5 { + font-size: 14px; + } + .title.is-6 { + font-size: 14px; + } +} + +.markdown > *:first-child { + margin-top: 0 !important; +} diff --git a/src/plugins.ts b/src/plugins/plugins.ts similarity index 88% rename from src/plugins.ts rename to src/plugins/plugins.ts index 09aea67a..78e70898 100644 --- a/src/plugins.ts +++ b/src/plugins/plugins.ts @@ -1,10 +1,10 @@ -import { RendererPluginType } from "./types"; -import { SimpleImageRenderer } from "./view/image"; -import { SimpleMarkdownRenderer } from "./view/markdown"; -import { SourceCodeRenderer } from "./view/code"; -import { SimpleMustacheRenderer } from "./view/mustache"; -import { OpenAIRenderer, OpenAIRendererModel } from "./view/openai"; -import { isBlank } from "./util"; +import { RendererPluginType } from "../types"; +import { SimpleImageRenderer } from "./image"; +import { SimpleMarkdownRenderer } from "./markdown"; +import { SourceCodeRenderer } from "./code"; +import { SimpleMustacheRenderer } from "./mustache"; +import { OpenAIRenderer, OpenAIRendererModel } from "./openai"; +import { isBlank } from "../util"; import { sprintf } from "sprintf-js"; const ImagePlugin: RendererPluginType = { diff --git a/src/prompt.less b/src/prompt.less deleted file mode 100644 index 26e0c242..00000000 --- a/src/prompt.less +++ /dev/null @@ -1,416 +0,0 @@ -@term-black: #000000; -@term-red: #cc0000; -@term-green: #4e9a06; -@term-yellow: #c4a000; -@term-blue: #3465a4; -@term-magenta: #75507b; -@term-cyan: #06989a; -@term-white: #d3d7cf; -@term-bright-black: #555753; -@term-bright-red: #ef2929; -@term-bright-green: #8ae234; -@term-bright-yellow: #fce94f; -@term-bright-blue: #32afff; -@term-bright-magenta: #ad7fa8; -@term-bright-cyan: #34e2e2; -@term-bright-white: #ffffff; - -@tab-black: rgb(0, 0, 0); -@tab-red: rgb(205, 49, 49); -@tab-green: rgb(0, 128, 0); -@tab-orange: rgb(255, 199, 6); -@tab-yellow: rgb(229, 229, 16); -@tab-blue: rgb(0, 71, 171); -@tab-magenta: rgb(188, 63, 188); -@tab-cyan: rgb(17, 168, 205); -@tab-white: rgb(249, 249, 249); - -@tab-black-text: #333; -@tab-white-text: #d7d7d7; - -@prompt-green: rgb(0, 177, 10); - -@soft-blue: #729fcf; - -@active-menu-color: rgb(0, 71, 171); - -@import "utils.less"; -@import "webshare.less"; -@import "views.less"; -@import "sidebar.less"; -@import "modals.less"; // includes settings -@import "comps.less"; // includes terminal -@import "tabs.less"; -@import "cmdinput.less"; -@import "lines.less"; - -// global settings / overrides - -:root { - --fa-style-family: "Font Awesome 6 Sharp"; -} - -html, -body, -#app, -#main { - background-color: #000; - height: 100vh; -} - -.content { - a:hover { - color: #485fc7; - } -} - -body { - overflow: hidden; -} - -body::-webkit-scrollbar { - display: none; -} - -*::-webkit-scrollbar { - background-color: #777; - width: 5px; - height: 5px; -} - -*::-webkit-scrollbar-thumb { - background: white; -} - -input[type="checkbox"] { - cursor: pointer; -} - -// main layout -#main { - height: 100vh; - display: flex; - flex-direction: column; - - .main-content { - display: flex; - flex-direction: row; - background-color: black; - height: 100%; - - .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; - 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; -} - -.image-renderer { - padding: 10px; - img { - display: block; - } -} - -.renderer-container { - .error-container { - color: @term-red; - font-size: 14px; - padding: 5px; - } - - .scroller { - overflow: auto; - display: flex; - flex-direction: row; - overscroll-behavior: contain; - } - - .dropdown { - background: #dbdbdb; - color: black; - border-radius: 6px 6px 0 0; - font-size: 10px; - padding: 2px 0 5px 5px; - outline: none; - } -} - -.renderer-container.code-renderer { - .scroller { - padding-top: 10px; - padding-bottom: 15px; - } - - .monaco-editor .monaco-editor-background { - background-color: rgba(255, 255, 255, 0.075) !important; - } - .monaco-editor .scrollbar { - height: 4px !important; - width: 4px !important; - } - .monaco-editor .scrollbar .slider { - background-color: rgba(255, 255, 255) !important; - } - - .cmd-hints, - .dropdown { - display: inline-block; - position: relative; - min-width: 6rem; - max-width: 6rem; - margin-right: 8px; - } - .hint-item { - border-radius: 4px 4px 0 0; - padding: 3px 9px 2px 8px; - line-height: 19px; - text-align: center; - } - section { - transition: height 0.3s ease-in-out; - } - .preview { - color: #000; - background-color: rgb(200, 200, 200); - } - .preview:hover { - background-color: white !important; - } - .save-enabled { - color: white; - background-color: #4e9a06; - } - .save-disabled { - color: rgb(52, 52, 52); - background-color: #aaaea7; - cursor: default !important; - } - .save-disabled:hover { - background-color: #aaaea7; - } - .close { - color: white; - background-color: #9e0000; - } - .message { - color: white; - border-radius: 6px; - margin-bottom: 1rem; - padding: 4px 1rem; - max-width: 80vw; - } - .readonly { - .mono-font(12px); - position: absolute; - top: calc(1.5rem + 3px); - right: 10rem; - border-radius: 5px; - background-color: @term-bright-red; - color: white; - z-index: 1; - padding: 0 6px 2px; - } -} - -.renderer-container.json-renderer { - padding: 10px; - font-size: 12px; -} - -.renderer-container.mustache-renderer { - color: @term-white; - .cmd-hints { - display: inline-block !important; - position: relative; - margin-right: 26px; - } - .hint-item { - border-radius: 4px 4px 0 0; - padding: 3px 9px 2px 8px; - line-height: 15px; - text-align: center; - } - .refresh-button { - color: rgb(52, 52, 52); - background-color: @term-white; - } -} - -.renderer-container .content { - padding: 5px; - line-height: 1.5; - width: fit-content; - - blockquote { - background-color: #222; - } - - code { - background-color: #222; - font-size: 14px; - } - - pre { - background-color: #222; - margin: 2px 10px 6px 10px; - padding: 4px 4px 4px 6px; - } - - h1, - h2, - h3, - h4, - h5, - h6 { - color: @term-white; - } -} - -.openai-renderer { - .openai-message { - display: flex; - flex-direction: row; - justify-content: flex-start; - - .openai-role { - color: @term-bright-green; - font-weight: bold; - width: 100px; - } - - .openai-role.openai-role-assistant { - color: @term-bright-white; - } - - .openai-content-user { - white-space: pre; - color: white; - } - - .openai-content-assistant { - color: white; - } - - .openai-role-error { - color: @term-bright-red; - } - - .openai-content-error { - color: @term-bright-red; - } - } -} diff --git a/src/remotes/remotes.less b/src/remotes/remotes.less new file mode 100644 index 00000000..dac58292 --- /dev/null +++ b/src/remotes/remotes.less @@ -0,0 +1,248 @@ +@import "../index.less"; + +.modal.prompt-modal.remotes-modal { + .modal-content { + min-width: 850px; + } + + .inner-content { + display: flex; + flex-direction: row; + align-items: stretch; + padding: 0; + max-height: 80vh; + + .remotes-menu { + flex: 0 0 200px; + min-height: 450px; + border-right: 1px solid #666; + overflow-y: auto; + height: 100px; + + .remote-menu-item { + border-top: 1px solid #666; + padding: 5px; + display: flex; + flex-direction: row; + cursor: pointer; + + &.add-remote { + font-size: 13px; + padding: 10px 5px 10px 5px; + } + + &:hover { + background-color: #333; + } + + &.is-selected { + background-color: @active-menu-color; + + .remote-name .remote-name-secondary { + color: white; + } + } + + &:first-child { + border-top: 0; + } + + .remote-status-light { + width: 15px; + margin-top: -2px; + } + + .remote-name { + flex-grow: 1; + + .remote-name-primary { + font-size: 12px; + font-weight: bold; + } + + .remote-name-secondary { + font-size: 11px; + color: #777; + } + } + } + } + + .remote-detail { + padding: 10px; + flex-grow: 1; + font-size: 12px; + display: flex; + flex-direction: column; + + .settings-field { + margin-top: 5px; + } + + * { + flex-shrink: 0; + } + + .detail-subtitle { + font-size: 18px; + margin-bottom: 10px; + margin-top: 10px; + } + + .title { + color: white; + padding-bottom: 8px; + margin-bottom: 0; + border-bottom: 1px solid #777; + } + + .terminal-wrapper { + margin-left: 0; + margin-bottom: 0; + + &.has-message { + margin-top: 0; + } + + box-shadow: none; + border: 1px solid #777; + } + + .action-buttons { + display: flex; + flex-direction: row; + gap: 10px; + margin-top: 2px; + } + + .remote-message { + margin-top: 5px; + padding: 8px; + border-radius: 5px 5px 0 0; + background-color: #333; + + i.fa-check { + color: @term-green; + } + + &.is-ok { + } + + .message-row { + display: flex; + flex-direction: row; + align-items: center; + } + + .remote-status { + position: relative; + top: -1px; + } + + .button { + height: 22px; + } + } + + .settings-field { + .update-auth-button { + visibility: hidden; + } + + &:hover { + .update-auth-button { + visibility: visible; + } + + .hide-hover { + display: none; + } + } + } + + &.auth-editing, + &.create-remote { + .settings-field.align-top { + align-items: flex-start; + + .settings-label { + margin-top: 8px; + } + + .settings-input { + align-items: flex-start; + } + } + + .settings-label { + display: flex; + flex-direction: row; + align-items: center; + width: 135px; + } + + .settings-field .settings-input .undo-icon { + cursor: pointer; + font-size: 18px; + margin-left: 5px; + } + + .editremote-dropdown .dropdown-trigger button { + width: 120px; + justify-content: flex-start; + } + + .settings-field .raw-input { + width: 120px; + } + + .settings-input input { + width: 250px; + } + + .dropdown .dropdown-trigger button { + font-size: 12px; + } + + .dropdown .dropdown-item { + font-size: 12px; + padding: 5px 5px 5px 12px; + } + + .settings-input { + .info-message { + margin-left: 22px; + } + } + + .settings-label { + .info-message { + margin-right: 15px; + } + } + } + } + } + + .terminal-wrapper { + position: relative; + background-color: #000; + padding: 2px 10px 5px 4px; + margin: 5px 5px 10px 5px; + box-shadow: 0 0 1px 1px rgba(255, 255, 255, 0.3); + &.focus { + box-shadow: 0 0 3px 3px rgba(255, 255, 255, 0.3); + } + + .term-tag { + position: absolute; + top: 0; + right: 0; + background-color: @term-red; + color: white; + z-index: 110; + padding: 4px; + .mono-font(10px); + } + } +} diff --git a/src/remotes.tsx b/src/remotes/remotes.tsx similarity index 98% rename from src/remotes.tsx rename to src/remotes/remotes.tsx index f6e5c918..7c01d094 100644 --- a/src/remotes.tsx +++ b/src/remotes/remotes.tsx @@ -1,16 +1,16 @@ 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 { If, For } from "tsx-control-statements/components"; import cn from "classnames"; -import { GlobalModel, GlobalCommandRunner, getTermPtyData, RemotesModalModel } from "./model"; -import { Toggle, RemoteStatusLight, InlineSettingsTextEdit, InfoMessage } from "./elements"; -import { RemoteType, RemoteInputPacketType, RemoteEditType } from "./types"; -import * as util from "./util"; -import * as textmeasure from "./textmeasure"; -import { TermWrap } from "./term"; +import { GlobalModel, GlobalCommandRunner, RemotesModalModel } from "../model"; +import { Toggle, RemoteStatusLight, InfoMessage } from "../common/common"; +import { RemoteType, RemoteEditType } from "../types"; +import * as util from "../util"; +import * as textmeasure from "../textmeasure"; + +import "./remotes.less"; type OV = mobx.IObservableValue; type OArr = mobx.IObservableArray; diff --git a/src/webshare-client-view.tsx b/src/webshare/webshare-client-view.tsx similarity index 97% rename from src/webshare-client-view.tsx rename to src/webshare/webshare-client-view.tsx index dbf567c3..92f93c17 100644 --- a/src/webshare-client-view.tsx +++ b/src/webshare/webshare-client-view.tsx @@ -7,9 +7,11 @@ import { v4 as uuidv4 } from "uuid"; import dayjs from "dayjs"; 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 * as util from "./util"; +import { GlobalModel, GlobalCommandRunner, Screen } from "../model"; +import { WebStopShareConfirmMarkdown } from "../main/modals/settings"; +import * as util from "../util"; + +import "./webshare.less"; type OV = mobx.IObservableValue; type OArr = mobx.IObservableArray; diff --git a/src/webshare-elems.tsx b/src/webshare/webshare-elems.tsx similarity index 100% rename from src/webshare-elems.tsx rename to src/webshare/webshare-elems.tsx diff --git a/src/webshare-model.ts b/src/webshare/webshare-model.ts similarity index 100% rename from src/webshare-model.ts rename to src/webshare/webshare-model.ts diff --git a/src/webshare-ws.ts b/src/webshare/webshare-ws.ts similarity index 100% rename from src/webshare-ws.ts rename to src/webshare/webshare-ws.ts diff --git a/src/webshare.less b/src/webshare/webshare.less similarity index 76% rename from src/webshare.less rename to src/webshare/webshare.less index ed744f2b..12b4bfd8 100644 --- a/src/webshare.less +++ b/src/webshare/webshare.less @@ -1,3 +1,5 @@ +@import "../index.less"; + body.prompt-webshare #main { display: flex; flex-direction: column; @@ -124,3 +126,42 @@ body.prompt-webshare #main { } } } + +.webshare-view { + .webshare-item { + padding: 4px 5px 8px 15px; + margin-bottom: 4px; + border-top: 1px solid white; + display: flex; + flex-direction: row; + position: relative; + min-height: 55px; + align-items: center; + + &:first-child { + border-top: 0; + } + + .webshare-vic { + width: 200px; + + .webshare-vic-link { + cursor: pointer; + + &:hover { + text-decoration: underline; + } + } + } + + .actions { + display: flex; + flex-direction: row; + gap: 10px; + } + + &:hover { + background-color: #222; + } + } +} diff --git a/src/webshare.ts b/src/webshare/webshare.ts similarity index 100% rename from src/webshare.ts rename to src/webshare/webshare.ts diff --git a/webpack.common.js b/webpack.common.js index a2d563ff..cdddc8a3 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -5,7 +5,7 @@ const path = require("path"); module.exports = { mode: "development", entry: { - prompt: ["./src/prompt.ts", "./src/prompt.less"], + prompt: ["./src/index.ts", "./src/index.less"], }, output: { path: path.resolve(__dirname, "dist"), diff --git a/webpack.share.js b/webpack.share.js index 289085db..33be3653 100644 --- a/webpack.share.js +++ b/webpack.share.js @@ -5,7 +5,7 @@ const path = require("path"); module.exports = { mode: "development", entry: { - webshare: ["./src/webshare.ts", "./src/prompt.less"], + webshare: ["./src/webshare.ts", "./src/index.less"], }, output: { path: path.resolve(__dirname, "webshare/dist"),