github.com/tilt-dev/tilt@v0.36.0/web/src/BuildButton.tsx (about)

     1  import React, { useCallback } from "react"
     2  import styled from "styled-components"
     3  import { ApiButton } from "./ApiButton"
     4  import { ReactComponent as StartBuildButtonManualSvg } from "./assets/svg/start-build-button-manual.svg"
     5  import { ReactComponent as StartBuildButtonSvg } from "./assets/svg/start-build-button.svg"
     6  import { ReactComponent as StopBuildButtonSvg } from "./assets/svg/stop-build-button.svg"
     7  import { InstrumentedButton } from "./instrumentedComponents"
     8  import TiltTooltip from "./Tooltip"
     9  import { BuildButtonTooltip, buildButtonTooltip } from "./trigger"
    10  import { TriggerMode, UIButton } from "./types"
    11  
    12  export type StartBuildButtonProps = {
    13    isBuilding: boolean
    14    hasBuilt: boolean
    15    triggerMode: TriggerMode
    16    isSelected?: boolean
    17    hasPendingChanges: boolean
    18    isQueued: boolean
    19    onStartBuild: () => void
    20    className?: string
    21  }
    22  
    23  type StopBuildButtonProps = {
    24    stopBuildButton?: UIButton
    25  }
    26  
    27  export type BuildButtonProps = StartBuildButtonProps & StopBuildButtonProps
    28  
    29  function BuildButton(props: BuildButtonProps) {
    30    const { stopBuildButton, ...startBuildButtonProps } = props
    31    if (props.isBuilding) {
    32      if (!stopBuildButton) {
    33        return null
    34      }
    35      let classes = [props.className, "stop-button", "is-clickable"]
    36      if (props.isSelected) {
    37        classes.push("is-selected")
    38      }
    39  
    40      return (
    41        <TiltTooltip title={BuildButtonTooltip.Stop}>
    42          <BuildButtonCursorWrapper>
    43            <ApiButton uiButton={stopBuildButton} className={classes.join(" ")}>
    44              <StopBuildButtonSvg className="icon" />
    45            </ApiButton>
    46          </BuildButtonCursorWrapper>
    47        </TiltTooltip>
    48      )
    49    } else {
    50      const classes = [props.className, "start-button"]
    51      return (
    52        <StartBuildButton
    53          {...startBuildButtonProps}
    54          className={classes.join(" ")}
    55        />
    56      )
    57    }
    58  }
    59  
    60  // A wrapper to receive pointer events so that we get cursor and tooltip when disabled
    61  // https://mui.com/components/tooltips/#disabled-elements
    62  const BuildButtonCursorWrapper = styled.div`
    63    display: inline-block;
    64    cursor: not-allowed;
    65    // keep the button in front of the "in-progress" barber pole animation
    66    z-index: 1;
    67    .is-clickable {
    68      cursor: pointer;
    69    }
    70  `
    71  
    72  function StartBuildButton(props: StartBuildButtonProps) {
    73    let isManual =
    74      props.triggerMode === TriggerMode.TriggerModeManual ||
    75      props.triggerMode === TriggerMode.TriggerModeManualWithAutoInit
    76    let isAutoInit =
    77      props.triggerMode === TriggerMode.TriggerModeAuto ||
    78      props.triggerMode === TriggerMode.TriggerModeManualWithAutoInit
    79  
    80    // clickable (i.e. start build button will appear) if it doesn't already have some kind of pending / active build
    81    let clickable =
    82      !props.isQueued && // already queued for manual run
    83      !props.isBuilding && // currently building
    84      !(isAutoInit && !props.hasBuilt) // waiting to perform its initial build
    85  
    86    let isEmphasized = false
    87    if (clickable) {
    88      if (props.hasPendingChanges && isManual) {
    89        isEmphasized = true
    90      } else if (!props.hasBuilt && !isAutoInit) {
    91        isEmphasized = true
    92      }
    93    }
    94  
    95    let onClick = useCallback(
    96      (e: React.MouseEvent<HTMLButtonElement>) => {
    97        // In the sidebar, StartBuildButton is nested in a link,
    98        // and preventDefault is the standard way to cancel the navigation.
    99        e.preventDefault()
   100  
   101        // stopPropagation prevents the overview card from opening.
   102        e.stopPropagation()
   103  
   104        props.onStartBuild()
   105      },
   106      [props.onStartBuild]
   107    )
   108  
   109    let classes = [props.className]
   110    if (props.isSelected) {
   111      classes.push("is-selected")
   112    }
   113    if (clickable) {
   114      classes.push("is-clickable")
   115    }
   116    if (props.isQueued) {
   117      classes.push("is-queued")
   118    }
   119    if (isManual) {
   120      classes.push("is-manual")
   121    }
   122    if (isEmphasized) {
   123      classes.push("is-emphasized")
   124    }
   125    if (props.isBuilding) {
   126      classes.push("is-building")
   127    }
   128    const tooltip = buildButtonTooltip(clickable, isEmphasized, props.isQueued)
   129    // Set the tooltip key to the tooltip message so that each message is a different "component" and enterNextDelay
   130    // applies when the message changes.
   131    // Otherwise, we often display a flicker of "resource is already queued!" after clicking "start build" before
   132    // the "stop build" button appears.
   133    return (
   134      <TiltTooltip title={tooltip} key={tooltip}>
   135        <BuildButtonCursorWrapper
   136          className={clickable ? "is-clickable" : undefined}
   137        >
   138          <InstrumentedButton
   139            onClick={onClick}
   140            className={classes.join(" ")}
   141            disabled={!clickable}
   142            aria-label={tooltip}
   143          >
   144            {isEmphasized ? (
   145              <StartBuildButtonManualSvg
   146                role="presentation"
   147                className="icon"
   148                data-testid="build-manual-icon"
   149              />
   150            ) : (
   151              <StartBuildButtonSvg
   152                role="presentation"
   153                className="icon"
   154                data-testid="build-auto-icon"
   155              />
   156            )}
   157          </InstrumentedButton>
   158        </BuildButtonCursorWrapper>
   159      </TiltTooltip>
   160    )
   161  }
   162  
   163  export default React.memo(BuildButton)