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:
parent
7355f61465
commit
c4aa88183f
@ -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`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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}
|
||||
/>
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user