mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-20 01:11:25 +00:00
[Task 3061766] Global Keyboard Shortcuts, implemented through the Command Bar (#1789)
* keyboard shortcuts using tinykeys * refmt and fix lints * retarget keyboard shortcuts to the body instead of the root element of the React component tree * refmt * Update src/Explorer/Menus/CommandBar/CommandBarUtil.tsx Co-authored-by: Laurent Nguyen <laurent.nguyen@microsoft.com> * add Save binding to New Item command bar --------- Co-authored-by: Laurent Nguyen <laurent.nguyen@microsoft.com>
This commit is contained in:
committed by
GitHub
parent
e0cb3da6aa
commit
a44ed1f45c
@@ -1,6 +1,7 @@
|
||||
/**
|
||||
* React component for Command button component.
|
||||
*/
|
||||
import { KeyboardAction } from "KeyboardShortcuts";
|
||||
import * as React from "react";
|
||||
import CollapseChevronDownIcon from "../../../../images/QueryBuilder/CollapseChevronDown_16x.png";
|
||||
import { KeyCodes } from "../../../Common/Constants";
|
||||
@@ -30,7 +31,7 @@ export interface CommandButtonComponentProps {
|
||||
/**
|
||||
* Click handler for command button click
|
||||
*/
|
||||
onCommandClick: (e: React.SyntheticEvent) => void;
|
||||
onCommandClick: (e: React.SyntheticEvent | KeyboardEvent) => void;
|
||||
|
||||
/**
|
||||
* Label for the button
|
||||
@@ -107,10 +108,17 @@ export interface CommandButtonComponentProps {
|
||||
* Vertical bar to divide buttons
|
||||
*/
|
||||
isDivider?: boolean;
|
||||
|
||||
/**
|
||||
* Aria-label for the button
|
||||
*/
|
||||
ariaLabel: string;
|
||||
|
||||
/**
|
||||
* If specified, a keyboard action that should trigger this button's onCommandClick handler when activated.
|
||||
* If not specified, the button will not be triggerable by keyboard shortcuts.
|
||||
*/
|
||||
keyboardAction?: KeyboardAction;
|
||||
}
|
||||
|
||||
export class CommandButtonComponent extends React.Component<CommandButtonComponentProps> {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
import { CommandBar as FluentCommandBar, ICommandBarItemProps } from "@fluentui/react";
|
||||
import { useNotebook } from "Explorer/Notebook/useNotebook";
|
||||
import { useKeyboardActionHandlers } from "KeyboardShortcuts";
|
||||
import { userContext } from "UserContext";
|
||||
import * as React from "react";
|
||||
import create, { UseStore } from "zustand";
|
||||
@@ -40,6 +41,7 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
|
||||
const buttons = useCommandBar((state) => state.contextButtons);
|
||||
const isHidden = useCommandBar((state) => state.isHidden);
|
||||
const backgroundColor = StyleConstants.BaseLight;
|
||||
const setKeyboardShortcutHandlers = useKeyboardActionHandlers((state) => state.setHandlers);
|
||||
|
||||
if (userContext.apiType === "Postgres" || userContext.apiType === "VCoreMongo") {
|
||||
const buttons =
|
||||
@@ -105,6 +107,10 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
|
||||
},
|
||||
};
|
||||
|
||||
const allButtons = staticButtons.concat(contextButtons).concat(controlButtons);
|
||||
const keyboardHandlers = CommandBarUtil.createKeyboardHandlers(allButtons);
|
||||
setKeyboardShortcutHandlers(keyboardHandlers);
|
||||
|
||||
return (
|
||||
<div className="commandBarContainer" style={{ display: isHidden ? "none" : "initial" }}>
|
||||
<FluentCommandBar
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { KeyboardAction } from "KeyboardShortcuts";
|
||||
import { ReactTabKind, useTabs } from "hooks/useTabs";
|
||||
import * as React from "react";
|
||||
import AddCollectionIcon from "../../../../images/AddCollection.svg";
|
||||
@@ -297,6 +298,7 @@ function createNewSQLQueryButton(selectedNodeState: SelectedNodeState): CommandB
|
||||
id: "newQueryBtn",
|
||||
iconSrc: AddSqlQueryIcon,
|
||||
iconAlt: label,
|
||||
keyboardAction: KeyboardAction.NEW_QUERY,
|
||||
onCommandClick: () => {
|
||||
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
|
||||
selectedCollection && selectedCollection.onNewQueryClick(selectedCollection);
|
||||
@@ -312,6 +314,7 @@ function createNewSQLQueryButton(selectedNodeState: SelectedNodeState): CommandB
|
||||
id: "newQueryBtn",
|
||||
iconSrc: AddSqlQueryIcon,
|
||||
iconAlt: label,
|
||||
keyboardAction: KeyboardAction.NEW_QUERY,
|
||||
onCommandClick: () => {
|
||||
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
|
||||
selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection);
|
||||
@@ -397,6 +400,7 @@ function createOpenQueryButton(container: Explorer): CommandButtonComponentProps
|
||||
return {
|
||||
iconSrc: BrowseQueriesIcon,
|
||||
iconAlt: label,
|
||||
keyboardAction: KeyboardAction.OPEN_QUERY,
|
||||
onCommandClick: () =>
|
||||
useSidePanel.getState().openSidePanel("Open Saved Queries", <BrowseQueriesPane explorer={container} />),
|
||||
commandButtonLabel: label,
|
||||
@@ -411,6 +415,7 @@ function createOpenQueryFromDiskButton(): CommandButtonComponentProps {
|
||||
return {
|
||||
iconSrc: OpenQueryFromDiskIcon,
|
||||
iconAlt: label,
|
||||
keyboardAction: KeyboardAction.OPEN_QUERY_FROM_DISK,
|
||||
onCommandClick: () => useSidePanel.getState().openSidePanel("Load Query", <LoadQueryPane />),
|
||||
commandButtonLabel: label,
|
||||
ariaLabel: label,
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
IDropdownStyles,
|
||||
} from "@fluentui/react";
|
||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
import { KeyboardHandlerMap } from "KeyboardShortcuts";
|
||||
import * as React from "react";
|
||||
import _ from "underscore";
|
||||
import ChevronDownIcon from "../../../../images/Chevron_down.svg";
|
||||
@@ -233,3 +234,28 @@ export const createConnectionStatus = (container: Explorer, poolId: PoolIdType,
|
||||
onRender: () => <ConnectionStatus container={container} poolId={poolId} />,
|
||||
};
|
||||
};
|
||||
|
||||
export function createKeyboardHandlers(allButtons: CommandButtonComponentProps[]): KeyboardHandlerMap {
|
||||
const handlers: KeyboardHandlerMap = {};
|
||||
|
||||
function createHandlers(buttons: CommandButtonComponentProps[]) {
|
||||
buttons.forEach((button) => {
|
||||
if (!button.disabled && button.keyboardAction) {
|
||||
handlers[button.keyboardAction] = (e) => {
|
||||
button.onCommandClick(e);
|
||||
|
||||
// If the handler is bound, it means the button is visible and enabled, so we should prevent the default action
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
if (button.children && button.children.length > 0) {
|
||||
createHandlers(button.children);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
createHandlers(allButtons);
|
||||
|
||||
return handlers;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ItemDefinition, PartitionKey, PartitionKeyDefinition, QueryIterator, Resource } from "@azure/cosmos";
|
||||
import { Platform, configContext } from "ConfigContext";
|
||||
import { querySampleDocuments, readSampleDocument } from "Explorer/QueryCopilot/QueryCopilotUtilities";
|
||||
import { KeyboardAction } from "KeyboardShortcuts";
|
||||
import { QueryConstants } from "Shared/Constants";
|
||||
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
||||
import * as ko from "knockout";
|
||||
@@ -907,6 +908,7 @@ export default class DocumentsTab extends TabsBase {
|
||||
buttons.push({
|
||||
iconSrc: SaveIcon,
|
||||
iconAlt: label,
|
||||
keyboardAction: KeyboardAction.SAVE_ITEM,
|
||||
onCommandClick: this.onSaveNewDocumentClick,
|
||||
commandButtonLabel: label,
|
||||
ariaLabel: label,
|
||||
@@ -936,6 +938,7 @@ export default class DocumentsTab extends TabsBase {
|
||||
buttons.push({
|
||||
iconSrc: SaveIcon,
|
||||
iconAlt: label,
|
||||
keyboardAction: KeyboardAction.SAVE_ITEM,
|
||||
onCommandClick: this.onSaveExistingDocumentClick,
|
||||
commandButtonLabel: label,
|
||||
ariaLabel: label,
|
||||
|
||||
@@ -10,6 +10,7 @@ import { OnExecuteQueryClick, QueryDocumentsPerPage } from "Explorer/QueryCopilo
|
||||
import { QueryCopilotSidebar } from "Explorer/QueryCopilot/V2/Sidebar/QueryCopilotSidebar";
|
||||
import { QueryResultSection } from "Explorer/Tabs/QueryTab/QueryResultSection";
|
||||
import { useSelectedNode } from "Explorer/useSelectedNode";
|
||||
import { KeyboardAction } from "KeyboardShortcuts";
|
||||
import { QueryConstants } from "Shared/Constants";
|
||||
import { LocalStorageUtility, StorageKey, getRUThreshold, ruThresholdEnabled } from "Shared/StorageUtility";
|
||||
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
||||
@@ -393,6 +394,7 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
||||
buttons.push({
|
||||
iconSrc: ExecuteQueryIcon,
|
||||
iconAlt: label,
|
||||
keyboardAction: KeyboardAction.EXECUTE_ITEM,
|
||||
onCommandClick: this.props.isSampleCopilotActive
|
||||
? () => OnExecuteQueryClick(this.props.copilotStore)
|
||||
: this.onExecuteQueryClick,
|
||||
@@ -408,6 +410,7 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
||||
buttons.push({
|
||||
iconSrc: SaveQueryIcon,
|
||||
iconAlt: label,
|
||||
keyboardAction: KeyboardAction.SAVE_ITEM,
|
||||
onCommandClick: this.onSaveQueryClick,
|
||||
commandButtonLabel: label,
|
||||
ariaLabel: label,
|
||||
@@ -468,6 +471,7 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
||||
buttons.push({
|
||||
iconSrc: CancelQueryIcon,
|
||||
iconAlt: label,
|
||||
keyboardAction: KeyboardAction.CANCEL_QUERY,
|
||||
onCommandClick: () => this.queryAbortController.abort(),
|
||||
commandButtonLabel: label,
|
||||
ariaLabel: label,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
|
||||
import { Pivot, PivotItem } from "@fluentui/react";
|
||||
import { KeyboardAction } from "KeyboardShortcuts";
|
||||
import React from "react";
|
||||
import ExecuteQueryIcon from "../../../../images/ExecuteQuery.svg";
|
||||
import DiscardIcon from "../../../../images/discard.svg";
|
||||
@@ -321,6 +322,7 @@ export default class StoredProcedureTabComponent extends React.Component<
|
||||
buttons.push({
|
||||
iconSrc: SaveIcon,
|
||||
iconAlt: label,
|
||||
keyboardAction: KeyboardAction.SAVE_ITEM,
|
||||
onCommandClick: this.onSaveClick,
|
||||
commandButtonLabel: label,
|
||||
ariaLabel: label,
|
||||
@@ -334,6 +336,7 @@ export default class StoredProcedureTabComponent extends React.Component<
|
||||
buttons.push({
|
||||
iconSrc: SaveIcon,
|
||||
iconAlt: label,
|
||||
keyboardAction: KeyboardAction.SAVE_ITEM,
|
||||
onCommandClick: this.onUpdateClick,
|
||||
commandButtonLabel: label,
|
||||
ariaLabel: label,
|
||||
@@ -360,6 +363,7 @@ export default class StoredProcedureTabComponent extends React.Component<
|
||||
buttons.push({
|
||||
iconSrc: ExecuteQueryIcon,
|
||||
iconAlt: label,
|
||||
keyboardAction: KeyboardAction.EXECUTE_ITEM,
|
||||
onCommandClick: () => {
|
||||
this.collection.container.openExecuteSprocParamsPanel(this.node);
|
||||
},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { TriggerDefinition } from "@azure/cosmos";
|
||||
import { Dropdown, IDropdownOption, Label, TextField } from "@fluentui/react";
|
||||
import { KeyboardAction } from "KeyboardShortcuts";
|
||||
import React, { Component } from "react";
|
||||
import DiscardIcon from "../../../images/discard.svg";
|
||||
import SaveIcon from "../../../images/save-cosmos.svg";
|
||||
@@ -227,6 +228,7 @@ export class TriggerTabContent extends Component<TriggerTab, ITriggerTabContentS
|
||||
...this,
|
||||
iconSrc: SaveIcon,
|
||||
iconAlt: label,
|
||||
keyboardAction: KeyboardAction.SAVE_ITEM,
|
||||
onCommandClick: this.onSaveClick,
|
||||
commandButtonLabel: label,
|
||||
ariaLabel: label,
|
||||
@@ -241,6 +243,7 @@ export class TriggerTabContent extends Component<TriggerTab, ITriggerTabContentS
|
||||
...this,
|
||||
iconSrc: SaveIcon,
|
||||
iconAlt: label,
|
||||
keyboardAction: KeyboardAction.SAVE_ITEM,
|
||||
onCommandClick: this.onUpdateClick,
|
||||
commandButtonLabel: label,
|
||||
ariaLabel: label,
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { UserDefinedFunctionDefinition } from "@azure/cosmos";
|
||||
import { Label, TextField } from "@fluentui/react";
|
||||
import { KeyboardAction } from "KeyboardShortcuts";
|
||||
import React, { Component } from "react";
|
||||
import DiscardIcon from "../../../images/discard.svg";
|
||||
import SaveIcon from "../../../images/save-cosmos.svg";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||
import { createUserDefinedFunction } from "../../Common/dataAccess/createUserDefinedFunction";
|
||||
import { updateUserDefinedFunction } from "../../Common/dataAccess/updateUserDefinedFunction";
|
||||
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
@@ -80,6 +81,7 @@ export default class UserDefinedFunctionTabContent extends Component<
|
||||
setState: this.setState,
|
||||
iconSrc: SaveIcon,
|
||||
iconAlt: label,
|
||||
keyboardAction: KeyboardAction.SAVE_ITEM,
|
||||
onCommandClick: this.onSaveClick,
|
||||
commandButtonLabel: label,
|
||||
ariaLabel: label,
|
||||
@@ -94,6 +96,7 @@ export default class UserDefinedFunctionTabContent extends Component<
|
||||
...this,
|
||||
iconSrc: SaveIcon,
|
||||
iconAlt: label,
|
||||
keyboardAction: KeyboardAction.SAVE_ITEM,
|
||||
onCommandClick: this.onUpdateClick,
|
||||
commandButtonLabel: label,
|
||||
ariaLabel: label,
|
||||
|
||||
Reference in New Issue
Block a user