1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

simplify validators, provide error to InfoPanel via React.context

Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
Roman 2021-10-25 15:06:30 +03:00
parent 207584d8e9
commit 8f2dfd0d34
5 changed files with 76 additions and 40 deletions

View File

@ -20,20 +20,21 @@
*/ */
import styles from "./dock-tab-content.module.css"; import styles from "./dock-tab-content.module.css";
import throttle from "lodash/throttle";
import React from "react"; import React from "react";
import { action, makeObservable, observable, reaction } from "mobx";
import { disposeOnUnmount, observer } from "mobx-react"; import { disposeOnUnmount, observer } from "mobx-react";
import { makeObservable, observable, reaction } from "mobx";
import { dockStore, TabId } from "./dock.store"; import { dockStore, TabId } from "./dock.store";
import { cssNames } from "../../utils"; import { cssNames } from "../../utils";
import { MonacoEditor } from "../monaco-editor"; import { MonacoEditor, MonacoEditorProps } from "../monaco-editor";
import throttle from "lodash/throttle"; import { DockTabContext } from "./dock-tab-context";
export interface DockTabContentProps extends React.HTMLAttributes<any> { export interface DockTabContentProps extends React.HTMLAttributes<any> {
tabId: TabId; tabId: TabId;
className?: string; className?: string;
withEditor?: boolean; withEditor?: boolean;
editorValue?: string; editorValue?: MonacoEditorProps["value"];
editorOnChange?(value: string): void; editorOnChange?: MonacoEditorProps["onChange"];
} }
@observer @observer
@ -56,24 +57,30 @@ export class DockTabContent extends React.Component<DockTabContentProps> {
render() { render() {
const { className, tabId, withEditor, editorValue, editorOnChange } = this.props; const { className, tabId, withEditor, editorValue, editorOnChange } = this.props;
const { error } = this;
if (!tabId) return null; if (!tabId) return null;
return ( return (
<div className={cssNames(styles.DockTabContent, className)}> <div className={cssNames(styles.DockTabContent, className)}>
{this.props.children} <DockTabContext.Provider value={{ error }}>
{this.props.children}
{withEditor && ( {withEditor && (
<MonacoEditor <MonacoEditor
autoFocus autoFocus
id={tabId} id={tabId}
className={styles.editor} className={styles.editor}
value={editorValue ?? ""} value={editorValue ?? ""}
onChange={editorOnChange} onChange={action((value, evt) => {
onError={error => this.error = error} this.error = ""; // reset first
ref={monaco => this.editor = monaco} editorOnChange?.(value, evt);
/> })}
)} onError={error => this.error = String(error)}
onModelChange={() => this.error = ""}
ref={monaco => this.editor = monaco}></MonacoEditor>
)}
</DockTabContext.Provider>
</div> </div>
); );
} }

View File

@ -0,0 +1,28 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import React from "react";
export const DockTabContext = React.createContext<DockTabContextValue>({});
export interface DockTabContextValue {
error?: string;
}

View File

@ -29,6 +29,7 @@ import { Icon } from "../icon";
import { Spinner } from "../spinner"; import { Spinner } from "../spinner";
import { dockStore, TabId } from "./dock.store"; import { dockStore, TabId } from "./dock.store";
import { Notifications } from "../notifications"; import { Notifications } from "../notifications";
import { DockTabContext, DockTabContextValue } from "./dock-tab-context";
export interface InfoPanelProps { export interface InfoPanelProps {
tabId: TabId; tabId: TabId;
@ -59,6 +60,12 @@ const defaultProps: Partial<InfoPanelProps> = {
@observer @observer
export class InfoPanel extends Component<InfoPanelProps> { export class InfoPanel extends Component<InfoPanelProps> {
static defaultProps = defaultProps as object; static defaultProps = defaultProps as object;
static contextType = DockTabContext;
declare context: DockTabContextValue;
get error() {
return this.props.error ?? this.context.error;
}
@observable waiting = false; @observable waiting = false;
@ -103,15 +110,13 @@ export class InfoPanel extends Component<InfoPanelProps> {
}; };
renderErrorIcon(): React.ReactNode { renderErrorIcon(): React.ReactNode {
const { error } = this.props; if (!this.error || !this.props.showInlineInfo) {
if (!error || !this.props.showInlineInfo) {
return null; return null;
} }
return ( return (
<div className="error"> <div className="error">
<Icon material="error_outline" tooltip={error}/> <Icon material="error_outline" tooltip={this.error}/>
</div> </div>
); );
} }

View File

@ -46,9 +46,10 @@ export interface MonacoEditorProps {
language?: "yaml" | "json"; // configure bundled list of languages in via MonacoWebpackPlugin({languages: []}) language?: "yaml" | "json"; // configure bundled list of languages in via MonacoWebpackPlugin({languages: []})
options?: Partial<editor.IStandaloneEditorConstructionOptions>; // customize editor's initialization options options?: Partial<editor.IStandaloneEditorConstructionOptions>; // customize editor's initialization options
onChange?(value: string, evt: editor.IModelContentChangedEvent): void; // catch latest value updates onChange?(value: string, evt: editor.IModelContentChangedEvent): void; // catch latest value updates
onError?(error?: string): void; // provide syntax validation errors, etc. onError?(error?: Error | unknown): void; // provide syntax validation error, etc.
onDidLayoutChange?(info: editor.EditorLayoutInfo): void; onDidLayoutChange?(info: editor.EditorLayoutInfo): void;
onDidContentSizeChange?(evt: editor.IContentSizeChangedEvent): void; onDidContentSizeChange?(evt: editor.IContentSizeChangedEvent): void;
onModelChange?(model: editor.ITextModel, prev?: editor.ITextModel): void;
} }
export const defaultEditorProps: Partial<MonacoEditorProps> = { export const defaultEditorProps: Partial<MonacoEditorProps> = {
@ -129,6 +130,7 @@ export class MonacoEditor extends React.Component<MonacoEditorProps> {
this.restoreViewState(model); this.restoreViewState(model);
this.editor.layout(); this.editor.layout();
this.editor.focus(); // keep focus in editor, e.g. when clicking between dock-tabs this.editor.focus(); // keep focus in editor, e.g. when clicking between dock-tabs
this.props.onModelChange?.(model, oldModel);
this.validateLazy(); this.validateLazy();
}; };
@ -254,21 +256,17 @@ export class MonacoEditor extends React.Component<MonacoEditorProps> {
} }
@action @action
validate = async (value = this.getValue()): Promise<void> => { validate = (value = this.getValue()) => {
const { language } = this.props; const validators: MonacoValidator[] = [
const validators: MonacoValidator[] = []; monacoValidators[this.props.language], // parsing syntax check
const syntaxValidator: MonacoValidator = monacoValidators[language]; ].filter(Boolean);
if (syntaxValidator) {
validators.push(syntaxValidator);
}
for (const validate of validators) { for (const validate of validators) {
try { try {
await validate(value); validate(value);
} catch (error) { } catch (error) {
error = String(error); logger.silly(`[MONACO]: validation error`, error);
this.props.onError?.(error); // emit error outside via callback this.props.onError?.(error); // emit error outside
} }
} }
}; };
@ -279,12 +277,10 @@ export class MonacoEditor extends React.Component<MonacoEditorProps> {
bindRef = (elem: HTMLElement) => this.containerElem = elem; bindRef = (elem: HTMLElement) => this.containerElem = elem;
render() { render() {
const { className } = this.props;
return ( return (
<div <div
data-test-component="monaco-editor" data-test-component="monaco-editor"
className={cssNames(styles.MonacoEditor, className)} className={cssNames(styles.MonacoEditor, this.props.className)}
ref={this.bindRef} ref={this.bindRef}
/> />
); );

View File

@ -21,18 +21,18 @@
import yaml, { YAMLException } from "js-yaml"; import yaml, { YAMLException } from "js-yaml";
export interface MonacoValidator { export interface MonacoValidator {
(value: string): Promise<void>; (value: string): void;
} }
export async function yamlValidator(value: string) { export function yamlValidator(value: string) {
try { try {
await yaml.load(value); yaml.load(value);
} catch (error) { } catch (error) {
throw String(error as YAMLException); throw String(error as YAMLException);
} }
} }
export async function jsonValidator(value: string) { export function jsonValidator(value: string) {
try { try {
JSON.parse(value); JSON.parse(value);
} catch (error) { } catch (error) {