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
|
@action
|
||||||
async load(params: { name: string; namespace?: string }): Promise<T> {
|
async load(params: { name: string; namespace?: string, save?: boolean }): Promise<T> {
|
||||||
const { name, namespace } = params;
|
const { name, namespace, save = true } = params;
|
||||||
let item = this.getByName(name, namespace);
|
let item = this.getByName(name, namespace);
|
||||||
|
|
||||||
if (!item) {
|
if (!item) {
|
||||||
item = await this.loadItem(params);
|
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;
|
return item;
|
||||||
@ -263,7 +266,10 @@ export abstract class KubeObjectStore<T extends KubeObject> extends ItemStore<T>
|
|||||||
async loadFromPath(resourcePath: string) {
|
async loadFromPath(resourcePath: string) {
|
||||||
const { namespace, name } = parseKubeApi(resourcePath);
|
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> {
|
protected async createItem(params: { name: string; namespace?: string }, data?: Partial<T>): Promise<T> {
|
||||||
|
|||||||
@ -20,7 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import isEqual from "lodash/isEqual";
|
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 { autoBind } from "../../utils";
|
||||||
import { createRelease, deleteRelease, HelmRelease, IReleaseCreatePayload, IReleaseUpdatePayload, listReleases, rollbackRelease, updateRelease } from "../../../common/k8s-api/endpoints/helm-releases.api";
|
import { createRelease, deleteRelease, HelmRelease, IReleaseCreatePayload, IReleaseUpdatePayload, listReleases, rollbackRelease, updateRelease } from "../../../common/k8s-api/endpoints/helm-releases.api";
|
||||||
import { ItemStore } from "../../../common/item.store";
|
import { ItemStore } from "../../../common/item.store";
|
||||||
@ -106,7 +106,10 @@ export class ReleaseStore extends ItemStore<HelmRelease> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async loadFromContextNamespaces(): Promise<void> {
|
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[]) {
|
async loadItems(namespaces: string[]) {
|
||||||
|
|||||||
@ -19,8 +19,8 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { autorun, computed, IReactionDisposer, IReactionOptions, makeObservable, observable, reaction } from "mobx";
|
import { comparer, makeObservable, observable, reaction } from "mobx";
|
||||||
import { autoBind, createStorage, Disposer, disposer, StorageHelper } from "../../utils";
|
import { autoBind, createStorage, disposer, StorageHelper } from "../../utils";
|
||||||
import { dockStore, TabId } from "./dock.store";
|
import { dockStore, TabId } from "./dock.store";
|
||||||
|
|
||||||
export interface DockTabStoreOptions {
|
export interface DockTabStoreOptions {
|
||||||
@ -29,14 +29,13 @@ export interface DockTabStoreOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class DockTabStore<T extends {}> {
|
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.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
|
@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();
|
protected dispose = disposer();
|
||||||
|
|
||||||
@computed get data() {
|
get data() {
|
||||||
if (this.options.storageKey) {
|
if (this.options.storageKey) {
|
||||||
return this.storage.get();
|
return this.storage.get();
|
||||||
}
|
}
|
||||||
@ -63,6 +62,7 @@ export class DockTabStore<T extends {}> {
|
|||||||
|
|
||||||
if (this.options.storageKey) {
|
if (this.options.storageKey) {
|
||||||
this.storage = createStorage(this.options.storageKey, this._data);
|
this.storage = createStorage(this.options.storageKey, this._data);
|
||||||
|
this.dispose.push(() => this.storage.dispose());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.options.autoInit) {
|
if (this.options.autoInit) {
|
||||||
@ -78,48 +78,33 @@ export class DockTabStore<T extends {}> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected init() {
|
protected init() {
|
||||||
this.dataReady = true;
|
|
||||||
|
|
||||||
this.dispose.push(
|
this.dispose.push(
|
||||||
autorun(() => {
|
reaction(() => ({
|
||||||
const docTabIds = dockStore.tabs.map(tab => tab.id);
|
dockTabs: dockStore.tabs.map(tab => tab.id) as TabId[],
|
||||||
const savedDataTabIds = Object.keys(this.data);
|
dataTabs: Object.keys(this.data) as TabId[],
|
||||||
|
}), ({ dataTabs, dockTabs }) => {
|
||||||
|
|
||||||
savedDataTabIds.forEach(tabId => {
|
// clear data for closed or non-existing dock tabs
|
||||||
const isTabClosed = !docTabIds.includes(tabId);
|
if (dockTabs.length < dataTabs.length) {
|
||||||
|
const closedDockTabs = dataTabs.filter(id => !dockTabs.includes(id));
|
||||||
|
|
||||||
if (isTabClosed) {
|
closedDockTabs.forEach(tabId => this.clearData(tabId));
|
||||||
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
|
fireImmediately: true,
|
||||||
}
|
equals: comparer.structural,
|
||||||
});
|
})
|
||||||
}),
|
|
||||||
|
|
||||||
// clean up data watchers (if any)
|
|
||||||
() => this.disposeWatchers(),
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
protected disposeWatchers() {
|
this.dataReady = true;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getData(tabId: TabId): T {
|
getData(tabId: TabId): T {
|
||||||
return this.data[tabId] ?? {} as T;
|
return this.data[tabId];
|
||||||
}
|
}
|
||||||
|
|
||||||
setData(tabId: TabId, data: T) {
|
setData(tabId: TabId, data: T) {
|
||||||
this.data[tabId] = { ...data };
|
this.data[tabId] = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
clearData(tabId: TabId) {
|
clearData(tabId: TabId) {
|
||||||
@ -128,12 +113,11 @@ export class DockTabStore<T extends {}> {
|
|||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.data = {};
|
this.data = {};
|
||||||
|
this.dataReady = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
this.dataReady = false;
|
|
||||||
this.dispose();
|
|
||||||
this.disposeWatchers();
|
|
||||||
this.reset();
|
this.reset();
|
||||||
|
this.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,8 +20,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import * as uuid from "uuid";
|
import * as uuid from "uuid";
|
||||||
import { action, computed, IReactionOptions, makeObservable, observable, reaction } from "mobx";
|
import { action, comparer, computed, IReactionOptions, makeObservable, observable, reaction } from "mobx";
|
||||||
import { autoBind, createStorage } from "../../utils";
|
import { autoBind, createStorage, Disposer } from "../../utils";
|
||||||
import throttle from "lodash/throttle";
|
import throttle from "lodash/throttle";
|
||||||
|
|
||||||
export type TabId = string;
|
export type TabId = string;
|
||||||
@ -189,6 +189,27 @@ export class DockStore implements DockStorageState {
|
|||||||
return reaction(() => [this.height, this.fullSize], callback, options);
|
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 = {}) {
|
onTabChange(callback?: (evt: DockTabChangeEvent) => void, options: DockTabChangeEventOptions = {}) {
|
||||||
const { kind, dockIsVisible = true, ...reactionOpts } = options;
|
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 type { KubeObject } from "../../../common/k8s-api/kube-object";
|
||||||
import { apiManager } from "../../../common/k8s-api/api-manager";
|
import { apiManager } from "../../../common/k8s-api/api-manager";
|
||||||
import type { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
|
import type { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
|
||||||
|
import jsYaml from "js-yaml";
|
||||||
|
|
||||||
export interface EditingResource {
|
export interface EditingResource {
|
||||||
resource: string; // resource path, e.g. /api/v1/namespaces/default
|
resource: string; // resource path, e.g. /api/v1/namespaces/default
|
||||||
@ -42,12 +43,17 @@ export class EditResourceStore extends DockTabStore<EditingResource> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async loadResource(tabId: TabId) {
|
async loadResource(tabId: TabId) {
|
||||||
const resource = this.getResource(tabId);
|
|
||||||
const store = this.getStore(tabId);
|
const store = this.getStore(tabId);
|
||||||
const data = this.getData(tabId);
|
const data = this.getData(tabId);
|
||||||
|
let resource = this.getResource(tabId);
|
||||||
|
|
||||||
if (!resource && store && data) {
|
if (!store || !data) return;
|
||||||
await store.loadFromPath(data.resource); // preloading resource for the first time
|
|
||||||
|
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 {
|
get draft(): string {
|
||||||
const { draft } = editResourceStore.getData(this.tabId);
|
return editResourceStore.getData(this.tabId)?.draft ?? "";
|
||||||
|
|
||||||
if (typeof draft === "string") {
|
|
||||||
return draft; // get previously edited draft
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.resource) {
|
|
||||||
return jsYaml.safeDump(this.resource.toPlainObject()); // dump resource first time
|
|
||||||
}
|
|
||||||
|
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
@ -80,10 +70,7 @@ export class EditResource extends React.Component<Props> {
|
|||||||
draft = draft ? jsYaml.safeDump(draft) : undefined;
|
draft = draft ? jsYaml.safeDump(draft) : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
editResourceStore.setData(this.tabId, {
|
editResourceStore.getData(this.tabId).draft = draft;
|
||||||
...editResourceStore.getData(this.tabId),
|
|
||||||
draft,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange = (draft: string) => {
|
onChange = (draft: string) => {
|
||||||
|
|||||||
@ -20,7 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import uniqueId from "lodash/uniqueId";
|
import uniqueId from "lodash/uniqueId";
|
||||||
import { reaction } from "mobx";
|
import { reaction, when } from "mobx";
|
||||||
import { podsStore } from "../+workloads-pods/pods.store";
|
import { podsStore } from "../+workloads-pods/pods.store";
|
||||||
|
|
||||||
import { IPodContainer, Pod } from "../../../common/k8s-api/endpoints";
|
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() {
|
protected init() {
|
||||||
super.init();
|
super.init();
|
||||||
|
|
||||||
|
|||||||
@ -21,10 +21,11 @@
|
|||||||
|
|
||||||
// Helper for working with storages (e.g. window.localStorage, NodeJS/file-system, etc.)
|
// 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 produce, { Draft } from "immer";
|
||||||
import { isEqual, isFunction, isPlainObject } from "lodash";
|
import { isEqual, isFunction, isPlainObject } from "lodash";
|
||||||
import logger from "../../main/logger";
|
import logger from "../../main/logger";
|
||||||
|
import { disposer } from "../../common/utils";
|
||||||
|
|
||||||
export interface StorageAdapter<T> {
|
export interface StorageAdapter<T> {
|
||||||
[metadata: string]: any;
|
[metadata: string]: any;
|
||||||
@ -36,6 +37,7 @@ export interface StorageAdapter<T> {
|
|||||||
|
|
||||||
export interface StorageHelperOptions<T> {
|
export interface StorageHelperOptions<T> {
|
||||||
autoInit?: boolean; // start preloading data immediately, default: true
|
autoInit?: boolean; // start preloading data immediately, default: true
|
||||||
|
autoSave?: boolean; // bind auto-saving changes to storage-adapter, default: true
|
||||||
storage: StorageAdapter<T>;
|
storage: StorageAdapter<T>;
|
||||||
defaultValue: T;
|
defaultValue: T;
|
||||||
}
|
}
|
||||||
@ -43,6 +45,7 @@ export interface StorageHelperOptions<T> {
|
|||||||
export class StorageHelper<T> {
|
export class StorageHelper<T> {
|
||||||
static logPrefix = "[StorageHelper]:";
|
static logPrefix = "[StorageHelper]:";
|
||||||
readonly storage: StorageAdapter<T>;
|
readonly storage: StorageAdapter<T>;
|
||||||
|
public dispose = disposer();
|
||||||
|
|
||||||
private data = observable.box<T>(undefined, {
|
private data = observable.box<T>(undefined, {
|
||||||
deep: true,
|
deep: true,
|
||||||
@ -63,17 +66,21 @@ export class StorageHelper<T> {
|
|||||||
constructor(readonly key: string, private options: StorageHelperOptions<T>) {
|
constructor(readonly key: string, private options: StorageHelperOptions<T>) {
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
|
|
||||||
const { storage, autoInit = true } = options;
|
const { storage, autoInit = true, autoSave = true } = options;
|
||||||
|
|
||||||
this.storage = storage;
|
this.storage = storage;
|
||||||
|
|
||||||
this.data.observe_(({ newValue, oldValue }) => {
|
|
||||||
this.onChange(newValue as T, oldValue as T);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (autoInit) {
|
if (autoInit) {
|
||||||
this.init();
|
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 => {
|
private onData = (data: T): void => {
|
||||||
@ -114,7 +121,7 @@ export class StorageHelper<T> {
|
|||||||
return isEqual(value, this.defaultValue);
|
return isEqual(value, this.defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onChange(value: T, oldValue?: T) {
|
protected onChange = (value: T, oldValue?: T) => {
|
||||||
if (!this.initialized) return;
|
if (!this.initialized) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -128,7 +135,7 @@ export class StorageHelper<T> {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`${StorageHelper.logPrefix} updating storage: ${error}`, this, { value, oldValue });
|
logger.error(`${StorageHelper.logPrefix} updating storage: ${error}`, this, { value, oldValue });
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
get(): T {
|
get(): T {
|
||||||
return this.data.get() ?? this.defaultValue;
|
return this.data.get() ?? this.defaultValue;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user