From 42421f2a06dec0e8ff2e868eaeeeb39a37360a49 Mon Sep 17 00:00:00 2001 From: spencrmartin Date: Wed, 3 Dec 2025 16:33:31 -0500 Subject: [PATCH] Implement Matrix Spaces navigation and room creation - Update MatrixService to support Matrix Spaces with isSpace, roomType, and isPublic properties - Add SpaceChild interface for representing space hierarchy - Implement space navigation with drill-down into sub-spaces and back navigation - Add contextual room/space creation (create spaces at top level, rooms within spaces) - Update ChannelsView to show only Matrix Spaces instead of all non-DM rooms - Add breadcrumb navigation and space content display - Include empty tiles for creating new rooms within spaces --- .../src/components/channels/ChannelsView.tsx | 323 ++++++++++++++++-- ui/desktop/src/contexts/MatrixContext.tsx | 32 +- ui/desktop/src/services/MatrixService.ts | 319 +++++++++++++++++ 3 files changed, 641 insertions(+), 33 deletions(-) diff --git a/ui/desktop/src/components/channels/ChannelsView.tsx b/ui/desktop/src/components/channels/ChannelsView.tsx index df98504492..feafb73d40 100644 --- a/ui/desktop/src/components/channels/ChannelsView.tsx +++ b/ui/desktop/src/components/channels/ChannelsView.tsx @@ -12,13 +12,16 @@ import { Wifi, WifiOff, Edit2, - Star + Star, + ArrowLeft, + ChevronRight, + Home } from 'lucide-react'; import { useMatrix } from '../../contexts/MatrixContext'; import MatrixAuth from '../peers/MatrixAuth'; import { useNavigate } from 'react-router-dom'; import { useTabContext } from '../../contexts/TabContext'; -import { matrixService } from '../../services/MatrixService'; +import { matrixService, SpaceChild } from '../../services/MatrixService'; interface Channel { roomId: string; @@ -155,7 +158,99 @@ const ChannelCard: React.FC<{ ); }; -const EmptyChannelTile: React.FC<{ onCreateChannel: () => void }> = ({ onCreateChannel }) => { +const SpaceChildCard: React.FC<{ + child: SpaceChild; + onChildClick: (child: SpaceChild) => void; +}> = ({ child, onChildClick }) => { + const [isHovered, setIsHovered] = useState(false); + + return ( + setIsHovered(true)} + onHoverEnd={() => setIsHovered(false)} + onClick={() => onChildClick(child)} + className=" + relative cursor-pointer group + bg-background-default + transition-colors duration-200 + hover:bg-background-medium + aspect-square + flex flex-col + rounded-2xl + overflow-hidden + " + > + {/* Cover Photo Section - Top half */} + {child.avatarUrl ? ( +
+ {child.name} +
+
+ ) : ( +
+
+
+
+
+ {child.isSpace ? ( + + ) : ( + + )} +
+
+ )} + + {/* Type indicator - top right */} +
+
+ {child.isSpace ? ( + + ) : child.isPublic ? ( + + ) : ( + + )} +
+
+ + {/* Content Section - Bottom half */} +
+

+ {child.name || (child.isSpace ? 'Unnamed Space' : 'Unnamed Room')} +

+ {child.topic && ( +

+ {child.topic} +

+ )} +
+ + {child.memberCount || 0} members + {child.suggested && ( + + Suggested + + )} +
+
+ + ); +}; + +const EmptyChannelTile: React.FC<{ onCreateChannel: () => void; isInSpace?: boolean }> = ({ onCreateChannel, isInSpace = false }) => { return ( void }> = ({ onCreateC

- Create Channel + {isInSpace ? 'Create Room' : 'Create Space'}

@@ -378,7 +473,8 @@ const CreateChannelModal: React.FC<{ isOpen: boolean; onClose: () => void; onCreate: (name: string, topic: string, isPublic: boolean) => Promise; -}> = ({ isOpen, onClose, onCreate }) => { + isInSpace?: boolean; +}> = ({ isOpen, onClose, onCreate, isInSpace = false }) => { const [name, setName] = useState(''); const [topic, setTopic] = useState(''); const [isPublic, setIsPublic] = useState(true); @@ -421,7 +517,9 @@ const CreateChannelModal: React.FC<{ onClick={(e) => e.stopPropagation()} >
-

Create Channel

+

+ Create {isInSpace ? 'Room' : 'Space'} +