mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-01-04 18:59:08 +01:00
Modal keybindings (#484)
* first draft at modal keybindings * added modal keybindings and inline settings text edit keybindings * added switch for keybindings in modal footer * removed logs * remove another console.log * fix userinput keybindings -- should be generic:cancel for 2nd one
This commit is contained in:
parent
923cf71e0a
commit
c0c53edb84
@ -8,6 +8,8 @@ import { boundMethod } from "autobind-decorator";
|
|||||||
import cn from "classnames";
|
import cn from "classnames";
|
||||||
import { If } from "tsx-control-statements/components";
|
import { If } from "tsx-control-statements/components";
|
||||||
import { checkKeyPressed, adaptFromReactOrNativeKeyEvent } from "@/util/keyutil";
|
import { checkKeyPressed, adaptFromReactOrNativeKeyEvent } from "@/util/keyutil";
|
||||||
|
import { GlobalModel } from "@/models";
|
||||||
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
|
||||||
import "./inlinesettingstextedit.less";
|
import "./inlinesettingstextedit.less";
|
||||||
|
|
||||||
@ -27,6 +29,11 @@ class InlineSettingsTextEdit extends React.Component<
|
|||||||
tempText: OV<string>;
|
tempText: OV<string>;
|
||||||
shouldFocus: boolean = false;
|
shouldFocus: boolean = false;
|
||||||
inputRef: React.RefObject<any> = React.createRef();
|
inputRef: React.RefObject<any> = React.createRef();
|
||||||
|
curId: string;
|
||||||
|
|
||||||
|
componentDidMount(): void {
|
||||||
|
this.curId = uuidv4();
|
||||||
|
}
|
||||||
|
|
||||||
componentDidUpdate(): void {
|
componentDidUpdate(): void {
|
||||||
if (this.shouldFocus) {
|
if (this.shouldFocus) {
|
||||||
@ -52,6 +59,7 @@ class InlineSettingsTextEdit extends React.Component<
|
|||||||
this.tempText = null;
|
this.tempText = null;
|
||||||
this.props.onChange(newText);
|
this.props.onChange(newText);
|
||||||
})();
|
})();
|
||||||
|
this.unregisterKeybindings();
|
||||||
}
|
}
|
||||||
|
|
||||||
@boundMethod
|
@boundMethod
|
||||||
@ -60,24 +68,38 @@ class InlineSettingsTextEdit extends React.Component<
|
|||||||
this.isEditing.set(false);
|
this.isEditing.set(false);
|
||||||
this.tempText = null;
|
this.tempText = null;
|
||||||
})();
|
})();
|
||||||
|
this.unregisterKeybindings();
|
||||||
}
|
}
|
||||||
|
|
||||||
@boundMethod
|
handleFocus() {
|
||||||
handleKeyDown(e: any): void {
|
this.registerKeybindings();
|
||||||
let waveEvent = adaptFromReactOrNativeKeyEvent(e);
|
}
|
||||||
if (checkKeyPressed(waveEvent, "Enter")) {
|
|
||||||
e.preventDefault();
|
registerKeybindings() {
|
||||||
e.stopPropagation();
|
let keybindManager = GlobalModel.keybindManager;
|
||||||
|
let domain = "inline-settings" + this.curId;
|
||||||
|
keybindManager.registerKeybinding("mainview", domain, "generic:confirm", (waveEvent) => {
|
||||||
this.confirmChange();
|
this.confirmChange();
|
||||||
return;
|
return true;
|
||||||
}
|
});
|
||||||
if (checkKeyPressed(waveEvent, "Escape")) {
|
keybindManager.registerKeybinding("mainview", domain, "generic:cancel", (waveEvent) => {
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
this.cancelChange();
|
this.cancelChange();
|
||||||
return;
|
return true;
|
||||||
}
|
});
|
||||||
return;
|
}
|
||||||
|
|
||||||
|
unregisterKeybindings() {
|
||||||
|
let domain = "inline-settings" + this.curId;
|
||||||
|
GlobalModel.keybindManager.unregisterDomain(domain);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleBlur() {
|
||||||
|
this.unregisterKeybindings();
|
||||||
|
this.cancelChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount(): void {
|
||||||
|
this.unregisterKeybindings();
|
||||||
}
|
}
|
||||||
|
|
||||||
@boundMethod
|
@boundMethod
|
||||||
@ -99,7 +121,8 @@ class InlineSettingsTextEdit extends React.Component<
|
|||||||
ref={this.inputRef}
|
ref={this.inputRef}
|
||||||
className="input"
|
className="input"
|
||||||
type="text"
|
type="text"
|
||||||
onKeyDown={this.handleKeyDown}
|
onFocus={this.handleFocus.bind(this)}
|
||||||
|
onBlur={this.handleBlur.bind(this)}
|
||||||
placeholder={this.props.placeholder}
|
placeholder={this.props.placeholder}
|
||||||
onChange={this.handleChangeText}
|
onChange={this.handleChangeText}
|
||||||
value={this.tempText.get()}
|
value={this.tempText.get()}
|
||||||
|
@ -6,8 +6,11 @@ import * as mobx from "mobx";
|
|||||||
import { If } from "tsx-control-statements/components";
|
import { If } from "tsx-control-statements/components";
|
||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
import { Button } from "./button";
|
import { Button } from "./button";
|
||||||
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
import { GlobalModel } from "@/models";
|
||||||
|
|
||||||
import "./modal.less";
|
import "./modal.less";
|
||||||
|
import { boundMethod } from "autobind-decorator";
|
||||||
|
|
||||||
interface ModalHeaderProps {
|
interface ModalHeaderProps {
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
@ -30,10 +33,52 @@ interface ModalFooterProps {
|
|||||||
onOk?: () => void;
|
onOk?: () => void;
|
||||||
cancelLabel?: string;
|
cancelLabel?: string;
|
||||||
okLabel?: string;
|
okLabel?: string;
|
||||||
|
keybindings?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ModalFooter: React.FC<ModalFooterProps> = ({ onCancel, onOk, cancelLabel = "Cancel", okLabel = "Ok" }) => (
|
class ModalKeybindings extends React.Component<{ onOk; onCancel }, {}> {
|
||||||
|
curId: string;
|
||||||
|
|
||||||
|
@boundMethod
|
||||||
|
componentDidMount(): void {
|
||||||
|
this.curId = uuidv4();
|
||||||
|
let domain = "modal-" + this.curId;
|
||||||
|
let keybindManager = GlobalModel.keybindManager;
|
||||||
|
if (this.props.onOk) {
|
||||||
|
keybindManager.registerKeybinding("modal", domain, "generic:confirm", (waveEvent) => {
|
||||||
|
this.props.onOk();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (this.props.onCancel) {
|
||||||
|
keybindManager.registerKeybinding("modal", domain, "generic:cancel", (waveEvent) => {
|
||||||
|
this.props.onCancel();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@boundMethod
|
||||||
|
componentWillUnmount(): void {
|
||||||
|
GlobalModel.keybindManager.unregisterDomain("modal-" + this.curId);
|
||||||
|
}
|
||||||
|
|
||||||
|
render(): React.ReactNode {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModalFooter: React.FC<ModalFooterProps> = ({
|
||||||
|
onCancel,
|
||||||
|
onOk,
|
||||||
|
cancelLabel = "Cancel",
|
||||||
|
okLabel = "Ok",
|
||||||
|
keybindings = true,
|
||||||
|
}) => (
|
||||||
<div className="wave-modal-footer">
|
<div className="wave-modal-footer">
|
||||||
|
<If condition={keybindings}>
|
||||||
|
<ModalKeybindings onOk={onOk} onCancel={onCancel}></ModalKeybindings>
|
||||||
|
</If>
|
||||||
{onCancel && (
|
{onCancel && (
|
||||||
<Button className="secondary" onClick={onCancel}>
|
<Button className="secondary" onClick={onCancel}>
|
||||||
{cancelLabel}
|
{cancelLabel}
|
||||||
@ -79,4 +124,4 @@ class Modal extends React.Component<ModalProps> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Modal };
|
export { Modal, ModalKeybindings };
|
||||||
|
@ -18,6 +18,8 @@ interface TextFieldProps {
|
|||||||
className?: string;
|
className?: string;
|
||||||
onChange?: (value: string) => void;
|
onChange?: (value: string) => void;
|
||||||
onKeyDown?: (event: React.KeyboardEvent<HTMLInputElement>) => void;
|
onKeyDown?: (event: React.KeyboardEvent<HTMLInputElement>) => void;
|
||||||
|
onFocus?: () => void;
|
||||||
|
onBlur?: () => void;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
defaultValue?: string;
|
defaultValue?: string;
|
||||||
decoration?: TextFieldDecorationProps;
|
decoration?: TextFieldDecorationProps;
|
||||||
@ -78,6 +80,9 @@ class TextField extends React.Component<TextFieldProps, TextFieldState> {
|
|||||||
@boundMethod
|
@boundMethod
|
||||||
handleFocus() {
|
handleFocus() {
|
||||||
this.setState({ focused: true });
|
this.setState({ focused: true });
|
||||||
|
if (this.props.onFocus) {
|
||||||
|
this.props.onFocus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@boundMethod
|
@boundMethod
|
||||||
@ -91,6 +96,9 @@ class TextField extends React.Component<TextFieldProps, TextFieldState> {
|
|||||||
this.setState({ error: false, focused: false });
|
this.setState({ error: false, focused: false });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (this.props.onBlur) {
|
||||||
|
this.props.onBlur();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@boundMethod
|
@boundMethod
|
||||||
|
@ -9,6 +9,7 @@ import { Markdown, Modal, Button, Checkbox } from "@/elements";
|
|||||||
import { GlobalModel, GlobalCommandRunner } from "@/models";
|
import { GlobalModel, GlobalCommandRunner } from "@/models";
|
||||||
|
|
||||||
import "./alert.less";
|
import "./alert.less";
|
||||||
|
import { ModalKeybindings } from "../elements/modal";
|
||||||
|
|
||||||
@mobxReact.observer
|
@mobxReact.observer
|
||||||
class AlertModal extends React.Component<{}, {}> {
|
class AlertModal extends React.Component<{}, {}> {
|
||||||
@ -54,6 +55,7 @@ class AlertModal extends React.Component<{}, {}> {
|
|||||||
</div>
|
</div>
|
||||||
<div className="wave-modal-footer">
|
<div className="wave-modal-footer">
|
||||||
<If condition={isConfirm}>
|
<If condition={isConfirm}>
|
||||||
|
<ModalKeybindings onOk={this.handleOK} onCancel={this.closeModal}></ModalKeybindings>
|
||||||
<Button className="secondary" onClick={this.closeModal}>
|
<Button className="secondary" onClick={this.closeModal}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
@ -62,6 +64,7 @@ class AlertModal extends React.Component<{}, {}> {
|
|||||||
</Button>
|
</Button>
|
||||||
</If>
|
</If>
|
||||||
<If condition={!isConfirm}>
|
<If condition={!isConfirm}>
|
||||||
|
<ModalKeybindings onOk={this.handleOK} onCancel={null}></ModalKeybindings>
|
||||||
<Button autoFocus={true} onClick={this.handleOK}>
|
<Button autoFocus={true} onClick={this.handleOK}>
|
||||||
Ok
|
Ok
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -2,7 +2,7 @@ import * as React from "react";
|
|||||||
import { GlobalModel } from "@/models";
|
import { GlobalModel } from "@/models";
|
||||||
import { Choose, When, If } from "tsx-control-statements/components";
|
import { Choose, When, If } from "tsx-control-statements/components";
|
||||||
import { Modal, PasswordField, TextField, Markdown, Checkbox } from "@/elements";
|
import { Modal, PasswordField, TextField, Markdown, Checkbox } from "@/elements";
|
||||||
import { checkKeyPressed, adaptFromReactOrNativeKeyEvent } from "@/util/keyutil";
|
import { checkKeyPressed, adaptFromReactOrNativeKeyEvent, KeybindManager } from "@/util/keyutil";
|
||||||
|
|
||||||
import "./userinput.less";
|
import "./userinput.less";
|
||||||
|
|
||||||
@ -44,17 +44,20 @@ export const UserInputModal = (userInputRequest: UserInputRequest) => {
|
|||||||
[userInputRequest]
|
[userInputRequest]
|
||||||
);
|
);
|
||||||
|
|
||||||
function handleTextKeyDown(e: React.KeyboardEvent<HTMLInputElement>) {
|
function handleTextFocus() {
|
||||||
let waveEvent = adaptFromReactOrNativeKeyEvent(e);
|
let keybindManager = GlobalModel.keybindManager;
|
||||||
if (checkKeyPressed(waveEvent, "Enter")) {
|
keybindManager.registerKeybinding("modal", "userinput", "generic:confirm", (waveEvent) => {
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
handleSendText();
|
handleSendText();
|
||||||
} else if (checkKeyPressed(waveEvent, "Escape")) {
|
return true;
|
||||||
e.preventDefault();
|
});
|
||||||
e.stopPropagation();
|
keybindManager.registerKeybinding("modal", "userinput", "generic:cancel", (waveEvent) => {
|
||||||
handleSendCancel();
|
handleSendCancel();
|
||||||
}
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTextBlur() {
|
||||||
|
GlobalModel.keybindManager.unregisterDomain("userinput");
|
||||||
}
|
}
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
@ -89,7 +92,8 @@ export const UserInputModal = (userInputRequest: UserInputRequest) => {
|
|||||||
value={responseText}
|
value={responseText}
|
||||||
maxLength={400}
|
maxLength={400}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
onKeyDown={(e) => handleTextKeyDown(e)}
|
onFocus={() => handleTextFocus()}
|
||||||
|
onBlur={() => handleTextBlur()}
|
||||||
/>
|
/>
|
||||||
</If>
|
</If>
|
||||||
<If condition={!userInputRequest.publictext}>
|
<If condition={!userInputRequest.publictext}>
|
||||||
@ -98,7 +102,8 @@ export const UserInputModal = (userInputRequest: UserInputRequest) => {
|
|||||||
value={responseText}
|
value={responseText}
|
||||||
maxLength={400}
|
maxLength={400}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
onKeyDown={(e) => handleTextKeyDown(e)}
|
onFocus={() => handleTextFocus()}
|
||||||
|
onBlur={() => handleTextBlur()}
|
||||||
/>
|
/>
|
||||||
</If>
|
</If>
|
||||||
</If>
|
</If>
|
||||||
|
@ -14,6 +14,7 @@ import * as textmeasure from "@/util/textmeasure";
|
|||||||
import * as appconst from "@/app/appconst";
|
import * as appconst from "@/app/appconst";
|
||||||
|
|
||||||
import "./viewremoteconndetail.less";
|
import "./viewremoteconndetail.less";
|
||||||
|
import { ModalKeybindings } from "../elements/modal";
|
||||||
|
|
||||||
@mobxReact.observer
|
@mobxReact.observer
|
||||||
class ViewRemoteConnDetailModal extends React.Component<{}, {}> {
|
class ViewRemoteConnDetailModal extends React.Component<{}, {}> {
|
||||||
@ -382,6 +383,20 @@ class ViewRemoteConnDetailModal extends React.Component<{}, {}> {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="wave-modal-footer">
|
<div className="wave-modal-footer">
|
||||||
|
<ModalKeybindings
|
||||||
|
onOk={() => {
|
||||||
|
if (selectedRemoteStatus == "connecting") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.handleClose();
|
||||||
|
}}
|
||||||
|
onCancel={() => {
|
||||||
|
if (selectedRemoteStatus == "connecting") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.handleClose();
|
||||||
|
}}
|
||||||
|
></ModalKeybindings>
|
||||||
<Button
|
<Button
|
||||||
className="secondary"
|
className="secondary"
|
||||||
disabled={selectedRemoteStatus == "connecting"}
|
disabled={selectedRemoteStatus == "connecting"}
|
||||||
|
@ -476,23 +476,6 @@ class Model {
|
|||||||
if (isModKeyPress(e)) {
|
if (isModKeyPress(e)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.alertMessage.get() != null) {
|
|
||||||
if (checkKeyPressed(waveEvent, "Escape")) {
|
|
||||||
e.preventDefault();
|
|
||||||
this.modalsModel.popModal(() => this.cancelAlert());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (checkKeyPressed(waveEvent, "Enter")) {
|
|
||||||
e.preventDefault();
|
|
||||||
this.confirmAlert();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (checkKeyPressed(waveEvent, "Escape") && this.modalsModel.store.length > 0) {
|
|
||||||
this.modalsModel.popModal();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.keybindManager.processKeyEvent(e, waveEvent)) {
|
if (this.keybindManager.processKeyEvent(e, waveEvent)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user