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

refactoring dock-tab stores -- part 3

Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
Roman 2021-10-05 12:11:26 +03:00
parent 5038267bbc
commit b421591931
8 changed files with 96 additions and 75 deletions

View File

@ -245,15 +245,18 @@ export abstract class KubeObjectStore<T extends KubeObject> extends ItemStore<T>
}
@action
async load(params: { name: string; namespace?: string }): Promise<T> {
const { name, namespace } = params;
async load(params: { name: string; namespace?: string, save?: boolean }): Promise<T> {
const { name, namespace, save = true } = params;
let item = this.getByName(name, namespace);
if (!item) {
item = await this.loadItem(params);
const newItems = this.sortItems([...this.items, item]);
this.items.replace(newItems);
if (save) {
const newItems = this.sortItems([...this.items, item]);
this.items.replace(newItems);
}
}
return item;
@ -263,7 +266,10 @@ export abstract class KubeObjectStore<T extends KubeObject> extends ItemStore<T>
async loadFromPath(resourcePath: string) {
const { namespace, name } = parseKubeApi(resourcePath);
return this.load({ name, namespace });
return this.load({
name, namespace,
save: false, // don't save loaded resource to store
});
}
protected async createItem(params: { name: string; namespace?: string }, data?: Partial<T>): Promise<T> {

View File

@ -20,7 +20,7 @@
*/
import isEqual from "lodash/isEqual";
import { action, observable, reaction, when, makeObservable } from "mobx";
import { action, makeObservable, observable, reaction, when } from "mobx";
import { autoBind } from "../../utils";
import { createRelease, deleteRelease, HelmRelease, IReleaseCreatePayload, IReleaseUpdatePayload, listReleases, rollbackRelease, updateRelease } from "../../../common/k8s-api/endpoints/helm-releases.api";
import { ItemStore } from "../../../common/item.store";
@ -106,7 +106,10 @@ export class ReleaseStore extends ItemStore<HelmRelease> {
}
async loadFromContextNamespaces(): Promise<void> {
return this.loadAll(namespaceStore.context.contextNamespaces);
const { contextNamespaces } = namespaceStore.context ?? {};
if (!contextNamespaces) return;
await this.loadAll(contextNamespaces);
}
async loadItems(namespaces: string[]) {

View File

@ -19,8 +19,8 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { autorun, computed, IReactionDisposer, IReactionOptions, makeObservable, observable, reaction } from "mobx";
import { autoBind, createStorage, Disposer, disposer, StorageHelper } from "../../utils";
import { comparer, makeObservable, observable, reaction } from "mobx";
import { autoBind, createStorage, disposer, StorageHelper } from "../../utils";
import { dockStore, TabId } from "./dock.store";
export interface DockTabStoreOptions {
@ -29,14 +29,13 @@ export interface DockTabStoreOptions {
}
export class DockTabStore<T extends {}> {
@observable private _data: Record<TabId, T> = {};
@observable.ref private storage?: StorageHelper<Record<TabId, T>>; // available only with `options.storageKey`
@observable private _data: Record<TabId, T> = {};
@observable dataReady = false; // dock-tab's data ready to interact, e.g. start editing resource
protected watchers = observable.map<TabId, IReactionDisposer | Disposer>();
protected dispose = disposer();
@computed get data() {
get data() {
if (this.options.storageKey) {
return this.storage.get();
}
@ -63,6 +62,7 @@ export class DockTabStore<T extends {}> {
if (this.options.storageKey) {
this.storage = createStorage(this.options.storageKey, this._data);
this.dispose.push(() => this.storage.dispose());
}
if (this.options.autoInit) {
@ -78,48 +78,33 @@ export class DockTabStore<T extends {}> {
}
protected init() {
this.dataReady = true;
this.dispose.push(
autorun(() => {
const docTabIds = dockStore.tabs.map(tab => tab.id);
const savedDataTabIds = Object.keys(this.data);
reaction(() => ({
dockTabs: dockStore.tabs.map(tab => tab.id) as TabId[],
dataTabs: Object.keys(this.data) as TabId[],
}), ({ dataTabs, dockTabs }) => {
savedDataTabIds.forEach(tabId => {
const isTabClosed = !docTabIds.includes(tabId);
// clear data for closed or non-existing dock tabs
if (dockTabs.length < dataTabs.length) {
const closedDockTabs = dataTabs.filter(id => !dockTabs.includes(id));
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(),
closedDockTabs.forEach(tabId => this.clearData(tabId));
}
}, {
fireImmediately: true,
equals: comparer.structural,
})
);
}
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);
this.dataReady = true;
}
getData(tabId: TabId): T {
return this.data[tabId] ?? {} as T;
return this.data[tabId];
}
setData(tabId: TabId, data: T) {
this.data[tabId] = { ...data };
this.data[tabId] = data;
}
clearData(tabId: TabId) {
@ -128,12 +113,11 @@ export class DockTabStore<T extends {}> {
reset() {
this.data = {};
this.dataReady = false;
}
destroy() {
this.dataReady = false;
this.dispose();
this.disposeWatchers();
this.reset();
this.dispose();
}
}

View File

@ -20,8 +20,8 @@
*/
import * as uuid from "uuid";
import { action, computed, IReactionOptions, makeObservable, observable, reaction } from "mobx";
import { autoBind, createStorage } from "../../utils";
import { action, comparer, computed, IReactionOptions, makeObservable, observable, reaction } from "mobx";
import { autoBind, createStorage, Disposer } from "../../utils";
import throttle from "lodash/throttle";
export type TabId = string;
@ -189,6 +189,27 @@ export class DockStore implements DockStorageState {
return reaction(() => [this.height, this.fullSize], callback, options);
}
onTabClose(tabId: TabId, callback: () => void, options: IReactionOptions = {}): Disposer {
let disposed = false;
const stopWatcher = reaction(() => this.getTabById(tabId), tab => {
if (!tab) {
disposed = true;
stopWatcher();
callback();
}
}, {
fireImmediately: true,
equals: comparer.shallow,
...options,
});
return () => {
if (disposed) return;
stopWatcher();
};
}
onTabChange(callback?: (evt: DockTabChangeEvent) => void, options: DockTabChangeEventOptions = {}) {
const { kind, dockIsVisible = true, ...reactionOpts } = options;

View File

@ -26,6 +26,7 @@ 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 jsYaml from "js-yaml";
export interface EditingResource {
resource: string; // resource path, e.g. /api/v1/namespaces/default
@ -42,12 +43,17 @@ export class EditResourceStore extends DockTabStore<EditingResource> {
}
async loadResource(tabId: TabId) {
const resource = this.getResource(tabId);
const store = this.getStore(tabId);
const data = this.getData(tabId);
let resource = this.getResource(tabId);
if (!resource && store && data) {
await store.loadFromPath(data.resource); // preloading resource for the first time
if (!store || !data) return;
try {
resource ??= await store.loadFromPath(data.resource);
this.getData(tabId).draft = jsYaml.safeDump(resource.toPlainObject());
} catch (error) {
console.error(`[DOCK]: dump of resource "${data.resource}" failed: ${error}`);
}
}

View File

@ -61,17 +61,7 @@ export class EditResource extends React.Component<Props> {
}
get draft(): string {
const { draft } = editResourceStore.getData(this.tabId);
if (typeof draft === "string") {
return draft; // get previously edited draft
}
if (this.resource) {
return jsYaml.safeDump(this.resource.toPlainObject()); // dump resource first time
}
return "";
return editResourceStore.getData(this.tabId)?.draft ?? "";
}
@action
@ -80,10 +70,7 @@ export class EditResource extends React.Component<Props> {
draft = draft ? jsYaml.safeDump(draft) : undefined;
}
editResourceStore.setData(this.tabId, {
...editResourceStore.getData(this.tabId),
draft,
});
editResourceStore.getData(this.tabId).draft = draft;
}
onChange = (draft: string) => {

View File

@ -20,7 +20,7 @@
*/
import uniqueId from "lodash/uniqueId";
import { reaction } from "mobx";
import { reaction, when } from "mobx";
import { podsStore } from "../+workloads-pods/pods.store";
import { IPodContainer, Pod } from "../../../common/k8s-api/endpoints";
@ -52,6 +52,13 @@ export class LogTabStore extends DockTabStore<LogTabData> {
});
}
get whenReady(): Promise<any> {
return Promise.all([
super.whenReady,
when(() => podsStore.isLoaded),
]);
}
protected init() {
super.init();

View File

@ -21,10 +21,11 @@
// Helper for working with storages (e.g. window.localStorage, NodeJS/file-system, etc.)
import { action, comparer, makeObservable, observable, toJS, when, } from "mobx";
import { action, comparer, IReactionDisposer, IReactionOptions, makeObservable, observable, reaction, toJS, when, } from "mobx";
import produce, { Draft } from "immer";
import { isEqual, isFunction, isPlainObject } from "lodash";
import logger from "../../main/logger";
import { disposer } from "../../common/utils";
export interface StorageAdapter<T> {
[metadata: string]: any;
@ -36,6 +37,7 @@ export interface StorageAdapter<T> {
export interface StorageHelperOptions<T> {
autoInit?: boolean; // start preloading data immediately, default: true
autoSave?: boolean; // bind auto-saving changes to storage-adapter, default: true
storage: StorageAdapter<T>;
defaultValue: T;
}
@ -43,6 +45,7 @@ export interface StorageHelperOptions<T> {
export class StorageHelper<T> {
static logPrefix = "[StorageHelper]:";
readonly storage: StorageAdapter<T>;
public dispose = disposer();
private data = observable.box<T>(undefined, {
deep: true,
@ -63,17 +66,21 @@ export class StorageHelper<T> {
constructor(readonly key: string, private options: StorageHelperOptions<T>) {
makeObservable(this);
const { storage, autoInit = true } = options;
const { storage, autoInit = true, autoSave = true } = options;
this.storage = storage;
this.data.observe_(({ newValue, oldValue }) => {
this.onChange(newValue as T, oldValue as T);
});
if (autoInit) {
this.init();
}
if (autoSave) {
this.dispose.push(this.bindAutoSave());
}
}
protected bindAutoSave(options: IReactionOptions = {}): IReactionDisposer {
return reaction(() => this.toJSON(), this.onChange, options);
}
private onData = (data: T): void => {
@ -114,7 +121,7 @@ export class StorageHelper<T> {
return isEqual(value, this.defaultValue);
}
protected onChange(value: T, oldValue?: T) {
protected onChange = (value: T, oldValue?: T) => {
if (!this.initialized) return;
try {
@ -128,7 +135,7 @@ export class StorageHelper<T> {
} catch (error) {
logger.error(`${StorageHelper.logPrefix} updating storage: ${error}`, this, { value, oldValue });
}
}
};
get(): T {
return this.data.get() ?? this.defaultValue;