Skip to content

Commit 337ca7f

Browse files
committed
feat: add widgets sidebar toggle button to tabbar
Add ability to toggle the Widgets sidebar visibility via a button in the tabbar. State persists across sessions and workspaces through workspace metadata (layout:widgetsvisible).
1 parent e4e77e7 commit 337ca7f

6 files changed

Lines changed: 69 additions & 3 deletions

File tree

frontend/app/tab/tabbar.tsx

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,30 @@ const WaveAIButton = memo(({ divRef }: { divRef?: React.RefObject<HTMLDivElement
7575
});
7676
WaveAIButton.displayName = "WaveAIButton";
7777

78+
const WidgetsSidebarButton = memo(({ divRef }: { divRef?: React.RefObject<HTMLDivElement> }) => {
79+
const widgetsSidebarVisible = useAtomValue(WorkspaceLayoutModel.getInstance().widgetsSidebarVisibleAtom);
80+
81+
const onClick = () => {
82+
const current = WorkspaceLayoutModel.getInstance().getWidgetsSidebarVisible();
83+
WorkspaceLayoutModel.getInstance().setWidgetsSidebarVisible(!current);
84+
};
85+
86+
return (
87+
<Tooltip
88+
content="Toggle Widgets Sidebar"
89+
placement="bottom"
90+
hideOnClick
91+
divClassName={`flex h-[22px] px-3.5 justify-end mb-1 items-center rounded-md mr-1 box-border cursor-pointer bg-hover hover:bg-hoverbg transition-colors text-[12px] ${widgetsSidebarVisible ? "text-accent" : "text-secondary"}`}
92+
divStyle={{ WebkitAppRegion: "no-drag" } as React.CSSProperties}
93+
divOnClick={onClick}
94+
divRef={divRef}
95+
>
96+
<i className="fa fa-bars" />
97+
</Tooltip>
98+
);
99+
});
100+
WidgetsSidebarButton.displayName = "WidgetsSidebarButton";
101+
78102
function strArrayIsEqual(a: string[], b: string[]) {
79103
// null check
80104
if (a == null && b == null) {
@@ -123,6 +147,7 @@ const TabBar = memo(({ workspace, noTabs }: TabBarProps) => {
123147
const rightContainerRef = useRef<HTMLDivElement>(null);
124148
const workspaceSwitcherRef = useRef<HTMLDivElement>(null);
125149
const waveAIButtonRef = useRef<HTMLDivElement>(null);
150+
const widgetsSidebarButtonRef = createRef<HTMLDivElement>();
126151
const appMenuButtonRef = useRef<HTMLDivElement>(null);
127152
const tabWidthRef = useRef<number>(TabDefaultWidth);
128153
const scrollableRef = useRef<boolean>(false);
@@ -665,6 +690,7 @@ const TabBar = memo(({ workspace, noTabs }: TabBarProps) => {
665690
</button>
666691
<div className="flex-1" />
667692
<div ref={rightContainerRef} className="flex flex-row gap-1 items-end">
693+
<WidgetsSidebarButton divRef={widgetsSidebarButtonRef} />
668694
<UpdateStatusBanner />
669695
<div
670696
className="h-full shrink-0 z-window-drag"
@@ -675,4 +701,4 @@ const TabBar = memo(({ workspace, noTabs }: TabBarProps) => {
675701
);
676702
});
677703

678-
export { TabBar, WaveAIButton };
704+
export { TabBar, WaveAIButton, WidgetsSidebarButton };

frontend/app/workspace/workspace-layout-model.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ class WorkspaceLayoutModel {
5656
private focusTimeoutRef: NodeJS.Timeout | null = null;
5757
private debouncedPersistAIWidth: () => void;
5858
private debouncedPersistVTabWidth: () => void;
59+
private widgetsSidebarVisible: boolean;
60+
widgetsSidebarVisibleAtom: jotai.PrimitiveAtom<boolean>;
61+
private debouncedPersistWidgetsSidebarVisible: () => void;
5962

6063
private constructor() {
6164
this.aiPanelRef = null;
@@ -71,6 +74,8 @@ class WorkspaceLayoutModel {
7174
this.vtabWidth = VTabBar_DefaultWidth;
7275
this.vtabVisible = false;
7376
this.panelVisibleAtom = jotai.atom(false);
77+
this.widgetsSidebarVisible = true;
78+
this.widgetsSidebarVisibleAtom = jotai.atom(true);
7479
this.initializeFromMeta();
7580

7681
this.handleWindowResize = this.handleWindowResize.bind(this);
@@ -104,6 +109,17 @@ class WorkspaceLayoutModel {
104109
console.warn("Failed to persist vtabbar width:", e);
105110
}
106111
}, 300);
112+
113+
this.debouncedPersistWidgetsSidebarVisible = debounce(() => {
114+
try {
115+
RpcApi.SetMetaCommand(TabRpcClient, {
116+
oref: WOS.makeORef("workspace", this.getWorkspaceId()),
117+
meta: { "layout:widgetsvisible": this.widgetsSidebarVisible },
118+
});
119+
} catch (e) {
120+
console.warn("Failed to persist widgets sidebar visibility:", e);
121+
}
122+
}, 300);
107123
}
108124

109125
static getInstance(): WorkspaceLayoutModel {
@@ -135,6 +151,10 @@ class WorkspaceLayoutModel {
135151
return getOrefMetaKeyAtom(WOS.makeORef("workspace", this.getWorkspaceId()), "layout:vtabbarwidth");
136152
}
137153

154+
private getWidgetsSidebarVisibleAtom(): jotai.Atom<boolean | undefined> {
155+
return getOrefMetaKeyAtom(WOS.makeORef("workspace", this.getWorkspaceId()), "layout:widgetsvisible");
156+
}
157+
138158
private initializeFromMeta(): void {
139159
try {
140160
const savedVisible = globalStore.get(this.getPanelOpenAtom());
@@ -150,6 +170,11 @@ class WorkspaceLayoutModel {
150170
if (savedVTabWidth != null && savedVTabWidth > 0) {
151171
this.vtabWidth = savedVTabWidth;
152172
}
173+
const savedWidgetsSidebarVisible = globalStore.get(this.getWidgetsSidebarVisibleAtom());
174+
if (savedWidgetsSidebarVisible != null) {
175+
this.widgetsSidebarVisible = savedWidgetsSidebarVisible;
176+
globalStore.set(this.widgetsSidebarVisibleAtom, savedWidgetsSidebarVisible);
177+
}
153178
const tabBarPosition = globalStore.get(getSettingsKeyAtom("app:tabbar")) ?? "top";
154179
const showLeftTabBar = tabBarPosition === "left" && !isBuilderWindow();
155180
this.vtabVisible = showLeftTabBar;
@@ -352,6 +377,17 @@ class WorkspaceLayoutModel {
352377
return this.getResolvedAIWidth(window.innerWidth);
353378
}
354379

380+
getWidgetsSidebarVisible(): boolean {
381+
return this.widgetsSidebarVisible;
382+
}
383+
384+
setWidgetsSidebarVisible(visible: boolean): void {
385+
if (this.widgetsSidebarVisible === visible) return;
386+
this.widgetsSidebarVisible = visible;
387+
globalStore.set(this.widgetsSidebarVisibleAtom, visible);
388+
this.debouncedPersistWidgetsSidebarVisible();
389+
}
390+
355391
// ---- Initial percentage helpers (used by workspace.tsx for defaultSize) ----
356392

357393
getLeftGroupInitialPercentage(windowWidth: number, showLeftTabBar: boolean): number {

frontend/app/workspace/workspace.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ const WorkspaceElem = memo(() => {
4646
const tabBarPosition = useAtomValue(getSettingsKeyAtom("app:tabbar")) ?? "top";
4747
const showLeftTabBar = tabBarPosition === "left";
4848
const aiPanelVisible = useAtomValue(workspaceLayoutModel.panelVisibleAtom);
49+
const widgetsSidebarVisible = useAtomValue(workspaceLayoutModel.widgetsSidebarVisibleAtom);
4950
const windowWidth = window.innerWidth;
5051
const leftGroupInitialPct = workspaceLayoutModel.getLeftGroupInitialPercentage(windowWidth, showLeftTabBar);
5152
const innerVTabInitialPct = workspaceLayoutModel.getInnerVTabInitialPercentage(windowWidth, showLeftTabBar);
@@ -158,7 +159,7 @@ const WorkspaceElem = memo(() => {
158159
) : (
159160
<div className="flex flex-row h-full">
160161
<TabContent key={tabId} tabId={tabId} noTopPadding={showLeftTabBar && isMacOS()} />
161-
<Widgets />
162+
{widgetsSidebarVisible && <Widgets />}
162163
</div>
163164
)}
164165
</Panel>

frontend/types/gotypes.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1143,6 +1143,7 @@ declare global {
11431143
"bg:bordercolor"?: string;
11441144
"bg:activebordercolor"?: string;
11451145
"layout:vtabbarwidth"?: number;
1146+
"layout:widgetsvisible"?: boolean;
11461147
"waveai:panelopen"?: boolean;
11471148
"waveai:panelwidth"?: number;
11481149
"waveai:model"?: string;

pkg/waveobj/metaconsts.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ const (
100100
MetaKey_BgActiveBorderColor = "bg:activebordercolor"
101101

102102
MetaKey_LayoutVTabBarWidth = "layout:vtabbarwidth"
103+
MetaKey_LayoutWidgetsVisible = "layout:widgetsvisible"
103104

104105
MetaKey_WaveAiPanelOpen = "waveai:panelopen"
105106
MetaKey_WaveAiPanelWidth = "waveai:panelwidth"

pkg/waveobj/wtypemeta.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,8 @@ type MetaTSType struct {
102102
BgActiveBorderColor string `json:"bg:activebordercolor,omitempty"` // frame:activebordercolor
103103

104104
// for workspace
105-
LayoutVTabBarWidth int `json:"layout:vtabbarwidth,omitempty"`
105+
LayoutVTabBarWidth int `json:"layout:vtabbarwidth,omitempty"`
106+
LayoutWidgetsVisible *bool `json:"layout:widgetsvisible,omitempty"`
106107

107108
// for tabs+waveai
108109
WaveAiPanelOpen bool `json:"waveai:panelopen,omitempty"`

0 commit comments

Comments
 (0)