mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Initial command palette feature (#1957)
* wip: command palette Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * register shortcut to global menu Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * introduce openCommandDialog & closeCommandDialog Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * fix ipc broadcast to frames from renderer Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * tweak Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * add more commands Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * cleanup Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * ipc fix Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * add integration tests Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * ipc fix Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * implement workspace edit Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * workspace edit fixes Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * make tests green Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * fixes from code review Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * cleanup ipc Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * cleanup CommandRegistry Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * ipc fix Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * fix ClusterManager cluster auto-init Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * ensure cluster view is active before sending a command Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * switch to last active cluster when workspace change Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * tweak integration tests Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * run integration tests serially Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * cleanup Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * fixes based on code review Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * cleanup Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * cleanup more Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * cleanup Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * add workspace fixes Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * cleanup Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
1 parent
9e1487685c
commit
b5e7be7591
48 files changed
+1446
-884
No files matched your search
@@ -0,0 +1,11 @@
|
||||
#command-container {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
width: 40%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding: 10px;
|
||||
background-color: var(--dockInfoBackground);
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
|
||||
import "./command-container.scss";
|
||||
import { action, observable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import React from "react";
|
||||
import { Dialog } from "../dialog";
|
||||
import { EventEmitter } from "../../../common/event-emitter";
|
||||
import { subscribeToBroadcast } from "../../../common/ipc";
|
||||
import { CommandDialog } from "./command-dialog";
|
||||
import { CommandRegistration, commandRegistry } from "../../../extensions/registries/command-registry";
|
||||
import { clusterStore } from "../../../common/cluster-store";
|
||||
import { workspaceStore } from "../../../common/workspace-store";
|
||||
import { Cluster } from "../../../main/cluster";
|
||||
|
||||
export type CommandDialogEvent = {
|
||||
component: React.ReactElement
|
||||
};
|
||||
|
||||
const commandDialogBus = new EventEmitter<[CommandDialogEvent]>();
|
||||
|
||||
export class CommandOverlay {
|
||||
static open(component: React.ReactElement) {
|
||||
commandDialogBus.emit({ component });
|
||||
}
|
||||
|
||||
static close() {
|
||||
commandDialogBus.emit({ component: null });
|
||||
}
|
||||
}
|
||||
|
||||
@observer
|
||||
export class CommandContainer extends React.Component<{cluster?: Cluster}> {
|
||||
@observable.ref commandComponent: React.ReactElement;
|
||||
|
||||
private escHandler(event: KeyboardEvent) {
|
||||
if (event.key === "Escape") {
|
||||
event.stopPropagation();
|
||||
this.closeDialog();
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
private closeDialog() {
|
||||
this.commandComponent = null;
|
||||
}
|
||||
|
||||
private findCommandById(commandId: string) {
|
||||
return commandRegistry.getItems().find((command) => command.id === commandId);
|
||||
}
|
||||
|
||||
private runCommand(command: CommandRegistration) {
|
||||
command.action({
|
||||
cluster: clusterStore.active,
|
||||
workspace: workspaceStore.currentWorkspace
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.cluster) {
|
||||
subscribeToBroadcast(`command-palette:run-action:${this.props.cluster.id}`, (event, commandId: string) => {
|
||||
const command = this.findCommandById(commandId);
|
||||
|
||||
if (command) {
|
||||
this.runCommand(command);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
subscribeToBroadcast("command-palette:open", () => {
|
||||
CommandOverlay.open(<CommandDialog />);
|
||||
});
|
||||
}
|
||||
window.addEventListener("keyup", (e) => this.escHandler(e), true);
|
||||
commandDialogBus.addListener((event) => {
|
||||
this.commandComponent = event.component;
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Dialog isOpen={!!this.commandComponent} animated={false} onClose={() => this.commandComponent = null}>
|
||||
<div id="command-container">
|
||||
{this.commandComponent}
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
|
||||
import { Select } from "../select";
|
||||
import { computed, observable, toJS } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import React from "react";
|
||||
import { commandRegistry } from "../../../extensions/registries/command-registry";
|
||||
import { clusterStore } from "../../../common/cluster-store";
|
||||
import { workspaceStore } from "../../../common/workspace-store";
|
||||
import { CommandOverlay } from "./command-container";
|
||||
import { broadcastMessage } from "../../../common/ipc";
|
||||
import { navigate } from "../../navigation";
|
||||
import { clusterViewURL } from "../cluster-manager/cluster-view.route";
|
||||
|
||||
@observer
|
||||
export class CommandDialog extends React.Component {
|
||||
@observable menuIsOpen = true;
|
||||
|
||||
@computed get options() {
|
||||
const context = {
|
||||
cluster: clusterStore.active,
|
||||
workspace: workspaceStore.currentWorkspace
|
||||
};
|
||||
|
||||
return commandRegistry.getItems().filter((command) => {
|
||||
if (command.scope === "cluster" && !clusterStore.active) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!command.isActive) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
return command.isActive(context);
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
|
||||
return false;
|
||||
}
|
||||
}).map((command) => {
|
||||
return { value: command.id, label: command.title };
|
||||
}).sort((a, b) => a.label > b.label ? 1 : -1);
|
||||
}
|
||||
|
||||
private onChange(value: string) {
|
||||
const command = commandRegistry.getItems().find((cmd) => cmd.id === value);
|
||||
|
||||
if (!command) {
|
||||
return;
|
||||
}
|
||||
|
||||
const action = toJS(command.action);
|
||||
|
||||
try {
|
||||
CommandOverlay.close();
|
||||
|
||||
if (command.scope === "global") {
|
||||
action({
|
||||
cluster: clusterStore.active,
|
||||
workspace: workspaceStore.currentWorkspace
|
||||
});
|
||||
} else if(clusterStore.active) {
|
||||
navigate(clusterViewURL({
|
||||
params: {
|
||||
clusterId: clusterStore.active.id
|
||||
}
|
||||
}));
|
||||
broadcastMessage(`command-palette:run-action:${clusterStore.active.id}`, command.id);
|
||||
}
|
||||
} catch(error) {
|
||||
console.error("[COMMAND-DIALOG] failed to execute command", command.id, error);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Select
|
||||
onChange={(v) => this.onChange(v.value)}
|
||||
components={{ DropdownIndicator: null, IndicatorSeparator: null }}
|
||||
menuIsOpen={this.menuIsOpen}
|
||||
options={this.options}
|
||||
autoFocus={true}
|
||||
escapeClearsValue={false}
|
||||
data-test-id="command-palette-search"
|
||||
placeholder="" />
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./command-container";
|
||||
export * from "./command-dialog";
|
||||
Reference in new issue
Block a user