Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 0 additions & 15 deletions apps/desktop/src/renderer/globals.css
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
@import "tailwindcss";
@import "tw-animate-css";
@import "streamdown/styles.css";
@import "./styles/fade-edge.css";

@source "./**/*.{ts,tsx}";
@source "../../../../packages/ui/src/**/*.{ts,tsx}";
Expand Down Expand Up @@ -311,20 +310,6 @@
display: none; /* Chrome/Safari/Electron */
}

/* Fade out the right edge of a horizontally-scrolling container */
.fade-edge-r {
mask-image: linear-gradient(
to right,
black calc(100% - 1.5rem),
transparent
);
-webkit-mask-image: linear-gradient(
to right,
black calc(100% - 1.5rem),
transparent
);
}

/* Dark mode scrollbar styling */
* {
scrollbar-width: thin;
Expand Down
5 changes: 5 additions & 0 deletions apps/desktop/src/renderer/lib/dev-chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ export const DEV_CHAT_MODELS: ModelOption[] = [
name: "Haiku 4.5",
provider: "Anthropic",
},
{
id: "openai/gpt-5.5",
name: "GPT-5.5",
provider: "OpenAI",
},
{
id: "openai/gpt-5.4",
name: "GPT-5.4",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@ import { memo, useCallback, useEffect, useMemo, useState } from "react";
import { createPortal } from "react-dom";
import { useDashboardSidebarState } from "renderer/routes/_authenticated/hooks/useDashboardSidebarState";
import { DashboardSidebarHeader } from "./components/DashboardSidebarHeader";
import { DashboardSidebarHoverCardOverlay } from "./components/DashboardSidebarHoverCardOverlay";
import { DashboardSidebarPortsList } from "./components/DashboardSidebarPortsList";
import { DashboardSidebarProjectSection } from "./components/DashboardSidebarProjectSection";
import { DashboardSidebarSectionRenameProvider } from "./components/DashboardSidebarSectionRenameContext";
import { useDashboardSidebarData } from "./hooks/useDashboardSidebarData";
import { useDashboardSidebarShortcuts } from "./hooks/useDashboardSidebarShortcuts";
import { DashboardSidebarHoverProvider } from "./providers/DashboardSidebarHoverProvider";
import type { DashboardSidebarProject } from "./types";

interface DashboardSidebarProps {
Expand Down Expand Up @@ -136,61 +138,65 @@ export function DashboardSidebar({

return (
<DashboardSidebarSectionRenameProvider>
<div className="flex h-full flex-col border-r border-border bg-muted/45 dark:bg-muted/35">
<DashboardSidebarHeader isCollapsed={isCollapsed} />
<DashboardSidebarHoverProvider>
<DashboardSidebarHoverCardOverlay>
<div className="flex h-full flex-col border-r border-border bg-muted/45 dark:bg-muted/35">
<DashboardSidebarHeader isCollapsed={isCollapsed} />

<div className="flex-1 overflow-y-auto hide-scrollbar">
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
measuring={{
droppable: { strategy: MeasuringStrategy.Always },
}}
onDragStart={({ active }) => {
const project = groups.find((p) => p.id === active.id);
setActiveProject(project ?? null);
}}
onDragEnd={handleDragEnd}
onDragCancel={() => setActiveProject(null)}
>
<SortableContext
items={projectOrder}
strategy={verticalListSortingStrategy}
>
{orderedGroups.map((project) => (
<SortableProjectWrapper
key={project.id}
project={project}
isCollapsed={isCollapsed}
isDraggingProject={activeProject != null}
workspaceShortcutLabels={workspaceShortcutLabels}
onWorkspaceHover={refreshWorkspacePullRequest}
onToggleCollapse={toggleProjectCollapsed}
/>
))}
</SortableContext>

{createPortal(
<DragOverlay dropAnimation={null}>
{activeProject && (
<div className="bg-background shadow-lg border-b border-border">
<DashboardSidebarProjectSection
project={activeProject}
isSidebarCollapsed={isCollapsed}
isDraggingProject
<div className="flex-1 overflow-y-auto hide-scrollbar">
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
measuring={{
droppable: { strategy: MeasuringStrategy.Always },
}}
onDragStart={({ active }) => {
const project = groups.find((p) => p.id === active.id);
setActiveProject(project ?? null);
}}
onDragEnd={handleDragEnd}
onDragCancel={() => setActiveProject(null)}
>
<SortableContext
items={projectOrder}
strategy={verticalListSortingStrategy}
>
{orderedGroups.map((project) => (
<SortableProjectWrapper
key={project.id}
project={project}
isCollapsed={isCollapsed}
isDraggingProject={activeProject != null}
workspaceShortcutLabels={workspaceShortcutLabels}
onWorkspaceHover={() => {}}
onToggleCollapse={() => {}}
onWorkspaceHover={refreshWorkspacePullRequest}
onToggleCollapse={toggleProjectCollapsed}
/>
</div>
))}
</SortableContext>

{createPortal(
<DragOverlay dropAnimation={null}>
{activeProject && (
<div className="bg-background shadow-lg border-b border-border">
<DashboardSidebarProjectSection
project={activeProject}
isSidebarCollapsed={isCollapsed}
isDraggingProject
workspaceShortcutLabels={workspaceShortcutLabels}
onWorkspaceHover={() => {}}
onToggleCollapse={() => {}}
/>
</div>
)}
</DragOverlay>,
document.body,
)}
</DragOverlay>,
document.body,
)}
</DndContext>
</div>
{!isCollapsed && <DashboardSidebarPortsList />}
</div>
</DndContext>
</div>
{!isCollapsed && <DashboardSidebarPortsList />}
</div>
</DashboardSidebarHoverCardOverlay>
</DashboardSidebarHoverProvider>
</DashboardSidebarSectionRenameProvider>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/* Animate the sidebar hover card sliding between rows. Targets the
* Radix popper wrapper (which carries the position transform) only after
* it has been placed at its anchor (`data-…="ready"`), so the initial
* measuring jump from translate(0, -200%) is not animated. */
[data-radix-popper-content-wrapper]:has(
> [data-dashboard-sidebar-hover-card="ready"]
) {
transition: transform 220ms cubic-bezier(0.32, 0.72, 0, 1);
will-change: transform;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { Popover, PopoverAnchor, PopoverContent } from "@superset/ui/popover";
import type { RefObject } from "react";
import { useEffect, useRef, useState } from "react";
import { useDiffStats } from "renderer/hooks/host-service/useDiffStats";
import { useDashboardSidebarHover } from "../../providers/DashboardSidebarHoverProvider";
import { DashboardSidebarWorkspaceHoverCardContent } from "../DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceHoverCardContent";
import "./DashboardSidebarHoverCardOverlay.css";

type Measurable = { getBoundingClientRect(): DOMRect };

export function DashboardSidebarHoverCardOverlay({
children,
}: {
children: React.ReactNode;
}) {
const {
hoveredId,
anchorElement,
payload,
contextMenuOpen,
cancelClose,
requestClose,
forceClose,
} = useDashboardSidebarHover();

const virtualRef = useRef<Measurable | null>(null);
virtualRef.current = anchorElement;

const open = hoveredId !== null && payload !== null && !contextMenuOpen;
const diffStats = useDiffStats(hoveredId ?? "");

// Suppress the transform transition until Radix has placed the popover at
// its real anchor — otherwise the initial jump from the off-screen measuring
// position (translate(0, -200%)) gets animated.
const [hasPositioned, setHasPositioned] = useState(false);
const frameRef = useRef<number | null>(null);
useEffect(() => {
if (!open) {
setHasPositioned(false);
return;
}
const first = requestAnimationFrame(() => {
frameRef.current = requestAnimationFrame(() => setHasPositioned(true));
});
frameRef.current = first;
return () => {
if (frameRef.current !== null) cancelAnimationFrame(frameRef.current);
};
}, [open]);

return (
<Popover
open={open}
onOpenChange={(nextOpen) => {
if (!nextOpen) forceClose();
}}
>
{children}
<PopoverAnchor virtualRef={virtualRef as RefObject<Measurable>} />
{payload && (
<PopoverContent
side="right"
align="start"
className="w-72"
data-dashboard-sidebar-hover-card={hasPositioned ? "ready" : ""}
onOpenAutoFocus={(event) => event.preventDefault()}
onPointerEnter={cancelClose}
onPointerLeave={() => {
if (hoveredId) requestClose(hoveredId);
}}
>
<DashboardSidebarWorkspaceHoverCardContent
workspace={payload.workspace}
diffStats={diffStats}
onEditBranchClick={payload.onEditBranchClick}
/>
</PopoverContent>
)}
</Popover>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { DashboardSidebarHoverCardOverlay } from "./DashboardSidebarHoverCardOverlay";
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { OverflowFadeContainer } from "@superset/ui/overflow-fade-container";
import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip";
import { cn } from "@superset/ui/utils";
import { useNavigate } from "@tanstack/react-router";
Expand Down Expand Up @@ -67,14 +68,17 @@ export function DashboardSidebarPortGroup({
</TooltipContent>
</Tooltip>
</div>
<div className="fade-edge-r grid auto-cols-max grid-flow-col grid-rows-2 gap-1 overflow-x-auto px-3 pb-1 hide-scrollbar">
<OverflowFadeContainer
observeChildren
className="grid auto-cols-max grid-flow-col grid-rows-2 gap-1 overflow-x-auto px-3 pb-1 hide-scrollbar"
>
{group.ports.map((port) => (
<DashboardSidebarPortBadge
key={`${port.hostId}:${port.terminalId}:${port.port}`}
port={port}
/>
))}
</div>
</OverflowFadeContainer>
</div>
);
}
Loading
Loading