* code window is always visible

* gotten resizing done. need more work

* starting on save now

* got save to display message. now need to intergrate with save() handler
This commit is contained in:
anandamarsh
2023-09-01 15:57:01 -07:00
committed by GitHub
parent 2e18c56471
commit b5e9e34c52
5 changed files with 339 additions and 40 deletions
+28
View File
@@ -600,6 +600,33 @@ class LineCmd extends React.Component<
};
}
scrollToBringIntoViewport = () => {
const container = document.getElementsByClassName("lines")[0];
const targetDiv = this.lineRef.current;
const targetPosition = targetDiv.getBoundingClientRect();
const containerPosition = container.getBoundingClientRect();
// Check if the top of the targetDiv is above the container's visible area
if (targetPosition.top < containerPosition.top) {
// Scroll up to make the top of the targetDiv visible
const scrollAmount = container.scrollTop + targetPosition.top - containerPosition.top;
container.scrollTo({
top: scrollAmount,
behavior: "smooth",
});
}
// Check if the bottom of the targetDiv is below the container's visible area
else if (targetPosition.bottom > containerPosition.bottom) {
// Scroll down to make the bottom of the targetDiv visible
const scrollAmount = container.scrollTop + targetPosition.bottom - containerPosition.bottom;
container.scrollTo({
top: scrollAmount,
behavior: "smooth",
});
}
// If both conditions are false, then targetDiv is already fully visible, no scrolling needed
};
render() {
let { screen, line, width, staticRender, visible, topBorder, renderMode } = this.props;
let model = GlobalModel;
@@ -734,6 +761,7 @@ class LineCmd extends React.Component<
plugin={rendererPlugin}
onHeightChange={this.handleHeightChange}
initParams={this.makeRendererModelInitializeParams()}
scrollToBringIntoViewport={this.scrollToBringIntoViewport}
/>
</If>
<If condition={rendererPlugin != null && rendererPlugin.rendererType == "full"}>
+34 -4
View File
@@ -239,12 +239,11 @@ input[type="checkbox"] {
}
.dropdown {
background: rgb(180, 180, 180);
background: #dbdbdb;
color: black;
border-radius: 4px 4px 0 0;
border-radius: 6px 6px 0 0;
font-size: 10px;
font-family: system-ui;
padding: 1px 0 3px 3px;
padding: 2px 0 5px 5px;
outline: none;
}
}
@@ -254,6 +253,37 @@ input[type="checkbox"] {
.monaco-editor .monaco-editor-background {
background-color: rgba(255, 255, 255, 0.075) !important;
}
.cmd-hints {
display: inline-block;
position: relative;
margin-right: 26px;
}
.hint-item {
border-radius: 4px 4px 0 0;
padding: 3px 9px 2px 8px;
line-height: 15px;
text-align: center;
}
section {
transition: height 0.3s ease-in-out;
}
.save-enabled {
color: white;
background-color: #4e9a06;
}
.save-disabled {
color: rgb(52, 52, 52);
background-color: #aaaea7;
cursor: default !important;
}
.error {
background-color: red;
color: white;
border-radius: 6px;
margin-bottom: 1rem;
padding: 4px 1rem;
max-width: 16rem;
}
}
.renderer-container.json-renderer {
+4 -1
View File
@@ -176,6 +176,7 @@ class SimpleBlobRenderer extends React.Component<
plugin: RendererPluginType;
onHeightChange: () => void;
initParams: RendererModelInitializeParams;
scrollToBringIntoViewport: () => void;
},
{}
> {
@@ -262,17 +263,19 @@ class SimpleBlobRenderer extends React.Component<
<div ref={this.wrapperDivRef}>(no component found in plugin)</div>;
}
let simpleModel = model as SimpleBlobRendererModel;
let { festate, cmdstr } = this.props.initParams.rawCmd;
let { festate, cmdstr, exitcode } = this.props.initParams.rawCmd;
return (
<div ref={this.wrapperDivRef}>
<Comp
cwd={festate.cwd}
cmdstr={cmdstr}
exitcode={exitcode}
data={simpleModel.dataBlob}
lineState={simpleModel.lineState}
context={simpleModel.context}
opts={simpleModel.opts}
savedHeight={simpleModel.savedHeight}
scrollToBringIntoViewport={this.props.scrollToBringIntoViewport}
/>
</div>
);
+124 -35
View File
@@ -1,90 +1,153 @@
import * as React from "react";
import * as mobx from "mobx";
import * as mobxReact from "mobx-react";
import { RendererContext, RendererOpts, LineStateType } from "../types";
import Editor from "@monaco-editor/react";
import { GlobalModel } from "../model";
type OV<V> = mobx.IObservableValue<V>;
@mobxReact.observer
class SourceCodeRenderer extends React.Component<
{
data: Blob;
cmdstr: String;
cwd: String;
exitcode: Number;
context: RendererContext;
opts: RendererOpts;
savedHeight: number;
scrollToBringIntoViewport: () => void;
lineState: LineStateType;
},
{}
> {
code: OV<string> = mobx.observable.box("");
language: OV<string> = mobx.observable.box("");
languages: OV<string[]> = mobx.observable.box([]);
selectedLanguage: OV<string> = mobx.observable.box("");
/**
* codeCache is a Hashmap with key=filepath and value=code
* Editor should never read the code directly from the filesystem. it should read from the cache.
* Upon loading a file (props.data contains the file-contents) FOR THE FIRST TIME,
* we will put it in the cache, and will update the contents of the cache upon every onChange().
* ALl this is to ensure that the file contents doesnt get reloaded when the line scrolls out of the viewport
* (and hence the react component gets destroyed)
*/
static codeCache = new Map();
editorRef;
filePath;
constructor(props) {
super(props);
this.editorRef = React.createRef();
this.state = {
code: "",
language: "",
languages: [],
selectedLanguage: "",
isFullWindow: false,
isSave: false,
editorHeight: props.savedHeight,
errorMessage: null,
};
}
componentDidMount() {
let prtn = this.props.data.text();
prtn.then((text) => this.code.set(text));
componentDidMount(): void {
// DANGEROUS ... I AM ASSUMING THE COMMAND IS IN FORMAT cat prompt_samples/sample.java
// filePath should be saved in the new lineOpts field that Mike is working on :)
this.filePath = `${this.props.cwd}/${this.props.cmdstr.split(" ")[1]}`;
const code = SourceCodeRenderer.codeCache.get(this.filePath);
if (code) {
this.setState({ code });
} else
this.props.data.text().then((code) => {
this.setState({ code });
SourceCodeRenderer.codeCache.set(this.filePath, code);
});
}
handleEditorDidMount = (editor, monaco) => {
// Use a regular expression to match a filename with an extension
const extension = this.props.cmdstr.match(/(?:[^\\\/:*?"<>|\r\n]+\.)([a-zA-Z0-9]+)\b/)?.[1] || "";
const detectedLanguage = monaco.languages
.getLanguages()
.find((lang) => lang.extensions && lang.extensions.includes("." + extension));
.find((lang) => lang.extensions?.includes("." + extension));
const languages = monaco.languages.getLanguages().map((lang) => lang.id);
this.languages.set(languages);
this.setState({ languages });
if (detectedLanguage) {
this.selectedLanguage.set(detectedLanguage.id);
this.editorRef.current = editor;
const model = editor.getModel();
if (model) {
monaco.editor.setModelLanguage(model, detectedLanguage.id);
this.language.set(detectedLanguage.id);
this.setState({ selectedLanguage: detectedLanguage.id, language: detectedLanguage.id });
}
}
this.setEditorHeight();
};
handleLanguageChange = (event) => {
const selectedLanguage = event.target.value;
this.selectedLanguage.set(selectedLanguage);
this.setState({ selectedLanguage });
if (this.editorRef.current) {
const model = this.editorRef.current.getModel();
if (model) {
monaco.editor.setModelLanguage(model, selectedLanguage);
this.language.set(selectedLanguage);
this.setState({ language: selectedLanguage });
}
}
};
render() {
let opts = this.props.opts;
let lang = this.language.get();
let code = this.code.get();
if (!code) {
return <div className="renderer-container code-renderer" style={{ height: this.props.savedHeight }} />;
toggleFit = () => {
const isFullWindow = !this.state.isFullWindow;
this.setState({ isFullWindow });
this.setEditorHeight();
setTimeout(() => this.props.scrollToBringIntoViewport(), 350);
};
doSave = () => {
// call the function that would save the file to filesystem. would likely be async
// as a result of the save operation, the entire component should get reloaded (** HOW **)
// once its reloaded, this.props.data.text() should contain the latest code
// in which case, the cache will get refilled and we can consider the transaction "committed"
this.setState({ errorMessage: "File could not be saved" });
setTimeout(() => this.setState({ errorMessage: null }), 3000);
};
handleEditorChange = (code) => {
this.setState({ isFullWindow: true, code });
SourceCodeRenderer.codeCache.set(this.filePath, code);
this.setEditorHeight();
setTimeout(() => this.props.scrollToBringIntoViewport(), 350);
this.props.data.text().then((originalCode) => this.setState({ isSave: code !== originalCode }));
};
setEditorHeight = () => {
const fullWindowHeight = parseInt(this.props.opts.maxSize.height);
let _editorHeight = fullWindowHeight;
if (!this.state.isFullWindow) {
const noOfLines = this.state.code.split("\n").length;
_editorHeight = Math.min(noOfLines * GlobalModel.termFontSize.get() * 1.5 + 10, fullWindowHeight);
}
const noOfLines = code.split("\n").length;
const editorHeight = Math.min(
noOfLines * GlobalModel.termFontSize.get() * 1.5 + 10,
parseInt(opts.maxSize.height)
);
this.setState({ editorHeight: _editorHeight });
};
render() {
const { opts, exitcode } = this.props;
const { lang, code, isSave } = this.state;
if (!code)
return <div className="renderer-container code-renderer" style={{ height: this.props.savedHeight }} />;
if (exitcode === 1)
return (
<div
className="renderer-container code-renderer"
style={{
fontSize: GlobalModel.termFontSize.get(),
fontFamily: "JetBrains Mono",
color: "white",
}}
>
{code}
</div>
);
return (
<div className="renderer-container code-renderer">
<div className="scroller" style={{ maxHeight: opts.maxSize.height, paddingBottom: "15px" }}>
<Editor
theme="hc-black"
height={editorHeight}
height={this.state.editorHeight}
defaultLanguage={lang}
defaultValue={code}
onMount={this.handleEditorDidMount}
@@ -94,22 +157,48 @@ class SourceCodeRenderer extends React.Component<
fontFamily: "JetBrains Mono",
readOnly: this.props.opts.readOnly,
}}
onChange={this.handleEditorChange}
/>
</div>
<div style={{ position: "absolute", bottom: "-3px", right: 0 }}>
<select
className="dropdown"
value={this.selectedLanguage.get()}
value={this.state.selectedLanguage}
onChange={this.handleLanguageChange}
style={{ maxWidth: "5rem", marginRight: "24px" }}
style={{ minWidth: "6rem", maxWidth: "6rem", marginRight: "8px" }}
>
{this.languages.get().map((lang, index) => (
{this.state.languages.map((lang, index) => (
<option key={index} value={lang}>
{lang}
</option>
))}
</select>
<div className="cmd-hints" style={{ minWidth: "6rem", maxWidth: "6rem" }}>
<div onClick={this.toggleFit} className="hint-item color-white">
{this.state.isFullWindow ? `shrink` : `expand`}
</div>
</div>
{!this.props.opts.readOnly && (
<div className="cmd-hints" style={{ minWidth: "6rem", maxWidth: "6rem", marginLeft: "-18px" }}>
<div
onClick={this.doSave}
className={`hint-item ${isSave ? "save-enabled" : "save-disabled"}`}
>
{"save"}
</div>
</div>
)}
</div>
{this.state.errorMessage && (
<div style={{ position: "absolute", bottom: "-3px", left: "14px" }}>
<div
className="error"
style={{ fontSize: GlobalModel.termFontSize.get(), fontFamily: "JetBrains Mono" }}
>
{this.state.errorMessage}
</div>
</div>
)}
</div>
);
}
+149
View File
@@ -0,0 +1,149 @@
import * as React from "react";
import * as mobx from "mobx";
import * as mobxReact from "mobx-react";
import { RendererContext, RendererOpts } from "../types";
import Editor from "@monaco-editor/react";
import { GlobalModel } from "../model";
type OV<V> = mobx.IObservableValue<V>;
@mobxReact.observer
class SourceCodeRenderer extends React.Component<
{
data: Blob;
cmdstr: String;
cwd: String;
context: RendererContext;
opts: RendererOpts;
savedHeight: number;
scrollToBringIntoViewport: () => void;
},
{}
> {
code: OV<string> = mobx.observable.box("");
language: OV<string> = mobx.observable.box("");
languages: OV<string[]> = mobx.observable.box([]);
selectedLanguage: OV<string> = mobx.observable.box("");
isFullWindow: OV<boolean> = mobx.observable.box(false); // load this from opts
editorHeight: OV<number> = mobx.observable.box(this.props.savedHeight); // load this from opts
editorRef;
resizeObserver;
constructor(props) {
super(props);
this.editorRef = React.createRef();
console.log(`resetting the code`);
this.props.data.text().then((text) => this.code.set(text));
}
componentDidMount() {}
componentWillUnmount(): void {
console.log(`will unmount`);
}
handleEditorDidMount = (editor, monaco) => {
// Use a regular expression to match a filename with an extension
const extension = this.props.cmdstr.match(/(?:[^\\\/:*?"<>|\r\n]+\.)([a-zA-Z0-9]+)\b/)?.[1] || "";
const detectedLanguage = monaco.languages
.getLanguages()
.find((lang) => lang.extensions && lang.extensions.includes("." + extension));
const languages = monaco.languages.getLanguages().map((lang) => lang.id);
this.languages.set(languages);
if (detectedLanguage) {
this.selectedLanguage.set(detectedLanguage.id);
this.editorRef.current = editor;
const model = editor.getModel();
if (model) {
monaco.editor.setModelLanguage(model, detectedLanguage.id);
this.language.set(detectedLanguage.id);
}
}
this.setEditorHeight();
};
handleLanguageChange = (event) => {
const selectedLanguage = event.target.value;
this.selectedLanguage.set(selectedLanguage);
if (this.editorRef.current) {
const model = this.editorRef.current.getModel();
if (model) {
monaco.editor.setModelLanguage(model, selectedLanguage);
this.language.set(selectedLanguage);
}
}
};
toggleFit = () => {
this.isFullWindow.set(!this.isFullWindow.get());
this.setEditorHeight();
setTimeout(() => this.props.scrollToBringIntoViewport(), 350);
};
handleEditorChange = (value, event) => {
// editing will always be in fullscreen
this.isFullWindow.set(true);
this.code.set(value);
this.setEditorHeight();
setTimeout(() => this.props.scrollToBringIntoViewport(), 350);
};
setEditorHeight = () => {
const fullWindowHeight = parseInt(this.props.opts.maxSize.height);
let _editorHeight = fullWindowHeight;
if (!this.isFullWindow.get()) {
const noOfLines = this.code.get().split("\n").length;
_editorHeight = Math.min(noOfLines * GlobalModel.termFontSize.get() * 1.5 + 10, fullWindowHeight);
}
this.editorHeight.set(_editorHeight);
};
render() {
let opts = this.props.opts;
let lang = this.language.get();
let code = this.code.get();
if (!code) {
return <div className="renderer-container code-renderer" style={{ height: this.props.savedHeight }} />;
}
return (
<div className="renderer-container code-renderer">
<div className="scroller" style={{ maxHeight: opts.maxSize.height, paddingBottom: "15px" }}>
<Editor
theme="hc-black"
height={this.editorHeight.get()}
defaultLanguage={lang}
defaultValue={code}
onMount={this.handleEditorDidMount}
options={{
scrollBeyondLastLine: false,
fontSize: GlobalModel.termFontSize.get(),
fontFamily: "JetBrains Mono",
readOnly: this.props.opts.readOnly,
}}
onChange={this.handleEditorChange}
/>
</div>
<div style={{ position: "absolute", bottom: "-3px", right: 0 }}>
<select
className="dropdown"
value={this.selectedLanguage.get()}
onChange={this.handleLanguageChange}
style={{ minWidth: "6rem", maxWidth: "6rem", marginRight: "8px" }}
>
{this.languages.get().map((lang, index) => (
<option key={index} value={lang}>
{lang}
</option>
))}
</select>
<div className="cmd-hints" style={{ minWidth: "6rem", maxWidth: "6rem" }}>
<div onClick={this.toggleFit} className="hint-item color-white">
{this.isFullWindow.get() ? `shrink` : `expand`}
</div>
</div>
</div>
</div>
);
}
}
export { SourceCodeRenderer };