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

mobx-6 migration -- part 1

Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
Roman 2021-05-05 22:06:49 +03:00
parent 1044c544ad
commit a0fb8b5933
47 changed files with 350 additions and 317 deletions

View File

@ -51,8 +51,6 @@ export class ExamplePreferencesStore extends Store.ExtensionStore<ExamplePrefere
toJSON(): ExamplePreferencesModel {
return toJS({
enabled: this.enabled
}, {
recurseEverything: true
});
}
}

View File

@ -183,6 +183,7 @@
"@kubernetes/client-node": "^0.12.0",
"abort-controller": "^3.0.0",
"array-move": "^3.0.0",
"autobind-decorator": "^2.4.0",
"await-lock": "^2.1.0",
"byline": "^5.0.0",
"chalk": "^4.1.0",
@ -207,9 +208,9 @@
"mac-ca": "^1.0.4",
"marked": "^2.0.3",
"md5-file": "^5.0.0",
"mobx": "^5.15.7",
"mobx-observable-history": "^1.0.3",
"mobx-react": "^6.2.2",
"mobx": "^6.3.0",
"mobx-observable-history": "^2.0.0",
"mobx-react": "^7.1.0",
"mock-fs": "^4.12.0",
"moment": "^2.26.0",
"moment-timezone": "^0.5.33",

View File

@ -288,7 +288,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
@action
protected fromStore({ activeCluster, clusters = [] }: ClusterStoreModel = {}) {
const currentClusters = this.clusters.toJS();
const currentClusters = toJS(this.clusters);
const newClusters = new Map<ClusterId, Cluster>();
const removedClusters = new Map<ClusterId, Cluster>();
@ -324,8 +324,6 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
return toJS({
activeCluster: this.activeCluster,
clusters: this.clustersList.map(cluster => cluster.toJSON()),
}, {
recurseEverything: true
});
}
}

View File

@ -0,0 +1,38 @@
// Setup default configuration for external npm-packages.
import * as Mobx from "mobx";
import * as Immer from "immer";
const { isObservable, toJS, observable } = Mobx;
/**
* Patch-fixing mobx@6.toJS() to support partially observable objects as data-input.
* Otherwise it won't be recursively converted to corresponding non-observable plain JS-structure.
* @example
* data = {one: 1, two: observable.array([2])}; // "data" itself is non-observable
*/
Object.defineProperty(Mobx, "toJS", {
value(data: any) {
if (typeof data === "object" && !isObservable(data)) {
return toJS(observable.box(data).get());
}
return toJS(data);
}
});
export default function configurePackages() {
// Docs: https://mobx.js.org/configuration.html
Mobx.configure({
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,
});
// Docs: https://immerjs.github.io/immer/
// Required for `storage-helper.ts`
Immer.setAutoFreeze(false); // allow to merge mobx observables
Immer.enableMapSet(); // allow to merge maps and sets
}

View File

@ -219,8 +219,6 @@ export class HotbarStore extends BaseStore<HotbarStoreModel> {
activeHotbarId: this.activeHotbarId
};
return toJS(model, {
recurseEverything: true,
});
return toJS(model);
}
}

View File

@ -18,7 +18,7 @@ export async function requestMain(channel: string, ...args: any[]) {
}
function getSubFrames(): ClusterFrameInfo[] {
return toJS(Array.from(clusterFrameMap.values()), { recurseEverything: true });
return toJS(Array.from(clusterFrameMap.values()));
}
export async function broadcastMessage(channel: string, ...args: any[]) {

View File

@ -285,9 +285,7 @@ export class UserStore extends BaseStore<UserStoreModel> {
},
};
return toJS(model, {
recurseEverything: true,
});
return toJS(model);
}
}

View File

