From f0ace27d4a97bc13f8f3ee430600dee0c8045edc Mon Sep 17 00:00:00 2001 From: Ginger Date: Sat, 13 Sep 2025 15:12:10 -0400 Subject: [PATCH] Move featured servers into the Featured section --- .vscode/settings.json | 5 +- src/app/components/info-card/styles.css.ts | 1 - src/app/components/room-card/RoomCard.tsx | 14 +- src/app/components/room-card/style.css.ts | 2 +- src/app/pages/client/explore/Explore.tsx | 148 +++++++++------------ src/app/pages/client/explore/Featured.tsx | 93 +++++++++++-- src/app/pages/client/explore/Server.tsx | 58 ++++---- 7 files changed, 194 insertions(+), 127 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 8134a7fd..e1027687 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,5 +4,8 @@ "typescript.tsdk": "node_modules/typescript/lib", "[typescript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[typescriptreact]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" } -} +} \ No newline at end of file diff --git a/src/app/components/info-card/styles.css.ts b/src/app/components/info-card/styles.css.ts index c852fbcf..68fe7f0a 100644 --- a/src/app/components/info-card/styles.css.ts +++ b/src/app/components/info-card/styles.css.ts @@ -5,6 +5,5 @@ export const InfoCard = style([ { padding: config.space.S200, borderRadius: config.radii.R300, - borderWidth: config.borderWidth.B300, }, ]); diff --git a/src/app/components/room-card/RoomCard.tsx b/src/app/components/room-card/RoomCard.tsx index 34a7e24b..3b40bafc 100644 --- a/src/app/components/room-card/RoomCard.tsx +++ b/src/app/components/room-card/RoomCard.tsx @@ -45,7 +45,7 @@ const setGridColumnCount = (grid: HTMLElement, count: GridColumnCount): void => grid.style.setProperty('grid-template-columns', `repeat(${count}, 1fr)`); }; -export function RoomCardGrid({ children }: { children: ReactNode }) { +export function CardGrid({ children }: { children: ReactNode }) { const gridRef = useRef(null); useElementSizeObserver( @@ -60,17 +60,17 @@ export function RoomCardGrid({ children }: { children: ReactNode }) { ); } -export const RoomCardBase = as<'div'>(({ className, ...props }, ref) => ( +export const CardBase = as<'div'>(({ className, ...props }, ref) => ( )); -export const RoomCardName = as<'h6'>(({ ...props }, ref) => ( +export const CardName = as<'h6'>(({ ...props }, ref) => ( )); @@ -208,7 +208,7 @@ export const RoomCard = as<'div', RoomCardProps>( const openTopic = () => setViewTopic(true); return ( - + ( )} - {roomName} + {roomName} {roomTopic} @@ -314,7 +314,7 @@ export const RoomCard = as<'div', RoomCardProps>( )} - + ); } ); diff --git a/src/app/components/room-card/style.css.ts b/src/app/components/room-card/style.css.ts index b15acfe4..e8c640a8 100644 --- a/src/app/components/room-card/style.css.ts +++ b/src/app/components/room-card/style.css.ts @@ -8,7 +8,7 @@ export const CardGrid = style({ gap: config.space.S400, }); -export const RoomCardBase = style([ +export const CardBase = style([ DefaultReset, ContainerColor({ variant: 'SurfaceVariant' }), { diff --git a/src/app/pages/client/explore/Explore.tsx b/src/app/pages/client/explore/Explore.tsx index e3a99c68..71138fc1 100644 --- a/src/app/pages/client/explore/Explore.tsx +++ b/src/app/pages/client/explore/Explore.tsx @@ -31,7 +31,6 @@ import { NavLink, } from '../../../components/nav'; import { getExploreFeaturedPath, getExploreServerPath } from '../../pathUtils'; -import { useClientConfig } from '../../../hooks/useClientConfig'; import { useExploreFeaturedRooms, useExploreServer, @@ -46,7 +45,7 @@ import { useExploreServers } from '../../../hooks/useExploreServers'; import { useAlive } from '../../../hooks/useAlive'; type AddExploreServerPromptProps = { - onSubmit: (server: string) => Promise; + onSubmit: (server: string, save: boolean) => Promise; header: ReactNode; children: ReactNode; selected?: boolean; @@ -69,19 +68,25 @@ export function AddExploreServerPrompt({ return server || undefined; }; - const [submitState, handleSubmit] = useAsyncCallback( - useCallback(async () => { + const submit = useCallback( + async (save: boolean) => { const server = getInputServer(); if (!server) return; await mx.publicRooms({ server, limit: 1 }); - await onSubmit(server); + await onSubmit(server, save); if (alive()) { setDialog(false); } - }, [alive, onSubmit, mx]) + }, + [alive, onSubmit, mx] ); + const [viewState, handleView] = useAsyncCallback(() => submit(false)); + const [saveViewState, handleSaveView] = useAsyncCallback(() => submit(true)); + const busy = + viewState.status === AsyncStatus.Loading || saveViewState.status === AsyncStatus.Loading; + return ( <> }> @@ -108,21 +113,12 @@ export function AddExploreServerPrompt({ - { - evt.preventDefault(); - handleSubmit(); - }} - style={{ padding: config.space.S400 }} - direction="Column" - gap="400" - > + Add server name to explore public communities. Server Name - {submitState.status === AsyncStatus.Error && ( + {viewState.status === AsyncStatus.Error && ( Failed to load public rooms. Please try again. @@ -131,18 +127,32 @@ export function AddExploreServerPrompt({ + @@ -158,30 +168,33 @@ export function AddExploreServerPrompt({ ); } +type ExploreServerNavItemAction = { + onClick: () => Promise; + icon: IconSrc; + alwaysVisible: boolean; +}; type ExploreServerNavItemProps = { server: string; selected: boolean; icon: IconSrc; - onRemove?: (() => Promise) | null; + action?: ExploreServerNavItemAction; }; export function ExploreServerNavItem({ server, selected, icon, - onRemove = null, + action, }: ExploreServerNavItemProps) { const [hover, setHover] = useState(false); const { hoverProps } = useHover({ onHoverChange: setHover }); const { focusWithinProps } = useFocusWithin({ onFocusWithinChange: setHover }); - const [removeState, removeCallback] = useAsyncCallback( + const [actionState, actionCallback] = useAsyncCallback( useCallback(async () => { - if (onRemove !== null) { - await onRemove(); - } - }, [onRemove]) + await action?.onClick(); + }, [action]) ); - const removeInProgress = - removeState.status === AsyncStatus.Loading || removeState.status === AsyncStatus.Success; + const actionInProgress = + actionState.status === AsyncStatus.Loading || actionState.status === AsyncStatus.Success; return ( - {onRemove !== null && (hover || removeInProgress) && ( + {action !== undefined && (hover || actionInProgress || action.alwaysVisible) && ( - {removeInProgress ? ( + {actionInProgress ? ( ) : ( - + )} @@ -232,13 +245,7 @@ export function Explore() { const navigate = useNavigate(); useNavToActivePathMapper('explore'); const userId = mx.getUserId(); - const clientConfig = useClientConfig(); const userServer = userId ? getMxIdServer(userId) : undefined; - const featuredServers = useMemo( - () => - clientConfig.featuredCommunities?.servers?.filter((server) => server !== userServer) ?? [], - [clientConfig, userServer] - ); const [exploreServers, addServer, removeServer] = useExploreServers(); const selectedServer = useExploreServer(); @@ -248,20 +255,19 @@ export function Explore() { !( selectedServer === undefined || selectedServer === userServer || - featuredServers.includes(selectedServer) || exploreServers.includes(selectedServer) ), - [exploreServers, featuredServers, selectedServer, userServer] + [exploreServers, selectedServer, userServer] ); const addServerCallback = useCallback( - async (server: string) => { - if (server !== userServer && selectedServer && !featuredServers.includes(selectedServer)) { + async (server: string, save: boolean) => { + if (save && server !== userServer && selectedServer) { await addServer(server); } navigate(getExploreServerPath(server)); }, - [addServer, navigate, userServer, featuredServers, selectedServer] + [addServer, navigate, userServer, selectedServer] ); const removeServerCallback = useCallback( @@ -271,13 +277,6 @@ export function Explore() { [removeServer] ); - const exploreUnlistedServerCallback = useCallback( - async (server: string) => { - navigate(getExploreServerPath(server)); - }, - [navigate] - ); - return ( @@ -293,29 +292,6 @@ export function Explore() { - View Server} - selected={exploringUnlistedServer} - > - - - - - - - Explore with Address - - - - - - - - - Featured - - @@ -325,7 +301,7 @@ export function Explore() { - Featured Rooms + Featured @@ -339,14 +315,18 @@ export function Explore() { icon={Icons.Home} /> )} - {featuredServers.map((server) => ( + {exploringUnlistedServer && selectedServer !== undefined && ( addServerCallback(selectedServer, true), + }} /> - ))} + )} @@ -359,8 +339,12 @@ export function Explore() { key={server} server={server} selected={server === selectedServer} - onRemove={() => removeServerCallback(server)} icon={Icons.Server} + action={{ + alwaysVisible: false, + icon: Icons.Minus, + onClick: () => removeServerCallback(server), + }} /> ))} unknown; +}; + +function ServerCard({ serverName, onExplore }: ServerCardProps) { + const mx = useMatrixClient(); + + const fetchPublicRooms = useCallback( + () => mx.publicRooms({ server: serverName }), + [mx, serverName] + ); + + const { data, isLoading, isError } = useQuery({ + queryKey: [serverName, `publicRooms`], + queryFn: fetchPublicRooms, + }); + const publicRoomCount = data?.total_room_count_estimate; + + return ( + + {serverName} + + {isLoading ? ( + + ) : ( + <> + + + {publicRoomCount === undefined + ? 'Error loading rooms' + : `${millify(publicRoomCount)} Public Rooms`} + + + )} + + + + ); +} export function FeaturedRooms() { const { featuredCommunities } = useClientConfig(); - const { rooms, spaces } = featuredCommunities ?? {}; + const { rooms, spaces, servers } = featuredCommunities ?? {}; const allRooms = useAtomValue(allRoomsAtom); const screenSize = useScreenSizeContext(); const { navigateSpace, navigateRoom } = useRoomNavigate(); + const navigate = useNavigate(); + + const exploreServer = useCallback( + async (server: string) => { + navigate(getExploreServerPath(server)); + }, + [navigate] + ); return ( @@ -49,15 +107,28 @@ export function FeaturedRooms() { } - title="Featured by Client" - subTitle="Find and explore public rooms and spaces featured by client provider." + title="Featured" + subTitle="Find and explore public communities featured by your client provider." /> + {servers && servers.length > 0 && ( + + Featured Servers + + {servers.map((serverName) => ( + exploreServer(serverName)} + /> + ))} + + + )} {spaces && spaces.length > 0 && ( Featured Spaces - + {spaces.map((roomIdOrAlias) => ( {(roomSummary) => ( @@ -80,13 +151,13 @@ export function FeaturedRooms() { )} ))} - + )} {rooms && rooms.length > 0 && ( Featured Rooms - + {rooms.map((roomIdOrAlias) => ( {(roomSummary) => ( @@ -109,7 +180,7 @@ export function FeaturedRooms() { )} ))} - + )} {((spaces && spaces.length === 0 && rooms && rooms.length === 0) || @@ -123,7 +194,7 @@ export function FeaturedRooms() { > - No rooms or spaces featured by client provider. + No rooms or spaces are featured. )} diff --git a/src/app/pages/client/explore/Server.tsx b/src/app/pages/client/explore/Server.tsx index 9a0618a1..b0a8c54f 100644 --- a/src/app/pages/client/explore/Server.tsx +++ b/src/app/pages/client/explore/Server.tsx @@ -37,7 +37,7 @@ import { MatrixClient, Method, RoomType } from 'matrix-js-sdk'; import { Page, PageContent, PageContentCenter, PageHeader } from '../../../components/page'; import { useMatrixClient } from '../../../hooks/useMatrixClient'; import { RoomTopicViewer } from '../../../components/room-topic-viewer'; -import { RoomCard, RoomCardBase, RoomCardGrid } from '../../../components/room-card'; +import { RoomCard, CardBase, CardGrid } from '../../../components/room-card'; import { ExploreServerPathSearchParams } from '../../paths'; import { getExploreServerPath, withSearchParam } from '../../pathUtils'; import * as css from './style.css'; @@ -361,9 +361,9 @@ export function PublicRooms() { const searchInputRef = useRef(null); const navigate = useNavigate(); const roomTypeFilters = useRoomTypeFilters(); - const [exploreServers, , removeServer] = useExploreServers(); - const isUserAddedServer = server && exploreServers.includes(server); - const isUserHomeServer = server && server === userServer; + const [exploreServers, addServer, removeServer] = useExploreServers(); + const isUserHomeserver = server && server === userServer; + const isBookmarkedServer = server && exploreServers.includes(server); const currentLimit: number = useMemo(() => { const limitParam = serverSearchParams.limit; @@ -480,15 +480,18 @@ export function PublicRooms() { setMenuAnchor(evt.currentTarget.getBoundingClientRect()); }; - const [removeServerState, handleRemoveServer] = useAsyncCallback( - useCallback(async () => { - if (!server) return; + const [menuActionState, handleMenuAction] = useAsyncCallback( + useCallback( + async (action: (server: string) => Promise) => { + if (!server) return; - setMenuAnchor(undefined); - await removeServer(server); - }, [server, removeServer]) + setMenuAnchor(undefined); + await action(server); + }, + [server] + ) ); - const isRemoving = removeServerState.status === AsyncStatus.Loading; + const menuActionBusy = menuActionState.status === AsyncStatus.Loading; return ( @@ -529,7 +532,9 @@ export function PublicRooms() { )} - {screenSize !== ScreenSize.Mobile && } + {screenSize !== ScreenSize.Mobile && ( + + )} {server} @@ -546,7 +551,7 @@ export function PublicRooms() { } > {(triggerRef) => - isUserAddedServer && ( + !isUserHomeserver && ( + handleMenuAction(isBookmarkedServer ? removeServer : addServer) + } + variant={isBookmarkedServer ? 'Critical' : 'Primary'} fill="None" size="300" after={ - isRemoving ? ( + menuActionBusy ? ( ) : ( - + ) } radii="300" - disabled={isRemoving} + disabled={menuActionBusy} > - Remove Server + {isBookmarkedServer ? 'Remove Server' : 'Add Server'} @@ -660,11 +670,11 @@ export function PublicRooms() { {isLoading && ( - + {[...Array(currentLimit).keys()].map((item) => ( - + ))} - + )} {error && ( @@ -675,7 +685,7 @@ export function PublicRooms() { {data && (data.chunk.length > 0 ? ( <> - + {data?.chunk.map((chunkRoom) => ( ))} - + {(data.prev_batch || data.next_batch) && (