From 9fea804c6762c10c8a254cc46d398748e83b9294 Mon Sep 17 00:00:00 2001 From: Stacey Levine Date: Thu, 13 Nov 2025 17:53:14 +0530 Subject: [PATCH 1/4] feat: display workspace logos in sidebar navigation - Modified Workspace.java to return null for logoUrl when no logo exists - Updated workspace reducer to sync logo changes to search results - Enhanced WorkspaceMenuItem to display uploaded logos with fallback to default icon - Added proper error handling and styling for workspace logo display --- .../src/ce/pages/Applications/index.tsx | 90 +++++++++++++++++++ .../reducers/uiReducers/workspaceReducer.ts | 14 +++ .../appsmith/server/domains/Workspace.java | 5 ++ 3 files changed, 109 insertions(+) diff --git a/app/client/src/ce/pages/Applications/index.tsx b/app/client/src/ce/pages/Applications/index.tsx index b52921360fe2..72b1188526f4 100644 --- a/app/client/src/ce/pages/Applications/index.tsx +++ b/app/client/src/ce/pages/Applications/index.tsx @@ -60,6 +60,7 @@ import { MenuItem as ListItem, Text, TextType, + FontWeight, } from "@appsmith/ads-old"; import { loadingUserWorkspaces } from "pages/Applications/ApplicationLoaders"; import PageWrapper from "pages/common/PageWrapper"; @@ -395,6 +396,59 @@ export const textIconStyles = (props: { color: string; hover: string }) => { `; }; +const WorkspaceItemRow = styled.a<{ disabled?: boolean; selected?: boolean }>` + display: flex; + align-items: center; + justify-content: space-between; + text-decoration: none; + padding: 0px var(--ads-spaces-6); + background-color: ${(props) => + props.selected ? "var(--ads-v2-color-bg-muted)" : "transparent"}; + .${Classes.TEXT} { + color: var(--ads-v2-color-fg); + } + .${Classes.ICON} { + svg { + path { + fill: var(--ads-v2-color-fg); + } + } + } + height: 38px; + + ${(props) => + !props.disabled + ? ` + &:hover { + text-decoration: none; + cursor: pointer; + background-color: var(--ads-v2-color-bg-subtle); + border-radius: var(--ads-v2-border-radius); + }` + : ` + &:hover { + text-decoration: none; + cursor: default; + } + `} +`; + +const WorkspaceIconContainer = styled.span` + display: flex; + align-items: center; + + .${Classes.ICON} { + margin-right: var(--ads-spaces-5); + } +`; + +const WorkspaceLogoImage = styled.img` + width: 20px; + height: 20px; + object-fit: contain; + margin-right: var(--ads-spaces-5); +`; + export function WorkspaceMenuItem({ isFetchingWorkspaces, selected, @@ -403,6 +457,7 @@ export function WorkspaceMenuItem({ }: any) { const history = useHistory(); const location = useLocation(); + const [imageError, setImageError] = React.useState(false); const handleWorkspaceClick = () => { const workspaceId = workspace?.id; @@ -414,8 +469,43 @@ export function WorkspaceMenuItem({ } }; + const handleImageError = () => { + setImageError(true); + }; + if (!workspace.id) return null; + const hasLogo = workspace?.logoUrl && !imageError; + const displayText = isFetchingWorkspaces + ? workspace?.name + : workspace?.name?.length > 22 + ? workspace.name.slice(0, 22).concat(" ...") + : workspace?.name; + + // Use custom component when there's a logo, otherwise use ListItem + if (hasLogo && !isFetchingWorkspaces) { + return ( + + + + + + {displayText} + + + + + ); + } + return ( workspace.id === action.payload.id, + ); + + if (searchWorkspaceIndex !== -1) { + draftState.searchEntities.workspaces[searchWorkspaceIndex] = { + ...draftState.searchEntities.workspaces[searchWorkspaceIndex], + ...action.payload, + }; + } + } }, [ReduxActionErrorTypes.SAVE_WORKSPACE_ERROR]: ( draftState: WorkspaceReduxState, diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Workspace.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Workspace.java index 49f8fc3afa82..2485a8a40f84 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Workspace.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Workspace.java @@ -71,6 +71,11 @@ public static String toSlug(String text) { @JsonView(Views.Public.class) public String getLogoUrl() { + // If there's no logo, return null instead of constructing a URL like "/api/v1/assets/null" + // This prevents the frontend from making pointless requests to load a non-existent image + if (logoAssetId == null || logoAssetId.isEmpty()) { + return null; + } return Url.ASSET_URL + "/" + logoAssetId; } From d221389a7e593e42462c23fcd3e40422678a3ffe Mon Sep 17 00:00:00 2001 From: Stacey Levine Date: Thu, 13 Nov 2025 18:12:12 +0530 Subject: [PATCH 2/4] Fix prettier lint errors Fix prettier lint errors --- .../src/ce/reducers/uiReducers/workspaceReducer.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/client/src/ce/reducers/uiReducers/workspaceReducer.ts b/app/client/src/ce/reducers/uiReducers/workspaceReducer.ts index b41e0e7d2ac5..8f5f148d4af8 100644 --- a/app/client/src/ce/reducers/uiReducers/workspaceReducer.ts +++ b/app/client/src/ce/reducers/uiReducers/workspaceReducer.ts @@ -111,10 +111,11 @@ export const handlers = { // Also update searchEntities if they exist to keep search results in sync if (draftState.searchEntities?.workspaces) { - const searchWorkspaceIndex = draftState.searchEntities.workspaces.findIndex( - (workspace: Workspace) => workspace.id === action.payload.id, - ); - + const searchWorkspaceIndex = + draftState.searchEntities.workspaces.findIndex( + (workspace: Workspace) => workspace.id === action.payload.id, + ); + if (searchWorkspaceIndex !== -1) { draftState.searchEntities.workspaces[searchWorkspaceIndex] = { ...draftState.searchEntities.workspaces[searchWorkspaceIndex], From f86707a0dd8e792dae5a3af374e5b64ac47917f9 Mon Sep 17 00:00:00 2001 From: Stacey Levine Date: Thu, 13 Nov 2025 18:20:39 +0530 Subject: [PATCH 3/4] Fixed check typ e errors. --- app/client/src/ce/pages/Applications/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/client/src/ce/pages/Applications/index.tsx b/app/client/src/ce/pages/Applications/index.tsx index 72b1188526f4..a56572c898ba 100644 --- a/app/client/src/ce/pages/Applications/index.tsx +++ b/app/client/src/ce/pages/Applications/index.tsx @@ -485,7 +485,7 @@ export function WorkspaceMenuItem({ // Use custom component when there's a logo, otherwise use ListItem if (hasLogo && !isFetchingWorkspaces) { return ( - + Date: Wed, 26 Nov 2025 10:01:07 -0500 Subject: [PATCH 4/4] Address comments - fixed logo size and removed tool tip --- .../src/ce/pages/Applications/index.tsx | 38 +++++++++---------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/app/client/src/ce/pages/Applications/index.tsx b/app/client/src/ce/pages/Applications/index.tsx index a56572c898ba..60bc9fbfbc9c 100644 --- a/app/client/src/ce/pages/Applications/index.tsx +++ b/app/client/src/ce/pages/Applications/index.tsx @@ -443,8 +443,8 @@ const WorkspaceIconContainer = styled.span` `; const WorkspaceLogoImage = styled.img` - width: 20px; - height: 20px; + width: 16px; + height: 16px; object-fit: contain; margin-right: var(--ads-spaces-5); `; @@ -485,24 +485,22 @@ export function WorkspaceMenuItem({ // Use custom component when there's a logo, otherwise use ListItem if (hasLogo && !isFetchingWorkspaces) { return ( - - - - - - {displayText} - - - - + + + + + {displayText} + + + ); }