From 1cd4344f3b7cdfbce81292618c09230708e79614 Mon Sep 17 00:00:00 2001 From: Anish Date: Thu, 24 Jul 2025 17:10:45 +0530 Subject: [PATCH 01/15] add sidebar layout to page_wrapper --- daras_ai_v2/settings.py | 13 +++- routers/root.py | 145 ++++++++++++++++++++++++++-------------- workspaces/widgets.py | 23 ++++--- 3 files changed, 120 insertions(+), 61 deletions(-) diff --git a/daras_ai_v2/settings.py b/daras_ai_v2/settings.py index 56019439c..3e4d3c2c7 100644 --- a/daras_ai_v2/settings.py +++ b/daras_ai_v2/settings.py @@ -261,6 +261,7 @@ GOOEY_LOGO_IMG = "https://storage.googleapis.com/dara-c1b52.appspot.com/daras_ai/media/2a3aacb4-0941-11ee-b236-02420a0001fb/thumbs/logo%20black.png_400x400.png" GOOEY_LOGO_IMG_WHITE = "https://storage.googleapis.com/dara-c1b52.appspot.com/daras_ai/media/ea26bc06-7eda-11ef-89fa-02420a0001f6/gooey-white-logo.png" GOOEY_LOGO_RECT = "https://storage.googleapis.com/dara-c1b52.appspot.com/daras_ai/media/d628be8a-9207-11ef-8aee-02420a000186/984x272%20rect%20gooey%20logo.png" +GOOEY_LOGO_FACE = "https://storage.googleapis.com/dara-c1b52.appspot.com/daras_ai/media/bb2587e4-66eb-11f0-a197-02420a00013e/gooey-logo-robo.png" os.environ["REPLICATE_API_TOKEN"] = config("REPLICATE_API_TOKEN", default="") @@ -309,7 +310,17 @@ ("/pricing", "Pricing"), (CONTACT_URL, "Contact"), ] -HEADER_ICONS = {} + +SIDEBAR_LINKS = [ + ("/explore/", "Explore", ""), + (DOCS_URL, "Docs", ""), + ("/api/", "API", ""), + (BLOG_URL, "Blog", ""), + ("/pricing", "Pricing", ""), + (CONTACT_URL, "Contact", ""), +] + +HEADER_ICONS = {"/explore/": ''} GPU_SERVER_1 = furl(config("GPU_SERVER_1", "http://gpu-1.gooey.ai")) diff --git a/routers/root.py b/routers/root.py index 0bae2977c..ce7c7cad0 100644 --- a/routers/root.py +++ b/routers/root.py @@ -368,7 +368,6 @@ def _api_docs_page(request: Request): manage_api_keys(workspace=page.current_workspace, user=page.request.user) - @gui.route( app, "/{page_slug}/examples/", @@ -706,57 +705,99 @@ def get_og_url_path(request) -> str: @contextmanager -def page_wrapper( - request: Request, - className="", - search_filters: typing.Optional[SearchFilters] = None, - show_search_bar: bool = True, -): - from routers.account import explore_in_current_workspace - - context = {"request": request, "block_incognito": True} - - with gui.div(className="d-flex flex-column min-vh-100"): - gui.html(templates.get_template("gtag.html").render(**context)) - - with ( - gui.div(className="header"), - gui.div(className="navbar navbar-expand-xl bg-transparent p-0 m-0"), - gui.div(className="container-xxl my-2"), - gui.div( - className="position-relative w-100 d-flex justify-content-between gap-2" - ), - ): - with ( - gui.div(className="d-md-block"), - gui.tag("a", href="/"), +def page_wrapper(request: Request, className=""): + context = { + "request": request, + "block_incognito": True, + } + sidebar_container, pane_container = gui.sidebar_layout( + toggle_key="main-sidebar-layout", + ) + is_sidebar_open = bool(gui.session_state.get("main-sidebar-layout", True)) + with sidebar_container: + with gui.div(className="overflow-hidden flex-grow-1 position-relative"): + with gui.div( + className="d-flex px-2 py-3 align-items-center justify-content-between overflow-hidden text-nowrap", + style={"min-width": "251px"}, ): gui.tag( "img", - src=settings.GOOEY_LOGO_IMG, - width="300", - height="142", - className="img-fluid logo d-none d-sm-block", + src=settings.GOOEY_LOGO_FACE, + width="44px", + height="44px", + className=" logo-face", ) - gui.tag( - "img", - src=settings.GOOEY_LOGO_RECT, - width="145", - height="40", - className="img-fluid logo d-sm-none", + current_workspace = None + if is_sidebar_open: + if request.user and not request.user.is_anonymous: + current_workspace = global_workspace_selector( + request.user, request.session + ) + else: + current_workspace = None + anonymous_login_container(request, context) + gui.button( + "", + unsafe_allow_html=True, + type="tertiary", + className="m-0", + id="main-sidebar-layout", ) - if show_search_bar: - _render_mobile_search_button(request, search_filters) - - with gui.div( - className="d-flex gap-2 justify-content-end flex-wrap align-items-center" + with ( + gui.styled("& i { font-size: 24px; }"), + gui.div( + className="d-flex flex-column flex-grow-1 gap-3 px-3 my-3 text-nowrap", + style={"min-width": "251px", "marginLeft": "4px"}, + ), ): - for url, label in settings.HEADER_LINKS: - render_header_link( - url=url, label=label, icon=settings.HEADER_ICONS.get(url) - ) + # saved + with gui.tag( + "a", href="/saved/", className="pe-2 text-decoration-none d-flex" + ): + with gui.div(className="d-inline-block me-4 small"): + gui.html( + "", + className="me-2", + ) + if is_sidebar_open: + gui.html("Saved") + + for i, (url, label, icon) in enumerate(settings.SIDEBAR_LINKS): + # closed sidebar + if not is_sidebar_open: + if i >= 1: + break + if icon: + with gui.div( + className="d-inline-block me-3 small", + style={"height": "24px"}, + ): + gui.html(icon) + else: + with gui.tag( + "a", href=url, className="text-decoration-none d-flex" + ): + if icon: + with gui.div(className="d-inline-block me-4 small"): + gui.html(icon) + else: + with gui.div( + className="d-inline-block me-3 small", + style={"width": "24px"}, + ): + gui.html(" ") + gui.html(label) + with ( + gui.styled("& img { width: 32px !important; height: 32px !important; }"), + gui.div( + className="p-3 position-absolute bottom-0", + style={"width": "100%", "zIndex": 1000}, + ), + ): + # workspace selector + if not is_sidebar_open: if request.user and not request.user.is_anonymous: render_header_link( url=get_route_path(explore_in_current_workspace), @@ -765,19 +806,23 @@ def page_wrapper( ) current_workspace = global_workspace_selector( - request.user, request.session + request.user, request.session, hide_name=True ) else: current_workspace = None anonymous_login_container(request, context) - gui.html(copy_to_clipboard_scripts) + with pane_container: + with gui.div(className="d-flex flex-column min-vh-100"): + gui.html(templates.get_template("gtag.html").render(**context)) + + gui.html(copy_to_clipboard_scripts) - with gui.div(id="main-content", className="container-xxl " + className): - yield current_workspace + with gui.div(id="main-content", className="px-3 " + className): + yield current_workspace - gui.html(templates.get_template("footer.html").render(**context)) - gui.html(templates.get_template("login_scripts.html").render(**context)) + gui.html(templates.get_template("footer.html").render(**context)) + gui.html(templates.get_template("login_scripts.html").render(**context)) def _render_mobile_search_button(request: Request, search_filters: SearchFilters): diff --git a/workspaces/widgets.py b/workspaces/widgets.py index a6d485e8b..e36c2003e 100644 --- a/workspaces/widgets.py +++ b/workspaces/widgets.py @@ -18,7 +18,7 @@ SWITCH_WORKSPACE_KEY = "--switch-workspace" -def global_workspace_selector(user: AppUser, session: dict): +def global_workspace_selector(user: AppUser, session: dict, hide_name: bool = False): from routers.account import ( members_route, profile_route, @@ -57,15 +57,18 @@ def global_workspace_selector(user: AppUser, session: dict): else: display_name = "Personal" else: - display_name = current.display_name(user) - with gui.div(className="d-inline-flex align-items-center gap-2 text-truncate"): - gui.html(f"{current.html_icon()}") - gui.html( - html.escape(display_name), - className="d-none d-md-inline text-truncate", - style={"maxWidth": "150px"}, - ) - gui.html('') + display_name = html.escape(current.display_name(user)) + gui.html( + " ".join( + [ + current.html_icon(), + display_name if not hide_name else "", + '' + if not hide_name + else "", + ], + ), + ) with ( content, From 4d6365919e54ab1081f4cb18063cb66b9b5d4786 Mon Sep 17 00:00:00 2001 From: Anish Date: Wed, 30 Jul 2025 18:58:20 +0530 Subject: [PATCH 02/15] move sidebar component locally --- daras_ai_v2/base.py | 4 + daras_ai_v2/icons.py | 1 + daras_ai_v2/settings.py | 2 +- routers/root.py | 182 ++++++++++++++++++------------------ widgets/explore.py | 1 + widgets/sidebar.py | 198 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 293 insertions(+), 95 deletions(-) create mode 100644 widgets/sidebar.py diff --git a/daras_ai_v2/base.py b/daras_ai_v2/base.py index b57ca2e0d..abe6ff9ff 100644 --- a/daras_ai_v2/base.py +++ b/daras_ai_v2/base.py @@ -91,6 +91,7 @@ ) from routers.root import PREVIEW_ROUTE_WORKFLOWS +from widgets.sidebar import render_default_sidebar, sidebar_logo_header MAX_SEED = 4294967294 gooey_rng = Random() @@ -193,6 +194,9 @@ def __init__( self.tab = tab self.request = request + def render_sidebar(self, request, sidebar_ref): + render_default_sidebar() + @classmethod def api_endpoint(cls) -> str: return f"/v2/{cls.slug_versions[0]}" diff --git a/daras_ai_v2/icons.py b/daras_ai_v2/icons.py index 1acaf65db..b9e2bac3c 100644 --- a/daras_ai_v2/icons.py +++ b/daras_ai_v2/icons.py @@ -53,6 +53,7 @@ info = "" search = '' library = '' +sidebar_flip = "" # brands github = '' diff --git a/daras_ai_v2/settings.py b/daras_ai_v2/settings.py index 3e4d3c2c7..60fc5d5bf 100644 --- a/daras_ai_v2/settings.py +++ b/daras_ai_v2/settings.py @@ -316,7 +316,7 @@ (DOCS_URL, "Docs", ""), ("/api/", "API", ""), (BLOG_URL, "Blog", ""), - ("/pricing", "Pricing", ""), + ("/pricing", "Pricing", ""), (CONTACT_URL, "Contact", ""), ] diff --git a/routers/root.py b/routers/root.py index ce7c7cad0..6b3e7b63d 100644 --- a/routers/root.py +++ b/routers/root.py @@ -47,6 +47,7 @@ from routers.static_pages import serve_static_file from widgets.workflow_search import SearchFilters, render_search_bar_with_redirect from workspaces.widgets import global_workspace_selector, workspace_selector_link +from widgets.sidebar import render_default_sidebar, sidebar_layout, use_sidebar app = CustomAPIRouter() @@ -368,6 +369,7 @@ def _api_docs_page(request: Request): manage_api_keys(workspace=page.current_workspace, user=page.request.user) + @gui.route( app, "/{page_slug}/examples/", @@ -688,7 +690,7 @@ def render_recipe_page( if not gui.session_state: gui.session_state.update(page.current_sr_to_session_state()) - with page_wrapper(request): + with page_wrapper(request, page=page): page.render() return dict( @@ -705,115 +707,107 @@ def get_og_url_path(request) -> str: @contextmanager -def page_wrapper(request: Request, className=""): +def page_wrapper(request: Request, className="", page=None): context = { "request": request, "block_incognito": True, } - sidebar_container, pane_container = gui.sidebar_layout( - toggle_key="main-sidebar-layout", - ) - is_sidebar_open = bool(gui.session_state.get("main-sidebar-layout", True)) + + sidebar_ref = use_sidebar("main-sidebar", default_open=True) + sidebar_container, pane_container = sidebar_layout(sidebar_ref) + + container = page if page else None with sidebar_container: - with gui.div(className="overflow-hidden flex-grow-1 position-relative"): + with gui.styled(""" + .gooey-sidebar-closed:hover { + & .hover-btn { + display: block !important; + } + & .logo-face { + display: none !important; + } + } + """): with gui.div( - className="d-flex px-2 py-3 align-items-center justify-content-between overflow-hidden text-nowrap", - style={"min-width": "251px"}, + className="overflow-hidden flex-grow-1 position-relative", ): - gui.tag( - "img", - src=settings.GOOEY_LOGO_FACE, - width="44px", - height="44px", - className=" logo-face", - ) - current_workspace = None - if is_sidebar_open: - if request.user and not request.user.is_anonymous: - current_workspace = global_workspace_selector( - request.user, request.session - ) - else: - current_workspace = None - anonymous_login_container(request, context) - gui.button( - "", - unsafe_allow_html=True, - type="tertiary", - className="m-0", - id="main-sidebar-layout", - ) + with gui.div( + className="d-flex px-md-2 px-3 py-2 py-md-3 align-items-center justify-content-between overflow-hidden text-nowrap", + ): + gui.tag( + "img", + src=settings.GOOEY_LOGO_FACE, + width="44px", + height="44px", + className=" logo-face d-block", + ) + open_sidebar_btn = gui.button( + label=icons.sidebar_flip, + className="m-0 d-none hover-btn", + unsafe_allow_html=True, + type="tertiary", + ) + if open_sidebar_btn: + sidebar_ref.set_open(True) + raise gui.RerunException() + + current_workspace = None + if gui.session_state.get("main-sidebar", True): + if request.user and not request.user.is_anonymous: + current_workspace = global_workspace_selector( + request.user, request.session + ) + else: + current_workspace = None + anonymous_login_container(request, context) + + close_mobile_sidebar = gui.button( + label=icons.cancel, + className="m-0 d-md-none", + unsafe_allow_html=True, + type="tertiary", + ) + if close_mobile_sidebar: + sidebar_ref.set_mobile_open(False) + raise gui.RerunException() + + close_sidebar = gui.button( + label=icons.sidebar_flip, + className="m-0 d-none d-md-block", + unsafe_allow_html=True, + type="tertiary", + ) + if close_sidebar: + sidebar_ref.set_open(False) + raise gui.RerunException() + + if container: + container.render_sidebar(request, sidebar_ref) + else: + render_default_sidebar() + # Bottom section with workspace selector when sidebar is closed with ( - gui.styled("& i { font-size: 24px; }"), + gui.styled( + "& img { width: 32px !important; height: 32px !important; }" + ), gui.div( - className="d-flex flex-column flex-grow-1 gap-3 px-3 my-3 text-nowrap", - style={"min-width": "251px", "marginLeft": "4px"}, + className="p-3 position-absolute bottom-0", + style={"width": "100%", "zIndex": 1000}, ), ): - # saved - with gui.tag( - "a", href="/saved/", className="pe-2 text-decoration-none d-flex" - ): - with gui.div(className="d-inline-block me-4 small"): - gui.html( - "", - className="me-2", + if not gui.session_state.get("main-sidebar", True): + if request.user and not request.user.is_anonymous: + current_workspace = global_workspace_selector( + request.user, request.session, hide_name=True ) - if is_sidebar_open: - gui.html("Saved") - - for i, (url, label, icon) in enumerate(settings.SIDEBAR_LINKS): - # closed sidebar - if not is_sidebar_open: - if i >= 1: - break - if icon: - with gui.div( - className="d-inline-block me-3 small", - style={"height": "24px"}, - ): - gui.html(icon) else: - with gui.tag( - "a", href=url, className="text-decoration-none d-flex" - ): - if icon: - with gui.div(className="d-inline-block me-4 small"): - gui.html(icon) - else: - with gui.div( - className="d-inline-block me-3 small", - style={"width": "24px"}, - ): - gui.html(" ") - gui.html(label) - - with ( - gui.styled("& img { width: 32px !important; height: 32px !important; }"), - gui.div( - className="p-3 position-absolute bottom-0", - style={"width": "100%", "zIndex": 1000}, - ), - ): - # workspace selector - if not is_sidebar_open: - if request.user and not request.user.is_anonymous: - render_header_link( - url=get_route_path(explore_in_current_workspace), - label="Saved", - icon=icons.save, - ) - - current_workspace = global_workspace_selector( - request.user, request.session, hide_name=True - ) - else: - current_workspace = None - anonymous_login_container(request, context) + current_workspace = None + anonymous_login_container(request, context) + # Main content pane with pane_container: - with gui.div(className="d-flex flex-column min-vh-100"): + with gui.div(className="d-flex flex-column min-vh-100 w-100"): gui.html(templates.get_template("gtag.html").render(**context)) gui.html(copy_to_clipboard_scripts) diff --git a/widgets/explore.py b/widgets/explore.py index 3deac0cef..9a69da435 100644 --- a/widgets/explore.py +++ b/widgets/explore.py @@ -16,6 +16,7 @@ render_search_filters, render_search_results, ) +from widgets.sidebar import sidebar_logo_header META_TITLE = "Explore AI Workflows" META_DESCRIPTION = "Find, fork and run your field’s favorite AI recipes on Gooey.AI" diff --git a/widgets/sidebar.py b/widgets/sidebar.py new file mode 100644 index 000000000..70f0c6eed --- /dev/null +++ b/widgets/sidebar.py @@ -0,0 +1,198 @@ +import gooey_gui as gui +from daras_ai_v2 import settings +from textwrap import dedent +from daras_ai_v2 import icons + + +class SidebarRef: + def __init__(self, key: str, is_open: bool = True, is_mobile_open: bool = False): + self.key = key + self.is_open = is_open + self.is_mobile_open = is_mobile_open + + def set_open(self, value: bool): + self.is_open = gui.session_state[self.key] = value + + def set_mobile_open(self, value: bool): + self.is_mobile_open = gui.session_state[self.mobile_key] = value + self.set_open(value) + + @property + def mobile_key(self): + return self.key + ":mobile" + + @property + def toggle_btn_key(self): + return self.key + ":toggle" + + @property + def close_btn_key(self): + return self.key + ":close" + + @property + def open_btn_key(self): + return self.key + ":open" + + +def use_sidebar(key: str, default_open: bool = True) -> SidebarRef: + """Create or get a sidebar reference with state management.""" + ref = SidebarRef( + key=key, + is_open=bool(gui.session_state.get(key, default_open)), + is_mobile_open=bool(gui.session_state.get(key + ":mobile", False)), + ) + + return ref + + +def sidebar_list_item(icon, title, is_sidebar_open): + with ( + gui.styled( + """ + & i { + font-size: 1.2rem; + max-width: 18px; + } + """ + ), + gui.div(className="d-inline-block me-4"), + ): + gui.html(icon, className="me-2") + if is_sidebar_open: + gui.html(title) + + +def sidebar_item_list(is_sidebar_open): + for i, (url, label, icon) in enumerate(settings.SIDEBAR_LINKS): + if not is_sidebar_open and i >= 1: + break + with gui.tag("a", href=url, className="text-decoration-none d-flex"): + if icon: + with gui.div( + className="d-inline-block me-3", + style={"height": "24px"}, + ): + sidebar_list_item(icon, label, is_sidebar_open) + else: + with gui.div( + className="d-inline-block me-3 small", + style={"width": "24px"}, + ): + gui.html(" ") + gui.html(label) + + +def render_default_sidebar(): + is_sidebar_open = gui.session_state.get("main-sidebar", True) + with gui.div( + className="d-flex flex-column flex-grow-1 gap-3 px-3 my-3 text-nowrap", + style={"marginLeft": "4px"}, + ): + with gui.tag( + "a", + href="/saved/", + className="pe-2 text-decoration-none d-flex", + ): + sidebar_list_item( + "", "Saved", is_sidebar_open + ) + + sidebar_item_list(is_sidebar_open) + + +def sidebar_logo_header(): + with gui.div( + className="d-flex align-items-center justify-content-between d-md-none me-2 w-100 py-2" + ): + sidebar_ref = use_sidebar("main-sidebar", default_open=True) + gui.tag( + "img", + src=settings.GOOEY_LOGO_FACE, + width="44px", + height="44px", + className=" logo-face", + ) + open_mobile_sidebar = gui.button( + label=icons.sidebar_flip, + className="m-0", + unsafe_allow_html=True, + type="tertiary", + ) + if open_mobile_sidebar: + print(sidebar_ref.set_mobile_open, ">>>") + sidebar_ref.set_mobile_open(True) + raise gui.RerunException() + + +def sidebar_layout(sidebar_ref: SidebarRef): + is_mobile_open = sidebar_ref.is_mobile_open + sidebar_funtion_classes = ( + "gooey-sidebar-open" if sidebar_ref.is_open else "gooey-sidebar-closed" + ) + side_bar_styles = dedent( + """ + & .gooey-sidebar { + transition: width 0.2s cubic-bezier(0.4, 0, 0.2, 1), min-width 0.2s cubic-bezier(0.4, 0, 0.2, 1), max-width 0.2s cubic-bezier(0.4, 0, 0.2, 1); + background-color: #f9f9f9; + position: sticky; + top: 0; + left: 0; + bottom: 0; + } + & .gooey-sidebar-open { + min-width: 250px; + width: 250px; + max-width: 250px; + } + & .gooey-sidebar-closed { + min-width: 60px; + width: 60px; + max-width: 60px; + } + + @media (max-width: 767px) { + & .gooey-sidebar-open { + position: fixed; + min-width: 100vw; + width: 100vw; + max-width: 100vw; + z-index: 2000; + } + & .gooey-sidebar-closed { + position: fixed; + min-width: 0px; + width: 0px; + max-width: 0px; + } + } + """ + ) + if not is_mobile_open: + side_bar_styles += dedent( + """ + @media (max-width: 767px) { + & .gooey-sidebar-open { + display: none !important; + position: fixed; + max-width: 0px !important; + } + } + """ + ) + + print(side_bar_styles, ">>>") + with ( + gui.styled(side_bar_styles), + gui.div( + className="d-flex w-100 h-100 position-relative", style={"height": "100dvh"} + ), + ): + sidebar_content_placeholder = gui.div( + className=f"d-flex flex-column flex-grow-1 gooey-sidebar {sidebar_funtion_classes}", + style={"height": "100dvh"}, + ) + pane_content_placeholder = gui.div(className="d-flex flex-grow-1") + # sidebar content + sidebar_content_placeholder + pane_content_placeholder + return sidebar_content_placeholder, pane_content_placeholder From 3eead3d42fb6927f977c275c55f0b32628f5f2de Mon Sep 17 00:00:00 2001 From: Anish Date: Wed, 30 Jul 2025 20:30:10 +0530 Subject: [PATCH 03/15] fix workspace selector overflow --- routers/root.py | 2 +- widgets/sidebar.py | 1 + workspaces/widgets.py | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/routers/root.py b/routers/root.py index 6b3e7b63d..59ac74241 100644 --- a/routers/root.py +++ b/routers/root.py @@ -729,7 +729,7 @@ def page_wrapper(request: Request, className="", page=None): } """): with gui.div( - className="overflow-hidden flex-grow-1 position-relative", + className="flex-grow-1 position-relative", ): with gui.div( className="d-flex px-md-2 px-3 py-2 py-md-3 align-items-center justify-content-between overflow-hidden text-nowrap", diff --git a/widgets/sidebar.py b/widgets/sidebar.py index 70f0c6eed..6485e200a 100644 --- a/widgets/sidebar.py +++ b/widgets/sidebar.py @@ -138,6 +138,7 @@ def sidebar_layout(sidebar_ref: SidebarRef): top: 0; left: 0; bottom: 0; + z-index: 2000; } & .gooey-sidebar-open { min-width: 250px; diff --git a/workspaces/widgets.py b/workspaces/widgets.py index e36c2003e..a43607cdf 100644 --- a/workspaces/widgets.py +++ b/workspaces/widgets.py @@ -73,7 +73,8 @@ def global_workspace_selector(user: AppUser, session: dict, hide_name: bool = Fa with ( content, gui.div( - className="d-flex flex-column bg-white border border-dark rounded shadow mx-2 overflow-hidden", + className="d-flex flex-column bg-white border border-dark rounded shadow mx-2 position-relative", + style={"width": "max-content"}, ), ): row_height = "2.2rem" From 8a5565a058dba323a215936044ba63c948e4e12c Mon Sep 17 00:00:00 2001 From: Anish Date: Tue, 5 Aug 2025 14:41:23 +0530 Subject: [PATCH 04/15] fix z-index, remove down arrow, reomve close button when closed --- routers/root.py | 21 +++++++++++---------- widgets/sidebar.py | 4 ++-- workspaces/widgets.py | 3 --- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/routers/root.py b/routers/root.py index 59ac74241..48663b046 100644 --- a/routers/root.py +++ b/routers/root.py @@ -771,15 +771,16 @@ def page_wrapper(request: Request, className="", page=None): sidebar_ref.set_mobile_open(False) raise gui.RerunException() - close_sidebar = gui.button( - label=icons.sidebar_flip, - className="m-0 d-none d-md-block", - unsafe_allow_html=True, - type="tertiary", - ) - if close_sidebar: - sidebar_ref.set_open(False) - raise gui.RerunException() + if sidebar_ref.is_open: + close_sidebar = gui.button( + label=icons.sidebar_flip, + className="m-0 d-none d-md-block", + unsafe_allow_html=True, + type="tertiary", + ) + if close_sidebar: + sidebar_ref.set_open(False) + raise gui.RerunException() if container: container.render_sidebar(request, sidebar_ref) @@ -792,7 +793,7 @@ def page_wrapper(request: Request, className="", page=None): "& img { width: 32px !important; height: 32px !important; }" ), gui.div( - className="p-3 position-absolute bottom-0", + className="p-3 position-absolute bottom-0 d-md-block d-none", style={"width": "100%", "zIndex": 1000}, ), ): diff --git a/widgets/sidebar.py b/widgets/sidebar.py index 6485e200a..3219da535 100644 --- a/widgets/sidebar.py +++ b/widgets/sidebar.py @@ -138,7 +138,7 @@ def sidebar_layout(sidebar_ref: SidebarRef): top: 0; left: 0; bottom: 0; - z-index: 2000; + z-index: 999; } & .gooey-sidebar-open { min-width: 250px; @@ -164,6 +164,7 @@ def sidebar_layout(sidebar_ref: SidebarRef): min-width: 0px; width: 0px; max-width: 0px; + overflow: hidden; } } """ @@ -181,7 +182,6 @@ def sidebar_layout(sidebar_ref: SidebarRef): """ ) - print(side_bar_styles, ">>>") with ( gui.styled(side_bar_styles), gui.div( diff --git a/workspaces/widgets.py b/workspaces/widgets.py index a43607cdf..841d3a752 100644 --- a/workspaces/widgets.py +++ b/workspaces/widgets.py @@ -63,9 +63,6 @@ def global_workspace_selector(user: AppUser, session: dict, hide_name: bool = Fa [ current.html_icon(), display_name if not hide_name else "", - '' - if not hide_name - else "", ], ), ) From ccf664a12867b2bfefb9f6d3f2fa880b0ce7a6dc Mon Sep 17 00:00:00 2001 From: Anish Date: Tue, 5 Aug 2025 18:16:36 +0530 Subject: [PATCH 05/15] fix: sidebar bugs --- daras_ai_v2/settings.py | 9 ++-- routers/account.py | 3 +- routers/root.py | 46 ++++++++++++----- static/css/app.css | 5 -- widgets/sidebar.py | 111 ++++++++++++++++++++++++++++------------ workspaces/widgets.py | 17 +++--- 6 files changed, 130 insertions(+), 61 deletions(-) diff --git a/daras_ai_v2/settings.py b/daras_ai_v2/settings.py index 60fc5d5bf..9953de4b8 100644 --- a/daras_ai_v2/settings.py +++ b/daras_ai_v2/settings.py @@ -313,12 +313,13 @@ SIDEBAR_LINKS = [ ("/explore/", "Explore", ""), - (DOCS_URL, "Docs", ""), - ("/api/", "API", ""), - (BLOG_URL, "Blog", ""), + (DOCS_URL, "Docs", ""), + ("/api/", "API", ""), + (BLOG_URL, "Blog", ""), ("/pricing", "Pricing", ""), - (CONTACT_URL, "Contact", ""), + (CONTACT_URL, "Contact", ""), ] +SIDEBAR_ICON_SIZE = "40px" HEADER_ICONS = {"/explore/": ''} diff --git a/routers/account.py b/routers/account.py index f633eeab3..235d3725a 100644 --- a/routers/account.py +++ b/routers/account.py @@ -27,6 +27,7 @@ from workspaces.models import Workspace, WorkspaceInvite from workspaces.views import invitation_page, workspaces_page from workspaces.widgets import get_current_workspace, SWITCH_WORKSPACE_KEY +from widgets.sidebar import sidebar_logo_header if typing.TYPE_CHECKING: from app_users.models import AppUser @@ -381,7 +382,7 @@ def account_page_wrapper(request: Request, current_tab: TabData): raise gui.RedirectException(str(redirect_url)) with page_wrapper(request) as current_workspace: - gui.div(className="mt-5") + sidebar_logo_header() with gui.nav_tabs(): for tab in AccountTabs.get_tabs_for_user(request.user, current_workspace): with gui.nav_item(tab.url_path, active=tab == current_tab): diff --git a/routers/root.py b/routers/root.py index 48663b046..887015a39 100644 --- a/routers/root.py +++ b/routers/root.py @@ -730,15 +730,18 @@ def page_wrapper(request: Request, className="", page=None): """): with gui.div( className="flex-grow-1 position-relative", + id="sidebar-click-container", ): with gui.div( - className="d-flex px-md-2 px-3 py-2 py-md-3 align-items-center justify-content-between overflow-hidden text-nowrap", + className="d-flex px-md-2 px-3 py-2 align-items-center justify-content-between text-nowrap", + style={"height": "64px"}, ): + # sidebar header gui.tag( "img", src=settings.GOOEY_LOGO_FACE, - width="44px", - height="44px", + width=settings.SIDEBAR_ICON_SIZE, + height=settings.SIDEBAR_ICON_SIZE, className=" logo-face d-block", ) open_sidebar_btn = gui.button( @@ -752,18 +755,35 @@ def page_wrapper(request: Request, className="", page=None): raise gui.RerunException() current_workspace = None - if gui.session_state.get("main-sidebar", True): - if request.user and not request.user.is_anonymous: - current_workspace = global_workspace_selector( - request.user, request.session - ) - else: - current_workspace = None - anonymous_login_container(request, context) + with ( + gui.styled( + """ + & > button { + max-width: 100%; + overflow-x: hidden; + text-overflow: ellipsis; + white-space: nowrap; + height: 100%; + margin: auto; + } + """, + ), + gui.div( + className="position-relative", style={"maxWidth": "50%"} + ), + ): + if sidebar_ref.is_open: + if request.user and not request.user.is_anonymous: + current_workspace = global_workspace_selector( + request.user, request.session + ) + else: + current_workspace = None + anonymous_login_container(request, context) close_mobile_sidebar = gui.button( label=icons.cancel, - className="m-0 d-md-none", + className="m-0 d-md-none p-2", unsafe_allow_html=True, type="tertiary", ) @@ -797,7 +817,7 @@ def page_wrapper(request: Request, className="", page=None): style={"width": "100%", "zIndex": 1000}, ), ): - if not gui.session_state.get("main-sidebar", True): + if not sidebar_ref.is_open: if request.user and not request.user.is_anonymous: current_workspace = global_workspace_selector( request.user, request.session, hide_name=True diff --git a/static/css/app.css b/static/css/app.css index 2daec1019..f916ce7ce 100644 --- a/static/css/app.css +++ b/static/css/app.css @@ -618,11 +618,6 @@ a.text-primary:hover { font-size: 14px; } -html { - /* fix for jumping scrollbar issue - https://css-tricks.com/elegant-fix-jumping-scrollbar-issue/ */ - margin-left: calc(100vw - 100%); -} - .gui-img { width: 100%; max-width: 450px; diff --git a/widgets/sidebar.py b/widgets/sidebar.py index 3219da535..f5503c938 100644 --- a/widgets/sidebar.py +++ b/widgets/sidebar.py @@ -45,41 +45,61 @@ def use_sidebar(key: str, default_open: bool = True) -> SidebarRef: return ref -def sidebar_list_item(icon, title, is_sidebar_open): +def sidebar_list_item(icon, title, is_sidebar_open, url=None): with ( gui.styled( """ - & i { - font-size: 1.2rem; + & a { + font-size: 1rem; + text-decoration: none; + } + & .sidebar-list-item { + min-height: 24px; + } + & .sidebar-list-item-icon { max-width: 18px; + min-width: 18px; + } + & a:hover { + .sidebar-list-item-title { + text-decoration: underline !important; + text-decoration-color: #000 !important; + text-decoration-thickness: 2px !important; + text-underline-offset: 2px !important; + } } """ ), - gui.div(className="d-inline-block me-4"), + gui.div(className="d-flex align-items-center"), ): - gui.html(icon, className="me-2") - if is_sidebar_open: - gui.html(title) + with gui.div(className="d-flex align-items-center w-100"): + with gui.tag( + "a", + href=url, + className="d-flex align-items-center sidebar-list-item w-100", + ): + if icon: + gui.html( + icon, + className="sidebar-list-item-icon me-2 d-flex justify-content-center", + ) + if is_sidebar_open: + gui.html(title, className="sidebar-list-item-title d-block") def sidebar_item_list(is_sidebar_open): for i, (url, label, icon) in enumerate(settings.SIDEBAR_LINKS): if not is_sidebar_open and i >= 1: break - with gui.tag("a", href=url, className="text-decoration-none d-flex"): - if icon: - with gui.div( - className="d-inline-block me-3", - style={"height": "24px"}, - ): - sidebar_list_item(icon, label, is_sidebar_open) - else: - with gui.div( - className="d-inline-block me-3 small", - style={"width": "24px"}, - ): - gui.html(" ") - gui.html(label) + if icon: + with gui.div(): + sidebar_list_item(icon, label, is_sidebar_open, url) + else: + with gui.div( + className="d-inline-block me-2 small", + ): + gui.html(" ") + gui.html(label) def render_default_sidebar(): @@ -88,13 +108,12 @@ def render_default_sidebar(): className="d-flex flex-column flex-grow-1 gap-3 px-3 my-3 text-nowrap", style={"marginLeft": "4px"}, ): - with gui.tag( - "a", - href="/saved/", - className="pe-2 text-decoration-none d-flex", - ): + with gui.div(className="pe-2"): sidebar_list_item( - "", "Saved", is_sidebar_open + "", + "Saved", + is_sidebar_open, + "/saved/", ) sidebar_item_list(is_sidebar_open) @@ -102,14 +121,15 @@ def render_default_sidebar(): def sidebar_logo_header(): with gui.div( - className="d-flex align-items-center justify-content-between d-md-none me-2 w-100 py-2" + className="d-flex align-items-center justify-content-between d-md-none me-2 w-100 py-2", + style={"height": "64px"}, ): sidebar_ref = use_sidebar("main-sidebar", default_open=True) gui.tag( "img", src=settings.GOOEY_LOGO_FACE, - width="44px", - height="44px", + width=settings.SIDEBAR_ICON_SIZE, + height=settings.SIDEBAR_ICON_SIZE, className=" logo-face", ) open_mobile_sidebar = gui.button( @@ -119,7 +139,6 @@ def sidebar_logo_header(): type="tertiary", ) if open_mobile_sidebar: - print(sidebar_ref.set_mobile_open, ">>>") sidebar_ref.set_mobile_open(True) raise gui.RerunException() @@ -131,6 +150,11 @@ def sidebar_layout(sidebar_ref: SidebarRef): ) side_bar_styles = dedent( """ + html { + /* override margin-left from app.css */ + margin-left: 0 !important; + } + & .gooey-sidebar { transition: width 0.2s cubic-bezier(0.4, 0, 0.2, 1), min-width 0.2s cubic-bezier(0.4, 0, 0.2, 1), max-width 0.2s cubic-bezier(0.4, 0, 0.2, 1); background-color: #f9f9f9; @@ -151,6 +175,10 @@ def sidebar_layout(sidebar_ref: SidebarRef): max-width: 60px; } + & .gooey-sidebar-closed:hover { + cursor: e-resize; + } + @media (max-width: 767px) { & .gooey-sidebar-open { position: fixed; @@ -185,9 +213,28 @@ def sidebar_layout(sidebar_ref: SidebarRef): with ( gui.styled(side_bar_styles), gui.div( - className="d-flex w-100 h-100 position-relative", style={"height": "100dvh"} + className="d-flex w-100 h-100 position-relative sidebar-click-container", + style={"height": "100dvh"}, + onClick=dedent( + """ + if (event.target.id === "sidebar-click-container") { + document.getElementById("sidebar-hidden-btn").click(); + } + """ + if not sidebar_ref.is_open + else "" + ), ), ): + open_sidebar_btn = gui.button( + label="", + className="d-none", + id="sidebar-hidden-btn", + ) + if open_sidebar_btn: + sidebar_ref.set_open(True) + raise gui.RerunException() + sidebar_content_placeholder = gui.div( className=f"d-flex flex-column flex-grow-1 gooey-sidebar {sidebar_funtion_classes}", style={"height": "100dvh"}, diff --git a/workspaces/widgets.py b/workspaces/widgets.py index 841d3a752..a8212f12c 100644 --- a/workspaces/widgets.py +++ b/workspaces/widgets.py @@ -47,8 +47,9 @@ def global_workspace_selector(user: AppUser, session: dict, hide_name: bool = Fa except (KeyError, IndexError): current = workspaces[0] - with gui.styled("& > button { padding-top: 5px; }"), gui.div(): - popover, content = gui.popover(interactive=True, placement="bottom") + popover, content = gui.popover( + interactive=True, placement="bottom", className="w-100" + ) with popover: if current.is_personal and current.created_by_id == user.id: @@ -70,11 +71,15 @@ def global_workspace_selector(user: AppUser, session: dict, hide_name: bool = Fa with ( content, gui.div( - className="d-flex flex-column bg-white border border-dark rounded shadow mx-2 position-relative", - style={"width": "max-content"}, + className="d-flex flex-column flex-nowrap bg-white border border-dark rounded shadow mx-2 position-relative", + style={ + "width": "max-content", + "max-height": "80dvh", + "overflow-y": "auto", + }, ), ): - row_height = "2.2rem" + row_height = "36px" for workspace in workspaces: with gui.tag( @@ -83,7 +88,7 @@ def global_workspace_selector(user: AppUser, session: dict, hide_name: bool = Fa name=SWITCH_WORKSPACE_KEY, type="submit", value=str(workspace.id), - style=dict(height=row_height), + style=dict(minHeight=row_height), ): with gui.div(className="row align-items-center"): with gui.div(className="col-2 d-flex justify-content-center"): From 01268e45f24700c3092e8aa2a958ae12f4eb7ae7 Mon Sep 17 00:00:00 2001 From: Anish Date: Wed, 6 Aug 2025 23:44:43 +0530 Subject: [PATCH 06/15] add mobile logo header back with new header --- daras_ai_v2/base.py | 3 +- routers/root.py | 67 ++++++++++++++++++++++++--------------------- widgets/explore.py | 1 + 3 files changed, 39 insertions(+), 32 deletions(-) diff --git a/daras_ai_v2/base.py b/daras_ai_v2/base.py index abe6ff9ff..a2d7a8e0c 100644 --- a/daras_ai_v2/base.py +++ b/daras_ai_v2/base.py @@ -394,7 +394,8 @@ def render(self): self.render_report_form() return - header_placeholder = gui.div(className="my-3 w-100") + sidebar_logo_header() + header_placeholder = gui.div(className="my-1 w-100") with ( gui.styled(NAV_TABS_CSS), gui.div(className="position-relative", id="recipe-nav-tabs"), diff --git a/routers/root.py b/routers/root.py index 887015a39..7f67a3c2d 100644 --- a/routers/root.py +++ b/routers/root.py @@ -251,7 +251,7 @@ def explore_page( ): from widgets import explore - with page_wrapper(request, search_filters=search_filters, show_search_bar=False): + with page_wrapper(request): explore.render(request, search_filters) return { @@ -707,7 +707,11 @@ def get_og_url_path(request) -> str: @contextmanager -def page_wrapper(request: Request, className="", page=None): +def page_wrapper( + request: Request, + className="", + page=None, +): context = { "request": request, "block_incognito": True, @@ -840,35 +844,36 @@ def page_wrapper(request: Request, className="", page=None): gui.html(templates.get_template("login_scripts.html").render(**context)) -def _render_mobile_search_button(request: Request, search_filters: SearchFilters): - with gui.div( - className="d-flex d-md-none flex-grow-1 justify-content-end", - ): - gui.button( - icons.search, - type="tertiary", - className="m-0", - onClick=JS_SHOW_MOBILE_SEARCH, - ) - - with ( - gui.styled("@media (min-width: 768px) { & { position: static !important; } }"), - gui.div( - className="d-md-flex flex-grow-1 justify-content-center align-items-center bg-white top-0 left-0", - style={"display": "none", "zIndex": "10"}, - id="mobile_search_container", - ), - ): - render_search_bar_with_redirect( - request=request, - search_filters=search_filters or SearchFilters(), - ) - gui.button( - "Cancel", - type="tertiary", - className="d-md-none fs-6 m-0 ms-1 p-1", - onClick=JS_HIDE_MOBILE_SEARCH, - ) +# def _render_mobile_search_button(request: Request, search_filters: SearchFilters): +# with gui.div( +# className="d-flex d-md-none flex-grow-1 justify-content-end", +# ): +# gui.button( +# icons.search, +# type="tertiary", +# unsafe_allow_html=True, +# className="m-0", +# onClick=JS_SHOW_MOBILE_SEARCH, +# ) + +# with ( +# gui.styled("@media (min-width: 768px) { & { position: static !important; } }"), +# gui.div( +# className="d-md-flex flex-grow-1 justify-content-center align-items-center bg-white top-0 left-0", +# style={"display": "none", "zIndex": "10"}, +# id="mobile_search_container", +# ), +# ): +# render_search_bar_with_redirect( +# request=request, +# search_filters=search_filters or SearchFilters(), +# ) +# gui.button( +# "Cancel", +# type="tertiary", +# className="d-md-none fs-6 m-0 ms-1 p-1", +# onClick=JS_HIDE_MOBILE_SEARCH, +# ) JS_SHOW_MOBILE_SEARCH = """ diff --git a/widgets/explore.py b/widgets/explore.py index 9a69da435..255e388eb 100644 --- a/widgets/explore.py +++ b/widgets/explore.py @@ -53,6 +53,7 @@ def build_meta_tags(url: str, search_filters: SearchFilters | None): def render(request: Request, search_filters: SearchFilters | None): + sidebar_logo_header() with gui.div(className="my-4"): # note: using css instead of `if not search_filters: ...` stops re-render # of the search bar. this preserves focus/blur between query-param redirects From 20e5315b64076ab3aace0486cf84ef3d3bc1ba28 Mon Sep 17 00:00:00 2001 From: Anish Date: Wed, 6 Aug 2025 23:58:27 +0530 Subject: [PATCH 07/15] explore -> search --- daras_ai_v2/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daras_ai_v2/settings.py b/daras_ai_v2/settings.py index 9953de4b8..4eaa8c11c 100644 --- a/daras_ai_v2/settings.py +++ b/daras_ai_v2/settings.py @@ -312,7 +312,7 @@ ] SIDEBAR_LINKS = [ - ("/explore/", "Explore", ""), + ("/explore/", "Search", ""), (DOCS_URL, "Docs", ""), ("/api/", "API", ""), (BLOG_URL, "Blog", ""), From 10be70a9280c96462159529d0b0d1f16c5ea4335 Mon Sep 17 00:00:00 2001 From: Anish Date: Thu, 7 Aug 2025 00:00:25 +0530 Subject: [PATCH 08/15] persist sidebar state in session --- daras_ai_v2/base.py | 4 ++-- routers/account.py | 2 +- routers/root.py | 6 +++--- widgets/explore.py | 2 +- widgets/sidebar.py | 28 ++++++++++++++++++---------- 5 files changed, 25 insertions(+), 17 deletions(-) diff --git a/daras_ai_v2/base.py b/daras_ai_v2/base.py index a2d7a8e0c..d35dbc16d 100644 --- a/daras_ai_v2/base.py +++ b/daras_ai_v2/base.py @@ -195,7 +195,7 @@ def __init__( self.request = request def render_sidebar(self, request, sidebar_ref): - render_default_sidebar() + render_default_sidebar(request.session) @classmethod def api_endpoint(cls) -> str: @@ -394,7 +394,7 @@ def render(self): self.render_report_form() return - sidebar_logo_header() + sidebar_logo_header(self.request.session) header_placeholder = gui.div(className="my-1 w-100") with ( gui.styled(NAV_TABS_CSS), diff --git a/routers/account.py b/routers/account.py index 235d3725a..e571cbed4 100644 --- a/routers/account.py +++ b/routers/account.py @@ -382,7 +382,7 @@ def account_page_wrapper(request: Request, current_tab: TabData): raise gui.RedirectException(str(redirect_url)) with page_wrapper(request) as current_workspace: - sidebar_logo_header() + sidebar_logo_header(request.session) with gui.nav_tabs(): for tab in AccountTabs.get_tabs_for_user(request.user, current_workspace): with gui.nav_item(tab.url_path, active=tab == current_tab): diff --git a/routers/root.py b/routers/root.py index 7f67a3c2d..c79b21f55 100644 --- a/routers/root.py +++ b/routers/root.py @@ -717,7 +717,7 @@ def page_wrapper( "block_incognito": True, } - sidebar_ref = use_sidebar("main-sidebar", default_open=True) + sidebar_ref = use_sidebar("main-sidebar", request.session, default_open=True) sidebar_container, pane_container = sidebar_layout(sidebar_ref) container = page if page else None @@ -809,7 +809,7 @@ def page_wrapper( if container: container.render_sidebar(request, sidebar_ref) else: - render_default_sidebar() + render_default_sidebar(request.session) # Bottom section with workspace selector when sidebar is closed with ( @@ -832,7 +832,7 @@ def page_wrapper( # Main content pane with pane_container: - with gui.div(className="d-flex flex-column min-vh-100 w-100"): + with gui.div(className="d-flex flex-column min-vh-100 w-100 pt-md-2"): gui.html(templates.get_template("gtag.html").render(**context)) gui.html(copy_to_clipboard_scripts) diff --git a/widgets/explore.py b/widgets/explore.py index 255e388eb..13dd34e5a 100644 --- a/widgets/explore.py +++ b/widgets/explore.py @@ -53,7 +53,7 @@ def build_meta_tags(url: str, search_filters: SearchFilters | None): def render(request: Request, search_filters: SearchFilters | None): - sidebar_logo_header() + sidebar_logo_header(request.session) with gui.div(className="my-4"): # note: using css instead of `if not search_filters: ...` stops re-render # of the search bar. this preserves focus/blur between query-param redirects diff --git a/widgets/sidebar.py b/widgets/sidebar.py index f5503c938..1e02af2d9 100644 --- a/widgets/sidebar.py +++ b/widgets/sidebar.py @@ -5,16 +5,23 @@ class SidebarRef: - def __init__(self, key: str, is_open: bool = True, is_mobile_open: bool = False): + def __init__( + self, + key: str, + session: dict, + is_open: bool = True, + is_mobile_open: bool = False, + ): self.key = key + self.session = session self.is_open = is_open self.is_mobile_open = is_mobile_open def set_open(self, value: bool): - self.is_open = gui.session_state[self.key] = value + self.is_open = self.session[self.key] = value def set_mobile_open(self, value: bool): - self.is_mobile_open = gui.session_state[self.mobile_key] = value + self.is_mobile_open = self.session[self.mobile_key] = value self.set_open(value) @property @@ -34,12 +41,13 @@ def open_btn_key(self): return self.key + ":open" -def use_sidebar(key: str, default_open: bool = True) -> SidebarRef: +def use_sidebar(key: str, session: dict, default_open: bool = True) -> SidebarRef: """Create or get a sidebar reference with state management.""" ref = SidebarRef( key=key, - is_open=bool(gui.session_state.get(key, default_open)), - is_mobile_open=bool(gui.session_state.get(key + ":mobile", False)), + session=session, + is_open=bool(session.get(key, default_open)), + is_mobile_open=bool(session.get(key + ":mobile", False)), ) return ref @@ -102,8 +110,8 @@ def sidebar_item_list(is_sidebar_open): gui.html(label) -def render_default_sidebar(): - is_sidebar_open = gui.session_state.get("main-sidebar", True) +def render_default_sidebar(session: dict): + is_sidebar_open = session.get("main-sidebar", True) with gui.div( className="d-flex flex-column flex-grow-1 gap-3 px-3 my-3 text-nowrap", style={"marginLeft": "4px"}, @@ -119,12 +127,12 @@ def render_default_sidebar(): sidebar_item_list(is_sidebar_open) -def sidebar_logo_header(): +def sidebar_logo_header(session: dict): with gui.div( className="d-flex align-items-center justify-content-between d-md-none me-2 w-100 py-2", style={"height": "64px"}, ): - sidebar_ref = use_sidebar("main-sidebar", default_open=True) + sidebar_ref = use_sidebar("main-sidebar", session, default_open=True) gui.tag( "img", src=settings.GOOEY_LOGO_FACE, From 1e08b8679cc0ca5326460f39383a00777da68e8d Mon Sep 17 00:00:00 2001 From: Anish Date: Thu, 7 Aug 2025 01:43:37 +0530 Subject: [PATCH 09/15] remove unused properties from sidebar --- widgets/sidebar.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/widgets/sidebar.py b/widgets/sidebar.py index 1e02af2d9..6a345296b 100644 --- a/widgets/sidebar.py +++ b/widgets/sidebar.py @@ -28,18 +28,6 @@ def set_mobile_open(self, value: bool): def mobile_key(self): return self.key + ":mobile" - @property - def toggle_btn_key(self): - return self.key + ":toggle" - - @property - def close_btn_key(self): - return self.key + ":close" - - @property - def open_btn_key(self): - return self.key + ":open" - def use_sidebar(key: str, session: dict, default_open: bool = True) -> SidebarRef: """Create or get a sidebar reference with state management.""" From 36f66f832eaff6cb4908fecbe3c13960c29b6545 Mon Sep 17 00:00:00 2001 From: Anish Date: Tue, 12 Aug 2025 13:24:29 +0530 Subject: [PATCH 10/15] limit pane container width to mw-100 --- widgets/sidebar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/widgets/sidebar.py b/widgets/sidebar.py index 6a345296b..b5549367e 100644 --- a/widgets/sidebar.py +++ b/widgets/sidebar.py @@ -235,7 +235,7 @@ def sidebar_layout(sidebar_ref: SidebarRef): className=f"d-flex flex-column flex-grow-1 gooey-sidebar {sidebar_funtion_classes}", style={"height": "100dvh"}, ) - pane_content_placeholder = gui.div(className="d-flex flex-grow-1") + pane_content_placeholder = gui.div(className="d-flex flex-grow-1 mw-100") # sidebar content sidebar_content_placeholder pane_content_placeholder From 72a6f4d309533b6a5a9a90f23900eabaad18681c Mon Sep 17 00:00:00 2001 From: Anish Date: Mon, 1 Sep 2025 17:37:01 +0530 Subject: [PATCH 11/15] fix: sidebar aesthetics --- daras_ai_v2/icons.py | 1 + daras_ai_v2/settings.py | 4 +- routers/root.py | 8 +-- widgets/sidebar.py | 148 +++++++++++++++++++++++++--------------- 4 files changed, 99 insertions(+), 62 deletions(-) diff --git a/daras_ai_v2/icons.py b/daras_ai_v2/icons.py index b9e2bac3c..ea7fa9f3d 100644 --- a/daras_ai_v2/icons.py +++ b/daras_ai_v2/icons.py @@ -54,6 +54,7 @@ search = '' library = '' sidebar_flip = "" +arrow_up_right = "" # brands github = '' diff --git a/daras_ai_v2/settings.py b/daras_ai_v2/settings.py index 4eaa8c11c..15fee2144 100644 --- a/daras_ai_v2/settings.py +++ b/daras_ai_v2/settings.py @@ -312,14 +312,14 @@ ] SIDEBAR_LINKS = [ - ("/explore/", "Search", ""), + # ("/explore/", "Search", ""), (DOCS_URL, "Docs", ""), ("/api/", "API", ""), (BLOG_URL, "Blog", ""), ("/pricing", "Pricing", ""), (CONTACT_URL, "Contact", ""), ] -SIDEBAR_ICON_SIZE = "40px" +SIDEBAR_ICON_SIZE = "32px" HEADER_ICONS = {"/explore/": ''} diff --git a/routers/root.py b/routers/root.py index c79b21f55..f74759ab9 100644 --- a/routers/root.py +++ b/routers/root.py @@ -738,7 +738,7 @@ def page_wrapper( ): with gui.div( className="d-flex px-md-2 px-3 py-2 align-items-center justify-content-between text-nowrap", - style={"height": "64px"}, + style={"height": "54px"}, ): # sidebar header gui.tag( @@ -750,7 +750,7 @@ def page_wrapper( ) open_sidebar_btn = gui.button( label=icons.sidebar_flip, - className="m-0 d-none hover-btn", + className="m-0 d-none hover-btn gooey-btn", unsafe_allow_html=True, type="tertiary", ) @@ -787,7 +787,7 @@ def page_wrapper( close_mobile_sidebar = gui.button( label=icons.cancel, - className="m-0 d-md-none p-2", + className="m-0 d-md-none gooey-btn", unsafe_allow_html=True, type="tertiary", ) @@ -798,7 +798,7 @@ def page_wrapper( if sidebar_ref.is_open: close_sidebar = gui.button( label=icons.sidebar_flip, - className="m-0 d-none d-md-block", + className="m-0 d-none d-md-block gooey-btn", unsafe_allow_html=True, type="tertiary", ) diff --git a/widgets/sidebar.py b/widgets/sidebar.py index b5549367e..3cee5d7db 100644 --- a/widgets/sidebar.py +++ b/widgets/sidebar.py @@ -41,7 +41,7 @@ def use_sidebar(key: str, session: dict, default_open: bool = True) -> SidebarRe return ref -def sidebar_list_item(icon, title, is_sidebar_open, url=None): +def sidebar_list_item(icon, title, is_sidebar_open, url=None, hover_icon=None): with ( gui.styled( """ @@ -50,46 +50,60 @@ def sidebar_list_item(icon, title, is_sidebar_open, url=None): text-decoration: none; } & .sidebar-list-item { - min-height: 24px; + border-radius: 8px; + height: 36px; + width: min-content; + padding: 6px 10px; } - & .sidebar-list-item-icon { - max-width: 18px; - min-width: 18px; + & .sidebar-list-item-hover-icon { + display: none; } - & a:hover { - .sidebar-list-item-title { - text-decoration: underline !important; - text-decoration-color: #000 !important; - text-decoration-thickness: 2px !important; - text-underline-offset: 2px !important; + & .sidebar-list-item:hover { + background-color: #f0f0f0; + .sidebar-list-item-hover-icon { + display: block; } } + & .sidebar-list-item-title { + font-size: 0.875rem; + } """ ), - gui.div(className="d-flex align-items-center"), + gui.div(), ): - with gui.div(className="d-flex align-items-center w-100"): - with gui.tag( - "a", - href=url, - className="d-flex align-items-center sidebar-list-item w-100", - ): + link_classes = "d-block sidebar-list-item ms-2" + if is_sidebar_open: + link_classes += " d-flex align-items-baseline justify-content-between w-100" + with gui.tag( + "a", + href=url, + className=link_classes, + ): + with gui.div(className="d-flex align-items-baseline"): + icon_classes = "d-block sidebar-list-item-icon" + if is_sidebar_open: + icon_classes += " me-2" + if icon: gui.html( icon, - className="sidebar-list-item-icon me-2 d-flex justify-content-center", + className=icon_classes, ) if is_sidebar_open: gui.html(title, className="sidebar-list-item-title d-block") + if hover_icon: + with gui.div(className="sidebar-list-item-hover-icon"): + gui.html(hover_icon, className="text-secondary") + def sidebar_item_list(is_sidebar_open): for i, (url, label, icon) in enumerate(settings.SIDEBAR_LINKS): - if not is_sidebar_open and i >= 1: - break if icon: with gui.div(): - sidebar_list_item(icon, label, is_sidebar_open, url) + sidebar_list_item( + icon, label, is_sidebar_open, url, icons.arrow_up_right + ) else: with gui.div( className="d-inline-block me-2 small", @@ -99,28 +113,34 @@ def sidebar_item_list(is_sidebar_open): def render_default_sidebar(session: dict): - is_sidebar_open = session.get("main-sidebar", True) + is_sidebar_open = use_sidebar("main-sidebar", session, default_open=True).is_open with gui.div( - className="d-flex flex-column flex-grow-1 gap-3 px-3 my-3 text-nowrap", - style={"marginLeft": "4px"}, + className=f"d-flex flex-column flex-grow-1 {'pe-3' if is_sidebar_open else ''} my-3 text-nowrap", ): - with gui.div(className="pe-2"): + with gui.div(className="mb-4"): sidebar_list_item( "", "Saved", is_sidebar_open, - "/saved/", + "/account/saved/", + ) + sidebar_list_item( + icons.search, + "Explore", + is_sidebar_open, + "/explore/", ) - sidebar_item_list(is_sidebar_open) + if is_sidebar_open: + sidebar_item_list(is_sidebar_open) def sidebar_logo_header(session: dict): with gui.div( className="d-flex align-items-center justify-content-between d-md-none me-2 w-100 py-2", - style={"height": "64px"}, + style={"height": "54px"}, ): - sidebar_ref = use_sidebar("main-sidebar", session, default_open=True) + sidebar_ref = use_sidebar("main-sidebar", session) gui.tag( "img", src=settings.GOOEY_LOGO_FACE, @@ -133,25 +153,39 @@ def sidebar_logo_header(session: dict): className="m-0", unsafe_allow_html=True, type="tertiary", + style={"padding": "6px 10px"}, ) if open_mobile_sidebar: sidebar_ref.set_mobile_open(True) raise gui.RerunException() +# Sidebar width variables +sidebar_open_width = "245px" +sidebar_closed_width = "53px" +sidebar_mobile_width = "80vw" + + def sidebar_layout(sidebar_ref: SidebarRef): is_mobile_open = sidebar_ref.is_mobile_open sidebar_funtion_classes = ( "gooey-sidebar-open" if sidebar_ref.is_open else "gooey-sidebar-closed" ) + side_bar_styles = dedent( - """ - html { + f""" + html {{ /* override margin-left from app.css */ margin-left: 0 !important; - } + }} + & .gooey-btn {{ + padding: 6px 10px !important; + }} + & .gooey-btn:hover {{ + background-color: #f0f0f0 !important; + }} - & .gooey-sidebar { + & .gooey-sidebar {{ transition: width 0.2s cubic-bezier(0.4, 0, 0.2, 1), min-width 0.2s cubic-bezier(0.4, 0, 0.2, 1), max-width 0.2s cubic-bezier(0.4, 0, 0.2, 1); background-color: #f9f9f9; position: sticky; @@ -159,38 +193,40 @@ def sidebar_layout(sidebar_ref: SidebarRef): left: 0; bottom: 0; z-index: 999; - } - & .gooey-sidebar-open { - min-width: 250px; - width: 250px; - max-width: 250px; - } - & .gooey-sidebar-closed { - min-width: 60px; - width: 60px; - max-width: 60px; - } + border-right: 1px solid #e0e0e0; + }} + + & .gooey-sidebar-open {{ + min-width: {sidebar_open_width}; + width: {sidebar_open_width}; + max-width: {sidebar_open_width}; + }} + & .gooey-sidebar-closed {{ + min-width: {sidebar_closed_width}; + width: {sidebar_closed_width}; + max-width: {sidebar_closed_width}; + }} - & .gooey-sidebar-closed:hover { + & .gooey-sidebar-closed:hover {{ cursor: e-resize; - } + }} - @media (max-width: 767px) { - & .gooey-sidebar-open { + @media (max-width: 767px) {{ + & .gooey-sidebar-open {{ position: fixed; - min-width: 100vw; - width: 100vw; - max-width: 100vw; + min-width: {sidebar_mobile_width}; + width: {sidebar_mobile_width}; + max-width: {sidebar_mobile_width}; z-index: 2000; - } - & .gooey-sidebar-closed { + }} + & .gooey-sidebar-closed {{ position: fixed; min-width: 0px; width: 0px; max-width: 0px; overflow: hidden; - } - } + }} + }} """ ) if not is_mobile_open: From cc397af30340c89ef9863b205d4a737774b39e81 Mon Sep 17 00:00:00 2001 From: Anish Date: Wed, 1 Oct 2025 18:58:01 +0530 Subject: [PATCH 12/15] sidebar: show selected --- daras_ai_v2/base.py | 6 +++--- routers/account.py | 4 ++-- routers/root.py | 2 +- widgets/explore.py | 4 ++-- widgets/sidebar.py | 29 +++++++++++++++++++---------- 5 files changed, 27 insertions(+), 18 deletions(-) diff --git a/daras_ai_v2/base.py b/daras_ai_v2/base.py index d35dbc16d..6e055eee8 100644 --- a/daras_ai_v2/base.py +++ b/daras_ai_v2/base.py @@ -91,7 +91,7 @@ ) from routers.root import PREVIEW_ROUTE_WORKFLOWS -from widgets.sidebar import render_default_sidebar, sidebar_logo_header +from widgets.sidebar import render_default_sidebar, sidebar_mobile_header MAX_SEED = 4294967294 gooey_rng = Random() @@ -195,7 +195,7 @@ def __init__( self.request = request def render_sidebar(self, request, sidebar_ref): - render_default_sidebar(request.session) + render_default_sidebar(sidebar_ref, request) @classmethod def api_endpoint(cls) -> str: @@ -394,7 +394,7 @@ def render(self): self.render_report_form() return - sidebar_logo_header(self.request.session) + sidebar_mobile_header(self.request.session) header_placeholder = gui.div(className="my-1 w-100") with ( gui.styled(NAV_TABS_CSS), diff --git a/routers/account.py b/routers/account.py index e571cbed4..33a4c1766 100644 --- a/routers/account.py +++ b/routers/account.py @@ -27,7 +27,7 @@ from workspaces.models import Workspace, WorkspaceInvite from workspaces.views import invitation_page, workspaces_page from workspaces.widgets import get_current_workspace, SWITCH_WORKSPACE_KEY -from widgets.sidebar import sidebar_logo_header +from widgets.sidebar import sidebar_mobile_header if typing.TYPE_CHECKING: from app_users.models import AppUser @@ -382,7 +382,7 @@ def account_page_wrapper(request: Request, current_tab: TabData): raise gui.RedirectException(str(redirect_url)) with page_wrapper(request) as current_workspace: - sidebar_logo_header(request.session) + sidebar_mobile_header(request.session) with gui.nav_tabs(): for tab in AccountTabs.get_tabs_for_user(request.user, current_workspace): with gui.nav_item(tab.url_path, active=tab == current_tab): diff --git a/routers/root.py b/routers/root.py index f74759ab9..953a40d68 100644 --- a/routers/root.py +++ b/routers/root.py @@ -809,7 +809,7 @@ def page_wrapper( if container: container.render_sidebar(request, sidebar_ref) else: - render_default_sidebar(request.session) + render_default_sidebar(sidebar_ref, request) # Bottom section with workspace selector when sidebar is closed with ( diff --git a/widgets/explore.py b/widgets/explore.py index 13dd34e5a..48f94af8c 100644 --- a/widgets/explore.py +++ b/widgets/explore.py @@ -16,7 +16,7 @@ render_search_filters, render_search_results, ) -from widgets.sidebar import sidebar_logo_header +from widgets.sidebar import sidebar_mobile_header META_TITLE = "Explore AI Workflows" META_DESCRIPTION = "Find, fork and run your field’s favorite AI recipes on Gooey.AI" @@ -53,7 +53,7 @@ def build_meta_tags(url: str, search_filters: SearchFilters | None): def render(request: Request, search_filters: SearchFilters | None): - sidebar_logo_header(request.session) + sidebar_mobile_header(request.session) with gui.div(className="my-4"): # note: using css instead of `if not search_filters: ...` stops re-render # of the search bar. this preserves focus/blur between query-param redirects diff --git a/widgets/sidebar.py b/widgets/sidebar.py index 3cee5d7db..5e021a00e 100644 --- a/widgets/sidebar.py +++ b/widgets/sidebar.py @@ -41,7 +41,10 @@ def use_sidebar(key: str, session: dict, default_open: bool = True) -> SidebarRe return ref -def sidebar_list_item(icon, title, is_sidebar_open, url=None, hover_icon=None): +def sidebar_list_item( + icon, title, is_sidebar_open, url=None, hover_icon=None, current_url=None +): + is_selected = current_url and url and current_url.startswith(url) with ( gui.styled( """ @@ -64,6 +67,9 @@ def sidebar_list_item(icon, title, is_sidebar_open, url=None, hover_icon=None): display: block; } } + & .sidebar-list-item.selected { + background-color: #ddd; + } & .sidebar-list-item-title { font-size: 0.875rem; } @@ -74,6 +80,8 @@ def sidebar_list_item(icon, title, is_sidebar_open, url=None, hover_icon=None): link_classes = "d-block sidebar-list-item ms-2" if is_sidebar_open: link_classes += " d-flex align-items-baseline justify-content-between w-100" + if is_selected: + link_classes += " selected" with gui.tag( "a", href=url, @@ -97,12 +105,12 @@ def sidebar_list_item(icon, title, is_sidebar_open, url=None, hover_icon=None): gui.html(hover_icon, className="text-secondary") -def sidebar_item_list(is_sidebar_open): +def sidebar_item_list(is_sidebar_open, current_url=None): for i, (url, label, icon) in enumerate(settings.SIDEBAR_LINKS): if icon: with gui.div(): sidebar_list_item( - icon, label, is_sidebar_open, url, icons.arrow_up_right + icon, label, is_sidebar_open, url, icons.arrow_up_right, current_url ) else: with gui.div( @@ -112,8 +120,10 @@ def sidebar_item_list(is_sidebar_open): gui.html(label) -def render_default_sidebar(session: dict): - is_sidebar_open = use_sidebar("main-sidebar", session, default_open=True).is_open +def render_default_sidebar(sidebar_ref: SidebarRef, request=None): + is_sidebar_open = sidebar_ref.is_open + current_url = request.url.path if request else None + with gui.div( className=f"d-flex flex-column flex-grow-1 {'pe-3' if is_sidebar_open else ''} my-3 text-nowrap", ): @@ -123,19 +133,21 @@ def render_default_sidebar(session: dict): "Saved", is_sidebar_open, "/account/saved/", + current_url=current_url, ) sidebar_list_item( icons.search, "Explore", is_sidebar_open, "/explore/", + current_url=current_url, ) if is_sidebar_open: - sidebar_item_list(is_sidebar_open) + sidebar_item_list(is_sidebar_open, current_url) -def sidebar_logo_header(session: dict): +def sidebar_mobile_header(session: dict): with gui.div( className="d-flex align-items-center justify-content-between d-md-none me-2 w-100 py-2", style={"height": "54px"}, @@ -272,7 +284,4 @@ def sidebar_layout(sidebar_ref: SidebarRef): style={"height": "100dvh"}, ) pane_content_placeholder = gui.div(className="d-flex flex-grow-1 mw-100") - # sidebar content - sidebar_content_placeholder - pane_content_placeholder return sidebar_content_placeholder, pane_content_placeholder From 28e94ea1f623edc1f5cc0c82eed396faa3d4bf2e Mon Sep 17 00:00:00 2001 From: Anish Date: Wed, 1 Oct 2025 19:07:14 +0530 Subject: [PATCH 13/15] sidebar: show small google button --- routers/root.py | 14 ++++++++------ templates/google_one_tap_button.html | 4 ++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/routers/root.py b/routers/root.py index 953a40d68..ac598e7b9 100644 --- a/routers/root.py +++ b/routers/root.py @@ -828,7 +828,7 @@ def page_wrapper( ) else: current_workspace = None - anonymous_login_container(request, context) + anonymous_login_container(request, context, hide_sign_in=True) # Main content pane with pane_container: @@ -894,16 +894,18 @@ def page_wrapper( """ -def anonymous_login_container(request: Request, context: dict): +def anonymous_login_container( + request: Request, context: dict, hide_sign_in: bool = False +): next_url = str(furl(request.url).set(origin=None)) login_url = str(furl("/login/", query_params=dict(next=next_url))) - with gui.tag("a", href=login_url, className="pe-2 d-none d-lg-block"): - gui.html("Sign In") - popover, content = gui.popover(interactive=True) - with popover, gui.div(className="d-flex align-items-center"): + with popover, gui.div(className="d-flex align-items-center overflow-hidden"): + if not hide_sign_in: + with gui.tag("a", href=login_url, className="pe-2 d-none d-lg-block"): + gui.html("Sign In") gui.html( templates.get_template("google_one_tap_button.html").render(**context) + '' diff --git a/templates/google_one_tap_button.html b/templates/google_one_tap_button.html index 397e1e512..44022e742 100644 --- a/templates/google_one_tap_button.html +++ b/templates/google_one_tap_button.html @@ -7,8 +7,8 @@ - - + +