Merge pull request #2 from nbenaglia/feature/remove-is-mobile-check

Remove is mobile check
This commit is contained in:
nico.benaz 2025-04-20 15:53:00 +02:00 committed by GitHub
commit d2e81fda44
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
40 changed files with 2038 additions and 2238 deletions

View File

@ -118,9 +118,7 @@ import {
import { useAppFullScreen } from './useAppFullscreen';
import { NotAuthenticated } from './ExtStates/NotAuthenticated';
import { handleGetFileFromIndexedDB } from './utils/indexedDB';
import { CoreSyncStatus } from './components/CoreSyncStatus';
import { Wallets } from './Wallets';
import { RandomSentenceGenerator } from './utils/seedPhrase/RandomSentenceGenerator';
import { useFetchResources } from './common/useFetchResources';
import { Tutorials } from './components/Tutorials/Tutorials';
import { useHandleTutorials } from './components/Tutorials/useHandleTutorials';
@ -182,28 +180,6 @@ const defaultValues: MyContextInterface = {
message: '',
},
};
export let isMobile = false;
const isMobileDevice = () => {
const userAgent = navigator.userAgent || navigator.vendor || window.opera;
if (/android/i.test(userAgent)) {
return true; // Android device
}
if (/iPad|iPhone|iPod/.test(userAgent) && !window.MSStream) {
return true; // iOS device
}
return false;
};
if (isMobileDevice()) {
isMobile = true;
console.log('Running on a mobile device');
} else {
console.log('Running on a desktop');
}
export const allQueues = {
requestQueueCommentCount: requestQueueCommentCount,
@ -436,16 +412,20 @@ function App() {
const [isOpenMinting, setIsOpenMinting] = useState(false);
const { toggleFullScreen } = useAppFullScreen(setFullScreen);
const generatorRef = useRef(null);
const exportSeedphrase = () => {
const seedPhrase = generatorRef.current.parsedString;
saveSeedPhraseToDisk(seedPhrase);
};
const passwordRef = useRef<HTMLInputElement>(null);
useEffect(() => {
if (extState === 'wallet-dropped' && passwordRef.current) {
passwordRef.current.focus();
}
}, [extState]);
useEffect(() => {
const isDevModeFromStorage = localStorage.getItem('isEnabledDevMode');
if (isDevModeFromStorage) {
@ -491,31 +471,38 @@ function App() {
);
};
}, [toggleFullScreen]);
//resets for recoil
const resetAtomSortablePinnedAppsAtom = useResetRecoilState(
sortablePinnedAppsAtom
);
const resetAtomIsUsingImportExportSettingsAtom = useResetRecoilState(
isUsingImportExportSettingsAtom
);
const resetAtomCanSaveSettingToQdnAtom = useResetRecoilState(
canSaveSettingToQdnAtom
);
const resetAtomSettingsQDNLastUpdatedAtom = useResetRecoilState(
settingsQDNLastUpdatedAtom
);
const resetAtomSettingsLocalLastUpdatedAtom = useResetRecoilState(
settingsLocalLastUpdatedAtom
);
const resetAtomOldPinnedAppsAtom = useResetRecoilState(oldPinnedAppsAtom);
const resetAtomQMailLastEnteredTimestampAtom = useResetRecoilState(
qMailLastEnteredTimestampAtom
);
const resetAtomMailsAtom = useResetRecoilState(mailsAtom);
const resetGroupPropertiesAtom = useResetRecoilState(groupsPropertiesAtom);
const resetLastPaymentSeenTimestampAtom = useResetRecoilState(
lastPaymentSeenTimestampAtom
);
const resetAllRecoil = () => {
resetAtomSortablePinnedAppsAtom();
resetAtomCanSaveSettingToQdnAtom();
@ -528,34 +515,11 @@ function App() {
resetGroupPropertiesAtom();
resetLastPaymentSeenTimestampAtom();
};
useEffect(() => {
if (!isMobile) return;
// Function to set the height of the app to the viewport height
const resetHeight = () => {
const height = window.visualViewport
? window.visualViewport.height
: window.innerHeight;
// Set the height to the root element (usually #root)
document.getElementById('root').style.height = height + 'px';
setRootHeight(height + 'px');
};
// Set the initial height
resetHeight();
// Add event listeners for resize and visualViewport changes
window.addEventListener('resize', resetHeight);
window.visualViewport?.addEventListener('resize', resetHeight);
// Clean up the event listeners when the component unmounts
return () => {
window.removeEventListener('resize', resetHeight);
window.visualViewport?.removeEventListener('resize', resetHeight);
};
}, []);
const handleSetGlobalApikey = (key) => {
globalApiKey = key;
};
useEffect(() => {
try {
setIsLoading(true);
@ -1232,14 +1196,6 @@ function App() {
// Handler for when the window gains focus
const handleFocus = () => {
setIsFocused(true);
if (isMobile) {
window.sendMessage('clearAllNotifications', {}).catch((error) => {
console.error(
'Failed to clear notifications:',
error.message || 'An error occurred'
);
});
}
};
// Handler for when the window loses focus
@ -1255,14 +1211,6 @@ function App() {
const handleVisibilityChange = () => {
if (document.visibilityState === 'visible') {
setIsFocused(true);
if (isMobile) {
window.sendMessage('clearAllNotifications', {}).catch((error) => {
console.error(
'Failed to clear notifications:',
error.message || 'An error occurred'
);
});
}
} else {
setIsFocused(false);
}
@ -1315,7 +1263,7 @@ function App() {
return (
<AuthenticatedContainerInnerLeft
sx={{
overflowY: isMobile && 'auto',
overflowY: 'auto',
padding: '0px 20px',
minWidth: '225px',
}}
@ -1568,27 +1516,9 @@ function App() {
backgroundColor: theme.palette.background.default,
display: 'flex',
justifyContent: 'flex-end',
width: isMobile ? '100vw' : 'auto',
width: 'auto',
}}
>
{isMobile && (
<Box
sx={{
display: 'flex',
justifyContent: 'flex-end',
padding: '10px',
}}
>
<CloseIcon
onClick={() => {
setIsOpenDrawerProfile(false);
}}
sx={{
cursor: 'pointer',
}}
/>
</Box>
)}
{desktopViewMode !== 'apps' &&
desktopViewMode !== 'dev' &&
desktopViewMode !== 'chat' && <>{renderProfileLeft()}</>}
@ -1609,51 +1539,46 @@ function App() {
>
<Spacer height="20px" />
{!isMobile && (
<>
<Spacer height="20px" />
<Tooltip
title={
<span
style={{
fontSize: '14px',
fontWeight: 700,
}}
>
LOG OUT
</span>
}
placement="left"
arrow
sx={{ fontSize: '24' }}
slotProps={{
tooltip: {
sx: {
color: theme.palette.text.primary,
backgroundColor: theme.palette.background.default,
},
},
arrow: {
sx: {
color: theme.palette.text.primary,
},
},
<Tooltip
title={
<span
style={{
fontSize: '14px',
fontWeight: 700,
}}
>
<Logout
style={{
cursor: 'pointer',
width: '20px',
height: 'auto',
}}
onClick={() => {
logoutFunc();
setIsOpenDrawerProfile(false);
}}
/>
</Tooltip>
</>
)}
LOG OUT
</span>
}
placement="left"
arrow
sx={{ fontSize: '24' }}
slotProps={{
tooltip: {
sx: {
color: theme.palette.text.primary,
backgroundColor: theme.palette.background.default,
},
},
arrow: {
sx: {
color: theme.palette.text.primary,
},
},
}}
>
<Logout
style={{
cursor: 'pointer',
width: '20px',
height: 'auto',
}}
onClick={() => {
logoutFunc();
setIsOpenDrawerProfile(false);
}}
/>
</Tooltip>
<Spacer height="20px" />
@ -2009,7 +1934,7 @@ function App() {
return (
<AppContainer
sx={{
height: isMobile ? '100%' : '100vh',
height: '100vh',
// backgroundImage: desktopViewMode === "apps" && 'url("appsBg.svg")',
// backgroundSize: desktopViewMode === "apps" && "cover",
// backgroundPosition: desktopViewMode === "apps" && "center",
@ -2075,51 +2000,50 @@ function App() {
>
<Box
sx={{
width: '100vw',
height: isMobile ? '100%' : '100vh',
display: 'flex',
flexDirection: isMobile ? 'column' : 'row',
overflow: isMobile && 'hidden',
flexDirection: 'row',
height: '100vh',
width: '100vw',
}}
>
<Group
logoutFunc={logoutFunc}
balance={balance}
userInfo={userInfo}
myAddress={address}
desktopViewMode={desktopViewMode}
isFocused={isFocused}
isMain={isMain}
isOpenDrawerProfile={isOpenDrawerProfile}
setIsOpenDrawerProfile={setIsOpenDrawerProfile}
desktopViewMode={desktopViewMode}
logoutFunc={logoutFunc}
myAddress={address}
setDesktopViewMode={setDesktopViewMode}
setIsOpenDrawerProfile={setIsOpenDrawerProfile}
userInfo={userInfo}
/>
{!isMobile && renderProfile()}
renderProfile()
</Box>
</MyContext.Provider>
)}
{isOpenSendQort && isMainWindow && (
<Box
sx={{
width: '100%',
height: '100%',
position: 'fixed',
alignItems: 'center',
background: theme.palette.background.default,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
height: '100%',
position: 'fixed',
width: '100%',
zIndex: 10000,
}}
>
<Spacer height="22px" />
<Box
sx={{
display: 'flex',
width: '100%',
justifyContent: 'flex-start',
paddingLeft: '22px',
boxSizing: 'border-box',
display: 'flex',
justifyContent: 'flex-start',
maxWidth: '700px',
paddingLeft: '22px',
width: '100%',
}}
>
<Return
@ -2130,7 +2054,9 @@ function App() {
onClick={returnToMain}
/>
</Box>
<Spacer height="35px" />
<QortPayment
balance={balance}
show={show}

View File

@ -0,0 +1,26 @@
import { useTheme } from '@mui/material';
import { SVGProps } from './interfaces';
export const Search: React.FC<SVGProps> = ({ color, opacity, ...children }) => {
const theme = useTheme();
const setColor = color ? color : theme.palette.text.primary;
const setOpacity = opacity ? opacity : 1;
return (
<svg
{...children}
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6.08728 0.00158245C2.72507 0.00158245 0 2.7262 0 6.08784C0 9.44948 2.72507 12.1741 6.08728 12.1741C7.62099 12.1741 9.02317 11.6043 10.0947 10.6668L13.3088 13.8803C13.3881 13.9596 13.4911 14 13.595 14C13.6988 14 13.8018 13.9596 13.8811 13.8803C14.0396 13.7218 14.0396 13.4643 13.8811 13.3066L10.667 10.093C11.6047 9.02162 12.1746 7.62202 12.1746 6.08626C12.1746 2.72461 9.44951 0 6.0873 0L6.08728 0.00158245ZM6.08728 11.3626C3.17756 11.3626 0.811637 8.99707 0.811637 6.08784C0.811637 3.17861 3.17756 0.813083 6.08728 0.813083C8.997 0.813083 11.3629 3.17861 11.3629 6.08784C11.3629 8.99707 8.997 11.3626 6.08728 11.3626Z"
fill={setColor}
opacity={setOpacity}
/>
</svg>
);
};

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

Before

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -1,5 +1,4 @@
// @ts-nocheck
import './qortalRequests';
import { isArray } from 'lodash';
import {
@ -30,7 +29,6 @@ import { RequestQueueWithPromise } from './utils/queue/queue';
import { validateAddress } from './utils/validateAddress';
import { Sha256 } from 'asmcrypto.js';
import { TradeBotRespondMultipleRequest } from './transactions/TradeBotRespondMultipleRequest';
import { RESOURCE_TYPE_NUMBER_GROUP_CHAT_REACTIONS } from './constants/resourceTypes';
import {
addDataPublishesCase,
@ -111,6 +109,7 @@ export let groupSecretkeys = {};
export function cleanUrl(url) {
return url?.replace(/^(https?:\/\/)?(www\.)?/, '');
}
export function getProtocol(url) {
if (url?.startsWith('https://')) {
return 'https';
@ -130,7 +129,6 @@ export const groupApiLocal = 'http://127.0.0.1:12391';
export const groupApiSocketLocal = 'ws://127.0.0.1:12391';
const timeDifferenceForNotificationChatsBackground = 86400000;
const requestQueueAnnouncements = new RequestQueueWithPromise(1);
let isMobile = true;
function handleNotificationClick(notificationId) {
// Decode the notificationId if it was encoded
@ -193,26 +191,6 @@ function handleNotificationClick(notificationId) {
}
}
const isMobileDevice = () => {
const userAgent = navigator.userAgent || navigator.vendor || window.opera;
if (/android/i.test(userAgent)) {
return true; // Android device
}
if (/iPad|iPhone|iPod/.test(userAgent) && !window.MSStream) {
return true; // iOS device
}
return false;
};
if (isMobileDevice()) {
isMobile = true;
console.log('Running on a mobile device');
} else {
console.log('Running on a desktop');
}
const allQueues = {
requestQueueAnnouncements: requestQueueAnnouncements,
};
@ -264,7 +242,9 @@ export const getForeignKey = async (foreignBlockchain) => {
};
export const pauseAllQueues = () => controlAllQueues('pause');
export const resumeAllQueues = () => controlAllQueues('resume');
export const checkDifference = (
createdTimestamp,
diff = timeDifferenceForNotificationChatsBackground
@ -302,6 +282,7 @@ export const getBaseApi = async (customApi?: string) => {
return groupApi;
}
};
export const isUsingLocal = async () => {
const apiKey = await getApiKeyFromStorage(); // Retrieve apiKey asynchronously
if (apiKey?.url) {
@ -345,13 +326,13 @@ const proxyAccountAddress = 'QXPejUe5Za1KD3zCMViWCX35AreMQ9H7ku';
const proxyAccountPublicKey = '5hP6stDWybojoDw5t8z9D51nV945oMPX7qBd29rhX1G7';
const pendingResponses = new Map();
let groups = null;
let socket;
let timeoutId;
let groupSocketTimeout;
let socketTimeout: any;
let interval;
let intervalThreads;
// Function to check each API endpoint
export async function findUsableApi() {
for (const endpoint of apiEndpoints) {
@ -435,6 +416,7 @@ async function checkWebviewFocus() {
window.addEventListener('message', handleMessage);
});
}
const worker = new ChatComputePowWorker();
export async function performPowTask(chatBytes, difficulty) {
@ -584,6 +566,7 @@ const handleNotificationDirect = async (directs) => {
setChatHeadsDirect(dataDirects);
}
};
async function getThreadActivity(): Promise<any | null> {
const wallet = await getSaveWallet();
const address = wallet.address0;
@ -852,6 +835,7 @@ export async function getNameInfoForOthers(address) {
return '';
}
}
export async function getAddressInfo(address) {
const validApi = await getBaseApi();
const response = await fetch(validApi + '/addresses/' + address);
@ -932,6 +916,7 @@ async function getTradeInfo(qortalAtAddress) {
const data = await response.json();
return data;
}
async function getTradesInfo(qortalAtAddresses) {
// Use Promise.all to fetch data for all addresses concurrently
const trades = await Promise.all(
@ -951,6 +936,7 @@ export async function getBalanceInfo() {
const data = await response.json();
return data;
}
export async function getLTCBalance() {
const wallet = await getSaveWallet();
let _url = `${buyTradeNodeBaseUrl}/crosschain/ltc/walletbalance`;
@ -1075,6 +1061,7 @@ const transaction = async (
data: res,
};
};
const makeTransactionRequest = async (
receiver,
lastRef,
@ -1113,6 +1100,7 @@ export const getLastRef = async () => {
const data = await response.text();
return data;
};
export const sendQortFee = async (): Promise<number> => {
const validApi = await getBaseApi();
const response = await fetch(
@ -1386,6 +1374,7 @@ export async function signChatFunc(
}
return response;
}
function sbrk(size, heap) {
let brk = 512 * 1024; // stack top
let old = brk;

View File

@ -2,7 +2,6 @@ import React, { useEffect, useMemo, useState } from 'react';
import {
AppCircle,
AppCircleContainer,
AppCircleLabel,
AppDownloadButton,
AppDownloadButtonText,
AppInfoAppName,
@ -17,14 +16,11 @@ import {
AppsCategoryInfoValue,
AppsInfoDescription,
AppsLibraryContainer,
AppsParent,
AppsWidthLimiter,
} from './Apps-styles';
import { Avatar, Box, ButtonBase, InputBase } from '@mui/material';
import { Add } from '@mui/icons-material';
import { getBaseApiReact, isMobile } from '../../App';
import { Avatar, Box } from '@mui/material';
import { getBaseApiReact } from '../../App';
import LogoSelected from '../../assets/svgs/LogoSelected.svg';
import { Spacer } from '../../common/Spacer';
import { executeEvent } from '../../utils/events';
import { AppRating } from './AppRating';
@ -51,9 +47,8 @@ export const AppInfo = ({ app, myName }) => {
return (
<AppsLibraryContainer
sx={{
height: !isMobile && '100%',
justifyContent: !isMobile && 'flex-start',
alignItems: isMobile && 'center',
height: '100%',
justifyContent: 'flex-start',
}}
>
<Box
@ -64,7 +59,8 @@ export const AppInfo = ({ app, myName }) => {
width: '90%',
}}
>
{!isMobile && <Spacer height="30px" />}
<Spacer height="30px" />
<AppsWidthLimiter>
<AppInfoSnippetContainer>
<AppInfoSnippetLeft
@ -172,15 +168,9 @@ export const AppInfo = ({ app, myName }) => {
}}
>
<AppDownloadButtonText>
{!isMobile ? (
<>
{isSelectedAppPinned
? 'Unpin from dashboard'
: 'Pin to dashboard'}
</>
) : (
<>{isSelectedAppPinned ? 'Unpin' : 'Pin'}</>
)}
{isSelectedAppPinned
? 'Unpin from dashboard'
: 'Pin to dashboard'}
</AppDownloadButtonText>
</AppDownloadButton>
<AppDownloadButton

View File

@ -12,9 +12,8 @@ import {
AppInfoUserName,
} from './Apps-styles';
import { Avatar, ButtonBase } from '@mui/material';
import { getBaseApiReact, isMobile } from '../../App';
import { getBaseApiReact } from '../../App';
import LogoSelected from '../../assets/svgs/LogoSelected.svg';
import { Spacer } from '../../common/Spacer';
import { executeEvent } from '../../utils/events';
import { AppRating } from './AppRating';
@ -124,52 +123,49 @@ export const AppInfoSnippet = ({
gap: '10px',
}}
>
{!isMobile && (
<AppDownloadButton
onClick={() => {
setSortablePinnedApps((prev) => {
let updatedApps;
<AppDownloadButton
onClick={() => {
setSortablePinnedApps((prev) => {
let updatedApps;
if (isSelectedAppPinned) {
// Remove the selected app if it is pinned
updatedApps = prev.filter(
(item) =>
!(
item?.name === app?.name &&
item?.service === app?.service
)
);
} else {
// Add the selected app if it is not pinned
updatedApps = [
...prev,
{
name: app?.name,
service: app?.service,
},
];
}
saveToLocalStorage(
'ext_saved_settings',
'sortablePinnedApps',
updatedApps
if (isSelectedAppPinned) {
// Remove the selected app if it is pinned
updatedApps = prev.filter(
(item) =>
!(
item?.name === app?.name && item?.service === app?.service
)
);
return updatedApps;
});
setSettingsLocalLastUpdated(Date.now());
}}
sx={{
backgroundColor: '#359ff7ff',
opacity: isSelectedAppPinned ? 0.6 : 1,
}}
>
<AppDownloadButtonText>
{' '}
{isSelectedAppPinned ? 'Unpin' : 'Pin'}
</AppDownloadButtonText>
</AppDownloadButton>
)}
} else {
// Add the selected app if it is not pinned
updatedApps = [
...prev,
{
name: app?.name,
service: app?.service,
},
];
}
saveToLocalStorage(
'ext_saved_settings',
'sortablePinnedApps',
updatedApps
);
return updatedApps;
});
setSettingsLocalLastUpdated(Date.now());
}}
sx={{
backgroundColor: '#359ff7ff',
opacity: isSelectedAppPinned ? 0.6 : 1,
}}
>
<AppDownloadButtonText>
{' '}
{isSelectedAppPinned ? 'Unpin' : 'Pin'}
</AppDownloadButtonText>
</AppDownloadButton>
<AppDownloadButton
onClick={() => {

View File

@ -1,4 +1,4 @@
import React, { useContext, useEffect, useMemo, useState } from "react";
import React, { useContext, useEffect, useState } from 'react';
import {
AppCircle,
AppCircleContainer,
@ -19,90 +19,74 @@ import {
PublishQAppCTAButton,
PublishQAppChoseFile,
PublishQAppInfo,
} from "./Apps-styles";
import {
Avatar,
Box,
ButtonBase,
InputBase,
InputLabel,
MenuItem,
Select,
} from "@mui/material";
import {
Select as BaseSelect,
SelectProps,
selectClasses,
SelectRootSlotProps,
} from "@mui/base/Select";
import { Option as BaseOption, optionClasses } from "@mui/base/Option";
import { styled } from "@mui/system";
import UnfoldMoreRoundedIcon from "@mui/icons-material/UnfoldMoreRounded";
import { Add } from "@mui/icons-material";
import { MyContext, getBaseApiReact, isMobile } from "../../App";
import LogoSelected from "../../assets/svgs/LogoSelected.svg";
import { Spacer } from "../../common/Spacer";
import { executeEvent } from "../../utils/events";
import { useDropzone } from "react-dropzone";
import { LoadingSnackbar } from "../Snackbar/LoadingSnackbar";
import { CustomizedSnackbars } from "../Snackbar/Snackbar";
import { getFee } from "../../background";
import { fileToBase64 } from "../../utils/fileReading";
} from './Apps-styles';
import { InputBase, InputLabel, MenuItem, Select } from '@mui/material';
import { styled } from '@mui/system';
import UnfoldMoreRoundedIcon from '@mui/icons-material/UnfoldMoreRounded';
import { Add } from '@mui/icons-material';
import { MyContext, getBaseApiReact } from '../../App';
import LogoSelected from '../../assets/svgs/LogoSelected.svg';
import { Spacer } from '../../common/Spacer';
import { executeEvent } from '../../utils/events';
import { useDropzone } from 'react-dropzone';
import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar';
import { CustomizedSnackbars } from '../Snackbar/Snackbar';
import { getFee } from '../../background';
import { fileToBase64 } from '../../utils/fileReading';
const CustomSelect = styled(Select)({
border: "0.5px solid var(--50-white, #FFFFFF80)",
padding: "0px 15px",
borderRadius: "5px",
height: "36px",
width: "100%",
maxWidth: "450px",
"& .MuiSelect-select": {
padding: "0px",
border: '0.5px solid var(--50-white, #FFFFFF80)',
padding: '0px 15px',
borderRadius: '5px',
height: '36px',
width: '100%',
maxWidth: '450px',
'& .MuiSelect-select': {
padding: '0px',
},
"&:hover": {
borderColor: "none", // Border color on hover
'&:hover': {
borderColor: 'none', // Border color on hover
},
"&.Mui-focused .MuiOutlinedInput-notchedOutline": {
borderColor: "none", // Border color when focused
'&.Mui-focused .MuiOutlinedInput-notchedOutline': {
borderColor: 'none', // Border color when focused
},
"&.Mui-disabled": {
'&.Mui-disabled': {
opacity: 0.5, // Lower opacity when disabled
},
"& .MuiSvgIcon-root": {
color: "var(--50-white, #FFFFFF80)",
'& .MuiSvgIcon-root': {
color: 'var(--50-white, #FFFFFF80)',
},
});
const CustomMenuItem = styled(MenuItem)({
backgroundColor: "#1f1f1f", // Background for dropdown items
color: "#ccc",
"&:hover": {
backgroundColor: "#333", // Darker background on hover
backgroundColor: '#1f1f1f', // Background for dropdown items
color: '#ccc',
'&:hover': {
backgroundColor: '#333', // Darker background on hover
},
});
export const AppPublish = ({ names, categories }) => {
const [name, setName] = useState("");
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
const [category, setCategory] = useState("");
const [appType, setAppType] = useState("APP");
const [name, setName] = useState('');
const [title, setTitle] = useState('');
const [description, setDescription] = useState('');
const [category, setCategory] = useState('');
const [appType, setAppType] = useState('APP');
const [file, setFile] = useState(null);
const { show } = useContext(MyContext);
const [tag1, setTag1] = useState("");
const [tag2, setTag2] = useState("");
const [tag3, setTag3] = useState("");
const [tag4, setTag4] = useState("");
const [tag5, setTag5] = useState("");
const [tag1, setTag1] = useState('');
const [tag2, setTag2] = useState('');
const [tag3, setTag3] = useState('');
const [tag4, setTag4] = useState('');
const [tag5, setTag5] = useState('');
const [openSnack, setOpenSnack] = useState(false);
const [infoSnack, setInfoSnack] = useState(null);
const [isLoading, setIsLoading] = useState("");
const maxFileSize = appType === "APP" ? 50 * 1024 * 1024 : 400 * 1024 * 1024; // 50MB or 400MB
const [isLoading, setIsLoading] = useState('');
const maxFileSize = appType === 'APP' ? 50 * 1024 * 1024 : 400 * 1024 * 1024; // 50MB or 400MB
const { getRootProps, getInputProps } = useDropzone({
accept: {
"application/zip": [".zip"], // Only accept zip files
'application/zip': ['.zip'], // Only accept zip files
},
maxSize: maxFileSize, // Set the max size based on appType
multiple: false, // Disable multiple file uploads
@ -114,7 +98,7 @@ export const AppPublish = ({ names, categories }) => {
onDropRejected: (fileRejections) => {
fileRejections.forEach(({ file, errors }) => {
errors.forEach((error) => {
if (error.code === "file-too-large") {
if (error.code === 'file-too-large') {
console.error(
`File ${file.name} is too large. Max size allowed is ${
maxFileSize / (1024 * 1024)
@ -128,13 +112,13 @@ export const AppPublish = ({ names, categories }) => {
const getQapp = React.useCallback(async (name, appType) => {
try {
setIsLoading("Loading app information");
setIsLoading('Loading app information');
const url = `${getBaseApiReact()}/arbitrary/resources/search?service=${appType}&mode=ALL&name=${name}&includemetadata=true`;
const response = await fetch(url, {
method: "GET",
method: 'GET',
headers: {
"Content-Type": "application/json",
'Content-Type': 'application/json',
},
});
if (!response?.ok) return;
@ -142,18 +126,18 @@ export const AppPublish = ({ names, categories }) => {
if (responseData?.length > 0) {
const myApp = responseData[0];
setTitle(myApp?.metadata?.title || "");
setDescription(myApp?.metadata?.description || "");
setCategory(myApp?.metadata?.category || "");
setTag1(myApp?.metadata?.tags[0] || "");
setTag2(myApp?.metadata?.tags[1] || "");
setTag3(myApp?.metadata?.tags[2] || "");
setTag4(myApp?.metadata?.tags[3] || "");
setTag5(myApp?.metadata?.tags[4] || "");
setTitle(myApp?.metadata?.title || '');
setDescription(myApp?.metadata?.description || '');
setCategory(myApp?.metadata?.category || '');
setTag1(myApp?.metadata?.tags[0] || '');
setTag2(myApp?.metadata?.tags[1] || '');
setTag3(myApp?.metadata?.tags[2] || '');
setTag4(myApp?.metadata?.tags[3] || '');
setTag5(myApp?.metadata?.tags[4] || '');
}
} catch (error) {
} finally {
setIsLoading("");
setIsLoading('');
}
}, []);
@ -173,12 +157,12 @@ export const AppPublish = ({ names, categories }) => {
file,
};
const requiredFields = [
"name",
"title",
"description",
"category",
"appType",
"file",
'name',
'title',
'description',
'category',
'appType',
'file',
];
const missingFields: string[] = [];
@ -188,32 +172,33 @@ export const AppPublish = ({ names, categories }) => {
}
});
if (missingFields.length > 0) {
const missingFieldsString = missingFields.join(", ");
const missingFieldsString = missingFields.join(', ');
const errorMsg = `Missing fields: ${missingFieldsString}`;
throw new Error(errorMsg);
}
const fee = await getFee("ARBITRARY");
const fee = await getFee('ARBITRARY');
await show({
message: "Would you like to publish this app?",
publishFee: fee.fee + " QORT",
message: 'Would you like to publish this app?',
publishFee: fee.fee + ' QORT',
});
setIsLoading("Publishing... Please wait.");
setIsLoading('Publishing... Please wait.');
const fileBase64 = await fileToBase64(file);
await new Promise((res, rej) => {
window.sendMessage("publishOnQDN", {
data: fileBase64,
service: appType,
title,
description,
category,
tag1,
tag2,
tag3,
tag4,
tag5,
uploadType: "zip",
})
window
.sendMessage('publishOnQDN', {
data: fileBase64,
service: appType,
title,
description,
category,
tag1,
tag2,
tag3,
tag4,
tag5,
uploadType: 'zip',
})
.then((response) => {
if (!response?.error) {
res(response);
@ -222,14 +207,13 @@ export const AppPublish = ({ names, categories }) => {
rej(response.error);
})
.catch((error) => {
rej(error.message || "An error occurred");
rej(error.message || 'An error occurred');
});
});
setInfoSnack({
type: "success",
type: 'success',
message:
"Successfully published. Please wait a couple minutes for the network to propogate the changes.",
'Successfully published. Please wait a couple minutes for the network to propogate the changes.',
});
setOpenSnack(true);
const dataObj = {
@ -242,35 +226,49 @@ export const AppPublish = ({ names, categories }) => {
},
created: Date.now(),
};
executeEvent("addTab", {
executeEvent('addTab', {
data: dataObj,
});
} catch (error) {
setInfoSnack({
type: "error",
message: error?.message || "Unable to publish app",
type: 'error',
message: error?.message || 'Unable to publish app',
});
setOpenSnack(true);
} finally {
setIsLoading("");
setIsLoading('');
}
};
return (
<AppsLibraryContainer sx={{
height: !isMobile ? '100%' : 'auto',
paddingTop: !isMobile && '30px',
alignItems: !isMobile && 'center'
}}>
<AppsWidthLimiter sx={{
width: !isMobile ? 'auto' : '90%'
}}>
<AppsLibraryContainer
sx={{
alignItems: 'center',
height: '100%',
paddingTop: '30px',
}}
>
<AppsWidthLimiter
sx={{
width: 'auto',
}}
>
<AppLibrarySubTitle>Create Apps!</AppLibrarySubTitle>
<Spacer height="18px" />
<PublishQAppInfo>
Note: Currently, only one App and Website is allowed per Name.
</PublishQAppInfo>
<Spacer height="18px" />
<InputLabel sx={{ color: '#888', fontSize: '14px', marginBottom: '2px' }}>Name/App</InputLabel>
<InputLabel
sx={{ color: '#888', fontSize: '14px', marginBottom: '2px' }}
>
Name/App
</InputLabel>
<CustomSelect
placeholder="Select Name/App"
displayEmpty
@ -280,19 +278,26 @@ export const AppPublish = ({ names, categories }) => {
<CustomMenuItem value="">
<em
style={{
color: "var(--50-white, #FFFFFF80)",
color: 'var(--50-white, #FFFFFF80)',
}}
>
Select Name/App
</em>{" "}
</em>{' '}
{/* This is the placeholder item */}
</CustomMenuItem>
{names.map((name) => {
return <CustomMenuItem value={name}>{name}</CustomMenuItem>;
})}
</CustomSelect>
<Spacer height="15px" />
<InputLabel sx={{ color: '#888', fontSize: '14px', marginBottom: '2px' }}>App service type</InputLabel>
<InputLabel
sx={{ color: '#888', fontSize: '14px', marginBottom: '2px' }}
>
App service type
</InputLabel>
<CustomSelect
placeholder="SERVICE TYPE"
displayEmpty
@ -302,59 +307,79 @@ export const AppPublish = ({ names, categories }) => {
<CustomMenuItem value="">
<em
style={{
color: "var(--50-white, #FFFFFF80)",
color: 'var(--50-white, #FFFFFF80)',
}}
>
Select App Type
</em>{" "}
</em>{' '}
{/* This is the placeholder item */}
</CustomMenuItem>
<CustomMenuItem value={"APP"}>App</CustomMenuItem>
<CustomMenuItem value={"WEBSITE"}>Website</CustomMenuItem>
<CustomMenuItem value={'APP'}>App</CustomMenuItem>
<CustomMenuItem value={'WEBSITE'}>Website</CustomMenuItem>
</CustomSelect>
<Spacer height="15px" />
<InputLabel sx={{ color: '#888', fontSize: '14px', marginBottom: '2px' }}>Title</InputLabel>
<InputLabel
sx={{ color: '#888', fontSize: '14px', marginBottom: '2px' }}
>
Title
</InputLabel>
<InputBase
value={title}
onChange={(e) => setTitle(e.target.value)}
sx={{
border: "0.5px solid var(--50-white, #FFFFFF80)",
padding: "0px 15px",
borderRadius: "5px",
height: "36px",
width: "100%",
maxWidth: "450px",
border: '0.5px solid var(--50-white, #FFFFFF80)',
padding: '0px 15px',
borderRadius: '5px',
height: '36px',
width: '100%',
maxWidth: '450px',
}}
placeholder="Title"
inputProps={{
"aria-label": "Title",
fontSize: "14px",
fontWeight: 400,
}}
/>
<Spacer height="15px" />
<InputLabel sx={{ color: '#888', fontSize: '14px', marginBottom: '2px' }}>Description</InputLabel>
<InputBase
value={description}
onChange={(e) => setDescription(e.target.value)}
sx={{
border: "0.5px solid var(--50-white, #FFFFFF80)",
padding: "0px 15px",
borderRadius: "5px",
height: "36px",
width: "100%",
maxWidth: "450px",
}}
placeholder="Description"
inputProps={{
"aria-label": "Description",
fontSize: "14px",
'aria-label': 'Title',
fontSize: '14px',
fontWeight: 400,
}}
/>
<Spacer height="15px" />
<InputLabel sx={{ color: '#888', fontSize: '14px', marginBottom: '2px' }}>Category</InputLabel>
<InputLabel
sx={{ color: '#888', fontSize: '14px', marginBottom: '2px' }}
>
Description
</InputLabel>
<InputBase
value={description}
onChange={(e) => setDescription(e.target.value)}
sx={{
border: '0.5px solid var(--50-white, #FFFFFF80)',
padding: '0px 15px',
borderRadius: '5px',
height: '36px',
width: '100%',
maxWidth: '450px',
}}
placeholder="Description"
inputProps={{
'aria-label': 'Description',
fontSize: '14px',
fontWeight: 400,
}}
/>
<Spacer height="15px" />
<InputLabel
sx={{ color: '#888', fontSize: '14px', marginBottom: '2px' }}
>
Category
</InputLabel>
<CustomSelect
displayEmpty
placeholder="Select Category"
@ -364,11 +389,11 @@ export const AppPublish = ({ names, categories }) => {
<CustomMenuItem value="">
<em
style={{
color: "var(--50-white, #FFFFFF80)",
color: 'var(--50-white, #FFFFFF80)',
}}
>
Select Category
</em>{" "}
</em>{' '}
{/* This is the placeholder item */}
</CustomMenuItem>
{categories?.map((category) => {
@ -379,23 +404,30 @@ export const AppPublish = ({ names, categories }) => {
);
})}
</CustomSelect>
<Spacer height="15px" />
<InputLabel sx={{ color: '#888', fontSize: '14px', marginBottom: '2px' }}>Tags</InputLabel>
<InputLabel
sx={{ color: '#888', fontSize: '14px', marginBottom: '2px' }}
>
Tags
</InputLabel>
<AppPublishTagsContainer>
<InputBase
value={tag1}
onChange={(e) => setTag1(e.target.value)}
sx={{
border: "0.5px solid var(--50-white, #FFFFFF80)",
padding: "0px 15px",
borderRadius: "5px",
height: "36px",
width: "100px",
border: '0.5px solid var(--50-white, #FFFFFF80)',
padding: '0px 15px',
borderRadius: '5px',
height: '36px',
width: '100px',
}}
placeholder="Tag 1"
inputProps={{
"aria-label": "Tag 1",
fontSize: "14px",
'aria-label': 'Tag 1',
fontSize: '14px',
fontWeight: 400,
}}
/>
@ -403,16 +435,16 @@ export const AppPublish = ({ names, categories }) => {
value={tag2}
onChange={(e) => setTag2(e.target.value)}
sx={{
border: "0.5px solid var(--50-white, #FFFFFF80)",
padding: "0px 15px",
borderRadius: "5px",
height: "36px",
width: "100px",
border: '0.5px solid var(--50-white, #FFFFFF80)',
padding: '0px 15px',
borderRadius: '5px',
height: '36px',
width: '100px',
}}
placeholder="Tag 2"
inputProps={{
"aria-label": "Tag 2",
fontSize: "14px",
'aria-label': 'Tag 2',
fontSize: '14px',
fontWeight: 400,
}}
/>
@ -420,16 +452,16 @@ export const AppPublish = ({ names, categories }) => {
value={tag3}
onChange={(e) => setTag3(e.target.value)}
sx={{
border: "0.5px solid var(--50-white, #FFFFFF80)",
padding: "0px 15px",
borderRadius: "5px",
height: "36px",
width: "100px",
border: '0.5px solid var(--50-white, #FFFFFF80)',
padding: '0px 15px',
borderRadius: '5px',
height: '36px',
width: '100px',
}}
placeholder="Tag 3"
inputProps={{
"aria-label": "Tag 3",
fontSize: "14px",
'aria-label': 'Tag 3',
fontSize: '14px',
fontWeight: 400,
}}
/>
@ -437,16 +469,16 @@ export const AppPublish = ({ names, categories }) => {
value={tag4}
onChange={(e) => setTag4(e.target.value)}
sx={{
border: "0.5px solid var(--50-white, #FFFFFF80)",
padding: "0px 15px",
borderRadius: "5px",
height: "36px",
width: "100px",
border: '0.5px solid var(--50-white, #FFFFFF80)',
padding: '0px 15px',
borderRadius: '5px',
height: '36px',
width: '100px',
}}
placeholder="Tag 4"
inputProps={{
"aria-label": "Tag 4",
fontSize: "14px",
'aria-label': 'Tag 4',
fontSize: '14px',
fontWeight: 400,
}}
/>
@ -454,27 +486,31 @@ export const AppPublish = ({ names, categories }) => {
value={tag5}
onChange={(e) => setTag5(e.target.value)}
sx={{
border: "0.5px solid var(--50-white, #FFFFFF80)",
padding: "0px 15px",
borderRadius: "5px",
height: "36px",
width: "100px",
border: '0.5px solid var(--50-white, #FFFFFF80)',
padding: '0px 15px',
borderRadius: '5px',
height: '36px',
width: '100px',
}}
placeholder="Tag 5"
inputProps={{
"aria-label": "Tag 5",
fontSize: "14px",
'aria-label': 'Tag 5',
fontSize: '14px',
fontWeight: 400,
}}
/>
</AppPublishTagsContainer>
<Spacer height="30px" />
<PublishQAppInfo>
Select .zip file containing static content:{" "}
Select .zip file containing static content:{' '}
</PublishQAppInfo>
<Spacer height="10px" />
<PublishQAppInfo>{`(${
appType === "APP" ? "50mb" : "400mb"
appType === 'APP' ? '50mb' : '400mb'
} MB maximum)`}</PublishQAppInfo>
{file && (
<>
@ -484,21 +520,25 @@ export const AppPublish = ({ names, categories }) => {
)}
<Spacer height="18px" />
<PublishQAppChoseFile {...getRootProps()}>
{" "}
{' '}
<input {...getInputProps()} />
Choose File
</PublishQAppChoseFile>
<Spacer height="35px" />
<PublishQAppCTAButton
sx={{
alignSelf: "center",
alignSelf: 'center',
}}
onClick={publishApp}
>
Publish
</PublishQAppCTAButton>
</AppsWidthLimiter>
<LoadingSnackbar
open={!!isLoading}
info={{
@ -512,7 +552,6 @@ export const AppPublish = ({ names, categories }) => {
info={infoSnack}
setInfo={setInfoSnack}
/>
</AppsLibraryContainer>
);
};

View File

@ -1,210 +1,249 @@
import React, { useContext, useEffect, useMemo, useState } from "react";
import React, { useContext, useEffect, useMemo, useState } from 'react';
import { Box } from '@mui/material';
import { MyContext, getBaseApiReact } from '../../App';
import { subscribeToEvent, unsubscribeFromEvent } from '../../utils/events';
import { useFrame } from 'react-frame-component';
import { useQortalMessageListener } from './useQortalMessageListener';
import { useThemeContext } from '../Theme/ThemeContext';
import { Box, } from "@mui/material";
import { MyContext, getBaseApiReact, isMobile } from "../../App";
export const AppViewer = React.forwardRef(
({ app, hide, isDevMode, skipAuth }, iframeRef) => {
const { rootHeight } = useContext(MyContext);
// const iframeRef = useRef(null);
const { window: frameWindow } = useFrame();
const { path, history, changeCurrentIndex, resetHistory } =
useQortalMessageListener(
frameWindow,
iframeRef,
app?.tabId,
isDevMode,
app?.name,
app?.service,
skipAuth
);
const [url, setUrl] = useState('');
const { themeMode } = useThemeContext();
import { subscribeToEvent, unsubscribeFromEvent } from "../../utils/events";
import { useFrame } from "react-frame-component";
import { useQortalMessageListener } from "./useQortalMessageListener";
import { useThemeContext } from "../Theme/ThemeContext";
useEffect(() => {
if (app?.isPreview) return;
if (isDevMode) {
setUrl(app?.url);
return;
}
let hasQueryParam = false;
if (app?.path && app.path.includes('?')) {
hasQueryParam = true;
}
setUrl(
`${getBaseApiReact()}/render/${app?.service}/${app?.name}${app?.path != null ? `/${app?.path}` : ''}${hasQueryParam ? '&' : '?'}theme=${themeMode}&identifier=${app?.identifier != null && app?.identifier != 'null' ? app?.identifier : ''}`
);
}, [app?.service, app?.name, app?.identifier, app?.path, app?.isPreview]);
useEffect(() => {
if (app?.isPreview && app?.url) {
resetHistory();
setUrl(app.url);
}
}, [app?.url, app?.isPreview]);
const defaultUrl = useMemo(() => {
return url;
}, [url, isDevMode]);
export const AppViewer = React.forwardRef(({ app , hide, isDevMode, skipAuth}, iframeRef) => {
const { rootHeight } = useContext(MyContext);
// const iframeRef = useRef(null);
const { window: frameWindow } = useFrame();
const {path, history, changeCurrentIndex, resetHistory} = useQortalMessageListener(frameWindow, iframeRef, app?.tabId, isDevMode, app?.name, app?.service, skipAuth)
const [url, setUrl] = useState('')
const { themeMode } = useThemeContext();
useEffect(()=> {
if(app?.isPreview) return
if(isDevMode){
setUrl(app?.url)
return
}
let hasQueryParam = false
if(app?.path && app.path.includes('?')){
hasQueryParam = true
}
setUrl(`${getBaseApiReact()}/render/${app?.service}/${app?.name}${app?.path != null ? `/${app?.path}` : ''}${hasQueryParam ? "&": "?" }theme=${themeMode}&identifier=${(app?.identifier != null && app?.identifier != 'null') ? app?.identifier : ''}`)
}, [app?.service, app?.name, app?.identifier, app?.path, app?.isPreview])
useEffect(()=> {
if(app?.isPreview && app?.url){
resetHistory()
setUrl(app.url)
}
}, [app?.url, app?.isPreview])
const defaultUrl = useMemo(()=> {
return url
}, [url, isDevMode])
const refreshAppFunc = (e) => {
const {tabId} = e.detail
if(tabId === app?.tabId){
if(isDevMode){
resetHistory()
if(!app?.isPreview || app?.isPrivate){
setUrl(app?.url + `?time=${Date.now()}`)
const refreshAppFunc = (e) => {
const { tabId } = e.detail;
if (tabId === app?.tabId) {
if (isDevMode) {
resetHistory();
if (!app?.isPreview || app?.isPrivate) {
setUrl(app?.url + `?time=${Date.now()}`);
}
return;
}
return
const constructUrl = `${getBaseApiReact()}/render/${app?.service}/${app?.name}${path != null ? path : ''}?theme=${themeMode}&identifier=${app?.identifier != null ? app?.identifier : ''}&time=${new Date().getMilliseconds()}`;
setUrl(constructUrl);
}
const constructUrl = `${getBaseApiReact()}/render/${app?.service}/${app?.name}${path != null ? path : ''}?theme=${themeMode}&identifier=${app?.identifier != null ? app?.identifier : ''}&time=${new Date().getMilliseconds()}`
setUrl(constructUrl)
}
};
useEffect(() => {
subscribeToEvent("refreshApp", refreshAppFunc);
return () => {
unsubscribeFromEvent("refreshApp", refreshAppFunc);
};
}, [app, path, isDevMode]);
useEffect(()=> {
if(!iframeRef?.current) return
const targetOrigin = iframeRef.current ? new URL(iframeRef.current.src).origin : "*";
// Send the navigation command after setting up the listener and timeout
iframeRef.current.contentWindow.postMessage(
{ action: 'THEME_CHANGED', theme: themeMode, requestedHandler: 'UI' }, targetOrigin
);
}, [themeMode])
useEffect(() => {
subscribeToEvent('refreshApp', refreshAppFunc);
const removeTrailingSlash = (str) => str.replace(/\/$/, '');
const copyLinkFunc = (e) => {
const {tabId} = e.detail
if(tabId === app?.tabId){
let link = 'qortal://' + app?.service + '/' + app?.name
if(path && path.startsWith('/')){
link = link + removeTrailingSlash(path)
}
if(path && !path.startsWith('/')){
link = link + '/' + removeTrailingSlash(path)
}
navigator.clipboard.writeText(link)
.then(() => {
console.log("Path copied to clipboard:", path);
})
.catch((error) => {
console.error("Failed to copy path:", error);
});
}
};
return () => {
unsubscribeFromEvent('refreshApp', refreshAppFunc);
};
}, [app, path, isDevMode]);
useEffect(() => {
subscribeToEvent("copyLink", copyLinkFunc);
return () => {
unsubscribeFromEvent("copyLink", copyLinkFunc);
};
}, [app, path]);
// Function to navigate back in iframe
const navigateBackInIframe = async () => {
if (iframeRef.current && iframeRef.current.contentWindow && history?.currentIndex > 0) {
// Calculate the previous index and path
const previousPageIndex = history.currentIndex - 1;
const previousPath = history.customQDNHistoryPaths[previousPageIndex];
const targetOrigin = iframeRef.current ? new URL(iframeRef.current.src).origin : "*";
// Signal non-manual navigation
iframeRef.current.contentWindow.postMessage(
{ action: 'PERFORMING_NON_MANUAL', currentIndex: previousPageIndex },targetOrigin
);
// Update the current index locally
changeCurrentIndex(previousPageIndex);
// Create a navigation promise with a 200ms timeout
const navigationPromise = new Promise((resolve, reject) => {
function handleNavigationSuccess(event) {
if (event.data?.action === 'NAVIGATION_SUCCESS' && event.data.path === previousPath) {
frameWindow.removeEventListener('message', handleNavigationSuccess);
resolve();
}
}
frameWindow.addEventListener('message', handleNavigationSuccess);
// Timeout after 200ms if no response
setTimeout(() => {
window.removeEventListener('message', handleNavigationSuccess);
reject(new Error("Navigation timeout"));
}, 200);
const targetOrigin = iframeRef.current ? new URL(iframeRef.current.src).origin : "*";
useEffect(() => {
if (!iframeRef?.current) return;
const targetOrigin = iframeRef.current
? new URL(iframeRef.current.src).origin
: '*';
// Send the navigation command after setting up the listener and timeout
iframeRef.current.contentWindow.postMessage(
{ action: 'NAVIGATE_TO_PATH', path: previousPath, requestedHandler: 'UI' }, targetOrigin
{ action: 'THEME_CHANGED', theme: themeMode, requestedHandler: 'UI' },
targetOrigin
);
});
}, [themeMode]);
// Execute navigation promise and handle timeout fallback
try {
await navigationPromise;
} catch (error) {
if(isDevMode){
setUrl(`${url}${previousPath != null ? previousPath : ''}?theme=${themeMode}&time=${new Date().getMilliseconds()}&isManualNavigation=false`)
return
}
setUrl(`${getBaseApiReact()}/render/${app?.service}/${app?.name}${previousPath != null ? previousPath : ''}?theme=${themeMode}&identifier=${(app?.identifier != null && app?.identifier != 'null') ? app?.identifier : ''}&time=${new Date().getMilliseconds()}&isManualNavigation=false`)
// iframeRef.current.contentWindow.location.href = previousPath; // Fallback URL update
}
} else {
console.log('Iframe not accessible or does not have a content window.');
}
};
const removeTrailingSlash = (str) => str.replace(/\/$/, '');
const navigateBackAppFunc = (e) => {
navigateBackInIframe()
};
useEffect(() => {
if(!app?.tabId) return
subscribeToEvent(`navigateBackApp-${app?.tabId}`, navigateBackAppFunc);
return () => {
unsubscribeFromEvent(`navigateBackApp-${app?.tabId}`, navigateBackAppFunc);
const copyLinkFunc = (e) => {
const { tabId } = e.detail;
if (tabId === app?.tabId) {
let link = 'qortal://' + app?.service + '/' + app?.name;
if (path && path.startsWith('/')) {
link = link + removeTrailingSlash(path);
}
if (path && !path.startsWith('/')) {
link = link + '/' + removeTrailingSlash(path);
}
navigator.clipboard
.writeText(link)
.then(() => {
console.log('Path copied to clipboard:', path);
})
.catch((error) => {
console.error('Failed to copy path:', error);
});
}
};
}, [app, history]);
useEffect(() => {
subscribeToEvent('copyLink', copyLinkFunc);
// Function to navigate back in iframe
const navigateForwardInIframe = async () => {
return () => {
unsubscribeFromEvent('copyLink', copyLinkFunc);
};
}, [app, path]);
if (iframeRef.current && iframeRef.current.contentWindow) {
const targetOrigin = iframeRef.current ? new URL(iframeRef.current.src).origin : "*";
iframeRef.current.contentWindow.postMessage(
{ action: 'NAVIGATE_FORWARD'},
// Function to navigate back in iframe
const navigateBackInIframe = async () => {
if (
iframeRef.current &&
iframeRef.current.contentWindow &&
history?.currentIndex > 0
) {
// Calculate the previous index and path
const previousPageIndex = history.currentIndex - 1;
const previousPath = history.customQDNHistoryPaths[previousPageIndex];
const targetOrigin = iframeRef.current
? new URL(iframeRef.current.src).origin
: '*';
// Signal non-manual navigation
iframeRef.current.contentWindow.postMessage(
{ action: 'PERFORMING_NON_MANUAL', currentIndex: previousPageIndex },
targetOrigin
);
} else {
console.log('Iframe not accessible or does not have a content window.');
);
// Update the current index locally
changeCurrentIndex(previousPageIndex);
// Create a navigation promise with a 200ms timeout
const navigationPromise = new Promise((resolve, reject) => {
function handleNavigationSuccess(event) {
if (
event.data?.action === 'NAVIGATION_SUCCESS' &&
event.data.path === previousPath
) {
frameWindow.removeEventListener(
'message',
handleNavigationSuccess
);
resolve();
}
}
frameWindow.addEventListener('message', handleNavigationSuccess);
// Timeout after 200ms if no response
setTimeout(() => {
window.removeEventListener('message', handleNavigationSuccess);
reject(new Error('Navigation timeout'));
}, 200);
const targetOrigin = iframeRef.current
? new URL(iframeRef.current.src).origin
: '*';
// Send the navigation command after setting up the listener and timeout
iframeRef.current.contentWindow.postMessage(
{
action: 'NAVIGATE_TO_PATH',
path: previousPath,
requestedHandler: 'UI',
},
targetOrigin
);
});
// Execute navigation promise and handle timeout fallback
try {
await navigationPromise;
} catch (error) {
if (isDevMode) {
setUrl(
`${url}${previousPath != null ? previousPath : ''}?theme=${themeMode}&time=${new Date().getMilliseconds()}&isManualNavigation=false`
);
return;
}
setUrl(
`${getBaseApiReact()}/render/${app?.service}/${app?.name}${previousPath != null ? previousPath : ''}?theme=${themeMode}&identifier=${app?.identifier != null && app?.identifier != 'null' ? app?.identifier : ''}&time=${new Date().getMilliseconds()}&isManualNavigation=false`
);
// iframeRef.current.contentWindow.location.href = previousPath; // Fallback URL update
}
} else {
console.log('Iframe not accessible or does not have a content window.');
}
};
const navigateBackAppFunc = (e) => {
navigateBackInIframe();
};
useEffect(() => {
if (!app?.tabId) return;
subscribeToEvent(`navigateBackApp-${app?.tabId}`, navigateBackAppFunc);
return () => {
unsubscribeFromEvent(
`navigateBackApp-${app?.tabId}`,
navigateBackAppFunc
);
};
}, [app, history]);
// Function to navigate back in iframe
const navigateForwardInIframe = async () => {
if (iframeRef.current && iframeRef.current.contentWindow) {
const targetOrigin = iframeRef.current
? new URL(iframeRef.current.src).origin
: '*';
iframeRef.current.contentWindow.postMessage(
{ action: 'NAVIGATE_FORWARD' },
targetOrigin
);
} else {
console.log('Iframe not accessible or does not have a content window.');
}
};
return (
<Box
sx={{
display: 'flex',
flexDirection: 'column',
}}
>
<iframe
ref={iframeRef}
style={{
height: '100vh',
border: 'none',
width: '100%',
}}
id="browser-iframe"
src={defaultUrl}
sandbox="allow-scripts allow-same-origin allow-forms allow-downloads allow-modals"
allow="fullscreen; clipboard-read; clipboard-write"
></iframe>
</Box>
);
}
};
return (
<Box sx={{
display: 'flex',
flexDirection: 'column',
}}>
<iframe ref={iframeRef} style={{
height: !isMobile ? '100vh' : `calc(${rootHeight} - 60px - 45px )`,
border: 'none',
width: '100%'
}} id="browser-iframe" src={defaultUrl} sandbox="allow-scripts allow-same-origin allow-forms allow-downloads allow-modals"
allow="fullscreen; clipboard-read; clipboard-write">
</iframe>
</Box>
);
});
);

View File

@ -1,20 +1,19 @@
import React, { useContext, } from 'react';
import React, { useContext } from 'react';
import { AppViewer } from './AppViewer';
import Frame from 'react-frame-component';
import { MyContext, isMobile } from '../../App';
import { MyContext } from '../../App';
const AppViewerContainer = React.forwardRef(({ app, isSelected, hide, isDevMode, customHeight, skipAuth }, ref) => {
const { rootHeight } = useContext(MyContext);
const AppViewerContainer = React.forwardRef(
({ app, isSelected, hide, isDevMode, customHeight, skipAuth }, ref) => {
const { rootHeight } = useContext(MyContext);
return (
<Frame
id={`browser-iframe-${app?.tabId}`}
head={
<>
<style>
{`
return (
<Frame
id={`browser-iframe-${app?.tabId}`}
head={
<>
<style>
{`
body {
margin: 0;
padding: 0;
@ -28,24 +27,31 @@ const AppViewerContainer = React.forwardRef(({ app, isSelected, hide, isDevMode,
}
.frame-content {
overflow: hidden;
height: ${!isMobile ? '100vh' : `calc(${rootHeight} - 60px - 45px)`};
height: '100vh';
}
`}
</style>
</>
}
style={{
position: (!isSelected || hide) && 'fixed',
left: (!isSelected || hide) && '-200vw',
height: customHeight ? customHeight : !isMobile ? '100vh' : `calc(${rootHeight} - 60px - 45px)`,
border: 'none',
width: '100%',
overflow: 'hidden',
}}
>
<AppViewer skipAuth={skipAuth} app={app} ref={ref} hide={!isSelected || hide} isDevMode={isDevMode} />
</Frame>
);
});
</style>
</>
}
style={{
border: 'none',
height: '100vh',
left: (!isSelected || hide) && '-200vw',
overflow: 'hidden',
position: (!isSelected || hide) && 'fixed',
width: '100%',
}}
>
<AppViewer
skipAuth={skipAuth}
app={app}
ref={ref}
hide={!isSelected || hide}
isDevMode={isDevMode}
/>
</Frame>
);
}
);
export default AppViewerContainer;

View File

@ -137,9 +137,9 @@ export const AppCircle = styled(Box)(({ theme }) => ({
theme.palette.mode === 'dark'
? 'rgb(209, 209, 209)'
: 'rgba(41, 41, 43, 1)',
borderWidth: '1px',
borderRadius: '50%',
borderStyle: 'solid',
borderWidth: '1px',
color: theme.palette.text.primary,
display: 'flex',
flexDirection: 'column',

View File

@ -22,7 +22,7 @@ import {
Input,
} from '@mui/material';
import { Add } from '@mui/icons-material';
import { MyContext, getBaseApiReact, isMobile } from '../../App';
import { MyContext, getBaseApiReact } from '../../App';
import LogoSelected from '../../assets/svgs/LogoSelected.svg';
import { executeEvent } from '../../utils/events';
import { Spacer } from '../../common/Spacer';
@ -279,7 +279,9 @@ export const AppsDevModeHome = ({
Dev Mode Apps
</AppLibrarySubTitle>
</AppsContainer>
<Spacer height="45px" />
<AppsContainer
sx={{
gap: '75px',
@ -293,7 +295,7 @@ export const AppsDevModeHome = ({
>
<AppCircleContainer
sx={{
gap: !isMobile ? '10px' : '5px',
gap: '10px',
}}
>
<AppCircle>
@ -302,6 +304,7 @@ export const AppsDevModeHome = ({
<AppCircleLabel>Server</AppCircleLabel>
</AppCircleContainer>
</ButtonBase>
<ButtonBase
onClick={() => {
addPreviewApp();
@ -309,15 +312,17 @@ export const AppsDevModeHome = ({
>
<AppCircleContainer
sx={{
gap: !isMobile ? '10px' : '5px',
gap: '10px',
}}
>
<AppCircle>
<Add>+</Add>
</AppCircle>
<AppCircleLabel>Zip</AppCircleLabel>
</AppCircleContainer>
</ButtonBase>
<ButtonBase
onClick={() => {
addPreviewAppWithDirectory();
@ -325,7 +330,7 @@ export const AppsDevModeHome = ({
>
<AppCircleContainer
sx={{
gap: !isMobile ? '10px' : '5px',
gap: '10px',
}}
>
<AppCircle>
@ -334,6 +339,7 @@ export const AppsDevModeHome = ({
<AppCircleLabel>Directory</AppCircleLabel>
</AppCircleContainer>
</ButtonBase>
<ButtonBase
onClick={() => {
executeEvent('appsDevModeAddTab', {
@ -347,7 +353,7 @@ export const AppsDevModeHome = ({
>
<AppCircleContainer
sx={{
gap: !isMobile ? '10px' : '5px',
gap: '10px',
}}
>
<AppCircle>
@ -371,9 +377,11 @@ export const AppsDevModeHome = ({
/>
</Avatar>
</AppCircle>
<AppCircleLabel>Q-Sandbox</AppCircleLabel>
</AppCircleContainer>
</ButtonBase>
<ButtonBase
onClick={() => {
executeEvent('appsDevModeAddTab', {
@ -387,7 +395,7 @@ export const AppsDevModeHome = ({
>
<AppCircleContainer
sx={{
gap: !isMobile ? '10px' : '5px',
gap: '10px',
}}
>
<AppCircle>
@ -411,10 +419,12 @@ export const AppsDevModeHome = ({
/>
</Avatar>
</AppCircle>
<AppCircleLabel>API</AppCircleLabel>
</AppCircleContainer>
</ButtonBase>
</AppsContainer>
{isShow && (
<Dialog
open={isShow}
@ -429,6 +439,7 @@ export const AppsDevModeHome = ({
<DialogTitle id="alert-dialog-title">
{'Add custom framework'}
</DialogTitle>
<DialogContent>
<Box
sx={{
@ -460,6 +471,7 @@ export const AppsDevModeHome = ({
/>
</Box>
</DialogContent>
<DialogActions>
<Button variant="contained" onClick={onCancel}>
Close

View File

@ -1,4 +1,4 @@
import React, { useState } from 'react';
import { useState } from 'react';
import {
AppCircle,
AppCircleContainer,
@ -8,7 +8,6 @@ import {
} from './Apps-styles';
import { Box, ButtonBase, Input, useTheme } from '@mui/material';
import { Add } from '@mui/icons-material';
import { isMobile } from '../../App';
import { executeEvent } from '../../utils/events';
import { Spacer } from '../../common/Spacer';
import { SortablePinnedApps } from './SortablePinnedApps';
@ -137,12 +136,13 @@ export const AppsHomeDesktop = ({
>
<AppCircleContainer
sx={{
gap: !isMobile ? '10px' : '5px',
gap: '10px',
}}
>
<AppCircle>
<Add>+</Add>
</AppCircle>
<AppCircleLabel>Library</AppCircleLabel>
</AppCircleContainer>
</ButtonBase>

View File

@ -1,11 +1,4 @@
import React, {
useCallback,
useContext,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { useEffect, useMemo, useRef, useState } from 'react';
import {
AppCircle,
AppCircleContainer,
@ -32,17 +25,14 @@ import {
Typography,
styled,
} from '@mui/material';
import { Add } from '@mui/icons-material';
import { MyContext, getBaseApiReact } from '../../App';
import { getBaseApiReact } from '../../App';
import LogoSelected from '../../assets/svgs/LogoSelected.svg';
import IconSearch from '../../assets/svgs/Search.svg';
import IconClearInput from '../../assets/svgs/ClearInput.svg';
import qappDevelopText from '../../assets/svgs/qappDevelopText.svg';
import qappLibraryText from '../../assets/svgs/qappLibraryText.svg';
import RefreshIcon from '@mui/icons-material/Refresh';
import qappDots from '../../assets/svgs/qappDots.svg';
import { Spacer } from '../../common/Spacer';
import { AppInfoSnippet } from './AppInfoSnippet';
import { Virtuoso } from 'react-virtuoso';
@ -57,6 +47,7 @@ import {
MailIconImg,
ShowMessageReturnButton,
} from '../Group/Forum/Mail-styles';
const officialAppList = [
'q-tube',
'q-blog',

View File

@ -6,7 +6,6 @@ import {
Dialog,
DialogActions,
DialogContent,
DialogTitle,
Input,
MenuItem,
Select,
@ -32,7 +31,7 @@ import {
PublishQAppInfo,
} from './Apps-styles';
import ImageUploader from '../../common/ImageUploader';
import { isMobile, MyContext } from '../../App';
import { MyContext } from '../../App';
import { fileToBase64 } from '../../utils/fileReading';
import { objectToBase64 } from '../../qdn/encryption/group-encryption';
import { getFee } from '../../background';
@ -67,6 +66,7 @@ export const AppsPrivate = ({ myName }) => {
(group) => groupsProperties[group?.groupId]?.isOpen === false
);
}, [memberGroups, groupsProperties]);
const [privateAppValues, setPrivateAppValues] = useState({
name: '',
service: 'DOCUMENT',
@ -230,7 +230,7 @@ export const AppsPrivate = ({ myName }) => {
>
<AppCircleContainer
sx={{
gap: !isMobile ? '10px' : '5px',
gap: '10px',
}}
>
<AppCircle>
@ -267,9 +267,8 @@ export const AppsPrivate = ({ myName }) => {
value={valueTabPrivateApp}
onChange={handleChange}
aria-label="basic tabs example"
variant={isMobile ? 'scrollable' : 'fullWidth'} // Scrollable on mobile, full width on desktop
variant={'fullWidth'}
scrollButtons="auto"
allowScrollButtonsMobile
sx={{
'& .MuiTabs-indicator': {
backgroundColor: theme.palette.background.default,
@ -283,7 +282,7 @@ export const AppsPrivate = ({ myName }) => {
'&.Mui-selected': {
color: theme.palette.text.primary,
},
fontSize: isMobile ? '0.75rem' : '1rem', // Adjust font size for mobile
fontSize: '1rem',
}}
/>
<Tab
@ -293,7 +292,7 @@ export const AppsPrivate = ({ myName }) => {
'&.Mui-selected': {
color: theme.palette.text.primary,
},
fontSize: isMobile ? '0.75rem' : '1rem', // Adjust font size for mobile
fontSize: '1rem',
}}
/>
</Tabs>

View File

@ -1,5 +1,5 @@
import { useContext, useEffect, useState } from 'react';
import { MyContext, isMobile } from '../../App';
import { MyContext } from '../../App';
import { Box, Typography } from '@mui/material';
import { AdminSpaceInner } from './AdminSpaceInner';
@ -29,10 +29,9 @@ export const AdminSpace = ({
return (
<div
style={{
// reference to change height
display: 'flex',
flexDirection: 'column',
height: isMobile ? `calc(${rootHeight} - 127px` : 'calc(100vh - 70px)',
height: 'calc(100vh - 70px)',
left: hide && '-1000px',
opacity: hide ? 0 : 1,
position: hide ? 'fixed' : 'relative',

View File

@ -21,7 +21,6 @@ import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import {
getArbitraryEndpointReact,
getBaseApiReact,
isMobile,
pauseAllQueues,
resumeAllQueues,
} from '../../App';
@ -56,12 +55,6 @@ export const AnnouncementDiscussion = ({
const clearEditorContent = () => {
if (editorRef.current) {
editorRef.current.chain().focus().clearContent().run();
if (isMobile) {
setTimeout(() => {
editorRef.current?.chain().blur().run();
setIsFocusedParent(false);
}, 200);
}
}
};
@ -278,7 +271,7 @@ export const AnnouncementDiscussion = ({
style={{
display: 'flex',
flexDirection: 'column',
height: isMobile ? '100%' : '100%',
height: '100%',
width: '100%',
}}
>

View File

@ -20,7 +20,6 @@ import { CustomizedSnackbars } from '../Snackbar/Snackbar';
import {
getBaseApiReact,
getBaseApiReactSocket,
isMobile,
pauseAllQueues,
resumeAllQueues,
} from '../../App';
@ -410,16 +409,6 @@ export const ChatDirect = ({
if (editorRef.current) {
setMessageSize(0);
editorRef.current.chain().focus().clearContent().run();
if (isMobile) {
setTimeout(() => {
editorRef.current?.chain().blur().run();
setIsFocusedParent(false);
executeEvent('sent-new-message-group', {});
setTimeout(() => {
triggerRerender();
}, 300);
}, 200);
}
}
};
useEffect(() => {
@ -547,108 +536,41 @@ export const ChatDirect = ({
background: theme.palette.background.default,
display: 'flex',
flexDirection: 'column',
height: isMobile ? '100%' : '100vh',
height: '100vh',
width: '100%',
}}
>
{!isMobile && (
<Box
onClick={close}
<Box
onClick={close}
sx={{
alignItems: 'center',
alignSelf: 'center',
background: theme.palette.background.default,
borderRadius: '3px',
cursor: 'pointer',
display: 'flex',
gap: '5px',
margin: '10px 0px',
padding: '4px 6px',
width: 'fit-content',
}}
>
<ArrowBackIcon
sx={{
alignItems: 'center',
alignSelf: 'center',
background: theme.palette.background.default,
borderRadius: '3px',
cursor: 'pointer',
display: 'flex',
gap: '5px',
margin: '10px 0px',
padding: '4px 6px',
width: 'fit-content',
color: theme.palette.text.primary,
fontSize: '20px',
}}
/>
<Typography
sx={{
color: theme.palette.text.primary,
fontSize: '14px',
}}
>
<ArrowBackIcon
sx={{
color: theme.palette.text.primary,
fontSize: '20px',
}}
/>
<Typography
sx={{
color: theme.palette.text.primary,
fontSize: '14px',
}}
>
Close Direct Chat
</Typography>
</Box>
)}
{isMobile && (
<Box
sx={{
alignItems: 'center',
display: 'flex',
height: '15px',
justifyContent: 'center',
marginTop: '14px',
width: '100%',
}}
>
<Box
sx={{
alignItems: 'center',
display: 'flex',
justifyContent: 'space-between',
width: '320px',
}}
>
<Box
sx={{
alignItems: 'center',
display: 'flex',
width: '50px',
}}
>
<ButtonBase
onClick={() => {
close();
}}
>
<ReturnIcon />
</ButtonBase>
</Box>
<Typography
sx={{
fontSize: '14px',
fontWeight: 600,
}}
>
{isNewChat
? ''
: selectedDirect?.name ||
selectedDirect?.address?.slice(0, 10) + '...'}
</Typography>
<Box
sx={{
alignItems: 'center',
display: 'flex',
justifyContent: 'flex-end',
width: '50px',
}}
>
<ButtonBase
onClick={() => {
setSelectedDirect(null);
setMobileViewModeKeepOpen('');
setNewChat(false);
}}
>
<ExitIcon />
</ButtonBase>
</Box>
</Box>
</Box>
)}
Close Direct Chat
</Typography>
</Box>
{isNewChat && (
<>
<Spacer height="30px" />
@ -685,7 +607,7 @@ export const ChatDirect = ({
flexShrink: 0,
minHeight: '150px',
overflow: 'hidden',
padding: isMobile ? '10px' : '20px',
padding: '20px',
position: isFocusedParent ? 'fixed' : 'relative',
top: isFocusedParent ? '0px' : 'unset',
width: '100%',
@ -753,7 +675,7 @@ export const ChatDirect = ({
setEditorRef={setEditorRef}
onEnter={sendMessage}
isChat
disableEnter={isMobile ? true : false}
disableEnter={false}
setIsFocusedParent={setIsFocusedParent}
/>
{messageSize > 750 && (

View File

@ -20,7 +20,6 @@ import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar';
import {
getBaseApiReact,
getBaseApiReactSocket,
isMobile,
MyContext,
pauseAllQueues,
resumeAllQueues,
@ -746,16 +745,6 @@ export const ChatGroup = ({
if (editorRef.current) {
setMessageSize(0);
editorRef.current.chain().focus().clearContent().run();
if (isMobile) {
setTimeout(() => {
editorRef.current?.chain().blur().run();
setIsFocusedParent(false);
executeEvent('sent-new-message-group', {});
setTimeout(() => {
triggerRerender();
}, 300);
}, 200);
}
}
};
@ -1109,7 +1098,7 @@ export const ChatGroup = ({
setEditorRef={setEditorRef}
onEnter={sendMessage}
isChat
disableEnter={isMobile ? true : false}
disableEnter={false}
isFocusedParent={isFocusedParent}
setIsFocusedParent={setIsFocusedParent}
membersWithNames={members}

View File

@ -26,7 +26,6 @@ import {
MyContext,
getArbitraryEndpointReact,
getBaseApiReact,
isMobile,
pauseAllQueues,
resumeAllQueues,
} from '../../App';
@ -262,15 +261,6 @@ export const GroupAnnouncements = ({
const clearEditorContent = () => {
if (editorRef.current) {
editorRef.current.chain().focus().clearContent().run();
if (isMobile) {
setTimeout(() => {
editorRef.current?.chain().blur().run();
setIsFocusedParent(false);
setTimeout(() => {
triggerRerender();
}, 300);
}, 200);
}
}
};
@ -529,12 +519,9 @@ export const GroupAnnouncements = ({
return (
<div
style={{
// reference to change height
height: isMobile
? `calc(${rootHeight} - 127px`
: 'calc(100vh - 70px)',
display: 'flex',
flexDirection: 'column',
height: 'calc(100vh - 70px)',
left: hide && '-1000px',
position: hide && 'fixed',
visibility: hide && 'hidden',
@ -576,26 +563,24 @@ export const GroupAnnouncements = ({
width: '100%',
}}
>
{!isMobile && (
<Box
<Box
sx={{
alignItems: 'center',
display: 'flex',
fontSize: '20px',
gap: '20px',
justifyContent: 'center',
padding: '25px',
width: '100%',
}}
>
<CampaignIcon
sx={{
alignItems: 'center',
display: 'flex',
fontSize: '20px',
gap: '20px',
justifyContent: 'center',
padding: '25px',
width: '100%',
fontSize: '30px',
}}
>
<CampaignIcon
sx={{
fontSize: '30px',
}}
/>
Group Announcements
</Box>
)}
/>
Group Announcements
</Box>
<Spacer height={'25px'} />
</div>

View File

@ -1,6 +1,6 @@
import { useContext, useEffect, useState } from 'react';
import { GroupMail } from '../Group/Forum/GroupMail';
import { MyContext, isMobile } from '../../App';
import { MyContext } from '../../App';
export const GroupForum = ({
selectedGroup,

View File

@ -23,7 +23,6 @@ import DeveloperModeIcon from '@mui/icons-material/DeveloperMode';
import Compressor from 'compressorjs';
import Mention from '@tiptap/extension-mention';
import ImageResize from 'tiptap-extension-resize-image'; // Import the ResizeImage extension
import { isMobile } from '../../App';
import tippy from 'tippy.js';
import 'tippy.js/dist/tippy.css';
import { ReactRenderer } from '@tiptap/react';
@ -137,7 +136,7 @@ const MenuBar = ({
color: editor.isActive('bold')
? theme.palette.text.primary
: theme.palette.text.secondary,
padding: isMobile ? '5px' : 'revert',
padding: 'revert',
}}
>
<FormatBoldIcon />
@ -149,7 +148,7 @@ const MenuBar = ({
color: editor.isActive('italic')
? theme.palette.text.primary
: theme.palette.text.secondary,
padding: isMobile ? '5px' : 'revert',
padding: 'revert',
}}
>
<FormatItalicIcon />
@ -161,7 +160,7 @@ const MenuBar = ({
color: editor.isActive('strike')
? theme.palette.text.primary
: theme.palette.text.secondary,
padding: isMobile ? '5px' : 'revert',
padding: 'revert',
}}
>
<StrikethroughSIcon />
@ -173,7 +172,7 @@ const MenuBar = ({
color: editor.isActive('code')
? theme.palette.text.primary
: theme.palette.text.secondary,
padding: isMobile ? '5px' : 'revert',
padding: 'revert',
}}
>
<CodeIcon />
@ -188,7 +187,7 @@ const MenuBar = ({
editor.isActive('code')
? theme.palette.text.primary
: theme.palette.text.secondary,
padding: isMobile ? '5px' : 'revert',
padding: 'revert',
}}
>
<FormatClearIcon />
@ -199,7 +198,7 @@ const MenuBar = ({
color: editor.isActive('bulletList')
? theme.palette.text.primary
: theme.palette.text.secondary,
padding: isMobile ? '5px' : 'revert',
padding: 'revert',
}}
>
<FormatListBulletedIcon />
@ -210,7 +209,7 @@ const MenuBar = ({
color: editor.isActive('orderedList')
? theme.palette.text.primary
: theme.palette.text.secondary,
padding: isMobile ? '5px' : 'revert',
padding: 'revert',
}}
>
<FormatListNumberedIcon />
@ -221,7 +220,7 @@ const MenuBar = ({
color: editor.isActive('codeBlock')
? theme.palette.text.primary
: theme.palette.text.secondary,
padding: isMobile ? '5px' : 'revert',
padding: 'revert',
}}
>
<DeveloperModeIcon />
@ -232,7 +231,7 @@ const MenuBar = ({
color: editor.isActive('blockquote')
? theme.palette.text.primary
: theme.palette.text.secondary,
padding: isMobile ? '5px' : 'revert',
padding: 'revert',
}}
>
<FormatQuoteIcon />
@ -240,7 +239,7 @@ const MenuBar = ({
<IconButton
onClick={() => editor.chain().focus().setHorizontalRule().run()}
disabled={!editor.can().chain().focus().setHorizontalRule().run()}
sx={{ color: 'gray', padding: isMobile ? '5px' : 'revert' }}
sx={{ color: 'gray', padding: 'revert' }}
>
<HorizontalRuleIcon />
</IconButton>
@ -252,7 +251,7 @@ const MenuBar = ({
color: editor.isActive('heading', { level: 1 })
? theme.palette.text.primary
: theme.palette.text.secondary,
padding: isMobile ? '5px' : 'revert',
padding: 'revert',
}}
>
<FormatHeadingIcon fontSize="small" />
@ -260,7 +259,7 @@ const MenuBar = ({
<IconButton
onClick={() => editor.chain().focus().undo().run()}
disabled={!editor.can().chain().focus().undo().run()}
sx={{ color: 'gray', padding: isMobile ? '5px' : 'revert' }}
sx={{ color: 'gray', padding: 'revert' }}
>
<UndoIcon />
</IconButton>
@ -313,7 +312,7 @@ const MenuBar = ({
onClick={triggerImageUpload}
sx={{
color: theme.palette.text.secondary,
padding: isMobile ? '5px' : 'revert',
padding: 'revert',
}}
>
<ImageIcon />
@ -398,11 +397,6 @@ export default ({
usersRef.current = users; // Keep users up-to-date
}, [users]);
const handleFocus = () => {
if (!isMobile) return;
setIsFocusedParent(true);
};
const handleBlur = () => {
const htmlContent = editorRef.current.getHTML();
if (!htmlContent?.trim() || htmlContent?.trim() === '<p></p>') {
@ -499,7 +493,7 @@ export default ({
>
<EditorProvider
slotBefore={
(isFocusedParent || !isMobile || overrideMobile) && (
(isFocusedParent || overrideMobile) && (
<MenuBar
setEditorRef={setEditorRefFunc}
isChat={isChat}
@ -511,21 +505,15 @@ export default ({
extensions={[...extensionsFiltered, ...additionalExtensions]}
content={content}
onCreate={({ editor }) => {
editor.on('focus', handleFocus); // Listen for focus event
editor.on('blur', handleBlur); // Listen for blur event
}}
onUpdate={({ editor }) => {
editor.on('focus', handleFocus); // Ensure focus is updated
editor.on('blur', handleBlur); // Ensure blur is updated
}}
editorProps={{
attributes: {
class: 'tiptap-prosemirror',
style: isMobile
? `overflow: auto; min-height: ${
customEditorHeight ? '200px' : '0px'
}; max-height:calc(100svh - ${customEditorHeight || '140px'})`
: `overflow: auto; max-height: 250px`,
style: `overflow: auto; max-height: 250px`,
},
handleKeyDown(view, event) {
if (

View File

@ -1,6 +1,5 @@
import Box from '@mui/material/Box';
import Drawer from '@mui/material/Drawer';
import { isMobile } from '../../App';
export const DrawerComponent = ({ open, setOpen, children }) => {
const toggleDrawer = (newOpen: boolean) => () => {
setOpen(newOpen);
@ -9,10 +8,7 @@ export const DrawerComponent = ({ open, setOpen, children }) => {
return (
<div>
<Drawer open={open} onClose={toggleDrawer(false)}>
<Box
sx={{ width: isMobile ? '100vw' : '400px', height: '100%' }}
role="presentation"
>
<Box sx={{ width: '400px', height: '100%' }} role="presentation">
{children}
</Box>
</Drawer>

View File

@ -1,23 +1,25 @@
import React, { useContext, useEffect, useMemo, useRef, useState } from 'react'
import ReactDOM from 'react-dom'
import { Box, IconButton, Slider } from '@mui/material'
import { CircularProgress, Typography } from '@mui/material'
import { Key } from 'ts-key-enum'
import React, { useContext, useEffect, useMemo, useRef, useState } from 'react';
import ReactDOM from 'react-dom';
import { Box, IconButton, Slider } from '@mui/material';
import { CircularProgress, Typography } from '@mui/material';
import { Key } from 'ts-key-enum';
import {
PlayArrow,
Pause,
VolumeUp,
Fullscreen,
PictureInPicture, VolumeOff, Calculate
} from '@mui/icons-material'
import { styled } from '@mui/system'
import { Refresh } from '@mui/icons-material'
PictureInPicture,
VolumeOff,
Calculate,
} from '@mui/icons-material';
import { styled } from '@mui/system';
import { Refresh } from '@mui/icons-material';
import { Menu, MenuItem } from '@mui/material'
import { MoreVert as MoreIcon } from '@mui/icons-material'
import { GlobalContext, getBaseApiReact } from '../../App'
import { resourceKeySelector } from '../../atoms/global'
import { useRecoilValue } from 'recoil'
import { Menu, MenuItem } from '@mui/material';
import { MoreVert as MoreIcon } from '@mui/icons-material';
import { GlobalContext, getBaseApiReact } from '../../App';
import { resourceKeySelector } from '../../atoms/global';
import { useRecoilValue } from 'recoil';
const VideoContainer = styled(Box)`
position: relative;
display: flex;
@ -28,14 +30,14 @@ const VideoContainer = styled(Box)`
height: 100%;
margin: 0px;
padding: 0px;
`
`;
const VideoElement = styled('video')`
width: 100%;
height: auto;
max-height: calc(100vh - 150px);
background: rgb(33, 33, 33);
`
`;
const ControlsContainer = styled(Box)`
position: absolute;
@ -47,18 +49,18 @@ const ControlsContainer = styled(Box)`
right: 0;
padding: 8px;
background-color: rgba(0, 0, 0, 0.6);
`
`;
interface VideoPlayerProps {
src?: string
poster?: string
name?: string
identifier?: string
service?: string
autoplay?: boolean
from?: string | null
customStyle?: any
user?: string
src?: string;
poster?: string;
name?: string;
identifier?: string;
service?: string;
autoplay?: boolean;
from?: string | null;
customStyle?: any;
user?: string;
}
export const VideoPlayer: React.FC<VideoPlayerProps> = ({
@ -69,33 +71,30 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
autoplay = true,
from = null,
customStyle = {},
node
node,
}) => {
const keyIdentifier = useMemo(()=> {
if(name && identifier && service){
return `${service}-${name}-${identifier}`
const keyIdentifier = useMemo(() => {
if (name && identifier && service) {
return `${service}-${name}-${identifier}`;
} else {
return undefined
return undefined;
}
}, [service, name, identifier])
}, [service, name, identifier]);
const download = useRecoilValue(resourceKeySelector(keyIdentifier));
const { downloadResource } = useContext(GlobalContext);
const videoRef = useRef<HTMLVideoElement | null>(null)
const [playing, setPlaying] = useState(false)
const [volume, setVolume] = useState(1)
const [mutedVolume, setMutedVolume] = useState(1)
const [isMuted, setIsMuted] = useState(false)
const [progress, setProgress] = useState(0)
const [isLoading, setIsLoading] = useState(false)
const [canPlay, setCanPlay] = useState(false)
const [startPlay, setStartPlay] = useState(false)
const [isMobileView, setIsMobileView] = useState(false)
const [playbackRate, setPlaybackRate] = useState(1)
const [anchorEl, setAnchorEl] = useState(null)
const reDownload = useRef<boolean>(false)
const videoRef = useRef<HTMLVideoElement | null>(null);
const [playing, setPlaying] = useState(false);
const [volume, setVolume] = useState(1);
const [mutedVolume, setMutedVolume] = useState(1);
const [isMuted, setIsMuted] = useState(false);
const [progress, setProgress] = useState(0);
const [isLoading, setIsLoading] = useState(false);
const [canPlay, setCanPlay] = useState(false);
const [startPlay, setStartPlay] = useState(false);
const [playbackRate, setPlaybackRate] = useState(1);
const [anchorEl, setAnchorEl] = useState(null);
const reDownload = useRef<boolean>(false);
const resetVideoState = () => {
// Reset all states to their initial values
@ -107,10 +106,9 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
setIsLoading(false);
setCanPlay(false);
setStartPlay(false);
setIsMobileView(false);
setPlaybackRate(1);
setAnchorEl(null);
// Reset refs to their initial values
if (videoRef.current) {
videoRef.current.pause(); // Ensure the video is paused
@ -120,18 +118,19 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
};
const src = useMemo(() => {
if(name && identifier && service){
return `${node || getBaseApiReact()}/arbitrary/${service}/${name}/${identifier}`
}
return ''
}, [service, name, identifier])
if (name && identifier && service) {
return `${node || getBaseApiReact()}/arbitrary/${service}/${name}/${identifier}`;
}
return '';
}, [service, name, identifier]);
useEffect(() => {
resetVideoState();
}, [keyIdentifier]);
useEffect(()=> {
resetVideoState()
}, [keyIdentifier])
const resourceStatus = useMemo(() => {
return download?.status || {}
}, [download])
return download?.status || {};
}, [download]);
const minSpeed = 0.25;
const maxSpeed = 4.0;
@ -139,306 +138,339 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
const updatePlaybackRate = (newSpeed: number) => {
if (videoRef.current) {
if (newSpeed > maxSpeed || newSpeed < minSpeed)
newSpeed = minSpeed
videoRef.current.playbackRate = newSpeed
setPlaybackRate(newSpeed)
if (newSpeed > maxSpeed || newSpeed < minSpeed) newSpeed = minSpeed;
videoRef.current.playbackRate = newSpeed;
setPlaybackRate(newSpeed);
}
}
};
const increaseSpeed = (wrapOverflow = true) => {
const changedSpeed = playbackRate + speedChange
let newSpeed = wrapOverflow ? changedSpeed : Math.min(changedSpeed, maxSpeed)
const changedSpeed = playbackRate + speedChange;
let newSpeed = wrapOverflow
? changedSpeed
: Math.min(changedSpeed, maxSpeed);
if (videoRef.current) {
updatePlaybackRate(newSpeed);
}
}
};
const decreaseSpeed = () => {
if (videoRef.current) {
updatePlaybackRate(playbackRate - speedChange);
}
}
};
const togglePlay = async () => {
if (!videoRef.current) return
setStartPlay(true)
if (!videoRef.current) return;
setStartPlay(true);
if (!src || resourceStatus?.status !== 'READY') {
ReactDOM.flushSync(() => {
setIsLoading(true)
})
getSrc()
setIsLoading(true);
});
getSrc();
}
if (playing) {
videoRef.current.pause()
videoRef.current.pause();
} else {
videoRef.current.play()
videoRef.current.play();
}
setPlaying(!playing)
}
setPlaying(!playing);
};
const onVolumeChange = (_: any, value: number | number[]) => {
if (!videoRef.current) return
videoRef.current.volume = value as number
setVolume(value as number)
setIsMuted(false)
}
if (!videoRef.current) return;
videoRef.current.volume = value as number;
setVolume(value as number);
setIsMuted(false);
};
const onProgressChange = (_: any, value: number | number[]) => {
if (!videoRef.current) return
videoRef.current.currentTime = value as number
setProgress(value as number)
if (!videoRef.current) return;
videoRef.current.currentTime = value as number;
setProgress(value as number);
if (!playing) {
videoRef.current.play()
setPlaying(true)
videoRef.current.play();
setPlaying(true);
}
}
};
const handleEnded = () => {
setPlaying(false)
}
setPlaying(false);
};
const updateProgress = () => {
if (!videoRef.current) return
setProgress(videoRef.current.currentTime)
}
if (!videoRef.current) return;
setProgress(videoRef.current.currentTime);
};
const [isFullscreen, setIsFullscreen] = useState(false)
const [isFullscreen, setIsFullscreen] = useState(false);
const enterFullscreen = () => {
if (!videoRef.current) return
if (!videoRef.current) return;
if (videoRef.current.requestFullscreen) {
videoRef.current.requestFullscreen()
videoRef.current.requestFullscreen();
}
}
};
const exitFullscreen = () => {
if (document.exitFullscreen) {
document.exitFullscreen()
document.exitFullscreen();
}
}
};
const toggleFullscreen = () => {
isFullscreen ? exitFullscreen() : enterFullscreen()
}
isFullscreen ? exitFullscreen() : enterFullscreen();
};
useEffect(() => {
const handleFullscreenChange = () => {
setIsFullscreen(!!document.fullscreenElement)
}
setIsFullscreen(!!document.fullscreenElement);
};
document.addEventListener('fullscreenchange', handleFullscreenChange)
document.addEventListener('fullscreenchange', handleFullscreenChange);
return () => {
document.removeEventListener('fullscreenchange', handleFullscreenChange)
}
}, [])
document.removeEventListener('fullscreenchange', handleFullscreenChange);
};
}, []);
const handleCanPlay = () => {
setIsLoading(false)
setCanPlay(true)
}
setIsLoading(false);
setCanPlay(true);
};
const getSrc = React.useCallback(async () => {
if (!name || !identifier || !service) return
if (!name || !identifier || !service) return;
try {
downloadResource({
name,
service,
identifier
})
} catch (error) {
console.error(error)
identifier,
});
} catch (error) {
console.error(error);
}
}, [identifier, name, service])
}, [identifier, name, service]);
function formatTime(seconds: number): string {
seconds = Math.floor(seconds)
let minutes: number | string = Math.floor(seconds / 60)
let hours: number | string = Math.floor(minutes / 60)
seconds = Math.floor(seconds);
let minutes: number | string = Math.floor(seconds / 60);
let hours: number | string = Math.floor(minutes / 60);
let remainingSeconds: number | string = seconds % 60
let remainingMinutes: number | string = minutes % 60
let remainingSeconds: number | string = seconds % 60;
let remainingMinutes: number | string = minutes % 60;
if (remainingSeconds < 10) {
remainingSeconds = '0' + remainingSeconds
remainingSeconds = '0' + remainingSeconds;
}
if (remainingMinutes < 10) {
remainingMinutes = '0' + remainingMinutes
remainingMinutes = '0' + remainingMinutes;
}
if (hours === 0) {
hours = ''
}
else {
hours = hours + ':'
hours = '';
} else {
hours = hours + ':';
}
return hours + remainingMinutes + ':' + remainingSeconds
return hours + remainingMinutes + ':' + remainingSeconds;
}
const reloadVideo = () => {
if (!videoRef.current) return
const currentTime = videoRef.current.currentTime
videoRef.current.src = src
videoRef.current.load()
videoRef.current.currentTime = currentTime
if (!videoRef.current) return;
const currentTime = videoRef.current.currentTime;
videoRef.current.src = src;
videoRef.current.load();
videoRef.current.currentTime = currentTime;
if (playing) {
videoRef.current.play()
videoRef.current.play();
}
}
};
useEffect(() => {
if (
resourceStatus?.status === 'DOWNLOADED' &&
reDownload?.current === false
) {
getSrc()
reDownload.current = true
getSrc();
reDownload.current = true;
}
}, [getSrc, resourceStatus])
}, [getSrc, resourceStatus]);
const handleMenuOpen = (event: any) => {
setAnchorEl(event.currentTarget)
}
setAnchorEl(event.currentTarget);
};
const handleMenuClose = () => {
setAnchorEl(null)
}
setAnchorEl(null);
};
useEffect(() => {
const videoWidth = videoRef?.current?.offsetWidth
if (videoWidth && videoWidth <= 600) {
setIsMobileView(true)
}
}, [canPlay])
const videoWidth = videoRef?.current?.offsetWidth;
}, [canPlay]);
const getDownloadProgress = (current: number, total: number) => {
const progress = current / total * 100;
return Number.isNaN(progress) ? '' : progress.toFixed(0) + '%'
}
const progress = (current / total) * 100;
return Number.isNaN(progress) ? '' : progress.toFixed(0) + '%';
};
const mute = () => {
setIsMuted(true)
setMutedVolume(volume)
setVolume(0)
if (videoRef.current) videoRef.current.volume = 0
}
setIsMuted(true);
setMutedVolume(volume);
setVolume(0);
if (videoRef.current) videoRef.current.volume = 0;
};
const unMute = () => {
setIsMuted(false)
setVolume(mutedVolume)
if (videoRef.current) videoRef.current.volume = mutedVolume
}
setIsMuted(false);
setVolume(mutedVolume);
if (videoRef.current) videoRef.current.volume = mutedVolume;
};
const toggleMute = () => {
isMuted ? unMute() : mute();
}
};
const changeVolume = (volumeChange: number) => {
if (videoRef.current) {
const minVolume = 0;
const maxVolume = 1;
let newVolume = volumeChange + volume;
let newVolume = volumeChange + volume
newVolume = Math.max(newVolume, minVolume);
newVolume = Math.min(newVolume, maxVolume);
newVolume = Math.max(newVolume, minVolume)
newVolume = Math.min(newVolume, maxVolume)
setIsMuted(false)
setMutedVolume(newVolume)
videoRef.current.volume = newVolume
setIsMuted(false);
setMutedVolume(newVolume);
videoRef.current.volume = newVolume;
setVolume(newVolume);
}
}
};
const setProgressRelative = (secondsChange: number) => {
if (videoRef.current) {
const currentTime = videoRef.current?.currentTime
const minTime = 0
const maxTime = videoRef.current?.duration || 100
const currentTime = videoRef.current?.currentTime;
const minTime = 0;
const maxTime = videoRef.current?.duration || 100;
let newTime = currentTime + secondsChange;
newTime = Math.max(newTime, minTime)
newTime = Math.min(newTime, maxTime)
newTime = Math.max(newTime, minTime);
newTime = Math.min(newTime, maxTime);
videoRef.current.currentTime = newTime;
setProgress(newTime);
}
}
};
const setProgressAbsolute = (videoPercent: number) => {
if (videoRef.current) {
videoPercent = Math.min(videoPercent, 100)
videoPercent = Math.max(videoPercent, 0)
const finalTime = videoRef.current?.duration * videoPercent / 100
videoRef.current.currentTime = finalTime
videoPercent = Math.min(videoPercent, 100);
videoPercent = Math.max(videoPercent, 0);
const finalTime = (videoRef.current?.duration * videoPercent) / 100;
videoRef.current.currentTime = finalTime;
setProgress(finalTime);
}
}
};
const keyboardShortcutsDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
e.preventDefault()
e.preventDefault();
switch (e.key) {
case Key.Add: increaseSpeed(false); break;
case '+': increaseSpeed(false); break;
case '>': increaseSpeed(false); break;
case Key.Add:
increaseSpeed(false);
break;
case '+':
increaseSpeed(false);
break;
case '>':
increaseSpeed(false);
break;
case Key.Subtract: decreaseSpeed(); break;
case '-': decreaseSpeed(); break;
case '<': decreaseSpeed(); break;
case Key.Subtract:
decreaseSpeed();
break;
case '-':
decreaseSpeed();
break;
case '<':
decreaseSpeed();
break;
case Key.ArrowLeft: {
if (e.shiftKey) setProgressRelative(-300);
else if (e.ctrlKey) setProgressRelative(-60);
else if (e.altKey) setProgressRelative(-10);
else setProgressRelative(-5);
} break;
case Key.ArrowLeft:
{
if (e.shiftKey) setProgressRelative(-300);
else if (e.ctrlKey) setProgressRelative(-60);
else if (e.altKey) setProgressRelative(-10);
else setProgressRelative(-5);
}
break;
case Key.ArrowRight: {
if (e.shiftKey) setProgressRelative(300);
else if (e.ctrlKey) setProgressRelative(60);
else if (e.altKey) setProgressRelative(10);
else setProgressRelative(5);
} break;
case Key.ArrowRight:
{
if (e.shiftKey) setProgressRelative(300);
else if (e.ctrlKey) setProgressRelative(60);
else if (e.altKey) setProgressRelative(10);
else setProgressRelative(5);
}
break;
case Key.ArrowDown: changeVolume(-0.05); break;
case Key.ArrowUp: changeVolume(0.05); break;
case Key.ArrowDown:
changeVolume(-0.05);
break;
case Key.ArrowUp:
changeVolume(0.05);
break;
}
}
};
const keyboardShortcutsUp = (e: React.KeyboardEvent<HTMLDivElement>) => {
e.preventDefault()
e.preventDefault();
switch (e.key) {
case ' ': togglePlay(); break;
case 'm': toggleMute(); break;
case ' ':
togglePlay();
break;
case 'm':
toggleMute();
break;
case 'f': enterFullscreen(); break;
case Key.Escape: exitFullscreen(); break;
case 'f':
enterFullscreen();
break;
case Key.Escape:
exitFullscreen();
break;
case '0': setProgressAbsolute(0); break;
case '1': setProgressAbsolute(10); break;
case '2': setProgressAbsolute(20); break;
case '3': setProgressAbsolute(30); break;
case '4': setProgressAbsolute(40); break;
case '5': setProgressAbsolute(50); break;
case '6': setProgressAbsolute(60); break;
case '7': setProgressAbsolute(70); break;
case '8': setProgressAbsolute(80); break;
case '9': setProgressAbsolute(90); break;
case '0':
setProgressAbsolute(0);
break;
case '1':
setProgressAbsolute(10);
break;
case '2':
setProgressAbsolute(20);
break;
case '3':
setProgressAbsolute(30);
break;
case '4':
setProgressAbsolute(40);
break;
case '5':
setProgressAbsolute(50);
break;
case '6':
setProgressAbsolute(60);
break;
case '7':
setProgressAbsolute(70);
break;
case '8':
setProgressAbsolute(80);
break;
case '9':
setProgressAbsolute(90);
break;
}
}
};
return (
<VideoContainer
@ -451,7 +483,6 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
height: '100%',
}}
>
{isLoading && (
<Box
position="absolute"
@ -467,40 +498,44 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
sx={{
display: 'flex',
flexDirection: 'column',
gap: '10px'
gap: '10px',
}}
>
<CircularProgress color="secondary" />
<Typography
variant="subtitle2"
component="div"
sx={{
color: 'white',
fontSize: '15px',
textAlign: 'center'
}}
>
{resourceStatus?.status === 'REFETCHING' ? (
<>
<>
{getDownloadProgress(resourceStatus?.localChunkCount, resourceStatus?.totalChunkCount)}
</>
<> Refetching data in 25 seconds</>
</>
) : resourceStatus?.status === 'DOWNLOADED' ? (
<>Download Completed: building tutorial video...</>
) : resourceStatus?.status !== 'READY' ? (
<Typography
variant="subtitle2"
component="div"
sx={{
color: 'white',
fontSize: '15px',
textAlign: 'center',
}}
>
{resourceStatus?.status === 'REFETCHING' ? (
<>
<>
{getDownloadProgress(resourceStatus?.localChunkCount || 0, resourceStatus?.totalChunkCount || 100)}
{getDownloadProgress(
resourceStatus?.localChunkCount,
resourceStatus?.totalChunkCount
)}
</>
) : (
<>Fetching tutorial from the Qortal Network...</>
)}
</Typography>
<> Refetching data in 25 seconds</>
</>
) : resourceStatus?.status === 'DOWNLOADED' ? (
<>Download Completed: building tutorial video...</>
) : resourceStatus?.status !== 'READY' ? (
<>
{getDownloadProgress(
resourceStatus?.localChunkCount || 0,
resourceStatus?.totalChunkCount || 100
)}
</>
) : (
<>Fetching tutorial from the Qortal Network...</>
)}
</Typography>
</Box>
)}
{((!src && !isLoading) || !startPlay) && (
@ -516,59 +551,61 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
zIndex={500}
bgcolor="rgba(0, 0, 0, 0.6)"
onClick={() => {
togglePlay()
togglePlay();
}}
sx={{
cursor: 'pointer'
cursor: 'pointer',
}}
>
<PlayArrow
sx={{
width: '50px',
height: '50px',
color: 'white'
color: 'white',
}}
/>
</Box>
)}
<Box sx={{
display: 'flex',
flexGrow: 1,
width: '100%',
height: 'calc(100% - 60px)',
}}>
<VideoElement
id={identifier}
ref={videoRef}
src={!startPlay ? '' : resourceStatus?.status === 'READY' ? src : ''}
poster={!startPlay ? poster : ""}
onTimeUpdate={updateProgress}
autoPlay={autoplay}
onClick={togglePlay}
onEnded={handleEnded}
// onLoadedMetadata={handleLoadedMetadata}
onCanPlay={handleCanPlay}
preload="metadata"
style={{
<Box
sx={{
display: 'flex',
flexGrow: 1,
width: '100%',
height: '100%',
...customStyle
height: 'calc(100% - 60px)',
}}
/>
>
<VideoElement
id={identifier}
ref={videoRef}
src={!startPlay ? '' : resourceStatus?.status === 'READY' ? src : ''}
poster={!startPlay ? poster : ''}
onTimeUpdate={updateProgress}
autoPlay={autoplay}
onClick={togglePlay}
onEnded={handleEnded}
// onLoadedMetadata={handleLoadedMetadata}
onCanPlay={handleCanPlay}
preload="metadata"
style={{
width: '100%',
height: '100%',
...customStyle,
}}
/>
</Box>
<ControlsContainer
sx={{
position: 'relative',
background: 'var(--bg-primary)',
width: '100%',
flexShrink: 0
flexShrink: 0,
}}
>
{isMobileView && canPlay ? (
{canPlay ? (
<>
<IconButton
sx={{
color: 'rgba(255, 255, 255, 0.7)'
color: 'rgba(255, 255, 255, 0.7)',
}}
onClick={togglePlay}
>
@ -577,77 +614,7 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
<IconButton
sx={{
color: 'rgba(255, 255, 255, 0.7)',
marginLeft: '15px'
}}
onClick={reloadVideo}
>
<Refresh />
</IconButton>
<Slider
value={progress}
onChange={onProgressChange}
min={0}
max={videoRef.current?.duration || 100}
sx={{ flexGrow: 1, mx: 2 }}
/>
<IconButton
edge="end"
color="inherit"
aria-label="menu"
onClick={handleMenuOpen}
>
<MoreIcon />
</IconButton>
<Menu
id="simple-menu"
anchorEl={anchorEl}
keepMounted
open={Boolean(anchorEl)}
onClose={handleMenuClose}
PaperProps={{
style: {
width: '250px'
}
}}
>
<MenuItem>
<VolumeUp />
<Slider
value={volume}
onChange={onVolumeChange}
min={0}
max={1}
step={0.01} />
</MenuItem>
<MenuItem onClick={() => increaseSpeed()}>
<Typography
sx={{
color: 'rgba(255, 255, 255, 0.7)',
fontSize: '14px'
}}
>
Speed: {playbackRate}x
</Typography>
</MenuItem>
<MenuItem onClick={toggleFullscreen}>
<Fullscreen />
</MenuItem>
</Menu>
</>
) : canPlay ? (
<>
<IconButton
sx={{
color: 'rgba(255, 255, 255, 0.7)'
}}
onClick={togglePlay}
>
{playing ? <Pause /> : <PlayArrow />}
</IconButton>
<IconButton
sx={{
color: 'rgba(255, 255, 255, 0.7)',
marginLeft: '15px'
marginLeft: '15px',
}}
onClick={reloadVideo}
>
@ -669,7 +636,7 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
!videoRef.current?.duration || !progress
? 'hidden'
: 'visible',
flexShrink: 0
flexShrink: 0,
}}
>
{progress && videoRef.current?.duration && formatTime(progress)}/
@ -680,7 +647,7 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
<IconButton
sx={{
color: 'rgba(255, 255, 255, 0.7)',
marginRight: '10px'
marginRight: '10px',
}}
onClick={toggleMute}
>
@ -694,14 +661,14 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
step={0.01}
sx={{
maxWidth: '100px',
color: 'var(--Mail-Background)'
color: 'var(--Mail-Background)',
}}
/>
<IconButton
sx={{
color: 'rgba(255, 255, 255, 0.7)',
fontSize: '14px',
marginLeft: '5px'
marginLeft: '5px',
}}
onClick={(e) => increaseSpeed()}
>
@ -709,7 +676,7 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
</IconButton>
<IconButton
sx={{
color: 'rgba(255, 255, 255, 0.7)'
color: 'rgba(255, 255, 255, 0.7)',
}}
onClick={toggleFullscreen}
>
@ -719,5 +686,5 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
) : null}
</ControlsContainer>
</VideoContainer>
)
}
);
};

View File

@ -26,7 +26,7 @@ import { AddGroupList } from './AddGroupList';
import { UserListOfInvites } from './UserListOfInvites';
import { CustomizedSnackbars } from '../Snackbar/Snackbar';
import { getFee } from '../../background';
import { MyContext, isMobile } from '../../App';
import { MyContext } from '../../App';
import { subscribeToEvent, unsubscribeFromEvent } from '../../utils/events';
export const Label = styled('label')`
@ -231,7 +231,7 @@ export const AddGroup = ({ address, open, setOpen }) => {
value={value}
onChange={handleChange}
aria-label="basic tabs example"
variant={isMobile ? 'scrollable' : 'fullWidth'} // Scrollable on mobile, full width on desktop
variant={'fullWidth'}
scrollButtons="auto"
allowScrollButtonsMobile
sx={{
@ -247,7 +247,7 @@ export const AddGroup = ({ address, open, setOpen }) => {
'&.Mui-selected': {
color: theme.palette.text.primary,
},
fontSize: isMobile ? '0.75rem' : '1rem', // Adjust font size for mobile
fontSize: '1rem',
}}
/>
<Tab
@ -257,7 +257,7 @@ export const AddGroup = ({ address, open, setOpen }) => {
'&.Mui-selected': {
color: theme.palette.text.primary,
},
fontSize: isMobile ? '0.75rem' : '1rem', // Adjust font size for mobile
fontSize: '1rem',
}}
/>
<Tab
@ -267,7 +267,7 @@ export const AddGroup = ({ address, open, setOpen }) => {
'&.Mui-selected': {
color: theme.palette.text.primary,
},
fontSize: isMobile ? '0.75rem' : '1rem', // Adjust font size for mobile
fontSize: '1rem',
}}
/>
</Tabs>

View File

@ -61,11 +61,7 @@ import {
unsubscribeFromEvent,
} from '../../../utils/events';
import RefreshIcon from '@mui/icons-material/Refresh';
import {
getArbitraryEndpointReact,
getBaseApiReact,
isMobile,
} from '../../../App';
import { getArbitraryEndpointReact, getBaseApiReact } from '../../../App';
import { WrapperUserAction } from '../../WrapperUserAction';
import { addDataPublishesFunc, getDataPublishesFunc } from '../Group';
const filterOptions = ['Recently active', 'Newest', 'Oldest'];
@ -754,7 +750,6 @@ export const GroupMail = ({
<ThreadSingleTitle
sx={{
fontWeight: shouldAppearLighter && 300,
fontSize: isMobile && '18px',
}}
>
{thread?.threadData?.title}

View File

@ -1,18 +1,9 @@
import React, { useEffect, useRef, useState } from 'react';
import {
Box,
Button,
CircularProgress,
Input,
Typography,
} from '@mui/material';
import { Box, CircularProgress, Input } from '@mui/material';
import ShortUniqueId from 'short-unique-id';
import CloseIcon from '@mui/icons-material/Close';
import ModalCloseSVG from '../../../assets/svgs/ModalClose.svg';
import ComposeIconSVG from '../../../assets/svgs/ComposeIcon.svg';
import {
AttachmentContainer,
CloseContainer,
@ -22,7 +13,6 @@ import {
InstanceFooter,
InstanceListContainer,
InstanceListHeader,
NewMessageAttachmentImg,
NewMessageCloseImg,
NewMessageHeaderP,
NewMessageInputRow,
@ -32,16 +22,9 @@ import {
import { ReusableModal } from './ReusableModal';
import { Spacer } from '../../../common/Spacer';
import { formatBytes } from '../../../utils/Size';
import { CreateThreadIcon } from '../../../assets/Icons/CreateThreadIcon';
import { SendNewMessage } from '../../../assets/Icons/SendNewMessage';
import { TextEditor } from './TextEditor';
import {
MyContext,
isMobile,
pauseAllQueues,
resumeAllQueues,
} from '../../../App';
import { MyContext, pauseAllQueues, resumeAllQueues } from '../../../App';
import { getFee } from '../../../background';
import TipTap from '../../Chat/TipTap';
import { MessageDisplay } from '../../Chat/MessageDisplay';
@ -411,8 +394,8 @@ export const NewThread = ({
>
<ComposeContainer
sx={{
padding: isMobile ? '5px' : '15px',
justifyContent: isMobile ? 'flex-start' : 'revert',
padding: '15px',
justifyContent: 'revert',
}}
onClick={() => setIsOpen(true)}
>
@ -423,7 +406,7 @@ export const NewThread = ({
<ReusableModal
open={isOpen}
customStyles={{
maxHeight: isMobile ? '95svh' : '95vh',
maxHeight: '95vh',
maxWidth: '950px',
height: '700px',
borderRadius: '12px 12px 0px 0px',
@ -434,8 +417,8 @@ export const NewThread = ({
>
<InstanceListHeader
sx={{
height: isMobile ? 'auto' : '50px',
padding: isMobile ? '5px' : '20px 42px',
height: '50px',
padding: '20px 42px',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
@ -457,7 +440,7 @@ export const NewThread = ({
<InstanceListContainer
sx={{
backgroundColor: '#434448',
padding: isMobile ? '5px' : '20px 42px',
padding: '20px 42px',
height: 'calc(100% - 165px)',
flexShrink: 0,
}}
@ -481,7 +464,7 @@ export const NewThread = ({
color: 'white',
'& .MuiInput-input::placeholder': {
color: 'rgba(255,255,255, 0.70) !important',
fontSize: isMobile ? '14px' : '20px',
fontSize: '20px',
fontStyle: 'normal',
fontWeight: 400,
lineHeight: '120%', // 24px
@ -509,7 +492,9 @@ export const NewThread = ({
<MessageDisplay htmlContent={postReply?.textContentV2} />
</Box>
)}
{!isMobile && <Spacer height="30px" />}
<Spacer height="30px" />
<Box
sx={{
maxHeight: '40vh',
@ -530,12 +515,13 @@ export const NewThread = ({
/> */}
</Box>
</InstanceListContainer>
<InstanceFooter
sx={{
backgroundColor: '#434448',
padding: isMobile ? '5px' : '20px 42px',
padding: '20px 42px',
alignItems: 'center',
height: isMobile ? 'auto' : '90px',
height: '90px',
}}
>
<NewMessageSendButton onClick={sendMail}>
@ -553,9 +539,11 @@ export const NewThread = ({
<CircularProgress sx={{}} size={'12px'} />
</Box>
)}
<NewMessageSendP>
{isMessage ? 'Post' : 'Create Thread'}
</NewMessageSendP>
{isMessage ? (
<SendNewMessage opacity={1} height="25px" width="25px" />
) : (
@ -564,6 +552,7 @@ export const NewThread = ({
</NewMessageSendButton>
</InstanceFooter>
</ReusableModal>
<CustomizedSnackbars
open={openSnack}
setOpen={setOpenSnack}

View File

@ -1,13 +1,12 @@
import React from 'react'
import { Box, Modal, useTheme } from '@mui/material'
import { isMobile } from '../../../App'
import React from 'react';
import { Box, Modal, useTheme } from '@mui/material';
interface MyModalProps {
open: boolean
onClose?: () => void
onSubmit?: (obj: any) => Promise<void>
children: any
customStyles?: any
open: boolean;
onClose?: () => void;
onSubmit?: (obj: any) => Promise<void>;
children: any;
customStyles?: any;
}
export const ReusableModal: React.FC<MyModalProps> = ({
@ -15,9 +14,10 @@ export const ReusableModal: React.FC<MyModalProps> = ({
onClose,
onSubmit,
children,
customStyles = {}
customStyles = {},
}) => {
const theme = useTheme()
const theme = useTheme();
return (
<Modal
open={open}
@ -32,27 +32,27 @@ export const ReusableModal: React.FC<MyModalProps> = ({
},
}}
disableAutoFocus
disableEnforceFocus
disableRestoreFocus
disableEnforceFocus
disableRestoreFocus
>
<Box
sx={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: isMobile ? '95%' : '75%',
bgcolor: theme.palette.primary.main,
boxShadow: 24,
p: 4,
display: 'flex',
flexDirection: 'column',
gap: 2,
...customStyles
left: '50%',
p: 4,
position: 'absolute',
top: '50%',
transform: 'translate(-50%, -50%)',
width: '75%',
...customStyles,
}}
>
{children}
</Box>
</Modal>
)
}
);
};

View File

@ -41,11 +41,7 @@ import {
import { LoadingSnackbar } from '../../Snackbar/LoadingSnackbar';
import { subscribeToEvent, unsubscribeFromEvent } from '../../../utils/events';
import RefreshIcon from '@mui/icons-material/Refresh';
import {
getArbitraryEndpointReact,
getBaseApiReact,
isMobile,
} from '../../../App';
import { getArbitraryEndpointReact, getBaseApiReact } from '../../../App';
import {
ArrowDownward as ArrowDownwardIcon,
ArrowUpward as ArrowUpwardIcon,
@ -602,23 +598,18 @@ export const Thread = ({
<Box
sx={{
display: 'flex',
gap: isMobile ? '45px' : '35px',
gap: '35px',
alignItems: 'center',
padding: isMobile && '5px',
}}
>
<ShowMessageReturnButton
sx={{
padding: isMobile && '5px',
minWidth: isMobile && '50px',
}}
onClick={() => {
setMessages([]);
closeThread();
}}
>
<MailIconImg src={ReturnSVG} />
{!isMobile && <ComposeP>Return to Threads</ComposeP>}
<ComposeP>Return to Threads</ComposeP>
</ShowMessageReturnButton>
{/* Conditionally render the scroll buttons */}
{showScrollButton &&
@ -628,7 +619,7 @@ export const Thread = ({
sx={{
color: 'white',
cursor: 'pointer',
fontSize: isMobile ? '28px' : '36px',
fontSize: '36px',
}}
/>
</ButtonBase>
@ -638,7 +629,7 @@ export const Thread = ({
sx={{
color: 'white',
cursor: 'pointer',
fontSize: isMobile ? '28px' : '36px',
fontSize: '36px',
}}
/>
</ButtonBase>
@ -655,38 +646,30 @@ export const Thread = ({
>
<div ref={threadBeginningRef} />
<ThreadContainer>
<Spacer height={isMobile ? '10px' : '30px'} />
<Spacer height={'30px'} />
<Box
sx={{
width: '100%',
alignItems: 'center',
display: 'flex',
justifyContent: 'space-between',
width: '100%',
}}
>
<GroupNameP
sx={{
fontSize: isMobile && '18px',
}}
>
{currentThread?.threadData?.title}
</GroupNameP>
<GroupNameP>{currentThread?.threadData?.title}</GroupNameP>
</Box>
<Spacer height={'15px'} />
<Box
sx={{
width: '100%',
alignItems: 'center',
display: 'flex',
justifyContent: 'center',
gap: '5px',
justifyContent: 'center',
width: '100%',
}}
>
<Button
sx={{
padding: isMobile && '5px',
fontSize: isMobile && '14px',
textTransformation: 'capitalize',
}}
onClick={() => {
@ -705,8 +688,6 @@ export const Thread = ({
</Button>
<Button
sx={{
padding: isMobile && '5px',
fontSize: isMobile && '14px',
textTransformation: 'capitalize',
}}
onClick={() => {
@ -725,8 +706,6 @@ export const Thread = ({
</Button>
<Button
sx={{
padding: isMobile && '5px',
fontSize: isMobile && '14px',
textTransformation: 'capitalize',
}}
onClick={() => {
@ -745,8 +724,6 @@ export const Thread = ({
</Button>
<Button
sx={{
padding: isMobile && '5px',
fontSize: isMobile && '14px',
textTransformation: 'capitalize',
}}
onClick={() => {
@ -764,7 +741,9 @@ export const Thread = ({
Last
</Button>
</Box>
<Spacer height={isMobile ? '10px' : '30px'} />
<Spacer height={'30px'} />
{combinedListTempAndReal.map((message, index, list) => {
let fullMessage = message;
@ -780,17 +759,17 @@ export const Thread = ({
>
<Box
style={{
width: '100%',
borderRadius: '8px',
flexDirection: 'column',
overflow: 'hidden',
position: 'relative',
flexDirection: 'column',
width: '100%',
}}
>
<Box
sx={{
display: 'flex',
alignItems: 'flex-start',
display: 'flex',
gap: '10px',
}}
>
@ -812,6 +791,7 @@ export const Thread = ({
{message?.name?.charAt(0)}
</Avatar>
</WrapperUserAction>
<ThreadInfoColumn>
<WrapperUserAction
disabled={userInfo?.name === message?.name}
@ -822,18 +802,20 @@ export const Thread = ({
{message?.name}
</ThreadInfoColumnNameP>
</WrapperUserAction>
<ThreadInfoColumnTime>
{formatTimestampForum(message?.created)}
</ThreadInfoColumnTime>
</ThreadInfoColumn>
</Box>
<Box
sx={{
width: '100%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
width: '100%',
}}
>
<Typography
@ -876,17 +858,17 @@ export const Thread = ({
>
<Box
style={{
width: '100%',
borderRadius: '8px',
flexDirection: 'column',
overflow: 'hidden',
position: 'relative',
flexDirection: 'column',
width: '100%',
}}
>
<Box
sx={{
display: 'flex',
alignItems: 'flex-start',
display: 'flex',
gap: '10px',
}}
>
@ -908,6 +890,7 @@ export const Thread = ({
{message?.name?.charAt(0)}
</Avatar>
</WrapperUserAction>
<ThreadInfoColumn>
<WrapperUserAction
disabled={userInfo?.name === message?.name}
@ -918,18 +901,20 @@ export const Thread = ({
{message?.name}
</ThreadInfoColumnNameP>
</WrapperUserAction>
<ThreadInfoColumnTime>
{formatTimestampForum(message?.created)}
</ThreadInfoColumnTime>
</ThreadInfoColumn>
</Box>
<Box
sx={{
width: '100%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
width: '100%',
}}
>
<CustomLoader />
@ -952,9 +937,9 @@ export const Thread = ({
<Spacer height="20px" />
<Box
sx={{
width: '100%',
display: 'flex',
justifyContent: 'flex-end',
width: '100%',
}}
>
<Button
@ -997,8 +982,6 @@ export const Thread = ({
>
<Button
sx={{
padding: isMobile && '5px',
fontSize: isMobile && '14px',
textTransformation: 'capitalize',
}}
onClick={() => {
@ -1017,8 +1000,6 @@ export const Thread = ({
</Button>
<Button
sx={{
padding: isMobile && '5px',
fontSize: isMobile && '14px',
textTransformation: 'capitalize',
}}
onClick={() => {
@ -1037,8 +1018,6 @@ export const Thread = ({
</Button>
<Button
sx={{
padding: isMobile && '5px',
fontSize: isMobile && '14px',
textTransformation: 'capitalize',
}}
onClick={() => {
@ -1057,8 +1036,6 @@ export const Thread = ({
</Button>
<Button
sx={{
padding: isMobile && '5px',
fontSize: isMobile && '14px',
textTransformation: 'capitalize',
}}
onClick={() => {

View File

@ -37,7 +37,6 @@ import {
clearAllQueues,
getArbitraryEndpointReact,
getBaseApiReact,
isMobile,
pauseAllQueues,
resumeAllQueues,
} from '../../App';
@ -1096,12 +1095,8 @@ export const Group = ({
return;
}
if (findDirect) {
if (!isMobile) {
setDesktopSideView('directs');
setDesktopViewMode('home');
} else {
setMobileViewModeKeepOpen('messaging');
}
setDesktopSideView('directs');
setDesktopViewMode('home');
setSelectedDirect(null);
setNewChat(false);
@ -1136,11 +1131,7 @@ export const Group = ({
);
if (findDirect) {
if (!isMobile) {
setDesktopSideView('directs');
} else {
setMobileViewModeKeepOpen('messaging');
}
setDesktopSideView('directs');
setSelectedDirect(null);
setNewChat(false);
@ -1162,11 +1153,7 @@ export const Group = ({
getTimestampEnterChat();
}, 200);
} else {
if (!isMobile) {
setDesktopSideView('directs');
} else {
setMobileViewModeKeepOpen('messaging');
}
setDesktopSideView('directs');
setNewChat(true);
setTimeout(() => {
executeEvent('setDirectToValueNewChat', {
@ -1284,9 +1271,7 @@ export const Group = ({
setupGroupWebsocketInterval.current = null;
settimeoutForRefetchSecretKey.current = null;
initiatedGetMembers.current = false;
if (!isMobile) {
setDesktopViewMode('home');
}
setDesktopViewMode('home');
};
const logoutEventFunc = () => {
@ -1303,34 +1288,7 @@ export const Group = ({
}, []);
const openAppsMode = () => {
if (isMobile) {
setMobileViewMode('apps');
}
if (!isMobile) {
setDesktopViewMode('apps');
}
if (isMobile) {
setIsOpenSideViewDirects(false);
setIsOpenSideViewGroups(false);
setGroupSection('default');
setSelectedGroup(null);
setNewChat(false);
setSelectedDirect(null);
setSecretKey(null);
setGroupOwner(null);
lastFetchedSecretKey.current = null;
initiatedGetMembers.current = false;
setSecretKeyPublishDate(null);
setAdmins([]);
setSecretKeyDetails(null);
setAdminsWithNames([]);
setMembers([]);
setMemberCountFromSecretKeyData(null);
setTriedToFetchSecretKey(false);
setFirstSecretKeyInCreation(false);
setIsOpenSideViewDirects(false);
setIsOpenSideViewGroups(false);
}
setDesktopViewMode('apps');
};
useEffect(() => {
@ -1372,9 +1330,7 @@ export const Group = ({
setTriedToFetchSecretKey(false);
setFirstSecretKeyInCreation(false);
setGroupSection('chat');
if (!isMobile) {
setDesktopViewMode('chat');
}
setDesktopViewMode('chat');
window
.sendMessage('addTimestampEnterChat', {
@ -1430,9 +1386,7 @@ export const Group = ({
setTriedToFetchSecretKey(false);
setFirstSecretKeyInCreation(false);
setGroupSection('announcement');
if (!isMobile) {
setDesktopViewMode('chat');
}
setDesktopViewMode('chat');
window
.sendMessage('addGroupNotificationTimestamp', {
timestamp: Date.now(),
@ -1496,9 +1450,7 @@ export const Group = ({
setFirstSecretKeyInCreation(false);
setGroupSection('forum');
setDefaultThread(data);
if (!isMobile) {
setDesktopViewMode('chat');
}
setDesktopViewMode('chat');
setTimeout(() => {
setSelectedGroup(findGroup);
setMobileViewMode('group');
@ -1521,12 +1473,6 @@ export const Group = ({
};
const goToHome = async () => {
if (isMobile) {
setMobileViewMode('home');
}
if (!isMobile) {
// TODO: empty block. Check it!
}
setDesktopViewMode('home');
await new Promise((res) => {
@ -1614,26 +1560,38 @@ export const Group = ({
borderRadius: '0px 15px 15px 0px',
display: 'flex',
flexDirection: 'column',
height: isMobile ? `calc(${rootHeight} - 45px)` : '100%',
width: isMobile ? '100%' : '380px',
height: '100%',
width: '380px',
}}
>
{!isMobile && (
<Box
sx={{
alignItems: 'center',
display: 'flex',
gap: '10px',
justifyContent: 'center',
width: '100%',
<Box
sx={{
alignItems: 'center',
display: 'flex',
gap: '10px',
justifyContent: 'center',
width: '100%',
}}
>
<ButtonBase
onClick={() => {
setDesktopSideView('groups');
}}
>
<ButtonBase
onClick={() => {
setDesktopSideView('groups');
}}
<IconWrapper
color={
groupChatHasUnread || groupsAnnHasUnread
? 'var(--unread)'
: desktopSideView === 'groups'
? theme.palette.text.primary
: theme.palette.text.secondary
}
label="Groups"
selected={desktopSideView === 'groups'}
customWidth="75px"
>
<IconWrapper
<HubsIcon
height={24}
color={
groupChatHasUnread || groupsAnnHasUnread
? 'var(--unread)'
@ -1641,29 +1599,28 @@ export const Group = ({
? theme.palette.text.primary
: theme.palette.text.secondary
}
label="Groups"
selected={desktopSideView === 'groups'}
customWidth="75px"
>
<HubsIcon
height={24}
color={
groupChatHasUnread || groupsAnnHasUnread
? 'var(--unread)'
: desktopSideView === 'groups'
? theme.palette.text.primary
: theme.palette.text.secondary
}
/>
</IconWrapper>
</ButtonBase>
<ButtonBase
onClick={() => {
setDesktopSideView('directs');
}}
/>
</IconWrapper>
</ButtonBase>
<ButtonBase
onClick={() => {
setDesktopSideView('directs');
}}
>
<IconWrapper
customWidth="75px"
color={
directChatHasUnread
? 'var(--unread)'
: desktopSideView === 'directs'
? theme.palette.text.primary
: theme.palette.text.secondary
}
label="Messaging"
selected={desktopSideView === 'directs'}
>
<IconWrapper
customWidth="75px"
<MessagingIcon
height={24}
color={
directChatHasUnread
? 'var(--unread)'
@ -1671,23 +1628,10 @@ export const Group = ({
? theme.palette.text.primary
: theme.palette.text.secondary
}
label="Messaging"
selected={desktopSideView === 'directs'}
>
<MessagingIcon
height={24}
color={
directChatHasUnread
? 'var(--unread)'
: desktopSideView === 'directs'
? theme.palette.text.primary
: theme.palette.text.secondary
}
/>
</IconWrapper>
</ButtonBase>
</Box>
)}
/>
</IconWrapper>
</ButtonBase>
</Box>
<div
style={{
@ -1843,30 +1787,42 @@ export const Group = ({
<div
style={{
display: 'flex',
width: isMobile ? '100%' : '380px',
width: '380px',
flexDirection: 'column',
alignItems: 'flex-start',
height: isMobile ? `calc(${rootHeight} - 45px)` : '100%',
background: !isMobile && theme.palette.background.default,
borderRadius: !isMobile && '0px 15px 15px 0px',
height: '100%',
background: theme.palette.background.default,
borderRadius: '0px 15px 15px 0px',
}}
>
{!isMobile && (
<Box
sx={{
width: '100%',
alignItems: 'center',
justifyContent: 'center',
display: 'flex',
gap: '10px',
<Box
sx={{
width: '100%',
alignItems: 'center',
justifyContent: 'center',
display: 'flex',
gap: '10px',
}}
>
<ButtonBase
onClick={() => {
setDesktopSideView('groups');
}}
>
<ButtonBase
onClick={() => {
setDesktopSideView('groups');
}}
<IconWrapper
color={
groupChatHasUnread || groupsAnnHasUnread
? 'var(--unread)'
: desktopSideView === 'groups'
? theme.palette.text.primary
: theme.palette.text.secondary
}
label="Groups"
selected={desktopSideView === 'groups'}
customWidth="75px"
>
<IconWrapper
<HubsIcon
height={24}
color={
groupChatHasUnread || groupsAnnHasUnread
? 'var(--unread)'
@ -1874,29 +1830,28 @@ export const Group = ({
? theme.palette.text.primary
: theme.palette.text.secondary
}
label="Groups"
selected={desktopSideView === 'groups'}
customWidth="75px"
>
<HubsIcon
height={24}
color={
groupChatHasUnread || groupsAnnHasUnread
? 'var(--unread)'
: desktopSideView === 'groups'
? theme.palette.text.primary
: theme.palette.text.secondary
}
/>
</IconWrapper>
</ButtonBase>
<ButtonBase
onClick={() => {
setDesktopSideView('directs');
}}
/>
</IconWrapper>
</ButtonBase>
<ButtonBase
onClick={() => {
setDesktopSideView('directs');
}}
>
<IconWrapper
customWidth="75px"
color={
directChatHasUnread
? 'var(--unread)'
: desktopSideView === 'directs'
? theme.palette.text.primary
: theme.palette.text.secondary
}
label="Messaging"
selected={desktopSideView === 'directs'}
>
<IconWrapper
customWidth="75px"
<MessagingIcon
height={24}
color={
directChatHasUnread
? 'var(--unread)'
@ -1904,23 +1859,10 @@ export const Group = ({
? theme.palette.text.primary
: theme.palette.text.secondary
}
label="Messaging"
selected={desktopSideView === 'directs'}
>
<MessagingIcon
height={24}
color={
directChatHasUnread
? 'var(--unread)'
: desktopSideView === 'directs'
? theme.palette.text.primary
: theme.palette.text.secondary
}
/>
</IconWrapper>
</ButtonBase>
</Box>
)}
/>
</IconWrapper>
</ButtonBase>
</Box>
<div
style={{
@ -2181,38 +2123,35 @@ export const Group = ({
alignItems: 'flex-start',
display: 'flex',
flexDirection: 'row',
height: isMobile ? '100%' : '100%',
height: '100%',
width: '100%',
}}
>
{!isMobile &&
((desktopViewMode !== 'apps' && desktopViewMode !== 'dev') ||
isOpenSideViewGroups) && (
<DesktopSideBar
desktopViewMode={desktopViewMode}
toggleSideViewGroups={toggleSideViewGroups}
toggleSideViewDirects={toggleSideViewDirects}
goToHome={goToHome}
mode={appsMode}
setMode={setAppsMode}
setDesktopSideView={setDesktopSideView}
hasUnreadDirects={directChatHasUnread}
isApps={desktopViewMode === 'apps'}
myName={userInfo?.name}
isGroups={isOpenSideViewGroups}
isDirects={isOpenSideViewDirects}
hasUnreadGroups={groupChatHasUnread || groupsAnnHasUnread}
setDesktopViewMode={setDesktopViewMode}
/>
)}
{((desktopViewMode !== 'apps' && desktopViewMode !== 'dev') ||
isOpenSideViewGroups) && (
<DesktopSideBar
desktopViewMode={desktopViewMode}
toggleSideViewGroups={toggleSideViewGroups}
toggleSideViewDirects={toggleSideViewDirects}
goToHome={goToHome}
mode={appsMode}
setMode={setAppsMode}
setDesktopSideView={setDesktopSideView}
hasUnreadDirects={directChatHasUnread}
isApps={desktopViewMode === 'apps'}
myName={userInfo?.name}
isGroups={isOpenSideViewGroups}
isDirects={isOpenSideViewDirects}
hasUnreadGroups={groupChatHasUnread || groupsAnnHasUnread}
setDesktopViewMode={setDesktopViewMode}
/>
)}
{!isMobile &&
desktopViewMode === 'chat' &&
{desktopViewMode === 'chat' &&
desktopSideView !== 'directs' &&
renderGroups()}
{!isMobile &&
desktopViewMode === 'chat' &&
{desktopViewMode === 'chat' &&
desktopSideView === 'directs' &&
renderDirects()}
@ -2231,66 +2170,10 @@ export const Group = ({
{newChat && (
<>
{isMobile && (
<Box
sx={{
alignItems: 'center',
display: 'flex',
height: '15px',
justifyContent: 'center',
marginTop: '14px',
width: '100%',
}}
>
<Box
sx={{
alignItems: 'center',
display: 'flex',
justifyContent: 'space-between',
width: '320px',
}}
>
<Box
sx={{
alignItems: 'center',
display: 'flex',
width: '50px',
}}
>
<ButtonBase
onClick={() => {
close();
}}
>
<ReturnIcon />
</ButtonBase>
</Box>
<Box
sx={{
alignItems: 'center',
display: 'flex',
justifyContent: 'flex-end',
width: '50px',
}}
>
<ButtonBase
onClick={() => {
setSelectedDirect(null);
setMobileViewModeKeepOpen('');
}}
>
<ExitIcon />
</ButtonBase>
</Box>
</Box>
</Box>
)}
<Box
sx={{
background: theme.palette.background.default,
bottom: !(desktopViewMode === 'chat') ? 'unset' : '0px',
height: isMobile && `calc(${rootHeight} - 45px)`,
left: !(desktopViewMode === 'chat') ? '-100000px' : '0px',
opacity: !(desktopViewMode === 'chat') ? 0 : 1,
position: 'absolute',
@ -2352,7 +2235,7 @@ export const Group = ({
: '0px',
}}
>
{!isMobile && (
{
<DesktopHeader
isPrivate={isPrivate}
selectedGroup={selectedGroup}
@ -2387,15 +2270,14 @@ export const Group = ({
setGroupSection={setGroupSection}
isForum={groupSection === 'forum'}
/>
)}
}
<Box
sx={{
position: 'relative',
flexGrow: 1,
display: 'flex',
// reference to change height
height: isMobile ? 'calc(100% - 82px)' : 'calc(100vh - 70px)',
flexGrow: 1,
height: 'calc(100vh - 70px)',
position: 'relative',
}}
>
{triedToFetchSecretKey && (
@ -2449,15 +2331,13 @@ export const Group = ({
(!secretKeyPublishDate && !firstSecretKeyInCreation) ? (
<div
style={{
display: 'flex',
width: '100%',
height: isMobile
? `calc(${rootHeight} - 113px)`
: 'calc(100vh - 70px)',
flexDirection: 'column',
alignItems: 'flex-start',
padding: '20px',
display: 'flex',
flexDirection: 'column',
height: 'calc(100vh - 70px)',
overflow: 'auto',
padding: '20px',
width: '100%',
}}
>
{' '}
@ -2605,7 +2485,6 @@ export const Group = ({
sx={{
background: theme.palette.background.default,
bottom: !(desktopViewMode === 'chat') ? 'unset' : '0px',
height: isMobile && `calc(${rootHeight} - 45px)`,
left: !(desktopViewMode === 'chat') ? '-100000px' : '0px',
opacity: !(desktopViewMode === 'chat') ? 0 : 1,
position: 'absolute',
@ -2642,7 +2521,7 @@ export const Group = ({
</>
)}
{!isMobile && (
{
<AppsDesktop
toggleSideViewGroups={toggleSideViewGroups}
toggleSideViewDirects={toggleSideViewDirects}
@ -2660,8 +2539,8 @@ export const Group = ({
isApps={desktopViewMode === 'apps'}
desktopViewMode={desktopViewMode}
/>
)}
{!isMobile && (
}
{
<AppsDevMode
toggleSideViewGroups={toggleSideViewGroups}
toggleSideViewDirects={toggleSideViewDirects}
@ -2679,9 +2558,9 @@ export const Group = ({
desktopViewMode={desktopViewMode}
isApps={desktopViewMode === 'apps'}
/>
)}
}
{!isMobile && (
{
<HomeDesktop
name={userInfo?.name}
refreshHomeDataFunc={refreshHomeDataFunc}
@ -2699,7 +2578,7 @@ export const Group = ({
setDesktopViewMode={setDesktopViewMode}
desktopViewMode={desktopViewMode}
/>
)}
}
</Box>
<AuthenticatedContainerInnerRight
@ -2708,7 +2587,6 @@ export const Group = ({
width: '31px',
padding: '5px',
display:
isMobile ||
desktopViewMode === 'apps' ||
desktopViewMode === 'dev' ||
desktopViewMode === 'chat'

View File

@ -9,7 +9,7 @@ import { executeEvent } from '../../utils/events';
import { Box, ButtonBase, Collapse, Typography, useTheme } from '@mui/material';
import { getGroupNames } from './UserListOfInvites';
import { CustomLoader } from '../../common/CustomLoader';
import { getBaseApiReact, isMobile } from '../../App';
import { getBaseApiReact } from '../../App';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
@ -97,7 +97,7 @@ export const GroupInvites = ({ myAddress, setOpenAddGroup }) => {
borderRadius: '19px',
display: 'flex',
flexDirection: 'column',
height: isMobile ? '165px' : '250px',
height: '250px',
padding: '20px',
width: '322px',
}}

View File

@ -1,244 +1,277 @@
import * as React from "react";
import List from "@mui/material/List";
import ListItem from "@mui/material/ListItem";
import ListItemButton from "@mui/material/ListItemButton";
import ListItemIcon from "@mui/material/ListItemIcon";
import ListItemText from "@mui/material/ListItemText";
import Checkbox from "@mui/material/Checkbox";
import IconButton from "@mui/material/IconButton";
import CommentIcon from "@mui/icons-material/Comment";
import InfoIcon from "@mui/icons-material/Info";
import { RequestQueueWithPromise } from "../../utils/queue/queue";
import * as React from 'react';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import ListItemButton from '@mui/material/ListItemButton';
import ListItemText from '@mui/material/ListItemText';
import IconButton from '@mui/material/IconButton';
import { RequestQueueWithPromise } from '../../utils/queue/queue';
import GroupAddIcon from '@mui/icons-material/GroupAdd';
import { executeEvent } from "../../utils/events";
import { Box, ButtonBase, Collapse, Typography } from "@mui/material";
import { Spacer } from "../../common/Spacer";
import { CustomLoader } from "../../common/CustomLoader";
import { getBaseApi } from "../../background";
import { MyContext, getBaseApiReact, isMobile } from "../../App";
import { myGroupsWhereIAmAdminAtom } from "../../atoms/global";
import { useSetRecoilState } from "recoil";
import { executeEvent } from '../../utils/events';
import { Box, ButtonBase, Collapse, Typography } from '@mui/material';
import { CustomLoader } from '../../common/CustomLoader';
import { MyContext, getBaseApiReact } from '../../App';
import { myGroupsWhereIAmAdminAtom } from '../../atoms/global';
import { useSetRecoilState } from 'recoil';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
export const requestQueueGroupJoinRequests = new RequestQueueWithPromise(2)
export const requestQueueGroupJoinRequests = new RequestQueueWithPromise(2);
export const GroupJoinRequests = ({ myAddress, groups, setOpenManageMembers, getTimestampEnterChat, setSelectedGroup, setGroupSection, setMobileViewMode, setDesktopViewMode }) => {
const [isExpanded, setIsExpanded] = React.useState(false)
const [groupsWithJoinRequests, setGroupsWithJoinRequests] = React.useState([])
const [loading, setLoading] = React.useState(true)
const {txList, setTxList} = React.useContext(MyContext)
const setMyGroupsWhereIAmAdmin = useSetRecoilState(
myGroupsWhereIAmAdminAtom
export const GroupJoinRequests = ({
myAddress,
groups,
setOpenManageMembers,
getTimestampEnterChat,
setSelectedGroup,
setGroupSection,
setMobileViewMode,
setDesktopViewMode,
}) => {
const [isExpanded, setIsExpanded] = React.useState(false);
const [groupsWithJoinRequests, setGroupsWithJoinRequests] = React.useState(
[]
);
const [loading, setLoading] = React.useState(true);
const { txList, setTxList } = React.useContext(MyContext);
const setMyGroupsWhereIAmAdmin = useSetRecoilState(myGroupsWhereIAmAdminAtom);
const getJoinRequests = async ()=> {
const getJoinRequests = async () => {
try {
setLoading(true)
let groupsAsAdmin = []
const getAllGroupsAsAdmin = groups.filter((item)=> item.groupId !== '0').map(async (group)=> {
const isAdminResponse = await requestQueueGroupJoinRequests.enqueue(()=> {
return fetch(
`${getBaseApiReact()}/groups/members/${group.groupId}?limit=0&onlyAdmins=true`
setLoading(true);
let groupsAsAdmin = [];
const getAllGroupsAsAdmin = groups
.filter((item) => item.groupId !== '0')
.map(async (group) => {
const isAdminResponse = await requestQueueGroupJoinRequests.enqueue(
() => {
return fetch(
`${getBaseApiReact()}/groups/members/${group.groupId}?limit=0&onlyAdmins=true`
);
}
);
})
const isAdminData = await isAdminResponse.json()
const isAdminData = await isAdminResponse.json();
const findMyself = isAdminData?.members?.find((member)=> member.member === myAddress)
if(findMyself){
groupsAsAdmin.push(group)
}
return true
})
const findMyself = isAdminData?.members?.find(
(member) => member.member === myAddress
);
await Promise.all(getAllGroupsAsAdmin)
setMyGroupsWhereIAmAdmin(groupsAsAdmin)
const res = await Promise.all(groupsAsAdmin.map(async (group)=> {
if (findMyself) {
groupsAsAdmin.push(group);
}
return true;
});
const joinRequestResponse = await requestQueueGroupJoinRequests.enqueue(()=> {
return fetch(
`${getBaseApiReact()}/groups/joinrequests/${group.groupId}`
);
})
await Promise.all(getAllGroupsAsAdmin);
setMyGroupsWhereIAmAdmin(groupsAsAdmin);
const res = await Promise.all(
groupsAsAdmin.map(async (group) => {
const joinRequestResponse =
await requestQueueGroupJoinRequests.enqueue(() => {
return fetch(
`${getBaseApiReact()}/groups/joinrequests/${group.groupId}`
);
});
const joinRequestData = await joinRequestResponse.json()
return {
group,
data: joinRequestData
}
}))
setGroupsWithJoinRequests(res)
const joinRequestData = await joinRequestResponse.json();
return {
group,
data: joinRequestData,
};
})
);
setGroupsWithJoinRequests(res);
} catch (error) {
} finally {
setLoading(false)
setLoading(false);
}
}
};
React.useEffect(() => {
if (myAddress && groups.length > 0) {
getJoinRequests()
getJoinRequests();
} else {
setLoading(false)
setLoading(false);
}
}, [myAddress, groups]);
const filteredJoinRequests = React.useMemo(()=> {
return groupsWithJoinRequests.map((group)=> {
const filteredGroupRequests = group?.data?.filter((gd)=> {
const findJoinRequsetInTxList = txList?.find((tx)=> tx?.groupId === group?.group?.groupId && tx?.qortalAddress === gd?.joiner && tx?.type === 'join-request-accept')
const filteredJoinRequests = React.useMemo(() => {
return groupsWithJoinRequests.map((group) => {
const filteredGroupRequests = group?.data?.filter((gd) => {
const findJoinRequsetInTxList = txList?.find(
(tx) =>
tx?.groupId === group?.group?.groupId &&
tx?.qortalAddress === gd?.joiner &&
tx?.type === 'join-request-accept'
);
if(findJoinRequsetInTxList) return false
return true
})
if (findJoinRequsetInTxList) return false;
return true;
});
return {
...group,
data: filteredGroupRequests
}
})
}, [groupsWithJoinRequests, txList])
data: filteredGroupRequests,
};
});
}, [groupsWithJoinRequests, txList]);
return (
<Box sx={{
width: "100%",
display: "flex",
flexDirection: "column",
alignItems: 'center'
}}>
<Box
sx={{
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
<ButtonBase
sx={{
width: "322px",
display: "flex",
flexDirection: "row",
width: '322px',
display: 'flex',
flexDirection: 'row',
padding: '0px 20px',
gap: '10px',
justifyContent: 'flex-start'
justifyContent: 'flex-start',
}}
onClick={()=> setIsExpanded((prev)=> !prev)}
onClick={() => setIsExpanded((prev) => !prev)}
>
<Typography
sx={{
fontSize: "1rem",
fontSize: '1rem',
}}
>
Join Requests {filteredJoinRequests?.filter((group)=> group?.data?.length > 0)?.length > 0 && ` (${filteredJoinRequests?.filter((group)=> group?.data?.length > 0)?.length})`}
Join Requests{' '}
{filteredJoinRequests?.filter((group) => group?.data?.length > 0)
?.length > 0 &&
` (${filteredJoinRequests?.filter((group) => group?.data?.length > 0)?.length})`}
</Typography>
{isExpanded ? <ExpandLessIcon sx={{
marginLeft: 'auto'
}} /> : (
<ExpandMoreIcon sx={{
marginLeft: 'auto'
}}/>
)}
{isExpanded ? (
<ExpandLessIcon
sx={{
marginLeft: 'auto',
}}
/>
) : (
<ExpandMoreIcon
sx={{
marginLeft: 'auto',
}}
/>
)}
</ButtonBase>
<Collapse in={isExpanded} timeout="auto" unmountOnExit>
<Box
sx={{
width: "322px",
height: isMobile ? "165px" : "250px",
display: "flex",
flexDirection: "column",
bgcolor: "background.paper",
padding: "20px",
borderRadius: '19px'
}}
>
{loading && filteredJoinRequests.length === 0 && (
<Box sx={{
width: '100%',
display: 'flex',
justifyContent: 'center'
}}>
<CustomLoader />
</Box>
)}
{!loading && (filteredJoinRequests.length === 0 || filteredJoinRequests?.filter((group)=> group?.data?.length > 0).length === 0) && (
<Box
sx={{
width: "100%",
display: "flex",
justifyContent: "center",
alignItems: 'center',
height: '100%',
}}
>
<Typography
sx={{
fontSize: "11px",
fontWeight: 400,
color: 'rgba(255, 255, 255, 0.2)'
}}
>
Nothing to display
</Typography>
</Box>
)}
<List className="scrollable-container" sx={{ width: "100%", maxWidth: 360, bgcolor: "background.paper", maxHeight: '300px', overflow: 'auto' }}>
{filteredJoinRequests?.map((group)=> {
if(group?.data?.length === 0) return null
return (
<ListItem
key={group?.groupId}
onClick={()=> {
setSelectedGroup(group?.group)
setMobileViewMode('group')
getTimestampEnterChat()
setGroupSection("announcement")
setOpenManageMembers(true)
if(!isMobile){
setDesktopViewMode('chat')
}
setTimeout(() => {
executeEvent("openGroupJoinRequest", {});
}, 300);
}}
<Collapse in={isExpanded} timeout="auto" unmountOnExit>
<Box
sx={{
marginBottom: '20px'
}}
disablePadding
secondaryAction={
<IconButton edge="end" aria-label="comments">
<GroupAddIcon
sx={{
color: "white",
fontSize: '18px'
}}
/>
</IconButton>
}
>
<ListItemButton sx={{
padding: "0px",
}} disableRipple role={undefined} dense>
<ListItemText sx={{
"& .MuiTypography-root": {
fontSize: "13px",
fontWeight: 400,
},
}} primary={`${group?.group?.groupName} has ${group?.data?.length} pending join requests.`} />
</ListItemButton>
</ListItem>
)
width: '322px',
height: '250px',
})}
</List>
</Box>
</Collapse>
display: 'flex',
flexDirection: 'column',
bgcolor: 'background.paper',
padding: '20px',
borderRadius: '19px',
}}
>
{loading && filteredJoinRequests.length === 0 && (
<Box
sx={{
width: '100%',
display: 'flex',
justifyContent: 'center',
}}
>
<CustomLoader />
</Box>
)}
{!loading &&
(filteredJoinRequests.length === 0 ||
filteredJoinRequests?.filter((group) => group?.data?.length > 0)
.length === 0) && (
<Box
sx={{
width: '100%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '100%',
}}
>
<Typography
sx={{
fontSize: '11px',
fontWeight: 400,
color: 'rgba(255, 255, 255, 0.2)',
}}
>
Nothing to display
</Typography>
</Box>
)}
<List
className="scrollable-container"
sx={{
width: '100%',
maxWidth: 360,
bgcolor: 'background.paper',
maxHeight: '300px',
overflow: 'auto',
}}
>
{filteredJoinRequests?.map((group) => {
if (group?.data?.length === 0) return null;
return (
<ListItem
key={group?.groupId}
onClick={() => {
setSelectedGroup(group?.group);
setMobileViewMode('group');
getTimestampEnterChat();
setGroupSection('announcement');
setOpenManageMembers(true);
setDesktopViewMode('chat');
setTimeout(() => {
executeEvent('openGroupJoinRequest', {});
}, 300);
}}
sx={{
marginBottom: '20px',
}}
disablePadding
secondaryAction={
<IconButton edge="end" aria-label="comments">
<GroupAddIcon
sx={{
color: 'white',
fontSize: '18px',
}}
/>
</IconButton>
}
>
<ListItemButton
sx={{
padding: '0px',
}}
disableRipple
role={undefined}
dense
>
<ListItemText
sx={{
'& .MuiTypography-root': {
fontSize: '13px',
fontWeight: 400,
},
}}
primary={`${group?.group?.groupName} has ${group?.data?.length} pending join requests.`}
/>
</ListItemButton>
</ListItem>
);
})}
</List>
</Box>
</Collapse>
</Box>
);
};

View File

@ -16,19 +16,12 @@ import {
DialogContent,
DialogContentText,
DialogTitle,
ListItem,
ListItemAvatar,
ListItemButton,
ListItemText,
MenuItem,
Popover,
Select,
TextField,
Typography,
} from '@mui/material';
import { getNameInfo } from './Group';
import { getBaseApi, getFee } from '../../background';
import { LoadingButton } from '@mui/lab';
import LockIcon from '@mui/icons-material/Lock';
import NoEncryptionGmailerrorredIcon from '@mui/icons-material/NoEncryptionGmailerrorred';
@ -36,7 +29,6 @@ import {
MyContext,
getArbitraryEndpointReact,
getBaseApiReact,
isMobile,
} from '../../App';
import { Spacer } from '../../common/Spacer';
import { CustomLoader } from '../../common/CustomLoader';
@ -51,7 +43,6 @@ import { Label } from './AddGroup';
import ShortUniqueId from 'short-unique-id';
import { CustomizedSnackbars } from '../Snackbar/Snackbar';
import { getGroupNames } from './UserListOfInvites';
import { WrapperUserAction } from '../WrapperUserAction';
import { useVirtualizer } from '@tanstack/react-virtual';
import ErrorBoundary from '../../common/ErrorBoundary';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
@ -390,6 +381,7 @@ export const ListOfGroupPromotions = () => {
/>
)}
</ButtonBase>
<Box
style={{
width: '330px',
@ -401,7 +393,7 @@ export const ListOfGroupPromotions = () => {
<>
<Box
sx={{
width: isMobile ? '320px' : '750px',
width: '750px',
maxWidth: '90%',
display: 'flex',
flexDirection: 'column',
@ -422,6 +414,7 @@ export const ListOfGroupPromotions = () => {
fontWeight: 600,
}}
></Typography>
<Button
variant="contained"
onClick={() => setIsShowModal(true)}
@ -432,18 +425,19 @@ export const ListOfGroupPromotions = () => {
Add Promotion
</Button>
</Box>
<Spacer height="10px" />
</Box>
<Box
sx={{
width: isMobile ? '320px' : '750px',
maxWidth: '90%',
maxHeight: '700px',
bgcolor: 'background.paper',
borderRadius: '19px',
display: 'flex',
flexDirection: 'column',
bgcolor: 'background.paper',
maxHeight: '700px',
maxWidth: '90%',
padding: '20px 0px',
borderRadius: '19px',
width: '750px',
}}
>
{loading && promotions.length === 0 && (
@ -589,6 +583,7 @@ export const ListOfGroupPromotions = () => {
>
Group name: {` ${promotion?.groupName}`}
</Typography>
<Typography
sx={{
fontSize: '13px',
@ -598,6 +593,7 @@ export const ListOfGroupPromotions = () => {
Number of members:{' '}
{` ${promotion?.memberCount}`}
</Typography>
{promotion?.description && (
<Typography
sx={{
@ -608,6 +604,7 @@ export const ListOfGroupPromotions = () => {
{promotion?.description}
</Typography>
)}
{promotion?.isOpen === false && (
<Typography
sx={{
@ -620,7 +617,9 @@ export const ListOfGroupPromotions = () => {
your request
</Typography>
)}
<Spacer height="5px" />
<Box
sx={{
display: 'flex',
@ -638,6 +637,7 @@ export const ListOfGroupPromotions = () => {
>
Close
</LoadingButton>
<LoadingButton
loading={isLoadingJoinGroup}
loadingPosition="start"
@ -682,6 +682,7 @@ export const ListOfGroupPromotions = () => {
>
{promotion?.name?.charAt(0)}
</Avatar>
<Typography
sx={{
fontWight: 600,
@ -692,6 +693,7 @@ export const ListOfGroupPromotions = () => {
{promotion?.name}
</Typography>
</Box>
<Typography
sx={{
fontWight: 600,
@ -702,7 +704,9 @@ export const ListOfGroupPromotions = () => {
{promotion?.groupName}
</Typography>
</Box>
<Spacer height="20px" />
<Box
sx={{
display: 'flex',
@ -735,7 +739,9 @@ export const ListOfGroupPromotions = () => {
: 'Private group'}
</Typography>
</Box>
<Spacer height="20px" />
<Typography
sx={{
fontWight: 600,
@ -745,7 +751,9 @@ export const ListOfGroupPromotions = () => {
>
{promotion?.data}
</Typography>
<Spacer height="20px" />
<Box
sx={{
display: 'flex',
@ -767,6 +775,7 @@ export const ListOfGroupPromotions = () => {
</Button>
</Box>
</Box>
<Spacer height="50px" />
</ErrorBoundary>
</div>
@ -779,6 +788,7 @@ export const ListOfGroupPromotions = () => {
</Box>
</>
</Collapse>
<Spacer height="20px" />
{isShowModal && (

View File

@ -1,21 +1,14 @@
import * as React from "react";
import List from "@mui/material/List";
import ListItem from "@mui/material/ListItem";
import ListItemButton from "@mui/material/ListItemButton";
import ListItemIcon from "@mui/material/ListItemIcon";
import ListItemText from "@mui/material/ListItemText";
import Checkbox from "@mui/material/Checkbox";
import IconButton from "@mui/material/IconButton";
import CommentIcon from "@mui/icons-material/Comment";
import InfoIcon from "@mui/icons-material/Info";
import GroupAddIcon from "@mui/icons-material/GroupAdd";
import { executeEvent } from "../../utils/events";
import { Box, Typography } from "@mui/material";
import { Spacer } from "../../common/Spacer";
import { getGroupNames } from "./UserListOfInvites";
import { CustomLoader } from "../../common/CustomLoader";
import VisibilityIcon from "@mui/icons-material/Visibility";
import { isMobile } from "../../App";
import * as React from 'react';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import ListItemButton from '@mui/material/ListItemButton';
import ListItemText from '@mui/material/ListItemText';
import IconButton from '@mui/material/IconButton';
import { executeEvent } from '../../utils/events';
import { Box, Typography } from '@mui/material';
import { Spacer } from '../../common/Spacer';
import { CustomLoader } from '../../common/CustomLoader';
import VisibilityIcon from '@mui/icons-material/Visibility';
export const ListOfThreadPostsWatched = () => {
const [posts, setPosts] = React.useState([]);
@ -24,33 +17,33 @@ export const ListOfThreadPostsWatched = () => {
const getPosts = async () => {
try {
await new Promise((res, rej) => {
window.sendMessage("getThreadActivity", {})
.then((response) => {
if (!response?.error) {
if (!response) {
res(null);
window
.sendMessage('getThreadActivity', {})
.then((response) => {
if (!response?.error) {
if (!response) {
res(null);
return;
}
const uniquePosts = response.reduce((acc, current) => {
const x = acc.find(
(item) => item?.thread?.threadId === current?.thread?.threadId
);
if (!x) {
return acc.concat([current]);
} else {
return acc;
}
}, []);
setPosts(uniquePosts);
res(uniquePosts);
return;
}
const uniquePosts = response.reduce((acc, current) => {
const x = acc.find(
(item) => item?.thread?.threadId === current?.thread?.threadId
);
if (!x) {
return acc.concat([current]);
} else {
return acc;
}
}, []);
setPosts(uniquePosts);
res(uniquePosts);
return;
}
rej(response.error);
})
.catch((error) => {
rej(error.message || "An error occurred");
});
rej(response.error);
})
.catch((error) => {
rej(error.message || 'An error occurred');
});
});
} catch (error) {
} finally {
@ -63,49 +56,50 @@ export const ListOfThreadPostsWatched = () => {
}, []);
return (
<Box sx={{
width: "100%",
display: "flex",
flexDirection: "column",
alignItems: 'center'
}}>
<Box
sx={{
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
<Box
sx={{
width: "322px",
display: "flex",
flexDirection: "column",
width: '322px',
display: 'flex',
flexDirection: 'column',
padding: '0px 20px',
}}
>
<Typography
sx={{
fontSize: "13px",
fontSize: '13px',
fontWeight: 600,
}}
>
New Thread Posts:
</Typography>
<Spacer height="10px" />
<Spacer height="10px" />
</Box>
<Box
sx={{
width: "322px",
height: isMobile ? "165px" : "250px",
display: "flex",
flexDirection: "column",
bgcolor: "background.paper",
padding: "20px",
borderRadius: '19px'
bgcolor: 'background.paper',
borderRadius: '19px',
display: 'flex',
flexDirection: 'column',
height: '250px',
padding: '20px',
width: '322px',
}}
>
{loading && posts.length === 0 && (
<Box
sx={{
width: "100%",
display: "flex",
justifyContent: "center",
width: '100%',
display: 'flex',
justifyContent: 'center',
}}
>
<CustomLoader />
@ -114,19 +108,18 @@ export const ListOfThreadPostsWatched = () => {
{!loading && posts.length === 0 && (
<Box
sx={{
width: "100%",
display: "flex",
justifyContent: "center",
width: '100%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '100%',
}}
>
<Typography
sx={{
fontSize: "11px",
fontSize: '11px',
fontWeight: 400,
color: 'rgba(255, 255, 255, 0.2)'
color: 'rgba(255, 255, 255, 0.2)',
}}
>
Nothing to display
@ -134,47 +127,46 @@ export const ListOfThreadPostsWatched = () => {
</Box>
)}
{posts?.length > 0 && (
<List
className="scrollable-container"
sx={{
width: "100%",
maxWidth: 360,
bgcolor: "background.paper",
maxHeight: "300px",
overflow: "auto",
}}
>
{posts?.map((post) => {
return (
<ListItem
key={post?.thread?.threadId}
onClick={() => {
executeEvent("openThreadNewPost", {
data: post,
});
}}
disablePadding
secondaryAction={
<IconButton edge="end" aria-label="comments">
<VisibilityIcon
sx={{
color: "red",
}}
/>
</IconButton>
}
>
<ListItemButton disableRipple role={undefined} dense>
<ListItemText
primary={`New post in ${post?.thread?.threadData?.title}`}
/>
</ListItemButton>
</ListItem>
);
})}
</List>
<List
className="scrollable-container"
sx={{
width: '100%',
maxWidth: 360,
bgcolor: 'background.paper',
maxHeight: '300px',
overflow: 'auto',
}}
>
{posts?.map((post) => {
return (
<ListItem
key={post?.thread?.threadId}
onClick={() => {
executeEvent('openThreadNewPost', {
data: post,
});
}}
disablePadding
secondaryAction={
<IconButton edge="end" aria-label="comments">
<VisibilityIcon
sx={{
color: 'red',
}}
/>
</IconButton>
}
>
<ListItemButton disableRipple role={undefined} dense>
<ListItemText
primary={`New post in ${post?.thread?.threadData?.title}`}
/>
</ListItemButton>
</ListItem>
);
})}
</List>
)}
</Box>
</Box>
);

View File

@ -1,10 +1,6 @@
import * as React from 'react';
import Button from '@mui/material/Button';
import Dialog from '@mui/material/Dialog';
import ListItemText from '@mui/material/ListItemText';
import ListItemButton from '@mui/material/ListItemButton';
import List from '@mui/material/List';
import Divider from '@mui/material/Divider';
import AppBar from '@mui/material/AppBar';
import Toolbar from '@mui/material/Toolbar';
import IconButton from '@mui/material/IconButton';
@ -19,7 +15,7 @@ import { ListOfBans } from './ListOfBans';
import { ListOfJoinRequests } from './ListOfJoinRequests';
import { Box, ButtonBase, Card, Tab, Tabs } from '@mui/material';
import { CustomizedSnackbars } from '../Snackbar/Snackbar';
import { MyContext, getBaseApiReact, isMobile } from '../../App';
import { MyContext, getBaseApiReact } from '../../App';
import { getGroupMembers, getNames } from './Group';
import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar';
import { getFee } from '../../background';
@ -27,6 +23,7 @@ import { LoadingButton } from '@mui/lab';
import { subscribeToEvent, unsubscribeFromEvent } from '../../utils/events';
import { Spacer } from '../../common/Spacer';
import InsertLinkIcon from '@mui/icons-material/InsertLink';
function a11yProps(index: number) {
return {
id: `simple-tab-${index}`,
@ -193,9 +190,9 @@ export const ManageMembers = ({
<Box
sx={{
bgcolor: '#27282c',
color: 'white',
flexGrow: 1,
overflowY: 'auto',
color: 'white',
}}
>
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
@ -221,9 +218,10 @@ export const ManageMembers = ({
'&.Mui-selected': {
color: 'white',
},
fontSize: isMobile ? '0.75rem' : '1rem', // Adjust font size for mobile
fontSize: '1rem',
}}
/>
<Tab
label="Invite new member"
{...a11yProps(1)}
@ -231,9 +229,10 @@ export const ManageMembers = ({
'&.Mui-selected': {
color: 'white',
},
fontSize: isMobile ? '0.75rem' : '1rem',
fontSize: '1rem',
}}
/>
<Tab
label="List of invites"
{...a11yProps(2)}
@ -241,9 +240,10 @@ export const ManageMembers = ({
'&.Mui-selected': {
color: 'white',
},
fontSize: isMobile ? '0.75rem' : '1rem',
fontSize: '1rem',
}}
/>
<Tab
label="List of bans"
{...a11yProps(3)}
@ -251,9 +251,10 @@ export const ManageMembers = ({
'&.Mui-selected': {
color: 'white',
},
fontSize: isMobile ? '0.75rem' : '1rem',
fontSize: '1rem',
}}
/>
<Tab
label="Join requests"
{...a11yProps(4)}
@ -261,11 +262,12 @@ export const ManageMembers = ({
'&.Mui-selected': {
color: 'white',
},
fontSize: isMobile ? '0.75rem' : '1rem',
fontSize: '1rem',
}}
/>
</Tabs>
</Box>
<Card
sx={{
padding: '10px',
@ -274,10 +276,13 @@ export const ManageMembers = ({
>
<Box>
<Typography>GroupId: {groupInfo?.groupId}</Typography>
<Typography>GroupName: {groupInfo?.groupName}</Typography>
<Typography>
Number of members: {groupInfo?.memberCount}
</Typography>
<ButtonBase
sx={{
gap: '10px',
@ -290,7 +295,9 @@ export const ManageMembers = ({
<InsertLinkIcon /> <Typography>Join Group Link</Typography>
</ButtonBase>
</Box>
<Spacer height="20px" />
{selectedGroup?.groupId && !isOwner && (
<LoadingButton
size="small"
@ -317,7 +324,9 @@ export const ManageMembers = ({
>
Load members with names
</Button>
<Spacer height="10px" />
<ListOfMembers
members={membersWithNames || []}
groupId={selectedGroup?.groupId}
@ -397,12 +406,14 @@ export const ManageMembers = ({
</Box>
)}
</Box>
<CustomizedSnackbars
open={openSnack}
setOpen={setOpenSnack}
info={infoSnack}
setInfo={setInfoSnack}
/>
<LoadingSnackbar
open={isLoadingMembers}
info={{

View File

@ -6,7 +6,7 @@ import ListItemIcon from '@mui/material/ListItemIcon';
import ListItemText from '@mui/material/ListItemText';
import moment from 'moment';
import { Box, ButtonBase, Collapse, Typography, useTheme } from '@mui/material';
import { getBaseApiReact, isMobile } from '../../App';
import { getBaseApiReact } from '../../App';
import MailIcon from '@mui/icons-material/Mail';
import MailOutlineIcon from '@mui/icons-material/MailOutline';
import { executeEvent } from '../../utils/events';
@ -180,7 +180,7 @@ export const QMailMessages = ({ userName, userAddress }) => {
borderRadius: '19px',
display: 'flex',
flexDirection: 'column',
height: isMobile ? '165px' : '250px',
height: '250px',
overflow: 'auto',
padding: '20px',
width: '322px',

View File

@ -1,28 +1,23 @@
import * as React from "react";
import List from "@mui/material/List";
import ListItem from "@mui/material/ListItem";
import ListItemButton from "@mui/material/ListItemButton";
import ListItemIcon from "@mui/material/ListItemIcon";
import ListItemText from "@mui/material/ListItemText";
import Checkbox from "@mui/material/Checkbox";
import IconButton from "@mui/material/IconButton";
import CommentIcon from "@mui/icons-material/Comment";
import InfoIcon from "@mui/icons-material/Info";
import { Box, Typography } from "@mui/material";
import { Spacer } from "../../common/Spacer";
import { isMobile } from "../../App";
import { QMailMessages } from "./QMailMessages";
import { executeEvent } from "../../utils/events";
import * as React from 'react';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import ListItemButton from '@mui/material/ListItemButton';
import ListItemIcon from '@mui/material/ListItemIcon';
import ListItemText from '@mui/material/ListItemText';
import { Box, Typography } from '@mui/material';
import { Spacer } from '../../common/Spacer';
import { QMailMessages } from './QMailMessages';
import { executeEvent } from '../../utils/events';
export const ThingsToDoInitial = ({ myAddress, name, hasGroups, balance, userInfo }) => {
export const ThingsToDoInitial = ({
myAddress,
name,
hasGroups,
balance,
userInfo,
}) => {
const [checked1, setChecked1] = React.useState(false);
const [checked2, setChecked2] = React.useState(false);
// const [checked3, setChecked3] = React.useState(false);
// React.useEffect(() => {
// if (hasGroups) setChecked3(true);
// }, [hasGroups]);
React.useEffect(() => {
if (balance && +balance >= 6) {
@ -30,111 +25,114 @@ export const ThingsToDoInitial = ({ myAddress, name, hasGroups, balance, userInf
}
}, [balance]);
React.useEffect(() => {
if (name) setChecked2(true);
}, [name]);
const isLoaded = React.useMemo(() => {
if (userInfo !== null) return true;
return false;
}, [userInfo]);
const isLoaded = React.useMemo(()=> {
if(userInfo !== null) return true
return false
}, [ userInfo])
const hasDoneNameAndBalanceAndIsLoaded = React.useMemo(() => {
if (isLoaded && checked1 && checked2) return true;
return false;
}, [checked1, isLoaded, checked2]);
const hasDoneNameAndBalanceAndIsLoaded = React.useMemo(()=> {
if(isLoaded && checked1 && checked2) return true
return false
}, [checked1, isLoaded, checked2])
if(hasDoneNameAndBalanceAndIsLoaded){
return (
<QMailMessages userAddress={userInfo?.address} userName={userInfo?.name} />
);
}
if(!isLoaded) return null
if (hasDoneNameAndBalanceAndIsLoaded) {
return (
<QMailMessages
userAddress={userInfo?.address}
userName={userInfo?.name}
/>
);
}
if (!isLoaded) return null;
return (
<Box
sx={{
width: "100%",
display: "flex",
flexDirection: "column",
alignItems: "center",
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
<Box
sx={{
width: "322px",
display: "flex",
flexDirection: "column",
padding: "0px 20px",
width: '322px',
display: 'flex',
flexDirection: 'column',
padding: '0px 20px',
}}
>
<Typography
sx={{
fontSize: "1rem",
fontSize: '1rem',
fontWeight: 600,
}}
>
{!isLoaded ? 'Loading...' : 'Getting Started' }
{!isLoaded ? 'Loading...' : 'Getting Started'}
</Typography>
<Spacer height="10px" />
</Box>
<Box
sx={{
width: "322px",
display: "flex",
flexDirection: "column",
bgcolor: "background.paper",
padding: "20px",
borderRadius: "19px",
width: '322px',
display: 'flex',
flexDirection: 'column',
bgcolor: 'background.paper',
padding: '20px',
borderRadius: '19px',
}}
>
{isLoaded && (
<List sx={{ width: "100%", maxWidth: 360 }}>
<ListItem
disablePadding
sx={{
marginBottom: '20px'
}}
>
<ListItemButton
sx={{
padding: "0px",
}}
disableRipple
role={undefined}
dense
onClick={()=> {
executeEvent("openBuyQortInfo", {})
}}
>
<ListItemText
sx={{
"& .MuiTypography-root": {
fontSize: "1rem",
fontWeight: 400,
},
}}
primary={`Have at least 6 QORT in your wallet`}
/>
<ListItemIcon
sx={{
justifyContent: "flex-end",
}}
>
<Box
sx={{
height: "18px",
width: "18px",
borderRadius: "50%",
backgroundColor: checked1 ? "rgba(9, 182, 232, 1)" : "transparent",
outline: "1px solid rgba(9, 182, 232, 1)",
}}
/>
{/* <Checkbox
<List sx={{ width: '100%', maxWidth: 360 }}>
<ListItem
disablePadding
sx={{
marginBottom: '20px',
}}
>
<ListItemButton
sx={{
padding: '0px',
}}
disableRipple
role={undefined}
dense
onClick={() => {
executeEvent('openBuyQortInfo', {});
}}
>
<ListItemText
sx={{
'& .MuiTypography-root': {
fontSize: '1rem',
fontWeight: 400,
},
}}
primary={`Have at least 6 QORT in your wallet`}
/>
<ListItemIcon
sx={{
justifyContent: 'flex-end',
}}
>
<Box
sx={{
height: '18px',
width: '18px',
borderRadius: '50%',
backgroundColor: checked1
? 'rgba(9, 182, 232, 1)'
: 'transparent',
outline: '1px solid rgba(9, 182, 232, 1)',
}}
/>
{/* <Checkbox
edge="start"
checked={checked1}
tabIndex={-1}
@ -149,52 +147,64 @@ if(!isLoaded) return null
},
}}
/> */}
</ListItemIcon>
</ListItemButton>
</ListItem>
<ListItem
sx={{
marginBottom: '20px'
}}
// secondaryAction={
// <IconButton edge="end" aria-label="comments">
// <InfoIcon
// sx={{
// color: "white",
// }}
// />
// </IconButton>
// }
disablePadding
>
<ListItemButton sx={{
padding: "0px",
}} disableRipple role={undefined} dense>
<ListItemText onClick={() => {
executeEvent('openRegisterName', {})
}} sx={{
"& .MuiTypography-root": {
fontSize: "1rem",
fontWeight: 400,
},
}} primary={`Register a name`} />
<ListItemIcon sx={{
justifyContent: "flex-end",
}}>
<Box
sx={{
height: "18px",
width: "18px",
borderRadius: "50%",
backgroundColor: checked2 ? "rgba(9, 182, 232, 1)" : "transparent",
outline: "1px solid rgba(9, 182, 232, 1)",
}}
/>
</ListItemIcon>
</ListItemButton>
</ListItem>
{/* <ListItem
</ListItemIcon>
</ListItemButton>
</ListItem>
<ListItem
sx={{
marginBottom: '20px',
}}
// secondaryAction={
// <IconButton edge="end" aria-label="comments">
// <InfoIcon
// sx={{
// color: "white",
// }}
// />
// </IconButton>
// }
disablePadding
>
<ListItemButton
sx={{
padding: '0px',
}}
disableRipple
role={undefined}
dense
>
<ListItemText
onClick={() => {
executeEvent('openRegisterName', {});
}}
sx={{
'& .MuiTypography-root': {
fontSize: '1rem',
fontWeight: 400,
},
}}
primary={`Register a name`}
/>
<ListItemIcon
sx={{
justifyContent: 'flex-end',
}}
>
<Box
sx={{
height: '18px',
width: '18px',
borderRadius: '50%',
backgroundColor: checked2
? 'rgba(9, 182, 232, 1)'
: 'transparent',
outline: '1px solid rgba(9, 182, 232, 1)',
}}
/>
</ListItemIcon>
</ListItemButton>
</ListItem>
{/* <ListItem
disablePadding
>
<ListItemButton sx={{
@ -222,9 +232,8 @@ if(!isLoaded) return null
</ListItemIcon>
</ListItemButton>
</ListItem> */}
</List>
</List>
)}
</Box>
</Box>
);

View File

@ -1,9 +1,8 @@
import React, { useState, useRef, useEffect } from 'react';
import { useState, useRef, useEffect } from 'react';
import ReactDOM from 'react-dom';
import Picker, { EmojiStyle, Theme } from 'emoji-picker-react';
import './ReactionPicker.css';
import { ButtonBase } from '@mui/material';
import { isMobile } from '../App';
export const ReactionPicker = ({ onReaction }) => {
const [showPicker, setShowPicker] = useState(false);
@ -30,7 +29,7 @@ export const ReactionPicker = ({ onReaction }) => {
} else {
// Get the button's position
const buttonRect = buttonRef.current.getBoundingClientRect();
const pickerWidth = isMobile ? 300 : 350; // Adjust based on picker width
const pickerWidth = 350;
// Calculate position to align the right edge of the picker with the button's right edge
setPickerPosition({
@ -90,15 +89,15 @@ export const ReactionPicker = ({ onReaction }) => {
}}
>
<Picker
height={isMobile ? 350 : 450}
width={isMobile ? 300 : 350}
reactionsDefaultOpen={true}
onReactionClick={handleReaction}
onEmojiClick={handlePicker}
allowExpandReactions={true}
autoFocusSearch={false}
theme={Theme.DARK}
emojiStyle={EmojiStyle.NATIVE}
height="450"
onEmojiClick={handlePicker}
onReactionClick={handleReaction}
reactionsDefaultOpen={true}
theme={Theme.DARK}
width="350"
/>
</div>,
document.body

View File

@ -12,7 +12,7 @@ import PendingIcon from '@mui/icons-material/Pending';
import TaskAltIcon from '@mui/icons-material/TaskAlt';
import ExpandLess from '@mui/icons-material/ExpandLess';
import ExpandMore from '@mui/icons-material/ExpandMore';
import { MyContext, getBaseApiReact, isMobile } from '../../App';
import { MyContext, getBaseApiReact } from '../../App';
import { executeEvent } from '../../utils/events';
export const TaskManager = ({ getUserInfo }) => {
@ -141,8 +141,7 @@ export const TaskManager = ({ getUserInfo }) => {
});
}, [txList]);
if (isMobile || txList?.length === 0 || txList.every((item) => item?.done))
return null;
if (txList?.length === 0 || txList.every((item) => item?.done)) return null;
return (
<>

View File

@ -1,72 +1,103 @@
import React, { useContext, useState } from 'react'
import { GlobalContext, MyContext } from '../../App';
import { Button, Dialog, DialogActions, DialogContent, DialogTitle, IconButton, Tab, Tabs, Typography } from '@mui/material';
import { useContext, useState } from 'react';
import { GlobalContext } from '../../App';
import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
IconButton,
Tab,
Tabs,
useTheme,
} from '@mui/material';
import CloseIcon from '@mui/icons-material/Close';
import { VideoPlayer } from '../Embeds/VideoPlayer';
export const Tutorials = () => {
const { openTutorialModal, setOpenTutorialModal } = useContext(GlobalContext);
const [multiNumber, setMultiNumber] = useState(0)
const handleClose = ()=> {
setOpenTutorialModal(null)
setMultiNumber(0)
}
if(!openTutorialModal) return null
if(openTutorialModal?.multi){
const selectedTutorial = openTutorialModal?.multi[multiNumber]
return (
<Dialog
const { openTutorialModal, setOpenTutorialModal } = useContext(GlobalContext);
const [multiNumber, setMultiNumber] = useState(0);
const theme = useTheme();
const handleClose = () => {
setOpenTutorialModal(null);
setMultiNumber(0);
};
if (!openTutorialModal) return null;
if (openTutorialModal?.multi) {
const selectedTutorial = openTutorialModal?.multi[multiNumber];
return (
<Dialog
onClose={handleClose}
aria-labelledby="customized-dialog-title"
open={!!openTutorialModal}
fullWidth={true}
maxWidth="xl"
>
<Tabs sx={{
"& .MuiTabs-indicator": {
backgroundColor: "white",
},
}} value={multiNumber} onChange={(e, value)=> setMultiNumber(value)} aria-label="basic tabs example">
{openTutorialModal?.multi?.map((item, index)=> {
return (
<Tab sx={{
"&.Mui-selected": {
color: "white",
},
}} label={item?.title} value={index} />
)
})}
</Tabs>
<DialogTitle sx={{ m: 0, p: 2 }} >
<Tabs
sx={{
'& .MuiTabs-indicator': {
backgroundColor: theme.palette.background.default,
},
}}
value={multiNumber}
onChange={(e, value) => setMultiNumber(value)}
aria-label="basic tabs example"
>
{openTutorialModal?.multi?.map((item, index) => {
return (
<Tab
sx={{
'&.Mui-selected': {
color: theme.palette.text.primary,
},
}}
label={item?.title}
value={index}
/>
);
})}
</Tabs>
<DialogTitle sx={{ m: 0, p: 2 }}>
{selectedTutorial?.title} {` Tutorial`}
</DialogTitle>
<IconButton
aria-label="close"
onClick={handleClose}
sx={(theme) => ({
sx={{
position: 'absolute',
right: 8,
top: 8,
color: theme.palette.grey[500],
})}
color: theme.palette.text.primary,
}}
>
<CloseIcon />
</IconButton>
<DialogContent dividers sx={{
height: '85vh'
}}>
<VideoPlayer node="https://ext-node.qortal.link" {...selectedTutorial?.resource || {}} />
<DialogContent
dividers
sx={{
height: '85vh',
}}
>
<VideoPlayer
node="https://ext-node.qortal.link"
{...(selectedTutorial?.resource || {})}
/>
</DialogContent>
<DialogActions>
<Button variant="contained" onClick={handleClose}>
Close
Close
</Button>
</DialogActions>
</Dialog>
)
}
);
}
return (
<>
<Dialog
@ -76,33 +107,41 @@ export const Tutorials = () => {
fullWidth={true}
maxWidth="xl"
>
<DialogTitle sx={{ m: 0, p: 2 }} >
<DialogTitle sx={{ m: 0, p: 2 }}>
{openTutorialModal?.title} {` Tutorial`}
</DialogTitle>
<IconButton
aria-label="close"
onClick={handleClose}
sx={(theme) => ({
sx={{
position: 'absolute',
right: 8,
top: 8,
color: theme.palette.grey[500],
})}
color: theme.palette.text.primary,
}}
>
<CloseIcon />
</IconButton>
<DialogContent dividers sx={{
height: '85vh'
}}>
<VideoPlayer node="https://ext-node.qortal.link" {...openTutorialModal?.resource || {}} />
<DialogContent
dividers
sx={{
height: '85vh',
}}
>
<VideoPlayer
node="https://ext-node.qortal.link"
{...(openTutorialModal?.resource || {})}
/>
</DialogContent>
<DialogActions>
<Button variant="contained" onClick={handleClose}>
Close
Close
</Button>
</DialogActions>
</Dialog>
</>
)
}
);
};

View File

@ -1,67 +1,85 @@
import { useCallback, useEffect } from 'react';
import { isMobile } from './App';
export const useAppFullScreen = (setFullScreen) => {
const enterFullScreen = useCallback(() => {
const element = document.documentElement; // Target the entire HTML document
if (element.requestFullscreen) {
element.requestFullscreen();
} else if (element.mozRequestFullScreen) { // Firefox
element.mozRequestFullScreen();
} else if (element.webkitRequestFullscreen) { // Chrome, Safari and Opera
element.webkitRequestFullscreen();
} else if (element.msRequestFullscreen) { // IE/Edge
element.msRequestFullscreen();
}
}, []);
const enterFullScreen = useCallback(() => {
const element = document.documentElement; // Target the entire HTML document
if (element.requestFullscreen) {
element.requestFullscreen();
} else if (element.mozRequestFullScreen) {
// Firefox
element.mozRequestFullScreen();
} else if (element.webkitRequestFullscreen) {
// Chrome, Safari and Opera
element.webkitRequestFullscreen();
} else if (element.msRequestFullscreen) {
// IE/Edge
element.msRequestFullscreen();
}
}, []);
const exitFullScreen = useCallback(() => {
if (document.fullscreenElement) {
document.exitFullscreen();
} else if (document.mozFullScreenElement) {
document.mozCancelFullScreen();
} else if (document.webkitFullscreenElement) {
document.webkitExitFullscreen();
} else if (document.msFullscreenElement) {
document.msExitFullscreen();
}
}, []);
const exitFullScreen = useCallback(() => {
if (document.fullscreenElement) {
document.exitFullscreen();
} else if (document.mozFullScreenElement) {
document.mozCancelFullScreen();
} else if (document.webkitFullscreenElement) {
document.webkitExitFullscreen();
} else if (document.msFullscreenElement) {
document.msExitFullscreen();
}
}, []);
const toggleFullScreen = useCallback(() => {
if(!isMobile || isMobile) return
if (document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || document.msFullscreenElement) {
exitFullScreen();
setFullScreen(false)
} else {
enterFullScreen();
setFullScreen(true)
}
}, [enterFullScreen, exitFullScreen]);
const toggleFullScreen = useCallback(() => {
if (
document.fullscreenElement ||
document.mozFullScreenElement ||
document.webkitFullscreenElement ||
document.msFullscreenElement
) {
exitFullScreen();
setFullScreen(false);
} else {
enterFullScreen();
setFullScreen(true);
}
}, [enterFullScreen, exitFullScreen]);
// Listen for changes to fullscreen state
useEffect(() => {
const handleFullScreenChange = () => {
if (document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || document.msFullscreenElement) {
} else {
setFullScreen(false);
}
};
// Listen for changes to fullscreen state
useEffect(() => {
const handleFullScreenChange = () => {
if (
document.fullscreenElement ||
document.mozFullScreenElement ||
document.webkitFullscreenElement ||
document.msFullscreenElement
) {
// TODO check empty block
} else {
setFullScreen(false);
}
};
document.addEventListener('fullscreenchange', handleFullScreenChange);
document.addEventListener('webkitfullscreenchange', handleFullScreenChange); // Safari
document.addEventListener('mozfullscreenchange', handleFullScreenChange); // Firefox
document.addEventListener('MSFullscreenChange', handleFullScreenChange); // IE/Edge
document.addEventListener('fullscreenchange', handleFullScreenChange);
document.addEventListener('webkitfullscreenchange', handleFullScreenChange); // Safari
document.addEventListener('mozfullscreenchange', handleFullScreenChange); // Firefox
document.addEventListener('MSFullscreenChange', handleFullScreenChange); // IE/Edge
return () => {
document.removeEventListener('fullscreenchange', handleFullScreenChange);
document.removeEventListener('webkitfullscreenchange', handleFullScreenChange);
document.removeEventListener('mozfullscreenchange', handleFullScreenChange);
document.removeEventListener('MSFullscreenChange', handleFullScreenChange);
};
}, []);
return () => {
document.removeEventListener('fullscreenchange', handleFullScreenChange);
document.removeEventListener(
'webkitfullscreenchange',
handleFullScreenChange
);
document.removeEventListener(
'mozfullscreenchange',
handleFullScreenChange
);
document.removeEventListener(
'MSFullscreenChange',
handleFullScreenChange
);
};
}, []);
return { enterFullScreen, exitFullScreen, toggleFullScreen };
return { enterFullScreen, exitFullScreen, toggleFullScreen };
};