mirror of
https://github.com/wavetermdev/backup.git
synced 2026-04-22 15:26:58 -07:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 01c9a3444a | |||
| 69b9ce33b3 | |||
| a121bd4bb5 | |||
| dcc7b2943e | |||
| bfa549ef22 | |||
| 376e339dfe | |||
| 646e260488 | |||
| d3c48e3a3e | |||
| c0c53edb84 | |||
| 923cf71e0a | |||
| f705a4df0a | |||
| 75a82de5bf |
@@ -1,7 +1,8 @@
|
||||
[
|
||||
{
|
||||
"command": "system:toggleDeveloperTools",
|
||||
"keys": ["Cmd:Option:i"]
|
||||
"keys": ["Cmd:Option:i"],
|
||||
"info": "Opens the chrome developer tool menu"
|
||||
},
|
||||
{
|
||||
"command": "system:hideWindow",
|
||||
@@ -15,10 +16,62 @@
|
||||
"command": "generic:confirm",
|
||||
"keys": ["Enter"]
|
||||
},
|
||||
{
|
||||
"command": "generic:expandTextInput",
|
||||
"keys": ["Shift:Enter", "Ctrl:Enter"]
|
||||
},
|
||||
{
|
||||
"command": "generic:deleteItem",
|
||||
"keys": ["Backspace", "Delete"]
|
||||
},
|
||||
{
|
||||
"command": "generic:space",
|
||||
"keys": ["Space"]
|
||||
},
|
||||
{
|
||||
"command": "generic:tab",
|
||||
"keys": ["Tab"]
|
||||
},
|
||||
{
|
||||
"command": "generic:numpad-0",
|
||||
"keys": ["0"]
|
||||
},
|
||||
{
|
||||
"command": "generic:numpad-1",
|
||||
"keys": ["1"]
|
||||
},
|
||||
{
|
||||
"command": "generic:numpad-2",
|
||||
"keys": ["2"]
|
||||
},
|
||||
{
|
||||
"command": "generic:numpad-3",
|
||||
"keys": ["3"]
|
||||
},
|
||||
{
|
||||
"command": "generic:numpad-4",
|
||||
"keys": ["4"]
|
||||
},
|
||||
{
|
||||
"command": "generic:numpad-5",
|
||||
"keys": ["5"]
|
||||
},
|
||||
{
|
||||
"command": "generic:numpad-6",
|
||||
"keys": ["6"]
|
||||
},
|
||||
{
|
||||
"command": "generic:numpad-7",
|
||||
"keys": ["7"]
|
||||
},
|
||||
{
|
||||
"command": "generic:numpad-8",
|
||||
"keys": ["8"]
|
||||
},
|
||||
{
|
||||
"command": "generic:numpad-9",
|
||||
"keys": ["9"]
|
||||
},
|
||||
{
|
||||
"command": "generic:selectAbove",
|
||||
"keys": ["ArrowUp"]
|
||||
@@ -27,6 +80,14 @@
|
||||
"command": "generic:selectBelow",
|
||||
"keys": ["ArrowDown"]
|
||||
},
|
||||
{
|
||||
"command": "generic:selectLeft",
|
||||
"keys": ["ArrowLeft"]
|
||||
},
|
||||
{
|
||||
"command": "generic:selectRight",
|
||||
"keys": ["ArrowRight"]
|
||||
},
|
||||
{
|
||||
"command": "generic:selectPageAbove",
|
||||
"keys": ["PageUp"]
|
||||
@@ -67,7 +128,8 @@
|
||||
},
|
||||
{
|
||||
"command": "app:restartCommand",
|
||||
"keys": ["Cmd:r"]
|
||||
"keys": ["Cmd:r"],
|
||||
"info": "Restarts the command running in the current selected line"
|
||||
},
|
||||
{
|
||||
"command": "app:restartLastCommand",
|
||||
|
||||
+14
@@ -134,14 +134,28 @@ github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaD
|
||||
gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE=
|
||||
go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A=
|
||||
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
||||
google.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
<base href="../" />
|
||||
<script charset="UTF-8" src="dist-dev/waveterm.js"></script>
|
||||
<link rel="stylesheet" href="public/bulma-0.9.4.min.css" />
|
||||
<link rel="stylesheet" href="public/fontawesome/css/fontawesome.min.css" />
|
||||
<link rel="stylesheet" href="public/fontawesome/css/brands.min.css" />
|
||||
<link rel="stylesheet" href="public/fontawesome/css/solid.min.css" />
|
||||
<link rel="stylesheet" href="public/fontawesome/css/sharp-solid.min.css" />
|
||||
<link rel="stylesheet" href="public/fontawesome/css/sharp-regular.min.css" />
|
||||
<link rel="stylesheet" href="public/fontawesome/css/fontawesome.min.css">
|
||||
<link rel="stylesheet" href="public/fontawesome/css/brands.min.css">
|
||||
<link rel="stylesheet" href="public/fontawesome/css/solid.min.css">
|
||||
<link rel="stylesheet" href="public/fontawesome/css/sharp-solid.min.css">
|
||||
<link rel="stylesheet" href="public/fontawesome/css/sharp-regular.min.css">
|
||||
<link rel="stylesheet" href="dist-dev/waveterm.css" />
|
||||
|
||||
<link rel="stylesheet" id="theme-stylesheet" href="public/themes/default.css" />
|
||||
<link rel="stylesheet" id="theme-stylesheet" href="public/themes/default.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="measure"></div>
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import React, { useState, useEffect, useRef, createRef } from "react";
|
||||
import * as mobx from "mobx";
|
||||
import ReactDOM from "react-dom";
|
||||
import dayjs from "dayjs";
|
||||
import cs from "classnames";
|
||||
import { Button } from "@/elements";
|
||||
import { If } from "tsx-control-statements/components";
|
||||
import { GlobalModel } from "@/models";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
import "./datepicker.less";
|
||||
|
||||
@@ -36,6 +39,10 @@ const DatePicker: React.FC<DatePickerProps> = ({ selectedDate, format = "MM/DD/Y
|
||||
MM: selDate.format("MM"),
|
||||
DD: selDate.format("DD"),
|
||||
});
|
||||
let curUuid = uuidv4();
|
||||
let keybindsRegistered = mobx.observable.box(false, {
|
||||
name: "datepicker-keybinds-registered",
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
inputRefs.current = {
|
||||
@@ -273,6 +280,11 @@ const DatePicker: React.FC<DatePickerProps> = ({ selectedDate, format = "MM/DD/Y
|
||||
setShowYearAccordion(false);
|
||||
};
|
||||
|
||||
const closeModal = () => {
|
||||
setIsOpen(false);
|
||||
setShowYearAccordion(false);
|
||||
};
|
||||
|
||||
const dayPickerModal = isOpen
|
||||
? ReactDOM.createPortal(
|
||||
<div ref={modalRef} className="day-picker-modal" style={calculatePosition()}>
|
||||
@@ -300,13 +312,13 @@ const DatePicker: React.FC<DatePickerProps> = ({ selectedDate, format = "MM/DD/Y
|
||||
}
|
||||
};
|
||||
|
||||
const handleArrowNavigation = (event, currentPart) => {
|
||||
const handleArrowNavigation = (key, currentPart) => {
|
||||
const currentIndex = formatParts.indexOf(currentPart);
|
||||
let targetInput;
|
||||
|
||||
if (event.key === "ArrowLeft" && currentIndex > 0) {
|
||||
if (key == "ArrowLeft" && currentIndex > 0) {
|
||||
targetInput = inputRefs.current[formatParts[currentIndex - 1]].current;
|
||||
} else if (event.key === "ArrowRight" && currentIndex < formatParts.length - 1) {
|
||||
} else if (key == "ArrowRight" && currentIndex < formatParts.length - 1) {
|
||||
targetInput = inputRefs.current[formatParts[currentIndex + 1]].current;
|
||||
}
|
||||
|
||||
@@ -315,51 +327,91 @@ const DatePicker: React.FC<DatePickerProps> = ({ selectedDate, format = "MM/DD/Y
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyDown = (event, currentPart) => {
|
||||
const key = event.key;
|
||||
const handleKeyDown = (event, currentPart) => {};
|
||||
|
||||
if (key === "ArrowLeft" || key === "ArrowRight") {
|
||||
// Handle arrow navigation without selecting text
|
||||
handleArrowNavigation(event, currentPart);
|
||||
const handleFocus = (event, part) => {
|
||||
event.target.select();
|
||||
registerKeybindings(event, part);
|
||||
};
|
||||
|
||||
const registerKeybindings = (event: any, part: string) => {
|
||||
if (keybindsRegistered.get() == true) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (key === " ") {
|
||||
// Handle spacebar press to toggle the modal
|
||||
mobx.action(() => {
|
||||
keybindsRegistered.set(true);
|
||||
})();
|
||||
let keybindManager = GlobalModel.keybindManager;
|
||||
let domain = "datepicker-" + curUuid + "-" + part;
|
||||
keybindManager.registerKeybinding("control", domain, "generic:selectLeft", (waveEvent) => {
|
||||
handleArrowNavigation("ArrowLeft", part);
|
||||
return true;
|
||||
});
|
||||
keybindManager.registerKeybinding("control", domain, "generic:selectRight", (waveEvent) => {
|
||||
handleArrowNavigation("ArrowRight", part);
|
||||
return true;
|
||||
});
|
||||
keybindManager.registerKeybinding("control", domain, "generic:space", (waveEvent) => {
|
||||
toggleModal();
|
||||
return;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
keybindManager.registerKeybinding("control", domain, "generic:confirm", (waveEvent) => {
|
||||
toggleModal();
|
||||
return true;
|
||||
});
|
||||
keybindManager.registerKeybinding("control", domain, "generic:cancel", (waveEvent) => {
|
||||
closeModal();
|
||||
return true;
|
||||
});
|
||||
keybindManager.registerKeybinding("control", domain, "generic:tab", (waveEvent) => {
|
||||
handleArrowNavigation("ArrowRight", part);
|
||||
return true;
|
||||
});
|
||||
for (let numpadKey = 0; numpadKey <= 9; numpadKey++) {
|
||||
keybindManager.registerKeybinding(
|
||||
"control",
|
||||
domain,
|
||||
"generic:numpad-" + numpadKey.toString(),
|
||||
(waveEvent) => {
|
||||
let currentPart = part;
|
||||
const maxLength = currentPart === "YYYY" ? 4 : 2;
|
||||
const newValue = event.target.value.length < maxLength ? event.target.value + numpadKey : numpadKey;
|
||||
let selectionTimeoutId = null;
|
||||
handleDatePartChange(currentPart, newValue);
|
||||
|
||||
if (key.match(/[0-9]/)) {
|
||||
// Handle numeric keys
|
||||
event.preventDefault();
|
||||
const maxLength = currentPart === "YYYY" ? 4 : 2;
|
||||
const newValue = event.target.value.length < maxLength ? event.target.value + key : key;
|
||||
let selectionTimeoutId = null;
|
||||
handleDatePartChange(currentPart, newValue);
|
||||
// Clear any existing timeout
|
||||
if (selectionTimeoutId !== null) {
|
||||
clearTimeout(selectionTimeoutId);
|
||||
}
|
||||
|
||||
// Clear any existing timeout
|
||||
if (selectionTimeoutId !== null) {
|
||||
clearTimeout(selectionTimeoutId);
|
||||
}
|
||||
|
||||
// Re-focus and select the input after state update
|
||||
selectionTimeoutId = setTimeout(() => {
|
||||
event.target.focus();
|
||||
event.target.select();
|
||||
}, 0);
|
||||
// Re-focus and select the input after state update
|
||||
selectionTimeoutId = setTimeout(() => {
|
||||
event.target.focus();
|
||||
event.target.select();
|
||||
}, 0);
|
||||
return true;
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleFocus = (event) => {
|
||||
event.target.select();
|
||||
const handleBlur = (event, part) => {
|
||||
unregisterKeybindings(part);
|
||||
};
|
||||
|
||||
const unregisterKeybindings = (part) => {
|
||||
mobx.action(() => {
|
||||
keybindsRegistered.set(false);
|
||||
})();
|
||||
let domain = "datepicker-" + curUuid + "-" + part;
|
||||
GlobalModel.keybindManager.unregisterDomain(domain);
|
||||
};
|
||||
|
||||
// Prevent use from selecting text in the input
|
||||
const handleMouseDown = (event) => {
|
||||
const handleMouseDown = (event, part) => {
|
||||
event.preventDefault();
|
||||
|
||||
handleFocus(event);
|
||||
handleFocus(event, part);
|
||||
};
|
||||
|
||||
const handleIconKeyDown = (event) => {
|
||||
@@ -413,8 +465,9 @@ const DatePicker: React.FC<DatePickerProps> = ({ selectedDate, format = "MM/DD/Y
|
||||
value={dateParts[part]}
|
||||
onChange={(e) => handleDatePartChange(part, e.target.value)}
|
||||
onKeyDown={(e) => handleKeyDown(e, part)}
|
||||
onMouseDown={handleMouseDown}
|
||||
onFocus={handleFocus}
|
||||
onMouseDown={(e) => handleMouseDown(e, part)}
|
||||
onFocus={(e) => handleFocus(e, part)}
|
||||
onBlur={(e) => handleBlur(e, part)}
|
||||
maxLength={part === "YYYY" ? 4 : 2}
|
||||
className="date-input"
|
||||
placeholder={part}
|
||||
|
||||
@@ -7,6 +7,8 @@ import { boundMethod } from "autobind-decorator";
|
||||
import cn from "classnames";
|
||||
import { If } from "tsx-control-statements/components";
|
||||
import ReactDOM from "react-dom";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { GlobalModel } from "@/models";
|
||||
|
||||
import "./dropdown.less";
|
||||
|
||||
@@ -39,6 +41,7 @@ class Dropdown extends React.Component<DropdownProps, DropdownState> {
|
||||
wrapperRef: React.RefObject<HTMLDivElement>;
|
||||
menuRef: React.RefObject<HTMLDivElement>;
|
||||
timeoutId: any;
|
||||
curUuid: string;
|
||||
|
||||
constructor(props: DropdownProps) {
|
||||
super(props);
|
||||
@@ -50,6 +53,7 @@ class Dropdown extends React.Component<DropdownProps, DropdownState> {
|
||||
};
|
||||
this.wrapperRef = React.createRef();
|
||||
this.menuRef = React.createRef();
|
||||
this.curUuid == uuidv4();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@@ -102,49 +106,76 @@ class Dropdown extends React.Component<DropdownProps, DropdownState> {
|
||||
@boundMethod
|
||||
handleFocus() {
|
||||
this.setState({ isTouched: true });
|
||||
this.registerKeybindings();
|
||||
}
|
||||
|
||||
registerKeybindings() {
|
||||
let keybindManager = GlobalModel.keybindManager;
|
||||
let domain = "dropdown-" + this.curUuid;
|
||||
keybindManager.registerKeybinding("control", domain, "generic:confirm", (waveEvent) => {
|
||||
this.handleConfirm();
|
||||
return true;
|
||||
});
|
||||
keybindManager.registerKeybinding("control", domain, "generic:space", (waveEvent) => {
|
||||
this.handleConfirm();
|
||||
return true;
|
||||
});
|
||||
keybindManager.registerKeybinding("control", domain, "generic:cancel", (waveEvent) => {
|
||||
this.setState({ isOpen: false });
|
||||
return true;
|
||||
});
|
||||
keybindManager.registerKeybinding("control", domain, "generic:selectAbove", (waveEvent) => {
|
||||
const { isOpen } = this.state;
|
||||
const { options } = this.props;
|
||||
if (isOpen) {
|
||||
this.setState((prevState) => ({
|
||||
highlightedIndex:
|
||||
prevState.highlightedIndex > 0 ? prevState.highlightedIndex - 1 : options.length - 1,
|
||||
}));
|
||||
}
|
||||
return true;
|
||||
});
|
||||
keybindManager.registerKeybinding("control", domain, "generic:selectBelow", (waveEvent) => {
|
||||
const { isOpen } = this.state;
|
||||
const { options } = this.props;
|
||||
if (isOpen) {
|
||||
this.setState((prevState) => ({
|
||||
highlightedIndex:
|
||||
prevState.highlightedIndex < options.length - 1 ? prevState.highlightedIndex + 1 : 0,
|
||||
}));
|
||||
}
|
||||
return true;
|
||||
});
|
||||
keybindManager.registerKeybinding("control", domain, "generic:tab", (waveEvent) => {
|
||||
this.setState({ isOpen: false });
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
handleConfirm() {
|
||||
const { options } = this.props;
|
||||
const { isOpen, highlightedIndex } = this.state;
|
||||
if (isOpen) {
|
||||
const option = options[highlightedIndex];
|
||||
if (option) {
|
||||
this.handleSelect(option.value, undefined);
|
||||
}
|
||||
} else {
|
||||
this.toggleDropdown();
|
||||
}
|
||||
}
|
||||
|
||||
handleBlur() {
|
||||
this.unregisterKeybindings();
|
||||
}
|
||||
|
||||
unregisterKeybindings() {
|
||||
let domain = "dropdown-" + this.curUuid;
|
||||
GlobalModel.keybindManager.unregisterDomain(domain);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleKeyDown(event: React.KeyboardEvent) {
|
||||
const { options } = this.props;
|
||||
const { isOpen, highlightedIndex } = this.state;
|
||||
|
||||
switch (event.key) {
|
||||
case "Enter":
|
||||
case " ":
|
||||
if (isOpen) {
|
||||
const option = options[highlightedIndex];
|
||||
if (option) {
|
||||
this.handleSelect(option.value, undefined);
|
||||
}
|
||||
} else {
|
||||
this.toggleDropdown();
|
||||
}
|
||||
break;
|
||||
case "Escape":
|
||||
this.setState({ isOpen: false });
|
||||
break;
|
||||
case "ArrowUp":
|
||||
if (isOpen) {
|
||||
this.setState((prevState) => ({
|
||||
highlightedIndex:
|
||||
prevState.highlightedIndex > 0 ? prevState.highlightedIndex - 1 : options.length - 1,
|
||||
}));
|
||||
}
|
||||
break;
|
||||
case "ArrowDown":
|
||||
if (isOpen) {
|
||||
this.setState((prevState) => ({
|
||||
highlightedIndex:
|
||||
prevState.highlightedIndex < options.length - 1 ? prevState.highlightedIndex + 1 : 0,
|
||||
}));
|
||||
}
|
||||
break;
|
||||
case "Tab":
|
||||
this.setState({ isOpen: false });
|
||||
break;
|
||||
}
|
||||
}
|
||||
handleKeyDown(event: React.KeyboardEvent) {}
|
||||
|
||||
@boundMethod
|
||||
handleSelect(value: string, event?: React.MouseEvent | React.KeyboardEvent) {
|
||||
@@ -228,7 +259,8 @@ class Dropdown extends React.Component<DropdownProps, DropdownState> {
|
||||
tabIndex={0}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
onClick={this.handleClick}
|
||||
onFocus={this.handleFocus}
|
||||
onFocus={this.handleFocus.bind(this)}
|
||||
onBlur={this.handleBlur.bind(this)}
|
||||
>
|
||||
{decoration?.startDecoration && <>{decoration.startDecoration}</>}
|
||||
<If condition={label}>
|
||||
|
||||
@@ -8,6 +8,8 @@ import { boundMethod } from "autobind-decorator";
|
||||
import cn from "classnames";
|
||||
import { If } from "tsx-control-statements/components";
|
||||
import { checkKeyPressed, adaptFromReactOrNativeKeyEvent } from "@/util/keyutil";
|
||||
import { GlobalModel } from "@/models";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
import "./inlinesettingstextedit.less";
|
||||
|
||||
@@ -27,6 +29,11 @@ class InlineSettingsTextEdit extends React.Component<
|
||||
tempText: OV<string>;
|
||||
shouldFocus: boolean = false;
|
||||
inputRef: React.RefObject<any> = React.createRef();
|
||||
curId: string;
|
||||
|
||||
componentDidMount(): void {
|
||||
this.curId = uuidv4();
|
||||
}
|
||||
|
||||
componentDidUpdate(): void {
|
||||
if (this.shouldFocus) {
|
||||
@@ -52,6 +59,7 @@ class InlineSettingsTextEdit extends React.Component<
|
||||
this.tempText = null;
|
||||
this.props.onChange(newText);
|
||||
})();
|
||||
this.unregisterKeybindings();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
@@ -60,24 +68,38 @@ class InlineSettingsTextEdit extends React.Component<
|
||||
this.isEditing.set(false);
|
||||
this.tempText = null;
|
||||
})();
|
||||
this.unregisterKeybindings();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleKeyDown(e: any): void {
|
||||
let waveEvent = adaptFromReactOrNativeKeyEvent(e);
|
||||
if (checkKeyPressed(waveEvent, "Enter")) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
handleFocus() {
|
||||
this.registerKeybindings();
|
||||
}
|
||||
|
||||
registerKeybindings() {
|
||||
let keybindManager = GlobalModel.keybindManager;
|
||||
let domain = "inline-settings" + this.curId;
|
||||
keybindManager.registerKeybinding("control", domain, "generic:confirm", (waveEvent) => {
|
||||
this.confirmChange();
|
||||
return;
|
||||
}
|
||||
if (checkKeyPressed(waveEvent, "Escape")) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return true;
|
||||
});
|
||||
keybindManager.registerKeybinding("control", domain, "generic:cancel", (waveEvent) => {
|
||||
this.cancelChange();
|
||||
return;
|
||||
}
|
||||
return;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
unregisterKeybindings() {
|
||||
let domain = "inline-settings" + this.curId;
|
||||
GlobalModel.keybindManager.unregisterDomain(domain);
|
||||
}
|
||||
|
||||
handleBlur() {
|
||||
this.unregisterKeybindings();
|
||||
this.cancelChange();
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
this.unregisterKeybindings();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
@@ -99,7 +121,8 @@ class InlineSettingsTextEdit extends React.Component<
|
||||
ref={this.inputRef}
|
||||
className="input"
|
||||
type="text"
|
||||
onKeyDown={this.handleKeyDown}
|
||||
onFocus={this.handleFocus.bind(this)}
|
||||
onBlur={this.handleBlur.bind(this)}
|
||||
placeholder={this.props.placeholder}
|
||||
onChange={this.handleChangeText}
|
||||
value={this.tempText.get()}
|
||||
|
||||
@@ -6,8 +6,11 @@ import * as mobx from "mobx";
|
||||
import { If } from "tsx-control-statements/components";
|
||||
import ReactDOM from "react-dom";
|
||||
import { Button } from "./button";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { GlobalModel } from "@/models";
|
||||
|
||||
import "./modal.less";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
|
||||
interface ModalHeaderProps {
|
||||
onClose?: () => void;
|
||||
@@ -30,10 +33,52 @@ interface ModalFooterProps {
|
||||
onOk?: () => void;
|
||||
cancelLabel?: 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">
|
||||
<If condition={keybindings}>
|
||||
<ModalKeybindings onOk={onOk} onCancel={onCancel}></ModalKeybindings>
|
||||
</If>
|
||||
{onCancel && (
|
||||
<Button className="secondary" onClick={onCancel}>
|
||||
{cancelLabel}
|
||||
@@ -79,4 +124,4 @@ class Modal extends React.Component<ModalProps> {
|
||||
}
|
||||
}
|
||||
|
||||
export { Modal };
|
||||
export { Modal, ModalKeybindings };
|
||||
|
||||
@@ -18,6 +18,8 @@ interface TextFieldProps {
|
||||
className?: string;
|
||||
onChange?: (value: string) => void;
|
||||
onKeyDown?: (event: React.KeyboardEvent<HTMLInputElement>) => void;
|
||||
onFocus?: () => void;
|
||||
onBlur?: () => void;
|
||||
placeholder?: string;
|
||||
defaultValue?: string;
|
||||
decoration?: TextFieldDecorationProps;
|
||||
@@ -78,6 +80,9 @@ class TextField extends React.Component<TextFieldProps, TextFieldState> {
|
||||
@boundMethod
|
||||
handleFocus() {
|
||||
this.setState({ focused: true });
|
||||
if (this.props.onFocus) {
|
||||
this.props.onFocus();
|
||||
}
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
@@ -91,6 +96,9 @@ class TextField extends React.Component<TextFieldProps, TextFieldState> {
|
||||
this.setState({ error: false, focused: false });
|
||||
}
|
||||
}
|
||||
if (this.props.onBlur) {
|
||||
this.props.onBlur();
|
||||
}
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Markdown, Modal, Button, Checkbox } from "@/elements";
|
||||
import { GlobalModel, GlobalCommandRunner } from "@/models";
|
||||
|
||||
import "./alert.less";
|
||||
import { ModalKeybindings } from "../elements/modal";
|
||||
|
||||
@mobxReact.observer
|
||||
class AlertModal extends React.Component<{}, {}> {
|
||||
@@ -54,6 +55,7 @@ class AlertModal extends React.Component<{}, {}> {
|
||||
</div>
|
||||
<div className="wave-modal-footer">
|
||||
<If condition={isConfirm}>
|
||||
<ModalKeybindings onOk={this.handleOK} onCancel={this.closeModal}></ModalKeybindings>
|
||||
<Button className="secondary" onClick={this.closeModal}>
|
||||
Cancel
|
||||
</Button>
|
||||
@@ -62,6 +64,7 @@ class AlertModal extends React.Component<{}, {}> {
|
||||
</Button>
|
||||
</If>
|
||||
<If condition={!isConfirm}>
|
||||
<ModalKeybindings onOk={this.handleOK} onCancel={null}></ModalKeybindings>
|
||||
<Button autoFocus={true} onClick={this.handleOK}>
|
||||
Ok
|
||||
</Button>
|
||||
|
||||
@@ -2,7 +2,6 @@ import * as React from "react";
|
||||
import { GlobalModel } from "@/models";
|
||||
import { Choose, When, If } from "tsx-control-statements/components";
|
||||
import { Modal, PasswordField, TextField, Markdown, Checkbox } from "@/elements";
|
||||
import { checkKeyPressed, adaptFromReactOrNativeKeyEvent } from "@/util/keyutil";
|
||||
|
||||
import "./userinput.less";
|
||||
|
||||
@@ -44,19 +43,6 @@ export const UserInputModal = (userInputRequest: UserInputRequest) => {
|
||||
[userInputRequest]
|
||||
);
|
||||
|
||||
function handleTextKeyDown(e: React.KeyboardEvent<HTMLInputElement>) {
|
||||
let waveEvent = adaptFromReactOrNativeKeyEvent(e);
|
||||
if (checkKeyPressed(waveEvent, "Enter")) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
handleSendText();
|
||||
} else if (checkKeyPressed(waveEvent, "Escape")) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
handleSendCancel();
|
||||
}
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
if (countdown == 0) {
|
||||
@@ -89,7 +75,6 @@ export const UserInputModal = (userInputRequest: UserInputRequest) => {
|
||||
value={responseText}
|
||||
maxLength={400}
|
||||
autoFocus={true}
|
||||
onKeyDown={(e) => handleTextKeyDown(e)}
|
||||
/>
|
||||
</If>
|
||||
<If condition={!userInputRequest.publictext}>
|
||||
@@ -98,7 +83,6 @@ export const UserInputModal = (userInputRequest: UserInputRequest) => {
|
||||
value={responseText}
|
||||
maxLength={400}
|
||||
autoFocus={true}
|
||||
onKeyDown={(e) => handleTextKeyDown(e)}
|
||||
/>
|
||||
</If>
|
||||
</If>
|
||||
|
||||
@@ -14,6 +14,7 @@ import * as textmeasure from "@/util/textmeasure";
|
||||
import * as appconst from "@/app/appconst";
|
||||
|
||||
import "./viewremoteconndetail.less";
|
||||
import { ModalKeybindings } from "../elements/modal";
|
||||
|
||||
@mobxReact.observer
|
||||
class ViewRemoteConnDetailModal extends React.Component<{}, {}> {
|
||||
@@ -382,6 +383,20 @@ class ViewRemoteConnDetailModal extends React.Component<{}, {}> {
|
||||
</div>
|
||||
</div>
|
||||
<div className="wave-modal-footer">
|
||||
<ModalKeybindings
|
||||
onOk={() => {
|
||||
if (selectedRemoteStatus == "connecting") {
|
||||
return;
|
||||
}
|
||||
this.handleClose();
|
||||
}}
|
||||
onCancel={() => {
|
||||
if (selectedRemoteStatus == "connecting") {
|
||||
return;
|
||||
}
|
||||
this.handleClose();
|
||||
}}
|
||||
></ModalKeybindings>
|
||||
<Button
|
||||
className="secondary"
|
||||
disabled={selectedRemoteStatus == "connecting"}
|
||||
|
||||
@@ -8,21 +8,64 @@ import { GlobalModel } from "@/models";
|
||||
import { isBlank } from "@/util/util";
|
||||
import { boundMethod } from "autobind-decorator";
|
||||
import cn from "classnames";
|
||||
import { For } from "tsx-control-statements/components";
|
||||
import { If, For } from "tsx-control-statements/components";
|
||||
import { Markdown } from "@/elements";
|
||||
import { checkKeyPressed, adaptFromReactOrNativeKeyEvent } from "@/util/keyutil";
|
||||
|
||||
class AIChatKeybindings extends React.Component<{ AIChatObject: AIChat }, {}> {
|
||||
componentDidMount(): void {
|
||||
let AIChatObject = this.props.AIChatObject;
|
||||
let keybindManager = GlobalModel.keybindManager;
|
||||
let inputModel = GlobalModel.inputModel;
|
||||
|
||||
keybindManager.registerKeybinding("pane", "aichat", "generic:confirm", (waveEvent) => {
|
||||
AIChatObject.onEnterKeyPressed();
|
||||
return true;
|
||||
});
|
||||
keybindManager.registerKeybinding("pane", "aichat", "generic:expandTextInput", (waveEvent) => {
|
||||
AIChatObject.onExpandInputPressed();
|
||||
return true;
|
||||
});
|
||||
keybindManager.registerKeybinding("pane", "aichat", "generic:cancel", (waveEvent) => {
|
||||
inputModel.closeAIAssistantChat(true);
|
||||
return true;
|
||||
});
|
||||
keybindManager.registerKeybinding("pane", "aichat", "aichat:clearHistory", (waveEvent) => {
|
||||
inputModel.clearAIAssistantChat();
|
||||
return true;
|
||||
});
|
||||
keybindManager.registerKeybinding("pane", "aichat", "generic:selectAbove", (waveEvent) => {
|
||||
return AIChatObject.onArrowUpPressed();
|
||||
});
|
||||
keybindManager.registerKeybinding("pane", "aichat", "generic:selectBelow", (waveEvent) => {
|
||||
return AIChatObject.onArrowDownPressed();
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
GlobalModel.keybindManager.unregisterDomain("aichat");
|
||||
}
|
||||
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class AIChat extends React.Component<{}, {}> {
|
||||
chatListKeyCount: number = 0;
|
||||
textAreaNumLines: mobx.IObservableValue<number> = mobx.observable.box(1, { name: "textAreaNumLines" });
|
||||
chatWindowScrollRef: React.RefObject<HTMLDivElement>;
|
||||
textAreaRef: React.RefObject<HTMLTextAreaElement>;
|
||||
isFocused: OV<boolean>;
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
this.chatWindowScrollRef = React.createRef();
|
||||
this.textAreaRef = React.createRef();
|
||||
this.isFocused = mobx.observable.box(false, {
|
||||
name: "aichat-isfocused",
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@@ -66,67 +109,82 @@ class AIChat extends React.Component<{}, {}> {
|
||||
return { numLines, linePos };
|
||||
}
|
||||
|
||||
@mobx.action
|
||||
@boundMethod
|
||||
onKeyDown(e: any) {
|
||||
onTextAreaFocused(e: any) {
|
||||
mobx.action(() => {
|
||||
let model = GlobalModel;
|
||||
let inputModel = model.inputModel;
|
||||
let ctrlMod = e.getModifierState("Control") || e.getModifierState("Meta") || e.getModifierState("Shift");
|
||||
let resetCodeSelect = !ctrlMod;
|
||||
let waveEvent = adaptFromReactOrNativeKeyEvent(e);
|
||||
if (checkKeyPressed(waveEvent, "Enter")) {
|
||||
e.preventDefault();
|
||||
if (!ctrlMod) {
|
||||
if (inputModel.getCodeSelectSelectedIndex() == -1) {
|
||||
let messageStr = e.target.value;
|
||||
this.submitChatMessage(messageStr);
|
||||
e.target.value = "";
|
||||
} else {
|
||||
inputModel.grabCodeSelectSelection();
|
||||
}
|
||||
} else {
|
||||
e.target.setRangeText("\n", e.target.selectionStart, e.target.selectionEnd, "end");
|
||||
}
|
||||
}
|
||||
if (checkKeyPressed(waveEvent, "Escape")) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
inputModel.closeAIAssistantChat(true);
|
||||
}
|
||||
|
||||
if (checkKeyPressed(waveEvent, "Ctrl:l")) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
inputModel.clearAIAssistantChat();
|
||||
}
|
||||
if (checkKeyPressed(waveEvent, "ArrowUp")) {
|
||||
if (this.getLinePos(e.target).linePos > 1) {
|
||||
// normal up arrow
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
inputModel.codeSelectSelectNextOldestCodeBlock();
|
||||
resetCodeSelect = false;
|
||||
}
|
||||
if (checkKeyPressed(waveEvent, "ArrowDown")) {
|
||||
if (inputModel.getCodeSelectSelectedIndex() == inputModel.codeSelectBottom) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
inputModel.codeSelectSelectNextNewestCodeBlock();
|
||||
resetCodeSelect = false;
|
||||
}
|
||||
|
||||
if (resetCodeSelect) {
|
||||
inputModel.codeSelectDeselectAll();
|
||||
}
|
||||
|
||||
// set height of textarea based on number of newlines
|
||||
this.textAreaNumLines.set(e.target.value.split(/\n/).length);
|
||||
this.isFocused.set(true);
|
||||
})();
|
||||
}
|
||||
|
||||
onTextAreaBlur(e: any) {
|
||||
mobx.action(() => {
|
||||
this.isFocused.set(false);
|
||||
})();
|
||||
}
|
||||
|
||||
onTextAreaChange(e: any) {
|
||||
// set height of textarea based on number of newlines
|
||||
mobx.action(() => {
|
||||
this.textAreaNumLines.set(e.target.value.split(/\n/).length);
|
||||
GlobalModel.inputModel.codeSelectDeselectAll();
|
||||
})();
|
||||
}
|
||||
|
||||
onEnterKeyPressed() {
|
||||
let inputModel = GlobalModel.inputModel;
|
||||
let currentRef = this.textAreaRef.current;
|
||||
if (currentRef == null) {
|
||||
return;
|
||||
}
|
||||
if (inputModel.getCodeSelectSelectedIndex() == -1) {
|
||||
let messageStr = currentRef.value;
|
||||
this.submitChatMessage(messageStr);
|
||||
currentRef.value = "";
|
||||
} else {
|
||||
inputModel.grabCodeSelectSelection();
|
||||
}
|
||||
}
|
||||
|
||||
onExpandInputPressed() {
|
||||
let currentRef = this.textAreaRef.current;
|
||||
if (currentRef == null) {
|
||||
return;
|
||||
}
|
||||
currentRef.setRangeText("\n", currentRef.selectionStart, currentRef.selectionEnd, "end");
|
||||
GlobalModel.inputModel.codeSelectDeselectAll();
|
||||
}
|
||||
|
||||
onArrowUpPressed(): boolean {
|
||||
let currentRef = this.textAreaRef.current;
|
||||
if (currentRef == null) {
|
||||
return false;
|
||||
}
|
||||
if (this.getLinePos(currentRef).linePos > 1) {
|
||||
// normal up arrow
|
||||
GlobalModel.inputModel.codeSelectDeselectAll();
|
||||
return false;
|
||||
}
|
||||
GlobalModel.inputModel.codeSelectSelectNextOldestCodeBlock();
|
||||
return true;
|
||||
}
|
||||
|
||||
onArrowDownPressed(): boolean {
|
||||
let currentRef = this.textAreaRef.current;
|
||||
let inputModel = GlobalModel.inputModel;
|
||||
if (currentRef == null) {
|
||||
return false;
|
||||
}
|
||||
if (inputModel.getCodeSelectSelectedIndex() == inputModel.codeSelectBottom) {
|
||||
GlobalModel.inputModel.codeSelectDeselectAll();
|
||||
return false;
|
||||
}
|
||||
inputModel.codeSelectSelectNextNewestCodeBlock();
|
||||
return true;
|
||||
}
|
||||
|
||||
@mobx.action
|
||||
@boundMethod
|
||||
onKeyDown(e: any) {}
|
||||
|
||||
renderError(err: string): any {
|
||||
return <div className="chat-msg-error">{err}</div>;
|
||||
}
|
||||
@@ -192,9 +250,13 @@ class AIChat extends React.Component<{}, {}> {
|
||||
const textAreaPadding = 2 * 0.5 * termFontSize;
|
||||
let textAreaMaxHeight = textAreaLineHeight * textAreaMaxLines + textAreaPadding;
|
||||
let textAreaInnerHeight = this.textAreaNumLines.get() * textAreaLineHeight + textAreaPadding;
|
||||
let isFocused = this.isFocused.get();
|
||||
|
||||
return (
|
||||
<div className="cmd-aichat">
|
||||
<If condition={isFocused}>
|
||||
<AIChatKeybindings AIChatObject={this}></AIChatKeybindings>
|
||||
</If>
|
||||
<div className="cmdinput-titlebar">
|
||||
<div className="title-icon">
|
||||
<i className="fa-sharp fa-solid fa-sparkles" />
|
||||
@@ -217,6 +279,9 @@ class AIChat extends React.Component<{}, {}> {
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
id="chat-cmd-input"
|
||||
onFocus={this.onTextAreaFocused.bind(this)}
|
||||
onBlur={this.onTextAreaBlur.bind(this)}
|
||||
onChange={this.onTextAreaChange.bind(this)}
|
||||
onKeyDown={this.onKeyDown}
|
||||
style={{ height: textAreaInnerHeight, maxHeight: textAreaMaxHeight, fontSize: termFontSize }}
|
||||
className={cn("chat-textarea")}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+2
-14
@@ -352,6 +352,7 @@ function createMainWindow(clientData: ClientDataType | null): Electron.BrowserWi
|
||||
const indexHtml = isDev ? "index-dev.html" : "index.html";
|
||||
win.loadFile(path.join(getElectronAppBasePath(), "public", indexHtml));
|
||||
win.webContents.on("before-input-event", (e, input) => {
|
||||
const waveEvent = adaptFromElectronKeyEvent(input);
|
||||
if (win.isFocused()) {
|
||||
wasActive = true;
|
||||
}
|
||||
@@ -379,7 +380,7 @@ function createMainWindow(clientData: ClientDataType | null): Electron.BrowserWi
|
||||
win.webContents.on("zoom-changed", (e) => {
|
||||
win.webContents.send("zoom-changed");
|
||||
});
|
||||
win.webContents.setWindowOpenHandler(({ url }) => {
|
||||
win.webContents.setWindowOpenHandler(({ url, frameName }) => {
|
||||
if (url.startsWith("https://docs.waveterm.dev/")) {
|
||||
console.log("openExternal docs", url);
|
||||
electron.shell.openExternal(url);
|
||||
@@ -400,19 +401,6 @@ function createMainWindow(clientData: ClientDataType | null): Electron.BrowserWi
|
||||
return { action: "deny" };
|
||||
});
|
||||
|
||||
electron.session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
|
||||
callback({
|
||||
responseHeaders: {
|
||||
...details.responseHeaders,
|
||||
"Content-Security-Policy": [
|
||||
`default-src 'file://${getElectronAppBasePath()}/dist${isDev ? "-dev" : ""}/'`,
|
||||
`script-src 'file://${getElectronAppBasePath()}/dist${isDev ? "-dev" : ""}/'`,
|
||||
`style-src 'file://${getElectronAppBasePath()}/dist${isDev ? "-dev" : ""}/'`,
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return win;
|
||||
}
|
||||
|
||||
|
||||
@@ -630,6 +630,9 @@ class InputModel {
|
||||
}
|
||||
|
||||
codeSelectDeselectAll(direction: number = this.codeSelectBottom) {
|
||||
if (this.codeSelectSelectedIndex.get() == direction) {
|
||||
return;
|
||||
}
|
||||
mobx.action(() => {
|
||||
this.codeSelectSelectedIndex.set(direction);
|
||||
this.codeSelectBlockRefArray = [];
|
||||
|
||||
@@ -476,23 +476,6 @@ class Model {
|
||||
if (isModKeyPress(e)) {
|
||||
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)) {
|
||||
return;
|
||||
}
|
||||
|
||||
+4
-67
@@ -12,7 +12,7 @@ import { MagicLayout } from "@/app/magiclayout";
|
||||
import * as appconst from "@/app/appconst";
|
||||
import { checkKeyPressed, adaptFromReactOrNativeKeyEvent } from "@/util/keyutil";
|
||||
import { Model } from "./model";
|
||||
import { GlobalCommandRunner } from "./global";
|
||||
import { GlobalCommandRunner, GlobalModel } from "./global";
|
||||
import { Cmd } from "./cmd";
|
||||
import { ScreenLines } from "./screenlines";
|
||||
import { getTermPtyData } from "@/util/modelutil";
|
||||
@@ -469,7 +469,7 @@ class Screen {
|
||||
this.renderers[lineId] = renderer;
|
||||
}
|
||||
|
||||
setLineFocus(lineNum: number, focus: boolean): void {
|
||||
setLineFocus(lineNum: number, lineid: string, focus: boolean): void {
|
||||
mobx.action(() => this.termLineNumFocus.set(focus ? lineNum : 0))();
|
||||
if (focus && this.selectedLine.get() != lineNum) {
|
||||
GlobalCommandRunner.screenSelectLine(String(lineNum), "cmd");
|
||||
@@ -498,71 +498,8 @@ class Screen {
|
||||
})();
|
||||
}
|
||||
|
||||
termCustomKeyHandlerInternal(e: any, termWrap: TermWrap): void {
|
||||
let waveEvent = adaptFromReactOrNativeKeyEvent(e);
|
||||
if (checkKeyPressed(waveEvent, "ArrowUp")) {
|
||||
termWrap.terminal.scrollLines(-1);
|
||||
return;
|
||||
}
|
||||
if (checkKeyPressed(waveEvent, "ArrowDown")) {
|
||||
termWrap.terminal.scrollLines(1);
|
||||
return;
|
||||
}
|
||||
if (checkKeyPressed(waveEvent, "PageUp")) {
|
||||
termWrap.terminal.scrollPages(-1);
|
||||
return;
|
||||
}
|
||||
if (checkKeyPressed(waveEvent, "PageDown")) {
|
||||
termWrap.terminal.scrollPages(1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
isTermCapturedKey(e: any): boolean {
|
||||
let waveEvent = adaptFromReactOrNativeKeyEvent(e);
|
||||
if (
|
||||
checkKeyPressed(waveEvent, "ArrowUp") ||
|
||||
checkKeyPressed(waveEvent, "ArrowDown") ||
|
||||
checkKeyPressed(waveEvent, "PageUp") ||
|
||||
checkKeyPressed(waveEvent, "PageDown")
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
termCustomKeyHandler(e: any, termWrap: TermWrap): boolean {
|
||||
let waveEvent = adaptFromReactOrNativeKeyEvent(e);
|
||||
if (e.type == "keypress" && checkKeyPressed(waveEvent, "Ctrl:Shift:c")) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
let sel = termWrap.terminal.getSelection();
|
||||
navigator.clipboard.writeText(sel);
|
||||
return false;
|
||||
}
|
||||
if (e.type == "keypress" && checkKeyPressed(waveEvent, "Ctrl:Shift:v")) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
let p = navigator.clipboard.readText();
|
||||
p.then((text) => {
|
||||
termWrap.dataHandler?.(text, termWrap);
|
||||
});
|
||||
return false;
|
||||
}
|
||||
if (termWrap.isRunning) {
|
||||
return true;
|
||||
}
|
||||
let isCaptured = this.isTermCapturedKey(e);
|
||||
if (!isCaptured) {
|
||||
return true;
|
||||
}
|
||||
if (e.type != "keydown" || isModKeyPress(e)) {
|
||||
return false;
|
||||
}
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
this.termCustomKeyHandlerInternal(e, termWrap);
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
loadTerminalRenderer(elem: Element, line: LineType, cmd: Cmd, width: number) {
|
||||
@@ -588,7 +525,7 @@ class Screen {
|
||||
termOpts: cmd.getTermOpts(),
|
||||
winSize: { height: 0, width: width },
|
||||
dataHandler: cmd.handleData.bind(cmd),
|
||||
focusHandler: (focus: boolean) => this.setLineFocus(line.linenum, focus),
|
||||
focusHandler: (focus: boolean) => this.setLineFocus(line.linenum, line.lineid, focus),
|
||||
isRunning: cmd.isRunning(),
|
||||
customKeyHandler: this.termCustomKeyHandler.bind(this),
|
||||
fontSize: this.globalModel.getTermFontSize(),
|
||||
|
||||
+68
-15
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import * as React from "react";
|
||||
import * as mobx from "mobx";
|
||||
import Editor, { Monaco } from "@monaco-editor/react";
|
||||
import type * as MonacoTypes from "monaco-editor/esm/vs/editor/editor.api";
|
||||
import cn from "classnames";
|
||||
@@ -10,7 +11,12 @@ import { Markdown } from "@/elements";
|
||||
import { GlobalModel, GlobalCommandRunner } from "@/models";
|
||||
import Split from "react-split-it";
|
||||
import loader from "@monaco-editor/loader";
|
||||
import { checkKeyPressed, adaptFromReactOrNativeKeyEvent } from "@/util/keyutil";
|
||||
import {
|
||||
checkKeyPressed,
|
||||
adaptFromReactOrNativeKeyEvent,
|
||||
KeybindManager,
|
||||
adaptFromElectronKeyEvent,
|
||||
} from "@/util/keyutil";
|
||||
import { Button, Dropdown } from "@/elements";
|
||||
|
||||
import "./code.less";
|
||||
@@ -46,6 +52,18 @@ function renderCmdText(text: string): any {
|
||||
// there is a global monaco variable (TODO get the correct TS type)
|
||||
declare var monaco: any;
|
||||
|
||||
class CodeKeybindings extends React.Component<{ codeObject: SourceCodeRenderer }, {}> {
|
||||
componentDidMount(): void {
|
||||
this.props.codeObject.registerKeybindings();
|
||||
}
|
||||
componentWillUnmount(): void {
|
||||
this.props.codeObject.unregisterKeybindings();
|
||||
}
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class SourceCodeRenderer extends React.Component<
|
||||
{
|
||||
data: ExtBlob;
|
||||
@@ -131,6 +149,10 @@ class SourceCodeRenderer extends React.Component<
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.unregisterKeybindings();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: any): void {
|
||||
if (!prevProps.shouldFocus && this.props.shouldFocus) {
|
||||
if (this.monacoEditor) {
|
||||
@@ -174,6 +196,30 @@ class SourceCodeRenderer extends React.Component<
|
||||
}
|
||||
};
|
||||
|
||||
registerKeybindings() {
|
||||
const { lineId } = this.props.context;
|
||||
let domain = "code-" + lineId;
|
||||
let keybindManager = GlobalModel.keybindManager;
|
||||
keybindManager.registerKeybinding("plugin", domain, "codeedit:save", (waveEvent) => {
|
||||
this.doSave();
|
||||
return true;
|
||||
});
|
||||
keybindManager.registerKeybinding("plugin", domain, "codeedit:close", (waveEvent) => {
|
||||
this.doClose();
|
||||
return true;
|
||||
});
|
||||
keybindManager.registerKeybinding("plugin", domain, "codeedit:togglePreview", (waveEvent) => {
|
||||
this.togglePreview();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
unregisterKeybindings() {
|
||||
const { lineId } = this.props.context;
|
||||
let domain = "code-" + lineId;
|
||||
GlobalModel.keybindManager.unregisterDomain(domain);
|
||||
}
|
||||
|
||||
handleEditorDidMount = (editor: MonacoTypes.editor.IStandaloneCodeEditor, monaco: Monaco) => {
|
||||
this.monacoEditor = editor;
|
||||
this.setInitialLanguage(editor);
|
||||
@@ -184,20 +230,15 @@ class SourceCodeRenderer extends React.Component<
|
||||
}, 2000);
|
||||
editor.onKeyDown((e: MonacoTypes.IKeyboardEvent) => {
|
||||
let waveEvent = adaptFromReactOrNativeKeyEvent(e.browserEvent);
|
||||
if (checkKeyPressed(waveEvent, "Cmd:s") && this.state.isSave) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.doSave();
|
||||
}
|
||||
if (checkKeyPressed(waveEvent, "Cmd:d")) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.doClose();
|
||||
}
|
||||
if (checkKeyPressed(waveEvent, "Cmd:p")) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.togglePreview();
|
||||
console.log("keydown?", waveEvent);
|
||||
if (
|
||||
GlobalModel.keybindManager.checkKeysPressed(waveEvent, [
|
||||
"codeedit:save",
|
||||
"codeedit:close",
|
||||
"codeedit:togglePreview",
|
||||
])
|
||||
) {
|
||||
GlobalModel.keybindManager.processKeyEvent(e.browserEvent, waveEvent);
|
||||
}
|
||||
});
|
||||
editor.onDidScrollChange((e) => {
|
||||
@@ -503,8 +544,20 @@ class SourceCodeRenderer extends React.Component<
|
||||
</div>
|
||||
);
|
||||
}
|
||||
let { lineNum } = this.props.context;
|
||||
let screen = GlobalModel.getActiveScreen();
|
||||
let lineIsSelected = mobx.computed(
|
||||
() => screen.getSelectedLine() == lineNum && screen.getFocusType() == "cmd",
|
||||
{
|
||||
name: "code-lineisselected",
|
||||
}
|
||||
);
|
||||
console.log("lineis selected:", lineIsSelected.get());
|
||||
return (
|
||||
<div className="code-renderer">
|
||||
<If condition={lineIsSelected.get()}>
|
||||
<CodeKeybindings codeObject={this}></CodeKeybindings>
|
||||
</If>
|
||||
<Split sizes={[editorFraction, 1 - editorFraction]} onSetSizes={this.setSizes}>
|
||||
{this.getCodeEditor()}
|
||||
{isPreviewerAvailable && showPreview && this.getPreviewer()}
|
||||
|
||||
@@ -148,7 +148,7 @@ class TermWrap {
|
||||
});
|
||||
this.terminal.open(elem);
|
||||
if (opts.keyHandler != null) {
|
||||
this.terminal.onKey((e) => opts.keyHandler(e, this));
|
||||
//this.terminal.onKey((e) => opts.keyHandler(e, this));
|
||||
}
|
||||
if (opts.dataHandler != null) {
|
||||
this.dataHandler = opts.dataHandler;
|
||||
|
||||
@@ -17,6 +17,60 @@ import "./terminal.less";
|
||||
|
||||
dayjs.extend(localizedFormat);
|
||||
|
||||
class TerminalKeybindings extends React.Component<{ termWrap: any; lineid: string }, {}> {
|
||||
componentDidMount(): void {
|
||||
this.registerKeybindings();
|
||||
}
|
||||
|
||||
registerKeybindings() {
|
||||
let keybindManager = GlobalModel.keybindManager;
|
||||
let domain = "line-" + this.props.lineid;
|
||||
let termWrap = this.props.termWrap;
|
||||
keybindManager.registerKeybinding("plugin", domain, "terminal:copy", (waveEvent) => {
|
||||
let termWrap = this.props.termWrap;
|
||||
let sel = termWrap.terminal.getSelection();
|
||||
navigator.clipboard.writeText(sel);
|
||||
return true;
|
||||
});
|
||||
keybindManager.registerKeybinding("plugin", domain, "terminal:paste", (waveEvent) => {
|
||||
let p = navigator.clipboard.readText();
|
||||
p.then((text) => {
|
||||
termWrap.dataHandler?.(text, termWrap);
|
||||
});
|
||||
return true;
|
||||
});
|
||||
keybindManager.registerKeybinding("plugin", domain, "generic:selectAbove", (waveEvent) => {
|
||||
termWrap.terminal.scrollLines(-1);
|
||||
return true;
|
||||
});
|
||||
keybindManager.registerKeybinding("plugin", domain, "generic:selectBelow", (waveEvent) => {
|
||||
termWrap.terminal.scrollLines(1);
|
||||
return true;
|
||||
});
|
||||
keybindManager.registerKeybinding("plugin", domain, "generic:selectPageAbove", (waveEvent) => {
|
||||
termWrap.terminal.scrollLines(-10);
|
||||
return true;
|
||||
});
|
||||
keybindManager.registerKeybinding("plugin", domain, "generic:selectPageBelow", (waveEvent) => {
|
||||
termWrap.terminal.scrollLines(10);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
unregisterKeybindings() {
|
||||
let domain = "line-" + this.props.lineid;
|
||||
GlobalModel.keybindManager.unregisterDomain(domain);
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
this.unregisterKeybindings();
|
||||
}
|
||||
|
||||
render(): React.ReactNode {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class TerminalRenderer extends React.Component<
|
||||
{
|
||||
@@ -153,6 +207,8 @@ class TerminalRenderer extends React.Component<
|
||||
termHeight = 0;
|
||||
}
|
||||
let termLoaded = this.termLoaded.get();
|
||||
let lineid = line.lineid;
|
||||
let termWrap = screen.getTermWrap(lineid);
|
||||
return (
|
||||
<div
|
||||
ref={this.elemRef}
|
||||
@@ -169,6 +225,9 @@ class TerminalRenderer extends React.Component<
|
||||
<If condition={!isFocused}>
|
||||
<div key="term-block" className="term-block" onClick={this.clickTermBlock}></div>
|
||||
</If>
|
||||
<If condition={isFocused}>
|
||||
<TerminalKeybindings termWrap={termWrap} lineid={lineid}></TerminalKeybindings>
|
||||
</If>
|
||||
<div
|
||||
key="term-connectelem"
|
||||
className="terminal-connectelem"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user