mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Monaco editor refactoring (#4225)
* monaco editor refactoring Signed-off-by: Roman <ixrock@gmail.com> * clean up / responding to comments Signed-off-by: Roman <ixrock@gmail.com> * move custom monaco themes to own folder Signed-off-by: Roman <ixrock@gmail.com> * fix lint Signed-off-by: Roman <ixrock@gmail.com> * removed unused rules from webpack's config (renderer) Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
1 parent
0ae4dfbeab
commit
368e2d9a00
44 files changed
+817
-520
No files matched your search
@@ -23,7 +23,6 @@ import React from "react";
|
||||
import { fireEvent, render } from "@testing-library/react";
|
||||
import "@testing-library/jest-dom/extend-expect";
|
||||
import fse from "fs-extra";
|
||||
|
||||
import { DockTabs } from "../dock-tabs";
|
||||
import { dockStore, DockTab, TabKind } from "../dock.store";
|
||||
import { noop } from "../../../utils";
|
||||
@@ -32,14 +31,6 @@ import { TerminalStore } from "../terminal.store";
|
||||
import { UserStore } from "../../../../common/user-store";
|
||||
import { AppPaths } from "../../../../common/app-paths";
|
||||
|
||||
jest.mock("react-monaco-editor", () => ({
|
||||
monaco: {
|
||||
editor: {
|
||||
getModel: jest.fn(),
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock("electron", () => ({
|
||||
app: {
|
||||
getVersion: () => "99.99.99",
|
||||
|
||||
@@ -33,8 +33,6 @@ import { UserStore } from "../../../../common/user-store";
|
||||
import mockFs from "mock-fs";
|
||||
import { AppPaths } from "../../../../common/app-paths";
|
||||
|
||||
jest.mock("react-monaco-editor", () => null);
|
||||
|
||||
jest.mock("electron", () => ({
|
||||
app: {
|
||||
getVersion: () => "99.99.99",
|
||||
|
||||
@@ -32,8 +32,6 @@ import { AppPaths } from "../../../../common/app-paths";
|
||||
|
||||
mockWindow();
|
||||
|
||||
jest.mock("react-monaco-editor", () => null);
|
||||
|
||||
jest.mock("electron", () => ({
|
||||
app: {
|
||||
getVersion: () => "99.99.99",
|
||||
|
||||
@@ -24,30 +24,27 @@ import "./create-resource.scss";
|
||||
import React from "react";
|
||||
import path from "path";
|
||||
import fs from "fs-extra";
|
||||
import { Select, GroupSelectOption, SelectOption } from "../select";
|
||||
import { GroupSelectOption, Select, SelectOption } from "../select";
|
||||
import yaml from "js-yaml";
|
||||
import { observable, makeObservable } from "mobx";
|
||||
import { makeObservable, observable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import { cssNames } from "../../utils";
|
||||
import { createResourceStore } from "./create-resource.store";
|
||||
import type { DockTab } from "./dock.store";
|
||||
import { EditorPanel } from "./editor-panel";
|
||||
import { InfoPanel } from "./info-panel";
|
||||
import * as resourceApplierApi from "../../../common/k8s-api/endpoints/resource-applier.api";
|
||||
import { Notifications } from "../notifications";
|
||||
import { monacoModelsManager } from "./monaco-model-manager";
|
||||
import logger from "../../../common/logger";
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
tab: DockTab;
|
||||
}
|
||||
|
||||
@observer
|
||||
export class CreateResource extends React.Component<Props> {
|
||||
@observable currentTemplates:Map<string, SelectOption> = new Map();
|
||||
@observable currentTemplates: Map<string, SelectOption> = new Map();
|
||||
@observable error = "";
|
||||
@observable templates:GroupSelectOption<SelectOption>[] = [];
|
||||
@observable templates: GroupSelectOption<SelectOption>[] = [];
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
@@ -59,12 +56,12 @@ export class CreateResource extends React.Component<Props> {
|
||||
createResourceStore.watchUserTemplates(() => createResourceStore.getMergedTemplates().then(v => this.updateGroupSelectOptions(v)));
|
||||
}
|
||||
|
||||
updateGroupSelectOptions(templates :Record<string, string[]>) {
|
||||
updateGroupSelectOptions(templates: Record<string, string[]>) {
|
||||
this.templates = Object.entries(templates)
|
||||
.map(([name, grouping]) => this.convertToGroup(name, grouping));
|
||||
}
|
||||
|
||||
convertToGroup(group:string, items:string[]):GroupSelectOption {
|
||||
convertToGroup(group: string, items: string[]): GroupSelectOption {
|
||||
const options = items.map(v => ({ label: path.parse(v).name, value: v }));
|
||||
|
||||
return { label: group, options };
|
||||
@@ -82,16 +79,19 @@ export class CreateResource extends React.Component<Props> {
|
||||
return this.currentTemplates.get(this.tabId) ?? null;
|
||||
}
|
||||
|
||||
onChange = (value: string, error?: string) => {
|
||||
onChange = (value: string) => {
|
||||
this.error = ""; // reset first, validation goes later
|
||||
createResourceStore.setData(this.tabId, value);
|
||||
this.error = error;
|
||||
};
|
||||
|
||||
onError = (error: Error | string) => {
|
||||
this.error = error.toString();
|
||||
};
|
||||
|
||||
onSelectTemplate = (item: SelectOption) => {
|
||||
this.currentTemplates.set(this.tabId, item);
|
||||
fs.readFile(item.value, "utf8").then(v => {
|
||||
createResourceStore.setData(this.tabId, v);
|
||||
monacoModelsManager.getModel(this.tabId).setValue(v ?? "");
|
||||
});
|
||||
};
|
||||
|
||||
@@ -129,42 +129,42 @@ export class CreateResource extends React.Component<Props> {
|
||||
return undefined;
|
||||
};
|
||||
|
||||
renderControls(){
|
||||
renderControls() {
|
||||
return (
|
||||
<div className="flex gaps align-center">
|
||||
<Select
|
||||
autoConvertOptions = {false}
|
||||
autoConvertOptions={false}
|
||||
controlShouldRenderValue={false} // always keep initial placeholder
|
||||
className="TemplateSelect"
|
||||
placeholder="Select Template ..."
|
||||
options={this.templates}
|
||||
menuPlacement="top"
|
||||
themeName="outlined"
|
||||
onChange={v => this.onSelectTemplate(v)}
|
||||
value = {this.currentTemplate}
|
||||
value={this.currentTemplate}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
const { tabId, data, error, create, onChange } = this;
|
||||
const { className } = this.props;
|
||||
const { tabId, data, error } = this;
|
||||
|
||||
return (
|
||||
<div className={cssNames("CreateResource flex column", className)}>
|
||||
<div className="CreateResource flex column">
|
||||
<InfoPanel
|
||||
tabId={tabId}
|
||||
error={error}
|
||||
controls={this.renderControls()}
|
||||
submit={create}
|
||||
submit={this.create}
|
||||
submitLabel="Create"
|
||||
showNotifications={false}
|
||||
/>
|
||||
<EditorPanel
|
||||
tabId={tabId}
|
||||
value={data}
|
||||
onChange={onChange}
|
||||
onChange={this.onChange}
|
||||
onError={this.onError}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
background: $terminalBackground;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
transition: flex 60ms ease-in-out;
|
||||
transition: flex-basis 25ms ease-in;
|
||||
|
||||
> *:not(.Spinner) {
|
||||
position: absolute;
|
||||
@@ -91,8 +91,4 @@
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.MonacoEditor {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
@@ -20,10 +20,9 @@
|
||||
*/
|
||||
|
||||
import * as uuid from "uuid";
|
||||
import { action, computed, IReactionOptions, makeObservable, observable, reaction } from "mobx";
|
||||
import { action, comparer, computed, IReactionOptions, makeObservable, observable, reaction, runInAction } from "mobx";
|
||||
import { autoBind, createStorage } from "../../utils";
|
||||
import throttle from "lodash/throttle";
|
||||
import { monacoModelsManager } from "./monaco-model-manager";
|
||||
|
||||
export type TabId = string;
|
||||
|
||||
@@ -89,6 +88,27 @@ export interface DockStorageState {
|
||||
isOpen?: boolean;
|
||||
}
|
||||
|
||||
export interface DockTabChangeEvent {
|
||||
tab: DockTab;
|
||||
tabId: TabId;
|
||||
prevTab?: DockTab;
|
||||
}
|
||||
|
||||
export interface DockTabChangeEventOptions extends IReactionOptions {
|
||||
/**
|
||||
* filter: by dockStore.selectedTab.kind == tabKind
|
||||
*/
|
||||
tabKind?: TabKind;
|
||||
/**
|
||||
* filter: dock and selected tab should be visible (default: true)
|
||||
*/
|
||||
dockIsVisible?: boolean;
|
||||
}
|
||||
|
||||
export interface DockTabCloseEvent {
|
||||
tabId: TabId; // closed tab id
|
||||
}
|
||||
|
||||
export class DockStore implements DockStorageState {
|
||||
constructor() {
|
||||
makeObservable(this);
|
||||
@@ -158,12 +178,6 @@ export class DockStore implements DockStorageState {
|
||||
private init() {
|
||||
// adjust terminal height if window size changes
|
||||
window.addEventListener("resize", throttle(this.adjustHeight, 250));
|
||||
// create monaco models
|
||||
this.whenReady.then(() => {this.tabs.forEach(tab => {
|
||||
if (this.usesMonacoEditor(tab)) {
|
||||
monacoModelsManager.addModel(tab.id);
|
||||
}
|
||||
});});
|
||||
}
|
||||
|
||||
get maxHeight() {
|
||||
@@ -185,21 +199,44 @@ export class DockStore implements DockStorageState {
|
||||
return reaction(() => [this.height, this.fullSize], callback, options);
|
||||
}
|
||||
|
||||
onTabChange(callback: (tabId: TabId) => void, options?: IReactionOptions) {
|
||||
return reaction(() => this.selectedTabId, callback, options);
|
||||
onTabClose(callback: (evt: DockTabCloseEvent) => void, options: IReactionOptions = {}) {
|
||||
return reaction(() => dockStore.tabs.map(tab => tab.id), (tabs: TabId[], prevTabs?: TabId[]) => {
|
||||
if (!Array.isArray(prevTabs)) {
|
||||
return; // tabs not yet modified
|
||||
}
|
||||
|
||||
const closedTabs: TabId[] = prevTabs.filter(id => !tabs.includes(id));
|
||||
|
||||
if (closedTabs.length > 0) {
|
||||
runInAction(() => {
|
||||
closedTabs.forEach(tabId => callback({ tabId }));
|
||||
});
|
||||
}
|
||||
}, {
|
||||
equals: comparer.structural,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
onTabChange(callback: (evt: DockTabChangeEvent) => void, options: DockTabChangeEventOptions = {}) {
|
||||
const { tabKind, dockIsVisible = true, ...reactionOpts } = options;
|
||||
|
||||
return reaction(() => this.selectedTab, ((tab, prevTab) => {
|
||||
if (!tab) return; // skip when dock is empty
|
||||
if (tabKind && tabKind !== tab.kind) return; // handle specific tab.kind only
|
||||
if (dockIsVisible && !this.isOpen) return;
|
||||
|
||||
callback({
|
||||
tab, prevTab,
|
||||
tabId: tab.id,
|
||||
});
|
||||
}), reactionOpts);
|
||||
}
|
||||
|
||||
hasTabs() {
|
||||
return this.tabs.length > 0;
|
||||
}
|
||||
|
||||
usesMonacoEditor(tab: DockTab): boolean {
|
||||
return [TabKind.CREATE_RESOURCE,
|
||||
TabKind.EDIT_RESOURCE,
|
||||
TabKind.INSTALL_CHART,
|
||||
TabKind.UPGRADE_CHART].includes(tab.kind);
|
||||
}
|
||||
|
||||
@action
|
||||
open(fullSize?: boolean) {
|
||||
this.isOpen = true;
|
||||
@@ -274,11 +311,6 @@ export class DockStore implements DockStorageState {
|
||||
title,
|
||||
};
|
||||
|
||||
// add monaco model
|
||||
if (this.usesMonacoEditor(tab)) {
|
||||
monacoModelsManager.addModel(id);
|
||||
}
|
||||
|
||||
this.tabs.push(tab);
|
||||
this.selectTab(tab.id);
|
||||
this.open();
|
||||
@@ -294,11 +326,6 @@ export class DockStore implements DockStorageState {
|
||||
return;
|
||||
}
|
||||
|
||||
// remove monaco model
|
||||
if (this.usesMonacoEditor(tab)) {
|
||||
monacoModelsManager.removeModel(tabId);
|
||||
}
|
||||
|
||||
this.tabs = this.tabs.filter(tab => tab.id !== tabId);
|
||||
|
||||
if (this.selectedTabId === tab.id) {
|
||||
|
||||
@@ -26,7 +26,6 @@ import { dockStore, DockTab, DockTabCreateSpecific, TabId, TabKind } from "./doc
|
||||
import type { KubeObject } from "../../../common/k8s-api/kube-object";
|
||||
import { apiManager } from "../../../common/k8s-api/api-manager";
|
||||
import type { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
|
||||
import { monacoModelsManager } from "./monaco-model-manager";
|
||||
|
||||
export interface EditingResource {
|
||||
resource: string; // resource path, e.g. /api/v1/namespaces/default
|
||||
@@ -63,7 +62,6 @@ export class EditResourceStore extends DockTabStore<EditingResource> {
|
||||
// preload resource for editing
|
||||
if (!obj && !store.isLoaded && !store.isLoading && isActiveTab) {
|
||||
store.loadFromPath(resource).catch(noop);
|
||||
monacoModelsManager.getModel(tabId).setValue(resource);
|
||||
}
|
||||
// auto-close tab when resource removed from store
|
||||
else if (!obj && store.isLoaded) {
|
||||
|
||||
@@ -26,7 +26,6 @@ import { action, computed, makeObservable, observable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import yaml from "js-yaml";
|
||||
import type { DockTab } from "./dock.store";
|
||||
import { cssNames } from "../../utils";
|
||||
import { editResourceStore } from "./edit-resource.store";
|
||||
import { InfoPanel } from "./info-panel";
|
||||
import { Badge } from "../badge";
|
||||
@@ -36,7 +35,6 @@ import type { KubeObject } from "../../../common/k8s-api/kube-object";
|
||||
import { createPatch } from "rfc6902";
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
tab: DockTab;
|
||||
}
|
||||
|
||||
@@ -88,11 +86,15 @@ export class EditResource extends React.Component<Props> {
|
||||
});
|
||||
}
|
||||
|
||||
onChange = (draft: string, error?: string) => {
|
||||
this.error = error;
|
||||
onChange = (draft: string) => {
|
||||
this.error = ""; // reset first
|
||||
this.saveDraft(draft);
|
||||
};
|
||||
|
||||
onError = (error?: Error | string) => {
|
||||
this.error = error.toString();
|
||||
};
|
||||
|
||||
save = async () => {
|
||||
if (this.error) {
|
||||
return null;
|
||||
@@ -114,14 +116,14 @@ export class EditResource extends React.Component<Props> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { tabId, error, onChange, save, draft, isReadyForEditing, resource } = this;
|
||||
const { tabId, error, onChange, onError, save, draft, isReadyForEditing, resource } = this;
|
||||
|
||||
if (!isReadyForEditing) {
|
||||
return <Spinner center/>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cssNames("EditResource flex column", this.props.className)}>
|
||||
<div className="EditResource flex column">
|
||||
<InfoPanel
|
||||
tabId={tabId}
|
||||
error={error}
|
||||
@@ -140,6 +142,7 @@ export class EditResource extends React.Component<Props> {
|
||||
tabId={tabId}
|
||||
value={draft}
|
||||
onChange={onChange}
|
||||
onError={onError}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
+4
-39
@@ -18,44 +18,9 @@
|
||||
* 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 { monaco } from "react-monaco-editor";
|
||||
|
||||
export type TabId = string;
|
||||
|
||||
interface ModelEntry {
|
||||
id?: TabId;
|
||||
modelUri?: monaco.Uri;
|
||||
lang?: string;
|
||||
}
|
||||
|
||||
export interface ModelsState {
|
||||
models: ModelEntry[];
|
||||
}
|
||||
|
||||
export class MonacoModelsManager implements ModelsState {
|
||||
models: ModelEntry[] = [];
|
||||
|
||||
addModel(tabId: string, { value = "", lang = "yaml" } = {}) {
|
||||
const uri = this.getUri(tabId);
|
||||
const model = monaco.editor.createModel(value, lang, uri);
|
||||
|
||||
if(!uri) this.models = this.models.concat({ id: tabId, modelUri: model.uri, lang });
|
||||
}
|
||||
|
||||
getModel(tabId: string): monaco.editor.ITextModel {
|
||||
return monaco.editor.getModel(this.getUri(tabId));
|
||||
}
|
||||
|
||||
getUri(tabId: string): monaco.Uri {
|
||||
return this.models.find(model => model.id == tabId)?.modelUri;
|
||||
}
|
||||
|
||||
removeModel(tabId: string) {
|
||||
const uri = this.getUri(tabId);
|
||||
|
||||
this.models = this.models.filter(v => v.id != tabId);
|
||||
monaco.editor.getModel(uri)?.dispose();
|
||||
}
|
||||
.EditorPanel {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
export const monacoModelsManager = new MonacoModelsManager();
|
||||
@@ -19,89 +19,65 @@
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import MonacoEditor, { monaco } from "react-monaco-editor";
|
||||
import styles from "./editor-panel.module.css";
|
||||
import throttle from "lodash/throttle";
|
||||
import React from "react";
|
||||
import yaml from "js-yaml";
|
||||
import { observable, makeObservable } from "mobx";
|
||||
import { makeObservable, observable, reaction } from "mobx";
|
||||
import { disposeOnUnmount, observer } from "mobx-react";
|
||||
import { dockStore, TabId } from "./dock.store";
|
||||
import { monacoModelsManager } from "./monaco-model-manager";
|
||||
import { ThemeStore } from "../../theme.store";
|
||||
import { UserStore } from "../../../common/user-store";
|
||||
import { cssNames } from "../../utils";
|
||||
import { MonacoEditor, MonacoEditorProps } from "../monaco-editor";
|
||||
|
||||
import "monaco-editor";
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
export interface EditorPanelProps {
|
||||
tabId: TabId;
|
||||
value?: string;
|
||||
onChange(value: string, error?: string): void;
|
||||
value: string;
|
||||
className?: string;
|
||||
autoFocus?: boolean; // default: true
|
||||
onChange: MonacoEditorProps["onChange"];
|
||||
onError?: MonacoEditorProps["onError"];
|
||||
}
|
||||
|
||||
const defaultProps: Partial<EditorPanelProps> = {
|
||||
autoFocus: true,
|
||||
};
|
||||
|
||||
@observer
|
||||
export class EditorPanel extends React.Component<Props> {
|
||||
model: monaco.editor.ITextModel;
|
||||
public editor: monaco.editor.IStandaloneCodeEditor;
|
||||
@observable yamlError = "";
|
||||
export class EditorPanel extends React.Component<EditorPanelProps> {
|
||||
static defaultProps = defaultProps as object;
|
||||
|
||||
constructor(props: Props) {
|
||||
@observable.ref editor?: MonacoEditor;
|
||||
|
||||
constructor(props: EditorPanelProps) {
|
||||
super(props);
|
||||
makeObservable(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// validate and run callback with optional error
|
||||
this.onChange(this.props.value || "");
|
||||
|
||||
disposeOnUnmount(this, [
|
||||
dockStore.onTabChange(this.onTabChange, { delay: 250 }),
|
||||
dockStore.onResize(this.onResize, { delay: 250 }),
|
||||
// keep focus on editor's area when <Dock/> just opened
|
||||
reaction(() => dockStore.isOpen, isOpen => isOpen && this.editor?.focus(), {
|
||||
fireImmediately: true,
|
||||
}),
|
||||
|
||||
// focus to editor on dock's resize or turning into fullscreen mode
|
||||
dockStore.onResize(throttle(() => this.editor?.focus(), 250)),
|
||||
]);
|
||||
}
|
||||
|
||||
editorDidMount = (editor: monaco.editor.IStandaloneCodeEditor) => {
|
||||
this.editor = editor;
|
||||
const model = monacoModelsManager.getModel(this.props.tabId);
|
||||
|
||||
model.setValue(this.props.value ?? "");
|
||||
this.editor.setModel(model);
|
||||
};
|
||||
|
||||
validate(value: string) {
|
||||
try {
|
||||
yaml.loadAll(value);
|
||||
this.yamlError = "";
|
||||
} catch (err) {
|
||||
this.yamlError = err.toString();
|
||||
}
|
||||
}
|
||||
|
||||
onTabChange = () => {
|
||||
this.editor.focus();
|
||||
const model = monacoModelsManager.getModel(this.props.tabId);
|
||||
|
||||
model.setValue(this.props.value ?? "");
|
||||
this.editor.setModel(model);
|
||||
};
|
||||
|
||||
onResize = () => {
|
||||
this.editor.focus();
|
||||
};
|
||||
|
||||
onChange = (value: string) => {
|
||||
this.validate(value);
|
||||
this.props.onChange?.(value, this.yamlError);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { className, autoFocus, tabId, value, onChange, onError } = this.props;
|
||||
|
||||
if (!tabId) return null;
|
||||
|
||||
return (
|
||||
<MonacoEditor
|
||||
options={{ model: null, ...UserStore.getInstance().getEditorOptions() }}
|
||||
theme={ThemeStore.getInstance().activeTheme.monacoTheme}
|
||||
language = "yaml"
|
||||
onChange = {this.onChange}
|
||||
editorDidMount={this.editorDidMount}
|
||||
autoFocus={autoFocus}
|
||||
id={tabId}
|
||||
value={value}
|
||||
className={cssNames(styles.EditorPanel, className)}
|
||||
onChange={onChange}
|
||||
onError={onError}
|
||||
ref={monaco => this.editor = monaco}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ import { DockTabStore } from "./dock-tab.store";
|
||||
import { getChartDetails, getChartValues, HelmChart } from "../../../common/k8s-api/endpoints/helm-charts.api";
|
||||
import type { IReleaseUpdateDetails } from "../../../common/k8s-api/endpoints/helm-releases.api";
|
||||
import { Notifications } from "../notifications";
|
||||
import { monacoModelsManager } from "./monaco-model-manager";
|
||||
|
||||
export interface IChartInstallData {
|
||||
name: string;
|
||||
@@ -91,7 +90,6 @@ export class InstallChartStore extends DockTabStore<IChartInstallData> {
|
||||
|
||||
if (values) {
|
||||
this.setData(tabId, { ...data, values });
|
||||
monacoModelsManager.getModel(tabId).setValue(values);
|
||||
} else if (attempt < 4) {
|
||||
return this.loadValues(tabId, attempt + 1);
|
||||
}
|
||||
|
||||
@@ -22,13 +22,13 @@
|
||||
import "./install-chart.scss";
|
||||
|
||||
import React, { Component } from "react";
|
||||
import { observable, makeObservable } from "mobx";
|
||||
import { action, makeObservable, observable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import { dockStore, DockTab } from "./dock.store";
|
||||
import { InfoPanel } from "./info-panel";
|
||||
import { Badge } from "../badge";
|
||||
import { NamespaceSelect } from "../+namespaces/namespace-select";
|
||||
import { boundMethod, prevDefault } from "../../utils";
|
||||
import { prevDefault } from "../../utils";
|
||||
import { IChartInstallData, installChartStore } from "./install-chart.store";
|
||||
import { Spinner } from "../spinner";
|
||||
import { Icon } from "../icon";
|
||||
@@ -75,8 +75,7 @@ export class InstallChart extends Component<Props> {
|
||||
return installChartStore.details.getData(this.tabId);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
viewRelease() {
|
||||
viewRelease = () => {
|
||||
const { release } = this.releaseDetails;
|
||||
|
||||
navigate(releaseURL({
|
||||
@@ -86,38 +85,39 @@ export class InstallChart extends Component<Props> {
|
||||
},
|
||||
}));
|
||||
dockStore.closeTab(this.tabId);
|
||||
}
|
||||
};
|
||||
|
||||
@boundMethod
|
||||
save(data: Partial<IChartInstallData>) {
|
||||
const chart = { ...this.chartData, ...data };
|
||||
|
||||
installChartStore.setData(this.tabId, chart);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
onVersionChange(option: SelectOption) {
|
||||
onVersionChange = (option: SelectOption) => {
|
||||
const version = option.value;
|
||||
|
||||
this.save({ version, values: "" });
|
||||
installChartStore.loadValues(this.tabId);
|
||||
}
|
||||
};
|
||||
|
||||
@boundMethod
|
||||
onValuesChange(values: string, error?: string) {
|
||||
this.error = error;
|
||||
@action
|
||||
onChange = (values: string) => {
|
||||
this.error = "";
|
||||
this.save({ values });
|
||||
}
|
||||
};
|
||||
|
||||
@boundMethod
|
||||
onNamespaceChange(opt: SelectOption) {
|
||||
@action
|
||||
onError = (error: Error | string) => {
|
||||
this.error = error.toString();
|
||||
};
|
||||
|
||||
onNamespaceChange = (opt: SelectOption) => {
|
||||
this.save({ namespace: opt.value });
|
||||
}
|
||||
};
|
||||
|
||||
@boundMethod
|
||||
onReleaseNameChange(name: string) {
|
||||
onReleaseNameChange = (name: string) => {
|
||||
this.save({ releaseName: name });
|
||||
}
|
||||
};
|
||||
|
||||
install = async () => {
|
||||
const { repo, name, version, namespace, values, releaseName } = this.chartData;
|
||||
@@ -138,14 +138,14 @@ export class InstallChart extends Component<Props> {
|
||||
const { tabId, chartData, values, versions, install } = this;
|
||||
|
||||
if (chartData?.values === undefined || !versions) {
|
||||
return <Spinner center />;
|
||||
return <Spinner center/>;
|
||||
}
|
||||
|
||||
if (this.releaseDetails) {
|
||||
return (
|
||||
<div className="InstallChartDone flex column gaps align-center justify-center">
|
||||
<p>
|
||||
<Icon material="check" big sticker />
|
||||
<Icon material="check" big sticker/>
|
||||
</p>
|
||||
<p>Installation complete!</p>
|
||||
<div className="flex gaps align-center">
|
||||
@@ -174,7 +174,7 @@ export class InstallChart extends Component<Props> {
|
||||
const panelControls = (
|
||||
<div className="install-controls flex gaps align-center">
|
||||
<span>Chart</span>
|
||||
<Badge label={`${repo}/${name}`} title="Repo/Name" />
|
||||
<Badge label={`${repo}/${name}`} title="Repo/Name"/>
|
||||
<span>Version</span>
|
||||
<Select
|
||||
className="chart-version"
|
||||
@@ -216,7 +216,8 @@ export class InstallChart extends Component<Props> {
|
||||
<EditorPanel
|
||||
tabId={tabId}
|
||||
value={values}
|
||||
onChange={this.onValuesChange}
|
||||
onChange={this.onChange}
|
||||
onError={this.onError}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -22,11 +22,11 @@
|
||||
@import "~xterm";
|
||||
|
||||
.TerminalWindow {
|
||||
margin: $padding;
|
||||
margin-left: $padding * 2;
|
||||
margin-top: $padding * 2;
|
||||
--spacing: calc(var(--unit) * 2);
|
||||
|
||||
> .xterm {
|
||||
overflow: hidden;
|
||||
}
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
left: var(--spacing) !important;
|
||||
top: var(--spacing) !important;
|
||||
bottom: var(--spacing) !important;
|
||||
}
|
||||
@@ -22,16 +22,14 @@
|
||||
import "./terminal-window.scss";
|
||||
|
||||
import React from "react";
|
||||
import { reaction } from "mobx";
|
||||
import { disposeOnUnmount, observer } from "mobx-react";
|
||||
import { cssNames } from "../../utils";
|
||||
import type { DockTab } from "./dock.store";
|
||||
import type { Terminal } from "./terminal";
|
||||
import { terminalStore } from "./terminal.store";
|
||||
import { TerminalStore } from "./terminal.store";
|
||||
import { ThemeStore } from "../../theme.store";
|
||||
import { dockStore, DockTab, TabKind, TabId } from "./dock.store";
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
tab: DockTab;
|
||||
}
|
||||
|
||||
@@ -42,25 +40,29 @@ export class TerminalWindow extends React.Component<Props> {
|
||||
|
||||
componentDidMount() {
|
||||
disposeOnUnmount(this, [
|
||||
reaction(() => this.props.tab.id, tabId => this.activate(tabId), {
|
||||
dockStore.onTabChange(({ tabId }) => this.activate(tabId), {
|
||||
tabKind: TabKind.TERMINAL,
|
||||
fireImmediately: true,
|
||||
}),
|
||||
|
||||
// refresh terminal available space (cols/rows) when <Dock/> resized
|
||||
dockStore.onResize(() => this.terminal?.fitLazy(), {
|
||||
fireImmediately: true,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
activate(tabId = this.props.tab.id) {
|
||||
activate(tabId: TabId) {
|
||||
if (this.terminal) this.terminal.detach(); // detach previous
|
||||
this.terminal = terminalStore.getTerminal(tabId);
|
||||
this.terminal = TerminalStore.getInstance().getTerminal(tabId);
|
||||
this.terminal.attachTo(this.elem);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className } = this.props;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cssNames("TerminalWindow", className, ThemeStore.getInstance().activeTheme.type)}
|
||||
ref={e => this.elem = e}
|
||||
className={cssNames("TerminalWindow", ThemeStore.getInstance().activeTheme.type)}
|
||||
ref={elem => this.elem = elem}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ import { DockTabStore } from "./dock-tab.store";
|
||||
import { getReleaseValues, HelmRelease } from "../../../common/k8s-api/endpoints/helm-releases.api";
|
||||
import { releaseStore } from "../+apps-releases/release.store";
|
||||
import { iter } from "../../utils";
|
||||
import { monacoModelsManager } from "./monaco-model-manager";
|
||||
|
||||
export interface IChartUpgradeData {
|
||||
releaseName: string;
|
||||
@@ -119,7 +118,6 @@ export class UpgradeChartStore extends DockTabStore<IChartUpgradeData> {
|
||||
const values = await getReleaseValues(releaseName, releaseNamespace, true);
|
||||
|
||||
this.values.setData(tabId, values);
|
||||
monacoModelsManager.getModel(tabId).setValue(values);
|
||||
}
|
||||
|
||||
getTabByRelease(releaseName: string): DockTab {
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
import "./upgrade-chart.scss";
|
||||
|
||||
import React from "react";
|
||||
import { observable, reaction, makeObservable } from "mobx";
|
||||
import { action, makeObservable, observable, reaction } from "mobx";
|
||||
import { disposeOnUnmount, observer } from "mobx-react";
|
||||
import { cssNames } from "../../utils";
|
||||
import type { DockTab } from "./dock.store";
|
||||
@@ -86,9 +86,15 @@ export class UpgradeChart extends React.Component<Props> {
|
||||
this.version = this.versions[0];
|
||||
}
|
||||
|
||||
onChange = (value: string, error?: string) => {
|
||||
@action
|
||||
onChange = (value: string) => {
|
||||
this.error = "";
|
||||
upgradeChartStore.values.setData(this.tabId, value);
|
||||
this.error = error;
|
||||
};
|
||||
|
||||
@action
|
||||
onError = (error: Error | string) => {
|
||||
this.error = error.toString();
|
||||
};
|
||||
|
||||
upgrade = async () => {
|
||||
@@ -118,7 +124,7 @@ export class UpgradeChart extends React.Component<Props> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { tabId, release, value, error, onChange, upgrade, versions, version } = this;
|
||||
const { tabId, release, value, error, onChange, onError, upgrade, versions, version } = this;
|
||||
const { className } = this.props;
|
||||
|
||||
if (!release || upgradeChartStore.isLoading() || !version) {
|
||||
@@ -157,6 +163,7 @@ export class UpgradeChart extends React.Component<Props> {
|
||||
tabId={tabId}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
onError={onError}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user