mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-22 16:48:23 +01:00
Prevent the status indicator flickering for quick-returning commands (#265)
* add status-indicator-visible * save * save * Prevent the status indicator flickering for quick returns * flx regression * reduce delay, reset spinnerVisible when there's no more running commands * clean up code reuse * move code around * slight optimizations to prevent rendering before spinner is visible * rename var * revert shouldSync change as it broke the sync
This commit is contained in:
parent
e576f7f07d
commit
40757fa7f4
@ -48,13 +48,33 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
The following accounts for a debounce in the status indicator. We will only display the status indicator icon if the parent indicates that it should be visible AND one of the following is true:
|
||||||
|
1. There is a status to be shown.
|
||||||
|
2. There is a spinner to be shown and the required delay has passed.
|
||||||
|
*/
|
||||||
|
.status-indicator-visible {
|
||||||
|
&.spinner-visible,
|
||||||
|
&.output,
|
||||||
|
&.error,
|
||||||
|
&.success {
|
||||||
|
.positional-icon-visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is set by the timeout in the status indicator component.
|
||||||
|
&.spinner-visible {
|
||||||
|
#spinner {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.status-indicator {
|
.status-indicator {
|
||||||
#spinner,
|
#spinner,
|
||||||
#indicator {
|
#indicator {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
.spin #spinner {
|
.spin #spinner {
|
||||||
visibility: visible;
|
|
||||||
stroke: @term-white;
|
stroke: @term-white;
|
||||||
}
|
}
|
||||||
&.error #indicator {
|
&.error #indicator {
|
||||||
|
@ -3,6 +3,8 @@ import { StatusIndicatorLevel } from "../../../types/types";
|
|||||||
import cn from "classnames";
|
import cn from "classnames";
|
||||||
import { ReactComponent as SpinnerIndicator } from "../../assets/icons/spinner-indicator.svg";
|
import { ReactComponent as SpinnerIndicator } from "../../assets/icons/spinner-indicator.svg";
|
||||||
import { boundMethod } from "autobind-decorator";
|
import { boundMethod } from "autobind-decorator";
|
||||||
|
import * as mobx from "mobx";
|
||||||
|
import * as mobxReact from "mobx-react";
|
||||||
|
|
||||||
import { ReactComponent as RotateIconSvg } from "../../assets/icons/line/rotate.svg";
|
import { ReactComponent as RotateIconSvg } from "../../assets/icons/line/rotate.svg";
|
||||||
|
|
||||||
@ -126,33 +128,90 @@ class SyncSpin extends React.Component<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface StatusIndicatorProps {
|
interface StatusIndicatorProps {
|
||||||
|
/**
|
||||||
|
* The level of the status indicator. This will determine the color of the status indicator.
|
||||||
|
*/
|
||||||
level: StatusIndicatorLevel;
|
level: StatusIndicatorLevel;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
/**
|
||||||
|
* If true, a spinner will be shown around the status indicator.
|
||||||
|
*/
|
||||||
runningCommands?: boolean;
|
runningCommands?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This component is used to show the status of a command. It will show a spinner around the status indicator if there are running commands. It will also delay showing the spinner for a short time to prevent flickering.
|
||||||
|
*/
|
||||||
|
@mobxReact.observer
|
||||||
export class StatusIndicator extends React.Component<StatusIndicatorProps> {
|
export class StatusIndicator extends React.Component<StatusIndicatorProps> {
|
||||||
iconRef: React.RefObject<HTMLDivElement> = React.createRef();
|
iconRef: React.RefObject<HTMLDivElement> = React.createRef();
|
||||||
|
spinnerVisible: mobx.IObservableValue<boolean> = mobx.observable.box(false);
|
||||||
|
timeout: NodeJS.Timeout;
|
||||||
|
|
||||||
|
clearSpinnerTimeout() {
|
||||||
|
if (this.timeout) {
|
||||||
|
clearTimeout(this.timeout);
|
||||||
|
this.timeout = null;
|
||||||
|
}
|
||||||
|
mobx.action(() => {
|
||||||
|
this.spinnerVisible.set(false);
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will apply a delay after there is a running command before showing the spinner. This prevents flickering for commands that return quickly.
|
||||||
|
*/
|
||||||
|
updateMountCallback() {
|
||||||
|
const runningCommands = this.props.runningCommands ?? false;
|
||||||
|
if (runningCommands && !this.timeout) {
|
||||||
|
this.timeout = setTimeout(
|
||||||
|
mobx.action(() => {
|
||||||
|
this.spinnerVisible.set(true);
|
||||||
|
}),
|
||||||
|
100
|
||||||
|
);
|
||||||
|
} else if (!runningCommands) {
|
||||||
|
this.clearSpinnerTimeout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(): void {
|
||||||
|
this.updateMountCallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount(): void {
|
||||||
|
this.updateMountCallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount(): void {
|
||||||
|
this.clearSpinnerTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { level, className, runningCommands } = this.props;
|
const { level, className, runningCommands } = this.props;
|
||||||
|
const spinnerVisible = this.spinnerVisible.get();
|
||||||
let statusIndicator = null;
|
let statusIndicator = null;
|
||||||
if (level != StatusIndicatorLevel.None || runningCommands) {
|
if (level != StatusIndicatorLevel.None || spinnerVisible) {
|
||||||
let levelClass = null;
|
let indicatorLevelClass = null;
|
||||||
switch (level) {
|
switch (level) {
|
||||||
case StatusIndicatorLevel.Output:
|
case StatusIndicatorLevel.Output:
|
||||||
levelClass = "output";
|
indicatorLevelClass = "output";
|
||||||
break;
|
break;
|
||||||
case StatusIndicatorLevel.Success:
|
case StatusIndicatorLevel.Success:
|
||||||
levelClass = "success";
|
indicatorLevelClass = "success";
|
||||||
break;
|
break;
|
||||||
case StatusIndicatorLevel.Error:
|
case StatusIndicatorLevel.Error:
|
||||||
levelClass = "error";
|
indicatorLevelClass = "error";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const spinnerVisibleClass = spinnerVisible ? "spinner-visible" : null;
|
||||||
statusIndicator = (
|
statusIndicator = (
|
||||||
<CenteredIcon divRef={this.iconRef} className={cn(className, levelClass, "status-indicator")}>
|
<CenteredIcon
|
||||||
<SpinnerIndicator className={runningCommands ? "spin" : null} />
|
divRef={this.iconRef}
|
||||||
|
className={cn(className, indicatorLevelClass, spinnerVisibleClass, "status-indicator")}
|
||||||
|
>
|
||||||
|
<SpinnerIndicator className={spinnerVisible ? "spin" : null} />
|
||||||
</CenteredIcon>
|
</CenteredIcon>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -188,7 +188,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&:not(:hover) .status-indicator {
|
&:not(:hover) .status-indicator {
|
||||||
.positional-icon-visible;
|
.status-indicator-visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.workspaces {
|
&.workspaces {
|
||||||
|
@ -287,7 +287,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&:not(:hover) .status-indicator {
|
&:not(:hover) .status-indicator {
|
||||||
.positional-icon-visible;
|
.status-indicator-visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
Loading…
Reference in New Issue
Block a user