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

dock-tabs: create-resoure refactoring & fixes

Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
Roman 2021-10-06 14:03:39 +03:00
parent 7355f61465
commit c4aa88183f
3 changed files with 127 additions and 42 deletions

View File

@ -22,29 +22,102 @@
import fs from "fs-extra";
import path from "path";
import os from "os";
import { autoBind } from "../../utils";
import { DockTabStore } from "./dock-tab.store";
import { dockStore, DockTabCreateSpecific, TabKind } from "./dock.store";
import { action, makeObservable, observable } from "mobx";
export interface TemplatesGroup {
label: string;
templates: Record<string/*filename*/, string /*contents*/>;
}
export class CreateResourceStore extends DockTabStore<string> {
constructor() {
super({
storageKey: "create_resource"
});
autoBind(this);
super({ storageKey: "create_resource" });
makeObservable(this);
}
protected async init() {
super.init();
await fs.ensureDir(this.userTemplatesFolder);
readonly templateGroups: Record<string, TemplatesGroup> = observable({
[this.appTemplatesFolder]: {
label: "Lens Templates",
templates: {} as Record<string/*filename*/, string /*contents*/>,
},
[this.userTemplatesFolder]: {
label: "Custom templates",
templates: {},
},
});
get appTemplatesFolder(): string {
// production: declare files to copy in "package.json" -> "build.extraResources"
return path.resolve(__static, "../templates/create-resource");
}
get userTemplatesFolder(): string {
return path.resolve(os.homedir(), "~/.k8slens/templates");
}
async getTemplate(fileName: string, ext = "yaml") {
return import(`../../../../templates/create-resource/${fileName}.${ext}?raw`);
protected async init() {
super.init();
// scan all available templates immediately
await this.scanTemplates(this.appTemplatesFolder);
await this.scanTemplates(this.userTemplatesFolder);
}
@action
private async scanTemplates(sourceFolder: string): Promise<void> {
try {
const templatesGroup = this.templateGroups[sourceFolder];
if (!templatesGroup) return; // exit: unknown templates source folder
await fs.ensureDir(sourceFolder);
const fileNames = await fs.readdir(sourceFolder);
templatesGroup.templates = Object.fromEntries(
fileNames.map(fileName => [fileName, "" /* empty: content not loaded yet */])
);
} catch (error) {
console.error(`[CREATE-RESOURCE]: scanning template folders error: ${error}`);
}
}
@action
async loadTemplate(init: { fileName: string, sourceFolder: string }): Promise<string> {
const { fileName, sourceFolder } = init;
const templatesGroup = this.templateGroups[sourceFolder];
if (!templatesGroup) {
return ""; // unknown group, exit
}
const template = templatesGroup.templates[fileName];
if (template) return template; // return cache, preloaded already
try {
const templatePath = path.resolve(sourceFolder, fileName);
const pathExists = await fs.pathExists(templatePath);
if (!pathExists) return ""; // template file not exists, skip
const textContent = await fs.readFile(templatePath, { encoding: "utf-8" });
templatesGroup.templates[fileName] = textContent; // save cache
return textContent;
} catch (error) {
console.error(`[CREATE-RESOURCE]: reading "${sourceFolder}/${fileName}" has failed: ${error}`);
}
return "";
}
// bundled app templates via webpack.renderer.ts, "?raw"-query loads content as text
async getBundledTemplate(fileName: string, ext = "yaml"): Promise<string> {
return import(`${this.appTemplatesFolder}/${fileName}.${ext}?raw`);
}
}

View File

@ -21,7 +21,7 @@
import "./create-resource.scss";
import React from "react";
import { Select, SelectOption } from "../select";
import { GroupSelectOption, Select, SelectOption } from "../select";
import jsYaml from "js-yaml";
import { computed, makeObservable, observable } from "mobx";
import { observer } from "mobx-react";
@ -39,34 +39,31 @@ interface Props {
tab: DockTab;
}
type SelectOptionTemplate = SelectOption<SelectOptionTemplateValue>;
interface SelectOptionTemplateValue {
sourceFolder: string;
fileName: string;
content?: string; // available after loading (template selection)
}
@observer
export class CreateResource extends React.Component<Props> {
@observable currentTemplates: Map<string, SelectOption> = new Map();
@observable error = "";
@observable templates = observable.map<string/*filename*/, string>();
constructor(props: Props) {
super(props);
makeObservable(this);
}
// TODO
@computed get templateOptions(): SelectOption<string>[] {
return [];
}
@observable error = "";
get tabId() {
return this.props.tab.id;
}
get data() {
get draft() {
return createResourceStore.getData(this.tabId);
}
get selectedTemplate() {
return this.currentTemplates.get(this.tabId) ?? null;
}
onChange = (value: string) => {
createResourceStore.setData(this.tabId, value);
};
@ -75,24 +72,14 @@ export class CreateResource extends React.Component<Props> {
this.error = error;
};
// FIXME
onSelectTemplate = (item: SelectOption) => {
console.log(`SELECTED TEMPLATE: ${this.tabId}`, item);
// this.currentTemplates.set(this.tabId, item);
//
// fs.readFile(item.value, "utf8").then(templateFileContent => {
// createResourceStore.setData(this.tabId, templateFileContent);
// });
};
create = async () => {
if (this.error || !this.data.trim()) {
if (this.error || !this.draft) {
// do not save when field is empty or there is an error
return null;
}
// skip empty documents if "---" pasted at the beginning or end
const resources = jsYaml.safeLoadAll(this.data).filter(Boolean);
const resources = jsYaml.safeLoadAll(this.draft).filter(Boolean);
const createdResources: string[] = [];
const errors: string[] = [];
@ -125,20 +112,41 @@ export class CreateResource extends React.Component<Props> {
<div className="flex gaps align-center">
<Select
autoConvertOptions={false}
controlShouldRenderValue={false} // always keep initial placeholder
className="SelectResourceTemplate"
placeholder="Select Template ..."
options={this.templateOptions}
menuPlacement="top"
themeName="outlined"
options={this.templateOptions}
onChange={v => this.onSelectTemplate(v)}
value={this.selectedTemplate}
/>
</div>
);
}
@computed get templateOptions(): GroupSelectOption<SelectOptionTemplate>[] {
return Object.entries(createResourceStore.templateGroups).map(([sourceFolder, group]) => {
return {
label: group.label,
options: Object.entries(group.templates).map(([fileName]) => {
return {
label: fileName,
value: { fileName, sourceFolder },
};
}),
};
});
}
onSelectTemplate = async ({ value }: SelectOptionTemplate) => {
const { fileName, sourceFolder, content } = value;
const templateContent = content ?? await createResourceStore.loadTemplate({ fileName, sourceFolder });
createResourceStore.setData(this.tabId, templateContent); // update draft
};
render() {
const { tabId, data, error, create } = this;
const { tabId, draft, error, create } = this;
const { className } = this.props;
return (
@ -153,7 +161,7 @@ export class CreateResource extends React.Component<Props> {
/>
<MonacoEditor
id={tabId}
value={data}
value={draft}
onChange={this.onChange}
onError={this.onError}
/>

View File

@ -65,8 +65,6 @@ export const defaultEditorProps: Partial<MonacoEditorProps> = {
}
};
// FIXME: apply changes of props.options and globalOptions.editor to active editor
@observer
export class MonacoEditor extends React.Component<MonacoEditorProps> {
static defaultProps = defaultEditorProps as object;
@ -186,6 +184,10 @@ export class MonacoEditor extends React.Component<MonacoEditorProps> {
this.disposeOnUnmount.push(
reaction(() => this.model, this.onModelChange),
reaction(() => this.props.theme, editor.setTheme),
reaction(() => this.props.value, value => this.setValue(value)),
reaction(() => toJS(this.props.options), opts => this.editor.updateOptions(opts)),
() => onDidLayoutChangeDisposer.dispose(),
() => onValueChangeDisposer.dispose(),
() => onContentSizeChangeDisposer.dispose(),
@ -237,6 +239,8 @@ export class MonacoEditor extends React.Component<MonacoEditorProps> {
}
setValue(value: string) {
if (value == this.getValue()) return;
this.editor.setValue(value);
}