mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-02-16 17:25:58 +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",
|
"knockout": "3.5.1",
|
||||||
"mkdirp": "1.0.4",
|
"mkdirp": "1.0.4",
|
||||||
"monaco-editor": "0.44.0",
|
"monaco-editor": "0.44.0",
|
||||||
|
"mousetrap": "1.6.5",
|
||||||
"ms": "2.1.3",
|
"ms": "2.1.3",
|
||||||
"p-retry": "4.6.2",
|
"p-retry": "4.6.2",
|
||||||
"patch-package": "8.0.0",
|
"patch-package": "8.0.0",
|
||||||
@ -128,6 +129,7 @@
|
|||||||
"@types/hasher": "0.0.31",
|
"@types/hasher": "0.0.31",
|
||||||
"@types/jest": "26.0.20",
|
"@types/jest": "26.0.20",
|
||||||
"@types/jquery": "3.5.29",
|
"@types/jquery": "3.5.29",
|
||||||
|
"@types/mousetrap": "1.6.15",
|
||||||
"@types/node": "12.11.1",
|
"@types/node": "12.11.1",
|
||||||
"@types/post-robot": "10.0.1",
|
"@types/post-robot": "10.0.1",
|
||||||
"@types/q": "1.5.1",
|
"@types/q": "1.5.1",
|
||||||
@ -12792,6 +12794,12 @@
|
|||||||
"@types/node": "*"
|
"@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": {
|
"node_modules/@types/node": {
|
||||||
"version": "12.11.1",
|
"version": "12.11.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.11.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.11.1.tgz",
|
||||||
@ -31631,6 +31639,11 @@
|
|||||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||||
"dev": true
|
"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": {
|
"node_modules/mrmime": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz",
|
||||||
|
@ -77,6 +77,7 @@
|
|||||||
"knockout": "3.5.1",
|
"knockout": "3.5.1",
|
||||||
"mkdirp": "1.0.4",
|
"mkdirp": "1.0.4",
|
||||||
"monaco-editor": "0.44.0",
|
"monaco-editor": "0.44.0",
|
||||||
|
"mousetrap": "1.6.5",
|
||||||
"ms": "2.1.3",
|
"ms": "2.1.3",
|
||||||
"p-retry": "4.6.2",
|
"p-retry": "4.6.2",
|
||||||
"patch-package": "8.0.0",
|
"patch-package": "8.0.0",
|
||||||
@ -123,6 +124,7 @@
|
|||||||
"@types/hasher": "0.0.31",
|
"@types/hasher": "0.0.31",
|
||||||
"@types/jest": "26.0.20",
|
"@types/jest": "26.0.20",
|
||||||
"@types/jquery": "3.5.29",
|
"@types/jquery": "3.5.29",
|
||||||
|
"@types/mousetrap": "1.6.15",
|
||||||
"@types/node": "12.11.1",
|
"@types/node": "12.11.1",
|
||||||
"@types/post-robot": "10.0.1",
|
"@types/post-robot": "10.0.1",
|
||||||
"@types/q": "1.5.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
|
// Image Dependencies
|
||||||
import { Platform } from "ConfigContext";
|
import { Platform } from "ConfigContext";
|
||||||
import { QueryCopilotCarousel } from "Explorer/QueryCopilot/CopilotCarousel";
|
import { QueryCopilotCarousel } from "Explorer/QueryCopilot/CopilotCarousel";
|
||||||
|
import { KeyboardShortcutRoot } from "KeyboardShortcuts";
|
||||||
import "../images/CosmosDB_rgb_ui_lighttheme.ico";
|
import "../images/CosmosDB_rgb_ui_lighttheme.ico";
|
||||||
import hdeConnectImage from "../images/HdeConnectCosmosDB.svg";
|
import hdeConnectImage from "../images/HdeConnectCosmosDB.svg";
|
||||||
import "../images/favicon.ico";
|
import "../images/favicon.ico";
|
||||||
@ -91,52 +92,54 @@ const App: React.FunctionComponent = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flexContainer" aria-hidden="false">
|
<KeyboardShortcutRoot>
|
||||||
<div id="divExplorer" className="flexContainer hideOverflows">
|
<div className="flexContainer" aria-hidden="false">
|
||||||
<div id="freeTierTeachingBubble"> </div>
|
<div id="divExplorer" className="flexContainer hideOverflows">
|
||||||
{/* Main Command Bar - Start */}
|
<div id="freeTierTeachingBubble"> </div>
|
||||||
<CommandBar container={explorer} />
|
{/* Main Command Bar - Start */}
|
||||||
{/* Collections Tree and Tabs - Begin */}
|
<CommandBar container={explorer} />
|
||||||
<div className="resourceTreeAndTabs">
|
{/* Collections Tree and Tabs - Begin */}
|
||||||
{/* Collections Tree - Start */}
|
<div className="resourceTreeAndTabs">
|
||||||
{userContext.apiType !== "Postgres" && userContext.apiType !== "VCoreMongo" && (
|
{/* Collections Tree - Start */}
|
||||||
<div id="resourcetree" data-test="resourceTreeId" className="resourceTree">
|
{userContext.apiType !== "Postgres" && userContext.apiType !== "VCoreMongo" && (
|
||||||
<div className="collectionsTreeWithSplitter">
|
<div id="resourcetree" data-test="resourceTreeId" className="resourceTree">
|
||||||
{/* Collections Tree Expanded - Start */}
|
<div className="collectionsTreeWithSplitter">
|
||||||
<ResourceTreeContainer
|
{/* Collections Tree Expanded - Start */}
|
||||||
container={explorer}
|
<ResourceTreeContainer
|
||||||
toggleLeftPaneExpanded={toggleLeftPaneExpanded}
|
container={explorer}
|
||||||
isLeftPaneExpanded={isLeftPaneExpanded}
|
toggleLeftPaneExpanded={toggleLeftPaneExpanded}
|
||||||
/>
|
isLeftPaneExpanded={isLeftPaneExpanded}
|
||||||
{/* Collections Tree Expanded - End */}
|
/>
|
||||||
{/* Collections Tree Collapsed - Start */}
|
{/* Collections Tree Expanded - End */}
|
||||||
<CollapsedResourceTree
|
{/* Collections Tree Collapsed - Start */}
|
||||||
toggleLeftPaneExpanded={toggleLeftPaneExpanded}
|
<CollapsedResourceTree
|
||||||
isLeftPaneExpanded={isLeftPaneExpanded}
|
toggleLeftPaneExpanded={toggleLeftPaneExpanded}
|
||||||
/>
|
isLeftPaneExpanded={isLeftPaneExpanded}
|
||||||
{/* Collections Tree Collapsed - End */}
|
/>
|
||||||
|
{/* Collections Tree Collapsed - End */}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
<Tabs explorer={explorer} />
|
||||||
<Tabs explorer={explorer} />
|
</div>
|
||||||
</div>
|
{/* Collections Tree and Tabs - End */}
|
||||||
{/* Collections Tree and Tabs - End */}
|
<div
|
||||||
<div
|
className="dataExplorerErrorConsoleContainer"
|
||||||
className="dataExplorerErrorConsoleContainer"
|
role="contentinfo"
|
||||||
role="contentinfo"
|
aria-label="Notification console"
|
||||||
aria-label="Notification console"
|
id="explorerNotificationConsole"
|
||||||
id="explorerNotificationConsole"
|
>
|
||||||
>
|
<NotificationConsole />
|
||||||
<NotificationConsole />
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<SidePanel />
|
||||||
|
<Dialog />
|
||||||
|
{<QuickstartCarousel isOpen={isCarouselOpen} />}
|
||||||
|
{<SQLQuickstartTutorial />}
|
||||||
|
{<MongoQuickstartTutorial />}
|
||||||
|
{<QueryCopilotCarousel isOpen={isCopilotCarouselOpen} explorer={explorer} />}
|
||||||
</div>
|
</div>
|
||||||
<SidePanel />
|
</KeyboardShortcutRoot>
|
||||||
<Dialog />
|
|
||||||
{<QuickstartCarousel isOpen={isCarouselOpen} />}
|
|
||||||
{<SQLQuickstartTutorial />}
|
|
||||||
{<MongoQuickstartTutorial />}
|
|
||||||
{<QueryCopilotCarousel isOpen={isCopilotCarouselOpen} explorer={explorer} />}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user