mirror of
https://github.com/wavetermdev/backup.git
synced 2026-04-22 15:26:58 -07:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| de271d3873 | |||
| 8aaea9f1c8 | |||
| 1307a77b56 | |||
| 4f06cd8095 | |||
| fbc7bc9688 | |||
| 67df71d1ca | |||
| d177fab6e3 | |||
| 8ea22c4c1f | |||
| af32c8f6f3 | |||
| f2e31a6483 | |||
| 8b8b7ea2b6 | |||
| dbc91d8a4e | |||
| de81464082 | |||
| 5e6cbf30e7 | |||
| 75ae8807eb | |||
| 84227a7128 | |||
| 498f91d7fe | |||
| 4d05ef9998 | |||
| 1eb7ff512e | |||
| ba5a089382 | |||
| e40f93b858 | |||
| 30b9453bcd | |||
| 2a79057314 | |||
| 268bfabec2 | |||
| 30d1e4dfad | |||
| c522d55a23 |
Generated
+14710
File diff suppressed because it is too large
Load Diff
+2
-1
@@ -38,7 +38,7 @@
|
||||
"monaco-editor": "0.48.0",
|
||||
"mustache": "^4.2.0",
|
||||
"node-fetch": "^3.2.10",
|
||||
"overlayscrollbars": "^2.6.1",
|
||||
"overlayscrollbars": "^2.7.3",
|
||||
"overlayscrollbars-react": "^0.5.5",
|
||||
"papaparse": "^5.4.1",
|
||||
"react": "^18.1.0",
|
||||
@@ -48,6 +48,7 @@
|
||||
"remark-gfm": "^4.0.0",
|
||||
"sprintf-js": "^1.1.2",
|
||||
"throttle-debounce": "^5.0.0",
|
||||
"tinycolor2": "^1.6.0",
|
||||
"tsx-control-statements": "^5.1.1",
|
||||
"uuid": "^9.0.0",
|
||||
"winston": "^3.8.2",
|
||||
|
||||
@@ -47,7 +47,10 @@ class CodeBlockMarkdown extends React.Component<
|
||||
let clickHandler: (e: React.MouseEvent<HTMLElement>, blockIndex: number) => void;
|
||||
let inputModel = GlobalModel.inputModel;
|
||||
clickHandler = (e: React.MouseEvent<HTMLElement>, blockIndex: number) => {
|
||||
inputModel.setCodeSelectSelectedCodeBlock(blockIndex);
|
||||
const sel = window.getSelection();
|
||||
if (sel?.toString().length == 0) {
|
||||
inputModel.setCodeSelectSelectedCodeBlock(blockIndex);
|
||||
}
|
||||
};
|
||||
let selected = this.blockIndex == this.props.codeSelectSelectedIndex;
|
||||
return (
|
||||
|
||||
@@ -23,7 +23,7 @@ let MagicLayout = {
|
||||
MainSidebarDefaultWidth: 240,
|
||||
|
||||
RightSidebarMinWidth: 0,
|
||||
RightSidebarMaxWidth: 300,
|
||||
RightSidebarMaxWidth: 400,
|
||||
RightSidebarSnapThreshold: 90,
|
||||
RightSidebarDragResistance: 50,
|
||||
RightSidebarDefaultWidth: 240,
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
.sidebar-aichat {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
|
||||
.titlebar {
|
||||
background-color: var(--app-panel-bg-color);
|
||||
color: var(--term-blue);
|
||||
padding: 6px 15px;
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
border-bottom: 1px solid var(--app-border-color);
|
||||
font: var(--base-font);
|
||||
user-select: none;
|
||||
cursor: default;
|
||||
line-height: 18px;
|
||||
overflow: hidden;
|
||||
|
||||
.title-string {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
> .content {
|
||||
flex-flow: column nowrap;
|
||||
flex: 1;
|
||||
margin-bottom: 0;
|
||||
overflow-y: auto;
|
||||
|
||||
.chat-window {
|
||||
display: flex;
|
||||
// margin-bottom: 5px;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
// This is the filler that will push the chat messages to the bottom until the chat window is full
|
||||
.filler {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-msg {
|
||||
padding: calc(var(--termpad) * 2);
|
||||
display: flex;
|
||||
|
||||
.chat-msg-header {
|
||||
display: flex;
|
||||
margin-bottom: 2px;
|
||||
|
||||
i {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chat-msg-assistant {
|
||||
color: var(--app-text-color);
|
||||
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
max-width: 100%;
|
||||
overflow-x: auto;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-msg-error {
|
||||
color: var(--cmdinput-text-error);
|
||||
font-family: var(--markdown-font);
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-input {
|
||||
padding: 16px 15px 7px;
|
||||
flex: 0 0 auto;
|
||||
border-top: 1px solid var(--app-border-color);
|
||||
|
||||
.chat-textarea {
|
||||
color: var(--app-text-primary-color);
|
||||
background-color: var(--cmdinput-textarea-bg);
|
||||
resize: none;
|
||||
width: 100%;
|
||||
border: transparent;
|
||||
outline: none;
|
||||
overflow: auto;
|
||||
overflow-wrap: anywhere;
|
||||
font-family: var(--termfontfamily);
|
||||
font-weight: normal;
|
||||
line-height: var(--termlineheight);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,327 @@
|
||||
// Copyright 2023, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import * as React from "react";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import { GlobalModel } from "@/models";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import { If, For } from "tsx-control-statements/components";
|
||||
import { Markdown } from "@/elements";
|
||||
import type { OverlayScrollbars } from "overlayscrollbars";
|
||||
import { OverlayScrollbarsComponent, OverlayScrollbarsComponentRef } from "overlayscrollbars-react";
|
||||
import tinycolor from "tinycolor2";
|
||||
|
||||
import "./aichat.less";
|
||||
|
||||
class AIChatKeybindings extends React.Component<{ AIChatObject: AIChat }, {}> {
|
||||
componentDidMount(): void {
|
||||
const AIChatObject = this.props.AIChatObject;
|
||||
const keybindManager = GlobalModel.keybindManager;
|
||||
const inputModel = GlobalModel.inputModel;
|
||||
|
||||
keybindManager.registerKeybinding("pane", "aichat", "generic:confirm", (waveEvent) => {
|
||||
AIChatObject.onEnterKeyPressed();
|
||||
return true;
|
||||
});
|
||||
keybindManager.registerKeybinding("pane", "aichat", "generic:expandTextInput", (waveEvent) => {
|
||||
AIChatObject.onExpandInputPressed();
|
||||
return true;
|
||||
});
|
||||
keybindManager.registerKeybinding("pane", "aichat", "generic:cancel", (waveEvent) => {
|
||||
inputModel.closeAuxView();
|
||||
return true;
|
||||
});
|
||||
keybindManager.registerKeybinding("pane", "aichat", "aichat:clearHistory", (waveEvent) => {
|
||||
inputModel.clearAIAssistantChat();
|
||||
return true;
|
||||
});
|
||||
keybindManager.registerKeybinding("pane", "aichat", "generic:selectAbove", (waveEvent) => {
|
||||
return AIChatObject.onArrowUpPressed();
|
||||
});
|
||||
keybindManager.registerKeybinding("pane", "aichat", "generic:selectBelow", (waveEvent) => {
|
||||
return AIChatObject.onArrowDownPressed();
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
GlobalModel.keybindManager.unregisterDomain("aichat");
|
||||
}
|
||||
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class ChatContent extends React.Component<{}, {}> {
|
||||
chatListKeyCount: number = 0;
|
||||
containerRef: React.RefObject<OverlayScrollbarsComponentRef> = React.createRef();
|
||||
chatWindowRef: React.RefObject<HTMLDivElement> = React.createRef();
|
||||
osInstance: OverlayScrollbars = null;
|
||||
|
||||
componentDidUpdate() {
|
||||
this.chatListKeyCount = 0;
|
||||
if (this.containerRef?.current && this.osInstance) {
|
||||
const { viewport } = this.osInstance.elements();
|
||||
viewport.scrollTo({
|
||||
behavior: "auto",
|
||||
top: this.chatWindowRef.current.scrollHeight,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.osInstance) {
|
||||
this.osInstance.destroy();
|
||||
this.osInstance = null;
|
||||
}
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
onScrollbarInitialized(instance) {
|
||||
this.osInstance = instance;
|
||||
const { viewport } = instance.elements();
|
||||
viewport.scrollTo({
|
||||
behavior: "auto",
|
||||
top: this.chatWindowRef.current.scrollHeight,
|
||||
});
|
||||
}
|
||||
|
||||
renderError(err: string): any {
|
||||
return <div className="chat-msg-error">{err}</div>;
|
||||
}
|
||||
|
||||
renderChatMessage(chatItem: OpenAICmdInfoChatMessageType): any {
|
||||
const curKey = "chatmsg-" + this.chatListKeyCount;
|
||||
this.chatListKeyCount++;
|
||||
const senderClassName = chatItem.isassistantresponse ? "chat-msg-assistant" : "chat-msg-user";
|
||||
const msgClassName = `chat-msg ${senderClassName}`;
|
||||
|
||||
let innerHTML: React.JSX.Element = (
|
||||
<>
|
||||
<div className="chat-msg-header">
|
||||
<i className="fa-sharp fa-solid fa-user"></i>
|
||||
</div>
|
||||
<div className="msg-text">{chatItem.userquery}</div>
|
||||
</>
|
||||
);
|
||||
if (chatItem.isassistantresponse) {
|
||||
if (chatItem.assistantresponse.error != null && chatItem.assistantresponse.error !== "") {
|
||||
innerHTML = this.renderError(chatItem.assistantresponse.error);
|
||||
} else {
|
||||
innerHTML = (
|
||||
<>
|
||||
<div className="chat-msg-header">
|
||||
<i className="fa-sharp fa-solid fa-sparkles"></i>
|
||||
</div>
|
||||
<Markdown text={chatItem.assistantresponse.message} codeSelect />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const cssVar = GlobalModel.isDev ? "--app-panel-bg-color-dev" : "--app-panel-bg-color";
|
||||
const panelBgColor = getComputedStyle(document.documentElement).getPropertyValue(cssVar).trim();
|
||||
const color = tinycolor(panelBgColor);
|
||||
const newColor = color.isValid() ? tinycolor(panelBgColor).darken(6).toString() : "none";
|
||||
const backgroundColor = this.chatListKeyCount % 2 === 0 ? "none" : newColor;
|
||||
|
||||
return (
|
||||
<div className={msgClassName} key={curKey} style={{ backgroundColor }}>
|
||||
{innerHTML}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const chatMessageItems = GlobalModel.inputModel.AICmdInfoChatItems.slice();
|
||||
const chitem: OpenAICmdInfoChatMessageType = null;
|
||||
return (
|
||||
<OverlayScrollbarsComponent
|
||||
ref={this.containerRef}
|
||||
className="content"
|
||||
options={{ scrollbars: { autoHide: "leave" } }}
|
||||
events={{ initialized: this.onScrollbarInitialized }}
|
||||
>
|
||||
<div ref={this.chatWindowRef} className="chat-window">
|
||||
<div className="filler"></div>
|
||||
<For each="chitem" index="idx" of={chatMessageItems}>
|
||||
{this.renderChatMessage(chitem)}
|
||||
</For>
|
||||
</div>
|
||||
</OverlayScrollbarsComponent>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class AIChat extends React.Component<{}, {}> {
|
||||
textAreaRef: React.RefObject<HTMLTextAreaElement> = React.createRef();
|
||||
termFontSize: number = 14;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
mobx.makeObservable(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const inputModel = GlobalModel.inputModel;
|
||||
if (this.textAreaRef.current != null) {
|
||||
this.textAreaRef.current.focus();
|
||||
// inputModel.setCmdInfoChatRefs(this.textAreaRef, this.chatWindowScrollRef);
|
||||
}
|
||||
this.requestChatUpdate();
|
||||
this.onTextAreaChange(null);
|
||||
}
|
||||
|
||||
requestChatUpdate() {
|
||||
const chatMessageItems = GlobalModel.inputModel.AICmdInfoChatItems.slice();
|
||||
if (chatMessageItems == null || chatMessageItems.length == 0) {
|
||||
this.submitChatMessage("");
|
||||
}
|
||||
}
|
||||
|
||||
submitChatMessage(messageStr: string) {
|
||||
const curLine = GlobalModel.inputModel.curLine;
|
||||
const prtn = GlobalModel.submitChatInfoCommand(messageStr, curLine, false);
|
||||
prtn.then((rtn) => {
|
||||
if (!rtn.success) {
|
||||
console.log("submit chat command error: " + rtn.error);
|
||||
}
|
||||
}).catch((_) => {});
|
||||
}
|
||||
|
||||
getLinePos(elem: any): { numLines: number; linePos: number } {
|
||||
const numLines = elem.value.split("\n").length;
|
||||
const linePos = elem.value.substr(0, elem.selectionStart).split("\n").length;
|
||||
return { numLines, linePos };
|
||||
}
|
||||
|
||||
@mobx.action.bound
|
||||
onTextAreaFocused(e: any) {
|
||||
GlobalModel.inputModel.setAuxViewFocus(true);
|
||||
this.onTextAreaChange(e);
|
||||
}
|
||||
|
||||
@mobx.action.bound
|
||||
onTextAreaBlur(e: any) {
|
||||
mobx.action(() => {
|
||||
GlobalModel.inputModel.setAuxViewFocus(false);
|
||||
})();
|
||||
}
|
||||
|
||||
// Adjust the height of the textarea to fit the text
|
||||
@boundMethod
|
||||
onTextAreaChange(e: any) {
|
||||
// Calculate the bounding height of the text area
|
||||
const textAreaMaxLines = 4;
|
||||
const textAreaLineHeight = this.termFontSize * 1.5;
|
||||
const textAreaMinHeight = textAreaLineHeight;
|
||||
const textAreaMaxHeight = textAreaLineHeight * textAreaMaxLines;
|
||||
|
||||
// Get the height of the wrapped text area content. Courtesy of https://stackoverflow.com/questions/995168/textarea-to-resize-based-on-content-length
|
||||
this.textAreaRef.current.style.height = "1px";
|
||||
const scrollHeight: number = this.textAreaRef.current.scrollHeight;
|
||||
|
||||
// Set the new height of the text area, bounded by the min and max height.
|
||||
const newHeight = Math.min(Math.max(scrollHeight, textAreaMinHeight), textAreaMaxHeight);
|
||||
this.textAreaRef.current.style.height = newHeight + "px";
|
||||
GlobalModel.inputModel.codeSelectDeselectAll();
|
||||
}
|
||||
|
||||
onEnterKeyPressed() {
|
||||
const inputModel = GlobalModel.inputModel;
|
||||
const currentRef = this.textAreaRef.current;
|
||||
if (currentRef == null) {
|
||||
return;
|
||||
}
|
||||
if (inputModel.getCodeSelectSelectedIndex() == -1) {
|
||||
const messageStr = currentRef.value;
|
||||
this.submitChatMessage(messageStr);
|
||||
currentRef.value = "";
|
||||
} else {
|
||||
mobx.action(() => {
|
||||
inputModel.grabCodeSelectSelection();
|
||||
inputModel.setAuxViewFocus(false);
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
onExpandInputPressed() {
|
||||
const currentRef = this.textAreaRef.current;
|
||||
if (currentRef == null) {
|
||||
return;
|
||||
}
|
||||
currentRef.setRangeText("\n", currentRef.selectionStart, currentRef.selectionEnd, "end");
|
||||
GlobalModel.inputModel.codeSelectDeselectAll();
|
||||
}
|
||||
|
||||
onArrowUpPressed(): boolean {
|
||||
const currentRef = this.textAreaRef.current;
|
||||
if (currentRef == null) {
|
||||
return false;
|
||||
}
|
||||
if (this.getLinePos(currentRef).linePos > 1) {
|
||||
// normal up arrow
|
||||
GlobalModel.inputModel.codeSelectDeselectAll();
|
||||
return false;
|
||||
}
|
||||
GlobalModel.inputModel.codeSelectSelectNextOldestCodeBlock();
|
||||
return true;
|
||||
}
|
||||
|
||||
onArrowDownPressed(): boolean {
|
||||
const currentRef = this.textAreaRef.current;
|
||||
const inputModel = GlobalModel.inputModel;
|
||||
if (currentRef == null) {
|
||||
return false;
|
||||
}
|
||||
if (inputModel.getCodeSelectSelectedIndex() == inputModel.codeSelectBottom) {
|
||||
GlobalModel.inputModel.codeSelectDeselectAll();
|
||||
return false;
|
||||
}
|
||||
inputModel.codeSelectSelectNextNewestCodeBlock();
|
||||
return true;
|
||||
}
|
||||
|
||||
@mobx.action
|
||||
@boundMethod
|
||||
onKeyDown(e: any) {}
|
||||
|
||||
renderError(err: string): any {
|
||||
return <div className="chat-msg-error">{err}</div>;
|
||||
}
|
||||
|
||||
render() {
|
||||
const chatMessageItems = GlobalModel.inputModel.AICmdInfoChatItems.slice();
|
||||
return (
|
||||
<div className="sidebar-aichat">
|
||||
<AIChatKeybindings AIChatObject={this}></AIChatKeybindings>
|
||||
<div className="titlebar">
|
||||
<div className="title-string">Wave AI</div>
|
||||
</div>
|
||||
<If condition={chatMessageItems.length > 0}>
|
||||
<ChatContent />
|
||||
</If>
|
||||
<div className="chat-input">
|
||||
<textarea
|
||||
key="main"
|
||||
ref={this.textAreaRef}
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoFocus={true}
|
||||
id="chat-cmd-input"
|
||||
onChange={this.onTextAreaChange}
|
||||
onKeyDown={this.onKeyDown}
|
||||
style={{ fontSize: this.termFontSize }}
|
||||
className="chat-textarea"
|
||||
placeholder="Send a Message..."
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { AIChat };
|
||||
+50
-44
@@ -13,58 +13,64 @@
|
||||
font-weight: var(--sidebar-font-weight);
|
||||
border-left: 1px solid var(--app-border-color);
|
||||
|
||||
.header {
|
||||
height: 39px;
|
||||
.sidebar-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
padding: 0 5px;
|
||||
border-bottom: 1px solid var(--app-border-color);
|
||||
}
|
||||
|
||||
.rsb-modes {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 5px 10px;
|
||||
gap: 5px;
|
||||
border-bottom: 1px solid var(--app-border-color);
|
||||
|
||||
.icon-container {
|
||||
padding: 2px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.icon-container:hover {
|
||||
background-color: var(--app-selected-mask-color);
|
||||
}
|
||||
}
|
||||
|
||||
&.collapsed {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.keybind-debug-pane {
|
||||
padding: 10px;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
|
||||
.keybind-pane-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
padding-bottom: 5px;
|
||||
.header {
|
||||
height: 39px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 5px;
|
||||
border-bottom: 1px solid var(--app-border-color);
|
||||
}
|
||||
|
||||
.keybind-level {
|
||||
margin-top: 10px;
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
.rsb-modes {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 5px 10px;
|
||||
gap: 5px;
|
||||
|
||||
.icon-container {
|
||||
padding: 2px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.icon-container:hover {
|
||||
background-color: var(--app-selected-mask-color);
|
||||
}
|
||||
}
|
||||
|
||||
.keybind-domain {
|
||||
font-size: 14px;
|
||||
margin-left: 20px;
|
||||
white-space: nowrap;
|
||||
&.collapsed {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.keybind-debug-pane {
|
||||
padding: 10px;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
|
||||
.keybind-pane-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.keybind-level {
|
||||
margin-top: 10px;
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.keybind-domain {
|
||||
font-size: 14px;
|
||||
margin-left: 20px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+32
-21
@@ -11,6 +11,7 @@ import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import { GlobalModel } from "@/models";
|
||||
import { ResizableSidebar, Button } from "@/elements";
|
||||
import { WaveBookDisplay } from "./wavebook";
|
||||
import { AIChat } from "./aichat";
|
||||
|
||||
import "./right.less";
|
||||
|
||||
@@ -77,35 +78,45 @@ class RightSideBar extends React.Component<RightSideBarProps, {}> {
|
||||
{(toggleCollapse) => (
|
||||
<React.Fragment>
|
||||
<div className="header">
|
||||
<div className="rsb-modes">
|
||||
<div
|
||||
className="icon-container"
|
||||
title="Show Keybinding Debugger"
|
||||
onClick={() => this.setMode("ai")}
|
||||
>
|
||||
<i className="fa-sharp fa-regular fa-sparkles fa-fw" />
|
||||
</div>
|
||||
<div className="flex-spacer" />
|
||||
<If condition={GlobalModel.isDev}>
|
||||
<div
|
||||
className="icon-container"
|
||||
title="Show Keybinding Debugger"
|
||||
onClick={() => this.setMode("keybind")}
|
||||
>
|
||||
<i className="fa-fw fa-sharp fa-keyboard fa-solid" />
|
||||
</div>
|
||||
</If>
|
||||
<div
|
||||
className="icon-container"
|
||||
title="Show Keybinding Debugger"
|
||||
onClick={() => this.setMode("wavebook")}
|
||||
>
|
||||
<i className="fa-sharp fa-solid fa-book-sparkles"></i>
|
||||
</div>
|
||||
</div>
|
||||
<Button className="secondary ghost" onClick={toggleCollapse}>
|
||||
<i className="fa-sharp fa-regular fa-xmark"></i>
|
||||
</Button>
|
||||
</div>
|
||||
<div className="rsb-modes">
|
||||
<div className="flex-spacer" />
|
||||
<If condition={GlobalModel.isDev}>
|
||||
<div
|
||||
className="icon-container"
|
||||
title="Show Keybinding Debugger"
|
||||
onClick={() => this.setMode("keybind")}
|
||||
>
|
||||
<i className="fa-fw fa-sharp fa-keyboard fa-solid" />
|
||||
</div>
|
||||
</If>
|
||||
<div
|
||||
className="icon-container"
|
||||
title="Show Keybinding Debugger"
|
||||
onClick={() => this.setMode("wavebook")}
|
||||
>
|
||||
<i className="fa-sharp fa-solid fa-book-sparkles"></i>
|
||||
</div>
|
||||
</div>
|
||||
<If condition={this.mode.get() == "keybind"}>
|
||||
{/* <If condition={this.mode.get() == "keybind"}>
|
||||
<KeybindDevPane></KeybindDevPane>
|
||||
</If>
|
||||
</If> */}
|
||||
<If condition={this.mode.get() == "wavebook"}>
|
||||
<WaveBookDisplay></WaveBookDisplay>
|
||||
</If>
|
||||
<If condition={this.mode.get() == "ai"}>
|
||||
<AIChat />
|
||||
</If>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</ResizableSidebar>
|
||||
|
||||
@@ -10,7 +10,7 @@ import "./auxview.less";
|
||||
import { OverlayScrollbarsComponent } from "overlayscrollbars-react";
|
||||
|
||||
interface AuxiliaryCmdViewProps {
|
||||
title: string;
|
||||
title?: string;
|
||||
className?: string;
|
||||
iconClass?: string;
|
||||
titleBarContents?: React.ReactElement[];
|
||||
|
||||
@@ -1,160 +0,0 @@
|
||||
// Copyright 2023, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import * as React from "react";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import * as mobx from "mobx";
|
||||
import { clsx } from "clsx";
|
||||
|
||||
import { If } from "tsx-control-statements/components";
|
||||
import { GlobalModel } from "@/models";
|
||||
import { isBlank } from "@/util/util";
|
||||
import { WorkspaceView } from "@/app/workspace/workspaceview";
|
||||
import { PluginsView } from "@/app/pluginsview/pluginsview";
|
||||
import { BookmarksView } from "@/app/bookmarks/bookmarks";
|
||||
import { HistoryView } from "@/app/history/history";
|
||||
import { ConnectionsView } from "@/app/connections/connections";
|
||||
import { ClientSettingsView } from "@/app/clientsettings/clientsettings";
|
||||
import { MainSideBar } from "@/app/sidebar/main";
|
||||
import { RightSideBar } from "@/app/sidebar/right";
|
||||
import { DisconnectedModal, ClientStopModal } from "@/modals";
|
||||
import { ModalsProvider } from "@/modals/provider";
|
||||
import { Button, TermStyleList } from "@/elements";
|
||||
import { ErrorBoundary } from "@/common/error/errorboundary";
|
||||
|
||||
import "@/app/app.less";
|
||||
|
||||
export const App2: React.FC = mobxReact.observer(() => {
|
||||
const [dcWait, setDcWait] = React.useState(false);
|
||||
const mainContentRef = React.useRef<HTMLDivElement>(null);
|
||||
const [termThemesLoaded, setTermThemesLoaded] = React.useState(false);
|
||||
|
||||
const handleContextMenu = (e: React.MouseEvent<HTMLElement>) => {
|
||||
let isInNonTermInput = false;
|
||||
const 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;
|
||||
}
|
||||
const opts: ContextMenuOpts = {};
|
||||
if (isInNonTermInput) {
|
||||
opts.showCut = true;
|
||||
}
|
||||
const sel = window.getSelection();
|
||||
if (!isBlank(sel?.toString()) || isInNonTermInput) {
|
||||
GlobalModel.contextEditMenu(e, opts);
|
||||
}
|
||||
};
|
||||
|
||||
const openMainSidebar = mobx.action(() => {
|
||||
const mainSidebarModel = GlobalModel.mainSidebarModel;
|
||||
const width = mainSidebarModel.getWidth(true);
|
||||
mainSidebarModel.saveState(width, false);
|
||||
});
|
||||
|
||||
const openRightSidebar = mobx.action(() => {
|
||||
const rightSidebarModel = GlobalModel.rightSidebarModel;
|
||||
const width = rightSidebarModel.getWidth(true);
|
||||
rightSidebarModel.saveState(width, false);
|
||||
});
|
||||
|
||||
const remotesModel = GlobalModel.remotesModel;
|
||||
const disconnected = !GlobalModel.ws.open.get() || !GlobalModel.waveSrvRunning.get();
|
||||
const hasClientStop = GlobalModel.getHasClientStop();
|
||||
const platform = GlobalModel.getPlatform();
|
||||
const clientData = GlobalModel.clientData.get();
|
||||
|
||||
// Previously, this is done in sidebar.tsx but it causes flicker when clientData is null cos screen-view shifts around.
|
||||
// Doing it here fixes the flicker cos app is not rendered until clientData is populated.
|
||||
// wait for termThemes as well (this actually means that the "connect" packet has been received)
|
||||
if (clientData == null || GlobalModel.termThemes.get() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (disconnected || hasClientStop) {
|
||||
if (!dcWait) {
|
||||
setTimeout(() => setDcWait(true), 1500);
|
||||
}
|
||||
return (
|
||||
<div id="main" className={"platform-" + platform} onContextMenu={handleContextMenu}>
|
||||
<div ref={mainContentRef} className="main-content">
|
||||
<MainSideBar parentRef={mainContentRef} />
|
||||
<div className="session-view" />
|
||||
</div>
|
||||
<If condition={dcWait}>
|
||||
<If condition={disconnected}>
|
||||
<DisconnectedModal />
|
||||
</If>
|
||||
<If condition={!disconnected && hasClientStop}>
|
||||
<ClientStopModal />
|
||||
</If>
|
||||
</If>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (dcWait) {
|
||||
setTimeout(() => setDcWait(false), 0);
|
||||
}
|
||||
|
||||
// used to force a full reload of the application
|
||||
const renderVersion = GlobalModel.renderVersion.get();
|
||||
const mainSidebarCollapsed = GlobalModel.mainSidebarModel.getCollapsed();
|
||||
const rightSidebarCollapsed = GlobalModel.rightSidebarModel.getCollapsed();
|
||||
const activeMainView = GlobalModel.activeMainView.get();
|
||||
const lightDarkClass = GlobalModel.isDarkTheme.get() ? "is-dark" : "is-light";
|
||||
const mainClassName = clsx(
|
||||
"platform-" + platform,
|
||||
{
|
||||
"mainsidebar-collapsed": mainSidebarCollapsed,
|
||||
"rightsidebar-collapsed": rightSidebarCollapsed,
|
||||
},
|
||||
lightDarkClass
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<TermStyleList onRendered={() => setTermThemesLoaded(true)} />
|
||||
<div
|
||||
key={`version- + ${renderVersion}`}
|
||||
id="main"
|
||||
className={mainClassName}
|
||||
onContextMenu={handleContextMenu}
|
||||
>
|
||||
<If condition={termThemesLoaded}>
|
||||
<If condition={mainSidebarCollapsed}>
|
||||
<div key="logo-button" className="logo-button-container">
|
||||
<div className="logo-button-spacer" />
|
||||
<div className="logo-button" onClick={openMainSidebar}>
|
||||
<img src="public/logos/wave-logo.png" alt="logo" />
|
||||
</div>
|
||||
</div>
|
||||
</If>
|
||||
<If condition={GlobalModel.isDev && rightSidebarCollapsed && activeMainView == "session"}>
|
||||
<div className="right-sidebar-triggers">
|
||||
<Button className="secondary ghost right-sidebar-trigger" onClick={openRightSidebar}>
|
||||
<i className="fa-sharp fa-solid fa-sidebar-flip"></i>
|
||||
</Button>
|
||||
</div>
|
||||
</If>
|
||||
<div ref={mainContentRef} className="main-content">
|
||||
<MainSideBar parentRef={mainContentRef} />
|
||||
<ErrorBoundary>
|
||||
<PluginsView />
|
||||
<WorkspaceView />
|
||||
<HistoryView />
|
||||
<BookmarksView />
|
||||
<ConnectionsView model={remotesModel} />
|
||||
<ClientSettingsView model={remotesModel} />
|
||||
</ErrorBoundary>
|
||||
<RightSideBar parentRef={mainContentRef} />
|
||||
</div>
|
||||
<ModalsProvider />
|
||||
</If>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
||||
@@ -520,10 +520,6 @@ electron.ipcMain.on("get-id", (event) => {
|
||||
event.returnValue = instanceId + ":" + event.processId;
|
||||
});
|
||||
|
||||
electron.ipcMain.on("get-window-id", (event) => {
|
||||
event.returnValue = event.sender.id;
|
||||
});
|
||||
|
||||
electron.ipcMain.on("get-platform", (event) => {
|
||||
event.returnValue = unamePlatform;
|
||||
});
|
||||
@@ -989,7 +985,6 @@ function configureAutoUpdater(enabled: boolean) {
|
||||
setTimeout(runActiveTimer, 5000); // start active timer, wait 5s just to be safe
|
||||
await app.whenReady();
|
||||
await createWindowWrap();
|
||||
await createWindowWrap();
|
||||
|
||||
app.on("activate", () => {
|
||||
if (electron.BrowserWindow.getAllWindows().length === 0) {
|
||||
|
||||
@@ -4,7 +4,6 @@ contextBridge.exposeInMainWorld("api", {
|
||||
hideWindow: () => ipc.Renderer.send("hide-window"),
|
||||
toggleDeveloperTools: () => ipcRenderer.send("toggle-developer-tools"),
|
||||
getId: () => ipcRenderer.sendSync("get-id"),
|
||||
getWindowId: () => ipcRenderer.sendSync("get-window-id"),
|
||||
getPlatform: () => ipcRenderer.sendSync("get-platform"),
|
||||
getIsDev: () => ipcRenderer.sendSync("get-isdev"),
|
||||
getAuthKey: () => ipcRenderer.sendSync("get-authkey"),
|
||||
|
||||
+1
-8
@@ -6,11 +6,9 @@ import * as React from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { sprintf } from "sprintf-js";
|
||||
import { App } from "@/app/app";
|
||||
import { App2 } from "@/app2/app";
|
||||
import * as DOMPurify from "dompurify";
|
||||
import { loadFonts } from "@/util/fontutil";
|
||||
import * as textmeasure from "@/util/textmeasure";
|
||||
import { getApi } from "./models";
|
||||
|
||||
// @ts-ignore
|
||||
let VERSION = __WAVETERM_VERSION__;
|
||||
@@ -20,12 +18,7 @@ let BUILD = __WAVETERM_BUILD__;
|
||||
loadFonts();
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
let reactElem;
|
||||
if (getApi().getWindowId() === 1) {
|
||||
reactElem = React.createElement(App, null, null);
|
||||
} else {
|
||||
reactElem = React.createElement(App2, null, null);
|
||||
}
|
||||
let reactElem = React.createElement(App, null, null);
|
||||
let elem = document.getElementById("app");
|
||||
let root = createRoot(elem);
|
||||
document.fonts.ready.then(() => {
|
||||
|
||||
Vendored
-1
@@ -943,7 +943,6 @@ declare global {
|
||||
hideWindow: () => void;
|
||||
toggleDeveloperTools: () => void;
|
||||
getId: () => string;
|
||||
getWindowId: () => number;
|
||||
getIsDev: () => boolean;
|
||||
getPlatform: () => string;
|
||||
getAuthKey: () => string;
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@/app/*": ["src/app/*"], // Points to the src folder
|
||||
"@/app2/*": ["src/app2/*"], // Points to the src folder
|
||||
"@/util/*": ["src/util/*"], // Points to the src folder
|
||||
"@/models": ["src/models/index"], // Points directly to the index file
|
||||
"@/models/*": ["src/models/*"], // For everything else inside models
|
||||
|
||||
@@ -60,7 +60,6 @@ var electronCommon = {
|
||||
extensions: [".ts", ".tsx", ".js"],
|
||||
alias: {
|
||||
"@/app": path.resolve(__dirname, "../src/app/"),
|
||||
"@/app2": path.resolve(__dirname, "../src/app2/"),
|
||||
"@/util": path.resolve(__dirname, "../src/util/"),
|
||||
"@/models": path.resolve(__dirname, "../src/models/"),
|
||||
"@/common": path.resolve(__dirname, "../src/app/common/"),
|
||||
|
||||
@@ -81,7 +81,6 @@ var webCommon = {
|
||||
extensions: [".ts", ".tsx", ".js", ".mjs", ".cjs", ".wasm", ".json", ".less", ".css"],
|
||||
alias: {
|
||||
"@/app": path.resolve(__dirname, "../src/app/"),
|
||||
"@/app2": path.resolve(__dirname, "../src/app2/"),
|
||||
"@/util": path.resolve(__dirname, "../src/util/"),
|
||||
"@/models": path.resolve(__dirname, "../src/models/"),
|
||||
"@/common": path.resolve(__dirname, "../src/app/common/"),
|
||||
|
||||
Reference in New Issue
Block a user