Cascade different refactorings to eliminate global shared state from app paths

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
Janne SavolainenandMikko Aspiala committed 2022-01-05 13:26:49 +02:00
1 parent 908a40975e
commit 9d33fff906
336 files changed
+9105 -3307

No files matched your search

@@ -0,0 +1,29 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
import { AddNamespaceDialogModel } from "./add-namespace-dialog-model";
const addNamespaceDialogModelInjectable = getInjectable({
instantiate: () => new AddNamespaceDialogModel(),
lifecycle: lifecycleEnum.singleton,
});
export default addNamespaceDialogModelInjectable;
@@ -0,0 +1,41 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { observable, makeObservable, action } from "mobx";
export class AddNamespaceDialogModel {
isOpen = false;
constructor() {
makeObservable(this, {
isOpen: observable,
open: action,
close: action,
});
}
open = () => {
this.isOpen = true;
};
close = () => {
this.isOpen = false;
};
}
@@ -26,38 +26,35 @@ import { observable, makeObservable } from "mobx";
import { observer } from "mobx-react";
import { Dialog, DialogProps } from "../dialog";
import { Wizard, WizardStep } from "../wizard";
import { namespaceStore } from "./namespace.store";
import type { Namespace } from "../../../common/k8s-api/endpoints";
import { Input } from "../input";
import { systemName } from "../input/input_validators";
import { Notifications } from "../notifications";
import { withInjectables } from "@ogre-tools/injectable-react";
import namespaceStoreInjectable from "./namespace-store/namespace-store.injectable";
import type { AddNamespaceDialogModel } from "./add-namespace-dialog-model/add-namespace-dialog-model";
import addNamespaceDialogModelInjectable
from "./add-namespace-dialog-model/add-namespace-dialog-model.injectable";
interface Props extends DialogProps {
onSuccess?(ns: Namespace): void;
onError?(error: any): void;
}
const dialogState = observable.object({
isOpen: false,
});
interface Dependencies {
createNamespace: (params: { name: string }) => Promise<Namespace>,
model: AddNamespaceDialogModel
}
@observer
export class AddNamespaceDialog extends React.Component<Props> {
class NonInjectedAddNamespaceDialog extends React.Component<Props & Dependencies> {
@observable namespace = "";
constructor(props: Props) {
constructor(props: Props & Dependencies) {
super(props);
makeObservable(this);
}
static open() {
dialogState.isOpen = true;
}
static close() {
dialogState.isOpen = false;
}
reset = () => {
this.namespace = "";
};
@@ -67,10 +64,10 @@ export class AddNamespaceDialog extends React.Component<Props> {
const { onSuccess, onError } = this.props;
try {
const created = await namespaceStore.create({ name: namespace });
const created = await this.props.createNamespace({ name: namespace });
onSuccess?.(created);
AddNamespaceDialog.close();
this.props.close();
} catch (err) {
Notifications.error(err);
onError?.(err);
@@ -78,7 +75,7 @@ export class AddNamespaceDialog extends React.Component<Props> {
};
render() {
const { ...dialogProps } = this.props;
const { model, createNamespace, ...dialogProps } = this.props;
const { namespace } = this;
const header = <h5>Create Namespace</h5>;
@@ -86,11 +83,11 @@ export class AddNamespaceDialog extends React.Component<Props> {
<Dialog
{...dialogProps}
className="AddNamespaceDialog"
isOpen={dialogState.isOpen}
isOpen={this.props.model.isOpen}
onOpen={this.reset}
close={AddNamespaceDialog.close}
close={this.props.model.close}
>
<Wizard header={header} done={AddNamespaceDialog.close}>
<Wizard header={header} done={this.props.model.close}>
<WizardStep
contentClass="flex gaps column"
nextLabel="Create"
@@ -110,3 +107,16 @@ export class AddNamespaceDialog extends React.Component<Props> {
);
}
}
export const AddNamespaceDialog = withInjectables<Dependencies, Props>(
NonInjectedAddNamespaceDialog,
{
getProps: (di, props) => ({
createNamespace: di.inject(namespaceStoreInjectable).create,
model: di.inject(addNamespaceDialogModelInjectable),
...props,
}),
},
);
@@ -25,7 +25,7 @@ import React from "react";
import { computed, makeObservable, observable, reaction } from "mobx";
import { disposeOnUnmount, observer } from "mobx-react";
import { DrawerItem } from "../drawer";
import { boundMethod, cssNames } from "../../utils";
import { boundMethod, cssNames, Disposer } from "../../utils";
import { getMetricsForNamespace, IPodMetrics, Namespace } from "../../../common/k8s-api/endpoints";
import type { KubeObjectDetailsProps } from "../kube-object-details";
import { Link } from "react-router-dom";
@@ -39,16 +39,24 @@ import { ClusterMetricsResourceType } from "../../../common/cluster-types";
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
import { getDetailsUrl } from "../kube-detail-params";
import logger from "../../../common/logger";
import { kubeWatchApi } from "../../../common/k8s-api/kube-watch-api";
import type { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
import type { KubeObject } from "../../../common/k8s-api/kube-object";
import { withInjectables } from "@ogre-tools/injectable-react";
import kubeWatchApiInjectable
from "../../kube-watch-api/kube-watch-api.injectable";
interface Props extends KubeObjectDetailsProps<Namespace> {
}
interface Dependencies {
subscribeStores: (stores: KubeObjectStore<KubeObject>[]) => Disposer
}
@observer
export class NamespaceDetails extends React.Component<Props> {
class NonInjectedNamespaceDetails extends React.Component<Props & Dependencies> {
@observable metrics: IPodMetrics = null;
constructor(props: Props) {
constructor(props: Props & Dependencies) {
super(props);
makeObservable(this);
}
@@ -58,7 +66,8 @@ export class NamespaceDetails extends React.Component<Props> {
reaction(() => this.props.object, () => {
this.metrics = null;
}),
kubeWatchApi.subscribeStores([
this.props.subscribeStores([
resourceQuotaStore,
limitRangeStore,
]),
@@ -138,3 +147,15 @@ export class NamespaceDetails extends React.Component<Props> {
);
}
}
export const NamespaceDetails = withInjectables<Dependencies, Props>(
NonInjectedNamespaceDetails,
{
getProps: (di, props) => ({
subscribeStores: di.inject(kubeWatchApiInjectable).subscribeStores,
...props,
}),
},
);
@@ -0,0 +1,30 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { NamespaceSelectFilterModel } from "./namespace-select-filter-model";
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
const NamespaceSelectFilterModelInjectable = getInjectable({
instantiate: () => new NamespaceSelectFilterModel(),
lifecycle: lifecycleEnum.singleton,
});
export default NamespaceSelectFilterModelInjectable;
@@ -0,0 +1,54 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { observable, makeObservable, action } from "mobx";
export class NamespaceSelectFilterModel {
constructor() {
makeObservable(this, {
menuIsOpen: observable,
closeMenu: action,
openMenu: action,
toggleMenu: action,
isMultiSelection: observable,
setIsMultiSelection: action,
});
}
menuIsOpen = false;
closeMenu = () => {
this.menuIsOpen = false;
};
openMenu = () => {
this.menuIsOpen = true;
};
toggleMenu = () => {
this.menuIsOpen = !this.menuIsOpen;
};
isMultiSelection = false;
setIsMultiSelection = (isMultiSelection: boolean) => {
this.isMultiSelection = isMultiSelection;
};
}
@@ -24,41 +24,26 @@ import "./namespace-select-filter.scss";
import React from "react";
import { disposeOnUnmount, observer } from "mobx-react";
import { components, PlaceholderProps } from "react-select";
import { action, computed, makeObservable, observable, reaction } from "mobx";
import { action, makeObservable, observable, reaction } from "mobx";
import { Icon } from "../icon";
import { NamespaceSelect } from "./namespace-select";
import { namespaceStore } from "./namespace.store";
import type { NamespaceStore } from "./namespace-store/namespace.store";
import type { SelectOption, SelectProps } from "../select";
import { isMac } from "../../../common/vars";
import { withInjectables } from "@ogre-tools/injectable-react";
import namespaceStoreInjectable from "./namespace-store/namespace-store.injectable";
import type { NamespaceSelectFilterModel } from "./namespace-select-filter-model/namespace-select-filter-model";
import namespaceSelectFilterModelInjectable from "./namespace-select-filter-model/namespace-select-filter-model.injectable";
const Placeholder = observer((props: PlaceholderProps<any, boolean>) => {
const getPlaceholder = (): React.ReactNode => {
const namespaces = namespaceStore.contextNamespaces;
if (namespaceStore.areAllSelectedImplicitly || !namespaces.length) {
return <>All namespaces</>;
}
if (namespaces.length === 1) {
return <>Namespace: {namespaces[0]}</>;
}
return <>Namespaces: {namespaces.join(", ")}</>;
};
return (
<components.Placeholder {...props}>
{getPlaceholder()}
</components.Placeholder>
);
});
interface Dependencies {
model: NamespaceSelectFilterModel,
namespaceStore: NamespaceStore
}
@observer
export class NamespaceSelectFilter extends React.Component<SelectProps> {
static isMultiSelection = observable.box(false);
static isMenuOpen = observable.box(false);
class NonInjectedNamespaceSelectFilter extends React.Component<SelectProps & Dependencies> {
/**
* Only updated on every open
@@ -66,32 +51,20 @@ export class NamespaceSelectFilter extends React.Component<SelectProps> {
private selected = observable.set<string>();
private didToggle = false;
constructor(props: SelectProps) {
constructor(props: SelectProps & Dependencies) {
super(props);
makeObservable(this);
}
@computed get isMultiSelection() {
return NamespaceSelectFilter.isMultiSelection.get();
}
set isMultiSelection(val: boolean) {
NamespaceSelectFilter.isMultiSelection.set(val);
}
@computed get isMenuOpen() {
return NamespaceSelectFilter.isMenuOpen.get();
}
set isMenuOpen(val: boolean) {
NamespaceSelectFilter.isMenuOpen.set(val);
get model() {
return this.props.model;
}
componentDidMount() {
disposeOnUnmount(this, [
reaction(() => this.isMenuOpen, newVal => {
reaction(() => this.model.menuIsOpen, newVal => {
if (newVal) { // rising edge of selection
this.selected.replace(namespaceStore.selectedNames);
this.selected.replace(this.props.namespaceStore.selectedNames);
this.didToggle = false;
}
}),
@@ -100,7 +73,7 @@ export class NamespaceSelectFilter extends React.Component<SelectProps> {
formatOptionLabel({ value: namespace, label }: SelectOption) {
if (namespace) {
const isSelected = namespaceStore.hasContext(namespace);
const isSelected = this.props.namespaceStore.hasContext(namespace);
return (
<div className="flex gaps align-center">
@@ -117,14 +90,14 @@ export class NamespaceSelectFilter extends React.Component<SelectProps> {
@action
onChange = ([{ value: namespace }]: SelectOption[]) => {
if (namespace) {
if (this.isMultiSelection) {
if (this.model.isMultiSelection) {
this.didToggle = true;
namespaceStore.toggleSingle(namespace);
this.props.namespaceStore.toggleSingle(namespace);
} else {
namespaceStore.selectSingle(namespace);
this.props.namespaceStore.selectSingle(namespace);
}
} else {
namespaceStore.selectAll();
this.props.namespaceStore.selectAll();
}
};
@@ -139,33 +112,33 @@ export class NamespaceSelectFilter extends React.Component<SelectProps> {
@action
onKeyDown = (e: React.KeyboardEvent) => {
if (this.isSelectionKey(e)) {
this.isMultiSelection = true;
this.model.setIsMultiSelection(true);
}
};
@action
onKeyUp = (e: React.KeyboardEvent) => {
if (this.isSelectionKey(e)) {
this.isMultiSelection = false;
this.model.setIsMultiSelection(false);
}
if (!this.isMultiSelection && this.didToggle) {
this.isMenuOpen = false;
if (!this.model.isMultiSelection && this.didToggle) {
this.model.closeMenu();
}
};
@action
onClick = () => {
if (!this.isMenuOpen) {
this.isMenuOpen = true;
} else if (!this.isMultiSelection) {
this.isMenuOpen = !this.isMenuOpen;
if (!this.model.menuIsOpen) {
this.model.openMenu();
} else if (!this.model.isMultiSelection) {
this.model.toggleMenu();
}
};
reset = () => {
this.isMultiSelection = false;
this.isMenuOpen = false;
this.model.setIsMultiSelection(true);
this.model.closeMenu();
};
render() {
@@ -173,7 +146,7 @@ export class NamespaceSelectFilter extends React.Component<SelectProps> {
<div onKeyUp={this.onKeyUp} onKeyDown={this.onKeyDown} onClick={this.onClick}>
<NamespaceSelect
isMulti={true}
menuIsOpen={this.isMenuOpen}
menuIsOpen={this.model.menuIsOpen}
components={{ Placeholder }}
showAllNamespacesOption={true}
closeMenuOnSelect={false}
@@ -190,3 +163,58 @@ export class NamespaceSelectFilter extends React.Component<SelectProps> {
);
}
}
export const NamespaceSelectFilter = withInjectables<Dependencies, SelectProps>(
NonInjectedNamespaceSelectFilter,
{
getProps: (di, props) => ({
model: di.inject(namespaceSelectFilterModelInjectable),
namespaceStore: di.inject(namespaceStoreInjectable),
...props,
}),
},
);
type CustomPlaceholderProps = PlaceholderProps<any, boolean>;
interface PlaceholderDependencies {
namespaceStore: NamespaceStore
}
const NonInjectedPlaceholder = observer(
({ namespaceStore, ...props }: CustomPlaceholderProps & PlaceholderDependencies) => {
const getPlaceholder = (): React.ReactNode => {
const namespaces = namespaceStore.contextNamespaces;
if (namespaceStore.areAllSelectedImplicitly || !namespaces.length) {
return <>All namespaces</>;
}
if (namespaces.length === 1) {
return <>Namespace: {namespaces[0]}</>;
}
return <>Namespaces: {namespaces.join(", ")}</>;
};
return (
<components.Placeholder {...props}>
{getPlaceholder()}
</components.Placeholder>
);
},
);
const Placeholder = withInjectables<PlaceholderDependencies, CustomPlaceholderProps>(
NonInjectedPlaceholder,
{
getProps: (di, props) => ({
namespaceStore: di.inject(namespaceStoreInjectable),
...props,
}),
},
);
@@ -27,7 +27,9 @@ import { observer } from "mobx-react";
import { Select, SelectOption, SelectProps } from "../select";
import { cssNames } from "../../utils";
import { Icon } from "../icon";
import { namespaceStore } from "./namespace.store";
import type { NamespaceStore } from "./namespace-store/namespace.store";
import { withInjectables } from "@ogre-tools/injectable-react";
import namespaceStoreInjectable from "./namespace-store/namespace-store.injectable";
interface Props extends SelectProps {
showIcons?: boolean;
@@ -40,11 +42,15 @@ const defaultProps: Partial<Props> = {
showIcons: true,
};
interface Dependencies {
namespaceStore: NamespaceStore
}
@observer
export class NamespaceSelect extends React.Component<Props> {
class NonInjectedNamespaceSelect extends React.Component<Props & Dependencies> {
static defaultProps = defaultProps as object;
constructor(props: Props) {
constructor(props: Props & Dependencies) {
super(props);
makeObservable(this);
}
@@ -53,7 +59,7 @@ export class NamespaceSelect extends React.Component<Props> {
@computed.struct get options(): SelectOption[] {
const { customizeOptions, showAllNamespacesOption, sort } = this.props;
let options: SelectOption[] = namespaceStore.items.map(ns => ({ value: ns.getName() }));
let options: SelectOption[] = this.props.namespaceStore.items.map(ns => ({ value: ns.getName() }));
if (sort) {
options.sort(sort);
@@ -97,3 +103,14 @@ export class NamespaceSelect extends React.Component<Props> {
);
}
}
export const NamespaceSelect = withInjectables<Dependencies, Props>(
NonInjectedNamespaceSelect,
{
getProps: (di, props) => ({
namespaceStore: di.inject(namespaceStoreInjectable),
...props,
}),
},
);
@@ -0,0 +1,49 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
import { NamespaceStore } from "./namespace.store";
import apiManagerInjectable from "../../kube-object-menu/dependencies/api-manager.injectable";
import createStorageInjectable from "../../../utils/create-storage/create-storage.injectable";
const namespaceStoreInjectable = getInjectable({
instantiate: (di) => {
const createStorage = di.inject(createStorageInjectable);
const storage = createStorage<string[] | undefined>(
"selected_namespaces",
undefined,
);
const namespaceStore = new NamespaceStore({
storage,
});
const apiManager = di.inject(apiManagerInjectable);
apiManager.registerStore(namespaceStore);
return namespaceStore;
},
lifecycle: lifecycleEnum.singleton,
});
export default namespaceStoreInjectable;
@@ -20,16 +20,18 @@
*/
import { action, comparer, computed, IReactionDisposer, makeObservable, reaction } from "mobx";
import { autoBind, createStorage, noop, ToggleSet } from "../../utils";
import { KubeObjectStore, KubeObjectStoreLoadingParams } from "../../../common/k8s-api/kube-object.store";
import { Namespace, namespacesApi } from "../../../common/k8s-api/endpoints/namespaces.api";
import { apiManager } from "../../../common/k8s-api/api-manager";
import { autoBind, noop, StorageHelper, ToggleSet } from "../../../utils";
import { KubeObjectStore, KubeObjectStoreLoadingParams } from "../../../../common/k8s-api/kube-object.store";
import { Namespace, namespacesApi } from "../../../../common/k8s-api/endpoints/namespaces.api";
interface Dependencies {
storage: StorageHelper<string[] | undefined>
}
export class NamespaceStore extends KubeObjectStore<Namespace> {
api = namespacesApi;
private storage = createStorage<string[] | undefined>("selected_namespaces", undefined);
constructor() {
constructor(private dependencies: Dependencies) {
super();
makeObservable(this);
autoBind(this);
@@ -39,7 +41,7 @@ export class NamespaceStore extends KubeObjectStore<Namespace> {
private async init() {
await this.contextReady;
await this.storage.whenReady;
await this.dependencies.storage.whenReady;
this.selectNamespaces(this.initialNamespaces);
this.autoLoadAllowedNamespaces();
@@ -61,7 +63,7 @@ export class NamespaceStore extends KubeObjectStore<Namespace> {
private get initialNamespaces(): string[] {
const { allowedNamespaces } = this;
const selectedNamespaces = this.storage.get(); // raw namespaces, undefined on first load
const selectedNamespaces = this.dependencies.storage.get(); // raw namespaces, undefined on first load
// return previously saved namespaces from local-storage (if any)
if (Array.isArray(selectedNamespaces)) {
@@ -83,7 +85,7 @@ export class NamespaceStore extends KubeObjectStore<Namespace> {
* The current value (list of namespaces names) in the storage layer
*/
@computed private get selectedNamespaces(): string[] {
return this.storage.get() ?? [];
return this.dependencies.storage.get() ?? [];
}
@computed get allowedNamespaces(): string[] {
@@ -147,22 +149,21 @@ export class NamespaceStore extends KubeObjectStore<Namespace> {
return namespaces;
}
@action
selectNamespaces(namespace: string | string[]) {
@action selectNamespaces = (namespace: string | string[]) => {
const namespaces = Array.from(new Set([namespace].flat()));
this.storage.set(namespaces);
}
this.dependencies.storage.set(namespaces);
};
@action
clearSelected(namespaces?: string | string[]) {
if (namespaces) {
const resettingNamespaces = [namespaces].flat();
const newNamespaces = this.storage.get().filter(ns => !resettingNamespaces.includes(ns));
const newNamespaces = this.dependencies.storage.get().filter(ns => !resettingNamespaces.includes(ns));
this.storage.set(newNamespaces);
this.dependencies.storage.set(newNamespaces);
} else {
this.storage.reset();
this.dependencies.storage.reset();
}
}
@@ -196,7 +197,7 @@ export class NamespaceStore extends KubeObjectStore<Namespace> {
nextState.toggle(namespace);
}
this.storage.set([...nextState]);
this.dependencies.storage.set([...nextState]);
}
/**
@@ -209,14 +210,14 @@ export class NamespaceStore extends KubeObjectStore<Namespace> {
const nextState = new ToggleSet(this.contextNamespaces);
nextState.toggle(namespace);
this.storage.set([...nextState]);
this.dependencies.storage.set([...nextState]);
}
/**
* Makes the given namespace the sole selected namespace
*/
selectSingle(namespace: string) {
this.storage.set([namespace]);
this.dependencies.storage.set([namespace]);
}
/**
@@ -247,9 +248,6 @@ export class NamespaceStore extends KubeObjectStore<Namespace> {
}
}
export const namespaceStore = new NamespaceStore();
apiManager.registerStore(namespaceStore);
export function getDummyNamespace(name: string) {
return new Namespace({
kind: Namespace.kind,
@@ -28,9 +28,13 @@ import { TabLayout } from "../layout/tab-layout";
import { Badge } from "../badge";
import type { RouteComponentProps } from "react-router";
import { KubeObjectListLayout } from "../kube-object-list-layout";
import { namespaceStore } from "./namespace.store";
import type { NamespaceStore } from "./namespace-store/namespace.store";
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
import type { NamespacesRouteParams } from "../../../common/routes";
import { withInjectables } from "@ogre-tools/injectable-react";
import namespaceStoreInjectable from "./namespace-store/namespace-store.injectable";
import addNamespaceDialogModelInjectable
from "./add-namespace-dialog-model/add-namespace-dialog-model.injectable";
enum columnId {
name = "name",
@@ -42,7 +46,12 @@ enum columnId {
interface Props extends RouteComponentProps<NamespacesRouteParams> {
}
export class Namespaces extends React.Component<Props> {
interface Dependencies {
namespaceStore: NamespaceStore
openAddNamespaceDialog: () => void
}
class NonInjectedNamespaces extends React.Component<Props & Dependencies> {
render() {
return (
<TabLayout>
@@ -50,7 +59,7 @@ export class Namespaces extends React.Component<Props> {
isConfigurable
tableId="namespaces"
className="Namespaces"
store={namespaceStore}
store={this.props.namespaceStore}
sortingCallbacks={{
[columnId.name]: ns => ns.getName(),
[columnId.labels]: ns => ns.getLabels(),
@@ -78,7 +87,7 @@ export class Namespaces extends React.Component<Props> {
]}
addRemoveButtons={{
addTooltip: "Add Namespace",
onAdd: () => AddNamespaceDialog.open(),
onAdd: () => this.props.openAddNamespaceDialog(),
}}
customizeTableRowProps={item => ({
disabled: item.getStatus() === NamespaceStatus.TERMINATING,
@@ -89,3 +98,15 @@ export class Namespaces extends React.Component<Props> {
);
}
}
export const Namespaces = withInjectables<Dependencies, Props>(
NonInjectedNamespaces,
{
getProps: (di, props) => ({
namespaceStore: di.inject(namespaceStoreInjectable),
openAddNamespaceDialog: di.inject(addNamespaceDialogModelInjectable).open,
...props,
}),
},
);