mirror of
https://github.com/wavetermdev/backup.git
synced 2026-08-05 13:57:07 -07:00
Pe 10 (#8)
* 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:
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 };
|
||||
Reference in New Issue
Block a user