mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2024-11-25 06:56:38 +00:00
initial mousetrap keyboard shortcuts
This commit is contained in:
parent
d35e2a325e
commit
e441b75325
13
package-lock.json
generated
13
package-lock.json
generated
@ -82,6 +82,7 @@
|
||||
"knockout": "3.5.1",
|
||||
"mkdirp": "1.0.4",
|
||||
"monaco-editor": "0.44.0",
|
||||
"mousetrap": "1.6.5",
|
||||
"ms": "2.1.3",
|
||||
"p-retry": "4.6.2",
|
||||
"patch-package": "8.0.0",
|
||||
@ -128,6 +129,7 @@
|
||||
"@types/hasher": "0.0.31",
|
||||
"@types/jest": "26.0.20",
|
||||
"@types/jquery": "3.5.29",
|
||||
"@types/mousetrap": "1.6.15",
|
||||
"@types/node": "12.11.1",
|
||||
"@types/post-robot": "10.0.1",
|
||||
"@types/q": "1.5.1",
|
||||
@ -12792,6 +12794,12 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/mousetrap": {
|
||||
"version": "1.6.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/mousetrap/-/mousetrap-1.6.15.tgz",
|
||||
"integrity": "sha512-qL0hyIMNPow317QWW/63RvL1x5MVMV+Ru3NaY9f/CuEpCqrmb7WeuK2071ZY5hczOnm38qExWM2i2WtkXLSqFw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "12.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.11.1.tgz",
|
||||
@ -31631,6 +31639,11 @@
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/mousetrap": {
|
||||
"version": "1.6.5",
|
||||
"resolved": "https://registry.npmjs.org/mousetrap/-/mousetrap-1.6.5.tgz",
|
||||
"integrity": "sha512-QNo4kEepaIBwiT8CDhP98umTetp+JNfQYBWvC1pc6/OAibuXtRcxZ58Qz8skvEHYvURne/7R8T5VoOI7rDsEUA=="
|
||||
},
|
||||
"node_modules/mrmime": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz",
|
||||
|
@ -77,6 +77,7 @@
|
||||
"knockout": "3.5.1",
|
||||
"mkdirp": "1.0.4",
|
||||
"monaco-editor": "0.44.0",
|
||||
"mousetrap": "1.6.5",
|
||||
"ms": "2.1.3",
|
||||
"p-retry": "4.6.2",
|
||||
"patch-package": "8.0.0",
|
||||
@ -123,6 +124,7 @@
|
||||
"@types/hasher": "0.0.31",
|
||||
"@types/jest": "26.0.20",
|
||||
"@types/jquery": "3.5.29",
|
||||
"@types/mousetrap": "1.6.15",
|
||||
"@types/node": "12.11.1",
|
||||
"@types/post-robot": "10.0.1",
|
||||
"@types/q": "1.5.1",
|
||||
|
131
src/KeyboardShortcuts.tsx
Normal file
131
src/KeyboardShortcuts.tsx
Normal file
@ -0,0 +1,131 @@
|
||||
import { useSelectedNode } from "Explorer/useSelectedNode";
|
||||
import { userContext } from "UserContext";
|
||||
import Mousetrap, { ExtendedKeyboardEvent } from "mousetrap";
|
||||
import * as React from "react";
|
||||
import * as ViewModels from "../Contracts/ViewModels";
|
||||
|
||||
type KeyboardShortcutRootProps = React.PropsWithChildren<unknown>;
|
||||
type KeyboardShortcutHandler = (e: ExtendedKeyboardEvent, combo: string) => boolean | void;
|
||||
|
||||
export interface KeyboardShortcutBinding {
|
||||
/**
|
||||
* The keyboard shortcut to bind to. This can be a single string or an array of strings.
|
||||
* Any combination supported by Mousetrap (https://craig.is/killing/mice#api.bind) is valid here.
|
||||
*/
|
||||
keys: string | string[],
|
||||
|
||||
/**
|
||||
* The handler to run when the keyboard shortcut is pressed.
|
||||
* @param e The keyboard event that triggered the shortcut.
|
||||
* @param combo The specific keyboard combination that was matched (in case a single handler is used for multiple shortcuts).
|
||||
* @returns If the handler returns `false`, the default action for the keyboard shortcut will be prevented AND propagation of the event will be stopped.
|
||||
*/
|
||||
handler: KeyboardShortcutHandler,
|
||||
|
||||
/**
|
||||
* The event to bind the keyboard shortcut to (keydown, keyup, etc.).
|
||||
* The default is 'keydown'
|
||||
*/
|
||||
action?: string,
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the provided keyboard shortcut handler in one that only runs if a collection is selected.
|
||||
* @param callback The callback to run if a collection is selected.
|
||||
* @returns If the handler returns `false`, the default action for the keyboard shortcut will be prevented AND propagation of the event will be stopped.
|
||||
*/
|
||||
function withSelectedCollection(callback: (selectedCollection: ViewModels.Collection, e: ExtendedKeyboardEvent, combo: string) => boolean | void): KeyboardShortcutHandler {
|
||||
return (e, combo) => {
|
||||
const state = useSelectedNode.getState();
|
||||
if (!state.selectedNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedCollection = state.findSelectedCollection();
|
||||
if (selectedCollection) {
|
||||
return callback(selectedCollection, e, combo);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
const bindings: KeyboardShortcutBinding[] = [
|
||||
{
|
||||
keys: ["ctrl+j"],
|
||||
handler: withSelectedCollection((selectedCollection) => {
|
||||
if (userContext.apiType === "SQL" || userContext.apiType === "Gremlin") {
|
||||
selectedCollection.onNewQueryClick(selectedCollection);
|
||||
return false;
|
||||
} else if (userContext.apiType === "Mongo") {
|
||||
selectedCollection.onNewMongoQueryClick(selectedCollection);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
},
|
||||
{
|
||||
keys: ["shift+enter"],
|
||||
handler: () => {
|
||||
alert("TODO: Execute Item");
|
||||
return false;
|
||||
},
|
||||
},
|
||||
{
|
||||
keys: ["esc"],
|
||||
handler: () => {
|
||||
alert("TODO: Cancel Query");
|
||||
return false;
|
||||
},
|
||||
},
|
||||
{
|
||||
keys: ["mod+s"],
|
||||
handler: () => {
|
||||
alert("TODO: Save Query");
|
||||
return false;
|
||||
},
|
||||
},
|
||||
{
|
||||
keys: ["mod+o"],
|
||||
handler: () => {
|
||||
alert("TODO: Open Query");
|
||||
return false;
|
||||
},
|
||||
},
|
||||
{
|
||||
keys: ["mod+shift+o"],
|
||||
handler: () => {
|
||||
alert("TODO: Open Query from Disk");
|
||||
return false;
|
||||
},
|
||||
},
|
||||
{
|
||||
keys: ["mod+s"],
|
||||
handler: () => {
|
||||
alert("TODO: Save");
|
||||
return false;
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
export function KeyboardShortcutRoot({ children }: KeyboardShortcutRootProps) {
|
||||
React.useEffect(() => {
|
||||
const m = new Mousetrap(document.body);
|
||||
const existingStopCallback = m.stopCallback;
|
||||
m.stopCallback = (e, element, combo) => {
|
||||
// Don't block mousetrap callback in the Monaco editor.
|
||||
if (element.matches(".monaco-editor textarea")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return existingStopCallback(e, element, combo);
|
||||
};
|
||||
|
||||
bindings.forEach(b => {
|
||||
m.bind(b.keys, b.handler, b.action);
|
||||
});
|
||||
}, []); // Using an empty dependency array means React will only run this _once_ when the component is mounted.
|
||||
|
||||
return <>
|
||||
{children}
|
||||
</>;
|
||||
}
|
89
src/Main.tsx
89
src/Main.tsx
@ -21,6 +21,7 @@ import "../externals/jquery.typeahead.min.js";
|
||||
// Image Dependencies
|
||||
import { Platform } from "ConfigContext";
|
||||
import { QueryCopilotCarousel } from "Explorer/QueryCopilot/CopilotCarousel";
|
||||
import { KeyboardShortcutRoot } from "KeyboardShortcuts";
|
||||
import "../images/CosmosDB_rgb_ui_lighttheme.ico";
|
||||
import hdeConnectImage from "../images/HdeConnectCosmosDB.svg";
|
||||
import "../images/favicon.ico";
|
||||
@ -91,52 +92,54 @@ const App: React.FunctionComponent = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flexContainer" aria-hidden="false">
|
||||
<div id="divExplorer" className="flexContainer hideOverflows">
|
||||
<div id="freeTierTeachingBubble"> </div>
|
||||
{/* Main Command Bar - Start */}
|
||||
<CommandBar container={explorer} />
|
||||
{/* Collections Tree and Tabs - Begin */}
|
||||
<div className="resourceTreeAndTabs">
|
||||
{/* Collections Tree - Start */}
|
||||
{userContext.apiType !== "Postgres" && userContext.apiType !== "VCoreMongo" && (
|
||||
<div id="resourcetree" data-test="resourceTreeId" className="resourceTree">
|
||||
<div className="collectionsTreeWithSplitter">
|
||||
{/* Collections Tree Expanded - Start */}
|
||||
<ResourceTreeContainer
|
||||
container={explorer}
|
||||
toggleLeftPaneExpanded={toggleLeftPaneExpanded}
|
||||
isLeftPaneExpanded={isLeftPaneExpanded}
|
||||
/>
|
||||
{/* Collections Tree Expanded - End */}
|
||||
{/* Collections Tree Collapsed - Start */}
|
||||
<CollapsedResourceTree
|
||||
toggleLeftPaneExpanded={toggleLeftPaneExpanded}
|
||||
isLeftPaneExpanded={isLeftPaneExpanded}
|
||||
/>
|
||||
{/* Collections Tree Collapsed - End */}
|
||||
<KeyboardShortcutRoot>
|
||||
<div className="flexContainer" aria-hidden="false">
|
||||
<div id="divExplorer" className="flexContainer hideOverflows">
|
||||
<div id="freeTierTeachingBubble"> </div>
|
||||
{/* Main Command Bar - Start */}
|
||||
<CommandBar container={explorer} />
|
||||
{/* Collections Tree and Tabs - Begin */}
|
||||
<div className="resourceTreeAndTabs">
|
||||
{/* Collections Tree - Start */}
|
||||
{userContext.apiType !== "Postgres" && userContext.apiType !== "VCoreMongo" && (
|
||||
<div id="resourcetree" data-test="resourceTreeId" className="resourceTree">
|
||||
<div className="collectionsTreeWithSplitter">
|
||||
{/* Collections Tree Expanded - Start */}
|
||||
<ResourceTreeContainer
|
||||
container={explorer}
|
||||
toggleLeftPaneExpanded={toggleLeftPaneExpanded}
|
||||
isLeftPaneExpanded={isLeftPaneExpanded}
|
||||
/>
|
||||
{/* Collections Tree Expanded - End */}
|
||||
{/* Collections Tree Collapsed - Start */}
|
||||
<CollapsedResourceTree
|
||||
toggleLeftPaneExpanded={toggleLeftPaneExpanded}
|
||||
isLeftPaneExpanded={isLeftPaneExpanded}
|
||||
/>
|
||||
{/* Collections Tree Collapsed - End */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<Tabs explorer={explorer} />
|
||||
</div>
|
||||
{/* Collections Tree and Tabs - End */}
|
||||
<div
|
||||
className="dataExplorerErrorConsoleContainer"
|
||||
role="contentinfo"
|
||||
aria-label="Notification console"
|
||||
id="explorerNotificationConsole"
|
||||
>
|
||||
<NotificationConsole />
|
||||
)}
|
||||
<Tabs explorer={explorer} />
|
||||
</div>
|
||||
{/* Collections Tree and Tabs - End */}
|
||||
<div
|
||||
className="dataExplorerErrorConsoleContainer"
|
||||
role="contentinfo"
|
||||
aria-label="Notification console"
|
||||
id="explorerNotificationConsole"
|
||||
>
|
||||
<NotificationConsole />
|
||||
</div>
|
||||
</div>
|
||||
<SidePanel />
|
||||
<Dialog />
|
||||
{<QuickstartCarousel isOpen={isCarouselOpen} />}
|
||||
{<SQLQuickstartTutorial />}
|
||||
{<MongoQuickstartTutorial />}
|
||||
{<QueryCopilotCarousel isOpen={isCopilotCarouselOpen} explorer={explorer} />}
|
||||
</div>
|
||||
<SidePanel />
|
||||
<Dialog />
|
||||
{<QuickstartCarousel isOpen={isCarouselOpen} />}
|
||||
{<SQLQuickstartTutorial />}
|
||||
{<MongoQuickstartTutorial />}
|
||||
{<QueryCopilotCarousel isOpen={isCopilotCarouselOpen} explorer={explorer} />}
|
||||
</div>
|
||||
</KeyboardShortcutRoot>
|
||||
);
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user