mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-03-11 13:23:06 +01:00
Pe 179 refactoring UI code (#25)
* refactored main JS files * modals have been refactored * lines and renderer refactored * all refactoring done. ready for reskinning
This commit is contained in:
parent
faeab0790a
commit
a25c7b4286
@ -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
|
||||
|
@ -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;
|
@ -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<V> = mobx.IObservableValue<V>;
|
||||
|
@ -1,4 +0,0 @@
|
||||
import { sprintf } from "sprintf-js";
|
||||
import { Database } from "better-sqlite3";
|
||||
|
||||
let DB = new Database();
|
641
src/index.less
Normal file
641
src/index.less
Normal file
@ -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;
|
||||
}
|
||||
}
|
@ -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";
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
2298
src/main.tsx
2298
src/main.tsx
File diff suppressed because it is too large
Load Diff
140
src/main/Main.tsx
Normal file
140
src/main/Main.tsx
Normal file
@ -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<V> = mobx.IObservableValue<V>;
|
||||
|
||||
@mobxReact.observer
|
||||
class Main extends React.Component<{}, {}> {
|
||||
dcWait: OV<boolean> = 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 (
|
||||
<div id="main" onContextMenu={this.handleContextMenu}>
|
||||
<div className="main-content">
|
||||
<MainSideBar />
|
||||
<div className="session-view" />
|
||||
</div>
|
||||
<If condition={dcWait}>
|
||||
<If condition={disconnected}>
|
||||
<DisconnectedModal />
|
||||
</If>
|
||||
<If condition={!disconnected && hasClientStop}>
|
||||
<ClientStopModal />
|
||||
</If>
|
||||
</If>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (dcWait) {
|
||||
setTimeout(() => this.updateDcWait(false), 0);
|
||||
}
|
||||
return (
|
||||
<div id="main" onContextMenu={this.handleContextMenu}>
|
||||
<div className="main-content">
|
||||
<MainSideBar />
|
||||
<SessionView />
|
||||
<HistoryView />
|
||||
<BookmarksView />
|
||||
<WebShareView />
|
||||
</div>
|
||||
<AlertModal />
|
||||
<If condition={GlobalModel.needsTos()}>
|
||||
<TosModal />
|
||||
</If>
|
||||
<If condition={GlobalModel.welcomeModalOpen.get()}>
|
||||
<WelcomeModal />
|
||||
</If>
|
||||
<If condition={screenSettingsModal != null}>
|
||||
<ScreenSettingsModal
|
||||
key={screenSettingsModal.sessionId + ":" + screenSettingsModal.screenId}
|
||||
sessionId={screenSettingsModal.sessionId}
|
||||
screenId={screenSettingsModal.screenId}
|
||||
/>
|
||||
</If>
|
||||
<If condition={sessionSettingsModal != null}>
|
||||
<SessionSettingsModal key={sessionSettingsModal} sessionId={sessionSettingsModal} />
|
||||
</If>
|
||||
<If condition={lineSettingsModal != null}>
|
||||
<LineSettingsModal key={String(lineSettingsModal)} linenum={lineSettingsModal} />
|
||||
</If>
|
||||
<If condition={clientSettingsModal}>
|
||||
<ClientSettingsModal />
|
||||
</If>
|
||||
<If condition={remotesModal}>
|
||||
<RemotesModal model={GlobalModel.remotesModalModel} />
|
||||
</If>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { Main };
|
99
src/main/bookmarks/bookmarks.less
Normal file
99
src/main/bookmarks/bookmarks.less
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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 }, {}> {
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
@ -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<V> = mobx.IObservableValue<V>;
|
@ -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 {
|
@ -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<V> = mobx.IObservableValue<V>;
|
||||
type OArr<V> = mobx.IObservableArray<V>;
|
@ -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);
|
||||
|
@ -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<V> = mobx.IObservableValue<V>;
|
@ -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<V> = mobx.IObservableValue<V>;
|
||||
type CV<V> = mobx.IComputedValue<V>;
|
357
src/main/modals/Modals.tsx
Normal file
357
src/main/modals/Modals.tsx
Normal file
@ -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<V> = mobx.IObservableValue<V>;
|
||||
|
||||
@mobxReact.observer
|
||||
class DisconnectedModal extends React.Component<{}, {}> {
|
||||
logRef: any = React.createRef();
|
||||
showLog: mobx.IObservableValue<boolean> = 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 (
|
||||
<div className="prompt-modal disconnected-modal modal is-active">
|
||||
<div className="modal-background"></div>
|
||||
<div className="modal-content">
|
||||
<div className="message-header">
|
||||
<div className="modal-title">Prompt Client Disconnected</div>
|
||||
</div>
|
||||
<If condition={this.showLog.get()}>
|
||||
<div className="inner-content">
|
||||
<div className="ws-log" ref={this.logRef}>
|
||||
<For each="logLine" index="idx" of={GlobalModel.ws.wsLog}>
|
||||
<div key={idx} className="ws-logline">
|
||||
{logLine}
|
||||
</div>
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
</If>
|
||||
<footer>
|
||||
<div className="footer-text-link" style={{ marginLeft: 10 }} onClick={this.handleShowLog}>
|
||||
<If condition={!this.showLog.get()}>
|
||||
<i className="fa-sharp fa-solid fa-plus" /> Show Log
|
||||
</If>
|
||||
<If condition={this.showLog.get()}>
|
||||
<i className="fa-sharp fa-solid fa-minus" /> Hide Log
|
||||
</If>
|
||||
</div>
|
||||
<div className="flex-spacer" />
|
||||
<button onClick={this.tryReconnect} className="button">
|
||||
<span className="icon">
|
||||
<i className="fa-sharp fa-solid fa-rotate" />
|
||||
</span>
|
||||
<span>Try Reconnect</span>
|
||||
</button>
|
||||
<button onClick={this.restartServer} className="button is-danger" style={{ marginLeft: 10 }}>
|
||||
<span className="icon">
|
||||
<i className="fa-sharp fa-solid fa-triangle-exclamation" />
|
||||
</span>
|
||||
<span>Restart Server</span>
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@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 (
|
||||
<div className="prompt-modal client-stop-modal modal is-active">
|
||||
<div className="modal-background"></div>
|
||||
<div className="modal-content">
|
||||
<div className="message-header">
|
||||
<div className="modal-title">[prompt] {title}</div>
|
||||
</div>
|
||||
<div className="inner-content">
|
||||
<If condition={cdata == null}>
|
||||
<div>Cannot get client data.</div>
|
||||
</If>
|
||||
</div>
|
||||
<footer>
|
||||
<button onClick={this.refreshClient} className="button">
|
||||
<span className="icon">
|
||||
<i className="fa-sharp fa-solid fa-rotate" />
|
||||
</span>
|
||||
<span>Hard Refresh Client</span>
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class LoadingSpinner extends React.Component<{}, {}> {
|
||||
render() {
|
||||
return (
|
||||
<div className="loading-spinner">
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@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 (
|
||||
<div className="modal prompt-modal is-active alert-modal">
|
||||
<div className="modal-background" />
|
||||
<div className="modal-content">
|
||||
<header>
|
||||
<p className="modal-title">
|
||||
<i className="fa-sharp fa-solid fa-triangle-exclamation" /> {title}
|
||||
</p>
|
||||
<div className="close-icon">
|
||||
<i onClick={this.closeModal} className="fa-sharp fa-solid fa-times" />
|
||||
</div>
|
||||
</header>
|
||||
<If condition={message.markdown}>
|
||||
<Markdown text={message.message} extraClassName="inner-content" />
|
||||
</If>
|
||||
<If condition={!message.markdown}>
|
||||
<div className="inner-content content">
|
||||
<p>{message.message}</p>
|
||||
</div>
|
||||
</If>
|
||||
<footer>
|
||||
<If condition={isConfirm}>
|
||||
<div onClick={this.closeModal} className="button is-prompt-cancel is-outlined is-small">
|
||||
Cancel
|
||||
</div>
|
||||
<div onClick={this.handleOK} className="button is-prompt-green is-outlined is-small">
|
||||
OK
|
||||
</div>
|
||||
</If>
|
||||
<If condition={!isConfirm}>
|
||||
<div onClick={this.handleOK} className="button is-prompt-green is-small">
|
||||
OK
|
||||
</div>
|
||||
</If>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class WelcomeModal extends React.Component<{}, {}> {
|
||||
totalPages: number = 3;
|
||||
pageNum: OV<number> = 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 <i key={String(num)} className="fa-sharp fa-solid fa-circle" />;
|
||||
}
|
||||
return <i key={String(num)} className="fa-sharp fa-regular fa-circle" />;
|
||||
}
|
||||
|
||||
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 (
|
||||
<div className={cn("modal welcome-modal prompt-modal is-active")}>
|
||||
<div className="modal-background" />
|
||||
<div className="modal-content">
|
||||
<header>
|
||||
<div className="modal-title">welcome to [prompt]</div>
|
||||
<div className="close-icon">
|
||||
<i onClick={this.closeModal} className="fa-sharp fa-solid fa-times" />
|
||||
</div>
|
||||
</header>
|
||||
<div className={cn("inner-content content", { "is-hidden": pageNum != 1 })}>
|
||||
<p>
|
||||
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!
|
||||
</p>
|
||||
</div>
|
||||
<footer>
|
||||
<If condition={pageNum > 1}>
|
||||
<button className={cn("button is-dark prev-button is-small")} onClick={this.goPrev}>
|
||||
<span className="icon is-small">
|
||||
<i className="fa-sharp fa-regular fa-angle-left" />
|
||||
</span>
|
||||
<span>Prev</span>
|
||||
</button>
|
||||
</If>
|
||||
<If condition={pageNum == 1}>
|
||||
<div className="prev-spacer" />
|
||||
</If>
|
||||
<div className="flex-spacer" />
|
||||
<div className="dots">{this.renderDots()}</div>
|
||||
<div className="flex-spacer" />
|
||||
<If condition={pageNum < this.totalPages}>
|
||||
<button className="button is-dark next-button is-small" onClick={this.goNext}>
|
||||
<span>Next</span>
|
||||
<span className="icon is-small">
|
||||
<i className="fa-sharp fa-regular fa-angle-right" />
|
||||
</span>
|
||||
</button>
|
||||
</If>
|
||||
<If condition={pageNum == this.totalPages}>
|
||||
<button className="button is-dark next-button is-small" onClick={this.closeModal}>
|
||||
<span>Done</span>
|
||||
</button>
|
||||
</If>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class TosModal extends React.Component<{}, {}> {
|
||||
@boundMethod
|
||||
acceptTos(): void {
|
||||
GlobalCommandRunner.clientAcceptTos();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={cn("modal tos-modal prompt-modal is-active")}>
|
||||
<div className="modal-background" />
|
||||
<div className="modal-content">
|
||||
<header>
|
||||
<div className="modal-title">Welcome to [prompt]</div>
|
||||
</header>
|
||||
<div className="inner-content">
|
||||
<div className="content">
|
||||
<p>Thank you for downloading Prompt!</p>
|
||||
<p>
|
||||
Prompt is a new terminal designed to help you save time and organize your command life.
|
||||
Prompt is currently in beta. If you'd like to give feedback, run into problems, have
|
||||
questions, or need help, please join the Prompt{" "}
|
||||
<a target="_blank" href={util.makeExternLink("https://discord.gg/XfvZ334gwU")}>
|
||||
discord server
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
<p>
|
||||
Prompt is free to use, no email or registration required (unless you're using the cloud
|
||||
features).
|
||||
</p>
|
||||
<p>
|
||||
<a target="_blank" href={util.makeExternLink("https://www.commandline.dev/tos.html")}>
|
||||
Full Terms of Service
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
<div className="flex-spacer" />
|
||||
<div onClick={this.acceptTos} className="button is-prompt-green is-outlined is-small">
|
||||
Accept Terms of Service
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { WelcomeModal, LoadingSpinner, ClientStopModal, AlertModal, DisconnectedModal, TosModal };
|
@ -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 {
|
@ -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<V> = mobx.IObservableValue<V>;
|
||||
type OArr<V> = mobx.IObservableArray<V>;
|
||||
type OMap<K, V> = mobx.ObservableMap<K, V>;
|
||||
type CV<V> = mobx.IComputedValue<V>;
|
||||
|
||||
const RemotePtyRows = 8;
|
||||
const RemotePtyCols = 80;
|
||||
const APITokenSentinel = "--apitoken--";
|
||||
|
||||
// @ts-ignore
|
||||
const VERSION = __PROMPT_VERSION__;
|
192
src/main/sessionview/CmdInput.tsx
Normal file
192
src/main/sessionview/CmdInput.tsx
Normal file
@ -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<V> = mobx.IObservableValue<V>;
|
||||
|
||||
@mobxReact.observer
|
||||
class CmdInput extends React.Component<{}, {}> {
|
||||
cmdInputRef: React.RefObject<any> = 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<string, string> = 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 (
|
||||
<div
|
||||
ref={this.cmdInputRef}
|
||||
className={cn(
|
||||
"cmd-input has-background-black",
|
||||
{ "has-info": infoShow },
|
||||
{ "has-history": historyShow }
|
||||
)}
|
||||
>
|
||||
<div key="focus" className={cn("focus-indicator", { active: focusVal })} />
|
||||
<div key="minmax" onClick={this.onInfoToggle} className="input-minmax-control">
|
||||
<If condition={infoShow || historyShow}>
|
||||
<i className="fa-sharp fa-solid fa-chevron-down" />
|
||||
</If>
|
||||
<If condition={!(infoShow || historyShow) && hasInfo}>
|
||||
<i className="fa-sharp fa-solid fa-chevron-up" />
|
||||
</If>
|
||||
</div>
|
||||
<If condition={historyShow}>
|
||||
<div className="cmd-input-grow-spacer"></div>
|
||||
<HistoryInfo />
|
||||
</If>
|
||||
<InfoMsg key="infomsg" />
|
||||
<If condition={remote && remote.status != "connected"}>
|
||||
<div className="remote-status-warning">
|
||||
WARNING:
|
||||
<span className="remote-name">[{GlobalModel.resolveRemoteIdToFullRef(remote.remoteid)}]</span>
|
||||
is {remote.status}
|
||||
<If condition={remote.status != "connecting"}>
|
||||
<div
|
||||
className="button is-prompt-green is-outlined is-small"
|
||||
onClick={() => this.clickConnectRemote(remote.remoteid)}
|
||||
>
|
||||
connect now
|
||||
</div>
|
||||
</If>
|
||||
</div>
|
||||
</If>
|
||||
<div key="prompt" className="cmd-input-context">
|
||||
<div className="has-text-white">
|
||||
<Prompt rptr={rptr} festate={feState} />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
key="input"
|
||||
className={cn(
|
||||
"cmd-input-field field has-addons",
|
||||
inputMode != null ? "inputmode-" + inputMode : null
|
||||
)}
|
||||
>
|
||||
<If condition={inputMode != null}>
|
||||
<div className="control cmd-quick-context">
|
||||
<div className="button is-static">{inputMode}</div>
|
||||
</div>
|
||||
</If>
|
||||
<TextAreaInput key={textAreaInputKey} onHeightChange={this.handleInnerHeightUpdate} />
|
||||
<div className="control cmd-exec">
|
||||
<div onClick={inputModel.uiSubmitCommand} className="button" title="Run Command">
|
||||
<span className="icon">
|
||||
<i className="fa-sharp fa-solid fa-rocket" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="cmd-hints">
|
||||
<div onClick={inputModel.toggleExpandInput} className="hint-item color-white">
|
||||
{inputModel.inputExpanded.get() ? "shrink" : "expand"} input ({renderCmdText("E")})
|
||||
</div>
|
||||
<If condition={!focusVal}>
|
||||
<div onClick={this.clickFocusInputHint} className="hint-item color-white">
|
||||
focus input ({renderCmdText("I")})
|
||||
</div>
|
||||
</If>
|
||||
<If condition={focusVal}>
|
||||
<div onMouseDown={this.clickHistoryHint} className="hint-item color-green">
|
||||
<i className={cn("fa-sharp fa-solid", historyShow ? "fa-angle-down" : "fa-angle-up")} />{" "}
|
||||
{historyShow ? "close history (esc)" : "show history (ctrl-r)"}
|
||||
</div>
|
||||
</If>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { CmdInput };
|
234
src/main/sessionview/HistoryInfo.tsx
Normal file
234
src/main/sessionview/HistoryInfo.tsx
Normal file
@ -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<string> = 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<string, string>,
|
||||
scrNames: Record<string, string>
|
||||
): 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<string, string>,
|
||||
scrNames: Record<string, string>
|
||||
): 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 (
|
||||
<div
|
||||
key={hitem.historynum}
|
||||
className={cn(
|
||||
"history-item",
|
||||
{ "is-selected": isSelected },
|
||||
{ "history-haderror": hitem.haderror },
|
||||
"hnum-" + hitem.historynum
|
||||
)}
|
||||
onClick={() => this.handleItemClick(hitem)}
|
||||
>
|
||||
<div className="history-line">
|
||||
{infoText} {lines[0]}
|
||||
</div>
|
||||
<For each="line" index="idx" of={lines.slice(1)}>
|
||||
<div key={idx} className="history-line">
|
||||
{infoTextSpacer} {line}
|
||||
</div>
|
||||
</For>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@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<string, string> = {};
|
||||
let scrNames: Record<string, string> = {};
|
||||
if (opts.queryType == "global") {
|
||||
scrNames = GlobalModel.getScreenNames();
|
||||
snames = GlobalModel.getSessionNames();
|
||||
} else if (opts.queryType == "session") {
|
||||
scrNames = GlobalModel.getScreenNames();
|
||||
}
|
||||
return (
|
||||
<div className="cmd-history">
|
||||
<div className="history-title">
|
||||
<div>history</div>
|
||||
<div className="spacer"></div>
|
||||
<div className="history-opt">[for {opts.queryType} ⌘S]</div>
|
||||
<div className="spacer"></div>
|
||||
<div className="history-opt">[containing '{opts.queryStr}']</div>
|
||||
<div className="spacer"></div>
|
||||
<div className="history-opt">[{opts.limitRemote ? "this" : "any"} remote ⌘R]</div>
|
||||
<div className="grow-spacer"></div>
|
||||
<div className="history-clickable-opt" onClick={this.handleClose}>
|
||||
(ESC)
|
||||
</div>
|
||||
<div className="spacer"></div>
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
"history-items",
|
||||
{ "show-remotes": !opts.limitRemote },
|
||||
{ "show-sessions": opts.queryType == "global" }
|
||||
)}
|
||||
>
|
||||
<If condition={hitems.length == 0}>[no history]</If>
|
||||
<If condition={hitems.length > 0}>
|
||||
<For each="hitem" index="idx" of={hitems}>
|
||||
{this.renderHItem(hitem, opts, hitem == selItem, snames, scrNames)}
|
||||
</For>
|
||||
</If>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { HistoryInfo };
|
113
src/main/sessionview/InfoMsg.tsx
Normal file
113
src/main/sessionview/InfoMsg.tsx
Normal file
@ -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 (
|
||||
<div className="cmd-input-info" style={{ display: infoShow ? "block" : "none" }}>
|
||||
<If condition={infoMsg && infoMsg.infotitle != null}>
|
||||
<div key="infotitle" className="info-title">
|
||||
{titleStr}
|
||||
</div>
|
||||
</If>
|
||||
<If condition={infoMsg && infoMsg.infomsg != null}>
|
||||
<div key="infomsg" className="info-msg">
|
||||
<If condition={infoMsg.infomsghtml}>
|
||||
<span dangerouslySetInnerHTML={{ __html: infoMsg.infomsg }} />
|
||||
</If>
|
||||
<If condition={!infoMsg.infomsghtml}>{infoMsg.infomsg}</If>
|
||||
</div>
|
||||
</If>
|
||||
<If condition={infoMsg && infoMsg.websharelink && activeScreen != null}>
|
||||
<div key="infomsg" className="info-msg">
|
||||
started sharing screen at{" "}
|
||||
<a target="_blank" href={makeExternLink(activeScreen.getWebShareUrl())}>
|
||||
[link]
|
||||
</a>
|
||||
</div>
|
||||
</If>
|
||||
<If condition={infoMsg && infoMsg.infolines != null}>
|
||||
<div key="infolines" className="info-lines">
|
||||
<For index="idx" each="line" of={infoMsg.infolines}>
|
||||
<div key={idx}>{line == "" ? " " : line}</div>
|
||||
</For>
|
||||
</div>
|
||||
</If>
|
||||
<If condition={infoMsg && infoMsg.infocomps != null && infoMsg.infocomps.length > 0}>
|
||||
<div key="infocomps" className="info-comps">
|
||||
<For each="istr" index="idx" of={infoMsg.infocomps}>
|
||||
<div
|
||||
onClick={() => this.handleCompClick(istr)}
|
||||
key={idx}
|
||||
className={cn(
|
||||
"info-comp",
|
||||
{ "has-space": this.hasSpace(istr) },
|
||||
{ "metacmd-comp": istr.startsWith("^") }
|
||||
)}
|
||||
>
|
||||
{this.getAfterSlash(istr)}
|
||||
</div>
|
||||
</For>
|
||||
<If condition={infoMsg.infocompsmore}>
|
||||
<div key="more" className="info-comp no-select">
|
||||
...
|
||||
</div>
|
||||
</If>
|
||||
</div>
|
||||
</If>
|
||||
<If condition={infoMsg && infoMsg.infoerror != null}>
|
||||
<div key="infoerror" className="info-error">
|
||||
[error] {infoMsg.infoerror}
|
||||
</div>
|
||||
</If>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { InfoMsg };
|
457
src/main/sessionview/SessionView.tsx
Normal file
457
src/main/sessionview/SessionView.tsx
Normal file
@ -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<V> = mobx.IObservableValue<V>;
|
||||
|
||||
@mobxReact.observer
|
||||
class SessionView extends React.Component<{}, {}> {
|
||||
render() {
|
||||
let model = GlobalModel;
|
||||
let session = model.getActiveSession();
|
||||
if (session == null) {
|
||||
return <div className="session-view">(no active session)</div>;
|
||||
}
|
||||
let activeScreen = session.getActiveScreen();
|
||||
let cmdInputHeight = model.inputModel.cmdInputHeight.get();
|
||||
if (cmdInputHeight == 0) {
|
||||
cmdInputHeight = 110;
|
||||
}
|
||||
let isHidden = GlobalModel.activeMainView.get() != "session";
|
||||
return (
|
||||
<div className={cn("session-view", { "is-hidden": isHidden })} data-sessionid={session.sessionId}>
|
||||
<ScreenView screen={activeScreen} />
|
||||
<ScreenTabs session={session} />
|
||||
<div style={{ height: cmdInputHeight }}></div>
|
||||
<CmdInput />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class ScreenView extends React.Component<{ screen: Screen }, {}> {
|
||||
render() {
|
||||
let { screen } = this.props;
|
||||
if (screen == null) {
|
||||
return <div className="screen-view">(no screen found)</div>;
|
||||
}
|
||||
let fontSize = GlobalModel.termFontSize.get();
|
||||
return (
|
||||
<div className="screen-view" data-screenid={screen.screenId}>
|
||||
<ScreenWindowView key={screen.screenId + ":" + fontSize} screen={screen} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// screen is not null
|
||||
@mobxReact.observer
|
||||
class ScreenWindowView extends React.Component<{ screen: Screen }, {}> {
|
||||
rszObs: any;
|
||||
windowViewRef: React.RefObject<any>;
|
||||
|
||||
width: mobx.IObservableValue<number> = mobx.observable.box(0, { name: "sw-view-width" });
|
||||
height: mobx.IObservableValue<number> = mobx.observable.box(0, { name: "sw-view-height" });
|
||||
setSize_debounced: (width: number, height: number) => void;
|
||||
|
||||
renderMode: OV<RenderModeType> = mobx.observable.box("normal", { name: "renderMode" });
|
||||
shareCopied: OV<boolean> = 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 (
|
||||
<div
|
||||
className="window-view"
|
||||
style={this.getWindowViewStyle()}
|
||||
ref={this.windowViewRef}
|
||||
data-screenid={screen.screenId}
|
||||
>
|
||||
<div key="lines" className="lines"></div>
|
||||
<div key="window-empty" className={cn("window-empty", { "should-fade": fade })}>
|
||||
<div>{message}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@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 <Line key={realLine.lineid} screen={screen} line={realLine} {...restProps} />;
|
||||
}
|
||||
|
||||
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 (
|
||||
<div className="window-view" style={this.getWindowViewStyle()} ref={this.windowViewRef}>
|
||||
<div
|
||||
key="rendermode-tag"
|
||||
className={cn("rendermode-tag", { "is-active": isActive })}
|
||||
style={{ display: "none" }}
|
||||
>
|
||||
<div className="render-mode" onClick={this.toggleRenderMode}>
|
||||
<If condition={renderMode == "normal"}>
|
||||
<i title="collapse" className="fa-sharp fa-solid fa-arrows-to-line" />
|
||||
</If>
|
||||
<If condition={renderMode == "collapsed"}>
|
||||
<i title="expand" className="fa-sharp fa-solid fa-arrows-from-line" />
|
||||
</If>
|
||||
</div>
|
||||
</div>
|
||||
<If condition={screen.isWebShared()}>
|
||||
<div key="share-tag" className="share-tag">
|
||||
<If condition={this.shareCopied.get()}>
|
||||
<div className="copied-indicator" />
|
||||
</If>
|
||||
<div className="share-tag-title">
|
||||
<i title="archived" className="fa-sharp fa-solid fa-share-nodes" /> web shared
|
||||
</div>
|
||||
<div className="share-tag-link">
|
||||
<div className="button is-prompt-green is-outlined is-small" onClick={this.copyShareLink}>
|
||||
<span>copy link</span>
|
||||
<span className="icon">
|
||||
<i className="fa-sharp fa-solid fa-copy" />
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="button is-prompt-green is-outlined is-small"
|
||||
onClick={this.openScreenSettings}
|
||||
>
|
||||
<span>open settings</span>
|
||||
<span className="icon">
|
||||
<i className="fa-sharp fa-solid fa-cog" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</If>
|
||||
<If condition={lines.length > 0}>
|
||||
<LinesView
|
||||
screen={screen}
|
||||
width={this.width.get()}
|
||||
lines={lines}
|
||||
renderMode={renderMode}
|
||||
lineFactory={this.buildLineComponent}
|
||||
/>
|
||||
</If>
|
||||
<If condition={lines.length == 0}>
|
||||
<div key="window-empty" className="window-empty">
|
||||
<div>
|
||||
<code>
|
||||
[session="{session.name.get()}" screen="{screen.name.get()}"]
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
</If>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class ScreenTabs extends React.Component<{ session: Session }, {}> {
|
||||
tabsRef: React.RefObject<any> = React.createRef();
|
||||
lastActiveScreenId: string = null;
|
||||
scrolling: OV<boolean> = 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 = <div className="tab-index">{renderCmdText(String(index + 1))}</div>;
|
||||
}
|
||||
let settings = (
|
||||
<div onClick={(e) => this.openScreenSettings(e, screen)} title="Settings" className="tab-gear">
|
||||
<i className="fa-sharp fa-solid fa-gear" />
|
||||
</div>
|
||||
);
|
||||
let archived = screen.archived.get() ? (
|
||||
<i title="archived" className="fa-sharp fa-solid fa-box-archive" />
|
||||
) : null;
|
||||
|
||||
let webShared = screen.isWebShared() ? (
|
||||
<i title="shared to web" className="fa-sharp fa-solid fa-share-nodes web-share-icon" />
|
||||
) : null;
|
||||
return (
|
||||
<div
|
||||
key={screen.screenId}
|
||||
data-screenid={screen.screenId}
|
||||
className={cn(
|
||||
"screen-tab",
|
||||
{ "is-active": activeScreenId == screen.screenId, "is-archived": screen.archived.get() },
|
||||
"color-" + screen.getTabColor()
|
||||
)}
|
||||
onClick={() => this.handleSwitchScreen(screen.screenId)}
|
||||
onContextMenu={(event) => this.openScreenSettings(event, screen)}
|
||||
>
|
||||
<div className="tab-name">
|
||||
{archived}
|
||||
{webShared}
|
||||
{screen.name.get()}
|
||||
</div>
|
||||
{tabIndex}
|
||||
{settings}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<div className="screen-tabs-container">
|
||||
<div
|
||||
className={cn("screen-tabs", { scrolling: this.scrolling.get() })}
|
||||
ref={this.tabsRef}
|
||||
onScroll={this.handleScroll}
|
||||
>
|
||||
<For each="screen" index="index" of={showingScreens}>
|
||||
{this.renderTab(screen, activeScreenId, index)}
|
||||
</For>
|
||||
<div key="new-screen" className="screen-tab new-screen" onClick={this.handleNewScreen}>
|
||||
<i className="fa-sharp fa-solid fa-plus" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="cmd-hints">
|
||||
<div className="hint-item color-green">move left {renderCmdText("[")}</div>
|
||||
<div className="hint-item color-green">move right {renderCmdText("]")}</div>
|
||||
<div className="hint-item color-green">new tab {renderCmdText("T")}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { SessionView };
|
573
src/main/sessionview/TextareaInput.tsx
Normal file
573
src/main/sessionview/TextareaInput.tsx
Normal file
@ -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<string> = mobx.observable.box(null);
|
||||
lastFocusType: string = null;
|
||||
mainInputRef: React.RefObject<any>;
|
||||
historyInputRef: React.RefObject<any>;
|
||||
controlRef: React.RefObject<any>;
|
||||
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 (
|
||||
<div className="control cmd-input-control is-expanded" ref={this.controlRef}>
|
||||
<textarea
|
||||
key="main"
|
||||
ref={this.mainInputRef}
|
||||
spellCheck="false"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
id="main-cmd-input"
|
||||
onFocus={this.handleMainFocus}
|
||||
onBlur={this.handleMainBlur}
|
||||
style={{ height: computedHeight, minHeight: computedHeight }}
|
||||
value={curLine}
|
||||
onKeyDown={this.onKeyDown}
|
||||
onChange={this.onChange}
|
||||
className={cn("textarea", { "display-disabled": disabled })}
|
||||
></textarea>
|
||||
<input
|
||||
key="history"
|
||||
ref={this.historyInputRef}
|
||||
spellCheck="false"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
className="history-input"
|
||||
type="text"
|
||||
onFocus={this.handleHistoryFocus}
|
||||
onKeyDown={this.onHistoryKeyDown}
|
||||
onChange={this.handleHistoryInput}
|
||||
value={inputModel.historyQueryOpts.get().queryStr}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { TextAreaInput };
|
@ -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;
|
@ -1,3 +1,5 @@
|
||||
@import "../../index.less";
|
||||
|
||||
#main .screen-tabs .screen-tab {
|
||||
&.color-green {
|
||||
color: @tab-white-text;
|
346
src/main/sidebar/MainSideBar.tsx
Normal file
346
src/main/sidebar/MainSideBar.tsx
Normal file
@ -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<V> = mobx.IObservableValue<V>;
|
||||
|
||||
@mobxReact.observer
|
||||
class MainSideBar extends React.Component<{}, {}> {
|
||||
collapsed: mobx.IObservableValue<boolean> = 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 (
|
||||
<>
|
||||
<span>{remote.remotealias}</span>
|
||||
<span className="small-text"> {remote.remotecanonicalname}</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return <span>{remote.remotecanonicalname}</span>;
|
||||
}
|
||||
|
||||
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 (
|
||||
<div className={cn("main-sidebar", { collapsed: isCollapsed }, { "is-dev": GlobalModel.isDev })}>
|
||||
<div className="logo-header">
|
||||
<h1
|
||||
className={cn(
|
||||
"title",
|
||||
"prompt-logo-small",
|
||||
{ collapsed: isCollapsed },
|
||||
{ "is-dev": GlobalModel.isDev }
|
||||
)}
|
||||
>
|
||||
{isCollapsed ? "[p]" : "[prompt]"}
|
||||
</h1>
|
||||
</div>
|
||||
<div className="collapse-container">
|
||||
<div className="arrow-container" onClick={this.toggleCollapsed}>
|
||||
<If condition={!isCollapsed}>
|
||||
<i className="fa-sharp fa-solid fa-angle-left" />
|
||||
</If>
|
||||
<If condition={isCollapsed}>
|
||||
<i className="fa-sharp fa-solid fa-angle-right" />
|
||||
</If>
|
||||
</div>
|
||||
</div>
|
||||
<div className="menu">
|
||||
<p className="menu-label">Sessions</p>
|
||||
<ul className="menu-list session-menu-list">
|
||||
<If condition={!model.sessionListLoaded.get()}>
|
||||
<li className="menu-loading-message">
|
||||
<a>...</a>
|
||||
</li>
|
||||
</If>
|
||||
<If condition={model.sessionListLoaded.get()}>
|
||||
<For each="session" index="idx" of={sessionList}>
|
||||
<li key={session.sessionId}>
|
||||
<a
|
||||
className={cn({
|
||||
"is-active": mainView == "session" && activeSessionId == session.sessionId,
|
||||
})}
|
||||
onClick={() => this.handleSessionClick(session.sessionId)}
|
||||
>
|
||||
<If condition={!session.archived.get()}>
|
||||
<div className="session-num">
|
||||
<span className="hotkey">^⌘</span>
|
||||
{idx + 1}
|
||||
</div>
|
||||
</If>
|
||||
<If condition={session.archived.get()}>
|
||||
<div className="session-num">
|
||||
<i title="archived" className="fa-sharp fa-solid fa-box-archive" />
|
||||
</div>
|
||||
</If>
|
||||
<div>{session.name.get()}</div>
|
||||
<div className="flex-spacer" />
|
||||
<div
|
||||
className="session-gear"
|
||||
onClick={(e) => this.openSessionSettings(e, session)}
|
||||
>
|
||||
<i className="fa-sharp fa-solid fa-gear" />
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
</For>
|
||||
<li className="new-session">
|
||||
<a onClick={() => this.handleNewSession()}>
|
||||
<i className="fa-sharp fa-solid fa-plus" /> New Session
|
||||
</a>
|
||||
</li>
|
||||
</If>
|
||||
</ul>
|
||||
<ul className="menu-list" style={{ marginTop: 20 }}>
|
||||
<li className="menu-history">
|
||||
<a onClick={this.handleHistoryClick} className={cn({ "is-active": mainView == "history" })}>
|
||||
<i className="fa-sharp fa-solid fa-clock" /> HISTORY{" "}
|
||||
<span className="hotkey">⌘H</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul className="menu-list">
|
||||
<li className="menu-bookmarks">
|
||||
<a
|
||||
onClick={this.handleBookmarksClick}
|
||||
className={cn({ "is-active": mainView == "bookmarks" })}
|
||||
>
|
||||
<i className="fa-sharp fa-solid fa-bookmark" /> BOOKMARKS{" "}
|
||||
<span className="hotkey">⌘B</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<p className="menu-label display-none">Playbooks</p>
|
||||
<ul className="menu-list display-none">
|
||||
<li key="default">
|
||||
<a onClick={this.handlePlaybookClick}>
|
||||
<i className="fa-sharp fa-solid fa-file-lines" /> default
|
||||
</a>
|
||||
</li>
|
||||
<li key="prompt-dev">
|
||||
<a onClick={this.handlePlaybookClick}>
|
||||
<i className="fa-sharp fa-solid fa-file-lines" /> prompt-dev
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div className="spacer"></div>
|
||||
<If condition={GlobalModel.debugScreen.get() && activeScreen != null}>
|
||||
<div>
|
||||
focus={activeScreen.focusType.get()}
|
||||
<br />
|
||||
sline={activeScreen.getSelectedLine()}
|
||||
<br />
|
||||
termfocus={activeScreen.termLineNumFocus.get()}
|
||||
<br />
|
||||
</div>
|
||||
</If>
|
||||
<ul className="menu-list" style={{ display: "none" }}>
|
||||
<li className="menu-bookmarks">
|
||||
<a
|
||||
onClick={this.handleWelcomeClick}
|
||||
className={cn({ "is-active": GlobalModel.welcomeModalOpen.get() })}
|
||||
>
|
||||
<i className="fa-sharp fa-solid fa-door-open" /> WELCOME
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul className="menu-list">
|
||||
<li className="menu-settings">
|
||||
<a onClick={this.handleSettingsClick}>
|
||||
<i className="fa-sharp fa-solid fa-cog" /> SETTINGS
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<p className="menu-label">
|
||||
<a onClick={() => this.clickLinks()}>
|
||||
LINKS{" "}
|
||||
<i
|
||||
className={cn(
|
||||
"fa-sharp fa-solid",
|
||||
GlobalModel.showLinks.get() ? "fa-angle-down" : "fa-angle-right"
|
||||
)}
|
||||
/>
|
||||
</a>
|
||||
</p>
|
||||
<ul className="menu-list" style={{ display: GlobalModel.showLinks.get() ? null : "none" }}>
|
||||
<li>
|
||||
<a target="_blank" href="https://docs.getprompt.dev/releasenotes">
|
||||
<i style={{ width: 20 }} className="fa-sharp fa-solid fa-notes" /> release notes
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" href="https://docs.getprompt.dev/">
|
||||
<i style={{ width: 20 }} className="fa-sharp fa-solid fa-book" /> documentation
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a target="_blank" href="https://discord.gg/XfvZ334gwU">
|
||||
<i style={{ width: 20 }} className="fa-brands fa-discord" /> discord
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<p className="menu-label">
|
||||
<a onClick={this.handleConnectionsClick}>Connections</a>
|
||||
</p>
|
||||
<ul className="menu-list remotes-menu-list">
|
||||
<For each="remote" of={remotes}>
|
||||
<li key={remote.remoteid} className={cn("remote-menu-item")}>
|
||||
<a
|
||||
className={cn({ "is-active": remote.remoteid == activeRemoteId })}
|
||||
onClick={() => this.clickRemote(remote)}
|
||||
>
|
||||
<RemoteStatusLight remote={remote} />
|
||||
{this.remoteDisplayName(remote)}
|
||||
</a>
|
||||
</li>
|
||||
</For>
|
||||
<li key="add-remote" className="add-remote">
|
||||
<a onClick={() => this.handleAddRemote()}>
|
||||
<i className="fa-sharp fa-solid fa-plus" /> Add Connection
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div className="bottom-spacer"></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { MainSideBar };
|
@ -1,3 +1,5 @@
|
||||
@import "../../index.less";
|
||||
|
||||
.main-sidebar .logo-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
@ -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<V> = mobx.IObservableValue<V>;
|
||||
|
||||
class TosModal extends React.Component<{}, {}> {
|
||||
@boundMethod
|
||||
acceptTos(): void {
|
||||
GlobalCommandRunner.clientAcceptTos();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={cn("modal tos-modal prompt-modal is-active")}>
|
||||
<div className="modal-background" />
|
||||
<div className="modal-content">
|
||||
<header>
|
||||
<div className="modal-title">Welcome to [prompt]</div>
|
||||
</header>
|
||||
<div className="inner-content">
|
||||
<div className="content">
|
||||
<p>Thank you for downloading Prompt!</p>
|
||||
<p>
|
||||
Prompt is a new terminal designed to help you save time and organize your command life.
|
||||
Prompt is currently in beta. If you'd like to give feedback, run into problems, have
|
||||
questions, or need help, please join the Prompt{" "}
|
||||
<a target="_blank" href={util.makeExternLink("https://discord.gg/XfvZ334gwU")}>
|
||||
discord server
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
<p>
|
||||
Prompt is free to use, no email or registration required (unless you're using the cloud
|
||||
features).
|
||||
</p>
|
||||
<p>
|
||||
<a target="_blank" href={util.makeExternLink("https://www.commandline.dev/tos.html")}>
|
||||
Full Terms of Service
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
<div className="flex-spacer" />
|
||||
<div onClick={this.acceptTos} className="button is-prompt-green is-outlined is-small">
|
||||
Accept Terms of Service
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { TosModal };
|
@ -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);
|
||||
|
@ -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 <span>⌘{text}</span>;
|
||||
}
|
@ -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<V> = mobx.IObservableValue<V>;
|
||||
type CV<V> = mobx.IComputedValue<V>;
|
@ -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<V> = mobx.IObservableValue<V>;
|
||||
|
||||
@ -44,7 +46,10 @@ class SimpleMarkdownRenderer extends React.Component<
|
||||
let dataBlob = this.props.data;
|
||||
if (dataBlob == null || dataBlob.notFound) {
|
||||
return (
|
||||
<div className="renderer-container markdown-renderer" style={{ fontSize: this.props.opts.termFontSize }}>
|
||||
<div
|
||||
className="renderer-container markdown-renderer"
|
||||
style={{ fontSize: this.props.opts.termFontSize }}
|
||||
>
|
||||
<div className="load-error-text">
|
||||
ERROR: file {dataBlob && dataBlob.name ? JSON.stringify(dataBlob.name) : ""} not found
|
||||
</div>
|
||||
@ -53,7 +58,10 @@ class SimpleMarkdownRenderer extends React.Component<
|
||||
}
|
||||
if (this.markdownError.get() != null) {
|
||||
return (
|
||||
<div className="renderer-container markdown-renderer" style={{ fontSize: this.props.opts.termFontSize }}>
|
||||
<div
|
||||
className="renderer-container markdown-renderer"
|
||||
style={{ fontSize: this.props.opts.termFontSize }}
|
||||
>
|
||||
<div className="load-error-text">{this.markdownError.get()}</div>
|
||||
</div>
|
||||
);
|
@ -11,9 +11,9 @@ import mustache from "mustache";
|
||||
import * as DOMPurify from "dompurify";
|
||||
import { GlobalModel } from "../model";
|
||||
|
||||
type OV<V> = mobx.IObservableValue<V>;
|
||||
import "./plugins.less";
|
||||
|
||||
const MaxMustacheSize = 200000;
|
||||
type OV<V> = mobx.IObservableValue<V>;
|
||||
|
||||
@mobxReact.observer
|
||||
class SimpleMustacheRenderer extends React.Component<
|
@ -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<V> = mobx.IObservableValue<V>;
|
||||
type OArr<V> = mobx.IObservableArray<V>;
|
||||
type OMap<K, V> = mobx.ObservableMap<K, V>;
|
||||
|
||||
type OpenAIOutputType = {
|
||||
model: string;
|
337
src/plugins/plugins.less
Normal file
337
src/plugins/plugins.less
Normal file
@ -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;
|
||||
}
|
@ -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 = {
|
416
src/prompt.less
416
src/prompt.less
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
248
src/remotes/remotes.less
Normal file
248
src/remotes/remotes.less
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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<V> = mobx.IObservableValue<V>;
|
||||
type OArr<V> = mobx.IObservableArray<V>;
|
@ -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<V> = mobx.IObservableValue<V>;
|
||||
type OArr<V> = mobx.IObservableArray<V>;
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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"),
|
||||
|
@ -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"),
|
||||
|
Loading…
Reference in New Issue
Block a user