Skip to content

Commit 9fea804

Browse files
committed
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
1 parent c2a0294 commit 9fea804

File tree

3 files changed

+109
-0
lines changed

3 files changed

+109
-0
lines changed

app/client/src/ce/pages/Applications/index.tsx

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ import {
6060
MenuItem as ListItem,
6161
Text,
6262
TextType,
63+
FontWeight,
6364
} from "@appsmith/ads-old";
6465
import { loadingUserWorkspaces } from "pages/Applications/ApplicationLoaders";
6566
import PageWrapper from "pages/common/PageWrapper";
@@ -395,6 +396,59 @@ export const textIconStyles = (props: { color: string; hover: string }) => {
395396
`;
396397
};
397398

399+
const WorkspaceItemRow = styled.a<{ disabled?: boolean; selected?: boolean }>`
400+
display: flex;
401+
align-items: center;
402+
justify-content: space-between;
403+
text-decoration: none;
404+
padding: 0px var(--ads-spaces-6);
405+
background-color: ${(props) =>
406+
props.selected ? "var(--ads-v2-color-bg-muted)" : "transparent"};
407+
.${Classes.TEXT} {
408+
color: var(--ads-v2-color-fg);
409+
}
410+
.${Classes.ICON} {
411+
svg {
412+
path {
413+
fill: var(--ads-v2-color-fg);
414+
}
415+
}
416+
}
417+
height: 38px;
418+
419+
${(props) =>
420+
!props.disabled
421+
? `
422+
&:hover {
423+
text-decoration: none;
424+
cursor: pointer;
425+
background-color: var(--ads-v2-color-bg-subtle);
426+
border-radius: var(--ads-v2-border-radius);
427+
}`
428+
: `
429+
&:hover {
430+
text-decoration: none;
431+
cursor: default;
432+
}
433+
`}
434+
`;
435+
436+
const WorkspaceIconContainer = styled.span`
437+
display: flex;
438+
align-items: center;
439+
440+
.${Classes.ICON} {
441+
margin-right: var(--ads-spaces-5);
442+
}
443+
`;
444+
445+
const WorkspaceLogoImage = styled.img`
446+
width: 20px;
447+
height: 20px;
448+
object-fit: contain;
449+
margin-right: var(--ads-spaces-5);
450+
`;
451+
398452
export function WorkspaceMenuItem({
399453
isFetchingWorkspaces,
400454
selected,
@@ -403,6 +457,7 @@ export function WorkspaceMenuItem({
403457
}: any) {
404458
const history = useHistory();
405459
const location = useLocation();
460+
const [imageError, setImageError] = React.useState(false);
406461

407462
const handleWorkspaceClick = () => {
408463
const workspaceId = workspace?.id;
@@ -414,8 +469,43 @@ export function WorkspaceMenuItem({
414469
}
415470
};
416471

472+
const handleImageError = () => {
473+
setImageError(true);
474+
};
475+
417476
if (!workspace.id) return null;
418477

478+
const hasLogo = workspace?.logoUrl && !imageError;
479+
const displayText = isFetchingWorkspaces
480+
? workspace?.name
481+
: workspace?.name?.length > 22
482+
? workspace.name.slice(0, 22).concat(" ...")
483+
: workspace?.name;
484+
485+
// Use custom component when there's a logo, otherwise use ListItem
486+
if (hasLogo && !isFetchingWorkspaces) {
487+
return (
488+
<Tooltip content={workspace?.name} placement="bottom-left">
489+
<WorkspaceItemRow
490+
className={selected ? "selected-workspace" : ""}
491+
onClick={handleWorkspaceClick}
492+
selected={selected}
493+
>
494+
<WorkspaceIconContainer>
495+
<WorkspaceLogoImage
496+
alt={`${workspace.name} logo`}
497+
onError={handleImageError}
498+
src={workspace.logoUrl}
499+
/>
500+
<Text type={TextType.H5} weight={FontWeight.NORMAL}>
501+
{displayText}
502+
</Text>
503+
</WorkspaceIconContainer>
504+
</WorkspaceItemRow>
505+
</Tooltip>
506+
);
507+
}
508+
419509
return (
420510
<ListItem
421511
className={selected ? "selected-workspace" : ""}

app/client/src/ce/reducers/uiReducers/workspaceReducer.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,20 @@ export const handlers = {
108108

109109
draftState.loadingStates.isSavingWorkspaceInfo = false;
110110
draftState.list = [...workspaces];
111+
112+
// Also update searchEntities if they exist to keep search results in sync
113+
if (draftState.searchEntities?.workspaces) {
114+
const searchWorkspaceIndex = draftState.searchEntities.workspaces.findIndex(
115+
(workspace: Workspace) => workspace.id === action.payload.id,
116+
);
117+
118+
if (searchWorkspaceIndex !== -1) {
119+
draftState.searchEntities.workspaces[searchWorkspaceIndex] = {
120+
...draftState.searchEntities.workspaces[searchWorkspaceIndex],
121+
...action.payload,
122+
};
123+
}
124+
}
111125
},
112126
[ReduxActionErrorTypes.SAVE_WORKSPACE_ERROR]: (
113127
draftState: WorkspaceReduxState,

app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Workspace.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,11 @@ public static String toSlug(String text) {
7171

7272
@JsonView(Views.Public.class)
7373
public String getLogoUrl() {
74+
// If there's no logo, return null instead of constructing a URL like "/api/v1/assets/null"
75+
// This prevents the frontend from making pointless requests to load a non-existent image
76+
if (logoAssetId == null || logoAssetId.isEmpty()) {
77+
return null;
78+
}
7479
return Url.ASSET_URL + "/" + logoAssetId;
7580
}
7681

0 commit comments

Comments
 (0)