+
{commandOverlay.component}
diff --git a/src/renderer/components/command-palette/command-overlay.injectable.ts b/src/renderer/components/command-palette/command-overlay.injectable.ts
index 542bab957d..988a5dcbdb 100644
--- a/src/renderer/components/command-palette/command-overlay.injectable.ts
+++ b/src/renderer/components/command-palette/command-overlay.injectable.ts
@@ -4,7 +4,7 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
-import { observable } from "mobx";
+import { action, observable } from "mobx";
import React from "react";
export class CommandOverlay {
@@ -14,17 +14,17 @@ export class CommandOverlay {
return Boolean(this.#component.get());
}
- open = (component: React.ReactElement) => {
+ open = action((component: React.ReactElement) => {
if (!React.isValidElement(component)) {
throw new TypeError("CommandOverlay.open must be passed a valid ReactElement");
}
this.#component.set(component);
- };
+ });
- close = () => {
+ close = action(() => {
this.#component.set(null);
- };
+ });
get component(): React.ReactElement | null {
return this.#component.get();
diff --git a/src/renderer/components/command-palette/registered-commands/get-entity-setting-commands.injectable.ts b/src/renderer/components/command-palette/registered-commands/get-entity-setting-commands.injectable.ts
new file mode 100644
index 0000000000..c2ea86d29c
--- /dev/null
+++ b/src/renderer/components/command-palette/registered-commands/get-entity-setting-commands.injectable.ts
@@ -0,0 +1,21 @@
+/**
+ * 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 type { RegisteredEntitySetting } from "../../../../extensions/registries";
+import { EntitySettingRegistry } from "../../../../extensions/registries";
+
+export type GetEntitySettingCommands = (kind: string, apiVersion: string, source?: string) => RegisteredEntitySetting[];
+
+const getEntitySettingCommandsInjectable = getInjectable({
+ id: "get-entity-setting-commands",
+ instantiate: (): GetEntitySettingCommands => {
+ const reg = EntitySettingRegistry.getInstance();
+
+ return (kind, apiVersion, source) => reg.getItemsForKind(kind, apiVersion, source);
+ },
+ causesSideEffects: true,
+});
+
+export default getEntitySettingCommandsInjectable;
diff --git a/src/renderer/components/command-palette/registered-commands/internal-commands.injectable.tsx b/src/renderer/components/command-palette/registered-commands/internal-commands.injectable.tsx
index 66f59df706..5bb37de137 100644
--- a/src/renderer/components/command-palette/registered-commands/internal-commands.injectable.tsx
+++ b/src/renderer/components/command-palette/registered-commands/internal-commands.injectable.tsx
@@ -5,7 +5,6 @@
import React from "react";
import type { RegisteredEntitySetting } from "../../../../extensions/registries";
-import { EntitySettingRegistry } from "../../../../extensions/registries";
import { HotbarAddCommand } from "../../hotbar/hotbar-add-command";
import { HotbarRemoveCommand } from "../../hotbar/hotbar-remove-command";
import { HotbarSwitchCommand } from "../../hotbar/hotbar-switch-command";
@@ -38,6 +37,7 @@ import navigateToJobsInjectable from "../../../../common/front-end-routing/route
import navigateToCronJobsInjectable from "../../../../common/front-end-routing/routes/cluster/workloads/cron-jobs/navigate-to-cron-jobs.injectable";
import navigateToCustomResourcesInjectable from "../../../../common/front-end-routing/routes/cluster/custom-resources/custom-resources/navigate-to-custom-resources.injectable";
import navigateToEntitySettingsInjectable from "../../../../common/front-end-routing/routes/entity-settings/navigate-to-entity-settings.injectable";
+import getEntitySettingCommandsInjectable from "./get-entity-setting-commands.injectable";
export function isKubernetesClusterActive(context: CommandContext): boolean {
return context.entity?.kind === "KubernetesCluster";
@@ -247,9 +247,7 @@ const internalCommandsInjectable = getInjectable({
instantiate: (di) => getInternalCommands({
openCommandDialog: di.inject(commandOverlayInjectable).open,
- getEntitySettingItems: EntitySettingRegistry
- .getInstance()
- .getItemsForKind,
+ getEntitySettingItems: di.inject(getEntitySettingCommandsInjectable),
createTerminalTab: di.inject(createTerminalTabInjectable),
navigateToPreferences: di.inject(navigateToPreferencesInjectable),
navigateToHelmCharts: di.inject(navigateToHelmChartsInjectable),
diff --git a/src/renderer/getDiForUnitTesting.tsx b/src/renderer/getDiForUnitTesting.tsx
index d643dabfb0..1aad20d7f3 100644
--- a/src/renderer/getDiForUnitTesting.tsx
+++ b/src/renderer/getDiForUnitTesting.tsx
@@ -57,9 +57,7 @@ import goForwardInjectable from "./components/layout/top-bar/go-forward.injectab
import closeWindowInjectable from "./components/layout/top-bar/close-window.injectable";
import maximizeWindowInjectable from "./components/layout/top-bar/maximize-window.injectable";
import toggleMaximizeWindowInjectable from "./components/layout/top-bar/toggle-maximize-window.injectable";
-import commandContainerRootFrameChildComponentInjectable from "./components/command-palette/command-container-root-frame-child-component.injectable";
import type { HotbarStore } from "../common/hotbars/store";
-import commandContainerClusterFrameChildComponentInjectable from "./components/command-palette/command-container-cluster-frame-child-component.injectable";
import cronJobTriggerDialogClusterFrameChildComponentInjectable from "./components/+workloads-cronjobs/cron-job-trigger-dialog-cluster-frame-child-component.injectable";
import deploymentScaleDialogClusterFrameChildComponentInjectable from "./components/+workloads-deployments/scale/deployment-scale-dialog-cluster-frame-child-component.injectable";
import replicasetScaleDialogClusterFrameChildComponentInjectable from "./components/+workloads-replicasets/scale-dialog/replicaset-scale-dialog-cluster-frame-child-component.injectable";
@@ -72,6 +70,8 @@ import setupSystemCaInjectable from "./frames/root-frame/setup-system-ca.injecta
import extensionShouldBeEnabledForClusterFrameInjectable from "./extension-loader/extension-should-be-enabled-for-cluster-frame.injectable";
import { asyncComputed } from "@ogre-tools/injectable-react";
import forceUpdateModalRootFrameComponentInjectable from "./application-update/force-update-modal/force-update-modal-root-frame-component.injectable";
+import legacyOnChannelListenInjectable from "./ipc/legacy-channel-listen.injectable";
+import getEntitySettingCommandsInjectable from "./components/command-palette/registered-commands/get-entity-setting-commands.injectable";
export const getDiForUnitTesting = (opts: { doGeneralOverrides?: boolean } = {}) => {
const {
@@ -110,17 +110,12 @@ export const getDiForUnitTesting = (opts: { doGeneralOverrides?: boolean } = {})
di.override(appVersionInjectable, () => "1.0.0");
di.override(historyInjectable, () => createMemoryHistory());
-
+ di.override(legacyOnChannelListenInjectable, () => () => noop);
di.override(requestAnimationFrameInjectable, () => (callback) => callback());
-
di.override(lensResourcesDirInjectable, () => "/irrelevant");
- // TODO: Remove side-effects and shared global state
- di.override(commandContainerRootFrameChildComponentInjectable, () => ({
- Component: () => null,
- id: "command-container",
- shouldRender: computed(() => false),
- }));
+ // TODO: remove when entity settings registry is refactored
+ di.override(getEntitySettingCommandsInjectable, () => () => []);
di.override(forceUpdateModalRootFrameComponentInjectable, () => ({
id: "force-update-modal",
@@ -135,7 +130,6 @@ export const getDiForUnitTesting = (opts: { doGeneralOverrides?: boolean } = {})
// TODO: Remove side-effects and shared global state
const clusterFrameChildComponentInjectables: Injectable
[] = [
- commandContainerClusterFrameChildComponentInjectable,
cronJobTriggerDialogClusterFrameChildComponentInjectable,
deploymentScaleDialogClusterFrameChildComponentInjectable,
replicasetScaleDialogClusterFrameChildComponentInjectable,
diff --git a/src/renderer/utils/on-keyboard-shortcut.ts b/src/renderer/utils/on-keyboard-shortcut.ts
new file mode 100644
index 0000000000..4504765ac7
--- /dev/null
+++ b/src/renderer/utils/on-keyboard-shortcut.ts
@@ -0,0 +1,54 @@
+/**
+ * Copyright (c) OpenLens Authors. All rights reserved.
+ * Licensed under MIT License. See LICENSE in root directory for more information.
+ */
+
+import type { WindowEventListener } from "../window/event-listener.injectable";
+
+function parseKeyDownDescriptor(descriptor: string): (event: KeyboardEvent) => boolean {
+ const parts = new Set((
+ descriptor
+ .split("+")
+ .filter(Boolean)
+ .map(part => part.toLowerCase())
+ ));
+
+ if (parts.size === 0) {
+ return () => true;
+ }
+
+ const hasShift = parts.delete("shift");
+ const hasAlt = parts.delete("alt");
+
+ const rawHasCtrl = parts.delete("ctrl");
+ const rawHasControl = parts.delete("control");
+ const hasCtrl = rawHasCtrl || rawHasControl;
+
+ const rawHasMeta = parts.delete("meta");
+ const rawHasCmd = parts.delete("cmd");
+ const hasMeta = rawHasCmd || rawHasMeta; // This means either matches
+
+ const [key, ...rest] = [...parts];
+
+ if (rest.length !== 0) {
+ throw new Error("only single key combinations are currently supported");
+ }
+
+ return (event) => {
+ return event.altKey === hasAlt
+ && event.shiftKey === hasShift
+ && event.ctrlKey === hasCtrl
+ && event.metaKey === hasMeta
+ && event.key.toLowerCase() === key.toLowerCase();
+ };
+}
+
+export function onKeyboardShortcut(descriptor: string, action: () => void): WindowEventListener<"keydown"> {
+ const isMatchingEvent = parseKeyDownDescriptor(descriptor);
+
+ return (event) => {
+ if (isMatchingEvent(event)) {
+ action();
+ }
+ };
+}
diff --git a/src/renderer/window/event-listener.injectable.ts b/src/renderer/window/event-listener.injectable.ts
index 84803cbd86..78a1827e19 100644
--- a/src/renderer/window/event-listener.injectable.ts
+++ b/src/renderer/window/event-listener.injectable.ts
@@ -7,8 +7,9 @@ import { getInjectable } from "@ogre-tools/injectable";
import type { Disposer } from "../utils";
export type AddWindowEventListener = typeof addWindowEventListener;
+export type WindowEventListener = (this: Window, ev: WindowEventMap[K]) => any;
-function addWindowEventListener(type: K, listener: (this: Window, ev: WindowEventMap[K]) => any, options?: boolean | AddEventListenerOptions): Disposer {
+function addWindowEventListener(type: K, listener: WindowEventListener, options?: boolean | AddEventListenerOptions): Disposer {
window.addEventListener(type, listener, options);
return () => void window.removeEventListener(type, listener);