getting list showing now!
This commit is contained in:
Vendored
+1
-1
@@ -8,7 +8,7 @@
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"workbench.colorTheme": "Default Light+",
|
||||
"javascript.format.semicolons": "insert",
|
||||
"window.zoomLevel": 2
|
||||
"window.zoomLevel": 1
|
||||
|
||||
// "eslint.workingDirectories": ["./packages/desktop"]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import { Avatar } from 'antd';
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
function LineIcon({
|
||||
sourceImages,
|
||||
grayscale = true,
|
||||
}: {
|
||||
sourceImages: string[];
|
||||
grayscale: boolean;
|
||||
}) {
|
||||
const isMultiple = useMemo(() => {
|
||||
if (sourceImages?.length > 1) return true;
|
||||
|
||||
return false;
|
||||
}, [sourceImages]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{isMultiple ? (
|
||||
<div className={`relative ${grayscale ? 'grayscale' : ''} h-[32px] w-[32px]`}>
|
||||
{sourceImages.map((avatarSrc, index) => {
|
||||
if (index === 0) {
|
||||
return (
|
||||
<Avatar
|
||||
key={`lineIcon-${avatarSrc}-${index}`}
|
||||
src={avatarSrc}
|
||||
shape="square"
|
||||
size={'small'}
|
||||
className={`shadow-lg absolute bottom-0 left-0 bg-slate-200`}
|
||||
/>
|
||||
);
|
||||
} else if (index === 1) {
|
||||
return (
|
||||
<Avatar
|
||||
key={`lineIcon-${avatarSrc}-${index}`}
|
||||
src={avatarSrc}
|
||||
shape="square"
|
||||
size={'small'}
|
||||
className={`absolute top-0 right-0 bg-slate-200`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: add third in the frame if we want later
|
||||
return <></>;
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
sourceImages?.map((avatarSrc, index) => (
|
||||
<Avatar
|
||||
key={`lineIcon-${avatarSrc}-${index}`}
|
||||
src={avatarSrc}
|
||||
shape="square"
|
||||
size={'default'}
|
||||
className={`${grayscale && 'grayscale'} shadow-lg`}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(LineIcon);
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState, useContext } from 'react';
|
||||
import React, { useEffect, useState, useContext, useCallback } from 'react';
|
||||
import useRooms from './RoomsProvider';
|
||||
import useSockets from './SocketProvider';
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
UserStartedBroadcastingResponse,
|
||||
UserStoppedBroadcastingResponse,
|
||||
} from '@nirvana/core/sockets/channels';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
type LineIdToMasterLine = {
|
||||
[lineId: string]: MasterLineData;
|
||||
@@ -26,17 +27,27 @@ type LineIdToMasterLine = {
|
||||
|
||||
interface IRealTimeRoomProvider {
|
||||
roomsMap: LineIdToMasterLine;
|
||||
|
||||
selectedLine?: MasterLineData;
|
||||
handleSelectLine: (newLineId: string) => void;
|
||||
}
|
||||
|
||||
const RealTimeRoomContext = React.createContext<IRealTimeRoomProvider>({ roomsMap: {} });
|
||||
const RealTimeRoomContext = React.createContext<IRealTimeRoomProvider>({
|
||||
roomsMap: {},
|
||||
handleSelectLine: () => {
|
||||
//
|
||||
},
|
||||
});
|
||||
|
||||
// handles reads of new data
|
||||
// does NOT handle sending socket emissions
|
||||
// keeps listening to incoming socket events to make sure that the realtime rooms map is highly available
|
||||
export function RealTimeRoomProvider({ children }: { children: React.ReactChild }) {
|
||||
const { rooms } = useRooms();
|
||||
const { $ws } = useSockets();
|
||||
const [realTimeRoomMap, setRealTimeRoomMap] = useState<LineIdToMasterLine>({});
|
||||
|
||||
const [selectedLine, setSelectedLine] = useState<MasterLineData>();
|
||||
|
||||
useEffect(() => {
|
||||
/**
|
||||
* initiate listeners
|
||||
@@ -188,8 +199,19 @@ export function RealTimeRoomProvider({ children }: { children: React.ReactChild
|
||||
}
|
||||
}, [rooms.value, setRealTimeRoomMap]);
|
||||
|
||||
/** show user line details on click of one line */
|
||||
const handleSelectLine = useCallback(
|
||||
(newLineIdToSelect: string) => {
|
||||
toast('selecting line!! NOT IMPLEMENTED');
|
||||
setSelectedLine(realTimeRoomMap[newLineIdToSelect]);
|
||||
},
|
||||
[setSelectedLine, realTimeRoomMap],
|
||||
);
|
||||
|
||||
return (
|
||||
<RealTimeRoomContext.Provider value={{ roomsMap: realTimeRoomMap }}>
|
||||
<RealTimeRoomContext.Provider
|
||||
value={{ roomsMap: realTimeRoomMap, handleSelectLine, selectedLine }}
|
||||
>
|
||||
{children}
|
||||
</RealTimeRoomContext.Provider>
|
||||
);
|
||||
|
||||
@@ -7,32 +7,19 @@ import MasterLineData from '@nirvana/core/models/masterLineData.model';
|
||||
|
||||
import useRooms from '../../../providers/RoomsProvider';
|
||||
import toast from 'react-hot-toast';
|
||||
import SidePanel from './panels/SidePanel';
|
||||
|
||||
export default function Terminal() {
|
||||
const { rooms: initialRoomsState } = useRooms();
|
||||
const { roomsMap } = useRealTimeRooms();
|
||||
const { desktopMode } = useElectron();
|
||||
|
||||
const { user } = useAuth();
|
||||
|
||||
const [selectedLine, setSelectedLine] = useState<MasterLineData>();
|
||||
|
||||
/** show user line details on click of one line */
|
||||
const handleSelectLine = useCallback(
|
||||
(newLineIdToSelect: string) => {
|
||||
toast('selecting line!! NOT IMPLEMENTED');
|
||||
setSelectedLine(roomsMap[newLineIdToSelect]);
|
||||
},
|
||||
[setSelectedLine],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col flex-1">
|
||||
<NavBar />
|
||||
|
||||
<SidePanel />
|
||||
|
||||
{/* <MainPanel /> */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// have a socket view component to just connect on mount and disconnect from specific | tunes in and untunes similarly
|
||||
// have a socket view component to just tune into tuned in lines
|
||||
// have a stream view component to just make peer connections and have stream components available
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
import { LineMemberState } from '@nirvana/core/models/line.model';
|
||||
import MasterLineData from '@nirvana/core/models/masterLineData.model';
|
||||
import { Avatar, Skeleton, Tooltip } from 'antd';
|
||||
import React, { useMemo } from 'react';
|
||||
import { FaPlus } from 'react-icons/fa';
|
||||
import { FiActivity, FiSun } from 'react-icons/fi';
|
||||
import useRealTimeRooms from '../../../../providers/RealTimeRoomProvider';
|
||||
import useRooms from '../../../../providers/RoomsProvider';
|
||||
import useAuth from '../../../../providers/AuthProvider';
|
||||
import LineIcon from '../../../../components/lineIcon';
|
||||
import moment from 'moment';
|
||||
|
||||
export default function SidePanel() {
|
||||
// using merely for loading state...better to add to realtimeroom context?
|
||||
const { rooms: initialRoomsFetch } = useRooms();
|
||||
|
||||
const { roomsMap, handleSelectLine, selectedLine } = useRealTimeRooms();
|
||||
|
||||
const [toggleTunedLines, allLines] = useMemo(() => {
|
||||
const masterLines: MasterLineData[] = Object.values(roomsMap);
|
||||
|
||||
const tunedLines: MasterLineData[] = [];
|
||||
const restLines: MasterLineData[] = [];
|
||||
|
||||
masterLines.forEach((masterLine) => {
|
||||
if (masterLine.currentUserMember.state === LineMemberState.TUNED) restLines.push(masterLine);
|
||||
else restLines.push(masterLine);
|
||||
});
|
||||
|
||||
return [tunedLines, restLines];
|
||||
}, [roomsMap]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col bg-white w-[400px] relative group">
|
||||
{/* tuned in lines block */}
|
||||
<div className="bg-gray-100 flex flex-col shadow-lg">
|
||||
{/* tuned in header + general controls */}
|
||||
|
||||
<Tooltip placement="right" title={'These are your active rooms...'}>
|
||||
<div className="flex flex-row items-center py-3 px-4 pb-0">
|
||||
<span className="flex flex-row gap-2 items-center justify-start text-gray-400 animate-pulse">
|
||||
<FiActivity className="text-sm" />
|
||||
|
||||
<h2 className="text-inherit text-sm">Rooms</h2>
|
||||
|
||||
<p className="text-slate-300 text-xs">{`${toggleTunedLines?.length || 0}/3`}</p>
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
{/* list of toggle tuned lines */}
|
||||
<div className="flex flex-col mt-2">
|
||||
{toggleTunedLines.map((masterLineData) => (
|
||||
<LineRow
|
||||
key={`terminalListLines-${masterLineData.lineDetails._id.toString()}`}
|
||||
line={masterLineData}
|
||||
onClick={() => handleSelectLine(masterLineData.lineDetails._id.toString())}
|
||||
isSelected={
|
||||
selectedLine?.lineDetails._id.toString() ===
|
||||
masterLineData.lineDetails._id.toString()
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!(allLines.length > 0) && !(toggleTunedLines.length > 0) && (
|
||||
<span className="text-gray-300 text-sm my-5 text-center">
|
||||
You have no lines! <br /> Create one to connect to your team instantly.
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* rest of the lines */}
|
||||
<div className={'flex flex-col'}>
|
||||
{initialRoomsFetch.loading ? (
|
||||
<Skeleton />
|
||||
) : (
|
||||
allLines.map((masterLineData) => (
|
||||
<LineRow
|
||||
key={`terminalListLines-${masterLineData.lineDetails._id.toString()}`}
|
||||
line={masterLineData}
|
||||
onClick={() => handleSelectLine(masterLineData.lineDetails._id.toString())}
|
||||
isSelected={
|
||||
selectedLine?.lineDetails._id.toString() ===
|
||||
masterLineData.lineDetails._id.toString()
|
||||
}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="absolute bottom-3 right-3 z-10 scale-0
|
||||
group-hover:scale-100 ease-in-out hover:transition group-hover:transition delay-100 duration-200"
|
||||
>
|
||||
<button
|
||||
className="flex flex-row gap-2 items-center justify-evenly
|
||||
shadow-xl bg-gray-800 p-2 text-white text-xs"
|
||||
>
|
||||
<FaPlus />
|
||||
<span>New line</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function LineRow({
|
||||
line,
|
||||
onClick,
|
||||
isSelected,
|
||||
}: {
|
||||
line: MasterLineData;
|
||||
onClick: () => void;
|
||||
isSelected: boolean;
|
||||
}) {
|
||||
const { user } = useAuth();
|
||||
|
||||
const isUserTunedIn = useMemo(
|
||||
() => line.tunedInMemberIds?.includes(user._id.toString()),
|
||||
[line.tunedInMemberIds, user],
|
||||
);
|
||||
|
||||
/**
|
||||
* TODO: slowly add to this and fix based on added features
|
||||
* */
|
||||
const renderActivityIcon = useMemo(() => {
|
||||
// if there is someone or me broadcasting here
|
||||
if (line.currentBroadcastersUserIds?.length > 0)
|
||||
return <FiSun className="text-teal-500 animate-pulse" />;
|
||||
|
||||
if (isUserTunedIn) return <FiActivity className="text-black animate-pulse" />;
|
||||
|
||||
// if there is new activity blocks for me
|
||||
if (line.currentUserMember.lastVisitDate)
|
||||
return <span className="h-2 w-2 rounded-full bg-slate-800 animate-pulse"></span>;
|
||||
|
||||
return <span className="h-2 w-2 rounded-full bg-white animate-pulse"></span>;
|
||||
}, [line, isUserTunedIn]);
|
||||
|
||||
const renderRightActivity = useMemo(() => {
|
||||
// TODO: get the profile pictures of the broadcasters
|
||||
if (line.currentBroadcastersUserIds?.length > 0)
|
||||
return (
|
||||
<Avatar.Group
|
||||
key={`lineRowRightActivityGroup-${line.lineDetails._id.toString()}`}
|
||||
maxCount={2}
|
||||
maxPopoverTrigger="click"
|
||||
size="small"
|
||||
maxStyle={{
|
||||
color: '#f56a00',
|
||||
backgroundColor: '#fde3cf',
|
||||
cursor: 'pointer',
|
||||
borderRadius: '0',
|
||||
}}
|
||||
className="shadow-lg"
|
||||
>
|
||||
{line.otherUserObjects?.map((otherUser, index) => (
|
||||
<Avatar
|
||||
key={`lineListActivitySection-${otherUser._id.toString()}-${index}`}
|
||||
src={otherUser.picture ?? ''}
|
||||
shape="square"
|
||||
size={'small'}
|
||||
/>
|
||||
))}
|
||||
</Avatar.Group>
|
||||
);
|
||||
|
||||
// if there is new activity/black dot, then show relative time as little bolder? or too much?
|
||||
|
||||
// TODO: compare last visit date to latest audio block
|
||||
if (line.currentUserMember)
|
||||
return (
|
||||
<span className={`text-gray-400 ml-auto text-xs font-semibold`}>
|
||||
{moment(line.currentUserMember.lastVisitDate).fromNow(true)}
|
||||
</span>
|
||||
);
|
||||
|
||||
return (
|
||||
<span className={`text-gray-300 ml-auto text-xs `}>
|
||||
{moment(line.currentUserMember.lastVisitDate).fromNow(true)}
|
||||
</span>
|
||||
);
|
||||
}, [line]);
|
||||
|
||||
const profilePictures = useMemo(() => {
|
||||
const pictureSources: string[] = [];
|
||||
|
||||
// ?don't add in my image as that's useless contextually?
|
||||
// if (userData?.user?.picture) pictureSources.push(userData.user.picture);
|
||||
|
||||
line.otherUserObjects?.forEach((otherUser) => {
|
||||
if (otherUser.picture) pictureSources.push(otherUser.picture);
|
||||
});
|
||||
|
||||
return pictureSources;
|
||||
}, [line, user]);
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={() => onClick()}
|
||||
role={'presentation'}
|
||||
className={`flex flex-row items-center justify-start gap-2 p-2 px-4 h-14 hover:bg-gray-200 cursor-pointer transition-all
|
||||
last:border-b-0 border-b border-b-gray-200 relative z-50 rounded ${
|
||||
isSelected && 'bg-gray-200 scale-110 shadow-2xl translate-x-3'
|
||||
}`}
|
||||
>
|
||||
{/* status dot */}
|
||||
<div className="flex-shrink-0 h-4 w-4">{renderActivityIcon}</div>
|
||||
|
||||
{profilePictures && <LineIcon grayscale={!isUserTunedIn} sourceImages={profilePictures} />}
|
||||
|
||||
<h2
|
||||
className={`text-inherit text-md max-w-[220px] truncate text-slate-800 ${
|
||||
line.currentUserMember.lastVisitDate ? 'font-semibold' : ''
|
||||
}`}
|
||||
>
|
||||
{line.lineDetails.name || line.otherUserObjects[0].givenName}
|
||||
</h2>
|
||||
|
||||
<div className="ml-auto flex-shrink-0">{renderRightActivity}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user