mirror of
https://github.com/Qortal/Qortal-Hub.git
synced 2025-05-09 11:17:51 +00:00
group list optimizations
This commit is contained in:
parent
8bc414356a
commit
6ee01a4ec3
@ -1,15 +1,15 @@
|
||||
import type { CapacitorConfig } from '@capacitor/cli';
|
||||
import type { CapacitorConfig } from "@capacitor/cli";
|
||||
|
||||
const config: CapacitorConfig = {
|
||||
appId: 'org.Qortal.Qortal-Hub',
|
||||
appName: 'Qortal-Hub',
|
||||
webDir: 'dist',
|
||||
"plugins": {
|
||||
"LocalNotifications": {
|
||||
"smallIcon": "qort",
|
||||
"iconColor": "#09b6e8"
|
||||
}
|
||||
}
|
||||
appId: "org.Qortal.Qortal-Hub",
|
||||
appName: "Qortal-Hub",
|
||||
webDir: "dist",
|
||||
plugins: {
|
||||
LocalNotifications: {
|
||||
smallIcon: "qort",
|
||||
iconColor: "#09b6e8",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
17
src/App.tsx
17
src/App.tsx
@ -100,6 +100,8 @@ import { useRecoilState, useResetRecoilState, useSetRecoilState } from 'recoil';
|
||||
import {
|
||||
canSaveSettingToQdnAtom,
|
||||
enabledDevModeAtom,
|
||||
groupAnnouncementsAtom,
|
||||
groupChatTimestampsAtom,
|
||||
groupsOwnerNamesAtom,
|
||||
groupsPropertiesAtom,
|
||||
hasSettingsChangedAtom,
|
||||
@ -107,11 +109,13 @@ import {
|
||||
isUsingImportExportSettingsAtom,
|
||||
lastPaymentSeenTimestampAtom,
|
||||
mailsAtom,
|
||||
mutedGroupsAtom,
|
||||
oldPinnedAppsAtom,
|
||||
qMailLastEnteredTimestampAtom,
|
||||
settingsLocalLastUpdatedAtom,
|
||||
settingsQDNLastUpdatedAtom,
|
||||
sortablePinnedAppsAtom,
|
||||
timestampEnterDataAtom,
|
||||
} from './atoms/global';
|
||||
import { NotAuthenticated } from './ExtStates/NotAuthenticated';
|
||||
import { handleGetFileFromIndexedDB } from './utils/indexedDB';
|
||||
@ -479,6 +483,15 @@ function App() {
|
||||
lastPaymentSeenTimestampAtom
|
||||
);
|
||||
const resetGroupsOwnerNamesAtom = useResetRecoilState(groupsOwnerNamesAtom);
|
||||
const resetGroupAnnouncementsAtom = useResetRecoilState(
|
||||
groupAnnouncementsAtom
|
||||
);
|
||||
const resetMutedGroupsAtom = useResetRecoilState(mutedGroupsAtom);
|
||||
|
||||
const resetGroupChatTimestampsAtom = useResetRecoilState(
|
||||
groupChatTimestampsAtom
|
||||
);
|
||||
const resetTimestampEnterAtom = useResetRecoilState(timestampEnterDataAtom);
|
||||
|
||||
const resetAllRecoil = () => {
|
||||
resetAtomSortablePinnedAppsAtom();
|
||||
@ -492,6 +505,10 @@ function App() {
|
||||
resetGroupPropertiesAtom();
|
||||
resetLastPaymentSeenTimestampAtom();
|
||||
resetGroupsOwnerNamesAtom();
|
||||
resetGroupAnnouncementsAtom();
|
||||
resetMutedGroupsAtom();
|
||||
resetGroupChatTimestampsAtom();
|
||||
resetTimestampEnterAtom();
|
||||
};
|
||||
|
||||
const handleSetGlobalApikey = (key) => {
|
||||
|
@ -201,3 +201,73 @@ export const isOpenBlockedModalAtom = atom({
|
||||
key: 'isOpenBlockedModalAtom',
|
||||
default: false,
|
||||
});
|
||||
|
||||
export const groupsOwnerNamesSelector = selectorFamily({
|
||||
key: 'groupsOwnerNamesSelector',
|
||||
get:
|
||||
(key) =>
|
||||
({ get }) => {
|
||||
const data = get(groupsOwnerNamesAtom);
|
||||
return data[key] || null; // Return the value for the key or null if not found
|
||||
},
|
||||
});
|
||||
|
||||
export const groupAnnouncementsAtom = atom({
|
||||
key: 'groupAnnouncementsAtom',
|
||||
default: {},
|
||||
});
|
||||
|
||||
export const groupAnnouncementSelector = selectorFamily({
|
||||
key: 'groupAnnouncementSelector',
|
||||
get:
|
||||
(key) =>
|
||||
({ get }) => {
|
||||
const data = get(groupAnnouncementsAtom);
|
||||
return data[key] || null; // Return the value for the key or null if not found
|
||||
},
|
||||
});
|
||||
|
||||
export const groupPropertySelector = selectorFamily({
|
||||
key: 'groupPropertySelector',
|
||||
get:
|
||||
(key) =>
|
||||
({ get }) => {
|
||||
const data = get(groupsPropertiesAtom);
|
||||
return data[key] || null; // Return the value for the key or null if not found
|
||||
},
|
||||
});
|
||||
|
||||
export const mutedGroupsAtom = atom({
|
||||
key: 'mutedGroupsAtom',
|
||||
default: [],
|
||||
});
|
||||
|
||||
export const groupChatTimestampsAtom = atom({
|
||||
key: 'groupChatTimestampsAtom',
|
||||
default: {},
|
||||
});
|
||||
|
||||
export const groupChatTimestampSelector = selectorFamily({
|
||||
key: 'groupChatTimestampSelector',
|
||||
get:
|
||||
(key) =>
|
||||
({ get }) => {
|
||||
const data = get(groupChatTimestampsAtom);
|
||||
return data[key] || null; // Return the value for the key or null if not found
|
||||
},
|
||||
});
|
||||
|
||||
export const timestampEnterDataAtom = atom({
|
||||
key: 'timestampEnterDataAtom',
|
||||
default: {},
|
||||
});
|
||||
|
||||
export const timestampEnterDataSelector = selectorFamily({
|
||||
key: 'timestampEnterDataSelector',
|
||||
get:
|
||||
(key) =>
|
||||
({ get }) => {
|
||||
const data = get(timestampEnterDataAtom);
|
||||
return data[key] || null; // Return the value for the key or null if not found
|
||||
},
|
||||
});
|
||||
|
@ -75,6 +75,21 @@ const getBadgeImg = (level) => {
|
||||
}
|
||||
};
|
||||
|
||||
const UserBadge = React.memo(({ userInfo }) => {
|
||||
return (
|
||||
<Tooltip disableFocusListener title={`level ${userInfo ?? 0}`}>
|
||||
<img
|
||||
style={{
|
||||
visibility: userInfo !== undefined ? 'visible' : 'hidden',
|
||||
width: '30px',
|
||||
height: 'auto',
|
||||
}}
|
||||
src={getBadgeImg(userInfo)}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
});
|
||||
|
||||
export const MessageItem = React.memo(
|
||||
({
|
||||
message,
|
||||
@ -210,16 +225,7 @@ export const MessageItem = React.memo(
|
||||
{message?.senderName?.charAt(0)}
|
||||
</Avatar>
|
||||
</WrapperUserAction>
|
||||
<Tooltip disableFocusListener title={`level ${userInfo ?? 0}`}>
|
||||
<img
|
||||
style={{
|
||||
visibility: userInfo !== undefined ? 'visible' : 'hidden',
|
||||
width: '30px',
|
||||
height: 'auto',
|
||||
}}
|
||||
src={getBadgeImg(userInfo)}
|
||||
/>
|
||||
</Tooltip>
|
||||
<UserBadge userInfo={userInfo} />
|
||||
</Box>
|
||||
)}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import { EditorProvider, useCurrentEditor } from '@tiptap/react';
|
||||
import StarterKit from '@tiptap/starter-kit';
|
||||
import { Color } from '@tiptap/extension-color';
|
||||
@ -41,7 +41,8 @@ function textMatcher(doc, from) {
|
||||
return { start, query };
|
||||
}
|
||||
|
||||
const MenuBar = ({
|
||||
const MenuBar = React.memo(
|
||||
({
|
||||
setEditorRef,
|
||||
isChat,
|
||||
isDisabledEditorEnter,
|
||||
@ -51,16 +52,16 @@ const MenuBar = ({
|
||||
const fileInputRef = useRef(null);
|
||||
const theme = useTheme();
|
||||
|
||||
if (!editor) {
|
||||
return null;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (editor && setEditorRef) {
|
||||
setEditorRef(editor);
|
||||
}
|
||||
}, [editor, setEditorRef]);
|
||||
|
||||
if (!editor) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleImageUpload = async (file) => {
|
||||
let compressedFile;
|
||||
await new Promise<void>((resolve) => {
|
||||
@ -329,7 +330,8 @@ const MenuBar = ({
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const extensions = [
|
||||
Color.configure({ types: [TextStyle.name, ListItem.name] }),
|
||||
@ -373,10 +375,10 @@ export default ({
|
||||
? extensions.filter((item) => item?.name !== 'image')
|
||||
: extensions;
|
||||
const editorRef = useRef(null);
|
||||
const setEditorRefFunc = (editorInstance) => {
|
||||
const setEditorRefFunc = useCallback((editorInstance) => {
|
||||
editorRef.current = editorInstance;
|
||||
setEditorRef(editorInstance);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// const users = [
|
||||
// { id: 1, label: 'Alice' },
|
||||
|
@ -10,6 +10,8 @@ import {
|
||||
import MailOutlineIcon from '@mui/icons-material/MailOutline';
|
||||
import NotificationsOffIcon from '@mui/icons-material/NotificationsOff';
|
||||
import { executeEvent } from '../utils/events';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { mutedGroupsAtom } from '../atoms/global';
|
||||
|
||||
const CustomStyledMenu = styled(Menu)(({ theme }) => ({
|
||||
'& .MuiPaper-root': {
|
||||
@ -28,16 +30,12 @@ const CustomStyledMenu = styled(Menu)(({ theme }) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
export const ContextMenu = ({
|
||||
children,
|
||||
groupId,
|
||||
getUserSettings,
|
||||
mutedGroups,
|
||||
}) => {
|
||||
export const ContextMenu = ({ children, groupId, getUserSettings }) => {
|
||||
const [menuPosition, setMenuPosition] = useState(null);
|
||||
const longPressTimeout = useRef(null);
|
||||
const preventClick = useRef(false); // Flag to prevent click after long-press or right-click
|
||||
const theme = useTheme();
|
||||
const [mutedGroups] = useRecoilState(mutedGroupsAtom);
|
||||
const isMuted = useMemo(() => {
|
||||
return mutedGroups.includes(groupId);
|
||||
}, [mutedGroups, groupId]);
|
||||
|
@ -199,6 +199,8 @@ export const AddGroup = ({ address, open, setOpen }) => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (!open) return null;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Dialog
|
||||
|
@ -68,10 +68,14 @@ import { AdminSpace } from '../Chat/AdminSpace';
|
||||
import { useRecoilState, useSetRecoilState } from 'recoil';
|
||||
import {
|
||||
addressInfoControllerAtom,
|
||||
groupAnnouncementsAtom,
|
||||
groupChatTimestampsAtom,
|
||||
groupsOwnerNamesAtom,
|
||||
groupsPropertiesAtom,
|
||||
isOpenBlockedModalAtom,
|
||||
mutedGroupsAtom,
|
||||
selectedGroupIdAtom,
|
||||
timestampEnterDataAtom,
|
||||
} from '../../atoms/global';
|
||||
import { sortArrayByTimestampAndGroupName } from '../../utils/time';
|
||||
import PersonOffIcon from '@mui/icons-material/PersonOff';
|
||||
@ -80,6 +84,7 @@ import NoEncryptionGmailerrorredIcon from '@mui/icons-material/NoEncryptionGmail
|
||||
import { BlockedUsersModal } from './BlockedUsersModal';
|
||||
import { WalletsAppWrapper } from './WalletsAppWrapper';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { GroupList } from './GroupList';
|
||||
|
||||
export const getPublishesFromAdmins = async (admins: string[], groupId) => {
|
||||
const queryString = admins.map((name) => `name=${name}`).join('&');
|
||||
@ -117,7 +122,7 @@ interface GroupProps {
|
||||
balance: number;
|
||||
}
|
||||
|
||||
const timeDifferenceForNotificationChats = 900000;
|
||||
export const timeDifferenceForNotificationChats = 900000;
|
||||
|
||||
export const requestQueueMemberNames = new RequestQueueWithPromise(5);
|
||||
export const requestQueueAdminMemberNames = new RequestQueueWithPromise(5);
|
||||
@ -410,7 +415,9 @@ export const Group = ({
|
||||
const { setMemberGroups, rootHeight, isRunningPublicNode } =
|
||||
useContext(MyContext);
|
||||
const lastGroupNotification = useRef<null | number>(null);
|
||||
const [timestampEnterData, setTimestampEnterData] = useState({});
|
||||
const [timestampEnterData, setTimestampEnterData] = useRecoilState(
|
||||
timestampEnterDataAtom
|
||||
);
|
||||
const [chatMode, setChatMode] = useState('groups');
|
||||
const [newChat, setNewChat] = useState(false);
|
||||
const [openSnack, setOpenSnack] = React.useState(false);
|
||||
@ -421,7 +428,10 @@ export const Group = ({
|
||||
const [firstSecretKeyInCreation, setFirstSecretKeyInCreation] =
|
||||
React.useState(false);
|
||||
const [groupSection, setGroupSection] = React.useState('home');
|
||||
const [groupAnnouncements, setGroupAnnouncements] = React.useState({});
|
||||
const [groupAnnouncements, setGroupAnnouncements] = useRecoilState(
|
||||
groupAnnouncementsAtom
|
||||
);
|
||||
|
||||
const [defaultThread, setDefaultThread] = React.useState(null);
|
||||
const [isOpenDrawer, setIsOpenDrawer] = React.useState(false);
|
||||
const setIsOpenBlockedUserModal = useSetRecoilState(isOpenBlockedModalAtom);
|
||||
@ -429,7 +439,7 @@ export const Group = ({
|
||||
const [hideCommonKeyPopup, setHideCommonKeyPopup] = React.useState(false);
|
||||
const [isLoadingGroupMessage, setIsLoadingGroupMessage] = React.useState('');
|
||||
const [drawerMode, setDrawerMode] = React.useState('groups');
|
||||
const [mutedGroups, setMutedGroups] = useState([]);
|
||||
const setMutedGroups = useSetRecoilState(mutedGroupsAtom);
|
||||
const [mobileViewMode, setMobileViewMode] = useState('home');
|
||||
const [mobileViewModeKeepOpen, setMobileViewModeKeepOpen] = useState('');
|
||||
const isFocusedRef = useRef(true);
|
||||
@ -443,7 +453,9 @@ export const Group = ({
|
||||
const settimeoutForRefetchSecretKey = useRef(null);
|
||||
const { clearStatesMessageQueueProvider } = useMessageQueue();
|
||||
const initiatedGetMembers = useRef(false);
|
||||
const [groupChatTimestamps, setGroupChatTimestamps] = React.useState({});
|
||||
const [groupChatTimestamps, setGroupChatTimestamps] = useRecoilState(
|
||||
groupChatTimestampsAtom
|
||||
);
|
||||
const [appsMode, setAppsMode] = useState('home');
|
||||
const [appsModeDev, setAppsModeDev] = useState('home');
|
||||
const [isOpenSideViewDirects, setIsOpenSideViewDirects] = useState(false);
|
||||
@ -500,7 +512,7 @@ export const Group = ({
|
||||
selectedDirectRef.current = selectedDirect;
|
||||
}, [selectedDirect]);
|
||||
|
||||
const getUserSettings = async () => {
|
||||
const getUserSettings = useCallback(async () => {
|
||||
try {
|
||||
return new Promise((res, rej) => {
|
||||
window
|
||||
@ -522,13 +534,13 @@ export const Group = ({
|
||||
} catch (error) {
|
||||
console.log('error', error);
|
||||
}
|
||||
};
|
||||
}, [setMutedGroups]);
|
||||
|
||||
useEffect(() => {
|
||||
getUserSettings();
|
||||
}, []);
|
||||
}, [getUserSettings]);
|
||||
|
||||
const getTimestampEnterChat = async () => {
|
||||
const getTimestampEnterChat = useCallback(async () => {
|
||||
try {
|
||||
return new Promise((res, rej) => {
|
||||
window
|
||||
@ -548,7 +560,7 @@ export const Group = ({
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const refreshHomeDataFunc = () => {
|
||||
setGroupSection('default');
|
||||
@ -650,41 +662,45 @@ export const Group = ({
|
||||
return hasUnread;
|
||||
}, [groupAnnouncements, groups]);
|
||||
|
||||
const getSecretKey = async (
|
||||
loadingGroupParam?: boolean,
|
||||
secretKeyToPublish?: boolean
|
||||
) => {
|
||||
const getSecretKey = useCallback(
|
||||
async (loadingGroupParam?: boolean, secretKeyToPublish?: boolean) => {
|
||||
try {
|
||||
setIsLoadingGroupMessage('Locating encryption keys');
|
||||
pauseAllQueues();
|
||||
|
||||
let dataFromStorage;
|
||||
let publishFromStorage;
|
||||
let adminsFromStorage;
|
||||
|
||||
if (
|
||||
secretKeyToPublish &&
|
||||
secretKey &&
|
||||
lastFetchedSecretKey.current &&
|
||||
Date.now() - lastFetchedSecretKey.current < 600000
|
||||
)
|
||||
) {
|
||||
return secretKey;
|
||||
}
|
||||
|
||||
if (loadingGroupParam) {
|
||||
setIsLoadingGroup(true);
|
||||
}
|
||||
|
||||
if (selectedGroup?.groupId !== selectedGroupRef.current.groupId) {
|
||||
if (settimeoutForRefetchSecretKey.current) {
|
||||
clearTimeout(settimeoutForRefetchSecretKey.current);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const prevGroupId = selectedGroupRef.current.groupId;
|
||||
// const validApi = await findUsableApi();
|
||||
|
||||
const { names, addresses, both } =
|
||||
adminsFromStorage || (await getGroupAdmins(selectedGroup?.groupId));
|
||||
setAdmins(addresses);
|
||||
setAdminsWithNames(both);
|
||||
if (!names.length) {
|
||||
throw new Error('Network error');
|
||||
}
|
||||
|
||||
if (!names.length) throw new Error('Network error');
|
||||
|
||||
const publish =
|
||||
publishFromStorage ||
|
||||
(await getPublishesFromAdmins(names, selectedGroup?.groupId));
|
||||
@ -695,6 +711,7 @@ export const Group = ({
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (publish === false) {
|
||||
setTriedToFetchSecretKey(true);
|
||||
settimeoutForRefetchSecretKey.current = setTimeout(() => {
|
||||
@ -702,29 +719,33 @@ export const Group = ({
|
||||
}, 120000);
|
||||
return false;
|
||||
}
|
||||
|
||||
setSecretKeyPublishDate(publish?.updated || publish?.created);
|
||||
|
||||
let data;
|
||||
if (dataFromStorage) {
|
||||
data = dataFromStorage;
|
||||
} else {
|
||||
// const shouldRebuild = !secretKeyPublishDate || (publish?.update && publish?.updated > secretKeyPublishDate)
|
||||
setIsLoadingGroupMessage('Downloading encryption keys');
|
||||
const res = await fetch(
|
||||
`${getBaseApiReact()}/arbitrary/DOCUMENT_PRIVATE/${publish.name}/${
|
||||
publish.identifier
|
||||
}?encoding=base64&rebuild=true`
|
||||
`${getBaseApiReact()}/arbitrary/DOCUMENT_PRIVATE/${publish.name}/${publish.identifier}?encoding=base64&rebuild=true`
|
||||
);
|
||||
data = await res.text();
|
||||
}
|
||||
|
||||
const decryptedKey: any = await decryptResource(data);
|
||||
const dataint8Array = base64ToUint8Array(decryptedKey.data);
|
||||
const decryptedKeyToObject = uint8ArrayToObject(dataint8Array);
|
||||
if (!validateSecretKey(decryptedKeyToObject))
|
||||
|
||||
if (!validateSecretKey(decryptedKeyToObject)) {
|
||||
throw new Error('SecretKey is not valid');
|
||||
}
|
||||
|
||||
setSecretKeyDetails(publish);
|
||||
setSecretKey(decryptedKeyToObject);
|
||||
lastFetchedSecretKey.current = Date.now();
|
||||
setMemberCountFromSecretKeyData(decryptedKey.count);
|
||||
|
||||
window
|
||||
.sendMessage('setGroupData', {
|
||||
groupId: selectedGroup?.groupId,
|
||||
@ -758,7 +779,22 @@ export const Group = ({
|
||||
setIsLoadingGroupMessage('');
|
||||
resumeAllQueues();
|
||||
}
|
||||
};
|
||||
},
|
||||
[
|
||||
secretKey,
|
||||
selectedGroup?.groupId,
|
||||
setIsLoadingGroup,
|
||||
setIsLoadingGroupMessage,
|
||||
setSecretKey,
|
||||
setSecretKeyDetails,
|
||||
setTriedToFetchSecretKey,
|
||||
setFirstSecretKeyInCreation,
|
||||
setMemberCountFromSecretKeyData,
|
||||
setAdmins,
|
||||
setAdminsWithNames,
|
||||
setSecretKeyPublishDate,
|
||||
]
|
||||
);
|
||||
|
||||
const getAdminsForPublic = async (selectedGroup) => {
|
||||
try {
|
||||
@ -1050,8 +1086,6 @@ export const Group = ({
|
||||
triedToFetchSecretKey,
|
||||
]);
|
||||
|
||||
console.log('groupOwner?.owner', groupOwner);
|
||||
|
||||
const notifyAdmin = async (admin) => {
|
||||
try {
|
||||
setIsLoadingNotifyAdmin(true);
|
||||
@ -1327,8 +1361,6 @@ export const Group = ({
|
||||
};
|
||||
}, []);
|
||||
|
||||
console.log('selectedGroup', selectedGroup);
|
||||
|
||||
const openGroupChatFromNotification = (e) => {
|
||||
if (isLoadingOpenSectionFromNotification.current) return;
|
||||
|
||||
@ -1498,9 +1530,9 @@ export const Group = ({
|
||||
};
|
||||
}, [groups, selectedGroup]);
|
||||
|
||||
const handleSecretKeyCreationInProgress = () => {
|
||||
const handleSecretKeyCreationInProgress = useCallback(() => {
|
||||
setFirstSecretKeyInCreation(true);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const goToHome = async () => {
|
||||
setDesktopViewMode('home');
|
||||
@ -1811,112 +1843,7 @@ export const Group = ({
|
||||
);
|
||||
};
|
||||
|
||||
const renderGroups = () => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
width: '380px',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-start',
|
||||
height: '100%',
|
||||
background: theme.palette.background.surface,
|
||||
borderRadius: '0px 15px 15px 0px',
|
||||
padding: '0px 2px',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
display: 'flex',
|
||||
gap: '10px',
|
||||
}}
|
||||
>
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
setDesktopSideView('groups');
|
||||
}}
|
||||
>
|
||||
<IconWrapper
|
||||
color={
|
||||
groupChatHasUnread || groupsAnnHasUnread
|
||||
? theme.palette.other.unread
|
||||
: desktopSideView === 'groups'
|
||||
? theme.palette.text.primary
|
||||
: theme.palette.text.secondary
|
||||
}
|
||||
label="Groups"
|
||||
selected={desktopSideView === 'groups'}
|
||||
customWidth="75px"
|
||||
>
|
||||
<HubsIcon
|
||||
height={24}
|
||||
color={
|
||||
groupChatHasUnread || groupsAnnHasUnread
|
||||
? theme.palette.other.unread
|
||||
: desktopSideView === 'groups'
|
||||
? theme.palette.text.primary
|
||||
: theme.palette.text.secondary
|
||||
}
|
||||
/>
|
||||
</IconWrapper>
|
||||
</ButtonBase>
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
setDesktopSideView('directs');
|
||||
}}
|
||||
>
|
||||
<IconWrapper
|
||||
customWidth="75px"
|
||||
color={
|
||||
directChatHasUnread
|
||||
? theme.palette.other.unread
|
||||
: desktopSideView === 'directs'
|
||||
? theme.palette.text.primary
|
||||
: theme.palette.text.secondary
|
||||
}
|
||||
label="Messaging"
|
||||
selected={desktopSideView === 'directs'}
|
||||
>
|
||||
<MessagingIcon
|
||||
height={24}
|
||||
color={
|
||||
directChatHasUnread
|
||||
? theme.palette.other.unread
|
||||
: desktopSideView === 'directs'
|
||||
? theme.palette.text.primary
|
||||
: theme.palette.text.secondary
|
||||
}
|
||||
/>
|
||||
</IconWrapper>
|
||||
</ButtonBase>
|
||||
</Box>
|
||||
|
||||
<div
|
||||
style={{
|
||||
alignItems: 'flex-start',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
left: chatMode === 'directs' && '-1000px',
|
||||
overflowY: 'auto',
|
||||
position: chatMode === 'directs' && 'fixed',
|
||||
visibility: chatMode === 'directs' && 'hidden',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
{groups.map((group: any) => (
|
||||
<List
|
||||
sx={{
|
||||
width: '100%',
|
||||
}}
|
||||
className="group-list"
|
||||
dense={true}
|
||||
>
|
||||
<ListItem
|
||||
onClick={() => {
|
||||
const selectGroupFunc = useCallback((group) => {
|
||||
setMobileViewMode('group');
|
||||
setDesktopSideView('groups');
|
||||
initiatedGetMembers.current = false;
|
||||
@ -1943,195 +1870,7 @@ export const Group = ({
|
||||
setTimeout(() => {
|
||||
setSelectedGroup(group);
|
||||
}, 200);
|
||||
}}
|
||||
sx={{
|
||||
display: 'flex',
|
||||
background:
|
||||
group?.groupId === selectedGroup?.groupId &&
|
||||
theme.palette.action.selected,
|
||||
borderRadius: '2px',
|
||||
cursor: 'pointer',
|
||||
flexDirection: 'column',
|
||||
padding: '2px',
|
||||
width: '100%',
|
||||
'&:hover': {
|
||||
backgroundColor: 'action.hover', // background on hover
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ContextMenu
|
||||
mutedGroups={mutedGroups}
|
||||
getUserSettings={getUserSettings}
|
||||
groupId={group.groupId}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<ListItemAvatar>
|
||||
{groupsOwnerNames[group?.groupId] ? (
|
||||
<Avatar
|
||||
alt={group?.groupName?.charAt(0)}
|
||||
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
|
||||
groupsOwnerNames[group?.groupId]
|
||||
}/qortal_group_avatar_${group?.groupId}?async=true`}
|
||||
>
|
||||
{group?.groupName?.charAt(0).toUpperCase()}
|
||||
</Avatar>
|
||||
) : (
|
||||
<Avatar alt={group?.groupName?.charAt(0)}>
|
||||
{' '}
|
||||
{group?.groupName?.charAt(0).toUpperCase() || 'G'}
|
||||
</Avatar>
|
||||
)}
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
primary={
|
||||
group.groupId === '0' ? 'General' : group.groupName
|
||||
}
|
||||
secondary={
|
||||
!group?.timestamp
|
||||
? 'no messages'
|
||||
: `last message: ${formatEmailDate(group?.timestamp)}`
|
||||
}
|
||||
primaryTypographyProps={{
|
||||
style: {
|
||||
color:
|
||||
group?.groupId === selectedGroup?.groupId &&
|
||||
theme.palette.text.primary,
|
||||
fontSize: '16px',
|
||||
},
|
||||
}} // Change the color of the primary text
|
||||
secondaryTypographyProps={{
|
||||
style: {
|
||||
color:
|
||||
group?.groupId === selectedGroup?.groupId &&
|
||||
theme.palette.text.primary,
|
||||
fontSize: '12px',
|
||||
},
|
||||
}}
|
||||
sx={{
|
||||
width: '150px',
|
||||
fontFamily: 'Inter',
|
||||
fontSize: '16px',
|
||||
}}
|
||||
/>
|
||||
{groupAnnouncements[group?.groupId] &&
|
||||
!groupAnnouncements[group?.groupId]?.seentimestamp && (
|
||||
<CampaignIcon
|
||||
sx={{
|
||||
color: theme.palette.other.unread,
|
||||
marginRight: '5px',
|
||||
marginBottom: 'auto',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '5px',
|
||||
justifyContent: 'flex-start',
|
||||
height: '100%',
|
||||
marginBottom: 'auto',
|
||||
}}
|
||||
>
|
||||
{group?.data &&
|
||||
groupChatTimestamps[group?.groupId] &&
|
||||
group?.sender !== myAddress &&
|
||||
group?.timestamp &&
|
||||
((!timestampEnterData[group?.groupId] &&
|
||||
Date.now() - group?.timestamp <
|
||||
timeDifferenceForNotificationChats) ||
|
||||
timestampEnterData[group?.groupId] <
|
||||
group?.timestamp) && (
|
||||
<MarkChatUnreadIcon
|
||||
sx={{
|
||||
color: theme.palette.other.unread,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{groupsProperties[group?.groupId]?.isOpen === false && (
|
||||
<LockIcon
|
||||
sx={{
|
||||
color: theme.palette.other.positive,
|
||||
marginBottom: 'auto',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</ContextMenu>
|
||||
</ListItem>
|
||||
</List>
|
||||
))}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
gap: '10px',
|
||||
justifyContent: 'center',
|
||||
padding: '10px',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
{chatMode === 'groups' && (
|
||||
<>
|
||||
<CustomButton
|
||||
onClick={() => {
|
||||
setOpenAddGroup(true);
|
||||
}}
|
||||
>
|
||||
<AddCircleOutlineIcon
|
||||
sx={{
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
/>
|
||||
Group
|
||||
</CustomButton>
|
||||
|
||||
{!isRunningPublicNode && (
|
||||
<CustomButton
|
||||
onClick={() => {
|
||||
setIsOpenBlockedUserModal(true);
|
||||
}}
|
||||
sx={{
|
||||
minWidth: 'unset',
|
||||
padding: '10px',
|
||||
}}
|
||||
>
|
||||
<PersonOffIcon
|
||||
sx={{
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
/>
|
||||
</CustomButton>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{chatMode === 'directs' && (
|
||||
<CustomButton
|
||||
onClick={() => {
|
||||
setNewChat(true);
|
||||
setSelectedDirect(null);
|
||||
setIsOpenDrawer(false);
|
||||
}}
|
||||
>
|
||||
<CreateIcon
|
||||
sx={{
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
/>
|
||||
New Chat
|
||||
</CustomButton>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -2176,9 +1915,24 @@ export const Group = ({
|
||||
/>
|
||||
)}
|
||||
|
||||
{desktopViewMode === 'chat' &&
|
||||
desktopSideView !== 'directs' &&
|
||||
renderGroups()}
|
||||
{desktopViewMode === 'chat' && desktopSideView !== 'directs' && (
|
||||
<GroupList
|
||||
selectGroupFunc={selectGroupFunc}
|
||||
setDesktopSideView={setDesktopSideView}
|
||||
groupChatHasUnread={groupChatHasUnread}
|
||||
groupsAnnHasUnread={groupsAnnHasUnread}
|
||||
desktopSideView={desktopSideView}
|
||||
directChatHasUnread={directChatHasUnread}
|
||||
chatMode={chatMode}
|
||||
groups={groups}
|
||||
selectedGroup={selectedGroup}
|
||||
getUserSettings={getUserSettings}
|
||||
setOpenAddGroup={setOpenAddGroup}
|
||||
isRunningPublicNode={isRunningPublicNode}
|
||||
setIsOpenBlockedUserModal={setIsOpenBlockedUserModal}
|
||||
myAddress={myAddress}
|
||||
/>
|
||||
)}
|
||||
|
||||
{desktopViewMode === 'chat' &&
|
||||
desktopSideView === 'directs' &&
|
||||
@ -2318,7 +2072,7 @@ export const Group = ({
|
||||
isPrivate={isPrivate}
|
||||
setSecretKey={setSecretKey}
|
||||
handleNewEncryptionNotification={setNewEncryptionNotification}
|
||||
hide={groupSection !== 'chat' || selectedDirect || newChat}
|
||||
hide={groupSection !== 'chat' || !!selectedDirect || newChat}
|
||||
hideView={!(desktopViewMode === 'chat' && selectedGroup)}
|
||||
handleSecretKeyCreationInProgress={
|
||||
handleSecretKeyCreationInProgress
|
||||
|
348
src/components/Group/GroupList.tsx
Normal file
348
src/components/Group/GroupList.tsx
Normal file
@ -0,0 +1,348 @@
|
||||
import {
|
||||
Avatar,
|
||||
Box,
|
||||
ButtonBase,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemAvatar,
|
||||
ListItemText,
|
||||
useTheme,
|
||||
} from '@mui/material';
|
||||
import React, { useCallback } from 'react';
|
||||
import { IconWrapper } from '../Desktop/DesktopFooter';
|
||||
import { HubsIcon } from '../../assets/Icons/HubsIcon';
|
||||
import { MessagingIcon } from '../../assets/Icons/MessagingIcon';
|
||||
import { ContextMenu } from '../ContextMenu';
|
||||
import { getBaseApiReact } from '../../App';
|
||||
import { formatEmailDate } from './QMailMessages';
|
||||
import CampaignIcon from '@mui/icons-material/Campaign';
|
||||
import MarkChatUnreadIcon from '@mui/icons-material/MarkChatUnread';
|
||||
import LockIcon from '@mui/icons-material/Lock';
|
||||
import { CustomButton } from '../../styles/App-styles';
|
||||
import AddCircleOutlineIcon from '@mui/icons-material/AddCircleOutline';
|
||||
import PersonOffIcon from '@mui/icons-material/PersonOff';
|
||||
import {
|
||||
groupAnnouncementSelector,
|
||||
groupChatTimestampSelector,
|
||||
groupPropertySelector,
|
||||
groupsOwnerNamesSelector,
|
||||
timestampEnterDataSelector,
|
||||
} from '../../atoms/global';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { timeDifferenceForNotificationChats } from './Group';
|
||||
|
||||
export const GroupList = ({
|
||||
selectGroupFunc,
|
||||
setDesktopSideView,
|
||||
groupChatHasUnread,
|
||||
groupsAnnHasUnread,
|
||||
desktopSideView,
|
||||
directChatHasUnread,
|
||||
chatMode,
|
||||
groups,
|
||||
selectedGroup,
|
||||
getUserSettings,
|
||||
setOpenAddGroup,
|
||||
isRunningPublicNode,
|
||||
setIsOpenBlockedUserModal,
|
||||
myAddress,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
width: '380px',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-start',
|
||||
height: '100%',
|
||||
background: theme.palette.background.surface,
|
||||
borderRadius: '0px 15px 15px 0px',
|
||||
padding: '0px 2px',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
display: 'flex',
|
||||
gap: '10px',
|
||||
}}
|
||||
>
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
setDesktopSideView('groups');
|
||||
}}
|
||||
>
|
||||
<IconWrapper
|
||||
color={
|
||||
groupChatHasUnread || groupsAnnHasUnread
|
||||
? theme.palette.other.unread
|
||||
: desktopSideView === 'groups'
|
||||
? theme.palette.text.primary
|
||||
: theme.palette.text.secondary
|
||||
}
|
||||
label="Groups"
|
||||
selected={desktopSideView === 'groups'}
|
||||
customWidth="75px"
|
||||
>
|
||||
<HubsIcon
|
||||
height={24}
|
||||
color={
|
||||
groupChatHasUnread || groupsAnnHasUnread
|
||||
? theme.palette.other.unread
|
||||
: desktopSideView === 'groups'
|
||||
? theme.palette.text.primary
|
||||
: theme.palette.text.secondary
|
||||
}
|
||||
/>
|
||||
</IconWrapper>
|
||||
</ButtonBase>
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
setDesktopSideView('directs');
|
||||
}}
|
||||
>
|
||||
<IconWrapper
|
||||
customWidth="75px"
|
||||
color={
|
||||
directChatHasUnread
|
||||
? theme.palette.other.unread
|
||||
: desktopSideView === 'directs'
|
||||
? theme.palette.text.primary
|
||||
: theme.palette.text.secondary
|
||||
}
|
||||
label="Messaging"
|
||||
selected={desktopSideView === 'directs'}
|
||||
>
|
||||
<MessagingIcon
|
||||
height={24}
|
||||
color={
|
||||
directChatHasUnread
|
||||
? theme.palette.other.unread
|
||||
: desktopSideView === 'directs'
|
||||
? theme.palette.text.primary
|
||||
: theme.palette.text.secondary
|
||||
}
|
||||
/>
|
||||
</IconWrapper>
|
||||
</ButtonBase>
|
||||
</Box>
|
||||
|
||||
<div
|
||||
style={{
|
||||
alignItems: 'flex-start',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
left: chatMode === 'directs' && '-1000px',
|
||||
overflowY: 'auto',
|
||||
position: chatMode === 'directs' && 'fixed',
|
||||
visibility: chatMode === 'directs' && 'hidden',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<List
|
||||
sx={{
|
||||
width: '100%',
|
||||
}}
|
||||
className="group-list"
|
||||
dense={false}
|
||||
>
|
||||
{groups.map((group: any) => (
|
||||
<GroupItem
|
||||
selectGroupFunc={selectGroupFunc}
|
||||
key={group.groupId}
|
||||
group={group}
|
||||
selectedGroup={selectedGroup}
|
||||
getUserSettings={getUserSettings}
|
||||
myAddress={myAddress}
|
||||
/>
|
||||
))}
|
||||
</List>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
gap: '10px',
|
||||
justifyContent: 'center',
|
||||
padding: '10px',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<>
|
||||
<CustomButton
|
||||
onClick={() => {
|
||||
setOpenAddGroup(true);
|
||||
}}
|
||||
>
|
||||
<AddCircleOutlineIcon
|
||||
sx={{
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
/>
|
||||
Group
|
||||
</CustomButton>
|
||||
|
||||
{!isRunningPublicNode && (
|
||||
<CustomButton
|
||||
onClick={() => {
|
||||
setIsOpenBlockedUserModal(true);
|
||||
}}
|
||||
sx={{
|
||||
minWidth: 'unset',
|
||||
padding: '10px',
|
||||
}}
|
||||
>
|
||||
<PersonOffIcon
|
||||
sx={{
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
/>
|
||||
</CustomButton>
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const GroupItem = React.memo(
|
||||
({ selectGroupFunc, group, selectedGroup, getUserSettings, myAddress }) => {
|
||||
const theme = useTheme();
|
||||
const ownerName = useRecoilValue(groupsOwnerNamesSelector(group?.groupId));
|
||||
const announcement = useRecoilValue(
|
||||
groupAnnouncementSelector(group?.groupId)
|
||||
);
|
||||
const groupProperty = useRecoilValue(groupPropertySelector(group?.groupId));
|
||||
const groupChatTimestamp = useRecoilValue(
|
||||
groupChatTimestampSelector(group?.groupId)
|
||||
);
|
||||
const timestampEnterData = useRecoilValue(
|
||||
timestampEnterDataSelector(group?.groupId)
|
||||
);
|
||||
const selectGroupHandler = useCallback(() => {
|
||||
selectGroupFunc(group);
|
||||
}, [group, selectGroupFunc]);
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
onClick={selectGroupHandler}
|
||||
sx={{
|
||||
display: 'flex',
|
||||
background:
|
||||
group?.groupId === selectedGroup?.groupId &&
|
||||
theme.palette.action.selected,
|
||||
borderRadius: '2px',
|
||||
cursor: 'pointer',
|
||||
flexDirection: 'column',
|
||||
padding: '10px',
|
||||
width: '100%',
|
||||
'&:hover': {
|
||||
backgroundColor: 'action.hover', // background on hover
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ContextMenu getUserSettings={getUserSettings} groupId={group.groupId}>
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<ListItemAvatar>
|
||||
{ownerName ? (
|
||||
<Avatar
|
||||
alt={group?.groupName?.charAt(0)}
|
||||
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
|
||||
ownerName
|
||||
}/qortal_group_avatar_${group?.groupId}?async=true`}
|
||||
>
|
||||
{group?.groupName?.charAt(0).toUpperCase()}
|
||||
</Avatar>
|
||||
) : (
|
||||
<Avatar alt={group?.groupName?.charAt(0)}>
|
||||
{' '}
|
||||
{group?.groupName?.charAt(0).toUpperCase() || 'G'}
|
||||
</Avatar>
|
||||
)}
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
primary={group.groupId === '0' ? 'General' : group.groupName}
|
||||
secondary={
|
||||
!group?.timestamp
|
||||
? 'no messages'
|
||||
: `last message: ${formatEmailDate(group?.timestamp)}`
|
||||
}
|
||||
primaryTypographyProps={{
|
||||
style: {
|
||||
color:
|
||||
group?.groupId === selectedGroup?.groupId &&
|
||||
theme.palette.text.primary,
|
||||
fontSize: '16px',
|
||||
},
|
||||
}} // Change the color of the primary text
|
||||
secondaryTypographyProps={{
|
||||
style: {
|
||||
color:
|
||||
group?.groupId === selectedGroup?.groupId &&
|
||||
theme.palette.text.primary,
|
||||
fontSize: '12px',
|
||||
},
|
||||
}}
|
||||
sx={{
|
||||
width: '150px',
|
||||
fontFamily: 'Inter',
|
||||
fontSize: '16px',
|
||||
}}
|
||||
/>
|
||||
{announcement && !announcement?.seentimestamp && (
|
||||
<CampaignIcon
|
||||
sx={{
|
||||
color: theme.palette.other.unread,
|
||||
marginRight: '5px',
|
||||
marginBottom: 'auto',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '5px',
|
||||
justifyContent: 'flex-start',
|
||||
height: '100%',
|
||||
marginBottom: 'auto',
|
||||
}}
|
||||
>
|
||||
{group?.data &&
|
||||
groupChatTimestamp &&
|
||||
group?.sender !== myAddress &&
|
||||
group?.timestamp &&
|
||||
((!timestampEnterData &&
|
||||
Date.now() - group?.timestamp <
|
||||
timeDifferenceForNotificationChats) ||
|
||||
timestampEnterData < group?.timestamp) && (
|
||||
<MarkChatUnreadIcon
|
||||
sx={{
|
||||
color: theme.palette.other.unread,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{groupProperty?.isOpen === false && (
|
||||
<LockIcon
|
||||
sx={{
|
||||
color: theme.palette.other.positive,
|
||||
marginBottom: 'auto',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</ContextMenu>
|
||||
</ListItem>
|
||||
);
|
||||
}
|
||||
);
|
Loading…
x
Reference in New Issue
Block a user