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

refactoring dock-tab stores -- part 1

Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
Roman 2021-10-03 15:59:22 +03:00
parent fa90eb833c
commit f140bc5b70
14 changed files with 295 additions and 370 deletions

View File

@ -199,7 +199,6 @@
"electron-devtools-installer": "^3.2.0",
"electron-updater": "^4.4.3",
"electron-window-state": "^5.0.3",
"filehound": "^1.17.4",
"fs-extra": "^9.0.1",
"glob-to-regexp": "^0.4.1",
"grapheme-splitter": "^1.0.4",

View File

@ -22,60 +22,29 @@
import fs from "fs-extra";
import path from "path";
import os from "os";
import groupBy from "lodash/groupBy";
import filehound from "filehound";
import { watch } from "chokidar";
import { autoBind } from "../../utils";
import { DockTabStore } from "./dock-tab.store";
import { DockTabsStore } from "./dock-tabs.store";
import { dockStore, DockTabCreateSpecific, TabKind } from "./dock.store";
export class CreateResourceStore extends DockTabStore<string> {
export class CreateResourceStore extends DockTabsStore<string> {
constructor() {
super({
storageKey: "create_resource"
});
autoBind(this);
fs.ensureDirSync(this.userTemplatesFolder);
}
get lensTemplatesFolder():string {
return path.resolve(__static, "../templates/create-resource");
protected async init() {
super.init();
await fs.ensureDir(this.userTemplatesFolder);
}
get userTemplatesFolder():string {
return path.join(os.homedir(), ".k8slens", "templates");
get userTemplatesFolder(): string {
return path.resolve(os.homedir(), "~/.k8slens/templates");
}
async getTemplates(templatesPath: string, defaultGroup: string) {
const templates = await filehound.create().path(templatesPath).ext(["yaml", "json"]).depth(1).find();
return templates ? this.groupTemplates(templates, templatesPath, defaultGroup) : {};
}
groupTemplates(templates: string[], templatesPath: string, defaultGroup: string) {
return groupBy(templates,(v:string) =>
path.relative(templatesPath,v).split(path.sep).length>1
? path.parse(path.relative(templatesPath,v)).dir
: defaultGroup);
}
async getMergedTemplates() {
const userTemplates = await this.getTemplates(this.userTemplatesFolder, "ungrouped");
const lensTemplates = await this.getTemplates(this.lensTemplatesFolder, "lens");
return {...userTemplates,...lensTemplates};
}
async watchUserTemplates(callback: ()=> void){
watch(this.userTemplatesFolder, {
depth: 1,
ignoreInitial: true,
awaitWriteFinish: {
stabilityThreshold: 500
}
}).on("all", () => {
callback();
});
async getBundledTemplate(fileName: string, ext = "yaml") {
return import(`../../../../templates/create-resource/${fileName}.${ext}`);
}
}

View File

@ -20,13 +20,10 @@
*/
import "./create-resource.scss";
import React from "react";
import path from "path";
import fs from "fs-extra";
import { GroupSelectOption, Select, SelectOption } from "../select";
import { Select, SelectOption } from "../select";
import jsYaml from "js-yaml";
import { makeObservable, observable } from "mobx";
import { computed, makeObservable, observable } from "mobx";
import { observer } from "mobx-react";
import { cssNames } from "../../utils";
import { createResourceStore } from "./create-resource.store";
@ -46,27 +43,16 @@ interface Props {
export class CreateResource extends React.Component<Props> {
@observable currentTemplates: Map<string, SelectOption> = new Map();
@observable error = "";
@observable templates: GroupSelectOption<SelectOption>[] = [];
@observable templates = observable.map<string/*filename*/, string>();
constructor(props: Props) {
super(props);
makeObservable(this);
}
componentDidMount() {
createResourceStore.getMergedTemplates().then(v => this.updateGroupSelectOptions(v));
createResourceStore.watchUserTemplates(() => createResourceStore.getMergedTemplates().then(v => this.updateGroupSelectOptions(v)));
}
updateGroupSelectOptions(templates: Record<string, string[]>) {
this.templates = Object.entries(templates)
.map(([name, grouping]) => this.convertToGroup(name, grouping));
}
convertToGroup(group: string, items: string[]): GroupSelectOption {
const options = items.map(v => ({ label: path.parse(v).name, value: v }));
return { label: group, options };
// TODO
@computed get selectTemplateOptions(): SelectOption<string>[] {
return [];
}
get tabId() {
@ -77,7 +63,7 @@ export class CreateResource extends React.Component<Props> {
return createResourceStore.getData(this.tabId);
}
get currentTemplate() {
get selectedTemplate() {
return this.currentTemplates.get(this.tabId) ?? null;
}
@ -89,12 +75,14 @@ export class CreateResource extends React.Component<Props> {
this.error = error;
};
// FIXME
onSelectTemplate = (item: SelectOption) => {
this.currentTemplates.set(this.tabId, item);
fs.readFile(item.value, "utf8").then(value => {
createResourceStore.setData(this.tabId, value);
});
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 () => {
@ -137,13 +125,13 @@ export class CreateResource extends React.Component<Props> {
<div className="flex gaps align-center">
<Select
autoConvertOptions={false}
className="TemplateSelect"
className="SelectResourceTemplate"
placeholder="Select Template ..."
options={this.templates}
options={this.selectTemplateOptions}
menuPlacement="top"
themeName="outlined"
onChange={v => this.onSelectTemplate(v)}
value={this.currentTemplate}
value={this.selectedTemplate}
/>
</div>
);

View File

@ -1,108 +0,0 @@
/**
* 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 { autorun, observable, reaction } from "mobx";
import { autoBind, createStorage, StorageHelper, toJS } from "../../utils";
import { dockStore, TabId } from "./dock.store";
export interface DockTabStoreOptions {
autoInit?: boolean; // load data from storage when `storageKey` is provided and bind events, default: true
storageKey?: string; // save data to persistent storage under the key
}
export type DockTabStorageState<T> = Record<TabId, T>;
export class DockTabStore<T> {
protected storage?: StorageHelper<DockTabStorageState<T>>;
protected data = observable.map<TabId, T>();
constructor(protected options: DockTabStoreOptions = {}) {
autoBind(this);
this.options = {
autoInit: true,
...this.options,
};
if (this.options.autoInit) {
this.init();
}
}
protected init() {
const { storageKey } = this.options;
// auto-save to local-storage
if (storageKey) {
this.storage = createStorage(storageKey, {});
this.storage.whenReady.then(() => {
this.data.replace(this.storage.get());
reaction(() => this.toJSON(), data => this.storage.set(data));
});
}
// clear data for closed tabs
autorun(() => {
const currentTabs = dockStore.tabs.map(tab => tab.id);
Array.from(this.data.keys()).forEach(tabId => {
if (!currentTabs.includes(tabId)) {
this.clearData(tabId);
}
});
});
}
protected finalizeDataForSave(data: T): T {
return data;
}
protected toJSON(): DockTabStorageState<T> {
const deepCopy = toJS(this.data);
deepCopy.forEach((tabData, key) => {
deepCopy.set(key, this.finalizeDataForSave(tabData));
});
return Object.fromEntries<T>(deepCopy);
}
isReady(tabId: TabId): boolean {
return Boolean(this.getData(tabId) !== undefined);
}
getData(tabId: TabId) {
return this.data.get(tabId);
}
setData(tabId: TabId, data: T) {
this.data.set(tabId, data);
}
clearData(tabId: TabId) {
this.data.delete(tabId);
}
reset() {
this.data.clear();
this.storage?.reset();
}
}

View File

@ -0,0 +1,141 @@
/**
* 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 { action, autorun, IReactionDisposer, IReactionOptions, makeObservable, observable, reaction } from "mobx";
import { autoBind, createStorage, Disposer, disposer, StorageHelper } from "../../utils";
import { dockStore, TabId } from "./dock.store";
export interface DockTabStoreOptions {
autoInit?: boolean; // load data from storage when `storageKey` is provided and bind events, default: true
storageKey?: string; // save data to persistent storage under the key
}
export class DockTabsStore<T extends {}> {
private storage?: StorageHelper<Record<TabId, T>>; // available only with `options.storageKey`
private _data = observable.object<Record<TabId, T>>({});
protected watchers = observable.map<TabId, IReactionDisposer | Disposer>([], { deep: false });
protected dispose = disposer();
get data() {
if (this.options.storageKey) {
return this.storage.get();
} else {
return this._data;
}
}
set data(value: Record<TabId, T>) {
if (this.options.storageKey) {
this.storage.set(value);
} else {
this._data = value;
}
}
constructor(protected options: DockTabStoreOptions = {}) {
autoBind(this);
makeObservable(this);
this.options = {
autoInit: true,
...this.options,
};
if (this.options.storageKey) {
this.storage = createStorage(this.options.storageKey, {}); // starts preloading json file via Node.fs right away
}
if (this.options.autoInit) {
this.whenReady.then(() => this.init());
}
}
get whenReady(): Promise<any> {
return Promise.allSettled([
dockStore.whenReady,
this.storage?.whenReady,
]);
}
protected init() {
this.dispose.push(
autorun(() => {
const docTabIds = dockStore.tabs.map(tab => tab.id);
const savedDataTabIds = Object.keys(this.data);
savedDataTabIds.forEach(tabId => {
const isTabClosed = !docTabIds.includes(tabId);
if (isTabClosed) {
this.clearData(tabId); // clear related tab's data
this.watchers.get(tabId)?.(); // dispose tab related data watcher
dockStore.closeTab(tabId); // make sure dock tab is closed
}
});
}),
// clean up data watchers (if any)
() => this.disposeWatchers(),
);
}
protected disposeWatchers() {
this.watchers.forEach(dispose => dispose());
this.watchers.clear();
}
onDataChange(callback: (entries: [TabId, T][]) => void, opts?: IReactionOptions): IReactionDisposer {
return reaction(() => Object.entries(this.data), callback, opts);
}
onTabDataChange(tabId: TabId, callback: (data: T, prevData?: T) => void, opts?: IReactionOptions): IReactionDisposer {
return reaction(() => this.data[tabId], callback, opts);
}
isReady(tabId: TabId): boolean {
return Boolean(this.getData(tabId) !== undefined);
}
getData(tabId: TabId) {
return this.data[tabId];
}
@action
setData(tabId: TabId, data: T) {
this.data[tabId] = data;
}
@action
clearData(tabId: TabId) {
delete this.data[tabId];
}
@action
reset() {
this.data = {};
}
destroy() {
this.dispose();
this.disposeWatchers();
this.reset();
}
}

View File

@ -88,6 +88,17 @@ export interface DockStorageState {
isOpen?: boolean;
}
export interface DockTabChangeEvent {
selectedTabId?: TabId;
tab?: DockTab;
prevTab?: DockTab;
}
export interface DockTabChangeEventOptions extends IReactionOptions {
kind?: TabKind; // filter: matching by dockStore.selectedTab.kind
isVisible?: boolean; // filter: dock and selected tab should be visible
}
export class DockStore implements DockStorageState {
constructor() {
makeObservable(this);
@ -178,8 +189,18 @@ 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);
onTabChange(callback?: (evt: DockTabChangeEvent) => void, options: DockTabChangeEventOptions = {}) {
const { kind, isVisible, ...reactionOpts } = options;
return reaction(() => this.selectedTab, ((tab, prevTab) => {
if (kind && kind !== tab.kind) return;
if (isVisible && !dockStore.isOpen) return;
callback({
tab, prevTab,
selectedTabId: this.selectedTabId,
});
}), reactionOpts);
}
hasTabs() {

View File

@ -20,8 +20,7 @@
*/
import { autoBind, noop } from "../../utils";
import { DockTabStore } from "./dock-tab.store";
import { autorun, IReactionDisposer } from "mobx";
import { DockTabsStore } from "./dock-tabs.store";
import { dockStore, DockTab, DockTabCreateSpecific, TabId, TabKind } from "./dock.store";
import type { KubeObject } from "../../../common/k8s-api/kube-object";
import { apiManager } from "../../../common/k8s-api/api-manager";
@ -32,9 +31,7 @@ export interface EditingResource {
draft?: string; // edited draft in yaml
}
export class EditResourceStore extends DockTabStore<EditingResource> {
private watchers = new Map<TabId, IReactionDisposer>();
export class EditResourceStore extends DockTabsStore<EditingResource> {
constructor() {
super({
storageKey: "edit_resource_store",
@ -44,34 +41,19 @@ export class EditResourceStore extends DockTabStore<EditingResource> {
protected async init() {
super.init();
await this.storage.whenReady;
autorun(() => {
Array.from(this.data).forEach(([tabId, { resource }]) => {
if (this.watchers.get(tabId)) {
return;
this.dispose.push(
dockStore.onTabChange(({ selectedTabId }) => {
const { resource } = this.getData(selectedTabId);
const store = apiManager.getStore(resource);
if (!store?.getByPath(resource)) {
store?.loadFromPath(resource).catch(noop); // preload resource by uri
}
this.watchers.set(tabId, autorun(() => {
const store = apiManager.getStore(resource);
if (store) {
const isActiveTab = dockStore.isOpen && dockStore.selectedTabId === tabId;
const obj = store.getByPath(resource);
// preload resource for editing
if (!obj && !store.isLoaded && !store.isLoading && isActiveTab) {
store.loadFromPath(resource).catch(noop);
}
// auto-close tab when resource removed from store
else if (!obj && store.isLoaded) {
dockStore.closeTab(tabId);
}
}
}, {
delay: 100 // make sure all kube-object stores are initialized
}));
});
});
}, {
fireImmediately: true,
})
);
}
protected finalizeDataForSave({ draft, ...data }: EditingResource): EditingResource {
@ -97,20 +79,12 @@ export class EditResourceStore extends DockTabStore<EditingResource> {
}
getTabByResource(object: KubeObject): DockTab {
const [tabId] = Array.from(this.data).find(([, { resource }]) => {
const [tabId] = Object.entries(this.data).find(([, { resource }]) => {
return object.selfLink === resource;
}) || [];
return dockStore.getTabById(tabId);
}
reset() {
super.reset();
Array.from(this.watchers).forEach(([tabId, dispose]) => {
this.watchers.delete(tabId);
dispose();
});
}
}
export const editResourceStore = new EditResourceStore();

View File

@ -19,9 +19,9 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { action, autorun, makeObservable } from "mobx";
import { action, makeObservable, observable } from "mobx";
import { dockStore, DockTabCreateSpecific, TabId, TabKind } from "./dock.store";
import { DockTabStore } from "./dock-tab.store";
import { DockTabsStore } from "./dock-tabs.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";
@ -37,23 +37,29 @@ export interface IChartInstallData {
lastVersion?: boolean;
}
export class InstallChartStore extends DockTabStore<IChartInstallData> {
public versions = new DockTabStore<string[]>();
public details = new DockTabStore<IReleaseUpdateDetails>();
export class InstallChartStore extends DockTabsStore<IChartInstallData> {
details = observable.map<TabId, IReleaseUpdateDetails>();
versions = observable.map<TabId, string[]>();
constructor() {
super({
storageKey: "install_charts"
});
makeObservable(this);
autorun(() => {
const { selectedTab, isOpen } = dockStore;
}
if (selectedTab?.kind === TabKind.INSTALL_CHART && isOpen) {
this.loadData(selectedTab.id)
.catch(err => Notifications.error(String(err)));
}
}, { delay: 250 });
protected init() {
super.init();
this.dispose.push(
dockStore.onTabChange(({ selectedTabId }) => {
this.loadData(selectedTabId).catch(err => Notifications.error(String(err)));
}, {
kind: TabKind.INSTALL_CHART,
fireImmediately: true,
isVisible: true,
}),
);
}
@action
@ -64,7 +70,7 @@ export class InstallChartStore extends DockTabStore<IChartInstallData> {
promises.push(this.loadValues(tabId));
}
if (!this.versions.getData(tabId)) {
if (!this.versions.get(tabId)) {
promises.push(this.loadVersions(tabId));
}
@ -75,11 +81,11 @@ export class InstallChartStore extends DockTabStore<IChartInstallData> {
async loadVersions(tabId: TabId) {
const { repo, name, version } = this.getData(tabId);
this.versions.clearData(tabId); // reset
this.versions.delete(tabId); // reset
const charts = await getChartDetails(repo, name, { version });
const versions = charts.versions.map(chartVersion => chartVersion.version);
this.versions.setData(tabId, versions);
this.versions.set(tabId, versions);
}
@action

View File

@ -68,11 +68,11 @@ export class InstallChart extends Component<Props> {
}
get versions() {
return installChartStore.versions.getData(this.tabId);
return installChartStore.versions.get(this.tabId);
}
get releaseDetails() {
return installChartStore.details.getData(this.tabId);
return installChartStore.details.get(this.tabId);
}
viewRelease = () => {
@ -120,7 +120,7 @@ export class InstallChart extends Component<Props> {
repo, namespace, version, values,
});
installChartStore.details.setData(this.tabId, details);
installChartStore.details.set(this.tabId, details);
return (
<p>Chart Release <b>{details.release.name}</b> successfully created.</p>

View File

@ -25,7 +25,7 @@ import { podsStore } from "../+workloads-pods/pods.store";
import { IPodContainer, Pod } from "../../../common/k8s-api/endpoints";
import type { WorkloadKubeObject } from "../../../common/k8s-api/workload-kube-object";
import { DockTabStore } from "./dock-tab.store";
import { DockTabsStore } from "./dock-tabs.store";
import { dockStore, DockTabCreateSpecific, TabKind } from "./dock.store";
export interface LogTabData {
@ -45,15 +45,19 @@ interface WorkloadLogsTabData {
workload: WorkloadKubeObject
}
export class LogTabStore extends DockTabStore<LogTabData> {
export class LogTabStore extends DockTabsStore<LogTabData> {
constructor() {
super({
storageKey: "pod_logs"
});
}
reaction(() => podsStore.items.length, () => {
this.updateTabsData();
});
protected init() {
super.init();
this.dispose.push(
reaction(() => podsStore.items.length, () => this.updateTabsData()),
);
}
createPodTab({ selectedPod, selectedContainer }: PodLogsTabData): void {
@ -108,10 +112,8 @@ export class LogTabStore extends DockTabStore<LogTabData> {
});
}
private async updateTabsData() {
const promises: Promise<void>[] = [];
for (const [tabId, tabData] of this.data) {
private updateTabsData() {
for (const [tabId, tabData] of Object.entries(this.data)) {
const pod = new Pod(tabData.selectedPod);
const pods = podsStore.getPodsByOwnerId(pod.getOwnerRefs()[0]?.uid);
const isSelectedPodInList = pods.find(item => item.getId() == pod.getId());
@ -127,17 +129,8 @@ export class LogTabStore extends DockTabStore<LogTabData> {
});
this.renameTab(tabId);
} else {
promises.push(this.closeTab(tabId));
}
}
await Promise.all(promises);
}
private async closeTab(tabId: string) {
this.clearData(tabId);
await dockStore.closeTab(tabId);
}
}

View File

@ -19,27 +19,19 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { action, autorun, computed, IReactionDisposer, makeObservable, reaction } from "mobx";
import { action, makeObservable, observable, when } from "mobx";
import { dockStore, DockTab, DockTabCreateSpecific, TabId, TabKind } from "./dock.store";
import { DockTabStore } from "./dock-tab.store";
import { DockTabsStore } from "./dock-tabs.store";
import { getReleaseValues, HelmRelease } from "../../../common/k8s-api/endpoints/helm-releases.api";
import { releaseStore } from "../+apps-releases/release.store";
import { iter } from "../../utils";
export interface IChartUpgradeData {
releaseName: string;
releaseNamespace: string;
}
export class UpgradeChartStore extends DockTabStore<IChartUpgradeData> {
private watchers = new Map<string, IReactionDisposer>();
values = new DockTabStore<string>();
@computed
private get releaseNameReverseLookup(): Map<string, string> {
return new Map(iter.map(this.data, ([id, { releaseName }]) => [releaseName, id]));
}
export class UpgradeChartStore extends DockTabsStore<IChartUpgradeData> {
public values = observable.map<TabId, string>();
constructor() {
super({
@ -47,82 +39,66 @@ export class UpgradeChartStore extends DockTabStore<IChartUpgradeData> {
});
makeObservable(this);
autorun(() => {
const { selectedTab, isOpen } = dockStore;
if (selectedTab?.kind === TabKind.UPGRADE_CHART && isOpen) {
this.loadData(selectedTab.id);
}
}, { delay: 250 });
autorun(() => {
const objects = [...this.data.values()];
objects.forEach(({ releaseName }) => this.createReleaseWatcher(releaseName));
});
}
private createReleaseWatcher(releaseName: string) {
if (this.watchers.get(releaseName)) {
return;
}
const dispose = reaction(() => {
const release = releaseStore.getByName(releaseName);
get whenReady() {
return Promise.all([
super.whenReady,
when(() => releaseStore.isLoaded),
]);
}
return release?.getRevision(); // watch changes only by revision
},
release => {
const releaseTab = this.getTabByRelease(releaseName);
protected init() {
super.init();
if (!releaseStore.isLoaded || !releaseTab) {
return;
}
// auto-reload values if was loaded before
if (release) {
if (dockStore.selectedTab === releaseTab && this.values.getData(releaseTab.id)) {
this.loadValues(releaseTab.id);
this.dispose.push(
dockStore.onTabChange(({ selectedTabId }) => {
if (!this.isLoaded(selectedTabId)) {
this.loadData(selectedTabId); // preload just once
}
}
// clean up watcher, close tab if release not exists / was removed
else {
dispose();
this.watchers.delete(releaseName);
dockStore.closeTab(releaseTab.id);
}
});
this.watchers.set(releaseName, dispose);
}, {
kind: TabKind.UPGRADE_CHART,
isVisible: true,
fireImmediately: true,
}),
);
}
isLoading(tabId = dockStore.selectedTabId) {
const values = this.values.getData(tabId);
const values = this.values.get(tabId);
return !releaseStore.isLoaded || values === undefined;
}
isLoaded(tabId: TabId) {
return this.values.has(tabId);
}
@action
async loadData(tabId: TabId) {
const values = this.values.getData(tabId);
private async loadData(tabId: TabId) {
const values = this.values.get(tabId);
await Promise.all([
!releaseStore.isLoaded && releaseStore.loadFromContextNamespaces(),
!values && this.loadValues(tabId)
!values && this.reloadValues(tabId)
]);
}
@action
async loadValues(tabId: TabId) {
this.values.clearData(tabId); // reset
private async reloadValues(tabId: TabId) {
this.values.delete(tabId);
const { releaseName, releaseNamespace } = this.getData(tabId);
const values = await getReleaseValues(releaseName, releaseNamespace, true);
this.values.setData(tabId, values);
this.values.set(tabId, values);
}
getTabByRelease(releaseName: string): DockTab {
return dockStore.getTabById(this.releaseNameReverseLookup.get(releaseName));
const entry = Object
.entries(this.data)
.find(([, data]) => data.releaseName === releaseName);
return dockStore.getTabById(entry?.[0]);
}
}

View File

@ -73,7 +73,7 @@ export class UpgradeChart extends React.Component<Props> {
}
get value() {
return upgradeChartStore.values.getData(this.tabId);
return upgradeChartStore.values.get(this.tabId);
}
async loadVersions() {
@ -87,7 +87,7 @@ export class UpgradeChart extends React.Component<Props> {
}
onChange = (value: string) => {
upgradeChartStore.values.setData(this.tabId, value);
upgradeChartStore.values.set(this.tabId, value);
};
onError = (error: string) => {

View File

@ -21,12 +21,12 @@
import "./monaco-editor.scss";
import React from "react";
import { action, computed, makeObservable, observable, reaction, toJS, when } from "mobx";
import { action, computed, makeObservable, observable, reaction, when } from "mobx";
import { observer } from "mobx-react";
import { editor, Uri } from "monaco-editor";
import { ThemeStore } from "../../theme.store";
import { UserStore } from "../../../common/user-store";
import { cssNames, disposer } from "../../utils";
import { cssNames, disposer, toJS } from "../../utils";
export interface MonacoEditorProps {
id?: string; // associating editor's ID with created model.uri
@ -65,6 +65,9 @@ export const defaultEditorProps: Partial<MonacoEditorProps> = {
}
};
// FIXME: load resource templates via webpack's dynamic import for create-resource dock tab
// 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;
@ -99,6 +102,7 @@ export class MonacoEditor extends React.Component<MonacoEditorProps> {
const resizeObserver = new ResizeObserver(entries => {
for (const entry of entries) {
const { width, height } = entry.contentRect;
this.setDimensions(width, height);
}
});
@ -128,13 +132,10 @@ export class MonacoEditor extends React.Component<MonacoEditorProps> {
return this.getModelById(this.editorId);
}
@computed get globalEditorOptions() {
return toJS(UserStore.getInstance().editorConfiguration);
}
componentDidMount() {
try {
this.createEditor();
if (this.props.autoFocus) {
this.editor.focus();
}
@ -159,6 +160,7 @@ export class MonacoEditor extends React.Component<MonacoEditorProps> {
return;
}
const { language, theme, readOnly, value: defaultValue, options } = this.props;
const globalEditorOptions = toJS(UserStore.getInstance().editorConfiguration);
this.editor = editor.create(this.containerElem, {
model: this.model,
@ -166,7 +168,7 @@ export class MonacoEditor extends React.Component<MonacoEditorProps> {
language,
theme,
readOnly,
...this.globalEditorOptions,
...globalEditorOptions,
...options,
});
console.info(`[MONACO]: editor created for language=${language}, theme=${theme}`);
@ -198,7 +200,7 @@ export class MonacoEditor extends React.Component<MonacoEditorProps> {
() => onContentSizeChangeDisposer.dispose(),
this.bindResizeObserver(),
);
};
}
private destroyEditor(): void {
if (!this.editor) return;
@ -232,12 +234,14 @@ export class MonacoEditor extends React.Component<MonacoEditorProps> {
getModelById(id: string): editor.ITextModel | null {
const uri = this.createUri(id);
const model = editor.getModels().find(model => String(model.uri) === String(uri));
if (model) {
return model; // model with corresponding props.id exists
}
// creating new temporary model if not exists regarding to props.ID
const { language, value } = this.props;
return editor.createModel(value, language, uri);
}

View File

@ -3223,7 +3223,7 @@ bluebird-lst@^1.0.9:
dependencies:
bluebird "^3.5.5"
bluebird@^3.4.7, bluebird@^3.5.0, bluebird@^3.5.1, bluebird@^3.5.3, bluebird@^3.5.5:
bluebird@^3.5.0, bluebird@^3.5.1, bluebird@^3.5.3, bluebird@^3.5.5:
version "3.7.2"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
@ -5981,7 +5981,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2:
assign-symbols "^1.0.0"
is-extendable "^1.0.1"
extend@^3.0.0, extend@~3.0.2:
extend@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
@ -6125,15 +6125,6 @@ file-entry-cache@^5.0.1:
dependencies:
flat-cache "^2.0.1"
file-js@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/file-js/-/file-js-0.3.0.tgz#fab46bf782346c9294499f1f0d2ad07d838f25d1"
integrity sha1-+rRr94I0bJKUSZ8fDSrQfYOPJdE=
dependencies:
bluebird "^3.4.7"
minimatch "^3.0.3"
proper-lockfile "^1.2.0"
file-loader@^6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d"
@ -6147,18 +6138,6 @@ file-uri-to-path@1.0.0:
resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==
filehound@^1.17.4:
version "1.17.4"
resolved "https://registry.yarnpkg.com/filehound/-/filehound-1.17.4.tgz#3f5b76c5b3edc1080311ba802e1ad43179e4291e"
integrity sha512-A74hiTADH20bpFbXBNyKtpqN4Guffa+ROmdGJWNnuCRhaD45UVSVoI6McLcpHYmuaOERrzD3gMV3v9VZq/SHeA==
dependencies:
bluebird "^3.5.1"
file-js "0.3.0"
lodash "^4.17.10"
minimatch "^3.0.4"
moment "^2.22.1"
unit-compare "^1.0.1"
filelist@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.1.tgz#f10d1a3ae86c1694808e8f20906f43d4c9132dbb"
@ -9605,7 +9584,7 @@ minimalistic-crypto-utils@^1.0.1:
resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=
minimatch@3.0.4, minimatch@^3.0.0, minimatch@^3.0.3, minimatch@^3.0.4:
minimatch@3.0.4, minimatch@^3.0.0, minimatch@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
@ -9731,7 +9710,7 @@ moment-timezone@^0.5.33:
dependencies:
moment ">= 2.9.0"
"moment@>= 2.9.0", moment@^2.10.2, moment@^2.14.1, moment@^2.22.1, moment@^2.29.1:
"moment@>= 2.9.0", moment@^2.10.2, moment@^2.29.1:
version "2.29.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
@ -11415,16 +11394,6 @@ prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2:
object-assign "^4.1.1"
react-is "^16.8.1"
proper-lockfile@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/proper-lockfile/-/proper-lockfile-1.2.0.tgz#ceff5dd89d3e5f10fb75e1e8e76bc75801a59c34"
integrity sha1-zv9d2J0+XxD7deHo52vHWAGlnDQ=
dependencies:
err-code "^1.0.0"
extend "^3.0.0"
graceful-fs "^4.1.2"
retry "^0.10.0"
proper-lockfile@^4.1.1, proper-lockfile@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/proper-lockfile/-/proper-lockfile-4.1.2.tgz#c8b9de2af6b2f1601067f98e01ac66baa223141f"
@ -14107,13 +14076,6 @@ unique-string@^2.0.0:
dependencies:
crypto-random-string "^2.0.0"
unit-compare@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/unit-compare/-/unit-compare-1.0.1.tgz#0c7459f0e5bf53637ea873ca3cee18de2eeca386"
integrity sha1-DHRZ8OW/U2N+qHPKPO4Y3i7so4Y=
dependencies:
moment "^2.14.1"
universalify@^0.1.0, universalify@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"