From e576f7f07d98a40ce1693c68093eaabd218a7733 Mon Sep 17 00:00:00 2001 From: Evan Simkowitz Date: Tue, 30 Jan 2024 14:16:33 -0800 Subject: [PATCH] Sync RotateIcon spin as well (#262) * Sync RotateIcon spin as well * make shouldSync a boolean value --- src/app/common/icons/icons.tsx | 74 ++++++++--- src/app/line/linecomps.tsx | 170 +++++++++++------------- src/app/workspace/cmdinput/cmdinput.tsx | 57 ++++---- 3 files changed, 165 insertions(+), 136 deletions(-) diff --git a/src/app/common/icons/icons.tsx b/src/app/common/icons/icons.tsx index 5575d318..24d32a33 100644 --- a/src/app/common/icons/icons.tsx +++ b/src/app/common/icons/icons.tsx @@ -4,6 +4,8 @@ import cn from "classnames"; import { ReactComponent as SpinnerIndicator } from "../../assets/icons/spinner-indicator.svg"; import { boundMethod } from "autobind-decorator"; +import { ReactComponent as RotateIconSvg } from "../../assets/icons/line/rotate.svg"; + interface PositionalIconProps { children?: React.ReactNode; className?: string; @@ -14,7 +16,11 @@ interface PositionalIconProps { export class FrontIcon extends React.Component { render() { return ( -
+
{this.props.children}
); @@ -49,14 +55,11 @@ export class ActionsIcon extends React.Component { } } -interface StatusIndicatorProps { - level: StatusIndicatorLevel; - className?: string; - runningCommands?: boolean; -} - -export class StatusIndicator extends React.Component { - iconRef: React.RefObject = React.createRef(); +class SyncSpin extends React.Component<{ + classRef?: React.RefObject; + children?: React.ReactNode; + shouldSync?: boolean; +}> { listenerAdded: boolean = false; componentDidMount() { @@ -68,8 +71,9 @@ export class StatusIndicator extends React.Component { } componentWillUnmount(): void { - if (this.iconRef.current != null && this.listenerAdded) { - const elem = this.iconRef.current; + const classRef = this.props.classRef; + if (classRef.current != null && this.listenerAdded) { + const elem = classRef.current; const svgElem = elem.querySelector("svg"); if (svgElem != null) { svgElem.removeEventListener("animationstart", this.handleAnimationStart); @@ -79,10 +83,11 @@ export class StatusIndicator extends React.Component { @boundMethod handleAnimationStart(e: AnimationEvent) { - if (this.iconRef.current == null) { + const classRef = this.props.classRef; + if (classRef.current == null) { return; } - const svgElem = this.iconRef.current.querySelector("svg"); + const svgElem = classRef.current.querySelector("svg"); if (svgElem == null) { return; } @@ -94,13 +99,12 @@ export class StatusIndicator extends React.Component { } syncSpinner() { - if (!this.props.runningCommands) { + const { classRef, shouldSync } = this.props; + const shouldSyncVal = shouldSync ?? true; + if (!shouldSyncVal || classRef.current == null) { return; } - if (this.iconRef.current == null) { - return; - } - const elem = this.iconRef.current; + const elem = classRef.current; const svgElem = elem.querySelector("svg"); if (svgElem == null) { return; @@ -116,6 +120,20 @@ export class StatusIndicator extends React.Component { animArr[0].startTime = 0; } + render() { + return this.props.children; + } +} + +interface StatusIndicatorProps { + level: StatusIndicatorLevel; + className?: string; + runningCommands?: boolean; +} + +export class StatusIndicator extends React.Component { + iconRef: React.RefObject = React.createRef(); + render() { const { level, className, runningCommands } = this.props; let statusIndicator = null; @@ -138,6 +156,24 @@ export class StatusIndicator extends React.Component { ); } - return statusIndicator; + return ( + + {statusIndicator} + + ); + } +} + +export class RotateIcon extends React.Component<{ + className?: string; + onClick?: React.MouseEventHandler; +}> { + iconRef: React.RefObject = React.createRef(); + render() { + return ( + + + + ); } } diff --git a/src/app/line/linecomps.tsx b/src/app/line/linecomps.tsx index b7ef7231..542be31a 100644 --- a/src/app/line/linecomps.tsx +++ b/src/app/line/linecomps.tsx @@ -13,8 +13,6 @@ import { GlobalModel, GlobalCommandRunner, Cmd, getTermPtyData } from "../../mod import { termHeightFromRows } from "../../util/textmeasure"; import type { LineType, - RemoteType, - RemotePtrType, RenderModeType, RendererOpts, RendererPluginType, @@ -24,11 +22,6 @@ import type { } from "../../types/types"; import cn from "classnames"; -import { ReactComponent as FavoritesIcon } from "../assets/icons/favourites.svg"; -import { ReactComponent as PinIcon } from "../assets/icons/pin.svg"; -import { ReactComponent as PlusIcon } from "../assets/icons/plus.svg"; -import { ReactComponent as MinusIcon } from "../assets/icons/minus.svg"; - import type { LineContainerModel } from "../../model/model"; import { renderCmdText } from "../common/common"; import { SimpleBlobRenderer } from "../../plugins/core/basicrenderer"; @@ -44,12 +37,13 @@ import * as appconst from "../appconst"; import { ReactComponent as CheckIcon } from "../assets/icons/line/check.svg"; import { ReactComponent as CommentIcon } from "../assets/icons/line/comment.svg"; import { ReactComponent as QuestionIcon } from "../assets/icons/line/question.svg"; -import { ReactComponent as RotateIcon } from "../assets/icons/line/rotate.svg"; import { ReactComponent as WarningIcon } from "../assets/icons/line/triangle-exclamation.svg"; import { ReactComponent as XmarkIcon } from "../assets/icons/line/xmark.svg"; import { ReactComponent as FillIcon } from "../assets/icons/line/fill.svg"; import { ReactComponent as GearIcon } from "../assets/icons/line/gear.svg"; +import { RotateIcon } from "../common/icons/icons"; + import "./lines.less"; dayjs.extend(localizedFormat); @@ -59,12 +53,12 @@ type OV = mobx.IObservableValue; @mobxReact.observer class SmallLineAvatar extends React.Component<{ line: LineType; cmd: Cmd; onRightClick?: (e: any) => void }, {}> { render() { - let { line, cmd } = this.props; - let lineNumStr = (line.linenumtemp ? "~" : "#") + String(line.linenum); - let status = cmd != null ? cmd.getStatus() : "done"; - let rtnstate = cmd != null ? cmd.getRtnState() : false; - let exitcode = cmd != null ? cmd.getExitCode() : 0; - let isComment = line.linetype == "text"; + const { line, cmd } = this.props; + const lineNumStr = (line.linenumtemp ? "~" : "#") + String(line.linenum); + const status = cmd != null ? cmd.getStatus() : "done"; + const rtnstate = cmd != null ? cmd.getRtnState() : false; + const exitcode = cmd != null ? cmd.getExitCode() : 0; + const isComment = line.linetype == "text"; let icon = null; let iconTitle = null; if (isComment) { @@ -142,7 +136,7 @@ class LineCmd extends React.Component< } checkStateDiffLoad(): void { - let { screen, line, staticRender, visible } = this.props; + const { screen, line, staticRender, visible } = this.props; if (staticRender) { return; } @@ -153,7 +147,7 @@ class LineCmd extends React.Component< } return; } - let cmd = screen.getCmd(line); + const cmd = screen.getCmd(line); if (cmd == null || !cmd.getRtnState() || this.rtnStateDiffFetched) { return; } @@ -167,15 +161,15 @@ class LineCmd extends React.Component< if (this.rtnStateDiffFetched) { return; } - let { line } = this.props; + const { line } = this.props; this.rtnStateDiffFetched = true; - let usp = new URLSearchParams({ + const usp = new URLSearchParams({ linenum: String(line.linenum), screenid: line.screenid, lineid: line.lineid, }); - let url = GlobalModel.getBaseHostPort() + "/api/rtnstate?" + usp.toString(); - let fetchHeaders = GlobalModel.getFetchHeaders(); + const url = GlobalModel.getBaseHostPort() + "/api/rtnstate?" + usp.toString(); + const fetchHeaders = GlobalModel.getFetchHeaders(); fetch(url, { headers: fetchHeaders }) .then((resp) => { if (!resp.ok) { @@ -233,7 +227,7 @@ class LineCmd extends React.Component< ); } - let isMultiLine = lineutil.isMultiLineCmdText(cmd.getCmdStr()); + const isMultiLine = lineutil.isMultiLineCmdText(cmd.getCmdStr()); return (
@@ -252,7 +246,7 @@ class LineCmd extends React.Component< // TODO: this might not be necessary anymore because we're using this.lastHeight getSnapshotBeforeUpdate(prevProps, prevState): { height: number } { - let elem = this.lineRef.current; + const elem = this.lineRef.current; if (elem == null) { return { height: 0 }; } @@ -266,25 +260,25 @@ class LineCmd extends React.Component< } checkCmdText() { - let metaElem = this.cmdTextRef.current; + const metaElem = this.cmdTextRef.current; if (metaElem == null || metaElem.childNodes.length == 0) { return; } - let metaElemWidth = metaElem.offsetWidth; + const metaElemWidth = metaElem.offsetWidth; if (metaElemWidth == 0) { return; } - let metaChild = metaElem.firstChild; + const metaChild = metaElem.firstChild; if (metaChild == null) { return; } - let children = metaChild.childNodes; + const children = metaChild.childNodes; let childWidth = 0; for (let i = 0; i < children.length; i++) { let ch = children[i]; childWidth += ch.offsetWidth; } - let isOverflow = childWidth > metaElemWidth; + const isOverflow = childWidth > metaElemWidth; if (isOverflow && isOverflow != this.isOverflow.get()) { mobx.action(() => { this.isOverflow.set(isOverflow); @@ -297,16 +291,16 @@ class LineCmd extends React.Component< if (this.props.onHeightChange == null) { return; } - let { line } = this.props; + const { line } = this.props; let curHeight = 0; - let elem = this.lineRef.current; + const elem = this.lineRef.current; if (elem != null) { curHeight = elem.offsetHeight; } if (this.lastHeight == curHeight) { return; } - let lastHeight = this.lastHeight; + const lastHeight = this.lastHeight; this.lastHeight = curHeight; this.props.onHeightChange(line.linenum, curHeight, lastHeight); // console.log("line height change: ", line.linenum, lastHeight, "=>", curHeight); @@ -314,13 +308,13 @@ class LineCmd extends React.Component< @boundMethod handleClick() { - let { line, noSelect } = this.props; + const { line, noSelect } = this.props; if (noSelect) { return; } - let sel = window.getSelection(); + const sel = window.getSelection(); if (this.lineRef.current != null) { - let selText = sel.toString(); + const selText = sel.toString(); if (sel.anchorNode != null && this.lineRef.current.contains(sel.anchorNode) && !isBlank(selText)) { return; } @@ -330,7 +324,7 @@ class LineCmd extends React.Component< @boundMethod clickStar() { - let { line } = this.props; + const { line } = this.props; if (!line.star || line.star == 0) { GlobalCommandRunner.lineStar(line.lineid, 1); } else { @@ -340,7 +334,7 @@ class LineCmd extends React.Component< @boundMethod clickPin() { - let { line } = this.props; + const { line } = this.props; if (!line.pinned) { GlobalCommandRunner.linePin(line.lineid, true); } else { @@ -350,19 +344,19 @@ class LineCmd extends React.Component< @boundMethod clickBookmark() { - let { line } = this.props; + const { line } = this.props; GlobalCommandRunner.lineBookmark(line.lineid); } @boundMethod clickDelete() { - let { line } = this.props; + const { line } = this.props; GlobalCommandRunner.lineDelete(line.lineid, true); } @boundMethod clickRestart() { - let { line } = this.props; + const { line } = this.props; GlobalCommandRunner.lineRestart(line.lineid, true); } @@ -375,7 +369,7 @@ class LineCmd extends React.Component< @boundMethod clickMoveToSidebar() { - let { line } = this.props; + const { line } = this.props; GlobalCommandRunner.screenSidebarAddLine(line.lineid); } @@ -390,20 +384,20 @@ class LineCmd extends React.Component< } getIsHidePrompt(): boolean { - let { line } = this.props; + const { line } = this.props; let rendererPlugin: RendererPluginType = null; - let isNoneRenderer = line.renderer == "none"; + const isNoneRenderer = line.renderer == "none"; if (!isBlank(line.renderer) && line.renderer != "terminal" && !isNoneRenderer) { rendererPlugin = PluginModel.getRendererPluginByName(line.renderer); } - let hidePrompt = rendererPlugin != null && rendererPlugin.hidePrompt; + const hidePrompt = rendererPlugin?.hidePrompt; return hidePrompt; } getTerminalRendererHeight(cmd: Cmd): number { - let { screen, line, width, renderMode } = this.props; + const { screen, line, width } = this.props; let height = 45 + 24; // height of zero height terminal - let usedRows = screen.getUsedRows(lineutil.getRendererContext(line), line, cmd, width); + const usedRows = screen.getUsedRows(lineutil.getRendererContext(line), line, cmd, width); if (usedRows > 0) { height = 48 + 24 + termHeightFromRows(usedRows, GlobalModel.termFontSize.get()); } @@ -412,7 +406,7 @@ class LineCmd extends React.Component< @boundMethod onAvatarRightClick(e: any): void { - let { line, noSelect } = this.props; + const { line, noSelect } = this.props; if (noSelect) { return; } @@ -426,20 +420,20 @@ class LineCmd extends React.Component< } renderSimple() { - let { screen, line } = this.props; - let cmd = screen.getCmd(line); + const { screen, line } = this.props; + const cmd = screen.getCmd(line); let height: number = 0; if (isBlank(line.renderer) || line.renderer == "terminal") { height = this.getTerminalRendererHeight(cmd); } else { // header is 16px tall with hide-prompt, 36px otherwise - let { screen, line, width } = this.props; - let hidePrompt = this.getIsHidePrompt(); - let usedRows = screen.getUsedRows(lineutil.getRendererContext(line), line, cmd, width); + const { screen, line, width } = this.props; + const hidePrompt = this.getIsHidePrompt(); + const usedRows = screen.getUsedRows(lineutil.getRendererContext(line), line, cmd, width); height = (hidePrompt ? 16 + 6 : 36 + 6) + usedRows; } - let formattedTime = lineutil.getLineDateTimeStr(line.ts); - let mainDivCn = cn("line", "line-cmd", "line-simple"); + const formattedTime = lineutil.getLineDateTimeStr(line.ts); + const mainDivCn = cn("line", "line-cmd", "line-simple"); return (
0) { formattedTime = "restarted @ " + lineutil.getLineDateTimeStr(restartTs); timeTitle = "original start time " + lineutil.getLineDateTimeStr(line.ts); - } - else { + } else { formattedTime = lineutil.getLineDateTimeStr(line.ts); } let renderer = line.renderer; return (
-
{formattedTime}
+
+ {formattedTime} +
 
@@ -506,7 +501,7 @@ class LineCmd extends React.Component< } getRendererOpts(cmd: Cmd): RendererOpts { - let { screen } = this.props; + const { screen } = this.props; return { maxSize: screen.getMaxContentSize(), idealSize: screen.getIdealContentSize(), @@ -516,9 +511,9 @@ class LineCmd extends React.Component< } makeRendererModelInitializeParams(): RendererModelInitializeParams { - let { screen, line } = this.props; - let context = lineutil.getRendererContext(line); - let cmd = screen.getCmd(line); // won't be null + const { screen, line } = this.props; + const context = lineutil.getRendererContext(line); + const cmd = screen.getCmd(line); // won't be null let savedHeight = screen.getContentHeight(context); if (savedHeight == null) { if (line.contentheight != null && line.contentheight != -1) { @@ -527,7 +522,7 @@ class LineCmd extends React.Component< savedHeight = 0; } } - let api = { + const api = { saveHeight: (height: number) => { screen.setContentHeight(lineutil.getRendererContext(line), height); }, @@ -581,12 +576,12 @@ class LineCmd extends React.Component< }; render() { - let { screen, line, width, staticRender, visible } = this.props; - let isVisible = visible.get(); + const { screen, line, width, staticRender, visible } = this.props; + const isVisible = visible.get(); if (staticRender || !isVisible) { return this.renderSimple(); } - let cmd = screen.getCmd(line); + const cmd = screen.getCmd(line); if (cmd == null) { return (
); } - let status = cmd.getStatus(); - let lineNumStr = (line.linenumtemp ? "~" : "") + String(line.linenum); - let isSelected = mobx + const isSelected = mobx .computed(() => screen.getSelectedLine() == line.linenum, { name: "computed-isSelected", }) .get(); - let isPhysicalFocused = mobx + const isPhysicalFocused = mobx .computed(() => screen.getIsFocused(line.linenum), { name: "computed-getIsFocused", }) .get(); - let isFocused = mobx + const isFocused = mobx .computed( () => { let screenFocusType = screen.getFocusType(); @@ -621,7 +614,7 @@ class LineCmd extends React.Component< { name: "computed-isFocused" } ) .get(); - let shouldCmdFocus = mobx + const shouldCmdFocus = mobx .computed( () => { let screenFocusType = screen.getFocusType(); @@ -630,7 +623,7 @@ class LineCmd extends React.Component< { name: "computed-shouldCmdFocus" } ) .get(); - let isInSidebar = mobx + const isInSidebar = mobx .computed( () => { return screen.isSidebarOpen() && screen.isLineIdInSidebar(line.lineid); @@ -638,11 +631,10 @@ class LineCmd extends React.Component< { name: "computed-isInSidebar" } ) .get(); - let isStatic = staticRender; - let isRunning = cmd.isRunning(); - let isExpanded = this.isCmdExpanded.get(); - let rsdiff = this.rtnStateDiff.get(); - let mainDivCn = cn( + const isRunning = cmd.isRunning(); + const isExpanded = this.isCmdExpanded.get(); + const rsdiff = this.rtnStateDiff.get(); + const mainDivCn = cn( "line", "line-cmd", { selected: isSelected }, @@ -651,18 +643,18 @@ class LineCmd extends React.Component< { "has-rtnstate": cmd.getRtnState() } ); let rendererPlugin: RendererPluginType = null; - let isNoneRenderer = line.renderer == "none"; + const isNoneRenderer = line.renderer == "none"; if (!isBlank(line.renderer) && line.renderer != "terminal" && !isNoneRenderer) { rendererPlugin = PluginModel.getRendererPluginByName(line.renderer); } - let rendererType = lineutil.getRendererType(line); - let hidePrompt = rendererPlugin != null && rendererPlugin.hidePrompt; - let termFontSize = GlobalModel.termFontSize.get(); + const rendererType = lineutil.getRendererType(line); + const hidePrompt = rendererPlugin?.hidePrompt; + const termFontSize = GlobalModel.termFontSize.get(); let rtnStateDiffSize = termFontSize - 2; if (rtnStateDiffSize < 10) { rtnStateDiffSize = Math.max(termFontSize, 10); } - let containerType = screen.getContainerType(); + const containerType = screen.getContainerType(); return (
{this.renderCmdText(cmd)}
- +
@@ -821,7 +813,7 @@ class Line extends React.Component< {} > { render() { - let line = this.props.line; + const line = this.props.line; if (line.archived) { return null; } @@ -847,7 +839,7 @@ class LineText extends React.Component< > { @boundMethod clickHandler() { - let { line, noSelect } = this.props; + const { line, noSelect } = this.props; if (noSelect) { return; } @@ -856,7 +848,7 @@ class LineText extends React.Component< @boundMethod onAvatarRightClick(e: any): void { - let { line, noSelect } = this.props; + const { line, noSelect } = this.props; if (noSelect) { return; } @@ -870,19 +862,19 @@ class LineText extends React.Component< } render() { - let { screen, line, renderMode } = this.props; - let formattedTime = lineutil.getLineDateTimeStr(line.ts); - let isSelected = mobx + const { screen, line } = this.props; + const formattedTime = lineutil.getLineDateTimeStr(line.ts); + const isSelected = mobx .computed(() => screen.getSelectedLine() == line.linenum, { name: "computed-isSelected", }) .get(); - let isFocused = mobx + const isFocused = mobx .computed(() => screen.getFocusType() == "cmd", { name: "computed-isFocused", }) .get(); - let mainClass = cn("line", "line-text", "focus-parent"); + const mainClass = cn("line", "line-text", "focus-parent"); return (
= mobx.IObservableValue; - @mobxReact.observer class CmdInput extends React.Component<{}, {}> { cmdInputRef: React.RefObject = React.createRef(); @@ -37,11 +34,11 @@ class CmdInput extends React.Component<{}, {}> { } updateCmdInputHeight() { - let elem = this.cmdInputRef.current; + const elem = this.cmdInputRef.current; if (elem == null) { return; } - let height = elem.offsetHeight; + const height = elem.offsetHeight; if (height == GlobalModel.inputModel.cmdInputHeight) { return; } @@ -50,7 +47,7 @@ class CmdInput extends React.Component<{}, {}> { })(); } - componentDidUpdate(prevProps, prevState, snapshot: {}): void { + componentDidUpdate(): void { this.updateCmdInputHeight(); } @@ -87,7 +84,7 @@ class CmdInput extends React.Component<{}, {}> { e.preventDefault(); e.stopPropagation(); - let inputModel = GlobalModel.inputModel; + const inputModel = GlobalModel.inputModel; if (inputModel.historyShow.get()) { inputModel.resetHistory(); } else { @@ -113,9 +110,9 @@ class CmdInput extends React.Component<{}, {}> { } render() { - let model = GlobalModel; - let inputModel = model.inputModel; - let screen = GlobalModel.getActiveScreen(); + const model = GlobalModel; + const inputModel = model.inputModel; + const screen = GlobalModel.getActiveScreen(); let ri: RemoteInstanceType = null; let rptr: RemotePtrType = null; if (screen != null) { @@ -129,15 +126,13 @@ class CmdInput extends React.Component<{}, {}> { feState = ri.festate; } feState = feState || {}; - let infoShow = inputModel.infoShow.get(); - let historyShow = !infoShow && inputModel.historyShow.get(); - let aiChatShow = inputModel.aIChatShow.get(); - let infoMsg = inputModel.infoMsg.get(); - let hasInfo = infoMsg != null; - let focusVal = inputModel.physicalInputFocused.get(); - let inputMode: string = inputModel.inputMode.get(); - let textAreaInputKey = screen == null ? "null" : screen.screenId; - let win = GlobalModel.getScreenLinesById(screen.screenId); + const infoShow = inputModel.infoShow.get(); + const historyShow = !infoShow && inputModel.historyShow.get(); + const aiChatShow = inputModel.aIChatShow.get(); + const focusVal = inputModel.physicalInputFocused.get(); + const inputMode: string = inputModel.inputMode.get(); + const textAreaInputKey = screen == null ? "null" : screen.screenId; + const win = GlobalModel.getScreenLinesById(screen.screenId); let numRunningLines = 0; if (win != null) { numRunningLines = mobx.computed(() => win.getRunningCmdLines().length).get(); @@ -232,11 +227,17 @@ class CmdInput extends React.Component<{}, {}> { {focusVal && (
-
close (esc)
+
+ close (esc) +
-
history (ctrl-r)
-
AI (ctrl-space)
+
+ history (ctrl-r) +
+
+ AI (ctrl-space) +
)}