From dddbac791c7cdf33ce8f0094289a948ccf8a421c Mon Sep 17 00:00:00 2001 From: Jari Kolehmainen Date: Thu, 14 Jan 2021 15:21:33 +0200 Subject: [PATCH] wip: command palette Signed-off-by: Jari Kolehmainen --- src/extensions/lens-renderer-extension.ts | 2 + src/extensions/registries/command-registry.ts | 22 ++++ .../+preferences/preferences.route.ts | 8 ++ .../components/+workspaces/workspaces.tsx | 8 ++ .../command-palette/command-dialog.scss | 28 +++++ .../command-palette/command-dialog.tsx | 103 ++++++++++++++++++ src/renderer/lens-app.tsx | 2 + 7 files changed, 173 insertions(+) create mode 100644 src/extensions/registries/command-registry.ts create mode 100644 src/renderer/components/command-palette/command-dialog.scss create mode 100644 src/renderer/components/command-palette/command-dialog.tsx diff --git a/src/extensions/lens-renderer-extension.ts b/src/extensions/lens-renderer-extension.ts index 25afaa76fe..8b9b132114 100644 --- a/src/extensions/lens-renderer-extension.ts +++ b/src/extensions/lens-renderer-extension.ts @@ -2,6 +2,7 @@ import type { AppPreferenceRegistration, ClusterFeatureRegistration, ClusterPage import type { Cluster } from "../main/cluster"; import { LensExtension } from "./lens-extension"; import { getExtensionPageUrl } from "./registries/page-registry"; +import { CommandRegistration } from "./registries/command-registry"; export class LensRendererExtension extends LensExtension { globalPages: PageRegistration[] = []; @@ -14,6 +15,7 @@ export class LensRendererExtension extends LensExtension { statusBarItems: StatusBarRegistration[] = []; kubeObjectDetailItems: KubeObjectDetailRegistration[] = []; kubeObjectMenuItems: KubeObjectMenuRegistration[] = []; + commands: CommandRegistration[] = []; async navigate

(pageId?: string, params?: P) { const { navigate } = await import("../renderer/navigation"); diff --git a/src/extensions/registries/command-registry.ts b/src/extensions/registries/command-registry.ts new file mode 100644 index 0000000000..359702c29f --- /dev/null +++ b/src/extensions/registries/command-registry.ts @@ -0,0 +1,22 @@ +// Extensions API -> Commands + +import type { Cluster } from "../../main/cluster"; +import type { Workspace } from "../../common/workspace-store"; +import { BaseRegistry } from "./base-registry"; + +export type CommandContext = { + cluster?: Cluster; + workspace?: Workspace; +}; + +export interface CommandRegistration { + id: string; + title: string; + action: (context: CommandContext) => void; + isActive?: (context: CommandContext) => boolean; +} + +export class CommandRegistry extends BaseRegistry { +} + +export const commandRegistry = new CommandRegistry(); diff --git a/src/renderer/components/+preferences/preferences.route.ts b/src/renderer/components/+preferences/preferences.route.ts index 7c880bd5fc..6937ecb0dd 100644 --- a/src/renderer/components/+preferences/preferences.route.ts +++ b/src/renderer/components/+preferences/preferences.route.ts @@ -1,8 +1,16 @@ import type { RouteProps } from "react-router"; +import { commandRegistry } from "../../../extensions/registries/command-registry"; import { buildURL } from "../../../common/utils/buildUrl"; +import { navigate } from "../../navigation"; export const preferencesRoute: RouteProps = { path: "/preferences" }; export const preferencesURL = buildURL(preferencesRoute.path); + +commandRegistry.add({ + id: "app.showPreferences", + title: "Preferences: Open", + action: () => navigate(preferencesURL.toString()) +}); diff --git a/src/renderer/components/+workspaces/workspaces.tsx b/src/renderer/components/+workspaces/workspaces.tsx index 932306adab..fa5897f5c8 100644 --- a/src/renderer/components/+workspaces/workspaces.tsx +++ b/src/renderer/components/+workspaces/workspaces.tsx @@ -12,6 +12,8 @@ import { cssNames, prevDefault } from "../../utils"; import { Button } from "../button"; import { isRequired, InputValidator } from "../input/input_validators"; import { clusterStore } from "../../../common/cluster-store"; +import { commandRegistry } from "../../../extensions/registries/command-registry"; +import { navigate } from "../../navigation"; @observer export class Workspaces extends React.Component { @@ -214,3 +216,9 @@ export class Workspaces extends React.Component { ); } } + +commandRegistry.add({ + id: "workspace.showList", + title: "Workspace: Open list", + action: () => navigate("/workspaces") +}); diff --git a/src/renderer/components/command-palette/command-dialog.scss b/src/renderer/components/command-palette/command-dialog.scss new file mode 100644 index 0000000000..0d03c4d0e9 --- /dev/null +++ b/src/renderer/components/command-palette/command-dialog.scss @@ -0,0 +1,28 @@ +#command-dialog { + position: absolute; + top: 20px; + width: 40%; + height: none !important; + left: 0; + right: 0; + margin-left: auto; + margin-right: auto; + z-index: 1000; + background-color: var(--dockInfoBackground); + + ul { + margin-top: 10px; + li { + padding-left: 4px; + margin-bottom: 10px; + a { + text-decoration: none; + border-bottom: none; + } + } + li:hover { + + cursor: pointer; + } + } +} diff --git a/src/renderer/components/command-palette/command-dialog.tsx b/src/renderer/components/command-palette/command-dialog.tsx new file mode 100644 index 0000000000..55605f9f8b --- /dev/null +++ b/src/renderer/components/command-palette/command-dialog.tsx @@ -0,0 +1,103 @@ + +import "./command-dialog.scss"; +import { Select } from "../select"; +import { action, computed, observable } from "mobx"; +import { observer } from "mobx-react"; +import React from "react"; +import { commandRegistry } from "../../../extensions/registries/command-registry"; +import { Dialog } from "../dialog"; +import { isMac } from "../../../common/vars"; +import { clusterStore } from "../../../common/cluster-store"; +import { workspaceStore } from "../../../common/workspace-store"; + +@observer +export class CommandDialog extends React.Component { + @observable visible = false; + @observable menuIsOpen = true; + + private escHandler(event: KeyboardEvent) { + if (event.key === "Escape") { + event.stopPropagation(); + this.closeDialog(); + } + } + + @action + private shortcutHandler(event: KeyboardEvent) { + console.log(event); + + if (isMac) { + if (event.shiftKey && event.metaKey && event.key === "p") { + this.visible = true; + this.menuIsOpen = true; + event.stopPropagation(); + } + } else { + if (event.shiftKey && event.ctrlKey && event.key === "p") { + this.visible = true; + this.menuIsOpen = true; + event.stopPropagation(); + } + } + } + + private closeDialog() { + this.menuIsOpen = false; + setTimeout(() => { + this.visible = false; + }, 1000); + } + + componentDidMount() { + window.addEventListener("keyup", (e) => this.escHandler(e), true); + window.addEventListener("keydown", (e) => this.shortcutHandler(e), true); + } + + componentWillUnmount() { + window.removeEventListener("keyup", this.escHandler); + } + + @computed get options() { + return commandRegistry.getItems().map((command) => { + return { value: command.id, label: command.title }; + }); + } + + private onChange(value: string) { + const command = commandRegistry.getItems().find((cmd) => cmd.id === value); + + console.log(value, command); + + if (!command) { + return; + } + + try { + command.action({ + cluster: clusterStore.active, + workspace: workspaceStore.currentWorkspace + }); + } catch(error) { + console.error("failed to execute command", command.id, error); + } finally { + this.closeDialog(); + } + } + + render() { + return ( +

+
+