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:
parent
5038267bbc
commit
b421591931
@ -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> {
|
||||
|
||||
@ -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[]) {
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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) => {
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user