mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-22 16:48:23 +01:00
codeedit ui updates (#366)
* codeedit UI updates * deal with long messages, cleanup
This commit is contained in:
parent
089862d990
commit
adf83c73b1
@ -67,6 +67,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.term-inline {
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
&.disabled {
|
&.disabled {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ interface ButtonProps {
|
|||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
autoFocus?: boolean;
|
autoFocus?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
termInline?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
class Button extends React.Component<ButtonProps> {
|
class Button extends React.Component<ButtonProps> {
|
||||||
@ -40,12 +41,31 @@ class Button extends React.Component<ButtonProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { leftIcon, rightIcon, theme, children, disabled, variant, color, style, autoFocus, className } =
|
const {
|
||||||
this.props;
|
leftIcon,
|
||||||
|
rightIcon,
|
||||||
|
theme,
|
||||||
|
children,
|
||||||
|
disabled,
|
||||||
|
variant,
|
||||||
|
color,
|
||||||
|
style,
|
||||||
|
autoFocus,
|
||||||
|
termInline,
|
||||||
|
className,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={cn("wave-button", theme, variant, color, { disabled: disabled }, className)}
|
className={cn(
|
||||||
|
"wave-button",
|
||||||
|
theme,
|
||||||
|
variant,
|
||||||
|
color,
|
||||||
|
{ disabled: disabled },
|
||||||
|
{ "term-inline": termInline },
|
||||||
|
className
|
||||||
|
)}
|
||||||
onClick={this.handleClick}
|
onClick={this.handleClick}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
style={style}
|
style={style}
|
||||||
|
@ -61,6 +61,7 @@
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
padding-right: 5px;
|
padding-right: 5px;
|
||||||
|
padding-left: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.icon {
|
div.icon {
|
||||||
|
@ -45,24 +45,17 @@
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
section {
|
|
||||||
// transition: height 0.3s ease-in-out;
|
.code-message {
|
||||||
}
|
text-wrap: nowrap;
|
||||||
.messageContainer {
|
text-overflow: ellipsis;
|
||||||
position: absolute;
|
overflow: hidden;
|
||||||
bottom: -3px;
|
color: var(--term-bright-green);
|
||||||
left: 14px;
|
|
||||||
.message {
|
|
||||||
background: var(--app-success-color);
|
|
||||||
border-radius: 6px;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
padding: 4px 1rem;
|
|
||||||
max-width: 80vw;
|
|
||||||
&.error {
|
&.error {
|
||||||
background: var(--app-error-color);
|
color: var(--term-bright-red);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.readonly {
|
.readonly {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0.2em;
|
top: 0.2em;
|
||||||
@ -83,14 +76,38 @@
|
|||||||
.split-horizontal {
|
.split-horizontal {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: calc(100% - var(--termlineheight) - 7px);
|
||||||
|
border-bottom: 1px solid var(--app-border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.split-vertical {
|
.split-vertical {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.code-statusbar {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--termpad);
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
font-size: var(--termfontsize);
|
||||||
|
line-height: var(--termlineheight);
|
||||||
|
background-color: var(--app-panel-bg-color);
|
||||||
|
border-top: 1px solid var(--app-border-color);
|
||||||
|
padding: 3px var(--termpad) 3px var(--termpad);
|
||||||
|
height: calc(var(--termlineheight) + 11px);
|
||||||
|
|
||||||
|
.wave-button {
|
||||||
|
line-height: var(--termlineheight) !important;
|
||||||
|
text-wrap: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
select.dropdown {
|
||||||
|
max-width: 200px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.gutter {
|
.gutter {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
flex-grow: 0;
|
flex-grow: 0;
|
||||||
|
@ -4,11 +4,14 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import Editor, { Monaco } from "@monaco-editor/react";
|
import Editor, { Monaco } from "@monaco-editor/react";
|
||||||
import type * as MonacoTypes from "monaco-editor/esm/vs/editor/editor.api";
|
import type * as MonacoTypes from "monaco-editor/esm/vs/editor/editor.api";
|
||||||
|
import cn from "classnames";
|
||||||
|
import { If } from "tsx-control-statements/components";
|
||||||
import { Markdown } from "@/elements";
|
import { Markdown } from "@/elements";
|
||||||
import { GlobalModel, GlobalCommandRunner } from "@/models";
|
import { GlobalModel, GlobalCommandRunner } from "@/models";
|
||||||
import Split from "react-split-it";
|
import Split from "react-split-it";
|
||||||
import loader from "@monaco-editor/loader";
|
import loader from "@monaco-editor/loader";
|
||||||
import { checkKeyPressed, adaptFromReactOrNativeKeyEvent } from "@/util/keyutil";
|
import { checkKeyPressed, adaptFromReactOrNativeKeyEvent } from "@/util/keyutil";
|
||||||
|
import { Button, Dropdown } from "@/elements";
|
||||||
|
|
||||||
import "./code.less";
|
import "./code.less";
|
||||||
|
|
||||||
@ -57,7 +60,7 @@ class SourceCodeRenderer extends React.Component<
|
|||||||
isSave: boolean;
|
isSave: boolean;
|
||||||
isClosed: boolean;
|
isClosed: boolean;
|
||||||
editorHeight: number;
|
editorHeight: number;
|
||||||
message: { status: string; text: string };
|
message: { status: "success" | "error"; text: string };
|
||||||
isPreviewerAvailable: boolean;
|
isPreviewerAvailable: boolean;
|
||||||
showPreview: boolean;
|
showPreview: boolean;
|
||||||
editorFraction: number;
|
editorFraction: number;
|
||||||
@ -71,13 +74,14 @@ class SourceCodeRenderer extends React.Component<
|
|||||||
static codeCache = new Map();
|
static codeCache = new Map();
|
||||||
|
|
||||||
// which languages have preview options
|
// which languages have preview options
|
||||||
languagesWithPreviewer = ["markdown"];
|
languagesWithPreviewer: string[] = ["markdown", "mdx"];
|
||||||
filePath;
|
filePath: string;
|
||||||
cacheKey;
|
cacheKey: string;
|
||||||
originalCode;
|
originalCode: string;
|
||||||
monacoEditor: any; // reference to mounted monaco editor. TODO need the correct type
|
monacoEditor: MonacoTypes.editor.IStandaloneCodeEditor; // reference to mounted monaco editor. TODO need the correct type
|
||||||
markdownRef;
|
markdownRef: React.RefObject<HTMLDivElement>;
|
||||||
syncing;
|
syncing: boolean;
|
||||||
|
monacoOptions: MonacoTypes.editor.IEditorOptions & MonacoTypes.editor.IGlobalEditorOptions;
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
@ -164,6 +168,10 @@ class SourceCodeRenderer extends React.Component<
|
|||||||
this.monacoEditor = editor;
|
this.monacoEditor = editor;
|
||||||
this.setInitialLanguage(editor);
|
this.setInitialLanguage(editor);
|
||||||
this.setEditorHeight();
|
this.setEditorHeight();
|
||||||
|
setTimeout(() => {
|
||||||
|
let opts = this.getEditorOptions();
|
||||||
|
editor.updateOptions(opts);
|
||||||
|
}, 2000);
|
||||||
editor.onKeyDown((e: MonacoTypes.IKeyboardEvent) => {
|
editor.onKeyDown((e: MonacoTypes.IKeyboardEvent) => {
|
||||||
let waveEvent = adaptFromReactOrNativeKeyEvent(e.browserEvent);
|
let waveEvent = adaptFromReactOrNativeKeyEvent(e.browserEvent);
|
||||||
if (checkKeyPressed(waveEvent, "Cmd:s") && this.state.isSave) {
|
if (checkKeyPressed(waveEvent, "Cmd:s") && this.state.isSave) {
|
||||||
@ -232,8 +240,8 @@ class SourceCodeRenderer extends React.Component<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLanguageChange = (event) => {
|
handleLanguageChange = (e: any) => {
|
||||||
const selectedLanguage = event.target.value;
|
const selectedLanguage = e.target.value;
|
||||||
this.setState({
|
this.setState({
|
||||||
selectedLanguage,
|
selectedLanguage,
|
||||||
isPreviewerAvailable: this.languagesWithPreviewer.includes(selectedLanguage),
|
isPreviewerAvailable: this.languagesWithPreviewer.includes(selectedLanguage),
|
||||||
@ -320,7 +328,7 @@ class SourceCodeRenderer extends React.Component<
|
|||||||
let allowEditing = this.getAllowEditing();
|
let allowEditing = this.getAllowEditing();
|
||||||
if (!allowEditing) {
|
if (!allowEditing) {
|
||||||
const noOfLines = Math.max(this.state.code.split("\n").length, 5);
|
const noOfLines = Math.max(this.state.code.split("\n").length, 5);
|
||||||
const lineHeight = Math.ceil(GlobalModel.getTermFontSize() * 1.5);
|
const lineHeight = Math.ceil(GlobalModel.lineHeightEnv.lineHeight);
|
||||||
_editorHeight = Math.min(noOfLines * lineHeight + 10, fullWindowHeight);
|
_editorHeight = Math.min(noOfLines * lineHeight + 10, fullWindowHeight);
|
||||||
}
|
}
|
||||||
this.setState({ editorHeight: _editorHeight }, () => {
|
this.setState({ editorHeight: _editorHeight }, () => {
|
||||||
@ -339,6 +347,27 @@ class SourceCodeRenderer extends React.Component<
|
|||||||
return !(this.props.readOnly || this.state.isClosed);
|
return !(this.props.readOnly || this.state.isClosed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateEditorOpts(): void {
|
||||||
|
if (!this.monacoEditor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let opts = this.getEditorOptions();
|
||||||
|
this.monacoEditor.updateOptions(opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
getEditorOptions(): MonacoTypes.editor.IEditorOptions {
|
||||||
|
let opts: MonacoTypes.editor.IEditorOptions = {
|
||||||
|
scrollBeyondLastLine: false,
|
||||||
|
fontSize: GlobalModel.getTermFontSize(),
|
||||||
|
fontFamily: GlobalModel.getTermFontFamily(),
|
||||||
|
readOnly: !this.getAllowEditing(),
|
||||||
|
};
|
||||||
|
if (this.state.showPreview) {
|
||||||
|
opts.minimap = { enabled: false };
|
||||||
|
}
|
||||||
|
return opts;
|
||||||
|
}
|
||||||
|
|
||||||
getCodeEditor = () => (
|
getCodeEditor = () => (
|
||||||
<div style={{ maxHeight: this.props.opts.maxSize.height }}>
|
<div style={{ maxHeight: this.props.opts.maxSize.height }}>
|
||||||
{this.state.showReadonly && <div className="readonly">{"read-only"}</div>}
|
{this.state.showReadonly && <div className="readonly">{"read-only"}</div>}
|
||||||
@ -348,12 +377,7 @@ class SourceCodeRenderer extends React.Component<
|
|||||||
defaultLanguage={this.state.selectedLanguage}
|
defaultLanguage={this.state.selectedLanguage}
|
||||||
value={this.state.code}
|
value={this.state.code}
|
||||||
onMount={this.handleEditorDidMount}
|
onMount={this.handleEditorDidMount}
|
||||||
options={{
|
options={this.getEditorOptions()}
|
||||||
scrollBeyondLastLine: false,
|
|
||||||
fontSize: GlobalModel.getTermFontSize(),
|
|
||||||
fontFamily: GlobalModel.getTermFontFamily(),
|
|
||||||
readOnly: !this.getAllowEditing(),
|
|
||||||
}}
|
|
||||||
onChange={this.handleEditorChange}
|
onChange={this.handleEditorChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -375,22 +399,23 @@ class SourceCodeRenderer extends React.Component<
|
|||||||
togglePreview = () => {
|
togglePreview = () => {
|
||||||
this.saveLineState({ showPreview: !this.state.showPreview });
|
this.saveLineState({ showPreview: !this.state.showPreview });
|
||||||
this.setState({ showPreview: !this.state.showPreview });
|
this.setState({ showPreview: !this.state.showPreview });
|
||||||
|
setTimeout(() => this.updateEditorOpts(), 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
getEditorControls = () => {
|
getEditorControls = () => {
|
||||||
const { selectedLanguage, isSave, languages, isPreviewerAvailable, showPreview } = this.state;
|
const { selectedLanguage, isSave, languages, isPreviewerAvailable, showPreview } = this.state;
|
||||||
let allowEditing = this.getAllowEditing();
|
let allowEditing = this.getAllowEditing();
|
||||||
return (
|
return (
|
||||||
<div className="buttonContainer">
|
<>
|
||||||
{isPreviewerAvailable && (
|
<If condition={isPreviewerAvailable}>
|
||||||
<div className="button">
|
<Button theme="primary" termInline={true}>
|
||||||
<div onClick={this.togglePreview} className={`preview`}>
|
<div onClick={this.togglePreview} className={`preview`}>
|
||||||
{`${showPreview ? "hide" : "show"} preview (`}
|
{`${showPreview ? "hide" : "show"} preview (`}
|
||||||
{renderCmdText("P")}
|
{renderCmdText("P")}
|
||||||
{`)`}
|
{`)`}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Button>
|
||||||
)}
|
</If>
|
||||||
<select className="dropdown" value={selectedLanguage} onChange={this.handleLanguageChange}>
|
<select className="dropdown" value={selectedLanguage} onChange={this.handleLanguageChange}>
|
||||||
{languages.map((lang, index) => (
|
{languages.map((lang, index) => (
|
||||||
<option key={index} value={lang}>
|
<option key={index} value={lang}>
|
||||||
@ -398,25 +423,23 @@ class SourceCodeRenderer extends React.Component<
|
|||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
{allowEditing && (
|
<If condition={allowEditing}>
|
||||||
<div className={`button ${isSave ? "" : "disabled"}`}>
|
<Button theme="primary" termInline={true}>
|
||||||
<div onClick={() => this.doSave()}>
|
<div onClick={() => this.doSave()}>
|
||||||
{`save (`}
|
{`save (`}
|
||||||
{renderCmdText("S")}
|
{renderCmdText("S")}
|
||||||
{`)`}
|
{`)`}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Button>
|
||||||
)}
|
<Button className="primary" termInline={true}>
|
||||||
{allowEditing && (
|
|
||||||
<div className="button">
|
|
||||||
<div onClick={this.doClose} className={`close`}>
|
<div onClick={this.doClose} className={`close`}>
|
||||||
{`close (`}
|
{`close (`}
|
||||||
{renderCmdText("D")}
|
{renderCmdText("D")}
|
||||||
{`)`}
|
{`)`}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Button>
|
||||||
)}
|
</If>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -461,8 +484,16 @@ class SourceCodeRenderer extends React.Component<
|
|||||||
{this.getCodeEditor()}
|
{this.getCodeEditor()}
|
||||||
{isPreviewerAvailable && showPreview && this.getPreviewer()}
|
{isPreviewerAvailable && showPreview && this.getPreviewer()}
|
||||||
</Split>
|
</Split>
|
||||||
|
<div className="flex-spacer" />
|
||||||
|
<div className="code-statusbar">
|
||||||
|
<If condition={message != null}>
|
||||||
|
<div className={cn("code-message", { error: message.status == "error" })}>
|
||||||
|
{this.state.message.text}
|
||||||
|
</div>
|
||||||
|
</If>
|
||||||
|
<div className="flex-spacer" />
|
||||||
{this.getEditorControls()}
|
{this.getEditorControls()}
|
||||||
{message && this.getMessage()}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user