codeedit ui updates (#366)

* codeedit UI updates

* deal with long messages, cleanup
This commit is contained in:
Mike Sawka 2024-03-01 18:11:28 -08:00 committed by GitHub
parent 089862d990
commit adf83c73b1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 127 additions and 53 deletions

View File

@ -67,6 +67,11 @@
} }
} }
&.term-inline {
padding: 2px 8px;
border-radius: 3px;
}
&.disabled { &.disabled {
opacity: 0.5; opacity: 0.5;
} }

View File

@ -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}

View File

@ -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 {

View File

@ -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;

View File

@ -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>
); );
} }