Remove BaseStore<T> and replace with composition of dependencies (#7002)

* Rename ExtensionStore -> BaseExtensionStore
- The name was too close to ExtensionsStore

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Move ExtensionsStore to new format

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Move ClusterStore to new format

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Move UserStore to new format

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Cleanup types to remove multiple cast locations

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Move HotbarStore to new format

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Move WeblinkStore to new format

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Move FileSystemProvisionerStore to new format

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Update snapshots

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Clean up impl and rename to better describe intent

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix remaining type errors

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fully split apart the enabled extensions storage

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fully split apart the clusters storage

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fully split apart the hotbar storage

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fully split apart the weblinks storage

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fully split apart the user preferences storage

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix crashing

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix tests and snapshots

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix integration test failures

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Improve typing to prevent errors in the future.

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Cleanup @k8slens/messaging and friends

- To fix type errors

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix lint issue

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix type errors

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix global override not being complete enough causing tests to fail

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Bump memory for unit tests on CI

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Attempt to fix memory issue on CI again

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fixup test because of new injectables

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Upgrade Jest

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix unit tests falling over

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Back out jest config change

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove console log

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Update snapshot

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix tests by matching equality instead of snapshots

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix tests by forcing specific snapshot style

- Ubuntu CI seems to format arrays in snapshots differently than macOS locally

Signed-off-by: Sebastian Malton <sebastian@malton.name>

---------

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton authored and GitHub committed 2023-03-28 17:54:25 +03:00
1 parent ba4a283af9
commit 49f0a1af9c
365 files changed
+6733 -3972

No files matched your search

@@ -4,16 +4,24 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx";
import clusterStoreInjectable from "../../../../common/cluster-store/cluster-store.injectable";
import getClusterByIdInjectable from "../../../../features/cluster/storage/common/get-by-id.injectable";
import catalogEntityRegistryInjectable from "./registry.injectable";
const activeEntityInternalClusterInjectable = getInjectable({
id: "active-entity-internal-cluster",
instantiate: (di) => {
const store = di.inject(clusterStoreInjectable);
const getClusterById = di.inject(getClusterByIdInjectable);
const entityRegistry = di.inject(catalogEntityRegistryInjectable);
return computed(() => store.getById(entityRegistry.activeEntity?.getId()));
return computed(() => {
const entityId = entityRegistry.activeEntity?.getId();
if (entityId) {
return getClusterById(entityId);
}
return undefined;
});
},
});
@@ -3,12 +3,12 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { shouldBaseStoreDisableSyncInIpcListenerInjectionToken } from "../../common/base-store/disable-sync";
import { shouldPersistentStorageDisableSyncInIpcListenerInjectionToken } from "../../common/persistent-storage/disable-sync";
const shouldBaseStoreDisableSyncInIpcListenerInjectable = getInjectable({
id: "should-base-store-disable-sync-in-ipc-listener",
instantiate: () => true,
injectionToken: shouldBaseStoreDisableSyncInIpcListenerInjectionToken,
injectionToken: shouldPersistentStorageDisableSyncInIpcListenerInjectionToken,
});
export default shouldBaseStoreDisableSyncInIpcListenerInjectable;
@@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { baseStoreIpcChannelPrefixesInjectionToken } from "../../common/base-store/channel-prefix";
import { persistentStorageIpcChannelPrefixesInjectionToken } from "../../common/persistent-storage/channel-prefix";
const baseStoreIpcChannelPrefixInjectable = getInjectable({
id: "base-store-ipc-channel-prefix",
@@ -11,7 +11,7 @@ const baseStoreIpcChannelPrefixInjectable = getInjectable({
local: "store-sync-renderer",
remote: "store-sync-main",
}),
injectionToken: baseStoreIpcChannelPrefixesInjectionToken,
injectionToken: persistentStorageIpcChannelPrefixesInjectionToken,
});
export default baseStoreIpcChannelPrefixInjectable;
@@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { persistStateToConfigInjectionToken } from "../../common/base-store/save-to-file";
import { persistStateToConfigInjectionToken } from "../../common/persistent-storage/save-to-file";
import { noop } from "@k8slens/utilities";
const persistStateToConfigInjectable = getInjectable({
@@ -4,9 +4,9 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import catalogCategoryRegistryInjectable from "../../../common/catalog/category-registry.injectable";
import getClusterByIdInjectable from "../../../common/cluster-store/get-by-id.injectable";
import loadKubeconfigInjectable from "../../../common/cluster/load-kubeconfig.injectable";
import loggerInjectable from "../../../common/logger.injectable";
import getClusterByIdInjectable from "../../../features/cluster/storage/common/get-by-id.injectable";
import openDeleteClusterDialogInjectable from "../../components/delete-cluster-dialog/open.injectable";
import { beforeFrameStartsSecondInjectionToken } from "../tokens";
@@ -4,16 +4,20 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import hostedClusterIdInjectable from "./hosted-cluster-id.injectable";
import clusterStoreInjectable from "../../common/cluster-store/cluster-store.injectable";
import getClusterByIdInjectable from "../../features/cluster/storage/common/get-by-id.injectable";
const hostedClusterInjectable = getInjectable({
id: "hosted-cluster",
instantiate: (di) => {
const hostedClusterId = di.inject(hostedClusterIdInjectable);
const store = di.inject(clusterStoreInjectable);
const getClusterById = di.inject(getClusterByIdInjectable);
return store.getById(hostedClusterId);
if (!hostedClusterId) {
return undefined;
}
return getClusterById(hostedClusterId);
},
});
@@ -5,7 +5,7 @@
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
import { computed } from "mobx";
import hostedClusterInjectable from "./hosted-cluster.injectable";
import { shouldShowResourceInjectionToken } from "../../common/cluster-store/allowed-resources-injection-token";
import { shouldShowResourceInjectionToken } from "../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token";
import type { KubeApiResourceDescriptor } from "../../common/rbac";
import { formatKubeApiResource } from "../../common/rbac";
@@ -11,7 +11,6 @@ import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
import type { AdditionalCategoryColumnRegistration, CategoryColumnRegistration } from "../custom-category-columns";
import type { CategoryColumns, GetCategoryColumnsParams } from "../columns/get.injectable";
import getCategoryColumnsInjectable from "../columns/get.injectable";
import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable";
import extensionInjectable from "../../../../extensions/extension-loader/extension/extension.injectable";
import currentlyInClusterFrameInjectable from "../../../routes/currently-in-cluster-frame.injectable";
@@ -46,7 +45,6 @@ describe("Custom Category Columns", () => {
beforeEach(() => {
di = getDiForUnitTesting();
di.override(hotbarStoreInjectable, () => ({}));
di.override(currentlyInClusterFrameInjectable, () => false);
getCategoryColumns = di.inject(getCategoryColumnsInjectable);
@@ -28,8 +28,6 @@ import type { VisitEntityContextMenu } from "../../../common/catalog/visit-entit
import visitEntityContextMenuInjectable from "../../../common/catalog/visit-entity-context-menu.injectable";
import type { NavigateToCatalog } from "../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable";
import navigateToCatalogInjectable from "../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable";
import type { HotbarStore } from "../../../common/hotbars/store";
import hotbarStoreInjectable from "../../../common/hotbars/store.injectable";
import type { Logger } from "../../../common/logger";
import loggerInjectable from "../../../common/logger.injectable";
import type { NormalizeCatalogEntityContextMenu } from "../../catalog/normalize-menu-item.injectable";
@@ -51,6 +49,8 @@ import type { OnCatalogEntityListClick } from "./entity-details/on-catalog-click
import onCatalogEntityListClickInjectable from "./entity-details/on-catalog-click.injectable";
import type { ShowEntityDetails } from "./entity-details/show.injectable";
import showEntityDetailsInjectable from "./entity-details/show.injectable";
import type { Hotbar } from "../../../features/hotbar/storage/common/hotbar";
import activeHotbarInjectable from "../../../features/hotbar/storage/common/active.injectable";
interface Dependencies {
catalogPreviousActiveTabStorage: StorageLayer<string | null>;
@@ -65,13 +65,13 @@ interface Dependencies {
kind: IComputedValue<string>;
};
navigateToCatalog: NavigateToCatalog;
hotbarStore: HotbarStore;
catalogCategoryRegistry: CatalogCategoryRegistry;
visitEntityContextMenu: VisitEntityContextMenu;
navigate: Navigate;
normalizeMenuItem: NormalizeCatalogEntityContextMenu;
showErrorNotification: ShowNotification;
logger: Logger;
activeHotbar: IComputedValue<Hotbar | undefined>;
}
@observer
@@ -156,11 +156,11 @@ class NonInjectedCatalog extends React.Component<Dependencies> {
}
addToHotbar(entity: CatalogEntity): void {
this.props.hotbarStore.addToHotbar(entity);
this.props.activeHotbar.get()?.addEntity(entity);
}
removeFromHotbar(entity: CatalogEntity): void {
this.props.hotbarStore.removeFromHotbar(entity.getId());
this.props.activeHotbar.get()?.removeEntity(entity.getId());
}
onTabChange = action((tabId: string | null) => {
@@ -323,7 +323,7 @@ export const Catalog = withInjectables<Dependencies>(NonInjectedCatalog, {
routeParameters: di.inject(catalogRouteParametersInjectable),
navigateToCatalog: di.inject(navigateToCatalogInjectable),
emitEvent: di.inject(emitAppEventInjectable),
hotbarStore: di.inject(hotbarStoreInjectable),
activeHotbar: di.inject(activeHotbarInjectable),
catalogCategoryRegistry: di.inject(catalogCategoryRegistryInjectable),
visitEntityContextMenu: di.inject(visitEntityContextMenuInjectable),
navigate: di.inject(navigateInjectable),
@@ -0,0 +1,27 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import styles from "../catalog.module.scss";
import type { RegisteredAdditionalCategoryColumn } from "../custom-category-columns";
import renderNamedCategoryColumnCellInjectable from "./render-named-category-column-cell.injectable";
const namedCategoryColumnInjectable = getInjectable({
id: "name-category-column",
instantiate: (di): RegisteredAdditionalCategoryColumn => ({
id: "name",
priority: 0,
renderCell: di.inject(renderNamedCategoryColumnCellInjectable),
titleProps: {
title: "Name",
className: styles.entityName,
id: "name",
sortBy: "name",
},
searchFilter: (entity) => entity.getName(),
sortCallback: (entity) => `name=${entity.getName()}`,
}),
});
export default namedCategoryColumnInjectable;
@@ -1,65 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import styles from "../catalog.module.scss";
import type { CatalogEntity } from "../../../../common/catalog";
import { prevDefault } from "@k8slens/utilities";
import { Avatar } from "../../avatar";
import { Icon } from "../../icon";
import React from "react";
import type { RegisteredAdditionalCategoryColumn } from "../custom-category-columns";
import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable";
import type { HotbarStore } from "../../../../common/hotbars/store";
const renderEntityName = (hotbarStore: HotbarStore) => (entity: CatalogEntity) => {
const isItemInHotbar = hotbarStore.isAddedToActive(entity);
const onClick = prevDefault(
isItemInHotbar
? () => hotbarStore.removeFromHotbar(entity.getId())
: () => hotbarStore.addToHotbar(entity),
);
return (
<>
<Avatar
title={entity.getName()}
colorHash={`${entity.getName()}-${entity.getSource()}`}
src={entity.spec.icon?.src}
background={entity.spec.icon?.background}
className={styles.catalogAvatar}
size={24}
>
{entity.spec.icon?.material && <Icon material={entity.spec.icon?.material} small/>}
</Avatar>
<span>{entity.getName()}</span>
<Icon
small
className={styles.pinIcon}
svg={isItemInHotbar ? "push_off" : "push_pin"}
tooltip={isItemInHotbar ? "Remove from Hotbar" : "Add to Hotbar"}
onClick={onClick}
/>
</>
);
};
const namedCategoryColumnInjectable = getInjectable({
id: "name-category-column",
instantiate: (di): RegisteredAdditionalCategoryColumn => ({
id: "name",
priority: 0,
renderCell: renderEntityName(di.inject(hotbarStoreInjectable)),
titleProps: {
title: "Name",
className: styles.entityName,
id: "name",
sortBy: "name",
},
searchFilter: (entity) => entity.getName(),
sortCallback: (entity) => `name=${entity.getName()}`,
}),
});
export default namedCategoryColumnInjectable;
@@ -0,0 +1,59 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import styles from "../catalog.module.scss";
import React from "react";
import activeHotbarInjectable from "../../../../features/hotbar/storage/common/active.injectable";
import { Avatar } from "../../avatar";
import type { RegisteredAdditionalCategoryColumn } from "../custom-category-columns";
import { Icon } from "../../icon";
import { prevDefault } from "@k8slens/utilities";
const renderNamedCategoryColumnCellInjectable = getInjectable({
id: "render-named-category-column-cell",
instantiate: (di): RegisteredAdditionalCategoryColumn["renderCell"] => {
const activeHotbar = di.inject(activeHotbarInjectable);
return (entity) => {
const hotbar = activeHotbar.get();
if (!hotbar) {
return null;
}
const isItemInHotbar = hotbar.hasEntity(entity.getId());
const onClick = prevDefault((
isItemInHotbar
? () => hotbar.removeEntity(entity.getId())
: () => hotbar.addEntity(entity)
));
return (
<>
<Avatar
title={entity.getName()}
colorHash={`${entity.getName()}-${entity.getSource()}`}
src={entity.spec.icon?.src}
background={entity.spec.icon?.background}
className={styles.catalogAvatar}
size={24}
>
{entity.spec.icon?.material && <Icon material={entity.spec.icon?.material} small/>}
</Avatar>
<span>{entity.getName()}</span>
<Icon
small
className={styles.pinIcon}
svg={isItemInHotbar ? "push_off" : "push_pin"}
tooltip={isItemInHotbar ? "Remove from Hotbar" : "Add to Hotbar"}
onClick={onClick}
/>
</>
);
};
},
});
export default renderNamedCategoryColumnCellInjectable;
@@ -9,11 +9,12 @@ import { MenuItem } from "../menu";
import type { CatalogEntity } from "../../api/catalog-entity";
import { withInjectables } from "@ogre-tools/injectable-react";
import hotbarStoreInjectable from "../../../common/hotbars/store.injectable";
import type { HotbarStore } from "../../../common/hotbars/store";
import type { IComputedValue } from "mobx";
import type { Hotbar } from "../../../features/hotbar/storage/common/hotbar";
import activeHotbarInjectable from "../../../features/hotbar/storage/common/active.injectable";
interface Dependencies {
hotbarStore: HotbarStore;
activeHotbar: IComputedValue<Hotbar | undefined>;
}
interface HotbarToggleMenuItemProps {
@@ -25,19 +26,19 @@ interface HotbarToggleMenuItemProps {
function NonInjectedHotbarToggleMenuItem({
addContent,
entity,
hotbarStore,
activeHotbar,
removeContent,
}: Dependencies & HotbarToggleMenuItemProps) {
const [itemInHotbar, setItemInHotbar] = useState(hotbarStore.isAddedToActive(entity));
const [itemInHotbar, setItemInHotbar] = useState(activeHotbar.get()?.hasEntity(entity.getId()) ?? false);
return (
<MenuItem
onClick={() => {
if (itemInHotbar) {
hotbarStore.removeFromHotbar(entity.getId());
activeHotbar.get()?.removeEntity(entity.getId());
setItemInHotbar(false);
} else {
hotbarStore.addToHotbar(entity);
activeHotbar.get()?.addEntity(entity);
setItemInHotbar(true);
}
}}
@@ -47,14 +48,10 @@ function NonInjectedHotbarToggleMenuItem({
);
}
export const HotbarToggleMenuItem = withInjectables<Dependencies, HotbarToggleMenuItemProps>(
NonInjectedHotbarToggleMenuItem,
{
getProps: (di, props) => ({
hotbarStore: di.inject(hotbarStoreInjectable),
...props,
}),
},
);
export const HotbarToggleMenuItem = withInjectables<Dependencies, HotbarToggleMenuItemProps>(NonInjectedHotbarToggleMenuItem, {
getProps: (di, props) => ({
...props,
activeHotbar: di.inject(activeHotbarInjectable),
}),
});
@@ -6,8 +6,8 @@ import { getInjectable } from "@ogre-tools/injectable";
import { withInjectables } from "@ogre-tools/injectable-react";
import React from "react";
import type { KubernetesCluster } from "../../../../common/catalog-entities";
import type { GetClusterById } from "../../../../common/cluster-store/get-by-id.injectable";
import getClusterByIdInjectable from "../../../../common/cluster-store/get-by-id.injectable";
import type { GetClusterById } from "../../../../features/cluster/storage/common/get-by-id.injectable";
import getClusterByIdInjectable from "../../../../features/cluster/storage/common/get-by-id.injectable";
import { ClusterIconSetting } from "../../cluster-settings/icon-settings";
import { ClusterKubeconfig } from "../../cluster-settings/kubeconfig";
import { ClusterNameSetting } from "../../cluster-settings/name-setting";
@@ -5,8 +5,8 @@
import { getInjectable } from "@ogre-tools/injectable";
import { withInjectables } from "@ogre-tools/injectable-react";
import React from "react";
import type { GetClusterById } from "../../../../common/cluster-store/get-by-id.injectable";
import getClusterByIdInjectable from "../../../../common/cluster-store/get-by-id.injectable";
import type { GetClusterById } from "../../../../features/cluster/storage/common/get-by-id.injectable";
import getClusterByIdInjectable from "../../../../features/cluster/storage/common/get-by-id.injectable";
import { ClusterMetricsSetting } from "../../cluster-settings/metrics-setting";
import { ClusterPrometheusSetting } from "../../cluster-settings/prometheus-setting";
import { ShowMetricsSetting } from "../../cluster-settings/show-metrics";
@@ -5,8 +5,8 @@
import { getInjectable } from "@ogre-tools/injectable";
import { withInjectables } from "@ogre-tools/injectable-react";
import React from "react";
import type { GetClusterById } from "../../../../common/cluster-store/get-by-id.injectable";
import getClusterByIdInjectable from "../../../../common/cluster-store/get-by-id.injectable";
import type { GetClusterById } from "../../../../features/cluster/storage/common/get-by-id.injectable";
import getClusterByIdInjectable from "../../../../features/cluster/storage/common/get-by-id.injectable";
import { ClusterAccessibleNamespaces } from "../../cluster-settings/accessible-namespaces";
import type { EntitySettingViewProps } from "../extension-registrator.injectable";
import { entitySettingInjectionToken } from "../token";
@@ -5,8 +5,8 @@
import { getInjectable } from "@ogre-tools/injectable";
import { withInjectables } from "@ogre-tools/injectable-react";
import React from "react";
import type { GetClusterById } from "../../../../common/cluster-store/get-by-id.injectable";
import getClusterByIdInjectable from "../../../../common/cluster-store/get-by-id.injectable";
import type { GetClusterById } from "../../../../features/cluster/storage/common/get-by-id.injectable";
import getClusterByIdInjectable from "../../../../features/cluster/storage/common/get-by-id.injectable";
import { ClusterNodeShellSetting } from "../../cluster-settings/node-shell-setting";
import type { EntitySettingViewProps } from "../extension-registrator.injectable";
import { entitySettingInjectionToken } from "../token";
@@ -5,8 +5,8 @@
import { getInjectable } from "@ogre-tools/injectable";
import { withInjectables } from "@ogre-tools/injectable-react";
import React from "react";
import type { GetClusterById } from "../../../../common/cluster-store/get-by-id.injectable";
import getClusterByIdInjectable from "../../../../common/cluster-store/get-by-id.injectable";
import type { GetClusterById } from "../../../../features/cluster/storage/common/get-by-id.injectable";
import getClusterByIdInjectable from "../../../../features/cluster/storage/common/get-by-id.injectable";
import { ClusterProxySetting } from "../../cluster-settings/proxy-setting";
import type { EntitySettingViewProps } from "../extension-registrator.injectable";
import { entitySettingInjectionToken } from "../token";
@@ -5,8 +5,8 @@
import { getInjectable } from "@ogre-tools/injectable";
import { withInjectables } from "@ogre-tools/injectable-react";
import React from "react";
import type { GetClusterById } from "../../../../common/cluster-store/get-by-id.injectable";
import getClusterByIdInjectable from "../../../../common/cluster-store/get-by-id.injectable";
import type { GetClusterById } from "../../../../features/cluster/storage/common/get-by-id.injectable";
import getClusterByIdInjectable from "../../../../features/cluster/storage/common/get-by-id.injectable";
import { ClusterLocalTerminalSetting } from "../../cluster-settings/local-terminal-settings";
import type { EntitySettingViewProps } from "../extension-registrator.injectable";
import { entitySettingInjectionToken } from "../token";
@@ -7,15 +7,15 @@ import { getInjectable } from "@ogre-tools/injectable";
import React from "react";
import execFileInjectable from "../../../../common/fs/exec-file.injectable";
import loggerInjectable from "../../../../common/logger.injectable";
import { defaultExtensionRegistryUrl } from "../../../../common/user-store/preferences-helpers";
import userStoreInjectable from "../../../../common/user-store/user-store.injectable";
import { defaultExtensionRegistryUrl } from "../../../../features/user-preferences/common/preferences-helpers";
import userPreferencesStateInjectable from "../../../../features/user-preferences/common/state.injectable";
import showErrorNotificationInjectable from "../../notifications/show-error-notification.injectable";
const getBaseRegistryUrlInjectable = getInjectable({
id: "get-base-registry-url",
instantiate: (di) => {
const { extensionRegistryUrl } = di.inject(userStoreInjectable);
const { extensionRegistryUrl } = di.inject(userPreferencesStateInjectable);
const showErrorNotification = di.inject(showErrorNotificationInjectable);
const logger = di.inject(loggerInjectable);
const execFile = di.inject(execFileInjectable);
@@ -4,7 +4,7 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx";
import { shouldShowResourceInjectionToken } from "../../../../common/cluster-store/allowed-resources-injection-token";
import { shouldShowResourceInjectionToken } from "../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token";
import { byOrderNumber } from "../../../../common/utils/composable-responsibilities/orderable/orderable";
import { workloadInjectionToken } from "./workload-injection-token";
@@ -8,9 +8,9 @@ import styles from "./avatar.module.scss";
import type { ImgHTMLAttributes, MouseEventHandler } from "react";
import React from "react";
import randomColor from "randomcolor";
import GraphemeSplitter from "grapheme-splitter";
import type { SingleOrMany } from "@k8slens/utilities";
import { cssNames, isDefined, iter } from "@k8slens/utilities";
import { cssNames } from "@k8slens/utilities";
import { computeDefaultShortName } from "../../../common/catalog/helpers";
export interface AvatarProps {
title: string;
@@ -28,40 +28,6 @@ export interface AvatarProps {
"data-testid"?: string;
}
function getNameParts(name: string): string[] {
const byWhitespace = name.split(/\s+/);
if (byWhitespace.length > 1) {
return byWhitespace;
}
const byDashes = name.split(/[-_]+/);
if (byDashes.length > 1) {
return byDashes;
}
return name.split(/@+/);
}
function getLabelFromTitle(title: string) {
if (!title) {
return "??";
}
const [rawFirst, rawSecond, rawThird] = getNameParts(title);
const splitter = new GraphemeSplitter();
const first = splitter.iterateGraphemes(rawFirst);
const second = rawSecond ? splitter.iterateGraphemes(rawSecond): first;
const third = rawThird ? splitter.iterateGraphemes(rawThird) : iter.newEmpty();
return [
...iter.take(first, 1),
...iter.take(second, 1),
...iter.take(third, 1),
].filter(isDefined).join("");
}
export const Avatar = ({
title,
variant = "rounded",
@@ -104,6 +70,6 @@ export const Avatar = ({
alt={title}
/>
)
: children || getLabelFromTitle(title)}
: children || computeDefaultShortName(title)}
</div>
);
@@ -7,15 +7,15 @@ import React from "react";
import { observer } from "mobx-react";
import { Input } from "../input";
import { isUrl } from "../input/input_validators";
import type { WeblinkStore } from "../../../common/weblinks-store/weblink-store";
import { computed, makeObservable, observable } from "mobx";
import { withInjectables } from "@ogre-tools/injectable-react";
import commandOverlayInjectable from "../command-palette/command-overlay.injectable";
import weblinkStoreInjectable from "../../../common/weblinks-store/weblink-store.injectable";
import type { AddWeblink } from "../../../features/weblinks/common/add.injectable";
import addWeblinkInjectable from "../../../features/weblinks/common/add.injectable";
interface Dependencies {
closeCommandOverlay: () => void;
weblinkStore: WeblinkStore;
addWeblink: AddWeblink;
}
@@ -42,7 +42,7 @@ class NonInjectedWeblinkAddCommand extends React.Component<Dependencies> {
}
onSubmit(name: string) {
this.props.weblinkStore.add({
this.props.addWeblink({
name: name || this.url,
url: this.url,
});
@@ -97,6 +97,6 @@ export const WeblinkAddCommand = withInjectables<Dependencies>(NonInjectedWeblin
getProps: (di, props) => ({
...props,
closeCommandOverlay: di.inject(commandOverlayInjectable).close,
weblinkStore: di.inject(weblinkStoreInjectable),
addWeblink: di.inject(addWeblinkInjectable),
}),
});
@@ -3,8 +3,8 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import getClusterByIdInjectable from "../../../common/cluster-store/get-by-id.injectable";
import loggerInjectable from "../../../common/logger.injectable";
import getClusterByIdInjectable from "../../../features/cluster/storage/common/get-by-id.injectable";
import { ClusterFrameHandler } from "./cluster-frame-handler";
import emitClusterVisibilityInjectable from "./emit-cluster-visibility.injectable";
@@ -9,8 +9,8 @@ import type { Disposer } from "@k8slens/utilities";
import { onceDefined } from "@k8slens/utilities";
import assert from "assert";
import type { Logger } from "../../../common/logger";
import type { GetClusterById } from "../../../common/cluster-store/get-by-id.injectable";
import { getClusterFrameUrl } from "../../../common/utils";
import type { GetClusterById } from "../../../features/cluster/storage/common/get-by-id.injectable";
export interface LensView {
isLoaded: boolean;
@@ -18,10 +18,10 @@ import clusterViewRouteParametersInjectable from "./cluster-view-route-parameter
import clusterFrameHandlerInjectable from "./cluster-frame-handler.injectable";
import type { CatalogEntityRegistry } from "../../api/catalog/entity/registry";
import catalogEntityRegistryInjectable from "../../api/catalog/entity/registry.injectable";
import type { GetClusterById } from "../../../common/cluster-store/get-by-id.injectable";
import getClusterByIdInjectable from "../../../common/cluster-store/get-by-id.injectable";
import type { RequestClusterActivation } from "../../../features/cluster/activation/common/request-token";
import requestClusterActivationInjectable from "../../../features/cluster/activation/renderer/request-activation.injectable";
import type { GetClusterById } from "../../../features/cluster/storage/common/get-by-id.injectable";
import getClusterByIdInjectable from "../../../features/cluster/storage/common/get-by-id.injectable";
interface Dependencies {
clusterId: IComputedValue<string>;
@@ -15,9 +15,7 @@ import { Dialog } from "../dialog";
import { Icon } from "../icon";
import { Select } from "../select";
import { Checkbox } from "../checkbox";
import type { HotbarStore } from "../../../common/hotbars/store";
import { withInjectables } from "@ogre-tools/injectable-react";
import hotbarStoreInjectable from "../../../common/hotbars/store.injectable";
import type { DeleteClusterDialogState } from "./state.injectable";
import deleteClusterDialogStateInjectable from "./state.injectable";
import type { RequestSetClusterAsDeleting } from "../../../features/cluster/delete-dialog/renderer/request-set-as-deleting.injectable";
@@ -32,16 +30,18 @@ import showErrorNotificationInjectable from "../notifications/show-error-notific
import { isCurrentContext } from "./is-current-context";
import type { IsInLocalKubeconfig } from "./is-in-local-kubeconfig.injectable";
import isInLocalKubeconfigInjectable from "./is-in-local-kubeconfig.injectable";
import type { RemoveEntityFromAllHotbars } from "../../../features/hotbar/storage/common/remove-entity-from-all.injectable";
import removeEntityFromAllHotbarsInjectable from "../../../features/hotbar/storage/common/remove-entity-from-all.injectable";
interface Dependencies {
state: IObservableValue<DeleteClusterDialogState | undefined>;
hotbarStore: HotbarStore;
requestSetClusterAsDeleting: RequestSetClusterAsDeleting;
requestDeleteCluster: RequestDeleteCluster;
requestClearClusterAsDeleting: RequestClearClusterAsDeleting;
showErrorNotification: ShowNotification;
saveKubeconfig: SaveKubeconfig;
isInLocalKubeconfig: IsInLocalKubeconfig;
removeEntityFromAllHotbars: RemoveEntityFromAllHotbars;
}
@observer
@@ -65,7 +65,7 @@ class NonInjectedDeleteClusterDialog extends React.Component<Dependencies> {
try {
await this.props.saveKubeconfig(config, cluster.kubeConfigPath.get());
this.props.hotbarStore.removeAllHotbarItems(cluster.id);
this.props.removeEntityFromAllHotbars(cluster.id);
await this.props.requestDeleteCluster(cluster.id);
} catch(error) {
this.props.showErrorNotification(`Cannot remove cluster, failed to process config file. ${error}`);
@@ -267,7 +267,6 @@ class NonInjectedDeleteClusterDialog extends React.Component<Dependencies> {
export const DeleteClusterDialog = withInjectables<Dependencies>(NonInjectedDeleteClusterDialog, {
getProps: (di) => ({
hotbarStore: di.inject(hotbarStoreInjectable),
state: di.inject(deleteClusterDialogStateInjectable),
requestSetClusterAsDeleting: di.inject(requestSetClusterAsDeletingInjectable),
requestClearClusterAsDeleting: di.inject(requestClearClusterAsDeletingInjectable),
@@ -275,5 +274,6 @@ export const DeleteClusterDialog = withInjectables<Dependencies>(NonInjectedDele
saveKubeconfig: di.inject(saveKubeconfigInjectable),
showErrorNotification: di.inject(showErrorNotificationInjectable),
isInLocalKubeconfig: di.inject(isInLocalKubeconfigInjectable),
removeEntityFromAllHotbars: di.inject(removeEntityFromAllHotbarsInjectable),
}),
});
@@ -15,7 +15,6 @@ import { disposeOnUnmount, observer } from "mobx-react";
import moment from "moment-timezone";
import type { Align, ListOnScrollProps } from "react-window";
import { SearchStore } from "../../../search-store/search-store";
import type { UserStore } from "../../../../common/user-store";
import { array, cssNames } from "@k8slens/utilities";
import type { VirtualListRef } from "../../virtual-list";
import { VirtualList } from "../../virtual-list";
@@ -23,8 +22,9 @@ import { ToBottom } from "./to-bottom";
import type { LogTabViewModel } from "../logs/logs-view-model";
import { Spinner } from "../../spinner";
import { withInjectables } from "@ogre-tools/injectable-react";
import userStoreInjectable from "../../../../common/user-store/user-store.injectable";
import autoBindReact from "auto-bind/react";
import type { UserPreferencesState } from "../../../../features/user-preferences/common/state.injectable";
import userPreferencesStateInjectable from "../../../../features/user-preferences/common/state.injectable";
export interface LogListProps {
model: LogTabViewModel;
@@ -37,7 +37,7 @@ export interface LogListRef {
}
interface Dependencies {
userStore: UserStore;
state: UserPreferencesState;
}
@observer
@@ -129,7 +129,7 @@ class NonForwardedLogList extends React.Component<Dependencies & LogListProps &
return this.props.model.timestampSplitLogs
.get()
.map(([logTimestamp, log]) => (`${logTimestamp && moment.tz(logTimestamp, this.props.userStore.localeTimezone).format()}${log}`));
.map(([logTimestamp, log]) => (`${logTimestamp && moment.tz(logTimestamp, this.props.state.localeTimezone).format()}${log}`));
}
/**
@@ -283,7 +283,7 @@ class NonForwardedLogList extends React.Component<Dependencies & LogListProps &
const InjectedNonForwardedLogList = withInjectables<Dependencies, LogListProps & { innerRef: ForwardedRef<LogListRef> }>(NonForwardedLogList, {
getProps: (di, props) => ({
...props,
userStore: di.inject(userStoreInjectable),
state: di.inject(userPreferencesStateInjectable),
}),
});
@@ -8,13 +8,12 @@ import { Terminal } from "./terminal";
import type { TabId } from "../dock/store";
import type { TerminalApi } from "../../../api/terminal-api";
import terminalSpawningPoolInjectable from "./terminal-spawning-pool.injectable";
import terminalConfigInjectable from "../../../../common/user-store/terminal-config.injectable";
import terminalCopyOnSelectInjectable
from "../../../../common/user-store/terminal-copy-on-select.injectable";
import isMacInjectable from "../../../../common/vars/is-mac.injectable";
import openLinkInBrowserInjectable from "../../../../common/utils/open-link-in-browser.injectable";
import xtermColorThemeInjectable from "../../../themes/terminal-colors.injectable";
import loggerInjectable from "../../../../common/logger.injectable";
import terminalConfigInjectable from "../../../../features/user-preferences/common/terminal-config.injectable";
import terminalCopyOnSelectInjectable from "../../../../features/user-preferences/common/terminal-copy-on-select.injectable";
export type CreateTerminal = (tabId: TabId, api: TerminalApi) => Terminal;
@@ -14,11 +14,11 @@ import { disposer } from "@k8slens/utilities";
import { once } from "lodash";
import { clipboard } from "electron";
import type { Logger } from "../../../../common/logger";
import type { TerminalConfig } from "../../../../common/user-store/preferences-helpers";
import assert from "assert";
import { TerminalChannels } from "../../../../common/terminal/channels";
import { LinkProvider } from "xterm-link-provider";
import type { OpenLinkInBrowser } from "../../../../common/utils/open-link-in-browser.injectable";
import type { TerminalConfig } from "../../../../features/user-preferences/common/preferences-helpers";
export interface TerminalDependencies {
readonly spawningPool: HTMLElement;
@@ -0,0 +1,97 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<HotbarRemoveCommand /> renders w/o errors 1`] = `
<div>
<div
class="Select theme-dark css-b62m3t-container"
>
<span
class="css-1f43avz-a11yText-A11yText"
id="react-select-2-live-region"
>
<span
id="aria-selection"
>
option , selected.
</span>
<span
id="aria-context"
>
2 results available. Use Up and Down to choose options, press Enter to select the currently focused option, press Escape to exit the menu, press Tab to select the option and exit the menu.
</span>
</span>
<span
aria-atomic="false"
aria-live="polite"
aria-relevant="additions text"
class="css-1f43avz-a11yText-A11yText"
/>
<div
class="Select__control Select__control--is-focused Select__control--menu-is-open css-t3ipsp-control"
>
<div
class="Select__value-container css-1fdsijx-ValueContainer"
>
<div
class="Select__placeholder css-1jqq78o-placeholder"
id="react-select-2-placeholder"
>
Remove hotbar
</div>
<div
class="Select__input-container css-qbdosj-Input"
data-value=""
>
<input
aria-autocomplete="list"
aria-controls="react-select-2-listbox"
aria-describedby="react-select-2-placeholder"
aria-expanded="true"
aria-haspopup="true"
aria-owns="react-select-2-listbox"
autocapitalize="none"
autocomplete="off"
autocorrect="off"
class="Select__input"
id="react-select-2-input"
role="combobox"
spellcheck="false"
style="opacity: 1; width: 100%; grid-area: 1 / 2; min-width: 2px; border: 0px; margin: 0px; outline: 0; padding: 0px;"
tabindex="0"
type="text"
value=""
/>
</div>
</div>
<div
class="Select__indicators css-1hb7zxy-IndicatorsContainer"
/>
</div>
<div
class="theme-dark Select__menu css-1nmdiq5-menu"
id="react-select-2-listbox"
>
<div
class="Select__menu-list css-1n6sfyn-MenuList"
>
<div
aria-disabled="false"
class="Select__option css-10wo9uf-option"
id="react-select-2-option-0"
tabindex="-1"
>
1: default
</div>
<div
aria-disabled="false"
class="Select__option css-10wo9uf-option"
id="react-select-2-option-1"
tabindex="-1"
>
2: non-default
</div>
</div>
</div>
</div>
</div>
`;
@@ -5,75 +5,56 @@
import "@testing-library/jest-dom/extend-expect";
import { HotbarRemoveCommand } from "../hotbar-remove-command";
import type { RenderResult } from "@testing-library/react";
import { fireEvent } from "@testing-library/react";
import React from "react";
import type { DiContainer } from "@ogre-tools/injectable";
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
import type { DiRender } from "../../test-utils/renderFor";
import { renderFor } from "../../test-utils/renderFor";
import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable";
import { ConfirmDialog } from "../../confirm-dialog";
import directoryForUserDataInjectable from "../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import type { HotbarStore } from "../../../../common/hotbars/store";
import storesAndApisCanBeCreatedInjectable from "../../../stores-apis-can-be-created.injectable";
const mockHotbars: Partial<Record<string, any>> = {
"1": {
id: "1",
name: "Default",
items: [],
},
};
import hotbarsStateInjectable from "../../../../features/hotbar/storage/common/state.injectable";
import type { CreateHotbar } from "../../../../features/hotbar/storage/common/create-hotbar.injectable";
import createHotbarInjectable from "../../../../features/hotbar/storage/common/create-hotbar.injectable";
import type { IComputedValue } from "mobx";
import type { Hotbar } from "../../../../features/hotbar/storage/common/hotbar";
import hotbarsInjectable from "../../../../features/hotbar/storage/common/hotbars.injectable";
describe("<HotbarRemoveCommand />", () => {
let di: DiContainer;
let render: DiRender;
let result: RenderResult;
let createHotbar: CreateHotbar;
let hotbars: IComputedValue<Hotbar[]>;
beforeEach(() => {
di = getDiForUnitTesting();
const di = getDiForUnitTesting();
const render = renderFor(di);
di.override(storesAndApisCanBeCreatedInjectable, () => true);
di.override(directoryForUserDataInjectable, () => "/some-directory-for-user-data");
render = renderFor(di);
});
createHotbar = di.inject(createHotbarInjectable);
hotbars = di.inject(hotbarsInjectable);
it("renders w/o errors", () => {
di.override(hotbarStoreInjectable, () => ({
hotbars: [mockHotbars["1"]],
getById: (id: string) => mockHotbars[id],
remove: () => {
},
hotbarIndex: () => 0,
getDisplayLabel: () => "1: Default",
}) as unknown as HotbarStore);
const hotbarsState = di.inject(hotbarsStateInjectable);
const defaultHotbar = createHotbar({ name: "default" });
const nonDefaultHotbar = createHotbar({ name: "non-default" });
const { container } = render(<HotbarRemoveCommand />);
hotbarsState.set(defaultHotbar.id, defaultHotbar);
hotbarsState.set(nonDefaultHotbar.id, nonDefaultHotbar);
expect(container).toBeInstanceOf(HTMLElement);
});
it("calls remove if you click on the entry", () => {
const removeMock = jest.fn();
di.override(hotbarStoreInjectable, () => ({
hotbars: [mockHotbars["1"]],
getById: (id: string) => mockHotbars[id],
remove: removeMock,
hotbarIndex: () => 0,
getDisplayLabel: () => "1: Default",
}) as unknown as HotbarStore);
const { getByText } = render(
result = render((
<>
<HotbarRemoveCommand />
<ConfirmDialog />
</>,
);
</>
));
});
fireEvent.click(getByText("1: Default"));
fireEvent.click(getByText("Remove Hotbar"));
it("renders w/o errors", () => {
expect(result.container).toMatchSnapshot();
});
expect(removeMock).toHaveBeenCalled();
it("calls remove if you click on the entry", () => {
fireEvent.click(result.getByText("1: default"));
fireEvent.click(result.getByText("Remove Hotbar"));
expect(hotbars.get().length).toBe(1);
});
});
@@ -7,15 +7,15 @@ import React from "react";
import { observer } from "mobx-react";
import type { InputValidator } from "../input";
import { Input } from "../input";
import type { CreateHotbarData, CreateHotbarOptions } from "../../../common/hotbars/types";
import { withInjectables } from "@ogre-tools/injectable-react";
import commandOverlayInjectable from "../command-palette/command-overlay.injectable";
import uniqueHotbarNameInjectable from "../input/validators/unique-hotbar-name.injectable";
import addHotbarInjectable from "../../../common/hotbars/add-hotbar.injectable";
import type { AddHotbar } from "../../../features/hotbar/storage/common/add.injectable";
import addHotbarInjectable from "../../../features/hotbar/storage/common/add.injectable";
interface Dependencies {
closeCommandOverlay: () => void;
addHotbar: (data: CreateHotbarData, opts: CreateHotbarOptions) => void;
addHotbar: AddHotbar;
uniqueHotbarName: InputValidator<boolean>;
}
@@ -5,7 +5,7 @@
import "./hotbar-menu.scss";
import React from "react";
import React, { useState } from "react";
import { observer } from "mobx-react";
import { HotbarEntityIcon } from "./hotbar-entity-icon";
import type { IClassName } from "@k8slens/utilities";
@@ -16,57 +16,46 @@ import { DragDropContext, Draggable, Droppable, type DropResult } from "react-be
import { HotbarSelector } from "./hotbar-selector";
import { HotbarCell } from "./hotbar-cell";
import { HotbarIcon } from "./hotbar-icon";
import type { HotbarItem } from "../../../common/hotbars/types";
import { defaultHotbarCells } from "../../../common/hotbars/types";
import { action, makeObservable, observable } from "mobx";
import hotbarStoreInjectable from "../../../common/hotbars/store.injectable";
import type { HotbarItem } from "../../../features/hotbar/storage/common/types";
import { defaultHotbarCells } from "../../../features/hotbar/storage/common/types";
import type { IComputedValue } from "mobx";
import { withInjectables } from "@ogre-tools/injectable-react";
import type { HotbarStore } from "../../../common/hotbars/store";
import catalogEntityRegistryInjectable from "../../api/catalog/entity/registry.injectable";
import type { Hotbar } from "../../../features/hotbar/storage/common/hotbar";
import activeHotbarInjectable from "../../../features/hotbar/storage/common/active.injectable";
export interface HotbarMenuProps {
className?: IClassName;
}
interface Dependencies {
hotbarStore: HotbarStore;
activeHotbar: IComputedValue<Hotbar | undefined>;
entityRegistry: CatalogEntityRegistry;
}
@observer
class NonInjectedHotbarMenu extends React.Component<Dependencies & HotbarMenuProps> {
@observable draggingOver = false;
const NonInjectedHotbarMenu = observer((props: Dependencies & HotbarMenuProps) => {
const {
activeHotbar,
entityRegistry,
className,
} = props;
constructor(props: Dependencies & HotbarMenuProps) {
super(props);
makeObservable(this);
}
const [draggingOver, setDraggingOver] = useState(false);
const hotbar = activeHotbar.get();
get hotbar() {
return this.props.hotbarStore.getActive();
}
getEntity(item: HotbarItem | null) {
const hotbar = this.props.hotbarStore.getActive();
if (!hotbar || !item) {
return null;
const getEntity = (item: HotbarItem | null) => {
if (!item) {
return undefined;
}
return this.props.entityRegistry.getById(item.entity.uid) ?? null;
}
return entityRegistry.getById(item.entity.uid);
};
const onDragStart = () => setDraggingOver(true);
const onDragEnd = (result: DropResult) => {
setDraggingOver(false);
@action
onDragStart() {
this.draggingOver = true;
}
@action
onDragEnd(result: DropResult) {
const { source, destination } = result;
this.draggingOver = false;
if (!destination) { // Dropped outside of the list
return;
}
@@ -74,135 +63,115 @@ class NonInjectedHotbarMenu extends React.Component<Dependencies & HotbarMenuPro
const from = parseInt(source.droppableId);
const to = parseInt(destination.droppableId);
this.props.hotbarStore.restackItems(from, to);
}
removeItem = (uid: string) => {
const hotbar = this.props.hotbarStore;
hotbar.removeFromHotbar(uid);
hotbar?.restack(from, to);
};
addItem = (entity: CatalogEntity, index = -1) => {
const hotbar = this.props.hotbarStore;
hotbar.addToHotbar(entity, index);
const removeItem = (entityId: string) => {
hotbar?.removeEntity(entityId);
};
getMoveAwayDirection(entityId: string | undefined | null, cellIndex: number) {
if (!entityId) {
const addItem = (entity: CatalogEntity) => {
hotbar?.addEntity(entity);
};
const getMoveAwayDirection = (entityId: string | undefined | null, cellIndex: number) => {
if (!entityId || !hotbar) {
return "animateDown";
}
const draggableItemIndex = this.hotbar.items.findIndex(item => item?.entity.uid == entityId);
const draggableItemIndex = hotbar.items.findIndex(item => item?.entity.uid == entityId);
return draggableItemIndex > cellIndex ? "animateDown" : "animateUp";
}
};
renderGrid() {
return this.hotbar.items.map((item, index) => {
const entity = this.getEntity(item);
return (
<Droppable droppableId={`${index}`} key={index}>
{(provided, snapshot) => (
<HotbarCell
index={index}
key={entity ? entity.getId() : `cell${index}`}
innerRef={provided.innerRef}
className={cssNames(
{
isDraggingOver: snapshot.isDraggingOver,
isDraggingOwner: snapshot.draggingOverWith == entity?.getId(),
},
this.getMoveAwayDirection(snapshot.draggingOverWith, index),
)}
{...provided.droppableProps}
>
{item && (
<Draggable
draggableId={item.entity.uid}
key={item.entity.uid}
index={0}
>
{(provided, snapshot) => {
const style = {
zIndex: defaultHotbarCells - index,
position: "absolute",
...provided.draggableProps.style,
} as React.CSSProperties;
return (
<div
key={item.entity.uid}
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={style}
>
{entity ? (
<HotbarEntityIcon
key={index}
index={index}
entity={entity}
onClick={() => this.props.entityRegistry.onRun(entity)}
className={cssNames({ isDragging: snapshot.isDragging })}
remove={this.removeItem}
add={this.addItem}
size={40}
/>
) : (
<HotbarIcon
uid={`hotbar-icon-${item.entity.uid}`}
title={item.entity.name}
source={item.entity.source ?? "local"}
tooltip={`${item.entity.name} (${item.entity.source})`}
menuItems={[
{
title: "Remove from Hotbar",
onClick: () => this.removeItem(item.entity.uid),
},
]}
disabled
size={40}
/>
)}
</div>
);
}}
</Draggable>
)}
{provided.placeholder}
</HotbarCell>
)}
</Droppable>
);
});
}
render() {
const { className, hotbarStore } = this.props;
const hotbar = hotbarStore.getActive();
const renderGrid = () => hotbar?.items.map((item, index) => {
const entity = getEntity(item);
return (
<div className={cssNames("HotbarMenu flex column", { draggingOver: this.draggingOver }, className)}>
<div className="HotbarItems flex column gaps">
<DragDropContext
onDragStart={() => this.onDragStart()}
onDragEnd={(result) => this.onDragEnd(result)}>
{this.renderGrid()}
</DragDropContext>
</div>
<HotbarSelector hotbar={hotbar} />
</div>
<Droppable droppableId={`${index}`} key={index}>
{(provided, snapshot) => (
<HotbarCell
index={index}
key={entity ? entity.getId() : `cell${index}`}
innerRef={provided.innerRef}
className={cssNames(
{
isDraggingOver: snapshot.isDraggingOver,
isDraggingOwner: snapshot.draggingOverWith == entity?.getId(),
},
getMoveAwayDirection(snapshot.draggingOverWith, index),
)}
{...provided.droppableProps}
>
{item && (
<Draggable
draggableId={item.entity.uid}
key={item.entity.uid}
index={0}
>
{(provided, snapshot) => (
<div
key={item.entity.uid}
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={{
zIndex: defaultHotbarCells - index,
position: "absolute" as const,
...provided.draggableProps.style,
}}
>
{entity ? (
<HotbarEntityIcon
key={index}
index={index}
entity={entity}
onClick={() => entityRegistry.onRun(entity)}
className={cssNames({ isDragging: snapshot.isDragging })}
remove={removeItem}
add={addItem}
size={40} />
) : (
<HotbarIcon
uid={`hotbar-icon-${item.entity.uid}`}
title={item.entity.name}
source={item.entity.source ?? "local"}
tooltip={`${item.entity.name} (${item.entity.source})`}
menuItems={[
{
title: "Remove from Hotbar",
onClick: () => removeItem(item.entity.uid),
},
]}
disabled
size={40} />
)}
</div>
)}
</Draggable>
)}
{provided.placeholder}
</HotbarCell>
)}
</Droppable>
);
}
}
});
return (
<div className={cssNames("HotbarMenu flex column", { draggingOver }, className)}>
<div className="HotbarItems flex column gaps">
<DragDropContext
onDragStart={() => onDragStart()}
onDragEnd={(result) => onDragEnd(result)}>
{renderGrid()}
</DragDropContext>
</div>
<HotbarSelector />
</div>
);
});
export const HotbarMenu = withInjectables<Dependencies, HotbarMenuProps>(NonInjectedHotbarMenu, {
getProps: (di, props) => ({
...props,
hotbarStore: di.inject(hotbarStoreInjectable),
entityRegistry: di.inject(catalogEntityRegistryInjectable),
activeHotbar: di.inject(activeHotbarInjectable),
}),
});
@@ -6,23 +6,32 @@
import React from "react";
import { observer } from "mobx-react";
import { Select } from "../select";
import hotbarStoreInjectable from "../../../common/hotbars/store.injectable";
import { withInjectables } from "@ogre-tools/injectable-react";
import commandOverlayInjectable from "../command-palette/command-overlay.injectable";
import type { HotbarStore } from "../../../common/hotbars/store";
import type { OpenConfirmDialog } from "../confirm-dialog/open.injectable";
import openConfirmDialogInjectable from "../confirm-dialog/open.injectable";
import type { IComputedValue } from "mobx";
import type { Hotbar } from "../../../features/hotbar/storage/common/hotbar";
import type { ComputeHotbarDisplayLabel } from "../../../features/hotbar/storage/common/compute-display-label.injectable";
import computeHotbarDisplayLabelInjectable from "../../../features/hotbar/storage/common/compute-display-label.injectable";
import hotbarsInjectable from "../../../features/hotbar/storage/common/hotbars.injectable";
import type { RemoveHotbar } from "../../../features/hotbar/storage/common/remove.injectable";
import removeHotbarInjectable from "../../../features/hotbar/storage/common/remove.injectable";
interface Dependencies {
closeCommandOverlay: () => void;
openConfirmDialog: OpenConfirmDialog;
hotbarStore: HotbarStore;
hotbars: IComputedValue<Hotbar[]>;
computeHotbarDisplayLabel: ComputeHotbarDisplayLabel;
removeHotbar: RemoveHotbar;
}
const NonInjectedHotbarRemoveCommand = observer(({
closeCommandOverlay,
hotbarStore,
openConfirmDialog,
hotbars,
computeHotbarDisplayLabel,
removeHotbar,
}: Dependencies) => (
<Select
menuPortalTarget={null}
@@ -38,28 +47,28 @@ const NonInjectedHotbarRemoveCommand = observer(({
primary: false,
accent: true,
},
ok: () => hotbarStore.remove(option.value),
ok: () => removeHotbar(option.value),
message: (
<div className="confirm flex column gaps">
<p>
Are you sure you want remove hotbar
{" "}
<b>
{option.value.name}
{option.value.name.get()}
</b>
?
</p>
</div>
),
});
} }
}}
components={{ DropdownIndicator: null, IndicatorSeparator: null }}
menuIsOpen={true}
options={(
hotbarStore.hotbars
hotbars.get()
.map(hotbar => ({
value: hotbar,
label: hotbarStore.getDisplayLabel(hotbar),
label: computeHotbarDisplayLabel(hotbar),
}))
)}
autoFocus={true}
@@ -72,7 +81,9 @@ export const HotbarRemoveCommand = withInjectables<Dependencies>(NonInjectedHotb
getProps: (di, props) => ({
...props,
closeCommandOverlay: di.inject(commandOverlayInjectable).close,
hotbarStore: di.inject(hotbarStoreInjectable),
openConfirmDialog: di.inject(openConfirmDialogInjectable),
computeHotbarDisplayLabel: di.inject(computeHotbarDisplayLabelInjectable),
hotbars: di.inject(hotbarsInjectable),
removeHotbar: di.inject(removeHotbarInjectable),
}),
});
@@ -6,36 +6,46 @@
import React, { useState } from "react";
import { observer } from "mobx-react";
import { Select } from "../select";
import hotbarStoreInjectable from "../../../common/hotbars/store.injectable";
import type { InputValidator } from "../input";
import { Input } from "../input";
import { withInjectables } from "@ogre-tools/injectable-react";
import commandOverlayInjectable from "../command-palette/command-overlay.injectable";
import uniqueHotbarNameInjectable from "../input/validators/unique-hotbar-name.injectable";
import type { HotbarStore } from "../../../common/hotbars/store";
import type { IComputedValue } from "mobx";
import { action } from "mobx";
import type { Hotbar } from "../../../features/hotbar/storage/common/hotbar";
import type { GetHotbarById } from "../../../features/hotbar/storage/common/get-by-id.injectable";
import getHotbarByIdInjectable from "../../../features/hotbar/storage/common/get-by-id.injectable";
import hotbarsInjectable from "../../../features/hotbar/storage/common/hotbars.injectable";
import type { ComputeHotbarDisplayLabel } from "../../../features/hotbar/storage/common/compute-display-label.injectable";
import computeHotbarDisplayLabelInjectable from "../../../features/hotbar/storage/common/compute-display-label.injectable";
interface Dependencies {
closeCommandOverlay: () => void;
hotbarStore: HotbarStore;
getHotbarById: GetHotbarById;
computeHotbarDisplayLabel: ComputeHotbarDisplayLabel;
uniqueHotbarName: InputValidator<false>;
hotbars: IComputedValue<Hotbar[]>;
}
const NonInjectedHotbarRenameCommand = observer(({
closeCommandOverlay,
hotbarStore,
getHotbarById,
computeHotbarDisplayLabel,
uniqueHotbarName,
hotbars,
}: Dependencies) => {
const [hotbarId, setHotbarId] = useState("");
const [hotbarName, setHotbarName] = useState("");
const onSubmit = (name: string) => {
const onSubmit = action((name: string) => {
if (!name.trim()) {
return;
}
hotbarStore.setHotbarName(hotbarId, name);
getHotbarById(hotbarId)?.name.set(name);
closeCommandOverlay();
};
});
if (hotbarId) {
return (
@@ -65,16 +75,16 @@ const NonInjectedHotbarRenameCommand = observer(({
onChange={(option) => {
if (option) {
setHotbarId(option.value.id);
setHotbarName(option.value.name);
setHotbarName(option.value.name.get());
}
}}
components={{ DropdownIndicator: null, IndicatorSeparator: null }}
menuIsOpen={true}
options={(
hotbarStore.hotbars
hotbars.get()
.map(hotbar => ({
value: hotbar,
label: hotbarStore.getDisplayLabel(hotbar),
label: computeHotbarDisplayLabel(hotbar),
}))
)}
autoFocus={true}
@@ -86,9 +96,11 @@ const NonInjectedHotbarRenameCommand = observer(({
export const HotbarRenameCommand = withInjectables<Dependencies>(NonInjectedHotbarRenameCommand, {
getProps: (di, props) => ({
closeCommandOverlay: di.inject(commandOverlayInjectable).close,
hotbarStore: di.inject(hotbarStoreInjectable),
uniqueHotbarName: di.inject(uniqueHotbarNameInjectable),
...props,
closeCommandOverlay: di.inject(commandOverlayInjectable).close,
uniqueHotbarName: di.inject(uniqueHotbarNameInjectable),
getHotbarById: di.inject(getHotbarByIdInjectable),
hotbars: di.inject(hotbarsInjectable),
computeHotbarDisplayLabel: di.inject(computeHotbarDisplayLabelInjectable),
}),
});
@@ -7,28 +7,40 @@ import styles from "./hotbar-selector.module.scss";
import React, { useRef, useState } from "react";
import { Icon } from "../icon";
import { Badge } from "../badge";
import hotbarStoreInjectable from "../../../common/hotbars/store.injectable";
import { HotbarSwitchCommand } from "./hotbar-switch-command";
import { Tooltip, TooltipPosition } from "../tooltip";
import { observer } from "mobx-react";
import type { Hotbar } from "../../../common/hotbars/types";
import { withInjectables } from "@ogre-tools/injectable-react";
import commandOverlayInjectable from "../command-palette/command-overlay.injectable";
import { cssNames } from "@k8slens/utilities";
import type { HotbarStore } from "../../../common/hotbars/store";
import type { IComputedValue } from "mobx";
import activeHotbarInjectable from "../../../features/hotbar/storage/common/active.injectable";
import type { SwitchToPreviousHotbar } from "../../../features/hotbar/storage/common/switch-to-previous.injectable";
import type { SwitchToNextHotbar } from "../../../features/hotbar/storage/common/switch-to-next.injectable";
import switchToNextHotbarInjectable from "../../../features/hotbar/storage/common/switch-to-next.injectable";
import switchToPreviousHotbarInjectable from "../../../features/hotbar/storage/common/switch-to-previous.injectable";
import type { Hotbar } from "../../../features/hotbar/storage/common/hotbar";
import type { ComputeDisplayIndex } from "../../../features/hotbar/storage/common/compute-display-index.injectable";
import computeDisplayIndexInjectable from "../../../features/hotbar/storage/common/compute-display-index.injectable";
interface Dependencies {
hotbarStore: HotbarStore;
activeHotbar: IComputedValue<Hotbar | undefined>;
openCommandOverlay: (component: React.ReactElement) => void;
switchToPreviousHotbar: SwitchToPreviousHotbar;
switchToNextHotbar: SwitchToNextHotbar;
computeDisplayIndex: ComputeDisplayIndex;
}
export interface HotbarSelectorProps extends Partial<Dependencies> {
hotbar: Hotbar;
}
const NonInjectedHotbarSelector = observer(({ hotbar, hotbarStore, openCommandOverlay }: HotbarSelectorProps & Dependencies) => {
const NonInjectedHotbarSelector = observer(({
activeHotbar,
openCommandOverlay,
switchToNextHotbar,
switchToPreviousHotbar,
computeDisplayIndex,
}: Dependencies) => {
const [tooltipVisible, setTooltipVisible] = useState(false);
const tooltipTimeout = useRef<number>();
const hotbar = activeHotbar.get();
function clearTimer() {
clearTimeout(tooltipTimeout.current);
@@ -42,12 +54,12 @@ const NonInjectedHotbarSelector = observer(({ hotbar, hotbarStore, openCommandOv
function onPrevClick() {
onTooltipShow();
hotbarStore.switchToPrevious();
switchToPreviousHotbar();
}
function onNextClick() {
onTooltipShow();
hotbarStore.switchToNext();
switchToNextHotbar();
}
function onMouseEvent(event: React.MouseEvent) {
@@ -59,13 +71,13 @@ const NonInjectedHotbarSelector = observer(({ hotbar, hotbarStore, openCommandOv
<div className={styles.HotbarSelector}>
<Icon
material="arrow_left"
className={cssNames(styles.Icon, styles.previous)}
className={cssNames(styles.Icon)}
onClick={onPrevClick}/>
<div className={styles.HotbarIndex}>
<Badge
id="hotbarIndex"
small
label={hotbarStore.getDisplayIndex(hotbarStore.getActive())}
label={hotbar ? computeDisplayIndex(hotbar.id) : "??"}
onClick={() => openCommandOverlay(<HotbarSwitchCommand />)}
className={styles.Badge}
onMouseEnter={onMouseEvent}
@@ -76,7 +88,7 @@ const NonInjectedHotbarSelector = observer(({ hotbar, hotbarStore, openCommandOv
targetId="hotbarIndex"
preferredPositions={[TooltipPosition.TOP, TooltipPosition.TOP_LEFT]}
>
{hotbar.name}
{hotbar?.name}
</Tooltip>
</div>
<Icon
@@ -88,10 +100,13 @@ const NonInjectedHotbarSelector = observer(({ hotbar, hotbarStore, openCommandOv
);
});
export const HotbarSelector = withInjectables<Dependencies, HotbarSelectorProps>(NonInjectedHotbarSelector, {
export const HotbarSelector = withInjectables<Dependencies>(NonInjectedHotbarSelector, {
getProps: (di, props) => ({
hotbarStore: di.inject(hotbarStoreInjectable),
openCommandOverlay: di.inject(commandOverlayInjectable).open,
...props,
openCommandOverlay: di.inject(commandOverlayInjectable).open,
activeHotbar: di.inject(activeHotbarInjectable),
switchToNextHotbar: di.inject(switchToNextHotbarInjectable),
switchToPreviousHotbar: di.inject(switchToPreviousHotbarInjectable),
computeDisplayIndex: di.inject(computeDisplayIndexInjectable),
}),
});
@@ -6,21 +6,28 @@
import React from "react";
import { observer } from "mobx-react";
import { Select } from "../select";
import hotbarStoreInjectable from "../../../common/hotbars/store.injectable";
import type { CommandOverlay } from "../command-palette";
import { HotbarAddCommand } from "./hotbar-add-command";
import { HotbarRemoveCommand } from "./hotbar-remove-command";
import { HotbarRenameCommand } from "./hotbar-rename-command";
import { withInjectables } from "@ogre-tools/injectable-react";
import commandOverlayInjectable from "../command-palette/command-overlay.injectable";
import type { HotbarStore } from "../../../common/hotbars/store";
import type { SetAsActiveHotbar } from "../../../features/hotbar/storage/common/set-as-active.injectable";
import type { IComputedValue } from "mobx";
import type { Hotbar } from "../../../features/hotbar/storage/common/hotbar";
import type { ComputeHotbarDisplayLabel } from "../../../features/hotbar/storage/common/compute-display-label.injectable";
import computeHotbarDisplayLabelInjectable from "../../../features/hotbar/storage/common/compute-display-label.injectable";
import hotbarsInjectable from "../../../features/hotbar/storage/common/hotbars.injectable";
import setAsActiveHotbarInjectable from "../../../features/hotbar/storage/common/set-as-active.injectable";
const hotbarAddAction = Symbol("hotbar-add");
const hotbarRemoveAction = Symbol("hotbar-remove");
const hotbarRenameAction = Symbol("hotbar-rename");
interface Dependencies {
hotbarStore: HotbarStore;
setAsActiveHotbar: SetAsActiveHotbar;
computeHotbarDisplayLabel: ComputeHotbarDisplayLabel;
hotbars: IComputedValue<Hotbar[]>;
commandOverlay: CommandOverlay;
}
@@ -29,7 +36,9 @@ function ignoreIf<T>(check: boolean, menuItems: T[]): T[] {
}
const NonInjectedHotbarSwitchCommand = observer(({
hotbarStore,
setAsActiveHotbar,
computeHotbarDisplayLabel,
hotbars,
commandOverlay,
}: Dependencies) => (
<Select
@@ -50,22 +59,22 @@ const NonInjectedHotbarSwitchCommand = observer(({
return commandOverlay.open(<HotbarRenameCommand />);
}
} else {
hotbarStore.setActiveHotbar(option.value);
setAsActiveHotbar(option.value);
commandOverlay.close();
}
}}
components={{ DropdownIndicator: null, IndicatorSeparator: null }}
menuIsOpen={true}
options={[
...hotbarStore.hotbars.map(hotbar => ({
...hotbars.get().map(hotbar => ({
value: hotbar,
label: hotbarStore.getDisplayLabel(hotbar),
label: computeHotbarDisplayLabel(hotbar),
})),
{
value: hotbarAddAction,
label: "Add hotbar ...",
},
...ignoreIf(hotbarStore.hotbars.length > 1, [
...ignoreIf(hotbars.get().length > 1, [
{
value: hotbarRemoveAction,
label: "Remove hotbar ...",
@@ -85,8 +94,10 @@ const NonInjectedHotbarSwitchCommand = observer(({
export const HotbarSwitchCommand = withInjectables<Dependencies>(NonInjectedHotbarSwitchCommand, {
getProps: (di, props) => ({
hotbarStore: di.inject(hotbarStoreInjectable),
commandOverlay: di.inject(commandOverlayInjectable),
...props,
commandOverlay: di.inject(commandOverlayInjectable),
computeHotbarDisplayLabel: di.inject(computeHotbarDisplayLabelInjectable),
hotbars: di.inject(hotbarsInjectable),
setAsActiveHotbar: di.inject(setAsActiveHotbarInjectable),
}),
});
@@ -4,19 +4,19 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable";
import findHotbarByNameInjectable from "../../../../features/hotbar/storage/common/find-by-name.injectable";
import { inputValidator } from "../input_validators";
const uniqueHotbarNameInjectable = getInjectable({
id: "unique-hotbar-name",
instantiate: di => {
const store = di.inject(hotbarStoreInjectable);
const findHotbarByName = di.inject(findHotbarByNameInjectable);
return inputValidator({
condition: ({ required }) => required,
message: () => "Hotbar with this name already exists",
validate: value => !store.findByName(value),
validate: value => !findHotbarByName(value),
});
},
});
@@ -25,15 +25,17 @@ import type { LensTheme } from "../../themes/lens-theme";
import { MenuActions } from "../menu/menu-actions";
import { MenuItem } from "../menu";
import { Checkbox } from "../checkbox";
import type { UserStore } from "../../../common/user-store";
import type { ItemListStore } from "./list-layout";
import { withInjectables } from "@ogre-tools/injectable-react";
import userStoreInjectable from "../../../common/user-store/user-store.injectable";
import pageFiltersStoreInjectable from "./page-filters/store.injectable";
import type { OpenConfirmDialog } from "../confirm-dialog/open.injectable";
import openConfirmDialogInjectable from "../confirm-dialog/open.injectable";
import activeThemeInjectable from "../../themes/active.injectable";
import autoBindReact from "auto-bind/react";
import type { ToggleTableColumnVisibility } from "../../../features/user-preferences/common/toggle-table-column-visibility.injectable";
import toggleTableColumnVisibilityInjectable from "../../../features/user-preferences/common/toggle-table-column-visibility.injectable";
import type { IsTableColumnHidden } from "../../../features/user-preferences/common/is-table-column-hidden.injectable";
import isTableColumnHiddenInjectable from "../../../features/user-preferences/common/is-table-column-hidden.injectable";
export interface ItemListLayoutContentProps<Item extends ItemObject, PreLoadStores extends boolean> {
getFilters: () => Filter[];
@@ -74,9 +76,10 @@ export interface ItemListLayoutContentProps<Item extends ItemObject, PreLoadStor
interface Dependencies {
activeTheme: IComputedValue<LensTheme>;
userStore: UserStore;
pageFiltersStore: PageFiltersStore;
openConfirmDialog: OpenConfirmDialog;
toggleTableColumnVisibility: ToggleTableColumnVisibility;
isTableColumnHidden: IsTableColumnHidden;
}
@observer
@@ -342,7 +345,7 @@ class NonInjectedItemListLayoutContent<
showColumn({ id: columnId, showWithColumn }: TableCellProps): boolean {
const { tableId, isConfigurable } = this.props;
return !isConfigurable || !tableId || !this.props.userStore.isTableColumnHidden(tableId, columnId, showWithColumn);
return !isConfigurable || !tableId || !this.props.isTableColumnHidden(tableId, columnId, showWithColumn);
}
renderColumnVisibilityMenu(tableId: string) {
@@ -365,7 +368,7 @@ class NonInjectedItemListLayoutContent<
<Checkbox
label={cellProps.title ?? `<${cellProps.className}>`}
value={this.showColumn(cellProps)}
onChange={() => this.props.userStore.toggleTableColumnVisibility(tableId, cellProps.id)}
onChange={() => this.props.toggleTableColumnVisibility(tableId, cellProps.id)}
/>
</MenuItem>
))
@@ -379,8 +382,9 @@ export const ItemListLayoutContent = withInjectables<Dependencies, ItemListLayou
getProps: (di, props) => ({
...props,
activeTheme: di.inject(activeThemeInjectable),
userStore: di.inject(userStoreInjectable),
pageFiltersStore: di.inject(pageFiltersStoreInjectable),
openConfirmDialog: di.inject(openConfirmDialogInjectable),
toggleTableColumnVisibility: di.inject(toggleTableColumnVisibilityInjectable),
isTableColumnHidden: di.inject(isTableColumnHiddenInjectable),
}),
}) as <Item extends ItemObject, PreLoadStores extends boolean>(props: ItemListLayoutContentProps<Item, PreLoadStores>) => React.ReactElement;
@@ -0,0 +1,36 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<SidebarCluster/> renders w/o errors 1`] = `
<div>
<div
class="SidebarCluster"
data-testid="sidebar-cluster-dropdown"
id="cluster-test-uid"
role="menubar"
tabindex="0"
>
<div
class="Avatar rounded avatar"
style="width: 40px; height: 40px; background: rgb(96, 130, 10);"
>
tc
</div>
<div
class="clusterName"
id="tooltip-cluster-test-uid"
>
test-cluster
</div>
<i
class="Icon dropdown material focusable"
>
<span
class="icon"
data-icon-name="arrow_drop_down"
>
arrow_drop_down
</span>
</i>
</div>
</div>
`;
@@ -5,56 +5,47 @@
import React from "react";
import "@testing-library/jest-dom/extend-expect";
import type { RenderResult } from "@testing-library/react";
import { fireEvent } from "@testing-library/react";
import { SidebarCluster } from "../sidebar-cluster";
import { KubernetesCluster } from "../../../../common/catalog-entities";
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
import type { DiRender } from "../../test-utils/renderFor";
import { renderFor } from "../../test-utils/renderFor";
import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable";
import type { HotbarStore } from "../../../../common/hotbars/store";
const clusterEntity = new KubernetesCluster({
metadata: {
uid: "test-uid",
name: "test-cluster",
source: "local",
labels: {},
},
spec: {
kubeconfigPath: "",
kubeconfigContext: "",
},
status: {
phase: "connected",
},
});
describe("<SidebarCluster/>", () => {
let render: DiRender;
let result: RenderResult;
beforeEach(() => {
const di = getDiForUnitTesting();
const render = renderFor(di);
di.override(hotbarStoreInjectable, () => ({
isAddedToActive: () => {},
}) as unknown as HotbarStore);
const clusterEntity = new KubernetesCluster({
metadata: {
uid: "test-uid",
name: "test-cluster",
source: "local",
labels: {},
},
spec: {
kubeconfigPath: "",
kubeconfigContext: "",
},
status: {
phase: "connected",
},
});
render = renderFor(di);
result = render(<SidebarCluster clusterEntity={clusterEntity}/>);
});
it("renders w/o errors", () => {
const { container } = render(<SidebarCluster clusterEntity={clusterEntity}/>);
expect(container).toBeInstanceOf(HTMLElement);
expect(result.container).toMatchSnapshot();
});
it("renders cluster avatar and name", () => {
const { getByText, getAllByText } = render(<SidebarCluster clusterEntity={clusterEntity}/>);
expect(result.getByText("tc")).toBeInTheDocument();
expect(getByText("tc")).toBeInTheDocument();
const v = getAllByText("test-cluster");
const v = result.getAllByText("test-cluster");
expect(v.length).toBeGreaterThan(0);
@@ -64,11 +55,8 @@ describe("<SidebarCluster/>", () => {
});
it("renders cluster menu", () => {
const { getByTestId, getByText } = render(<SidebarCluster clusterEntity={clusterEntity}/>);
const link = getByTestId("sidebar-cluster-dropdown");
fireEvent.click(link);
expect(getByText("Add to Hotbar")).toBeInTheDocument();
fireEvent.click(result.getByTestId("sidebar-cluster-dropdown"));
expect(result.getByText("Add to Hotbar")).toBeInTheDocument();
});
});
@@ -14,8 +14,6 @@ import { Icon } from "../icon";
import { Menu, MenuItem } from "../menu";
import { Tooltip } from "../tooltip";
import { withInjectables } from "@ogre-tools/injectable-react";
import hotbarStoreInjectable from "../../../common/hotbars/store.injectable";
import type { HotbarStore } from "../../../common/hotbars/store";
import { observer } from "mobx-react";
import type { VisitEntityContextMenu } from "../../../common/catalog/visit-entity-context-menu.injectable";
import visitEntityContextMenuInjectable from "../../../common/catalog/visit-entity-context-menu.injectable";
@@ -23,6 +21,8 @@ import type { Navigate } from "../../navigation/navigate.injectable";
import type { NormalizeCatalogEntityContextMenu } from "../../catalog/normalize-menu-item.injectable";
import navigateInjectable from "../../navigation/navigate.injectable";
import normalizeCatalogEntityContextMenuInjectable from "../../catalog/normalize-menu-item.injectable";
import type { ActiveHotbarModel } from "../../../features/hotbar/storage/common/toggling.injectable";
import activeHotbarInjectable from "../../../features/hotbar/storage/common/toggling.injectable";
export interface SidebarClusterProps {
clusterEntity: CatalogEntity | null | undefined;
@@ -31,16 +31,16 @@ export interface SidebarClusterProps {
interface Dependencies {
navigate: Navigate;
normalizeMenuItem: NormalizeCatalogEntityContextMenu;
hotbarStore: HotbarStore;
visitEntityContextMenu: VisitEntityContextMenu;
entityInActiveHotbar: ActiveHotbarModel;
}
const NonInjectedSidebarCluster = observer(({
clusterEntity,
hotbarStore,
visitEntityContextMenu: onContextMenuOpen,
navigate,
normalizeMenuItem,
entityInActiveHotbar,
}: Dependencies & SidebarClusterProps) => {
const [menuItems] = useState(observable.array<CatalogEntityContextMenu>());
const [opened, setOpened] = useState(false );
@@ -61,13 +61,10 @@ const NonInjectedSidebarCluster = observer(({
}
const onMenuOpen = () => {
const isAddedToActive = hotbarStore.isAddedToActive(clusterEntity);
const title = isAddedToActive
const title = entityInActiveHotbar.hasEntity(clusterEntity.getId())
? "Remove from Hotbar"
: "Add to Hotbar";
const onClick = isAddedToActive
? () => hotbarStore.removeFromHotbar(clusterEntity.getId())
: () => hotbarStore.addToHotbar(clusterEntity);
const onClick = () => entityInActiveHotbar.toggleEntity(clusterEntity);
menuItems.replace([{ title, onClick }]);
onContextMenuOpen(clusterEntity, {
@@ -148,9 +145,9 @@ const NonInjectedSidebarCluster = observer(({
export const SidebarCluster = withInjectables<Dependencies, SidebarClusterProps>(NonInjectedSidebarCluster, {
getProps: (di, props) => ({
...props,
hotbarStore: di.inject(hotbarStoreInjectable),
visitEntityContextMenu: di.inject(visitEntityContextMenuInjectable),
navigate: di.inject(navigateInjectable),
normalizeMenuItem: di.inject(normalizeCatalogEntityContextMenuInjectable),
entityInActiveHotbar: di.inject(activeHotbarInjectable),
}),
});
@@ -6,27 +6,27 @@
import React from "react";
import { observer } from "mobx-react";
import moment from "moment-timezone";
import type { UserStore } from "../../../common/user-store";
import { withInjectables } from "@ogre-tools/injectable-react";
import userStoreInjectable from "../../../common/user-store/user-store.injectable";
import type { UserPreferencesState } from "../../../features/user-preferences/common/state.injectable";
import userPreferencesStateInjectable from "../../../features/user-preferences/common/state.injectable";
export interface LocaleDateProps {
date: string;
}
interface Dependencies {
userStore: UserStore;
state: UserPreferencesState;
}
const NonInjectedLocaleDate = observer(({ date, userStore }: LocaleDateProps & Dependencies) => (
const NonInjectedLocaleDate = observer(({ date, state }: LocaleDateProps & Dependencies) => (
<>
{`${moment.tz(date, userStore.localeTimezone).format()}`}
{`${moment.tz(date, state.localeTimezone).format()}`}
</>
));
export const LocaleDate = withInjectables<Dependencies, LocaleDateProps>(NonInjectedLocaleDate, {
getProps: (di, props) => ({
...props,
userStore: di.inject(userStoreInjectable),
state: di.inject(userPreferencesStateInjectable),
}),
});
@@ -13,15 +13,15 @@ import type { MonacoTheme } from "./monaco-themes";
import { type MonacoValidator, monacoValidators } from "./monaco-validators";
import { debounce, merge } from "lodash";
import { cssNames, disposer } from "@k8slens/utilities";
import type { UserStore } from "../../../common/user-store";
import type { LensTheme } from "../../themes/lens-theme";
import { withInjectables } from "@ogre-tools/injectable-react";
import userStoreInjectable from "../../../common/user-store/user-store.injectable";
import activeThemeInjectable from "../../themes/active.injectable";
import getEditorHeightFromLinesCountInjectable from "./get-editor-height-from-lines-number.injectable";
import type { Logger } from "../../../common/logger";
import loggerInjectable from "../../../common/logger.injectable";
import autoBindReact from "auto-bind/react";
import type { UserPreferencesState } from "../../../features/user-preferences/common/state.injectable";
import userPreferencesStateInjectable from "../../../features/user-preferences/common/state.injectable";
export type MonacoEditorId = string;
@@ -45,7 +45,7 @@ export interface MonacoEditorProps {
}
interface Dependencies {
userStore: UserStore;
state: UserPreferencesState;
activeTheme: IComputedValue<LensTheme>;
getEditorHeightFromLinesCount: (linesCount: number) => number;
logger: Logger;
@@ -116,7 +116,7 @@ class NonInjectedMonacoEditor extends React.Component<MonacoEditorProps & Depend
@computed get options(): editor.IStandaloneEditorConstructionOptions {
return merge({},
this.props.userStore.editorConfiguration,
this.props.state.editorConfiguration,
this.props.options,
);
}
@@ -325,15 +325,16 @@ class NonInjectedMonacoEditor extends React.Component<MonacoEditorProps & Depend
}
}
export const MonacoEditor = withInjectables<Dependencies, MonacoEditorProps, MonacoEditorRef>(
React.forwardRef<MonacoEditorRef, MonacoEditorProps & Dependencies>((props, ref) => <NonInjectedMonacoEditor innerRef={ref} {...props} />),
{
getProps: (di, props) => ({
...props,
userStore: di.inject(userStoreInjectable),
activeTheme: di.inject(activeThemeInjectable),
getEditorHeightFromLinesCount: di.inject(getEditorHeightFromLinesCountInjectable),
logger: di.inject(loggerInjectable),
}),
},
);
const ForwardedRefMonacoEditor = React.forwardRef<MonacoEditorRef, MonacoEditorProps & Dependencies>((
(props, ref) => <NonInjectedMonacoEditor innerRef={ref} {...props} />
));
export const MonacoEditor = withInjectables<Dependencies, MonacoEditorProps, MonacoEditorRef>(ForwardedRefMonacoEditor, {
getProps: (di, props) => ({
...props,
state: di.inject(userPreferencesStateInjectable),
activeTheme: di.inject(activeThemeInjectable),
getEditorHeightFromLinesCount: di.inject(getEditorHeightFromLinesCountInjectable),
logger: di.inject(loggerInjectable),
}),
});
@@ -3,7 +3,6 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { Writable } from "type-fest";
import fileSystemProvisionerStoreInjectable from "../../../extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.injectable";
import { lensExtensionDependencies } from "../../../extensions/lens-extension";
import { LensMainExtension } from "../../../extensions/lens-main-extension";
import navigateForExtensionInjectable from "../../../main/start-main-application/lens-window/navigate-for-extension.injectable";
@@ -16,6 +15,7 @@ import catalogEntityRegistryForMainInjectable from "../../../main/catalog/entity
import catalogEntityRegistryForRendererInjectable from "../../api/catalog/entity/registry.injectable";
import type { DiContainer } from "@ogre-tools/injectable";
import loggerInjectable from "../../../common/logger.injectable";
import ensureHashedDirectoryForExtensionInjectable from "../../../extensions/extension-loader/file-system-provisioner-store/ensure-hashed-directory-for-extension.injectable";
export class TestExtensionMain extends LensMainExtension {}
export class TestExtensionRenderer extends LensRendererExtension {}
@@ -47,7 +47,7 @@ export const getMainExtensionFakeWith = (di: DiContainer) => ({ id, name, mainOp
Object.assign(instance, mainOptions);
(instance as Writable<LensMainExtension>)[lensExtensionDependencies] = {
fileSystemProvisionerStore: di.inject(fileSystemProvisionerStoreInjectable),
ensureHashedDirectoryForExtension: di.inject(ensureHashedDirectoryForExtensionInjectable),
entityRegistry: di.inject(catalogEntityRegistryForMainInjectable),
navigate: di.inject(navigateForExtensionInjectable),
logger: di.inject(loggerInjectable),
@@ -78,7 +78,7 @@ export const getRendererExtensionFakeWith = (di: DiContainer) => ({ id, name, re
(instance as Writable<LensRendererExtension>)[lensExtensionDependencies] = {
categoryRegistry: di.inject(catalogCategoryRegistryInjectable),
entityRegistry: di.inject(catalogEntityRegistryForRendererInjectable),
fileSystemProvisionerStore: di.inject(fileSystemProvisionerStoreInjectable),
ensureHashedDirectoryForExtension: di.inject(ensureHashedDirectoryForExtensionInjectable),
getExtensionPageParameters: di.inject(getExtensionPageParametersInjectable),
navigateToRoute: di.inject(navigateToRouteInjectable),
routes: di.inject(routesInjectable),
@@ -7,7 +7,7 @@ import type { Writable } from "type-fest";
import catalogCategoryRegistryInjectable from "../../common/catalog/category-registry.injectable";
import loggerInjectable from "../../common/logger.injectable";
import { createExtensionInstanceInjectionToken } from "../../extensions/extension-loader/create-extension-instance.token";
import fileSystemProvisionerStoreInjectable from "../../extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.injectable";
import ensureHashedDirectoryForExtensionInjectable from "../../extensions/extension-loader/file-system-provisioner-store/ensure-hashed-directory-for-extension.injectable";
import { lensExtensionDependencies } from "../../extensions/lens-extension";
import type { LensRendererExtensionDependencies } from "../../extensions/lens-extension-set-dependencies";
import type { LensRendererExtension } from "../../extensions/lens-renderer-extension";
@@ -22,7 +22,7 @@ const createExtensionInstanceInjectable = getInjectable({
const deps: LensRendererExtensionDependencies = {
categoryRegistry: di.inject(catalogCategoryRegistryInjectable),
entityRegistry: di.inject(catalogEntityRegistryInjectable),
fileSystemProvisionerStore: di.inject(fileSystemProvisionerStoreInjectable),
ensureHashedDirectoryForExtension: di.inject(ensureHashedDirectoryForExtensionInjectable),
getExtensionPageParameters: di.inject(getExtensionPageParametersInjectable),
navigateToRoute: di.inject(navigateToRouteInjectable),
routes: di.inject(routesInjectable),
@@ -3,24 +3,24 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import userStoreInjectable from "../../common/user-store/user-store.injectable";
import React from "react";
import navigateToKubernetesPreferencesInjectable from "../../features/preferences/common/navigate-to-kubernetes-preferences.injectable";
import { runInAction } from "mobx";
import showSuccessNotificationInjectable from "../components/notifications/show-success-notification.injectable";
import userPreferencesStateInjectable from "../../features/user-preferences/common/state.injectable";
const addSyncEntriesInjectable = getInjectable({
id: "add-sync-entries",
instantiate: (di) => {
const userStore = di.inject(userStoreInjectable);
const state = di.inject(userPreferencesStateInjectable);
const navigateToKubernetesPreferences = di.inject(navigateToKubernetesPreferencesInjectable);
const showSuccessNotification = di.inject(showSuccessNotificationInjectable);
return async (paths: string[]) => {
runInAction(() => {
for (const path of paths) {
userStore.syncKubeconfigEntries.set(path, {});
state.syncKubeconfigEntries.set(path, {});
}
});
@@ -7,7 +7,7 @@ import { withInjectables } from "@ogre-tools/injectable-react";
import type { IComputedValue } from "mobx";
import { observer } from "mobx-react";
import React from "react";
import { shouldShowResourceInjectionToken } from "../../common/cluster-store/allowed-resources-injection-token";
import { shouldShowResourceInjectionToken } from "../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token";
import { Events } from "../components/+events/events";
export interface WorkloadEventsProps {}
@@ -10,8 +10,8 @@ import type { IpcRendererEvent } from "electron";
import React from "react";
import notificationsStoreInjectable from "../components/notifications/notifications-store.injectable";
import { getMillisecondsFromUnixEpoch } from "../../common/utils/date/get-current-date-time";
import getClusterByIdInjectable from "../../common/cluster-store/get-by-id.injectable";
import showSuccessNotificationInjectable from "../components/notifications/show-success-notification.injectable";
import getClusterByIdInjectable from "../../features/cluster/storage/common/get-by-id.injectable";
const intervalBetweenNotifications = 1000 * 60; // 60s
@@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { defaultHotbarCells } from "../../common/hotbars/types";
import { defaultHotbarCells } from "../../features/hotbar/storage/common/types";
import { clusterListNamespaceForbiddenChannel } from "../../common/ipc/cluster";
import { hotbarTooManyItemsChannel } from "../../common/ipc/hotbar";
import showErrorNotificationInjectable from "../components/notifications/show-error-notification.injectable";
@@ -12,11 +12,9 @@ import navigateToExtensionsInjectable from "../../../common/front-end-routing/ro
import navigateToEntitySettingsInjectable from "../../../common/front-end-routing/routes/entity-settings/navigate-to-entity-settings.injectable";
import navigateToClusterViewInjectable from "../../../common/front-end-routing/routes/cluster-view/navigate-to-cluster-view.injectable";
import catalogEntityRegistryInjectable from "../../api/catalog/entity/registry.injectable";
// TODO: Importing from features is not OK. Make protocol-router to comply with Open Closed Principle to allow moving implementation under a feature
import navigateToPreferencesInjectable from "../../../features/preferences/common/navigate-to-preferences.injectable";
import getClusterByIdInjectable from "../../../common/cluster-store/get-by-id.injectable";
import showShortInfoNotificationInjectable from "../../components/notifications/show-short-info.injectable";
import getClusterByIdInjectable from "../../../features/cluster/storage/common/get-by-id.injectable";
const bindProtocolAddRouteHandlersInjectable = getInjectable({
id: "bind-protocol-add-route-handlers",
@@ -17,7 +17,7 @@ import type { NavigateToEntitySettings } from "../../../common/front-end-routing
import type { NavigateToClusterView } from "../../../common/front-end-routing/routes/cluster-view/navigate-to-cluster-view.injectable";
import assert from "assert";
import type { AttemptInstallByInfo } from "../../components/+extensions/attempt-install-by-info.injectable";
import type { GetClusterById } from "../../../common/cluster-store/get-by-id.injectable";
import type { GetClusterById } from "../../../features/cluster/storage/common/get-by-id.injectable";
interface Dependencies {
attemptInstallByInfo: AttemptInstallByInfo;
@@ -5,17 +5,17 @@
import { getInjectable } from "@ogre-tools/injectable";
import extensionLoaderInjectable from "../../../extensions/extension-loader/extension-loader.injectable";
import { LensProtocolRouterRenderer } from "./lens-protocol-router-renderer";
import extensionsStoreInjectable from "../../../extensions/extensions-store/extensions-store.injectable";
import loggerInjectable from "../../../common/logger.injectable";
import showErrorNotificationInjectable from "../../components/notifications/show-error-notification.injectable";
import showShortInfoNotificationInjectable from "../../components/notifications/show-short-info.injectable";
import isExtensionEnabledInjectable from "../../../features/extensions/enabled/common/is-enabled.injectable";
const lensProtocolRouterRendererInjectable = getInjectable({
id: "lens-protocol-router-renderer",
instantiate: (di) => new LensProtocolRouterRenderer({
extensionLoader: di.inject(extensionLoaderInjectable),
extensionsStore: di.inject(extensionsStoreInjectable),
isExtensionEnabled: di.inject(isExtensionEnabledInjectable),
logger: di.inject(loggerInjectable),
showErrorNotification: di.inject(showErrorNotificationInjectable),
showShortInfoNotification: di.inject(showShortInfoNotificationInjectable),
@@ -1,23 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import userStoreInjectable from "../../common/user-store/user-store.injectable";
import { beforeFrameStartsSecondInjectionToken } from "../before-frame-starts/tokens";
import initDefaultUpdateChannelInjectable from "../vars/default-update-channel/init.injectable";
const initUserStoreInjectable = getInjectable({
id: "init-user-store",
instantiate: (di) => ({
run: () => {
const userStore = di.inject(userStoreInjectable);
return userStore.load();
},
runAfter: initDefaultUpdateChannelInjectable,
}),
injectionToken: beforeFrameStartsSecondInjectionToken,
});
export default initUserStoreInjectable;
@@ -5,7 +5,7 @@
import { getInjectable } from "@ogre-tools/injectable";
import assert from "assert";
import { computed } from "mobx";
import lensColorThemePreferenceInjectable from "../../common/user-store/lens-color-theme.injectable";
import lensColorThemePreferenceInjectable from "../../features/user-preferences/common/lens-color-theme.injectable";
import { lensThemeDeclarationInjectionToken } from "./declaration";
import defaultLensThemeInjectable from "./default-theme.injectable";
import systemThemeConfigurationInjectable from "./system-theme.injectable";
@@ -4,9 +4,9 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import loggerInjectable from "../../common/logger.injectable";
import userStoreInjectable from "../../common/user-store/user-store.injectable";
import { object } from "@k8slens/utilities";
import type { LensTheme } from "./lens-theme";
import resetThemeInjectable from "../../features/user-preferences/common/reset-theme.injectable";
export type ApplyLensTheme = (theme: LensTheme) => void;
@@ -14,7 +14,7 @@ const applyLensThemeInjectable = getInjectable({
id: "apply-lens-theme",
instantiate: (di): ApplyLensTheme => {
const logger = di.inject(loggerInjectable);
const userStore = di.inject(userStoreInjectable);
const resetTheme = di.inject(resetThemeInjectable);
return (theme) => {
try {
@@ -28,7 +28,7 @@ const applyLensThemeInjectable = getInjectable({
document.body.classList.toggle("theme-light", theme.type === "light");
} catch (error) {
logger.error("[THEME]: Failed to apply active theme", error);
userStore.resetTheme();
resetTheme();
}
};
},
@@ -6,7 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable";
import { reaction } from "mobx";
import initializeSystemThemeTypeInjectable from "../../features/theme/system-type/renderer/initialize.injectable";
import { beforeFrameStartsSecondInjectionToken } from "../before-frame-starts/tokens";
import initUserStoreInjectable from "../stores/init-user-store.injectable";
import initUserStoreInjectable from "../../features/user-preferences/renderer/load-storage.injectable";
import activeThemeInjectable from "./active.injectable";
import applyLensThemeInjectable from "./apply-lens-theme.injectable";
@@ -4,7 +4,7 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx";
import terminalThemePreferenceInjectable from "../../common/user-store/terminal-theme.injectable";
import terminalThemePreferenceInjectable from "../../features/user-preferences/common/terminal-theme.injectable";
import activeThemeInjectable from "./active.injectable";
import lensThemesInjectable from "./themes.injectable";