@ -1,45 +1,10 @@
// Decorator for binding class methods
// Can be applied to class or single method as @autobind()
type Constructor<T = {}> = new (...args: any[]) => T;
// Decorator for binding class methods to proper "this"-context.
// API: https://github.com/andreypopp/autobind-decorator
import bindMethodOrClass from "autobind-decorator"
export function autobind() {
return function (target: Constructor | object, prop?: string, descriptor?: PropertyDescriptor) {
if (target instanceof Function) return bindClass(target);
else return bindMethod(target, prop, descriptor);
// TODO: unwrap, replace usages of @autobind() to @autobind
export function autobind<T extends object>() {
return function (target: { new(...args: any[]): T } | object, prop?: string, descriptor?: PropertyDescriptor) {
return bindMethodOrClass(target, prop, descriptor) as any;
};
}
function bindClass<T extends Constructor>(constructor: T) {
const proto = constructor.prototype;
const descriptors = Object.getOwnPropertyDescriptors(proto);
const skipMethod = (methodName: string) => {
return methodName === "constructor"
|| typeof descriptors[methodName].value !== "function";
};
Object.keys(descriptors).forEach(prop => {
if (skipMethod(prop)) return;
const boundDescriptor = bindMethod(proto, prop, descriptors[prop]);
Object.defineProperty(proto, prop, boundDescriptor);
});
}
function bindMethod(target: object, prop?: string, descriptor?: PropertyDescriptor) {
if (!descriptor || typeof descriptor.value !== "function") {
throw new Error(`@autobind() must be used on class or method only`);
}
const { value: func, enumerable, configurable } = descriptor;
const boundFunc = new WeakMap<object, Function>();
return Object.defineProperty(target, prop, {
enumerable,
configurable,
get() {
if (this === target) return func; // direct access from prototype
if (!boundFunc.has(this)) boundFunc.set(this, func.bind(this));
return boundFunc.get(this);
}
});
}

View File

@ -444,8 +444,6 @@ export class ExtensionDiscovery extends Singleton {
toJSON(): ExtensionDiscoveryChannelMessage {
return toJS({
isLoaded: this.isLoaded
}, {
recurseEverything: true
});
}

View File

@ -1,7 +1,7 @@
import { app, ipcRenderer, remote } from "electron";
import { EventEmitter } from "events";
import { isEqual } from "lodash";
import { action, computed, observable, reaction, toJS, when } from "mobx";
import { action, computed, observable, reaction, when } from "mobx";
import path from "path";
import { getHostedCluster } from "../common/cluster-store";
import { broadcastMessage, handleRequest, requestMain, subscribeToBroadcast } from "../common/ipc";
@ -40,7 +40,7 @@ export class ExtensionLoader extends Singleton {
whenLoaded = when(() => this.isLoaded);
@computed get userExtensions(): Map<LensExtensionId, InstalledExtension> {
const extensions = this.extensions.toJS();
const extensions = this.toJSON();
extensions.forEach((ext, extId) => {
if (ext.isBundled) {
@ -54,7 +54,7 @@ export class ExtensionLoader extends Singleton {
@computed get userExtensionsByName(): Map<string, LensExtension> {
const extensions = new Map();
for (const [, val] of this.instances.toJS()) {
for (const [, val] of this.instances.toJSON()) {
if (val.isBundled) {
continue;
}
@ -135,7 +135,7 @@ export class ExtensionLoader extends Singleton {
}
}
protected async initMain() {
protected async initMain() {
this.isLoaded = true;
this.loadOnMain();
@ -152,7 +152,7 @@ export class ExtensionLoader extends Singleton {
});
}
protected async initRenderer() {
protected async initRenderer() {
const extensionListHandler = (extensions: [LensExtensionId, InstalledExtension][]) => {
this.isLoaded = true;
this.syncExtensions(extensions);
@ -311,10 +311,7 @@ export class ExtensionLoader extends Singleton {
}
toJSON(): Map<LensExtensionId, InstalledExtension> {
return toJS(this.extensions, {
exportMapsAsObjects: false,
recurseEverything: true,
});
return new Map(this.extensions.toJSON());
}
broadcastExtensions(main = true) {

View File

@ -47,9 +47,7 @@ export class ExtensionsStore extends BaseStore<LensExtensionsStoreModel> {
toJSON(): LensExtensionsStoreModel {
return toJS({
extensions: this.state.toJSON(),
}, {
recurseEverything: true
extensions: Object.fromEntries(this.state.toJSON()),
});
}
}

View File

@ -54,7 +54,7 @@ describe("getPageUrl", () => {
});
it("gets page url with custom params", () => {
const params: PageParams<string> = { test1: "one", test2: "2" };
const params: PageParams = { test1: "one", test2: "2" };
const searchParams = new URLSearchParams(params);
const pageUrl = getExtensionPageUrl({ extensionId: ext.name, pageId: "page-with-params", params });

View File

@ -3,7 +3,7 @@
import React from "react";
import { observer } from "mobx-react";
import { BaseRegistry } from "./base-registry";
import { LensExtension, sanitizeExtensionName } from "../lens-extension";
import { LensExtension, LensExtensionId, sanitizeExtensionName } from "../lens-extension";
import { PageParam, PageParamInit } from "../../renderer/navigation/page-param";
import { createPageParam } from "../../renderer/navigation/helpers";
@ -13,21 +13,18 @@ export interface PageRegistration {
* When not provided, first registered page without "id" would be used for page-menus without target.pageId for same extension
*/
id?: string;
params?: PageParams<string | ExtensionPageParamInit>;
params?: PageParams<string | Omit<PageParamInit<any>, "name" | "prefix">>;
components: PageComponents;
}
// exclude "name" field since provided as key in page.params
export type ExtensionPageParamInit = Omit<PageParamInit, "name" | "isSystem">;
export interface PageComponents {
Page: React.ComponentType<any>;
}
export interface PageTarget<P = PageParams> {
export interface PageTarget {
extensionId?: string;
pageId?: string;
params?: P;
params?: PageParams;
}
export interface PageParams<V = any> {
@ -62,13 +59,11 @@ export function getExtensionPageUrl(target: PageTarget): string {
if (registeredPage?.params) {
Object.entries(registeredPage.params).forEach(([name, param]) => {
const paramValue = param.stringify(targetParams[name]);
pageUrl.searchParams.delete(name); // first off, clear existing value(s)
if (param.init.skipEmpty && param.isEmpty(paramValue)) {
pageUrl.searchParams.delete(name);
} else {
pageUrl.searchParams.set(name, paramValue);
}
param.stringify(targetParams[name]).forEach(value => {
pageUrl.searchParams.append(name, value);
});
});
}
@ -79,7 +74,7 @@ export class PageRegistry extends BaseRegistry<PageRegistration, RegisteredPage>
protected getRegisteredItem(page: PageRegistration, ext: LensExtension): RegisteredPage {
const { id: pageId } = page;
const extensionId = ext.name;
const params = this.normalizeParams(page.params);
const params = this.normalizeParams(extensionId, page.params);
const components = this.normalizeComponents(page.components, params);
const url = getExtensionPageUrl({ extensionId, pageId });
@ -92,25 +87,48 @@ export class PageRegistry extends BaseRegistry<PageRegistration, RegisteredPage>
if (params) {
const { Page } = components;
// inject extension's page component props.params
components.Page = observer((props: object) => React.createElement(Page, { params, ...props }));
}
return components;
}
protected normalizeParams(params?: PageParams<string | ExtensionPageParamInit>): PageParams<PageParam> {
if (!params) {
return;
}
Object.entries(params).forEach(([name, value]) => {
const paramInit: PageParamInit = typeof value === "object"
? { name, ...value }
: { name, defaultValue: value };
protected normalizeParams(extensionId: LensExtensionId, params?: PageParams<string | Partial<PageParamInit>>): PageParams<PageParam> {
if (!params) return;
const normalizedParams: PageParams<PageParam> = {};
params[paramInit.name] = createPageParam(paramInit);
Object.entries(params).forEach(([paramName, paramValue]) => {
const paramInit: PageParamInit = {
name: paramName,
prefix: `${extensionId}:`,
defaultValue: paramValue,
};
// handle non-string params
if (typeof paramValue !== "string") {
const { defaultValue: value, parse, stringify } = paramValue;
const notAStringValue = typeof value !== "string" || (
Array.isArray(value) && !value.every(value => typeof value === "string")
);
if (notAStringValue && !(parse || stringify)) {
throw new Error(
`PageRegistry: param's "${paramName}" initialization has failed:
paramInit.parse() and paramInit.stringify() are required for non string | string[] "defaultValue"`
)
}
paramInit.defaultValue = value;
paramInit.parse = parse;
paramInit.stringify = stringify;
}
normalizedParams[paramName] = createPageParam(paramInit);
});
return params as PageParams<PageParam>;
return normalizedParams;
}
getByPageTarget(target: PageTarget): RegisteredPage | null {

View File

@ -1,12 +1,10 @@
import { PageParam, PageParamInit } from "../../renderer/navigation/page-param";
import { navigation } from "../../renderer/navigation";
import { navigation, PageParam, PageParamInit } from "../../renderer/navigation";
export type { PageParamInit, PageParam } from "../../renderer/navigation/page-param";
export { PageParamInit, PageParam } from "../../renderer/navigation/page-param";
export { navigate, isActiveRoute } from "../../renderer/navigation/helpers";
export { hideDetails, showDetails, getDetailsUrl } from "../../renderer/components/kube-object/kube-object-details";
export { IURLParams } from "../../common/utils/buildUrl";
// exporting to extensions-api version of helper without `isSystem` flag
export function createPageParam<V = string>(init: PageParamInit<V>) {
export function createPageParam<V>(init: PageParamInit<V>) {
return new PageParam<V>(init, navigation);
}

View File

@ -1,4 +1,9 @@
import configurePackages from "./common/configure-packages";
import fetchMock from "jest-fetch-mock";
// setup default configuration for external npm-packages
configurePackages();
// rewire global.fetch to call 'fetchMock'
fetchMock.enableMocks();

View File

@ -14,14 +14,14 @@ export class CatalogPusher {
init() {
const disposers: Disposer[] = [];
disposers.push(reaction(() => toJS(this.catalog.items, { recurseEverything: true }), (items) => {
disposers.push(reaction(() => toJS(this.catalog.items), (items) => {
broadcastMessage("catalog:items", items);
}, {
fireImmediately: true,
}));
const listener = subscribeToBroadcast("catalog:broadcast", () => {
broadcastMessage("catalog:items", toJS(this.catalog.items, { recurseEverything: true }));
broadcastMessage("catalog:items", toJS(this.catalog.items));
});
disposers.push(() => unsubscribeFromBroadcast("catalog:broadcast", listener));

View File

@ -47,7 +47,7 @@ export class KubeconfigSyncManager extends Singleton {
this.startNewSync(filePath);
}
this.syncListDisposer = UserStore.getInstance().syncKubeconfigEntries.observe(change => {
this.syncListDisposer = UserStore.getInstance().syncKubeconfigEntries.observe_(change => {
switch (change.type) {
case "add":
this.startNewSync(change.name);

View File

@ -14,7 +14,7 @@ export class ClusterManager extends Singleton {
constructor() {
super();
reaction(() => toJS(ClusterStore.getInstance().clustersList, { recurseEverything: true }), () => {
reaction(() => toJS(ClusterStore.getInstance().clustersList), () => {
this.updateCatalog(ClusterStore.getInstance().clustersList);
}, { fireImmediately: true });

View File

@ -206,9 +206,7 @@ export class Cluster implements ClusterModel, ClusterState {
@computed get prometheusPreferences(): ClusterPrometheusPreferences {
const { prometheus, prometheusProvider } = this.preferences;
return toJS({ prometheus, prometheusProvider }, {
recurseEverything: true,
});
return toJS({ prometheus, prometheusProvider });
}
/**
@ -549,9 +547,7 @@ export class Cluster implements ClusterModel, ClusterState {
accessibleNamespaces: this.accessibleNamespaces,
};
return toJS(model, {
recurseEverything: true
});
return toJS(model);
}
/**
@ -571,9 +567,7 @@ export class Cluster implements ClusterModel, ClusterState {
isGlobalWatchEnabled: this.isGlobalWatchEnabled,
};
return toJS(state, {
recurseEverything: true
});
return toJS(state);
}
/**

View File

@ -50,9 +50,7 @@ export class FilesystemProvisionerStore extends BaseStore<FSProvisionModel> {
toJSON(): FSProvisionModel {
return toJS({
extensions: this.registeredExtensions.toJSON(),
}, {
recurseEverything: true
extensions: Object.fromEntries(this.registeredExtensions.toJSON()),
});
}
}

View File

@ -34,6 +34,7 @@ import { HotbarStore } from "../common/hotbar-store";
import { HelmRepoManager } from "./helm/helm-repo-manager";
import { KubeconfigSyncManager } from "./catalog-sources";
import { handleWsUpgrade } from "./proxy/ws-upgrade";
import configurePackages from "../common/configure-packages";
const workingDir = path.join(app.getPath("appData"), appName);
@ -55,6 +56,7 @@ if (process.env.LENS_DISABLE_GPU) {
app.disableHardwareAcceleration();
}
configurePackages();
mangleProxyEnv();
if (app.commandLine.getSwitchValue("proxy-server") !== "") {

View File

@ -22,6 +22,9 @@ import { ThemeStore } from "./theme.store";
import { HelmRepoManager } from "../main/helm/helm-repo-manager";
import { ExtensionInstallationStateStore } from "./components/+extensions/extension-install.store";
import { DefaultProps } from "./mui-base-theme";
import configurePackages from "../common/configure-packages";
configurePackages();
/**
* If this is a development buid, wait a second to attach

View File

@ -51,7 +51,7 @@ export class ReleaseDetails extends Component<Props> {
);
@disposeOnUnmount
secretWatcher = reaction(() => secretsStore.items.toJS(), () => {
secretWatcher = reaction(() => secretsStore.items.toJSON(), () => {
if (!this.props.release) return;
const { getReleaseSecret } = releaseStore;
const { release } = this.props;

View File

@ -20,7 +20,7 @@ export class ReleaseStore extends ItemStore<HelmRelease> {
}
watchAssociatedSecrets(): (() => void) {
return reaction(() => secretsStore.items.toJS(), () => {
return reaction(() => secretsStore.items.toJSON(), () => {
if (this.isLoading) return;
const newSecrets = this.getReleaseSecrets();
const amountChanged = newSecrets.length !== this.releaseSecrets.size;

View File

@ -39,7 +39,7 @@ export class ConfigMapDetails extends React.Component<Props> {
try {
this.isSaving = true;
await configMapsStore.update(configMap, { ...configMap, data: this.data.toJSON() });
await configMapsStore.update(configMap, { ...configMap, data: Object.fromEntries(this.data.toJSON()) });
Notifications.ok(
<p>
<>ConfigMap <b>{configMap.getName()}</b> successfully updated.</>
@ -54,7 +54,7 @@ export class ConfigMapDetails extends React.Component<Props> {
const { object: configMap } = this.props;
if (!configMap) return null;
const data = Object.entries(this.data.toJSON());
const data = this.data.toJSON();
return (
<div className="ConfigMapDetails">

View File

@ -14,8 +14,6 @@ import { Icon } from "../icon";
export const crdGroupsUrlParam = createPageParam<string[]>({
name: "groups",
multiValues: true,
isSystem: true,
defaultValue: [],
});

View File

@ -26,7 +26,7 @@ export class CRDStore extends KubeObjectStore<CustomResourceDefinition> {
super();
// auto-init stores for crd-s
reaction(() => this.items.toJS(), items => items.forEach(initStore));
reaction(() => this.items.toJSON(), items => items.forEach(initStore));
}
protected sortItems(items: CustomResourceDefinition[]) {

View File

@ -9,8 +9,6 @@ const selectedNamespaces = createStorage<string[] | undefined>("selected_namespa
export const namespaceUrlParam = createPageParam<string[]>({
name: "namespaces",
isSystem: true,
multiValues: true,
defaultValue: [],
});

View File

@ -78,7 +78,7 @@ export class App extends React.Component {
whatInput.ask(); // Start to monitor user input device
// Setup hosted cluster context
KubeObjectStore.defaultContext = clusterContext;
KubeObjectStore.defaultContext.set(clusterContext);
kubeWatchApi.context = clusterContext;
}

View File

@ -42,7 +42,7 @@ export class Dialog extends React.PureComponent<DialogProps, DialogState> {
};
@disposeOnUnmount
closeOnNavigate = reaction(() => navigation.getPath(), () => this.close());
closeOnNavigate = reaction(() => navigation.toString(), () => this.close());
public state: DialogState = {
isOpen: this.props.isOpen,

View File

@ -54,7 +54,7 @@ export class DockTabStore<T> {
}
protected getStorableData(): DockTabStorageState<T> {
const allTabsData = toJS(this.data, { recurseEverything: true });
const allTabsData = toJS(this.data);
return Object.fromEntries(
Object.entries(allTabsData).map(([tabId, tabData]) => {

View File

@ -21,7 +21,7 @@ export class ErrorBoundary extends React.Component<Props, State> {
@disposeOnUnmount
resetOnNavigate = reaction(
() => navigation.getPath(),
() => navigation.toString(),
() => this.setState({ error: null, errorInfo: null })
);

View File

@ -8,7 +8,6 @@ import { createPageParam } from "../../navigation";
export const searchUrlParam = createPageParam({
name: "search",
isSystem: true,
defaultValue: "",
});

View File

@ -18,7 +18,6 @@ import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry"
*/
export const kubeDetailsUrlParam = createPageParam({
name: "kube-details",
isSystem: true,
});
/**
@ -30,7 +29,6 @@ export const kubeDetailsUrlParam = createPageParam({
*/
export const kubeSelectedUrlParam = createPageParam({
name: "kube-selected",
isSystem: true,
get defaultValue() {
return kubeDetailsUrlParam.get();
}
@ -49,12 +47,12 @@ export function hideDetails() {
export function getDetailsUrl(selfLink: string, resetSelected = false, mergeGlobals = true) {
const params = new URLSearchParams(mergeGlobals ? navigation.searchParams : "");
params.set(kubeDetailsUrlParam.urlName, selfLink);
params.set(kubeDetailsUrlParam.name, selfLink);
if (resetSelected) {
params.delete(kubeSelectedUrlParam.urlName);
params.delete(kubeSelectedUrlParam.name);
} else {
params.set(kubeSelectedUrlParam.urlName, kubeSelectedUrlParam.get());
params.set(kubeSelectedUrlParam.name, kubeSelectedUrlParam.get());
}
return `?${params}`;

View File

@ -46,12 +46,10 @@ export interface TableProps extends React.DOMAttributes<HTMLDivElement> {
export const sortByUrlParam = createPageParam({
name: "sort",
isSystem: true,
});
export const orderByUrlParam = createPageParam({
name: "order",
isSystem: true,
});
@observer

View File

@ -1,6 +1,6 @@
import { useState } from "react";
import { createStorage } from "../utils";
import { CreateObservableOptions } from "mobx/lib/api/observable";
import { CreateObservableOptions } from "mobx";
export function useStorage<T>(key: string, initialValue: T, options?: CreateObservableOptions) {
const storage = createStorage(key, initialValue, options);

View File

@ -24,7 +24,7 @@ export abstract class ItemStore<T extends ItemObject = ItemObject> {
}
public getItems(): T[] {
return this.items.toJS();
return this.items.toJSON();
}
public getTotalCount(): number {

View File

@ -18,7 +18,7 @@ export interface KubeObjectStoreLoadingParams {
@autobind()
export abstract class KubeObjectStore<T extends KubeObject = any> extends ItemStore<T> {
@observable static defaultContext: ClusterContext; // TODO: support multiple cluster contexts
static defaultContext = observable.box<ClusterContext>(); // TODO: support multiple cluster contexts
abstract api: KubeApi<T>;
public readonly limit?: number;
@ -34,7 +34,7 @@ export abstract class KubeObjectStore<T extends KubeObject = any> extends ItemSt
}
get context(): ClusterContext {
return KubeObjectStore.defaultContext;
return KubeObjectStore.defaultContext.get();
}
@computed get contextItems(): T[] {
@ -350,7 +350,7 @@ export abstract class KubeObjectStore<T extends KubeObject = any> extends ItemSt
@action
protected updateFromEventsBuffer() {
const items = this.items.toJS();
const items = this.items.toJSON();
for (const { type, object } of this.eventsBuffer.clear()) {
const index = items.findIndex(item => item.getId() === object.metadata?.uid);

View File

@ -1,6 +1,6 @@
import type { LocationDescriptor } from "history";
import { matchPath, RouteProps } from "react-router";
import { PageParam, PageSystemParamInit } from "./page-param";
import { PageParam, PageParamInit } from "./page-param";
import { clusterViewRoute, IClusterViewRouteParams } from "../components/cluster-manager/cluster-view.route";
import { navigation } from "./history";
@ -14,7 +14,7 @@ export function navigate(location: LocationDescriptor) {
}
}
export function createPageParam<V = string>(init: PageSystemParamInit<V>) {
export function createPageParam<V = string>(init: PageParamInit<V>) {
return new PageParam<V>(init, navigation);
}

View File

@ -4,7 +4,14 @@ import { createObservableHistory } from "mobx-observable-history";
import logger from "../../main/logger";
export const history = ipcRenderer ? createBrowserHistory() : createMemoryHistory();
export const navigation = createObservableHistory(history);
export const navigation = createObservableHistory(history, {
searchParams: {
skipEmpty: true, // skip empty params, e.g. "?x=&y2=" will be "?y=2"
joinArrays: false, // join multiple params with same name, e.g. "?x=1&x=2" => "?x=1,2"
joinArraysWith: ",", // param values splitter, applicable only with {joinArrays:true}
}
});
navigation.listen((location, action) => {
const isClusterView = !process.isMainFrame;

View File

@ -4,5 +4,6 @@ import { bindEvents } from "./events";
export * from "./history";
export * from "./helpers";
export * from "./page-param";
bindEvents();

View File

@ -1,135 +1,113 @@
// Manage observable URL-param from document.location.search
import { IObservableHistory } from "mobx-observable-history";
// Manage observable param from document's location.search
import type { ObservableHistory } from "mobx-observable-history";
import { action, computed } from "mobx";
export interface PageParamInit<V = any> {
name: string;
defaultValue?: V;
defaultValueStringified?: string | string[]; // serialized version of "defaultValue"
multiValues?: boolean; // false == by default
multiValueSep?: string; // joining multiple values with separator, default: ","
skipEmpty?: boolean; // skip empty value(s), e.g. "?param=", default: true
parse?(value: string[]): V; // deserialize from URL
stringify?(value: V): string | string[]; // serialize params to URL
}
export interface PageSystemParamInit<V = any> extends PageParamInit<V> {
isSystem?: boolean;
prefix?: string; // name prefix, for extensions it's `${extension.id}:`
parse?(value: string | string[]): V; // from URL
stringify?(value: V): string | string[]; // to URL
}
// TODO: write tests
export class PageParam<V = any> {
static SYSTEM_PREFIX = "lens-";
readonly name: string;
readonly urlName: string;
readonly isMulti: boolean;
constructor(readonly init: PageParamInit<V> | PageSystemParamInit<V>, protected history: IObservableHistory) {
const { isSystem, name } = init as PageSystemParamInit;
constructor(private init: PageParamInit<V>, private history: ObservableHistory) {
const { prefix, name, defaultValue } = init;
this.name = name;
this.init.skipEmpty ??= true;
this.init.multiValueSep ??= ",";
// prefixing to avoid collisions with extensions
this.urlName = `${isSystem ? PageParam.SYSTEM_PREFIX : ""}${name}`;
this.name = `${prefix ?? ""}${name}`; // actual prefixed URL-name
this.isMulti = Array.isArray(defaultValue); // multi-values param
}
isEmpty(value: V | any) {
return [value].flat().every(value => value == "" || value == null);
// should be a getter since `init.defaultValue` could be a getter too
get defaultValue(): V | undefined {
return this.init.defaultValue;
}
parse(values: string[]): V {
const { parse, multiValues } = this.init;
parse(values: string | string[]): V {
const { parse } = this.init;
if (!multiValues) values.splice(1); // reduce values to single item
const parsedValues = [parse ? parse(values) : values].flat();
return multiValues ? parsedValues : parsedValues[0] as any;
}
stringify(value: V = this.get()): string {
const { stringify, multiValues, multiValueSep, skipEmpty } = this.init;
if (skipEmpty && this.isEmpty(value)) {
return "";
if (parse) {
return parse(values);
}
if (multiValues) {
const values = [value].flat();
const stringValues = [stringify ? stringify(value) : values.map(String)].flat();
// return as-is ("string"-value based params)
return values as any as V;
}
return stringValues.join(multiValueSep);
stringify(value: V = this.get()): string[] {
const { stringify } = this.init;
if (stringify) {
return [stringify(value)].flat();
}
return [stringify ? stringify(value) : String(value)].flat()[0];
return [value].flat().map(String);
}
get(): V {
const value = this.parse(this.getRaw());
if (this.init.skipEmpty && this.isEmpty(value)) {
return this.getDefaultValue();
}
return value;
return this.parse(this.getRaw()) ?? this.defaultValue;
}
set(value: V, { mergeGlobals = true, replaceHistory = false } = {}) {
const search = this.toSearchString({ mergeGlobals, value });
@action
set(value: V, { mergeGlobals = true, replaceHistory = false } = {}): void {
const search = this.toString({ mergeGlobals, value });
this.history.merge({ search }, replaceHistory);
}
setRaw(value: string | string[]) {
const { history, urlName } = this;
const { multiValues, multiValueSep, skipEmpty } = this.init;
const paramValue = multiValues ? [value].flat().join(multiValueSep) : String(value);
/**
* Set stringified raw value(s) and update `document.location.search`
* @param {string | string[]} value
*/
@action
setRaw(value: string | string[]): void {
const values: string[] = [value].flat();
if (skipEmpty && this.isEmpty(paramValue)) {
history.searchParams.delete(urlName);
} else {
history.searchParams.set(urlName, paramValue);
}
}
getRaw(): string[] {
const { history, urlName } = this;
const { multiValueSep } = this.init;
return history.searchParams.getAsArray(urlName, multiValueSep);
}
getDefaultValue() {
const { defaultValue, defaultValueStringified } = this.init;
return defaultValueStringified ? this.parse([defaultValueStringified].flat()) : defaultValue;
}
clear() {
this.history.searchParams.delete(this.urlName);
}
toSearchString({ withPrefix = true, mergeGlobals = true, value = this.get() } = {}): string {
const { history, urlName, init: { skipEmpty } } = this;
const searchParams = new URLSearchParams(mergeGlobals ? history.location.search : "");
searchParams.set(urlName, this.stringify(value));
if (skipEmpty) {
searchParams.forEach((value: any, paramName) => {
if (this.isEmpty(value)) searchParams.delete(paramName);
if (this.isMulti) {
this.clear();
values.forEach(value => {
this.history.searchParams.append(this.name, value);
});
} else {
this.history.searchParams.set(this.name, values[0]);
}
if (Array.from(searchParams).length > 0) {
return `${withPrefix ? "?" : ""}${searchParams}`;
}
return "";
}
toObjectParam(value = this.get()): Record<string, V> {
/**
* Get stringified raw value(s) from `document.location.search`
*/
@computed getRaw(): string | string[] {
const values: string[] = this.history.searchParams.getAll(this.name);
return this.isMulti ? values : values[0];
}
@action
clear() {
this.history.searchParams.delete(this.name);
}
toString({ withPrefix = true, mergeGlobals = true, value = this.get() } = {}): string {
let searchParams = new URLSearchParams();
if (mergeGlobals) {
searchParams = new URLSearchParams(this.history.searchParams);
searchParams.delete(this.name);
}
this.stringify(value).forEach(value => {
searchParams.append(this.name, value);
});
return `${withPrefix ? "?" : ""}${searchParams}`;
}
toObjectParam(): Record<string, V> {
return {
[this.urlName]: value,
[this.name]: this.get(),
};
}
}

View File

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

View File

@ -1,10 +1,8 @@
// Keeps window.localStorage state in external JSON-files.
// Because app creates random port between restarts => storage session wiped out each time.
import type { CreateObservableOptions } from "mobx/lib/api/observable";
import path from "path";
import { app, remote } from "electron";
import { observable, reaction, when } from "mobx";
import { observable, reaction, when, CreateObservableOptions } from "mobx";
import fse from "fs-extra";
import { StorageHelper } from "./storageHelper";
import { ClusterStore, getHostedClusterId } from "../../common/cluster-store";

View File

@ -1,14 +1,10 @@
// Helper for working with storages (e.g. window.localStorage, NodeJS/file-system, etc.)
import type { CreateObservableOptions } from "mobx/lib/api/observable";
import { action, comparer, observable, toJS, when, IObservableValue } from "mobx";
import produce, { Draft, enableMapSet, setAutoFreeze } from "immer";
import { action, comparer, CreateObservableOptions, IObservableValue, observable, toJS, when } from "mobx";
import produce, { Draft } from "immer";
import { isEqual, isFunction, isPlainObject } from "lodash";
import logger from "../../main/logger";
setAutoFreeze(false); // allow to merge observables
enableMapSet(); // allow merging maps and sets
export interface StorageAdapter<T> {
[metadata: string]: any;
getItem(key: string): T | Promise<T>;
@ -45,10 +41,8 @@ export class StorageHelper<T> {
...StorageHelper.defaultOptions.observable,
...(options.observable ?? {})
});
this.data.observe(change => {
const { newValue, oldValue } = toJS(change, { recurseEverything: true });
this.onChange(newValue, oldValue);
this.data.observe_(({ newValue, oldValue }) => {
this.onChange(newValue as T, oldValue as T);
});
this.storage = options.storage;
@ -117,17 +111,18 @@ export class StorageHelper<T> {
return this.data.get();
}
@action
set(value: T) {
if (value == null) {
// This cannot use recursion because defaultValue might be null or undefined
this.data.set(this.defaultValue);
this.reset();
} else {
this.data.set(value);
}
}
@action
reset() {
this.set(this.defaultValue);
this.data.set(this.defaultValue);
}
merge(value: Partial<T> | ((draft: Draft<T>) => Partial<T> | void)) {
@ -142,7 +137,7 @@ export class StorageHelper<T> {
this.set(nextValue as T);
}
toJS() {
return toJS(this.get(), { recurseEverything: true });
toJSON(): T {
return toJS(this.get());
}
}

View File

@ -23,6 +23,7 @@
"allowSyntheticDefaultImports": true,
"traceResolution": false,
"resolveJsonModule": true,
"useDefineForClassFields": false,
"paths": {
"*": [
"node_modules/*",

165
yarn.lock
View File

@ -1269,11 +1269,16 @@
"@types/shot" "*"
joi "^17.3.0"
"@types/history@*", "@types/history@^4.7.3":
"@types/history@*":
version "4.7.6"
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.6.tgz#ed8fc802c45b8e8f54419c2d054e55c9ea344356"
integrity sha512-GRTZLeLJ8ia00ZH8mxMO8t0aC9M1N9bN461Z2eaRurJo6Fpa+utgCwLzI4jQHcrdzuzp5WPN9jRwpsCQ1VhJ5w==
"@types/history@^4.7.8":
version "4.7.8"
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934"
integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA==
"@types/hoist-non-react-statics@^3.3.0", "@types/hoist-non-react-statics@^3.3.1":
version "3.3.1"
resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
@ -2703,6 +2708,11 @@ atob@^2.1.2:
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
autobind-decorator@^2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/autobind-decorator/-/autobind-decorator-2.4.0.tgz#ea9e1c98708cf3b5b356f7cf9f10f265ff18239c"
integrity sha512-OGYhWUO72V6DafbF8PM8rm3EPbfuyMZcJhtm5/n26IDwO18pohE4eNazLoCGhPiXOCD0gEGmrbU3849QvM8bbw==
await-lock@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/await-lock/-/await-lock-2.1.0.tgz#bc78c51d229a34d5d90965a1c94770e772c6145e"
@ -4390,13 +4400,20 @@ debug@4.1.0:
dependencies:
ms "^2.1.1"
debug@^3.0.0, debug@^3.1.0, debug@^3.1.1, debug@^3.2.5, debug@^3.2.6:
debug@^3.0.0, debug@^3.1.1, debug@^3.2.5, debug@^3.2.6:
version "3.2.6"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
dependencies:
ms "^2.1.1"
debug@^3.1.0:
version "3.2.7"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==
dependencies:
ms "^2.1.1"
debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1:
version "4.3.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee"
@ -4411,7 +4428,7 @@ debug@^4.3.2:
dependencies:
ms "2.1.2"
debuglog@^1.0.1:
debuglog@*, debuglog@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492"
integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=
@ -5893,6 +5910,11 @@ fill-range@^7.0.1:
dependencies:
to-regex-range "^5.0.1"
filter-obj@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-1.1.0.tgz#9b311112bc6c6127a16e016c6c5d7f19e0805c5b"
integrity sha1-mzERErxsYSehbgFsbF1/GeCAXFs=
finalhandler@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d"
@ -6530,12 +6552,12 @@ got@^9.6.0:
to-readable-stream "^1.0.0"
url-parse-lax "^3.0.0"
graceful-fs@^4.1.0, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.2.2, graceful-fs@^4.2.4:
graceful-fs@^4.1.0, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.2.4:
version "4.2.4"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==
graceful-fs@^4.1.6, graceful-fs@^4.2.0:
graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2:
version "4.2.6"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee"
integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==
@ -6739,11 +6761,16 @@ homedir-polyfill@^1.0.1:
dependencies:
parse-passwd "^1.0.0"
hosted-git-info@^2.1.4, hosted-git-info@^2.7.1, hosted-git-info@^2.8.8:
hosted-git-info@^2.1.4:
version "2.8.8"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==
hosted-git-info@^2.7.1, hosted-git-info@^2.8.9:
version "2.8.9"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==
hosted-git-info@^3.0.8:
version "3.0.8"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-3.0.8.tgz#6e35d4cc87af2c5f816e4cb9ce350ba87a3f370d"
@ -7063,7 +7090,7 @@ import-local@^3.0.2:
pkg-dir "^4.2.0"
resolve-cwd "^3.0.0"
imurmurhash@^0.1.4:
imurmurhash@*, imurmurhash@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
@ -7133,7 +7160,7 @@ ini@2.0.0:
resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5"
integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==
ini@^1.3.4, ini@^1.3.5, ini@~1.3.0:
ini@^1.3.4, ini@^1.3.5, ini@^1.3.8, ini@~1.3.0:
version "1.3.8"
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
@ -8884,6 +8911,11 @@ lodash-es@^4.17.20:
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==
lodash._baseindexof@*:
version "3.1.0"
resolved "https://registry.yarnpkg.com/lodash._baseindexof/-/lodash._baseindexof-3.1.0.tgz#fe52b53a1c6761e42618d654e4a25789ed61822c"
integrity sha1-/lK1OhxnYeQmGNZU5KJXie1hgiw=
lodash._baseuniq@~4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz#0ebb44e456814af7905c6212fa2c9b2d51b841e8"
@ -8892,11 +8924,33 @@ lodash._baseuniq@~4.6.0:
lodash._createset "~4.0.0"
lodash._root "~3.0.0"
lodash._bindcallback@*:
version "3.0.1"
resolved "https://registry.yarnpkg.com/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz#e531c27644cf8b57a99e17ed95b35c748789392e"
integrity sha1-5THCdkTPi1epnhftlbNcdIeJOS4=
lodash._cacheindexof@*:
version "3.0.2"
resolved "https://registry.yarnpkg.com/lodash._cacheindexof/-/lodash._cacheindexof-3.0.2.tgz#3dc69ac82498d2ee5e3ce56091bafd2adc7bde92"
integrity sha1-PcaayCSY0u5ePOVgkbr9Ktx73pI=
lodash._createcache@*:
version "3.1.2"
resolved "https://registry.yarnpkg.com/lodash._createcache/-/lodash._createcache-3.1.2.tgz#56d6a064017625e79ebca6b8018e17440bdcf093"
integrity sha1-VtagZAF2JeeevKa4AY4XRAvc8JM=
dependencies:
lodash._getnative "^3.0.0"
lodash._createset@~4.0.0:
version "4.0.3"
resolved "https://registry.yarnpkg.com/lodash._createset/-/lodash._createset-4.0.3.tgz#0f4659fbb09d75194fa9e2b88a6644d363c9fe26"
integrity sha1-D0ZZ+7CddRlPqeK4imZE02PJ/iY=
lodash._getnative@*, lodash._getnative@^3.0.0:
version "3.9.1"
resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5"
integrity sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=
lodash._root@~3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/lodash._root/-/lodash._root-3.0.1.tgz#fba1c4524c19ee9a5f8136b4609f017cf4ded692"
@ -8917,6 +8971,11 @@ lodash.memoize@4.x:
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=
lodash.restparam@*:
version "3.6.1"
resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805"
integrity sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=
lodash.sortby@^4.7.0:
version "4.7.0"
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
@ -9151,9 +9210,9 @@ md5.js@^1.3.4:
safe-buffer "^5.1.2"
meant@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/meant/-/meant-1.0.2.tgz#5d0c78310a3d8ae1408a16be0fe0bd42a969f560"
integrity sha512-KN+1uowN/NK+sT/Lzx7WSGIj2u+3xe5n2LbwObfjOhPZiA+cCfCm6idVl0RkEfjThkw5XJ96CyRcanq6GmKtUg==
version "1.0.3"
resolved "https://registry.yarnpkg.com/meant/-/meant-1.0.3.tgz#67769af9de1d158773e928ae82c456114903554c"
integrity sha512-88ZRGcNxAq4EH38cQ4D85PM57pikCwS8Z99EWHODxN7KBY+UuPiqzRTtZzS8KTXO/ywSWbdjjJST2Hly/EQxLw==
media-typer@0.3.0:
version "0.3.0"
@ -9458,36 +9517,31 @@ mkdirp@1.x, mkdirp@^1.0.3:
dependencies:
minimist "^1.2.5"
mobx-observable-history@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/mobx-observable-history/-/mobx-observable-history-1.0.3.tgz#7e34be76024dc09c71b17c3996b422f50a347c98"
integrity sha512-3JNF00AWbAXBSFJ8y/+xiPEpyySEDsNiWoT5q1G7V2w5BUQ+umqPSrmoJJlSzSs5d6Ey8HHGyPm0aGK444+hcA==
mobx-observable-history@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/mobx-observable-history/-/mobx-observable-history-2.0.0.tgz#aec91929b8c0d363a19aac8dfd071a92c509caff"
integrity sha512-xX8w4lM5mKAO0S3yJ2M8FQ2pQ7Ns7zOvq7+goEnhHizO8HeHfC7O5SCy+BL5LN3Ahr5p4AW08KAj3edLBK5h4Q==
dependencies:
"@types/history" "^4.7.3"
"@types/history" "^4.7.8"
history "^4.10.1"
mobx "^5.15.4"
mobx "^6.3.0"
mobx-react-lite@>=2.2.0:
version "2.2.2"
resolved "https://registry.yarnpkg.com/mobx-react-lite/-/mobx-react-lite-2.2.2.tgz#87c217dc72b4e47b22493daf155daf3759f868a6"
integrity sha512-2SlXALHIkyUPDsV4VTKVR9DW7K3Ksh1aaIv3NrNJygTbhXe2A9GrcKHZ2ovIiOp/BXilOcTYemfHHZubP431dg==
mobx-react-lite@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/mobx-react-lite/-/mobx-react-lite-3.2.0.tgz#331d7365a6b053378dfe9c087315b4e41c5df69f"
integrity sha512-q5+UHIqYCOpBoFm/PElDuOhbcatvTllgRp3M1s+Hp5j0Z6XNgDbgqxawJ0ZAUEyKM8X1zs70PCuhAIzX1f4Q/g==
mobx-react@^6.2.2:
version "6.3.0"
resolved "https://registry.yarnpkg.com/mobx-react/-/mobx-react-6.3.0.tgz#7d11799f988bbdadc49e725081993b18baa20329"
integrity sha512-C14yya2nqEBRSEiJjPkhoWJLlV8pcCX3m2JRV7w1KivwANJqipoiPx9UMH4pm6QNMbqDdvJqoyl+LqNu9AhvEQ==
mobx-react@^7.1.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/mobx-react/-/mobx-react-7.1.0.tgz#d947cada3cfad294b4b6f692e969c242b9c16eaf"
integrity sha512-DxvA6VXmnZ+N9f/UTtolWtdRnAAQY2iHWTSPLktfpj8NKlXUe4dabBAjuXrBcZUM8GjLWnxD1ZEjssXq1M0RAw==
dependencies:
mobx-react-lite ">=2.2.0"
mobx-react-lite "^3.2.0"
mobx@^5.15.4:
version "5.15.4"
resolved "https://registry.yarnpkg.com/mobx/-/mobx-5.15.4.tgz#9da1a84e97ba624622f4e55a0bf3300fb931c2ab"
integrity sha512-xRFJxSU2Im3nrGCdjSuOTFmxVDGeqOHL+TyADCGbT0k4HHqGmx5u2yaHNryvoORpI4DfbzjJ5jPmuv+d7sioFw==
mobx@^5.15.7:
version "5.15.7"
resolved "https://registry.yarnpkg.com/mobx/-/mobx-5.15.7.tgz#b9a5f2b6251f5d96980d13c78e9b5d8d4ce22665"
integrity sha512-wyM3FghTkhmC+hQjyPGGFdpehrcX1KOXsDuERhfK2YbJemkUhEB+6wzEN639T21onxlfYBmriA1PFnvxTUhcKw==
mobx@^6.3.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/mobx/-/mobx-6.3.0.tgz#a8fb693c3047bdfcb1eaff9aa48e36a7eb084f96"
integrity sha512-Aa1+VXsg4WxqJMTQfWoYuJi5UD10VZhiobSmcs5kcmI3BIT0aVtn7DysvCeDADCzl7dnbX+0BTHUj/v7gLlZpQ==
mock-fs@^4.12.0:
version "4.12.0"
@ -9921,9 +9975,9 @@ npm-audit-report@^1.3.3:
console-control-strings "^1.1.0"
npm-bundled@^1.0.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.1.tgz#1edd570865a94cdb1bc8220775e29466c9fb234b"
integrity sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==
version "1.1.2"
resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.2.tgz#944c78789bd739035b70baa2ca5cc32b8d860bc1"
integrity sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ==
dependencies:
npm-normalize-package-bin "^1.0.1"
@ -10040,15 +10094,15 @@ npm-run-path@^4.0.0:
dependencies:
path-key "^3.0.0"
npm-user-validate@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/npm-user-validate/-/npm-user-validate-1.0.0.tgz#8ceca0f5cea04d4e93519ef72d0557a75122e951"
integrity sha1-jOyg9c6gTU6TUZ73LQVXp1Ei6VE=
npm-user-validate@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/npm-user-validate/-/npm-user-validate-1.0.1.tgz#31428fc5475fe8416023f178c0ab47935ad8c561"
integrity sha512-uQwcd/tY+h1jnEaze6cdX/LrhWhoBxfSknxentoqmIuStxUExxjWd3ULMLFPiFUrZKbOVMowH6Jq2FRWfmhcEw==
npm@^6.14.8:
version "6.14.8"
resolved "https://registry.yarnpkg.com/npm/-/npm-6.14.8.tgz#64ef754345639bc035982ec3f609353c8539033c"
integrity sha512-HBZVBMYs5blsj94GTeQZel7s9odVuuSUHy1+AlZh7rPVux1os2ashvEGLy/STNK7vUjbrCg5Kq9/GXisJgdf6A==
version "6.14.13"
resolved "https://registry.yarnpkg.com/npm/-/npm-6.14.13.tgz#e88bcb6c48209869c40b5cedad8a1508e58e6f30"
integrity sha512-SRl4jJi0EBHY2xKuu98FLRMo3VhYQSA6otyLnjSEiHoSG/9shXCFNJy9tivpUJvtkN9s6VDdItHa5Rn+fNBzag==
dependencies:
JSONStream "^1.3.5"
abbrev "~1.1.1"
@ -10080,12 +10134,12 @@ npm@^6.14.8:
glob "^7.1.6"
graceful-fs "^4.2.4"
has-unicode "~2.0.1"
hosted-git-info "^2.8.8"
hosted-git-info "^2.8.9"
iferr "^1.0.2"
infer-owner "^1.0.4"
inflight "~1.0.6"
inherits "^2.0.4"
ini "^1.3.5"
ini "^1.3.8"
init-package-json "^1.10.3"
is-cidr "^3.0.0"
json-parse-better-errors "^1.0.2"
@ -10122,10 +10176,10 @@ npm@^6.14.8:
npm-pick-manifest "^3.0.2"
npm-profile "^4.0.4"
npm-registry-fetch "^4.0.7"
npm-user-validate "~1.0.0"
npm-user-validate "^1.0.1"
npmlog "~4.1.2"
once "~1.4.0"
opener "^1.5.1"
opener "^1.5.2"
osenv "^0.1.5"
pacote "^9.5.12"
path-is-inside "~1.0.2"
@ -10149,7 +10203,7 @@ npm@^6.14.8:
slide "~1.1.6"
sorted-object "~2.0.1"
sorted-union-stream "~2.1.3"
ssri "^6.0.1"
ssri "^6.0.2"
stringify-package "^1.0.1"
tar "^4.4.13"
text-table "~0.2.0"
@ -10377,7 +10431,7 @@ open@^7.3.1:
is-docker "^2.0.0"
is-wsl "^2.1.1"
opener@^1.5.1:
opener@^1.5.2:
version "1.5.2"
resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598"
integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==
@ -11390,11 +11444,12 @@ query-string@^5.0.1:
strict-uri-encode "^1.0.0"
query-string@^6.8.2:
version "6.13.4"
resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.13.4.tgz#b35a9a3bd4955bce55f94feb0e819b3d0be6f66f"
integrity sha512-E2NPIeJoBEJGQNy3ib1k/Z/OkDBUKIo8IV2ZVwbKfoa65IS9unqWWUlLcbfU70Da0qNoxUZZA8CfKUjKLE641Q==
version "6.14.1"
resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.14.1.tgz#7ac2dca46da7f309449ba0f86b1fd28255b0c86a"
integrity sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw==
dependencies:
decode-uri-component "^0.2.0"
filter-obj "^1.1.0"
split-on-first "^1.0.0"
strict-uri-encode "^2.0.0"
@ -12896,7 +12951,7 @@ sshpk@^1.7.0:
safer-buffer "^2.0.2"
tweetnacl "~0.14.0"
ssri@^6.0.0, ssri@^6.0.1:
ssri@^6.0.0, ssri@^6.0.1, ssri@^6.0.2:
version "6.0.2"
resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.2.tgz#157939134f20464e7301ddba3e90ffa8f7728ac5"
integrity sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==