diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/PortsList.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/PortsList.tsx index 12813863547..956490ba274 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/PortsList.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/PortsList.tsx @@ -1,7 +1,14 @@ import { COMPANY } from "@superset/shared/constants"; import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip"; -import { LuChevronRight, LuCircleHelp, LuRadioTower } from "react-icons/lu"; +import { useCallback, useRef } from "react"; +import { + LuChevronRight, + LuCircleHelp, + LuFilter, + LuRadioTower, +} from "react-icons/lu"; import { usePortsStore } from "renderer/stores"; +import { MIN_LIST_HEIGHT, MAX_LIST_HEIGHT } from "renderer/stores/ports/store"; import { STROKE_WIDTH } from "../constants"; import { WorkspacePortGroup } from "./components/WorkspacePortGroup"; import { usePortsData } from "./hooks/usePortsData"; @@ -11,10 +18,43 @@ const PORTS_DOCS_URL = `${COMPANY.DOCS_URL}/ports`; export function PortsList() { const isCollapsed = usePortsStore((s) => s.isListCollapsed); const toggleCollapsed = usePortsStore((s) => s.toggleListCollapsed); + const listHeight = usePortsStore((s) => s.listHeight); + const setListHeight = usePortsStore((s) => s.setListHeight); + const showConfiguredOnly = usePortsStore((s) => s.showConfiguredOnly); + const setShowConfiguredOnly = usePortsStore((s) => s.setShowConfiguredOnly); const { workspacePortGroups, totalPortCount } = usePortsData(); - if (totalPortCount === 0) { + // --- Drag-to-resize handle --- + const isDragging = useRef(false); + const startY = useRef(0); + const startHeight = useRef(0); + + const handleMouseDown = useCallback( + (e: React.MouseEvent) => { + e.preventDefault(); + isDragging.current = true; + startY.current = e.clientY; + startHeight.current = listHeight; + + const handleMouseMove = (ev: MouseEvent) => { + if (!isDragging.current) return; + // Dragging UP increases height (handle is at top of list) + const delta = startY.current - ev.clientY; + setListHeight(startHeight.current + delta); + }; + const handleMouseUp = () => { + isDragging.current = false; + document.removeEventListener("mousemove", handleMouseMove); + document.removeEventListener("mouseup", handleMouseUp); + }; + document.addEventListener("mousemove", handleMouseMove); + document.addEventListener("mouseup", handleMouseUp); + }, + [listHeight, setListHeight], + ); + + if (totalPortCount === 0 && !showConfiguredOnly) { return null; } @@ -24,7 +64,16 @@ export function PortsList() { }; return ( -
+
+ {/* Resize handle */} + {!isCollapsed && ( +
+
+
+ )}
+ + +

+ {showConfiguredOnly + ? "Show all ports" + : "Show only ports.json ports"} +

+
+ + -

Learn about static port configuration

+

+ Learn about static port configuration +

- {totalPortCount} + + {totalPortCount} +
{!isCollapsed && ( -
- {workspacePortGroups.map((group) => ( - - ))} +
+ {workspacePortGroups.length > 0 ? ( + workspacePortGroups.map((group) => ( + + )) + ) : showConfiguredOnly ? ( +

+ No ports defined in ports.json +

+ ) : null}
)}
diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/hooks/usePortsData.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/hooks/usePortsData.ts index 4e3e926b9fb..d795fe76ffe 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/hooks/usePortsData.ts +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/hooks/usePortsData.ts @@ -1,5 +1,6 @@ import { useMemo } from "react"; import { electronTrpc } from "renderer/lib/electron-trpc"; +import { usePortsStore } from "renderer/stores"; import type { EnrichedPort } from "shared/types"; const PORTS_FALLBACK_REFETCH_INTERVAL_MS = 10_000; @@ -29,7 +30,13 @@ export function usePortsData() { }, }); - const ports = detectedPorts ?? []; + const showConfiguredOnly = usePortsStore((s) => s.showConfiguredOnly); + + const ports = useMemo(() => { + const all = detectedPorts ?? []; + if (!showConfiguredOnly) return all; + return all.filter((p) => p.label != null); + }, [detectedPorts, showConfiguredOnly]); const workspaceNames = useMemo(() => { if (!allWorkspaces) return {}; diff --git a/apps/desktop/src/renderer/stores/ports/store.ts b/apps/desktop/src/renderer/stores/ports/store.ts index 2081bfebba1..814f7c7559b 100644 --- a/apps/desktop/src/renderer/stores/ports/store.ts +++ b/apps/desktop/src/renderer/stores/ports/store.ts @@ -1,28 +1,55 @@ import { create } from "zustand"; import { devtools, persist } from "zustand/middleware"; +const DEFAULT_LIST_HEIGHT = 288; // 18rem (max-h-72) +const MIN_LIST_HEIGHT = 80; +const MAX_LIST_HEIGHT = 600; + interface PortsState { isListCollapsed: boolean; + /** Height of the ports list in pixels (user-resizable). */ + listHeight: number; + /** When true, only ports with a label from ports.json are shown. */ + showConfiguredOnly: boolean; setListCollapsed: (collapsed: boolean) => void; toggleListCollapsed: () => void; + setListHeight: (height: number) => void; + setShowConfiguredOnly: (value: boolean) => void; } +export { MIN_LIST_HEIGHT, MAX_LIST_HEIGHT }; + export const usePortsStore = create()( devtools( persist( (set, get) => ({ isListCollapsed: false, + listHeight: DEFAULT_LIST_HEIGHT, + showConfiguredOnly: false, setListCollapsed: (collapsed) => set({ isListCollapsed: collapsed }), toggleListCollapsed: () => set({ isListCollapsed: !get().isListCollapsed }), + + setListHeight: (height) => + set({ + listHeight: Math.max( + MIN_LIST_HEIGHT, + Math.min(MAX_LIST_HEIGHT, height), + ), + }), + + setShowConfiguredOnly: (value) => + set({ showConfiguredOnly: value }), }), { name: "ports-store", partialize: (state) => ({ isListCollapsed: state.isListCollapsed, + listHeight: state.listHeight, + showConfiguredOnly: state.showConfiguredOnly, }), }, ),