mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-22 16:48:23 +01:00
Cmd-P feature -- Quick Tab Selector (#200)
* register cmd+p shortcut * tab switcher modal * initial implementation * tab switcher modal content * fix styles * fix scroll bugs * set selected index when clicking option * hover effect for options * switch when Enter key is pressed * remove fuse.js * only use switchscreen for switching sessions and tabs * var naming changes * fix multiple focused options on mouse hover * fix duplicate focused options and scrollbar * clean imports * fix wrong function name * merge color styles in app.less * remove debugging code * use For component when iterating thru options * minor style fix * remove mouse interaction, keep focusedIdx in bounds, increase max number of tabs shown, small layout adjustment for big tab names (and more space for icon)
This commit is contained in:
parent
8d88e2cf94
commit
452836bffc
@ -476,17 +476,26 @@ a.a-block {
|
||||
}
|
||||
}
|
||||
|
||||
.icon.color-default,
|
||||
.icon.color-green {
|
||||
path,
|
||||
circle {
|
||||
fill: @tab-green;
|
||||
}
|
||||
|
||||
i {
|
||||
color: @tab-green;
|
||||
}
|
||||
}
|
||||
|
||||
.icon.color-red {
|
||||
path,
|
||||
circle {
|
||||
fill: @tab-red;
|
||||
}
|
||||
}
|
||||
|
||||
.icon.color-green {
|
||||
path,
|
||||
circle {
|
||||
fill: @tab-green;
|
||||
i {
|
||||
color: @tab-red;
|
||||
}
|
||||
}
|
||||
|
||||
@ -495,6 +504,10 @@ a.a-block {
|
||||
circle {
|
||||
fill: @tab-orange;
|
||||
}
|
||||
|
||||
i {
|
||||
color: @tab-orange;
|
||||
}
|
||||
}
|
||||
|
||||
.icon.color-blue {
|
||||
@ -502,6 +515,10 @@ a.a-block {
|
||||
circle {
|
||||
fill: @tab-blue;
|
||||
}
|
||||
|
||||
i {
|
||||
color: @tab-blue;
|
||||
}
|
||||
}
|
||||
|
||||
.icon.color-yellow {
|
||||
@ -509,6 +526,10 @@ a.a-block {
|
||||
circle {
|
||||
fill: @tab-yellow;
|
||||
}
|
||||
|
||||
i {
|
||||
color: @tab-yellow;
|
||||
}
|
||||
}
|
||||
|
||||
.icon.color-pink {
|
||||
@ -516,6 +537,10 @@ a.a-block {
|
||||
circle {
|
||||
fill: @tab-pink;
|
||||
}
|
||||
|
||||
i {
|
||||
color: @tab-pink;
|
||||
}
|
||||
}
|
||||
|
||||
.icon.color-mint {
|
||||
@ -523,6 +548,10 @@ a.a-block {
|
||||
circle {
|
||||
fill: @tab-mint;
|
||||
}
|
||||
|
||||
i {
|
||||
color: @tab-mint;
|
||||
}
|
||||
}
|
||||
|
||||
.icon.color-cyan {
|
||||
@ -530,6 +559,10 @@ a.a-block {
|
||||
circle {
|
||||
fill: @tab-cyan;
|
||||
}
|
||||
|
||||
i {
|
||||
color: @tab-cyan;
|
||||
}
|
||||
}
|
||||
|
||||
.icon.color-violet {
|
||||
@ -537,6 +570,10 @@ a.a-block {
|
||||
circle {
|
||||
fill: @tab-violet;
|
||||
}
|
||||
|
||||
i {
|
||||
color: @tab-violet;
|
||||
}
|
||||
}
|
||||
|
||||
.icon.color-white {
|
||||
@ -544,6 +581,10 @@ a.a-block {
|
||||
circle {
|
||||
fill: @tab-white;
|
||||
}
|
||||
|
||||
i {
|
||||
color: @tab-white;
|
||||
}
|
||||
}
|
||||
|
||||
.status-icon.status-connected {
|
||||
|
@ -16,12 +16,6 @@ import { PluginsView } from "./pluginsview/pluginsview";
|
||||
import { BookmarksView } from "./bookmarks/bookmarks";
|
||||
import { HistoryView } from "./history/history";
|
||||
import { ConnectionsView } from "./connections/connections";
|
||||
import {
|
||||
ScreenSettingsModal,
|
||||
SessionSettingsModal,
|
||||
LineSettingsModal,
|
||||
ClientSettingsModal,
|
||||
} from "./common/modals/settings";
|
||||
import { MainSideBar } from "./sidebar/sidebar";
|
||||
import { DisconnectedModal, ClientStopModal, ModalsProvider } from "./common/modals/modals";
|
||||
import { ErrorBoundary } from "./common/error/errorboundary";
|
||||
|
@ -7,6 +7,7 @@ export const SCREEN_SETTINGS = "screenSettings";
|
||||
export const SESSION_SETTINGS = "sessionSettings";
|
||||
export const LINE_SETTINGS = "lineSettings";
|
||||
export const CLIENT_SETTINGS = "clientSettings";
|
||||
export const TAB_SWITCHER = "tabSwitcher";
|
||||
|
||||
export const LineContainer_Main = "main";
|
||||
export const LineContainer_History = "history";
|
||||
|
@ -837,6 +837,14 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.no-label {
|
||||
height: 34px;
|
||||
|
||||
input {
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wave-input-decoration {
|
||||
|
@ -332,7 +332,7 @@ interface TextFieldDecorationProps {
|
||||
endDecoration?: React.ReactNode;
|
||||
}
|
||||
interface TextFieldProps {
|
||||
label: string;
|
||||
label?: string;
|
||||
value?: string;
|
||||
className?: string;
|
||||
onChange?: (value: string) => void;
|
||||
@ -445,10 +445,11 @@ class TextField extends React.Component<TextFieldProps, TextFieldState> {
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(`wave-textfield ${className || ""}`, {
|
||||
className={cn("wave-textfield", className, {
|
||||
focused: focused,
|
||||
error: error,
|
||||
disabled: disabled,
|
||||
"no-label": !label,
|
||||
})}
|
||||
onFocus={this.handleComponentFocus}
|
||||
onBlur={this.handleComponentBlur}
|
||||
@ -456,15 +457,17 @@ class TextField extends React.Component<TextFieldProps, TextFieldState> {
|
||||
>
|
||||
{decoration?.startDecoration && <>{decoration.startDecoration}</>}
|
||||
<div className="wave-textfield-inner">
|
||||
<label
|
||||
className={cn("wave-textfield-inner-label", {
|
||||
float: this.state.hasContent || this.state.focused || placeholder,
|
||||
"offset-left": decoration?.startDecoration,
|
||||
})}
|
||||
htmlFor={label}
|
||||
>
|
||||
{label}
|
||||
</label>
|
||||
<If condition={label}>
|
||||
<label
|
||||
className={cn("wave-textfield-inner-label", {
|
||||
float: this.state.hasContent || this.state.focused || placeholder,
|
||||
"offset-left": decoration?.startDecoration,
|
||||
})}
|
||||
htmlFor={label}
|
||||
>
|
||||
{label}
|
||||
</label>
|
||||
</If>
|
||||
<input
|
||||
className={cn("wave-textfield-inner-input", { "offset-left": decoration?.startDecoration })}
|
||||
ref={this.inputRef}
|
||||
@ -774,7 +777,7 @@ class InfoMessage extends React.Component<{ width: number; children: React.React
|
||||
function LinkRenderer(props: any): any {
|
||||
let newUrl = "https://extern?" + encodeURIComponent(props.href);
|
||||
return (
|
||||
<a href={newUrl} target="_blank" rel={"noopener"}>
|
||||
<a href={newUrl} target="_blank" rel={"noopener"}>
|
||||
{props.children}
|
||||
</a>
|
||||
);
|
||||
@ -1141,7 +1144,7 @@ class Modal extends React.Component<ModalProps> {
|
||||
}
|
||||
|
||||
render() {
|
||||
return ReactDOM.createPortal(this.renderModal(), document.getElementById("app") );
|
||||
return ReactDOM.createPortal(this.renderModal(), document.getElementById("app"));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -453,6 +453,89 @@
|
||||
}
|
||||
}
|
||||
|
||||
.tabswitcher-modal {
|
||||
width: 452px;
|
||||
min-height: 384px;
|
||||
|
||||
.wave-modal-content {
|
||||
.wave-modal-body {
|
||||
display: flex;
|
||||
padding: 0px;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
width: 100%;
|
||||
|
||||
.textfield-wrapper {
|
||||
padding: 20px 20px 0px;
|
||||
|
||||
.wave-input-decoration.start-position {
|
||||
height: 100%;
|
||||
|
||||
.tabswitcher-search-prefix {
|
||||
opacity: 0.5;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.list-container {
|
||||
overflow: hidden;
|
||||
padding: 10px 0 20px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.list-container-inner {
|
||||
width: 100%;
|
||||
max-height: 300px;
|
||||
overflow-y: scroll;
|
||||
padding: 0 16px 0 20px;
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover::-webkit-scrollbar-thumb {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.options-list {
|
||||
width: 100%;
|
||||
|
||||
.search-option {
|
||||
padding: 5px 5px 5px 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: 1px solid transparent;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
div.tabname {
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
div.icon {
|
||||
flex-shrink: 0;
|
||||
width: 20px;
|
||||
margin-right: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.focused-option {
|
||||
border: 1px solid rgba(241, 246, 243, 0.15);
|
||||
border-radius: 4px;
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.screen-settings-tooltip .wave-tooltip-icon {
|
||||
i {
|
||||
font-size: 13px;
|
||||
|
@ -27,6 +27,8 @@ import {
|
||||
import * as util from "../../../util/util";
|
||||
import * as textmeasure from "../../../util/textmeasure";
|
||||
import { ClientDataType } from "../../../types/types";
|
||||
import { Session, Screen } from "../../../model/model";
|
||||
import { ReactComponent as SquareIcon } from "../../assets/icons/tab/square.svg";
|
||||
|
||||
import { ReactComponent as WarningIcon } from "../../assets/icons/line/triangle-exclamation.svg";
|
||||
import shield from "../../assets/icons/shield_check.svg";
|
||||
@ -42,6 +44,7 @@ const VERSION = __WAVETERM_VERSION__;
|
||||
let BUILD = __WAVETERM_BUILD__;
|
||||
|
||||
type OV<V> = mobx.IObservableValue<V>;
|
||||
type OArr<V> = mobx.IObservableArray<V>;
|
||||
|
||||
const RemotePtyRows = 9;
|
||||
const RemotePtyCols = 80;
|
||||
@ -1090,7 +1093,7 @@ class ViewRemoteConnDetailModal extends React.Component<{}, {}> {
|
||||
<div className="wave-modal-body">
|
||||
<div className="name-header-actions-wrapper">
|
||||
<div className="name text-primary name-wrapper">
|
||||
{getName(remote)} {getImportTooltip(remote)}
|
||||
{util.getRemoteName(remote)} {getImportTooltip(remote)}
|
||||
</div>
|
||||
<div className="header-actions">{this.renderHeaderBtns(remote)}</div>
|
||||
</div>
|
||||
@ -1343,7 +1346,7 @@ class EditRemoteConnModal extends React.Component<{}, {}> {
|
||||
<Modal.Header title="Edit Connection" onClose={this.model.closeModal} />
|
||||
<div className="wave-modal-body">
|
||||
<div className="name-actions-section">
|
||||
<div className="name text-primary">{getName(this.selectedRemote)}</div>
|
||||
<div className="name text-primary">{util.getRemoteName(this.selectedRemote)}</div>
|
||||
</div>
|
||||
<div className="alias-section">
|
||||
<TextField
|
||||
@ -1459,14 +1462,309 @@ class EditRemoteConnModal extends React.Component<{}, {}> {
|
||||
}
|
||||
}
|
||||
|
||||
const getName = (remote: T.RemoteType): string => {
|
||||
if (remote == null) {
|
||||
return "";
|
||||
}
|
||||
const { remotealias, remotecanonicalname } = remote;
|
||||
return remotealias ? `${remotealias} [${remotecanonicalname}]` : remotecanonicalname;
|
||||
type SwitcherDataType = {
|
||||
sessionId: string;
|
||||
sessionName: string;
|
||||
sessionIdx: number;
|
||||
screenId: string;
|
||||
screenIdx: number;
|
||||
screenName: string;
|
||||
icon: string;
|
||||
color: string;
|
||||
};
|
||||
|
||||
const MaxOptionsToDisplay = 100;
|
||||
|
||||
@mobxReact.observer
|
||||
class TabSwitcherModal extends React.Component<{}, {}> {
|
||||
screens: Map<string, OV<string>>[];
|
||||
sessions: Map<string, OV<string>>[];
|
||||
options: SwitcherDataType[] = [];
|
||||
sOptions: OArr<SwitcherDataType> = mobx.observable.array(null, {
|
||||
name: "TabSwitcherModal-sOptions",
|
||||
});
|
||||
focusedIdx: OV<number> = mobx.observable.box(0, { name: "TabSwitcherModal-selectedIdx" });
|
||||
activeSessionIdx: number;
|
||||
optionRefs = [];
|
||||
listWrapperRef = React.createRef<HTMLDivElement>();
|
||||
prevFocusedIdx = 0;
|
||||
|
||||
componentDidMount() {
|
||||
this.activeSessionIdx = GlobalModel.getActiveSession().sessionIdx.get();
|
||||
let oSessions = GlobalModel.sessionList;
|
||||
let oScreens = GlobalModel.screenMap;
|
||||
oScreens.forEach((oScreen) => {
|
||||
// Find the matching session in the observable array
|
||||
let foundSession = oSessions.find((s) => {
|
||||
if (s.sessionId === oScreen.sessionId && s.archived.get() == false) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (foundSession) {
|
||||
let data: SwitcherDataType = {
|
||||
sessionName: foundSession.name.get(),
|
||||
sessionId: foundSession.sessionId,
|
||||
sessionIdx: foundSession.sessionIdx.get(),
|
||||
screenName: oScreen.name.get(),
|
||||
screenId: oScreen.screenId,
|
||||
screenIdx: oScreen.screenIdx.get(),
|
||||
icon: this.getTabIcon(oScreen),
|
||||
color: this.getTabColor(oScreen),
|
||||
};
|
||||
this.options.push(data);
|
||||
}
|
||||
});
|
||||
|
||||
mobx.action(() => {
|
||||
this.sOptions.replace(this.sortOptions(this.options).slice(0, MaxOptionsToDisplay));
|
||||
})();
|
||||
|
||||
document.addEventListener("keydown", this.handleKeyDown);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener("keydown", this.handleKeyDown);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
let currFocusedIdx = this.focusedIdx.get();
|
||||
|
||||
// Check if selectedIdx has changed
|
||||
if (currFocusedIdx !== this.prevFocusedIdx) {
|
||||
let optionElement = this.optionRefs[currFocusedIdx]?.current;
|
||||
|
||||
if (optionElement) {
|
||||
optionElement.scrollIntoView({ block: "nearest" });
|
||||
}
|
||||
|
||||
// Update prevFocusedIdx for the next update cycle
|
||||
this.prevFocusedIdx = currFocusedIdx;
|
||||
}
|
||||
if (currFocusedIdx >= this.sOptions.length && this.sOptions.length > 0) {
|
||||
this.setFocusedIndex(this.sOptions.length - 1);
|
||||
}
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
getTabIcon(screen: Screen): string {
|
||||
let tabIcon = "default";
|
||||
let screenOpts = screen.opts.get();
|
||||
if (screenOpts != null && !util.isBlank(screenOpts.tabicon)) {
|
||||
tabIcon = screenOpts.tabicon;
|
||||
}
|
||||
return tabIcon;
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
getTabColor(screen: Screen): string {
|
||||
let tabColor = "default";
|
||||
let screenOpts = screen.opts.get();
|
||||
if (screenOpts != null && !util.isBlank(screenOpts.tabcolor)) {
|
||||
tabColor = screenOpts.tabcolor;
|
||||
}
|
||||
return tabColor;
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleKeyDown(e) {
|
||||
if (e.key === "Escape") {
|
||||
this.closeModal();
|
||||
} else if (e.key === "ArrowUp" || e.key === "ArrowDown") {
|
||||
e.preventDefault();
|
||||
let newIndex = this.calculateNewIndex(e.key === "ArrowUp");
|
||||
this.setFocusedIndex(newIndex);
|
||||
} else if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
this.handleSelect(this.focusedIdx.get());
|
||||
}
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
calculateNewIndex(isUpKey) {
|
||||
let currentIndex = this.focusedIdx.get();
|
||||
if (isUpKey) {
|
||||
return Math.max(currentIndex - 1, 0);
|
||||
} else {
|
||||
return Math.min(currentIndex + 1, this.sOptions.length - 1);
|
||||
}
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
setFocusedIndex(index) {
|
||||
mobx.action(() => {
|
||||
this.focusedIdx.set(index);
|
||||
})();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
closeModal(): void {
|
||||
GlobalModel.modalsModel.popModal();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleSelect(index: number): void {
|
||||
const selectedOption = this.sOptions[index];
|
||||
if (selectedOption) {
|
||||
GlobalCommandRunner.switchScreen(selectedOption.screenId, selectedOption.sessionId);
|
||||
this.closeModal();
|
||||
}
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleSearch(val: string): void {
|
||||
let sOptions: SwitcherDataType[];
|
||||
if (val == "") {
|
||||
sOptions = this.sortOptions(this.options).slice(0, MaxOptionsToDisplay);
|
||||
} else {
|
||||
sOptions = this.filterOptions(val);
|
||||
sOptions = this.sortOptions(sOptions);
|
||||
if (sOptions.length > MaxOptionsToDisplay) {
|
||||
sOptions = sOptions.slice(0, MaxOptionsToDisplay);
|
||||
}
|
||||
}
|
||||
mobx.action(() => {
|
||||
this.sOptions.replace(sOptions);
|
||||
this.focusedIdx.set(0);
|
||||
})();
|
||||
}
|
||||
|
||||
@mobx.computed
|
||||
@boundMethod
|
||||
filterOptions(searchInput: string): SwitcherDataType[] {
|
||||
let filteredScreens = [];
|
||||
|
||||
for (let i = 0; i < this.options.length; i++) {
|
||||
let tab = this.options[i];
|
||||
let match = false;
|
||||
|
||||
if (searchInput.includes("/")) {
|
||||
let [sessionFilter, screenFilter] = searchInput.split("/").map((s) => s.trim().toLowerCase());
|
||||
match =
|
||||
tab.sessionName.toLowerCase().includes(sessionFilter) &&
|
||||
tab.screenName.toLowerCase().includes(screenFilter);
|
||||
} else {
|
||||
match =
|
||||
tab.sessionName.toLowerCase().includes(searchInput) ||
|
||||
tab.screenName.toLowerCase().includes(searchInput);
|
||||
}
|
||||
|
||||
// Add tab to filtered list if it matches the criteria
|
||||
if (match) {
|
||||
filteredScreens.push(tab);
|
||||
}
|
||||
}
|
||||
|
||||
return filteredScreens;
|
||||
}
|
||||
|
||||
@mobx.computed
|
||||
@boundMethod
|
||||
sortOptions(options: SwitcherDataType[]): SwitcherDataType[] {
|
||||
return options.sort((a, b) => {
|
||||
let aInCurrentSession = a.sessionIdx === this.activeSessionIdx;
|
||||
let bInCurrentSession = b.sessionIdx === this.activeSessionIdx;
|
||||
|
||||
// Tabs in the current session are sorted by screenIdx
|
||||
if (aInCurrentSession && bInCurrentSession) {
|
||||
return a.screenIdx - b.screenIdx;
|
||||
}
|
||||
// a is in the current session and b is not, so a comes first
|
||||
else if (aInCurrentSession) {
|
||||
return -1;
|
||||
}
|
||||
// b is in the current session and a is not, so b comes first
|
||||
else if (bInCurrentSession) {
|
||||
return 1;
|
||||
}
|
||||
// Both are in different, non-current sessions - sort by sessionIdx and then by screenIdx
|
||||
else {
|
||||
if (a.sessionIdx === b.sessionIdx) {
|
||||
return a.screenIdx - b.screenIdx;
|
||||
} else {
|
||||
return a.sessionIdx - b.sessionIdx;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
renderIcon(option: SwitcherDataType): React.ReactNode {
|
||||
let tabIcon = option.icon;
|
||||
if (tabIcon === "default" || tabIcon === "square") {
|
||||
return <SquareIcon className="left-icon" />;
|
||||
}
|
||||
return <i className={`fa-sharp fa-solid fa-${tabIcon}`}></i>;
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
renderOption(option: SwitcherDataType, index: number): JSX.Element {
|
||||
if (!this.optionRefs[index]) {
|
||||
this.optionRefs[index] = React.createRef();
|
||||
}
|
||||
return (
|
||||
<div
|
||||
key={option.sessionId + "/" + option.screenId}
|
||||
ref={this.optionRefs[index]}
|
||||
className={cn("search-option unselectable", {
|
||||
"focused-option": this.focusedIdx.get() === index,
|
||||
})}
|
||||
onClick={() => this.handleSelect(index)}
|
||||
>
|
||||
<div className={cn("icon", "color-" + option.color)}>{this.renderIcon(option)}</div>
|
||||
<div className="tabname">
|
||||
#{option.sessionName} / {option.screenName}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
let option: SwitcherDataType;
|
||||
let index: number;
|
||||
return (
|
||||
<Modal className="tabswitcher-modal">
|
||||
<div className="wave-modal-body">
|
||||
<div className="textfield-wrapper">
|
||||
<TextField
|
||||
onChange={this.handleSearch}
|
||||
maxLength={400}
|
||||
autoFocus={true}
|
||||
decoration={{
|
||||
startDecoration: (
|
||||
<InputDecoration position="start">
|
||||
<div className="tabswitcher-search-prefix">Switch to Tab:</div>
|
||||
</InputDecoration>
|
||||
),
|
||||
endDecoration: (
|
||||
<InputDecoration>
|
||||
<Tooltip
|
||||
message={`Type to filter workspaces and tabs.`}
|
||||
icon={<i className="fa-sharp fa-regular fa-circle-question" />}
|
||||
>
|
||||
<i className="fa-sharp fa-regular fa-circle-question" />
|
||||
</Tooltip>
|
||||
</InputDecoration>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="list-container">
|
||||
<div ref={this.listWrapperRef} className="list-container-inner">
|
||||
<div className="options-list">
|
||||
<For each="option" index="index" of={this.sOptions}>
|
||||
{this.renderOption(option, index)}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getImportTooltip = (remote: T.RemoteType): React.ReactElement<any, any> => {
|
||||
if (remote.sshconfigsrc == "sshconfig-import") {
|
||||
return (
|
||||
@ -1493,4 +1791,5 @@ export {
|
||||
ViewRemoteConnDetailModal,
|
||||
EditRemoteConnModal,
|
||||
ModalsProvider,
|
||||
TabSwitcherModal,
|
||||
};
|
||||
|
@ -8,6 +8,7 @@ import {
|
||||
ViewRemoteConnDetailModal,
|
||||
EditRemoteConnModal,
|
||||
AlertModal,
|
||||
TabSwitcherModal,
|
||||
} from "./modals";
|
||||
import { ScreenSettingsModal, SessionSettingsModal, LineSettingsModal, ClientSettingsModal } from "./settings";
|
||||
import * as constants from "../../appconst";
|
||||
@ -22,6 +23,7 @@ const modalsRegistry: { [key: string]: () => React.ReactElement } = {
|
||||
[constants.SESSION_SETTINGS]: () => <SessionSettingsModal />,
|
||||
[constants.LINE_SETTINGS]: () => <LineSettingsModal />,
|
||||
[constants.CLIENT_SETTINGS]: () => <ClientSettingsModal />,
|
||||
[constants.TAB_SWITCHER]: () => <TabSwitcherModal />,
|
||||
};
|
||||
|
||||
export { modalsRegistry };
|
||||
|
@ -173,10 +173,10 @@ let menuTemplate = [
|
||||
role: "appMenu",
|
||||
submenu: [
|
||||
{
|
||||
label: 'About Wave Terminal',
|
||||
label: "About Wave Terminal",
|
||||
click: () => {
|
||||
MainWindow?.webContents.send('menu-item-about');
|
||||
}
|
||||
MainWindow?.webContents.send("menu-item-about");
|
||||
},
|
||||
},
|
||||
{ type: "separator" },
|
||||
{ role: "services" },
|
||||
@ -250,7 +250,7 @@ function createMainWindow(clientData) {
|
||||
minWidth: 800,
|
||||
minHeight: 600,
|
||||
transparent: true,
|
||||
icon: (unamePlatform == "linux") ? "public/logos/wave-logo-dark.png" : undefined,
|
||||
icon: unamePlatform == "linux" ? "public/logos/wave-logo-dark.png" : undefined,
|
||||
webPreferences: {
|
||||
preload: path.join(getAppBasePath(), DistDir, "preload.js"),
|
||||
},
|
||||
@ -302,6 +302,11 @@ function createMainWindow(clientData) {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
if (input.code == "KeyP" && input.meta) {
|
||||
win.webContents.send("p-cmd", mods);
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
if (input.meta && (input.code == "ArrowUp" || input.code == "ArrowDown")) {
|
||||
if (input.code == "ArrowUp") {
|
||||
win.webContents.send("meta-arrowup");
|
||||
|
@ -13,6 +13,7 @@ contextBridge.exposeInMainWorld("api", {
|
||||
onLCmd: (callback) => ipcRenderer.on("l-cmd", callback),
|
||||
onHCmd: (callback) => ipcRenderer.on("h-cmd", callback),
|
||||
onWCmd: (callback) => ipcRenderer.on("w-cmd", callback),
|
||||
onPCmd: (callback) => ipcRenderer.on("p-cmd", callback),
|
||||
onMetaArrowUp: (callback) => ipcRenderer.on("meta-arrowup", callback),
|
||||
onMetaArrowDown: (callback) => ipcRenderer.on("meta-arrowdown", callback),
|
||||
onMetaPageUp: (callback) => ipcRenderer.on("meta-pageup", callback),
|
||||
|
@ -197,6 +197,7 @@ type ElectronApi = {
|
||||
onICmd: (callback: (mods: KeyModsType) => void) => void;
|
||||
onLCmd: (callback: (mods: KeyModsType) => void) => void;
|
||||
onHCmd: (callback: (mods: KeyModsType) => void) => void;
|
||||
onPCmd: (callback: (mods: KeyModsType) => void) => void;
|
||||
onMenuItemAbout: (callback: () => void) => void;
|
||||
onMetaArrowUp: (callback: () => void) => void;
|
||||
onMetaArrowDown: (callback: () => void) => void;
|
||||
@ -3207,6 +3208,7 @@ class Model {
|
||||
getApi().onICmd(this.onICmd.bind(this));
|
||||
getApi().onLCmd(this.onLCmd.bind(this));
|
||||
getApi().onHCmd(this.onHCmd.bind(this));
|
||||
getApi().onPCmd(this.onPCmd.bind(this));
|
||||
getApi().onMenuItemAbout(this.onMenuItemAbout.bind(this));
|
||||
getApi().onMetaArrowUp(this.onMetaArrowUp.bind(this));
|
||||
getApi().onMetaArrowDown(this.onMetaArrowDown.bind(this));
|
||||
@ -3535,6 +3537,10 @@ class Model {
|
||||
GlobalModel.historyViewModel.reSearch();
|
||||
}
|
||||
|
||||
onPCmd(e: any, mods: KeyModsType) {
|
||||
GlobalModel.modalsModel.pushModal(appconst.TAB_SWITCHER);
|
||||
}
|
||||
|
||||
getFocusedLine(): LineFocusType {
|
||||
if (this.inputModel.hasFocus()) {
|
||||
return { cmdInputFocus: true };
|
||||
@ -4268,11 +4274,15 @@ class CommandRunner {
|
||||
GlobalModel.submitCommand("session", null, [session], { nohist: "1" }, false);
|
||||
}
|
||||
|
||||
switchScreen(screen: string) {
|
||||
switchScreen(screen: string, session?: string) {
|
||||
mobx.action(() => {
|
||||
GlobalModel.activeMainView.set("session");
|
||||
})();
|
||||
GlobalModel.submitCommand("screen", null, [screen], { nohist: "1" }, false);
|
||||
let kwargs = { nohist: "1" };
|
||||
if (session != null) {
|
||||
kwargs["session"] = session;
|
||||
}
|
||||
GlobalModel.submitCommand("screen", null, [screen], kwargs, false);
|
||||
}
|
||||
|
||||
lineView(sessionId: string, screenId: string, lineNum?: number) {
|
||||
|
@ -404,6 +404,14 @@ function commandRtnHandler(prtn: Promise<CommandRtnType>, errorMessage: OV<strin
|
||||
});
|
||||
}
|
||||
|
||||
function getRemoteName(remote: RemoteType): string {
|
||||
if (remote == null) {
|
||||
return "";
|
||||
}
|
||||
let { remotealias, remotecanonicalname } = remote;
|
||||
return remotealias ? `${remotealias} [${remotecanonicalname}]` : remotecanonicalname;
|
||||
}
|
||||
|
||||
export {
|
||||
handleJsonFetchResponse,
|
||||
base64ToArray,
|
||||
@ -428,4 +436,5 @@ export {
|
||||
getColorRGB,
|
||||
commandRtnHandler,
|
||||
getRemoteConnVal,
|
||||
getRemoteName,
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user