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

replacing toJS({}) to utils/cloneJson where might be necessary

Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
Roman 2021-04-23 16:10:29 +03:00
parent f620c09095
commit 4fbefb648a
19 changed files with 110 additions and 110 deletions

View File

@ -49,9 +49,9 @@ export class ExamplePreferencesStore extends Store.ExtensionStore<ExamplePrefere
} }
toJSON(): ExamplePreferencesModel { toJSON(): ExamplePreferencesModel {
return toJS({ return {
enabled: this.enabled enabled: toJS(this.enabled),
}); };
} }
} }
``` ```
@ -71,7 +71,6 @@ The `enabled` field of the `ExamplePreferencesStore` is set to the value from th
The `toJSON()` method is complementary to `fromStore()`. The `toJSON()` method is complementary to `fromStore()`.
It is called when the store is being saved. It is called when the store is being saved.
`toJSON()` must provide a JSON serializable object, facilitating its storage in JSON format. `toJSON()` must provide a JSON serializable object, facilitating its storage in JSON format.
The `toJS()` function from [`mobx`](https://mobx.js.org/README.html) is convenient for this purpose, and is used here.
Finally, `ExamplePreferencesStore` is created by calling `ExamplePreferencesStore.getInstanceOrCreate()`, and exported for use by other parts of the extension. Finally, `ExamplePreferencesStore` is created by calling `ExamplePreferencesStore.getInstanceOrCreate()`, and exported for use by other parts of the extension.
Note that `ExamplePreferencesStore` is a singleton. Note that `ExamplePreferencesStore` is a singleton.

View File

@ -25,6 +25,7 @@ export class KubernetesCluster implements CatalogEntity {
constructor(data: CatalogEntityData) { constructor(data: CatalogEntityData) {
makeObservable(this); makeObservable(this);
this.metadata = data.metadata; this.metadata = data.metadata;
this.status = data.status as KubernetesClusterStatus; this.status = data.status as KubernetesClusterStatus;
this.spec = data.spec as KubernetesClusterSpec; this.spec = data.spec as KubernetesClusterSpec;

View File

@ -1,4 +1,4 @@
import { action, computed, observable, IObservableArray, makeObservable } from "mobx"; import { action, computed, IObservableArray, makeObservable, observable, toJS } from "mobx";
import { CatalogEntity } from "./catalog-entity"; import { CatalogEntity } from "./catalog-entity";
export class CatalogEntityRegistry { export class CatalogEntityRegistry {
@ -17,7 +17,7 @@ export class CatalogEntityRegistry {
} }
@computed get items(): CatalogEntity[] { @computed get items(): CatalogEntity[] {
return Array.from(this.sources.values()).flat(); return Array.from(toJS(this.sources).values()).flat();
} }
getItemsForApiKind<T extends CatalogEntity>(apiVersion: string, kind: string): T[] { getItemsForApiKind<T extends CatalogEntity>(apiVersion: string, kind: string): T[] {

View File

@ -1,7 +1,7 @@
import path from "path"; import path from "path";
import { app, ipcRenderer, remote, webFrame } from "electron"; import { app, ipcRenderer, remote, webFrame } from "electron";
import { unlink } from "fs-extra"; import { unlink } from "fs-extra";
import { action, comparer, computed, observable, reaction, toJS, makeObservable } from "mobx"; import { action, comparer, computed, makeObservable, observable, reaction, toJS } from "mobx";
import { BaseStore } from "./base-store"; import { BaseStore } from "./base-store";
import { Cluster, ClusterState } from "../main/cluster"; import { Cluster, ClusterState } from "../main/cluster";
import migrations from "../migrations/cluster-store"; import migrations from "../migrations/cluster-store";
@ -12,6 +12,7 @@ import { saveToAppFiles } from "./utils/saveToAppFiles";
import { KubeConfig } from "@kubernetes/client-node"; import { KubeConfig } from "@kubernetes/client-node";
import { handleRequest, requestMain, subscribeToBroadcast, unsubscribeAllFromBroadcast } from "./ipc"; import { handleRequest, requestMain, subscribeToBroadcast, unsubscribeAllFromBroadcast } from "./ipc";
import { ResourceType } from "../renderer/components/cluster-settings/components/cluster-metrics-setting"; import { ResourceType } from "../renderer/components/cluster-settings/components/cluster-metrics-setting";
import { cloneJson } from "./utils";
export interface ClusterIconUpload { export interface ClusterIconUpload {
clusterId: string; clusterId: string;
@ -47,7 +48,7 @@ export interface ClusterModel {
* Workspace id * Workspace id
* *
* @deprecated * @deprecated
*/ */
workspace?: string; workspace?: string;
/** User context in kubeconfig */ /** User context in kubeconfig */
@ -330,7 +331,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
} }
toJSON(): ClusterStoreModel { toJSON(): ClusterStoreModel {
return toJS({ return cloneJson({
activeCluster: this.activeCluster, activeCluster: this.activeCluster,
clusters: this.clustersList.map(cluster => cluster.toJSON()), clusters: this.clustersList.map(cluster => cluster.toJSON()),
}); });

View File

@ -1,7 +1,8 @@
import { action, comparer, observable, toJS, makeObservable } from "mobx"; import { action, comparer, makeObservable, observable } from "mobx";
import { BaseStore } from "./base-store"; import { BaseStore } from "./base-store";
import migrations from "../migrations/hotbar-store"; import migrations from "../migrations/hotbar-store";
import * as uuid from "uuid"; import * as uuid from "uuid";
import { cloneJson } from "./utils";
export interface HotbarItem { export interface HotbarItem {
entity: { entity: {
@ -59,7 +60,8 @@ export class HotbarStore extends BaseStore<HotbarStoreModel> {
return this.hotbars.findIndex((hotbar) => hotbar.id === this.activeHotbarId); return this.hotbars.findIndex((hotbar) => hotbar.id === this.activeHotbarId);
} }
@action protected async fromStore(data: Partial<HotbarStoreModel> = {}) { @action
protected async fromStore(data: Partial<HotbarStoreModel> = {}) {
if (data.hotbars?.length === 0) { if (data.hotbars?.length === 0) {
this.hotbars = [{ this.hotbars = [{
id: uuid.v4(), id: uuid.v4(),
@ -139,11 +141,9 @@ export class HotbarStore extends BaseStore<HotbarStoreModel> {
} }
toJSON(): HotbarStoreModel { toJSON(): HotbarStoreModel {
const model: HotbarStoreModel = { return cloneJson({
hotbars: this.hotbars, hotbars: this.hotbars,
activeHotbarId: this.activeHotbarId activeHotbarId: this.activeHotbarId
}; });
return toJS(model);
} }
} }

View File

@ -2,10 +2,10 @@
// https://www.electronjs.org/docs/api/ipc-main // https://www.electronjs.org/docs/api/ipc-main
// https://www.electronjs.org/docs/api/ipc-renderer // https://www.electronjs.org/docs/api/ipc-renderer
import { ipcMain, ipcRenderer, webContents, remote } from "electron"; import { ipcMain, ipcRenderer, remote, webContents } from "electron";
import { toJS } from "mobx"; import { toJS } from "mobx";
import logger from "../../main/logger"; import logger from "../../main/logger";
import { ClusterFrameInfo, clusterFrameMap } from "../cluster-frames"; import { ClusterFrameInfo, clusterFrameMap } from "../cluster-frames";
const subFramesChannel = "ipc:get-sub-frames"; const subFramesChannel = "ipc:get-sub-frames";
@ -18,7 +18,7 @@ export async function requestMain(channel: string, ...args: any[]) {
} }
function getSubFrames(): ClusterFrameInfo[] { function getSubFrames(): ClusterFrameInfo[] {
return toJS(Array.from(clusterFrameMap.values())); return Array.from(toJS(clusterFrameMap).values());
} }
export async function broadcastMessage(channel: string, ...args: any[]) { export async function broadcastMessage(channel: string, ...args: any[]) {

View File

@ -1,15 +1,19 @@
// Global configuration setup for external packages. // Global configuration setup for external packages.
// Should be imported at the top of app's entry points. // Should be imported at the top of app's entry points.
import { configure } from "mobx"; import * as Mobx from "mobx";
import { enableMapSet, setAutoFreeze } from "immer"; import * as Immer from "immer";
// Mobx
// Docs: https://mobx.js.org/configuration.html // Docs: https://mobx.js.org/configuration.html
configure({ Mobx.configure({
isolateGlobalState: true,
enforceActions: "never", enforceActions: "never",
isolateGlobalState: true,
// TODO: enable later (read more: https://mobx.js.org/migrating-from-4-or-5.html)
// computedRequiresReaction: true,
// reactionRequiresObservable: true,
// observableRequiresReaction: true,
}); });
// Immer // Docs: https://immerjs.github.io/immer/
setAutoFreeze(false); // allow to merge mobx observables, docs: https://immerjs.github.io/immer/freezing Immer.setAutoFreeze(false); // allow to merge mobx observables
enableMapSet(); // allow to merge maps and sets, docs: https://immerjs.github.io/immer/map-set Immer.enableMapSet(); // allow to merge maps and sets

View File

@ -2,16 +2,15 @@ import type { ThemeId } from "../renderer/theme.store";
import { app, remote } from "electron"; import { app, remote } from "electron";
import semver from "semver"; import semver from "semver";
import { readFile } from "fs-extra"; import { readFile } from "fs-extra";
import { action, computed, observable, reaction, toJS, makeObservable } from "mobx"; import { action, computed, makeObservable, observable, reaction } from "mobx";
import moment from "moment-timezone"; import moment from "moment-timezone";
import { BaseStore } from "./base-store"; import { BaseStore } from "./base-store";
import migrations from "../migrations/user-store"; import migrations, { fileNameMigration } from "../migrations/user-store";
import { getAppVersion } from "./utils/app-version"; import { cloneJson, getAppVersion } from "./utils";
import { kubeConfigDefaultPath, loadConfig } from "./kube-helpers"; import { kubeConfigDefaultPath, loadConfig } from "./kube-helpers";
import { appEventBus } from "./event-bus"; import { appEventBus } from "./event-bus";
import logger from "../main/logger"; import logger from "../main/logger";
import path from "path"; import path from "path";
import { fileNameMigration } from "../migrations/user-store";
export interface UserStoreModel { export interface UserStoreModel {
kubeConfigPath: string; kubeConfigPath: string;
@ -181,7 +180,7 @@ export class UserStore extends BaseStore<UserStoreModel> {
} }
toJSON(): UserStoreModel { toJSON(): UserStoreModel {
return toJS({ return cloneJson({
kubeConfigPath: this.kubeConfigPath, kubeConfigPath: this.kubeConfigPath,
lastSeenAppVersion: this.lastSeenAppVersion, lastSeenAppVersion: this.lastSeenAppVersion,
seenContexts: Array.from(this.seenContexts), seenContexts: Array.from(this.seenContexts),

View File

@ -1,5 +1,5 @@
// Clone json-serializable object // Clone json-serializable object
export function cloneJsonObject<T = object>(obj: T): T { export function cloneJson<T>(data: T): T {
return JSON.parse(JSON.stringify(obj)); return JSON.parse(JSON.stringify(data));
} }

View File

@ -2,30 +2,29 @@ import { watch } from "chokidar";
import { ipcRenderer } from "electron"; import { ipcRenderer } from "electron";
import { EventEmitter } from "events"; import { EventEmitter } from "events";
import fs from "fs-extra"; import fs from "fs-extra";
import { observable, reaction, toJS, when, makeObservable } from "mobx"; import { makeObservable, observable, reaction, when } from "mobx";
import os from "os"; import os from "os";
import path from "path"; import path from "path";
import { broadcastMessage, handleRequest, requestMain, subscribeToBroadcast } from "../common/ipc"; import { broadcastMessage, handleRequest, requestMain, subscribeToBroadcast } from "../common/ipc";
import { Singleton } from "../common/utils"; import { cloneJson, Singleton } from "../common/utils";
import logger from "../main/logger"; import logger from "../main/logger";
import { extensionInstaller, PackageJson } from "./extension-installer"; import { extensionInstaller, PackageJson } from "./extension-installer";
import { ExtensionsStore } from "./extensions-store"; import { ExtensionsStore } from "./extensions-store";
import type { LensExtensionId, LensExtensionManifest } from "./lens-extension"; import type { LensExtensionId, LensExtensionManifest } from "./lens-extension";
export interface InstalledExtension { export interface InstalledExtension {
id: LensExtensionId; id: LensExtensionId;
readonly manifest: LensExtensionManifest;
readonly manifest: LensExtensionManifest; // Absolute path to the non-symlinked source folder,
// e.g. "/Users/user/.k8slens/extensions/helloworld"
readonly absolutePath: string;
// Absolute path to the non-symlinked source folder, // Absolute to the symlinked package.json file
// e.g. "/Users/user/.k8slens/extensions/helloworld" readonly manifestPath: string;
readonly absolutePath: string; readonly isBundled: boolean; // defined in project root's package.json
isEnabled: boolean;
// Absolute to the symlinked package.json file }
readonly manifestPath: string;
readonly isBundled: boolean; // defined in project root's package.json
isEnabled: boolean;
}
const logModule = "[EXTENSION-DISCOVERY]"; const logModule = "[EXTENSION-DISCOVERY]";
@ -151,7 +150,7 @@ export class ExtensionDiscovery extends Singleton {
.on("unlinkDir", this.handleWatchUnlinkDir); .on("unlinkDir", this.handleWatchUnlinkDir);
} }
handleWatchFileAdd = async (manifestPath: string) => { handleWatchFileAdd = async (manifestPath: string) => {
// e.g. "foo/package.json" // e.g. "foo/package.json"
const relativePath = path.relative(this.localFolderPath, manifestPath); const relativePath = path.relative(this.localFolderPath, manifestPath);
@ -262,7 +261,6 @@ export class ExtensionDiscovery extends Singleton {
// fs.remove won't throw if path is missing // fs.remove won't throw if path is missing
await fs.remove(path.join(extensionInstaller.extensionPackagesRoot, "package-lock.json")); await fs.remove(path.join(extensionInstaller.extensionPackagesRoot, "package-lock.json"));
try { try {
// Verify write access to static/extensions, which is needed for symlinking // Verify write access to static/extensions, which is needed for symlinking
await fs.access(this.inTreeFolderPath, fs.constants.W_OK); await fs.access(this.inTreeFolderPath, fs.constants.W_OK);
@ -447,7 +445,7 @@ export class ExtensionDiscovery extends Singleton {
} }
toJSON(): ExtensionDiscoveryChannelMessage { toJSON(): ExtensionDiscoveryChannelMessage {
return toJS({ return cloneJson({
isLoaded: this.isLoaded isLoaded: this.isLoaded
}); });
} }

View File

@ -1,6 +1,7 @@
import type { LensExtensionId } from "./lens-extension"; import type { LensExtensionId } from "./lens-extension";
import { BaseStore } from "../common/base-store"; import { BaseStore } from "../common/base-store";
import { action, computed, observable, toJS, makeObservable } from "mobx"; import { action, computed, makeObservable, observable } from "mobx";
import { cloneJson } from "../common/utils";
export interface LensExtensionsStoreModel { export interface LensExtensionsStoreModel {
extensions: Record<LensExtensionId, LensExtensionState>; extensions: Record<LensExtensionId, LensExtensionState>;
@ -22,8 +23,8 @@ export class ExtensionsStore extends BaseStore<LensExtensionsStoreModel> {
@computed @computed
get enabledExtensions() { get enabledExtensions() {
return Array.from(this.state.values()) return Array.from(this.state.values())
.filter(({enabled}) => enabled) .filter(({ enabled }) => enabled)
.map(({name}) => name); .map(({ name }) => name);
} }
protected state = observable.map<LensExtensionId, LensExtensionState>(); protected state = observable.map<LensExtensionId, LensExtensionState>();
@ -47,7 +48,7 @@ export class ExtensionsStore extends BaseStore<LensExtensionsStoreModel> {
} }
toJSON(): LensExtensionsStoreModel { toJSON(): LensExtensionsStoreModel {
return toJS({ return cloneJson({
extensions: Object.fromEntries(this.state), extensions: Object.fromEntries(this.state),
}); });
} }

View File

@ -1,13 +1,13 @@
import "../common/cluster-ipc"; import "../common/cluster-ipc";
import type http from "http"; import type http from "http";
import { ipcMain } from "electron"; import { ipcMain } from "electron";
import { action, autorun, observable, reaction, toJS, makeObservable } from "mobx"; import { action, autorun, makeObservable, observable, reaction, toJS } from "mobx";
import { ClusterStore, getClusterIdFromHost } from "../common/cluster-store"; import { ClusterStore, getClusterIdFromHost } from "../common/cluster-store";
import { Cluster } from "./cluster"; import { Cluster } from "./cluster";
import logger from "./logger"; import logger from "./logger";
import { apiKubePrefix } from "../common/vars"; import { apiKubePrefix } from "../common/vars";
import { Singleton } from "../common/utils"; import { cloneJson, Singleton } from "../common/utils";
import { CatalogEntity } from "../common/catalog-entity"; import { CatalogEntity, CatalogEntityData } from "../common/catalog-entity";
import { KubernetesCluster } from "../common/catalog-entities/kubernetes-cluster"; import { KubernetesCluster } from "../common/catalog-entities/kubernetes-cluster";
import { catalogEntityRegistry } from "../common/catalog-entity-registry"; import { catalogEntityRegistry } from "../common/catalog-entity-registry";
@ -41,7 +41,6 @@ export class ClusterManager extends Singleton {
this.syncClustersFromCatalog(entities); this.syncClustersFromCatalog(entities);
}); });
// auto-stop removed clusters // auto-stop removed clusters
autorun(() => { autorun(() => {
const removedClusters = Array.from(ClusterStore.getInstance().removedClusters.values()); const removedClusters = Array.from(ClusterStore.getInstance().removedClusters.values());
@ -57,11 +56,16 @@ export class ClusterManager extends Singleton {
delay: 250 delay: 250
}); });
ipcMain.on("network:offline", () => { this.onNetworkOffline(); }); ipcMain.on("network:offline", () => {
ipcMain.on("network:online", () => { this.onNetworkOnline(); }); this.onNetworkOffline();
});
ipcMain.on("network:online", () => {
this.onNetworkOnline();
});
} }
@action protected updateCatalogSource(clusters: Cluster[]) { @action
protected updateCatalogSource(clusters: Cluster[]) {
this.catalogSource.forEach((entity, index) => { this.catalogSource.forEach((entity, index) => {
const clusterIndex = clusters.findIndex((cluster) => entity.metadata.uid === cluster.id); const clusterIndex = clusters.findIndex((cluster) => entity.metadata.uid === cluster.id);
@ -121,7 +125,7 @@ export class ClusterManager extends Singleton {
} }
protected catalogEntityFromCluster(cluster: Cluster) { protected catalogEntityFromCluster(cluster: Cluster) {
return new KubernetesCluster(toJS({ const data: CatalogEntityData = cloneJson({
apiVersion: "entity.k8slens.dev/v1alpha1", apiVersion: "entity.k8slens.dev/v1alpha1",
kind: "KubernetesCluster", kind: "KubernetesCluster",
metadata: { metadata: {
@ -142,7 +146,9 @@ export class ClusterManager extends Singleton {
message: "", message: "",
active: !cluster.disconnected active: !cluster.disconnected
} }
})); });
return new KubernetesCluster(data);
} }
protected onNetworkOffline() { protected onNetworkOffline() {

View File

@ -1,7 +1,7 @@
import { ipcMain } from "electron"; import { ipcMain } from "electron";
import type { ClusterId, ClusterMetadata, ClusterModel, ClusterPreferences, ClusterPrometheusPreferences } from "../common/cluster-store"; import type { ClusterId, ClusterMetadata, ClusterModel, ClusterPreferences, ClusterPrometheusPreferences } from "../common/cluster-store";
import type { IMetricsReqParams } from "../renderer/api/endpoints/metrics.api"; import type { IMetricsReqParams } from "../renderer/api/endpoints/metrics.api";
import { action, comparer, computed, makeObservable, observable, reaction, toJS, when } from "mobx"; import { action, comparer, computed, makeObservable, observable, reaction, when } from "mobx";
import { apiKubePrefix } from "../common/vars"; import { apiKubePrefix } from "../common/vars";
import { broadcastMessage, ClusterListNamespaceForbiddenChannel, InvalidKubeconfigChannel } from "../common/ipc"; import { broadcastMessage, ClusterListNamespaceForbiddenChannel, InvalidKubeconfigChannel } from "../common/ipc";
import { ContextHandler } from "./context-handler"; import { ContextHandler } from "./context-handler";
@ -15,6 +15,7 @@ import logger from "./logger";
import { VersionDetector } from "./cluster-detectors/version-detector"; import { VersionDetector } from "./cluster-detectors/version-detector";
import { detectorRegistry } from "./cluster-detectors/detector-registry"; import { detectorRegistry } from "./cluster-detectors/detector-registry";
import plimit from "p-limit"; import plimit from "p-limit";
import { cloneJson } from "../common/utils";
export enum ClusterStatus { export enum ClusterStatus {
AccessGranted = 2, AccessGranted = 2,
@ -240,7 +241,7 @@ export class Cluster implements ClusterModel, ClusterState {
@computed get prometheusPreferences(): ClusterPrometheusPreferences { @computed get prometheusPreferences(): ClusterPrometheusPreferences {
const { prometheus, prometheusProvider } = this.preferences; const { prometheus, prometheusProvider } = this.preferences;
return toJS({ prometheus, prometheusProvider }); return cloneJson({ prometheus, prometheusProvider });
} }
/** /**
@ -604,7 +605,7 @@ export class Cluster implements ClusterModel, ClusterState {
} }
toJSON(): ClusterModel { toJSON(): ClusterModel {
const model: ClusterModel = { return cloneJson({
id: this.id, id: this.id,
contextName: this.contextName, contextName: this.contextName,
kubeConfigPath: this.kubeConfigPath, kubeConfigPath: this.kubeConfigPath,
@ -613,16 +614,14 @@ export class Cluster implements ClusterModel, ClusterState {
metadata: this.metadata, metadata: this.metadata,
ownerRef: this.ownerRef, ownerRef: this.ownerRef,
accessibleNamespaces: this.accessibleNamespaces, accessibleNamespaces: this.accessibleNamespaces,
}; });
return toJS(model);
} }
/** /**
* Serializable cluster-state used for sync btw main <-> renderer * Serializable cluster-state used for sync btw main <-> renderer
*/ */
getState(): ClusterState { getState(): ClusterState {
const state: ClusterState = { return cloneJson({
initialized: this.initialized, initialized: this.initialized,
enabled: this.enabled, enabled: this.enabled,
apiUrl: this.apiUrl, apiUrl: this.apiUrl,
@ -635,9 +634,7 @@ export class Cluster implements ClusterModel, ClusterState {
allowedNamespaces: this.allowedNamespaces, allowedNamespaces: this.allowedNamespaces,
allowedResources: this.allowedResources, allowedResources: this.allowedResources,
isGlobalWatchEnabled: this.isGlobalWatchEnabled, isGlobalWatchEnabled: this.isGlobalWatchEnabled,
}; });
return toJS(state);
} }
/** /**
@ -680,7 +677,7 @@ export class Cluster implements ClusterModel, ClusterState {
const api = (await this.getProxyKubeconfig()).makeApiClient(CoreV1Api); const api = (await this.getProxyKubeconfig()).makeApiClient(CoreV1Api);
try { try {
const { body: { items }} = await api.listNamespace(); const { body: { items } } = await api.listNamespace();
const namespaces = items.map(ns => ns.metadata.name); const namespaces = items.map(ns => ns.metadata.name);
this.getAllowedNamespacesErrorCount = 0; // reset on success this.getAllowedNamespacesErrorCount = 0; // reset on success

View File

@ -2,17 +2,18 @@ import { randomBytes } from "crypto";
import { SHA256 } from "crypto-js"; import { SHA256 } from "crypto-js";
import { app, remote } from "electron"; import { app, remote } from "electron";
import fse from "fs-extra"; import fse from "fs-extra";
import { action, observable, toJS, makeObservable } from "mobx"; import { action, makeObservable, observable } from "mobx";
import path from "path"; import path from "path";
import { BaseStore } from "../common/base-store"; import { BaseStore } from "../common/base-store";
import { LensExtensionId } from "../extensions/lens-extension"; import { LensExtensionId } from "../extensions/lens-extension";
import { cloneJson } from "../common/utils";
interface FSProvisionModel { interface FSProvisionModel {
extensions: Record<string, string>; // extension names to paths extensions: Record<string, string>; // extension names to paths
} }
export class FilesystemProvisionerStore extends BaseStore<FSProvisionModel> { export class FilesystemProvisionerStore extends BaseStore<FSProvisionModel> {
@observable registeredExtensions = observable.map<LensExtensionId, string>(); registeredExtensions = observable.map<LensExtensionId, string>();
constructor() { constructor() {
super({ super({
@ -50,8 +51,8 @@ export class FilesystemProvisionerStore extends BaseStore<FSProvisionModel> {
} }
toJSON(): FSProvisionModel { toJSON(): FSProvisionModel {
return toJS({ return cloneJson({
extensions: Object.fromEntries(this.registeredExtensions.toJSON()), extensions: Object.fromEntries(this.registeredExtensions),
}); });
} }
} }

View File

@ -7,7 +7,7 @@ import path from "path";
import * as tempy from "tempy"; import * as tempy from "tempy";
import logger from "./logger"; import logger from "./logger";
import { appEventBus } from "../common/event-bus"; import { appEventBus } from "../common/event-bus";
import { cloneJsonObject } from "../common/utils"; import { cloneJson } from "../common/utils";
export class ResourceApplier { export class ResourceApplier {
constructor(protected cluster: Cluster) { constructor(protected cluster: Cluster) {
@ -83,7 +83,7 @@ export class ResourceApplier {
} }
protected sanitizeObject(resource: KubernetesObject | any) { protected sanitizeObject(resource: KubernetesObject | any) {
resource = cloneJsonObject(resource); resource = cloneJson(resource);
delete resource.status; delete resource.status;
delete resource.metadata?.resourceVersion; delete resource.metadata?.resourceVersion;
const annotations = resource.metadata?.annotations; const annotations = resource.metadata?.annotations;

View File

@ -1,6 +1,6 @@
import { Select } from "../select"; import { Select } from "../select";
import { computed, observable, toJS, makeObservable } from "mobx"; import { computed, observable, makeObservable } from "mobx";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import React from "react"; import React from "react";
import { commandRegistry } from "../../../extensions/registries/command-registry"; import { commandRegistry } from "../../../extensions/registries/command-registry";
@ -52,13 +52,11 @@ export class CommandDialog extends React.Component {
return; return;
} }
const action = toJS(command.action);
try { try {
CommandOverlay.close(); CommandOverlay.close();
if (command.scope === "global") { if (command.scope === "global") {
action({ command.action({
entity: commandRegistry.activeEntity entity: commandRegistry.activeEntity
}); });
} else if(commandRegistry.activeEntity) { } else if(commandRegistry.activeEntity) {

View File

@ -1,13 +1,12 @@
import debounce from "lodash/debounce"; import { reaction } from "mobx";
import { reaction, toJS } from "mobx";
import { Terminal as XTerm } from "xterm"; import { Terminal as XTerm } from "xterm";
import { FitAddon } from "xterm-addon-fit"; import { FitAddon } from "xterm-addon-fit";
import { dockStore, TabId } from "./dock.store"; import { dockStore, TabId } from "./dock.store";
import { TerminalApi } from "../../api/terminal-api"; import { TerminalApi } from "../../api/terminal-api";
import { ThemeStore } from "../../theme.store"; import { ThemeStore } from "../../theme.store";
import { autobind } from "../../utils"; import { autobind, cloneJson } from "../../utils";
import { isMac } from "../../../common/vars"; import { isMac } from "../../../common/vars";
import { camelCase } from "lodash"; import { camelCase, debounce } from "lodash";
export class Terminal { export class Terminal {
static spawningPool: HTMLElement; static spawningPool: HTMLElement;
@ -104,7 +103,7 @@ export class Terminal {
window.addEventListener("resize", this.onResize); window.addEventListener("resize", this.onResize);
this.disposers.push( this.disposers.push(
reaction(() => toJS(ThemeStore.getInstance().activeTheme.colors), this.setTheme, { reaction(() => cloneJson(ThemeStore.getInstance().activeTheme.colors), this.setTheme, {
fireImmediately: true fireImmediately: true
}), }),
dockStore.onResize(this.onResize), dockStore.onResize(this.onResize),
@ -132,7 +131,7 @@ export class Terminal {
const { cols, rows } = this.xterm; const { cols, rows } = this.xterm;
this.api.sendTerminalSize(cols, rows); this.api.sendTerminalSize(cols, rows);
} catch(error) { } catch (error) {
console.error(error); console.error(error);
return; // see https://github.com/lensapp/lens/issues/1891 return; // see https://github.com/lensapp/lens/issues/1891
@ -183,12 +182,12 @@ export class Terminal {
// Handle custom hotkey bindings // Handle custom hotkey bindings
if (ctrlKey) { if (ctrlKey) {
switch (code) { switch (code) {
// Ctrl+C: prevent terminal exit on windows / linux (?) // Ctrl+C: prevent terminal exit on windows / linux (?)
case "KeyC": case "KeyC":
if (this.xterm.hasSelection()) return false; if (this.xterm.hasSelection()) return false;
break; break;
// Ctrl+W: prevent unexpected terminal tab closing, e.g. editing file in vim // Ctrl+W: prevent unexpected terminal tab closing, e.g. editing file in vim
case "KeyW": case "KeyW":
evt.preventDefault(); evt.preventDefault();
break; break;

View File

@ -175,7 +175,7 @@ describe("renderer/utils/StorageHelper", () => {
it("storage.get() is observable", () => { it("storage.get() is observable", () => {
expect(storageHelper.get()).toEqual(defaultValue); expect(storageHelper.get()).toEqual(defaultValue);
reaction(() => toJS(storageHelper), change => { reaction(() => toJS(storageHelper.toJS()), change => {
observedChanges.push(change); observedChanges.push(change);
}); });

View File

@ -1,6 +1,6 @@
// 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, CreateObservableOptions, IObservableValue, makeObservable, observable, reaction, toJS, when } from "mobx"; import { action, comparer, CreateObservableOptions, IObservableValue, IReactionDisposer, makeObservable, observable, reaction, toJS, when } from "mobx";
import produce, { Draft } from "immer"; import produce, { Draft } from "immer";
import { isEqual, isFunction, isPlainObject, merge } from "lodash"; import { isEqual, isFunction, isPlainObject, merge } from "lodash";
import logger from "../../main/logger"; import logger from "../../main/logger";
@ -30,19 +30,24 @@ export class StorageHelper<T> {
}; };
private data: IObservableValue<T>; private data: IObservableValue<T>;
@observable initialized = false; protected unwatchChanges: IReactionDisposer;
whenReady = when(() => this.initialized);
public readonly storage: StorageAdapter<T>; public readonly storage: StorageAdapter<T>;
public readonly defaultValue: T; public readonly defaultValue: T;
@observable initialized = false;
whenReady = when(() => this.initialized);
constructor(readonly key: string, private options: StorageHelperOptions<T>) { constructor(readonly key: string, private options: StorageHelperOptions<T>) {
makeObservable(this); makeObservable(this);
this.options = merge({}, StorageHelper.defaultOptions, options); this.options = merge({}, StorageHelper.defaultOptions, options);
this.storage = options.storage; this.storage = options.storage;
this.defaultValue = options.defaultValue; this.defaultValue = options.defaultValue;
this.observeData(); this.data = observable.box(this.defaultValue, this.options.observable);
this.unwatchChanges = reaction(() => toJS(this.data.get()), (newValue, oldValue) => {
this.onChange(newValue, oldValue);
}, this.options.observable);
if (this.options.autoInit) { if (this.options.autoInit) {
this.init(); this.init();
@ -93,19 +98,6 @@ export class StorageHelper<T> {
return isEqual(value, this.defaultValue); return isEqual(value, this.defaultValue);
} }
private observeData(value = this.options.defaultValue) {
const observableOptions: CreateObservableOptions = {
...StorageHelper.defaultOptions.observable, // inherit default observability options
...this.options.observable,
};
this.data = observable.box<T>(value, observableOptions);
return reaction(() => toJS(this.data.get()), (newValue, oldValue) => {
this.onChange(newValue, oldValue);
}, observableOptions);
}
protected onChange(value: T, oldValue?: T) { protected onChange(value: T, oldValue?: T) {
if (!this.initialized) return; if (!this.initialized) return;
@ -151,6 +143,10 @@ export class StorageHelper<T> {
this.set(nextValue as T); this.set(nextValue as T);
} }
destroy() {
this.unwatchChanges();
}
toJS() { toJS() {
return toJS(this.get()); return toJS(this.get());
} }