diff --git a/app/client/src/ce/pages/Applications/index.tsx b/app/client/src/ce/pages/Applications/index.tsx index b52921360fe2..c418f4e16984 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: 16px; + height: 16px; + 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,50 @@ 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) { + const showTooltip = workspace?.name && workspace.name.length > 22; + const itemContent = ( + + + + + {displayText} + + + + ); + + return showTooltip ? ( + + {itemContent} + + ) : ( + itemContent + ); + } + 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/client/src/pages/common/SearchBar/WorkspaceSearchItems.tsx b/app/client/src/pages/common/SearchBar/WorkspaceSearchItems.tsx index 74eeee3dd2f4..f2cbdd92ea2a 100644 --- a/app/client/src/pages/common/SearchBar/WorkspaceSearchItems.tsx +++ b/app/client/src/pages/common/SearchBar/WorkspaceSearchItems.tsx @@ -1,6 +1,6 @@ import type { Workspace } from "ee/constants/workspaceConstants"; import { Icon, Text } from "@appsmith/ads"; -import React from "react"; +import React, { useState } from "react"; import { useHistory } from "react-router"; import styled from "styled-components"; @@ -15,14 +15,68 @@ export const SearchListItem = styled.div` } `; +const WorkspaceLogoImage = styled.img` + width: 16px; + height: 16px; + min-width: 16px; + min-height: 16px; + object-fit: contain; + margin-right: 8px; +`; + interface Props { workspacesList: Workspace[] | undefined; setIsDropdownOpen: (isOpen: boolean) => void; } +interface WorkspaceItemProps { + workspace: Workspace; + setIsDropdownOpen: (isOpen: boolean) => void; +} + +const WorkspaceItem = ({ + setIsDropdownOpen, + workspace, +}: WorkspaceItemProps) => { + const history = useHistory(); + const [imageError, setImageError] = useState(false); + const hasLogo = workspace.logoUrl && !imageError; + + const handleImageError = () => { + setImageError(true); + }; + + return ( + { + setIsDropdownOpen(false); + history.push(`/applications?workspaceId=${workspace?.id}`); + }} + > + {hasLogo ? ( + + ) : ( + + )} + + {workspace.name} + + + ); +}; + const WorkspaceSearchItems = (props: Props) => { const { setIsDropdownOpen, workspacesList } = props; - const history = useHistory(); if (!workspacesList || workspacesList?.length === 0) return null; @@ -32,24 +86,11 @@ const WorkspaceSearchItems = (props: Props) => { Workspaces {workspacesList.map((workspace: Workspace) => ( - { - setIsDropdownOpen(false); - history.push(`/applications?workspaceId=${workspace?.id}`); - }} - > - - - {workspace.name} - - + setIsDropdownOpen={setIsDropdownOpen} + workspace={workspace} + /> ))} ); 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; }