github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/web/src/OverviewResourcePane.tsx (about)

     1  import React, { useEffect, useState } from "react"
     2  import SplitPane from "react-split-pane"
     3  import styled from "styled-components"
     4  import { Alert, combinedAlerts } from "./alerts"
     5  import { AnalyticsType } from "./analytics"
     6  import { ApiButtonType, buttonsForComponent } from "./ApiButton"
     7  import HeaderBar from "./HeaderBar"
     8  import { LogUpdateAction, LogUpdateEvent, useLogStore } from "./LogStore"
     9  import OverviewResourceDetails from "./OverviewResourceDetails"
    10  import OverviewResourceSidebar from "./OverviewResourceSidebar"
    11  import "./Resizer.scss"
    12  import { useResourceNav } from "./ResourceNav"
    13  import { useSidebarContext } from "./SidebarContext"
    14  import StarredResourceBar, {
    15    starredResourcePropsFromView,
    16  } from "./StarredResourceBar"
    17  import { Color, Width } from "./style-helpers"
    18  import { ResourceName, UIResource } from "./types"
    19  
    20  type OverviewResourcePaneProps = {
    21    view: Proto.webviewView
    22    isSocketConnected: boolean
    23  }
    24  
    25  let OverviewResourcePaneRoot = styled.div`
    26    display: flex;
    27    flex-direction: column;
    28    width: 100%;
    29    height: 100vh;
    30    background-color: ${Color.gray20};
    31    max-height: 100%;
    32  `
    33  let Main = styled.div`
    34    display: flex;
    35    width: 100%;
    36    // In Safari, flex-basis "auto" squishes OverviewTabBar + OverviewResourceBar
    37    flex: 1 1 100%;
    38    overflow: hidden;
    39    position: relative;
    40  
    41    .SplitPane {
    42      position: relative !important;
    43    }
    44    .Pane {
    45      display: flex;
    46    }
    47  `
    48  
    49  export default function OverviewResourcePane(props: OverviewResourcePaneProps) {
    50    let nav = useResourceNav()
    51    const logStore = useLogStore()
    52    let resources = props.view?.uiResources || []
    53    let name = nav.invalidResource || nav.selectedResource || ""
    54    let r: UIResource | undefined
    55    let all = name === "" || name === ResourceName.all
    56    let starred = name === ResourceName.starred
    57    if (!all) {
    58      r = resources.find((r) => r.metadata?.name === name)
    59    }
    60    let selectedTab = ""
    61    if (all) {
    62      selectedTab = ResourceName.all
    63    } else if (starred) {
    64      selectedTab = ResourceName.starred
    65    } else if (r?.metadata?.name) {
    66      selectedTab = r.metadata.name
    67    }
    68  
    69    const { isSidebarOpen, setSidebarOpen, setSidebarClosed } =
    70      useSidebarContext()
    71  
    72    const [paneSize, setPaneSize] = useState<number>(
    73      isSidebarOpen ? Width.sidebarDefault : Width.sidebarMinimum
    74    )
    75  
    76    // listen for changes from sidebar context in case it was toggled instead
    77    // being dragged past a breakpoint.
    78    useEffect(() => {
    79      setPaneSize(
    80        isSidebarOpen ? Width.sidebarDefault : Width.sidebarMinimum + 0.01
    81        // adds 0.01 so there's still a state diff when the user releases after dragging
    82      )
    83    }, [isSidebarOpen])
    84  
    85    const [truncateCount, setTruncateCount] = useState<number>(0)
    86  
    87    // add a listener to rebuild alerts whenever a truncation event occurs
    88    // truncateCount is a dummy state variable to trigger a re-render to
    89    // simplify logic vs reconciliation between logStore + props
    90    useEffect(() => {
    91      const rebuildAlertsOnLogClear = (e: LogUpdateEvent) => {
    92        if (e.action === LogUpdateAction.truncate) {
    93          setTruncateCount(truncateCount + 1)
    94        }
    95      }
    96  
    97      logStore.addUpdateListener(rebuildAlertsOnLogClear)
    98      return () => logStore.removeUpdateListener(rebuildAlertsOnLogClear)
    99    }, [truncateCount])
   100  
   101    let alerts: Alert[] = []
   102    if (r) {
   103      alerts = combinedAlerts(r, logStore)
   104    } else if (all) {
   105      resources.forEach((r) => alerts.push(...combinedAlerts(r, logStore)))
   106    }
   107  
   108    const buttons = buttonsForComponent(
   109      props.view.uiButtons,
   110      ApiButtonType.Resource,
   111      name
   112    )
   113  
   114    const handleSplitPaneResize = (newSize: number) => {
   115      if (newSize < Width.sidebarBreakpoint && isSidebarOpen) {
   116        setSidebarClosed()
   117      } else if (newSize >= Width.sidebarBreakpoint && !isSidebarOpen) {
   118        setSidebarOpen()
   119      }
   120    }
   121  
   122    return (
   123      <OverviewResourcePaneRoot>
   124        <HeaderBar
   125          view={props.view}
   126          currentPage={AnalyticsType.Detail}
   127          isSocketConnected={props.isSocketConnected}
   128        />
   129        <StarredResourceBar
   130          {...starredResourcePropsFromView(props.view, selectedTab)}
   131        />
   132        <Main>
   133          <SplitPane
   134            split="vertical"
   135            size={paneSize}
   136            minSize={Width.sidebarMinimum}
   137            onChange={handleSplitPaneResize}
   138            onDragFinished={() =>
   139              setPaneSize(
   140                isSidebarOpen ? Width.sidebarDefault : Width.sidebarMinimum
   141              )
   142            }
   143          >
   144            <OverviewResourceSidebar {...props} name={name} />
   145            <OverviewResourceDetails
   146              resource={r}
   147              name={name}
   148              alerts={alerts}
   149              buttons={buttons}
   150            />
   151          </SplitPane>
   152        </Main>
   153      </OverviewResourcePaneRoot>
   154    )
   155  }