diff --git a/integration/__tests__/cluster-pages.tests.ts b/integration/__tests__/cluster-pages.tests.ts index be032afab3..1fa5970bc1 100644 --- a/integration/__tests__/cluster-pages.tests.ts +++ b/integration/__tests__/cluster-pages.tests.ts @@ -433,7 +433,7 @@ utils.describeIf(minikubeReady(TEST_NAMESPACE))("Minikube based tests", () => { await frame.waitForTimeout(100_000); } - const inputField = await frame.waitForSelector(".CreateResource div.react-monaco-editor-container"); + const inputField = await frame.waitForSelector(".CreateResource .MonacoEditor"); await inputField.click(); await inputField.type("apiVersion: v1", { delay: 10 }); diff --git a/package.json b/package.json index f93a37a352..6a37700fe7 100644 --- a/package.json +++ b/package.json @@ -232,7 +232,6 @@ "react": "^17.0.2", "react-dom": "^17.0.2", "react-material-ui-carousel": "^2.3.1", - "react-monaco-editor": "^0.44.0", "react-router": "^5.2.0", "react-virtualized-auto-sizer": "^1.0.6", "readable-stream": "^3.6.0", diff --git a/src/renderer/components/+add-cluster/add-cluster.tsx b/src/renderer/components/+add-cluster/add-cluster.tsx index ac6a2de5cd..e0a5a7c608 100644 --- a/src/renderer/components/+add-cluster/add-cluster.tsx +++ b/src/renderer/components/+add-cluster/add-cluster.tsx @@ -122,6 +122,7 @@ export class AddCluster extends React.Component {

{ this.customConfig = value; diff --git a/src/renderer/components/monaco-editor/monaco-editor.scss b/src/renderer/components/monaco-editor/monaco-editor.scss index 8fcf112750..caf1ef34db 100644 --- a/src/renderer/components/monaco-editor/monaco-editor.scss +++ b/src/renderer/components/monaco-editor/monaco-editor.scss @@ -20,7 +20,7 @@ */ .MonacoEditor { - width: inherit; - height: inherit; + width: 100%; + height: 100%; flex: 1; } diff --git a/src/renderer/components/monaco-editor/monaco-editor.tsx b/src/renderer/components/monaco-editor/monaco-editor.tsx index 527ed92ab9..c44b664f23 100644 --- a/src/renderer/components/monaco-editor/monaco-editor.tsx +++ b/src/renderer/components/monaco-editor/monaco-editor.tsx @@ -21,27 +21,45 @@ import "./monaco-editor.scss"; import React from "react"; -import { computed, makeObservable, observable, reaction, toJS, when } from "mobx"; -import { disposeOnUnmount, observer } from "mobx-react"; -import * as monaco from "monaco-editor"; +import { action, computed, makeObservable, observable, reaction, toJS, when } from "mobx"; +import { observer } from "mobx-react"; +import { editor, Uri } from "monaco-editor"; import logger from "../../../common/logger"; -import ReactMonacoEditor, { EditorDidMount, MonacoEditorProps } from "react-monaco-editor"; import { ThemeStore } from "../../theme.store"; import { UserStore } from "../../../common/user-store"; -import { cssNames } from "../../utils"; +import { cssNames, disposer } from "../../utils"; -interface Props extends MonacoEditorProps { - id?: string; // associate editor's model.uri with some ID (e.g. DockStore.TabId) +export interface MonacoEditorProps { + id?: string; // associating editor's ID with created model.uri + value?: string; // initial text value for editor className?: string; autoFocus?: boolean; readOnly?: boolean; theme?: "vs" /* default, light theme */ | "vs-dark" | "hc-black" | string; language?: "yaml" | "json"; // configure bundled list of languages in via MonacoWebpackPlugin({languages: []}) - onError?(error: string): void; // TODO: validation or some another occurred error + options?: editor.IStandaloneEditorConstructionOptions; // customize editor's initialization options + onChange?: onChangeCallback; + onError?: onErrorCallback; + onDidLayoutChange?(info: editor.EditorLayoutInfo): void; + onDidContentSizeChange?(evt: editor.IContentSizeChangedEvent): void; } -export const defaultEditorProps: Partial = { +export interface onChangeCallback { + (value: string, data: { + model: editor.ITextModel, // current model + event: editor.IModelContentChangedEvent; + }): void; +} + +export interface onErrorCallback { + (error: string): void; // TODO: provide validation or some another occurred error +} + +export const defaultEditorProps: Partial = { + value: "", + options: {}, language: "yaml", + autoFocus: false, get theme(): string { // theme for monaco-editor defined in `src/renderer/themes/lens-*.json` return ThemeStore.getInstance().activeTheme.monacoTheme; @@ -49,28 +67,32 @@ export const defaultEditorProps: Partial = { }; @observer -export class MonacoEditor extends React.Component { +export class MonacoEditor extends React.Component { static defaultProps = defaultEditorProps as object; - static models = new WeakMap(); - static viewStates = new WeakMap(); + static models = new WeakMap(); + static viewStates = new WeakMap(); - @observable.ref editor: monaco.editor.IStandaloneCodeEditor; public staticId = `editor-id#${Math.round(1e7 * Math.random())}`; + public disposeOnUnmount = disposer(); - constructor(props: Props) { + @observable.ref containerElem: HTMLElement; + @observable.ref editor: editor.IStandaloneCodeEditor; + @observable dimensions: { width?: number, height?: number } = {}; + @observable unmounting = false; + + constructor(props: MonacoEditorProps) { super(props); makeObservable(this); + MonacoEditor.models.set(this, []); - - disposeOnUnmount(this, [ - reaction(() => this.model, this.onModelChange), - ]); - - this.whenReady.then(() => this.bindResizeObserver()); } - get whenReady() { - return when(() => Boolean(this.editor)); + get whenEditorReady() { + return when(() => Boolean(this.containerElem && this.editor)); + } + + get whenUnmounting() { + return when(() => this.unmounting); } /** @@ -82,8 +104,8 @@ export class MonacoEditor extends React.Component { for (const entry of entries) { const { width, height } = entry.contentRect; - this.editor.layout({ width, height }); logger.info(`[MONACO]: refreshing dimensions to width=${width} and height=${height}`, entry); + this.setDimensions(width, height); } }); @@ -94,16 +116,16 @@ export class MonacoEditor extends React.Component { return () => resizeObserver.unobserve(containerElem); } - onModelChange = (model: monaco.editor.ITextModel, oldModel?: monaco.editor.ITextModel) => { + onModelChange = (model: editor.ITextModel, oldModel?: editor.ITextModel) => { logger.info("[MONACO]: model change", { model, oldModel }); + this.saveViewState(oldModel); // save previously used view-model state this.editor.setModel(model); - this.editor.restoreViewState(this.getViewState(model)); + this.editor.restoreViewState(this.getViewState(model)); // restore cursor position, selection, etc. this.editor.layout(); - this.editor.focus(); }; - @computed get model(): monaco.editor.ITextModel { + @computed get model(): editor.ITextModel { const { language, value, id = this.staticId } = this.props; const model = this.getModelById(id); @@ -112,34 +134,94 @@ export class MonacoEditor extends React.Component { // creating new temporary model const uri = this.createUri(id); - const newModel = monaco.editor.createModel(value, language, uri); + const newModel = editor.createModel(value, language, uri); - MonacoEditor.models.get(this).push(newModel); logger.info(`[MONACO]: creating new model ${uri}`, newModel); + MonacoEditor.models.get(this).push(newModel); return newModel; } - editorDidMount: EditorDidMount = (editor, monaco) => { - this.props.editorDidMount?.(editor, monaco); - this.editor = editor; + @computed get globalEditorOptions() { + return toJS(UserStore.getInstance().editorConfiguration); + } + + componentDidMount() { + this.createEditor(); + logger.info(`[MONACO]: editor did mounted`); if (this.props.autoFocus) { this.editor.focus(); } - }; + } componentWillUnmount() { logger.info(`[MONACO]: unmounting editor..`); + this.unmounting = true; + this.disposeOnUnmount(); MonacoEditor.models.get(this).forEach(model => model.dispose()); MonacoEditor.models.delete(this); + this.editor?.dispose(); } - createUri(id: string): monaco.Uri { - return monaco.Uri.file(`/monaco-editor/${id}`); + private createEditor = (): void => { + if (!this.containerElem || this.editor || this.unmounting) { + return; + } + const { language, theme, readOnly, value: defaultValue, options } = this.props; + + this.editor = editor.create(this.containerElem, { + model: this.model, + value: defaultValue, + language, + theme, + readOnly, + ...this.globalEditorOptions, + ...options, + }); + logger.info(`[MONACO]: editor created for language=${language}, theme=${theme}`); + + const onDidLayoutChangeDisposer = this.editor.onDidLayoutChange(layoutInfo => { + this.props.onDidLayoutChange?.(layoutInfo); + // logger.info("[MONACO]: onDidLayoutChange()", layoutInfo); + }); + + const onValueChangeDisposer = this.editor.onDidChangeModelContent(event => { + const value = this.editor.getValue(); + // logger.info("[MONACO]: value changed", { value, event }); + + this.props.onChange?.(value, { + model: this.model, + event, + }); + }); + + const onContentSizeChangeDisposer = this.editor.onDidContentSizeChange((params) => { + this.props.onDidContentSizeChange?.(params); + // logger.info("[MONACO]: onDidContentSizeChange():", params) + }); + + this.disposeOnUnmount.push( + reaction(() => this.model, this.onModelChange), + () => onDidLayoutChangeDisposer.dispose(), + () => onValueChangeDisposer.dispose(), + () => onContentSizeChangeDisposer.dispose(), + this.bindResizeObserver(), + ); + }; + + @action + setDimensions(width: number, height: number) { + this.dimensions.width = width; + this.dimensions.height = height; + this.editor?.layout({ width, height }); } - getViewState(model = this.model): monaco.editor.ICodeEditorViewState { + createUri(id: string): Uri { + return Uri.file(`/monaco-editor/${id}`); + } + + getViewState(model = this.model): editor.ICodeEditorViewState { return MonacoEditor.viewStates.get(model) ?? null; } @@ -148,7 +230,7 @@ export class MonacoEditor extends React.Component { MonacoEditor.viewStates.set(model, this.editor.saveViewState()); } - getModelById(id: string): monaco.editor.ITextModel | null { + getModelById(id: string): editor.ITextModel | null { const uri = this.createUri(id); for (const model of MonacoEditor.models.get(this)) { @@ -166,23 +248,16 @@ export class MonacoEditor extends React.Component { return this.editor.getValue(opts); } + bindRef = (elem: HTMLElement) => this.containerElem = elem; + render() { - const { autoFocus, readOnly, className, options = {}, ...reactMonacoEditorProps } = this.props; - const globalOptions = toJS(UserStore.getInstance().editorConfiguration); + const { className } = this.props; return ( - - - +
); } } diff --git a/webpack.renderer.ts b/webpack.renderer.ts index 6e6b199d8a..345fc95b80 100755 --- a/webpack.renderer.ts +++ b/webpack.renderer.ts @@ -150,8 +150,10 @@ export function webpackLensRenderer({ showVars = true } = {}): webpack.Configura new ProgressBarPlugin(), new ForkTsCheckerPlugin(), + // see also: https://github.com/Microsoft/monaco-editor-webpack-plugin#options new MonacoWebpackPlugin({ - // available options are documented at https://github.com/Microsoft/monaco-editor-webpack-plugin#options + // publicPath: "/", + // filename: "[name].worker.js", languages: ["json", "yaml"], globalAPI: isDevelopment, }), diff --git a/yarn.lock b/yarn.lock index 3fc5e008f1..7a7f4c6c82 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4683,7 +4683,7 @@ debug@^3.0.0, debug@^3.1.0, debug@^3.1.1, debug@^3.2.6: dependencies: ms "^2.1.1" -debuglog@*, debuglog@^1.0.1: +debuglog@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI= @@ -7392,7 +7392,7 @@ import-local@^3.0.2: pkg-dir "^4.2.0" resolve-cwd "^3.0.0" -imurmurhash@*, imurmurhash@^0.1.4: +imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= @@ -9160,11 +9160,6 @@ lodash-es@^4.17.20: resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== -lodash._baseindexof@*: - version "3.1.0" - resolved "https://registry.yarnpkg.com/lodash._baseindexof/-/lodash._baseindexof-3.1.0.tgz#fe52b53a1c6761e42618d654e4a25789ed61822c" - integrity sha1-/lK1OhxnYeQmGNZU5KJXie1hgiw= - lodash._baseuniq@~4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz#0ebb44e456814af7905c6212fa2c9b2d51b841e8" @@ -9173,33 +9168,11 @@ lodash._baseuniq@~4.6.0: lodash._createset "~4.0.0" lodash._root "~3.0.0" -lodash._bindcallback@*: - version "3.0.1" - resolved "https://registry.yarnpkg.com/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz#e531c27644cf8b57a99e17ed95b35c748789392e" - integrity sha1-5THCdkTPi1epnhftlbNcdIeJOS4= - -lodash._cacheindexof@*: - version "3.0.2" - resolved "https://registry.yarnpkg.com/lodash._cacheindexof/-/lodash._cacheindexof-3.0.2.tgz#3dc69ac82498d2ee5e3ce56091bafd2adc7bde92" - integrity sha1-PcaayCSY0u5ePOVgkbr9Ktx73pI= - -lodash._createcache@*: - version "3.1.2" - resolved "https://registry.yarnpkg.com/lodash._createcache/-/lodash._createcache-3.1.2.tgz#56d6a064017625e79ebca6b8018e17440bdcf093" - integrity sha1-VtagZAF2JeeevKa4AY4XRAvc8JM= - dependencies: - lodash._getnative "^3.0.0" - lodash._createset@~4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/lodash._createset/-/lodash._createset-4.0.3.tgz#0f4659fbb09d75194fa9e2b88a6644d363c9fe26" integrity sha1-D0ZZ+7CddRlPqeK4imZE02PJ/iY= -lodash._getnative@*, lodash._getnative@^3.0.0: - version "3.9.1" - resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5" - integrity sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U= - lodash._root@~3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/lodash._root/-/lodash._root-3.0.1.tgz#fba1c4524c19ee9a5f8136b4609f017cf4ded692" @@ -9225,11 +9198,6 @@ lodash.isequal@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= -lodash.restparam@*: - version "3.6.1" - resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805" - integrity sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU= - lodash.toarray@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561" @@ -9775,11 +9743,6 @@ monaco-editor-webpack-plugin@^4.2.0: dependencies: loader-utils "^2.0.0" -monaco-editor@^0.26.1: - version "0.26.1" - resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.26.1.tgz#62bb5f658bc95379f8abb64b147632bd1c019d73" - integrity sha512-mm45nUrBDk0DgZKgbD7+bhDOtcAFNGPJJRAdS6Su1kTGl6XEgC7U3xOmDUW/0RrLf+jlvCGaqLvD4p2VjwuwwQ== - monaco-editor@^0.28.1: version "0.28.1" resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.28.1.tgz#732788ff2172d59e6d436b206da8cac715413940" @@ -11747,14 +11710,6 @@ react-material-ui-carousel@^2.3.1: auto-bind "^2.1.1" react-swipeable "^6.1.0" -react-monaco-editor@^0.44.0: - version "0.44.0" - resolved "https://registry.yarnpkg.com/react-monaco-editor/-/react-monaco-editor-0.44.0.tgz#9f966fd00b6c30e8be8873a3fbc86f14a0da2ba4" - integrity sha512-GPheXTIpBXpwv857H7/jA8HX5yae4TJ7vFwDJ5iTvy05LxIQTsD3oofXznXGi66lVA93ST/G7wRptEf4CJ9dOg== - dependencies: - monaco-editor "^0.26.1" - prop-types "^15.7.2" - react-redux@^7.2.0: version "7.2.3" resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.3.tgz#4c084618600bb199012687da9e42123cca3f0be9"