starling-devex 0.1.2

Starling: a local dev orchestrator with a central daemon, shared named-URL proxy, and a k9s-style TUI (a Rust port of Tilt + portless)
import React, { useEffect, useState } from "react"
import SplitPane from "react-split-pane"
import styled from "styled-components"
import { Alert, combinedAlerts } from "./alerts"
import { ApiButtonType, buttonsForComponent } from "./ApiButton"
import HeaderBar, { HeaderBarPage } from "./HeaderBar"
import { LogUpdateAction, LogUpdateEvent, useLogStore } from "./LogStore"
import OverviewResourceDetails from "./OverviewResourceDetails"
import OverviewResourceSidebar from "./OverviewResourceSidebar"
import "./Resizer.scss"
import { useResourceNav } from "./ResourceNav"
import { useSidebarContext } from "./SidebarContext"
import StarredResourceBar, {
  starredResourcePropsFromView,
} from "./StarredResourceBar"
import { Color, Width } from "./style-helpers"
import { ResourceName, UIResource } from "./types"
import type { View } from "./webview"

type OverviewResourcePaneProps = {
  view: View
  isSocketConnected: boolean
}

let OverviewResourcePaneRoot = styled.div`
  display: flex;
  flex-direction: column;
  width: 100%;
  height: 100vh;
  background-color: ${Color.gray20};
  max-height: 100%;
`
let Main = styled.div`
  display: flex;
  width: 100%;
  // In Safari, flex-basis "auto" squishes OverviewTabBar + OverviewResourceBar
  flex: 1 1 100%;
  overflow: hidden;
  position: relative;

  .SplitPane {
    position: relative !important;
  }
  .Pane {
    display: flex;
  }
`

export default function OverviewResourcePane(props: OverviewResourcePaneProps) {
  let nav = useResourceNav()
  const logStore = useLogStore()
  let resources = props.view?.uiResources || []
  let name = nav.invalidResource || nav.selectedResource || ""
  let r: UIResource | undefined
  let all = name === "" || name === ResourceName.all
  let starred = name === ResourceName.starred
  if (!all) {
    r = resources.find((r) => r.metadata?.name === name)
  }
  let selectedTab = ""
  if (all) {
    selectedTab = ResourceName.all
  } else if (starred) {
    selectedTab = ResourceName.starred
  } else if (r?.metadata?.name) {
    selectedTab = r.metadata.name
  }

  const { isSidebarOpen, setSidebarOpen, setSidebarClosed } =
    useSidebarContext()

  const [paneSize, setPaneSize] = useState<number>(
    isSidebarOpen ? Width.sidebarDefault : Width.sidebarMinimum
  )

  // listen for changes from sidebar context in case it was toggled instead
  // being dragged past a breakpoint.
  useEffect(() => {
    setPaneSize(
      isSidebarOpen ? Width.sidebarDefault : Width.sidebarMinimum + 0.01
      // adds 0.01 so there's still a state diff when the user releases after dragging
    )
  }, [isSidebarOpen])

  const [truncateCount, setTruncateCount] = useState<number>(0)

  // add a listener to rebuild alerts whenever a truncation event occurs
  // truncateCount is a dummy state variable to trigger a re-render to
  // simplify logic vs reconciliation between logStore + props
  useEffect(() => {
    const rebuildAlertsOnLogClear = (e: LogUpdateEvent) => {
      if (e.action === LogUpdateAction.truncate) {
        setTruncateCount(truncateCount + 1)
      }
    }

    logStore.addUpdateListener(rebuildAlertsOnLogClear)
    return () => logStore.removeUpdateListener(rebuildAlertsOnLogClear)
  }, [truncateCount])

  let alerts: Alert[] = []
  if (r) {
    alerts = combinedAlerts(r, logStore)
  } else if (all) {
    resources.forEach((r) => alerts.push(...combinedAlerts(r, logStore)))
  }

  const buttons = buttonsForComponent(
    props.view.uiButtons,
    ApiButtonType.Resource,
    name
  )

  const handleSplitPaneResize = (newSize: number) => {
    if (newSize < Width.sidebarBreakpoint && isSidebarOpen) {
      setSidebarClosed()
    } else if (newSize >= Width.sidebarBreakpoint && !isSidebarOpen) {
      setSidebarOpen()
    }
  }

  return (
    <OverviewResourcePaneRoot>
      <HeaderBar
        view={props.view}
        currentPage={HeaderBarPage.Detail}
        isSocketConnected={props.isSocketConnected}
      />
      <StarredResourceBar
        {...starredResourcePropsFromView(props.view, selectedTab)}
      />
      <Main>
        <SplitPane
          split="vertical"
          size={paneSize}
          minSize={Width.sidebarMinimum}
          onChange={handleSplitPaneResize}
          onDragFinished={() =>
            setPaneSize(
              isSidebarOpen ? Width.sidebarDefault : Width.sidebarMinimum
            )
          }
        >
          <OverviewResourceSidebar {...props} name={name} />
          <OverviewResourceDetails
            resource={r}
            name={name}
            alerts={alerts}
            buttons={buttons}
          />
        </SplitPane>
      </Main>
    </OverviewResourcePaneRoot>
  )
}