mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-22 16:48:23 +01:00
Add a button to filter out non-running commands (#113)
* add filter button to the command input box This will become a button that temporarily filters out the non-running commands from your screen. At the moment it is only a placeholder design that will likely change with more feedback. It does not have any functionality at the moment. * add view indication of active filter This will become a clickable notification to let users know that a filter is being applied. It displays the number of lines that are being filtered. The plan is for it to be clickable to remove the filter. The current version is a placeholder that is likely to change. It has no functionality at the moment. * add basic state to the filtering buttons The filtering buttons up until this point haven't done anything. Now they can be clicked and unclick causing them to render differently depending on if they're selected. They still have no functionality outside of their own appearance. * add filtering functionality to filter button The filter button now hides all non-running commands. And pressing it again or pressing the other filter button will bring back the hidden commands. There are currently some formatting issues with the second button as it jumps to the top of the screen if the filter is on and no running commands are present. An additional change was made to remove a variable accidentally introduced in the last commit. * add count for number of lines filtered out The secondary filter button now lists the number of non-running commands that have been filtered out. This count is added to the screen model in case it is needed elsewhere. * fix the style on the secondary buttons This fixes the margin an the button to bring it in line with the line items. It also fixes empty window screen to use a different css class. Previously, the window-view class being used would cover the button. It is now using the window-empty class instead. * change formatting for secondary filter button The button is now yellow with a border style instead of red with a solid style. The border-radius has been changed to give the button a pill shape. Additionally, a style tab has been added to the button component to provide it with custom styling. It should be changed to a custom class design in the future. * update style on primary filter button This is being changed to simpler hover text in line with other text in the cmd box. * add number display as text for first filter button The main filter button originally displayed a somewhat vague message. Now it displays the number of running tasks with the rotating arrow symbol. * remove numLinesHidden count from model This numLineHidden count is no longer needed with the new button design. Furthermore, it created several warnings in react due to its implementation. For both of these reasons, it has been removed. * update filter functionality to better utilize mobx This consisted of a few changes. The first was to move the filter state from the GlobalModel to ScreenLines in order to track state separately for each screen. Then several of the functions had to be rewritten to wrap setting variables in the mobx.action wrapper. As is, there are still a few issues with this design: - the filter is not remembered when switching tabs - if all running tasks expire, the second filter button is still present * move filtering observable to Screen model The previous observable did not persist when changing tabs because ScreenLines did not persist. By moving it to Screen, the ovservable now persists after changing tabs.
This commit is contained in:
parent
23b6bb29e7
commit
7310481383
@ -992,6 +992,26 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.color-yellow {
|
||||||
|
&.solid {
|
||||||
|
border-color: @warning-yellow;
|
||||||
|
background-color: mix(@warning-yellow, @term-white, 50%);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.outlined {
|
||||||
|
color: @warning-yellow;
|
||||||
|
border-color: @warning-yellow;
|
||||||
|
&:hover {
|
||||||
|
color: @term-white;
|
||||||
|
border-color: @term-white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.ghost {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.color-red {
|
&.color-red {
|
||||||
&.solid {
|
&.solid {
|
||||||
border-color: @term-red;
|
border-color: @term-red;
|
||||||
|
@ -229,6 +229,7 @@ interface ButtonProps {
|
|||||||
leftIcon?: React.ReactNode;
|
leftIcon?: React.ReactNode;
|
||||||
rightIcon?: React.ReactNode;
|
rightIcon?: React.ReactNode;
|
||||||
color?: string;
|
color?: string;
|
||||||
|
style?: React.CSSProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
class Button extends React.Component<ButtonProps> {
|
class Button extends React.Component<ButtonProps> {
|
||||||
@ -236,6 +237,7 @@ class Button extends React.Component<ButtonProps> {
|
|||||||
theme: "primary",
|
theme: "primary",
|
||||||
variant: "solid",
|
variant: "solid",
|
||||||
color: "",
|
color: "",
|
||||||
|
style: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
@boundMethod
|
@boundMethod
|
||||||
@ -246,13 +248,14 @@ class Button extends React.Component<ButtonProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { leftIcon, rightIcon, theme, children, disabled, variant, color } = this.props;
|
const { leftIcon, rightIcon, theme, children, disabled, variant, color, style } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={cn("wave-button", theme, variant, color, { disabled: disabled })}
|
className={cn("wave-button", theme, variant, color, { disabled: disabled })}
|
||||||
onClick={this.handleClick}
|
onClick={this.handleClick}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
style={style}
|
||||||
>
|
>
|
||||||
{leftIcon && <span className="icon-left">{leftIcon}</span>}
|
{leftIcon && <span className="icon-left">{leftIcon}</span>}
|
||||||
{children}
|
{children}
|
||||||
|
@ -73,6 +73,42 @@
|
|||||||
.cmd-input-context {
|
.cmd-input-context {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmd-input-filter {
|
||||||
|
opacity: 0.5;
|
||||||
|
&:hover {
|
||||||
|
opacity: 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
display: inline-block;
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
margin: 0 0.5em;
|
||||||
|
vertical-align: text-top;
|
||||||
|
fill: @base-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning {
|
||||||
|
fill: @warning-yellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes infiniteRotate {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.spin {
|
||||||
|
animation: infiniteRotate 2s linear infinite;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.cmd-input-field {
|
.cmd-input-field {
|
||||||
|
@ -5,18 +5,19 @@ import * as React from "react";
|
|||||||
import * as mobxReact from "mobx-react";
|
import * as mobxReact from "mobx-react";
|
||||||
import * as mobx from "mobx";
|
import * as mobx from "mobx";
|
||||||
import { boundMethod } from "autobind-decorator";
|
import { boundMethod } from "autobind-decorator";
|
||||||
import { If } from "tsx-control-statements/components";
|
import { If, Choose, When, Otherwise } from "tsx-control-statements/components";
|
||||||
import cn from "classnames";
|
import cn from "classnames";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import type { RemoteType, RemoteInstanceType, RemotePtrType } from "../../../types/types";
|
import type { RemoteType, RemoteInstanceType, RemotePtrType } from "../../../types/types";
|
||||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||||
import { GlobalModel, GlobalCommandRunner } from "../../../model/model";
|
import { GlobalModel, GlobalCommandRunner, Screen, ScreenLines } from "../../../model/model";
|
||||||
import { renderCmdText } from "../../common/common";
|
import { renderCmdText, Button } from "../../common/common";
|
||||||
import { TextAreaInput } from "./textareainput";
|
import { TextAreaInput } from "./textareainput";
|
||||||
import { InfoMsg } from "./infomsg";
|
import { InfoMsg } from "./infomsg";
|
||||||
import { HistoryInfo } from "./historyinfo";
|
import { HistoryInfo } from "./historyinfo";
|
||||||
import { Prompt } from "../../common/prompt/prompt";
|
import { Prompt } from "../../common/prompt/prompt";
|
||||||
import { ReactComponent as ExecIcon } from "../../assets/icons/exec.svg";
|
import { ReactComponent as ExecIcon } from "../../assets/icons/exec.svg";
|
||||||
|
import { ReactComponent as RotateIcon } from "../../assets/icons/line/rotate.svg";
|
||||||
import "./cmdinput.less";
|
import "./cmdinput.less";
|
||||||
|
|
||||||
dayjs.extend(localizedFormat);
|
dayjs.extend(localizedFormat);
|
||||||
@ -90,6 +91,13 @@ class CmdInput extends React.Component<{}, {}> {
|
|||||||
GlobalCommandRunner.connectRemote(remoteId);
|
GlobalCommandRunner.connectRemote(remoteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@boundMethod
|
||||||
|
toggleFilter(screen: Screen) {
|
||||||
|
mobx.action(() => {
|
||||||
|
screen.filterRunning.set(!screen.filterRunning.get());
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let model = GlobalModel;
|
let model = GlobalModel;
|
||||||
let inputModel = model.inputModel;
|
let inputModel = model.inputModel;
|
||||||
@ -113,6 +121,8 @@ class CmdInput extends React.Component<{}, {}> {
|
|||||||
let focusVal = inputModel.physicalInputFocused.get();
|
let focusVal = inputModel.physicalInputFocused.get();
|
||||||
let inputMode: string = inputModel.inputMode.get();
|
let inputMode: string = inputModel.inputMode.get();
|
||||||
let textAreaInputKey = screen == null ? "null" : screen.screenId;
|
let textAreaInputKey = screen == null ? "null" : screen.screenId;
|
||||||
|
let win = GlobalModel.getScreenLinesById(screen.screenId) ?? GlobalModel.loadScreenLines(screen.screenId);
|
||||||
|
let numRunningLines = win.getRunningCmdLines().length;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={this.cmdInputRef}
|
ref={this.cmdInputRef}
|
||||||
@ -142,6 +152,14 @@ class CmdInput extends React.Component<{}, {}> {
|
|||||||
<div className="has-text-white">
|
<div className="has-text-white">
|
||||||
<span ref={this.promptRef}><Prompt rptr={rptr} festate={feState} /></span>
|
<span ref={this.promptRef}><Prompt rptr={rptr} festate={feState} /></span>
|
||||||
</div>
|
</div>
|
||||||
|
<If condition={numRunningLines > 0}>
|
||||||
|
<div onClick={() => this.toggleFilter(screen)}className="cmd-input-filter">
|
||||||
|
{numRunningLines}
|
||||||
|
<div className="avatar">
|
||||||
|
<RotateIcon className="warning spin" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</If>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
key="input"
|
key="input"
|
||||||
|
@ -98,6 +98,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.filter-running {
|
||||||
|
margin: auto 1rem 0 1rem;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.screen-settings-inline {
|
.screen-settings-inline {
|
||||||
|
@ -14,7 +14,7 @@ import { GlobalCommandRunner, TabColors, TabIcons } from "../../../model/model";
|
|||||||
import type { LineType, RenderModeType, LineFactoryProps, CommandRtnType } from "../../../types/types";
|
import type { LineType, RenderModeType, LineFactoryProps, CommandRtnType } from "../../../types/types";
|
||||||
import * as T from "../../../types/types";
|
import * as T from "../../../types/types";
|
||||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||||
import { InlineSettingsTextEdit, RemoteStatusLight } from "../../common/common";
|
import { InlineSettingsTextEdit, RemoteStatusLight, Button } from "../../common/common";
|
||||||
import { getRemoteStr } from "../../common/prompt/prompt";
|
import { getRemoteStr } from "../../common/prompt/prompt";
|
||||||
import { GlobalModel, ScreenLines, Screen, Session } from "../../../model/model";
|
import { GlobalModel, ScreenLines, Screen, Session } from "../../../model/model";
|
||||||
import { Line } from "../../line/linecomps";
|
import { Line } from "../../line/linecomps";
|
||||||
@ -334,6 +334,22 @@ class ScreenWindowView extends React.Component<{ session: Session; screen: Scree
|
|||||||
return <Line key={realLine.lineid} screen={screen} line={realLine} {...restProps} />;
|
return <Line key={realLine.lineid} screen={screen} line={realLine} {...restProps} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
determineVisibleLines(win: ScreenLines): LineType[] {
|
||||||
|
let { screen } = this.props;
|
||||||
|
if (screen.filterRunning.get()) {
|
||||||
|
return win.getRunningCmdLines();
|
||||||
|
}
|
||||||
|
return win.getNonArchivedLines();
|
||||||
|
}
|
||||||
|
|
||||||
|
@boundMethod
|
||||||
|
disableFilter() {
|
||||||
|
let { screen } = this.props;
|
||||||
|
mobx.action(() => {
|
||||||
|
screen.filterRunning.set(false);
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let { session, screen } = this.props;
|
let { session, screen } = this.props;
|
||||||
let win = this.getScreenLines();
|
let win = this.getScreenLines();
|
||||||
@ -351,7 +367,7 @@ class ScreenWindowView extends React.Component<{ session: Session; screen: Scree
|
|||||||
return this.renderError("loading client data", true);
|
return this.renderError("loading client data", true);
|
||||||
}
|
}
|
||||||
let isActive = screen.isActive();
|
let isActive = screen.isActive();
|
||||||
let lines = win.getNonArchivedLines();
|
let lines = this.determineVisibleLines(win);
|
||||||
let renderMode = this.renderMode.get();
|
let renderMode = this.renderMode.get();
|
||||||
return (
|
return (
|
||||||
<div className="window-view" ref={this.windowViewRef}>
|
<div className="window-view" ref={this.windowViewRef}>
|
||||||
@ -374,7 +390,7 @@ class ScreenWindowView extends React.Component<{ session: Session; screen: Scree
|
|||||||
<NewTabSettings screen={screen} />
|
<NewTabSettings screen={screen} />
|
||||||
</If>
|
</If>
|
||||||
<If condition={screen.nextLineNum.get() != 1}>
|
<If condition={screen.nextLineNum.get() != 1}>
|
||||||
<div className="window-view" ref={this.windowViewRef} data-screenid={screen.screenId}>
|
<div className="window-empty" ref={this.windowViewRef} data-screenid={screen.screenId}>
|
||||||
<div key="lines" className="lines"></div>
|
<div key="lines" className="lines"></div>
|
||||||
<div key="window-empty" className={cn("window-empty")}>
|
<div key="window-empty" className={cn("window-empty")}>
|
||||||
<div>
|
<div>
|
||||||
@ -422,6 +438,19 @@ class ScreenWindowView extends React.Component<{ session: Session; screen: Scree
|
|||||||
lineFactory={this.buildLineComponent}
|
lineFactory={this.buildLineComponent}
|
||||||
/>
|
/>
|
||||||
</If>
|
</If>
|
||||||
|
<If condition={screen.filterRunning.get()}>
|
||||||
|
<div className='filter-running'>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
color="color-yellow"
|
||||||
|
style={{borderRadius: '999px'}}
|
||||||
|
onClick={this.disableFilter}
|
||||||
|
>
|
||||||
|
Showing Running Commands
|
||||||
|
<i className="fa-sharp fa-solid fa-xmark" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</If>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -357,6 +357,7 @@ class Screen {
|
|||||||
renderers: Record<string, RendererModel> = {}; // lineid => RendererModel
|
renderers: Record<string, RendererModel> = {}; // lineid => RendererModel
|
||||||
shareMode: OV<string>;
|
shareMode: OV<string>;
|
||||||
webShareOpts: OV<WebShareOpts>;
|
webShareOpts: OV<WebShareOpts>;
|
||||||
|
filterRunning: OV<boolean>;
|
||||||
|
|
||||||
constructor(sdata: ScreenDataType) {
|
constructor(sdata: ScreenDataType) {
|
||||||
this.sessionId = sdata.sessionid;
|
this.sessionId = sdata.sessionid;
|
||||||
@ -393,6 +394,9 @@ class Screen {
|
|||||||
this.webShareOpts = mobx.observable.box(sdata.webshareopts, {
|
this.webShareOpts = mobx.observable.box(sdata.webshareopts, {
|
||||||
name: "screen-webShareOpts",
|
name: "screen-webShareOpts",
|
||||||
});
|
});
|
||||||
|
this.filterRunning = mobx.observable.box(false, {
|
||||||
|
name: "screen-filter-running",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose() {}
|
dispose() {}
|
||||||
|
Loading…
Reference in New Issue
Block a user