Remove isMobile check

This commit is contained in:
Nicola Benaglia 2025-04-20 15:41:45 +02:00
parent c71853f754
commit de9285a280
37 changed files with 2134 additions and 2279 deletions

View File

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

View File

@ -1,8 +1,6 @@
import React, { useEffect, useMemo, useState } from "react";
import { import {
AppCircle, AppCircle,
AppCircleContainer, AppCircleContainer,
AppCircleLabel,
AppDownloadButton, AppDownloadButton,
AppDownloadButtonText, AppDownloadButtonText,
AppInfoAppName, AppInfoAppName,
@ -17,193 +15,204 @@ import {
AppsCategoryInfoValue, AppsCategoryInfoValue,
AppsInfoDescription, AppsInfoDescription,
AppsLibraryContainer, AppsLibraryContainer,
AppsParent,
AppsWidthLimiter, AppsWidthLimiter,
} from "./Apps-styles"; } from './Apps-styles';
import { Avatar, Box, ButtonBase, InputBase } from "@mui/material"; import { Avatar, Box } from '@mui/material';
import { Add } from "@mui/icons-material"; import { getBaseApiReact } from '../../App';
import { getBaseApiReact, isMobile } from "../../App"; import LogoSelected from '../../assets/svgs/LogoSelected.svg';
import LogoSelected from "../../assets/svgs/LogoSelected.svg"; import { Spacer } from '../../common/Spacer';
import { executeEvent } from '../../utils/events';
import { Spacer } from "../../common/Spacer"; import { AppRating } from './AppRating';
import { executeEvent } from "../../utils/events"; import {
import { AppRating } from "./AppRating"; settingsLocalLastUpdatedAtom,
import { settingsLocalLastUpdatedAtom, sortablePinnedAppsAtom } from "../../atoms/global"; sortablePinnedAppsAtom,
import { saveToLocalStorage } from "./AppsNavBar"; } from '../../atoms/global';
import { useRecoilState, useSetRecoilState } from "recoil"; import { saveToLocalStorage } from './AppsNavBar';
import { useRecoilState, useSetRecoilState } from 'recoil';
export const AppInfo = ({ app, myName }) => { export const AppInfo = ({ app, myName }) => {
const isInstalled = app?.status?.status === "READY"; const isInstalled = app?.status?.status === 'READY';
const [sortablePinnedApps, setSortablePinnedApps] = useRecoilState(sortablePinnedAppsAtom); const [sortablePinnedApps, setSortablePinnedApps] = useRecoilState(
sortablePinnedAppsAtom
);
const isSelectedAppPinned = !!sortablePinnedApps?.find((item)=> item?.name === app?.name && item?.service === app?.service) const isSelectedAppPinned = !!sortablePinnedApps?.find(
const setSettingsLocalLastUpdated = useSetRecoilState(settingsLocalLastUpdatedAtom); (item) => item?.name === app?.name && item?.service === app?.service
);
const setSettingsLocalLastUpdated = useSetRecoilState(
settingsLocalLastUpdatedAtom
);
return ( return (
<AppsLibraryContainer <AppsLibraryContainer
sx={{ sx={{
height: !isMobile && "100%", height: '100%',
justifyContent: !isMobile && "flex-start", justifyContent: 'flex-start',
alignItems: isMobile && 'center'
}} }}
> >
<Box sx={{ <Box
display: 'flex', sx={{
flexDirection: 'column', display: 'flex',
maxWidth: "500px", flexDirection: 'column',
width: '90%' maxWidth: '500px',
}}> width: '90%',
}}
>
<Spacer height="30px" />
<AppsWidthLimiter>
{!isMobile && <Spacer height="30px" />} <AppInfoSnippetContainer>
<AppsWidthLimiter> <AppInfoSnippetLeft
<AppInfoSnippetContainer>
<AppInfoSnippetLeft
sx={{
flexGrow: 1,
gap: "18px",
}}
>
<AppCircleContainer
sx={{ sx={{
width: "auto", flexGrow: 1,
gap: '18px',
}} }}
> >
<AppCircle <AppCircleContainer
sx={{ sx={{
border: "none", width: 'auto',
height: "100px",
width: "100px",
}} }}
> >
<Avatar <AppCircle
sx={{ sx={{
height: "43px", border: 'none',
width: "43px", height: '100px',
"& img": { width: '100px',
objectFit: "fill",
},
}} }}
alt={app?.name}
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
app?.name
}/qortal_avatar?async=true`}
> >
<img <Avatar
style={{ sx={{
width: "43px", height: '43px',
height: "auto", width: '43px',
'& img': {
objectFit: 'fill',
},
}} }}
src={LogoSelected} alt={app?.name}
alt="center-icon" src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
/> app?.name
</Avatar> }/qortal_avatar?async=true`}
</AppCircle> >
</AppCircleContainer> <img
<AppInfoSnippetMiddle> style={{
<AppInfoAppName> width: '43px',
{app?.metadata?.title || app?.name} height: 'auto',
</AppInfoAppName> }}
<Spacer height="6px" /> src={LogoSelected}
<AppInfoUserName>{app?.name}</AppInfoUserName> alt="center-icon"
<Spacer height="3px" /> />
</AppInfoSnippetMiddle> </Avatar>
</AppInfoSnippetLeft> </AppCircle>
<AppInfoSnippetRight></AppInfoSnippetRight> </AppCircleContainer>
</AppInfoSnippetContainer> <AppInfoSnippetMiddle>
<Spacer height="11px" /> <AppInfoAppName>
<Box sx={{ {app?.metadata?.title || app?.name}
width: '100%', </AppInfoAppName>
display: 'flex', <Spacer height="6px" />
alignItems: 'center', <AppInfoUserName>{app?.name}</AppInfoUserName>
gap: '20px' <Spacer height="3px" />
}}> </AppInfoSnippetMiddle>
<AppDownloadButton </AppInfoSnippetLeft>
onClick={() => { <AppInfoSnippetRight></AppInfoSnippetRight>
setSortablePinnedApps((prev) => { </AppInfoSnippetContainer>
let updatedApps; <Spacer height="11px" />
<Box
if (isSelectedAppPinned) { sx={{
// Remove the selected app if it is pinned width: '100%',
updatedApps = prev.filter( display: 'flex',
(item) => !(item?.name === app?.name && item?.service === app?.service) alignItems: 'center',
gap: '20px',
}}
>
<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
); );
} else { return updatedApps;
// Add the selected app if it is not pinned });
updatedApps = [...prev, { setSettingsLocalLastUpdated(Date.now());
name: app?.name, }}
service: app?.service, sx={{
}]; backgroundColor: '#359ff7ff',
} width: '100%',
maxWidth: '320px',
saveToLocalStorage('ext_saved_settings', 'sortablePinnedApps', updatedApps) height: '29px',
return updatedApps; opacity: isSelectedAppPinned ? 0.6 : 1,
}); }}
setSettingsLocalLastUpdated(Date.now()) >
}} <AppDownloadButtonText>
sx={{ {isSelectedAppPinned
backgroundColor: "#359ff7ff", ? 'Unpin from dashboard'
width: "100%", : 'Pin to dashboard'}
maxWidth: "320px", </AppDownloadButtonText>
height: "29px", </AppDownloadButton>
opacity: isSelectedAppPinned ? 0.6 : 1 <AppDownloadButton
}} onClick={() => {
> executeEvent('addTab', {
<AppDownloadButtonText> data: app,
{!isMobile ? ( });
<> }}
{isSelectedAppPinned ? 'Unpin from dashboard' : 'Pin to dashboard'} sx={{
</> backgroundColor: isInstalled ? '#0091E1' : '#247C0E',
) : ( width: '100%',
<> maxWidth: '320px',
{isSelectedAppPinned ? 'Unpin' : 'Pin'} height: '29px',
</> }}
)} >
<AppDownloadButtonText>
</AppDownloadButtonText> {isInstalled ? 'Open' : 'Download'}
</AppDownloadButton> </AppDownloadButtonText>
<AppDownloadButton </AppDownloadButton>
onClick={() => { </Box>
executeEvent("addTab", { </AppsWidthLimiter>
data: app, <Spacer height="20px" />
}); <AppsWidthLimiter>
}} <AppsCategoryInfo>
sx={{ <AppRating ratingCountPosition="top" myName={myName} app={app} />
backgroundColor: isInstalled ? "#0091E1" : "#247C0E", <Spacer width="16px" />
width: "100%", <Spacer height="40px" width="1px" backgroundColor="white" />
maxWidth: "320px", <Spacer width="16px" />
height: "29px", <AppsCategoryInfoSub>
}} <AppsCategoryInfoLabel>Category:</AppsCategoryInfoLabel>
> <Spacer height="4px" />
<AppDownloadButtonText> <AppsCategoryInfoValue>
{isInstalled ? "Open" : "Download"} {app?.metadata?.categoryName || 'none'}
</AppDownloadButtonText> </AppsCategoryInfoValue>
</AppDownloadButton> </AppsCategoryInfoSub>
</Box> </AppsCategoryInfo>
<Spacer height="30px" />
</AppsWidthLimiter> <AppInfoAppName>About this Q-App</AppInfoAppName>
<Spacer height="20px" /> </AppsWidthLimiter>
<AppsWidthLimiter> <Spacer height="20px" />
<AppsCategoryInfo> <AppsInfoDescription>
<AppRating ratingCountPosition="top" myName={myName} app={app} /> {app?.metadata?.description || 'No description'}
<Spacer width="16px" /> </AppsInfoDescription>
<Spacer height="40px" width="1px" backgroundColor="white" />
<Spacer width="16px" />
<AppsCategoryInfoSub>
<AppsCategoryInfoLabel>Category:</AppsCategoryInfoLabel>
<Spacer height="4px" />
<AppsCategoryInfoValue>
{app?.metadata?.categoryName || "none"}
</AppsCategoryInfoValue>
</AppsCategoryInfoSub>
</AppsCategoryInfo>
<Spacer height="30px" />
<AppInfoAppName>About this Q-App</AppInfoAppName>
</AppsWidthLimiter>
<Spacer height="20px" />
<AppsInfoDescription>
{app?.metadata?.description || "No description"}
</AppsInfoDescription>
</Box> </Box>
</AppsLibraryContainer> </AppsLibraryContainer>
); );

View File

@ -1,4 +1,3 @@
import React from "react";
import { import {
AppCircle, AppCircle,
AppCircleContainer, AppCircleContainer,
@ -10,148 +9,176 @@ import {
AppInfoSnippetMiddle, AppInfoSnippetMiddle,
AppInfoSnippetRight, AppInfoSnippetRight,
AppInfoUserName, AppInfoUserName,
} from "./Apps-styles"; } from './Apps-styles';
import { Avatar, ButtonBase } from "@mui/material"; import { Avatar, ButtonBase } from '@mui/material';
import { getBaseApiReact, isMobile } from "../../App"; import { getBaseApiReact } from '../../App';
import LogoSelected from "../../assets/svgs/LogoSelected.svg"; import LogoSelected from '../../assets/svgs/LogoSelected.svg';
import { Spacer } from '../../common/Spacer';
import { executeEvent } from '../../utils/events';
import { AppRating } from './AppRating';
import { useRecoilState, useSetRecoilState } from 'recoil';
import {
settingsLocalLastUpdatedAtom,
sortablePinnedAppsAtom,
} from '../../atoms/global';
import { saveToLocalStorage } from './AppsNavBar';
import { Spacer } from "../../common/Spacer"; export const AppInfoSnippet = ({
import { executeEvent } from "../../utils/events"; app,
import { AppRating } from "./AppRating"; myName,
import { useRecoilState, useSetRecoilState } from "recoil"; isFromCategory,
import { settingsLocalLastUpdatedAtom, sortablePinnedAppsAtom } from "../../atoms/global"; parentStyles = {},
import { saveToLocalStorage } from "./AppsNavBar"; }) => {
const isInstalled = app?.status?.status === 'READY';
const [sortablePinnedApps, setSortablePinnedApps] = useRecoilState(
sortablePinnedAppsAtom
);
export const AppInfoSnippet = ({ app, myName, isFromCategory, parentStyles = {} }) => { const isSelectedAppPinned = !!sortablePinnedApps?.find(
(item) => item?.name === app?.name && item?.service === app?.service
const isInstalled = app?.status?.status === 'READY' );
const [sortablePinnedApps, setSortablePinnedApps] = useRecoilState(sortablePinnedAppsAtom); const setSettingsLocalLastUpdated = useSetRecoilState(
settingsLocalLastUpdatedAtom
const isSelectedAppPinned = !!sortablePinnedApps?.find((item)=> item?.name === app?.name && item?.service === app?.service) );
const setSettingsLocalLastUpdated = useSetRecoilState(settingsLocalLastUpdatedAtom);
return ( return (
<AppInfoSnippetContainer sx={{ <AppInfoSnippetContainer
...parentStyles sx={{
}}> ...parentStyles,
}}
>
<AppInfoSnippetLeft> <AppInfoSnippetLeft>
<ButtonBase <ButtonBase
sx={{ sx={{
height: "80px", height: '80px',
width: "60px", width: '60px',
}} }}
onClick={()=> { onClick={() => {
if(isFromCategory){ if (isFromCategory) {
executeEvent("selectedAppInfoCategory", { executeEvent('selectedAppInfoCategory', {
data: app,
});
return;
}
executeEvent('selectedAppInfo', {
data: app, data: app,
}); });
return }}
} >
executeEvent("selectedAppInfo", { <AppCircleContainer>
data: app, <AppCircle
}); sx={{
}} border: 'none',
> }}
<AppCircleContainer> >
<AppCircle <Avatar
sx={{ sx={{
border: "none", height: '42px',
width: '42px',
'& img': {
objectFit: 'fill',
},
}}
alt={app?.name}
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
app?.name
}/qortal_avatar?async=true`}
>
<img
style={{
width: '31px',
height: 'auto',
}}
src={LogoSelected}
alt="center-icon"
/>
</Avatar>
</AppCircle>
</AppCircleContainer>
</ButtonBase>
<AppInfoSnippetMiddle>
<ButtonBase
onClick={() => {
if (isFromCategory) {
executeEvent('selectedAppInfoCategory', {
data: app,
});
return;
}
executeEvent('selectedAppInfo', {
data: app,
});
}} }}
> >
<Avatar <AppInfoAppName>{app?.metadata?.title || app?.name}</AppInfoAppName>
sx={{ </ButtonBase>
height: "42px", <Spacer height="6px" />
width: "42px", <AppInfoUserName>{app?.name}</AppInfoUserName>
'& img': {
objectFit: 'fill',
}
}}
alt={app?.name}
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
app?.name
}/qortal_avatar?async=true`}
>
<img
style={{
width: "31px",
height: "auto",
}}
src={LogoSelected}
alt="center-icon"
/>
</Avatar>
</AppCircle>
</AppCircleContainer>
</ButtonBase>
<AppInfoSnippetMiddle>
<ButtonBase onClick={()=> {
if(isFromCategory){
executeEvent("selectedAppInfoCategory", {
data: app,
});
return
}
executeEvent("selectedAppInfo", {
data: app,
});
}}>
<AppInfoAppName >
{app?.metadata?.title || app?.name}
</AppInfoAppName>
</ButtonBase>
<Spacer height="6px" />
<AppInfoUserName>
{ app?.name}
</AppInfoUserName>
<Spacer height="3px" /> <Spacer height="3px" />
<AppRating app={app} myName={myName} /> <AppRating app={app} myName={myName} />
</AppInfoSnippetMiddle> </AppInfoSnippetMiddle>
</AppInfoSnippetLeft> </AppInfoSnippetLeft>
<AppInfoSnippetRight sx={{ <AppInfoSnippetRight
gap: '10px' sx={{
}}> gap: '10px',
{!isMobile && ( }}
<AppDownloadButton onClick={()=> { >
<AppDownloadButton
setSortablePinnedApps((prev) => { onClick={() => {
let updatedApps; setSortablePinnedApps((prev) => {
let updatedApps;
if (isSelectedAppPinned) {
// Remove the selected app if it is pinned if (isSelectedAppPinned) {
updatedApps = prev.filter( // Remove the selected app if it is pinned
(item) => !(item?.name === app?.name && item?.service === app?.service) updatedApps = prev.filter(
); (item) =>
} else { !(
// Add the selected app if it is not pinned item?.name === app?.name && item?.service === app?.service
updatedApps = [...prev, { )
name: app?.name, );
service: app?.service, } else {
}]; // Add the selected app if it is not pinned
} updatedApps = [
...prev,
saveToLocalStorage('ext_saved_settings', 'sortablePinnedApps', updatedApps) {
return updatedApps; name: app?.name,
}); service: app?.service,
setSettingsLocalLastUpdated(Date.now()) },
}} sx={{ ];
backgroundColor: '#359ff7ff', }
opacity: isSelectedAppPinned ? 0.6 : 1
saveToLocalStorage(
}}> 'ext_saved_settings',
<AppDownloadButtonText> {isSelectedAppPinned ? 'Unpin' : 'Pin'}</AppDownloadButtonText> 'sortablePinnedApps',
updatedApps
);
return updatedApps;
});
setSettingsLocalLastUpdated(Date.now());
}}
sx={{
backgroundColor: '#359ff7ff',
opacity: isSelectedAppPinned ? 0.6 : 1,
}}
>
<AppDownloadButtonText>
{' '}
{isSelectedAppPinned ? 'Unpin' : 'Pin'}
</AppDownloadButtonText>
</AppDownloadButton> </AppDownloadButton>
)}
<AppDownloadButton
<AppDownloadButton onClick={()=> { onClick={() => {
executeEvent('addTab', {
executeEvent("addTab", { data: app,
data: app });
}) }}
}} sx={{ sx={{
backgroundColor: isInstalled ? '#0091E1' : '#247C0E', backgroundColor: isInstalled ? '#0091E1' : '#247C0E',
}}
}}> >
<AppDownloadButtonText>{isInstalled ? 'Open' : 'Download'}</AppDownloadButtonText> <AppDownloadButtonText>
{isInstalled ? 'Open' : 'Download'}
</AppDownloadButtonText>
</AppDownloadButton> </AppDownloadButton>
</AppInfoSnippetRight> </AppInfoSnippetRight>
</AppInfoSnippetContainer> </AppInfoSnippetContainer>

View File

@ -1,4 +1,4 @@
import React, { useContext, useEffect, useMemo, useState } from "react"; import React, { useContext, useEffect, useState } from 'react';
import { import {
AppCircle, AppCircle,
AppCircleContainer, AppCircleContainer,
@ -19,90 +19,74 @@ import {
PublishQAppCTAButton, PublishQAppCTAButton,
PublishQAppChoseFile, PublishQAppChoseFile,
PublishQAppInfo, PublishQAppInfo,
} from "./Apps-styles"; } from './Apps-styles';
import { import { InputBase, InputLabel, MenuItem, Select } from '@mui/material';
Avatar, import { styled } from '@mui/system';
Box, import UnfoldMoreRoundedIcon from '@mui/icons-material/UnfoldMoreRounded';
ButtonBase, import { Add } from '@mui/icons-material';
InputBase, import { MyContext, getBaseApiReact } from '../../App';
InputLabel, import LogoSelected from '../../assets/svgs/LogoSelected.svg';
MenuItem, import { Spacer } from '../../common/Spacer';
Select, import { executeEvent } from '../../utils/events';
} from "@mui/material"; import { useDropzone } from 'react-dropzone';
import { import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar';
Select as BaseSelect, import { CustomizedSnackbars } from '../Snackbar/Snackbar';
SelectProps, import { getFee } from '../../background';
selectClasses, import { fileToBase64 } from '../../utils/fileReading';
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";
const CustomSelect = styled(Select)({ const CustomSelect = styled(Select)({
border: "0.5px solid var(--50-white, #FFFFFF80)", border: '0.5px solid var(--50-white, #FFFFFF80)',
padding: "0px 15px", padding: '0px 15px',
borderRadius: "5px", borderRadius: '5px',
height: "36px", height: '36px',
width: "100%", width: '100%',
maxWidth: "450px", maxWidth: '450px',
"& .MuiSelect-select": { '& .MuiSelect-select': {
padding: "0px", padding: '0px',
}, },
"&:hover": { '&:hover': {
borderColor: "none", // Border color on hover borderColor: 'none', // Border color on hover
}, },
"&.Mui-focused .MuiOutlinedInput-notchedOutline": { '&.Mui-focused .MuiOutlinedInput-notchedOutline': {
borderColor: "none", // Border color when focused borderColor: 'none', // Border color when focused
}, },
"&.Mui-disabled": { '&.Mui-disabled': {
opacity: 0.5, // Lower opacity when disabled opacity: 0.5, // Lower opacity when disabled
}, },
"& .MuiSvgIcon-root": { '& .MuiSvgIcon-root': {
color: "var(--50-white, #FFFFFF80)", color: 'var(--50-white, #FFFFFF80)',
}, },
}); });
const CustomMenuItem = styled(MenuItem)({ const CustomMenuItem = styled(MenuItem)({
backgroundColor: "#1f1f1f", // Background for dropdown items backgroundColor: '#1f1f1f', // Background for dropdown items
color: "#ccc", color: '#ccc',
"&:hover": { '&:hover': {
backgroundColor: "#333", // Darker background on hover backgroundColor: '#333', // Darker background on hover
}, },
}); });
export const AppPublish = ({ names, categories }) => { export const AppPublish = ({ names, categories }) => {
const [name, setName] = useState(""); const [name, setName] = useState('');
const [title, setTitle] = useState(""); const [title, setTitle] = useState('');
const [description, setDescription] = useState(""); const [description, setDescription] = useState('');
const [category, setCategory] = useState(""); const [category, setCategory] = useState('');
const [appType, setAppType] = useState("APP"); const [appType, setAppType] = useState('APP');
const [file, setFile] = useState(null); const [file, setFile] = useState(null);
const { show } = useContext(MyContext); const { show } = useContext(MyContext);
const [tag1, setTag1] = useState(""); const [tag1, setTag1] = useState('');
const [tag2, setTag2] = useState(""); const [tag2, setTag2] = useState('');
const [tag3, setTag3] = useState(""); const [tag3, setTag3] = useState('');
const [tag4, setTag4] = useState(""); const [tag4, setTag4] = useState('');
const [tag5, setTag5] = useState(""); const [tag5, setTag5] = useState('');
const [openSnack, setOpenSnack] = useState(false); const [openSnack, setOpenSnack] = useState(false);
const [infoSnack, setInfoSnack] = useState(null); const [infoSnack, setInfoSnack] = useState(null);
const [isLoading, setIsLoading] = useState(""); const [isLoading, setIsLoading] = useState('');
const maxFileSize = appType === "APP" ? 50 * 1024 * 1024 : 400 * 1024 * 1024; // 50MB or 400MB const maxFileSize = appType === 'APP' ? 50 * 1024 * 1024 : 400 * 1024 * 1024; // 50MB or 400MB
const { getRootProps, getInputProps } = useDropzone({ const { getRootProps, getInputProps } = useDropzone({
accept: { accept: {
"application/zip": [".zip"], // Only accept zip files 'application/zip': ['.zip'], // Only accept zip files
}, },
maxSize: maxFileSize, // Set the max size based on appType maxSize: maxFileSize, // Set the max size based on appType
multiple: false, // Disable multiple file uploads multiple: false, // Disable multiple file uploads
@ -114,7 +98,7 @@ export const AppPublish = ({ names, categories }) => {
onDropRejected: (fileRejections) => { onDropRejected: (fileRejections) => {
fileRejections.forEach(({ file, errors }) => { fileRejections.forEach(({ file, errors }) => {
errors.forEach((error) => { errors.forEach((error) => {
if (error.code === "file-too-large") { if (error.code === 'file-too-large') {
console.error( console.error(
`File ${file.name} is too large. Max size allowed is ${ `File ${file.name} is too large. Max size allowed is ${
maxFileSize / (1024 * 1024) maxFileSize / (1024 * 1024)
@ -128,13 +112,13 @@ export const AppPublish = ({ names, categories }) => {
const getQapp = React.useCallback(async (name, appType) => { const getQapp = React.useCallback(async (name, appType) => {
try { try {
setIsLoading("Loading app information"); setIsLoading('Loading app information');
const url = `${getBaseApiReact()}/arbitrary/resources/search?service=${appType}&mode=ALL&name=${name}&includemetadata=true`; const url = `${getBaseApiReact()}/arbitrary/resources/search?service=${appType}&mode=ALL&name=${name}&includemetadata=true`;
const response = await fetch(url, { const response = await fetch(url, {
method: "GET", method: 'GET',
headers: { headers: {
"Content-Type": "application/json", 'Content-Type': 'application/json',
}, },
}); });
if (!response?.ok) return; if (!response?.ok) return;
@ -142,18 +126,18 @@ export const AppPublish = ({ names, categories }) => {
if (responseData?.length > 0) { if (responseData?.length > 0) {
const myApp = responseData[0]; const myApp = responseData[0];
setTitle(myApp?.metadata?.title || ""); setTitle(myApp?.metadata?.title || '');
setDescription(myApp?.metadata?.description || ""); setDescription(myApp?.metadata?.description || '');
setCategory(myApp?.metadata?.category || ""); setCategory(myApp?.metadata?.category || '');
setTag1(myApp?.metadata?.tags[0] || ""); setTag1(myApp?.metadata?.tags[0] || '');
setTag2(myApp?.metadata?.tags[1] || ""); setTag2(myApp?.metadata?.tags[1] || '');
setTag3(myApp?.metadata?.tags[2] || ""); setTag3(myApp?.metadata?.tags[2] || '');
setTag4(myApp?.metadata?.tags[3] || ""); setTag4(myApp?.metadata?.tags[3] || '');
setTag5(myApp?.metadata?.tags[4] || ""); setTag5(myApp?.metadata?.tags[4] || '');
} }
} catch (error) { } catch (error) {
} finally { } finally {
setIsLoading(""); setIsLoading('');
} }
}, []); }, []);
@ -173,12 +157,12 @@ export const AppPublish = ({ names, categories }) => {
file, file,
}; };
const requiredFields = [ const requiredFields = [
"name", 'name',
"title", 'title',
"description", 'description',
"category", 'category',
"appType", 'appType',
"file", 'file',
]; ];
const missingFields: string[] = []; const missingFields: string[] = [];
@ -188,32 +172,33 @@ export const AppPublish = ({ names, categories }) => {
} }
}); });
if (missingFields.length > 0) { if (missingFields.length > 0) {
const missingFieldsString = missingFields.join(", "); const missingFieldsString = missingFields.join(', ');
const errorMsg = `Missing fields: ${missingFieldsString}`; const errorMsg = `Missing fields: ${missingFieldsString}`;
throw new Error(errorMsg); throw new Error(errorMsg);
} }
const fee = await getFee("ARBITRARY"); const fee = await getFee('ARBITRARY');
await show({ await show({
message: "Would you like to publish this app?", message: 'Would you like to publish this app?',
publishFee: fee.fee + " QORT", publishFee: fee.fee + ' QORT',
}); });
setIsLoading("Publishing... Please wait."); setIsLoading('Publishing... Please wait.');
const fileBase64 = await fileToBase64(file); const fileBase64 = await fileToBase64(file);
await new Promise((res, rej) => { await new Promise((res, rej) => {
window.sendMessage("publishOnQDN", { window
data: fileBase64, .sendMessage('publishOnQDN', {
service: appType, data: fileBase64,
title, service: appType,
description, title,
category, description,
tag1, category,
tag2, tag1,
tag3, tag2,
tag4, tag3,
tag5, tag4,
uploadType: "zip", tag5,
}) uploadType: 'zip',
})
.then((response) => { .then((response) => {
if (!response?.error) { if (!response?.error) {
res(response); res(response);
@ -222,14 +207,13 @@ export const AppPublish = ({ names, categories }) => {
rej(response.error); rej(response.error);
}) })
.catch((error) => { .catch((error) => {
rej(error.message || "An error occurred"); rej(error.message || 'An error occurred');
}); });
}); });
setInfoSnack({ setInfoSnack({
type: "success", type: 'success',
message: 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); setOpenSnack(true);
const dataObj = { const dataObj = {
@ -242,35 +226,49 @@ export const AppPublish = ({ names, categories }) => {
}, },
created: Date.now(), created: Date.now(),
}; };
executeEvent("addTab", { executeEvent('addTab', {
data: dataObj, data: dataObj,
}); });
} catch (error) { } catch (error) {
setInfoSnack({ setInfoSnack({
type: "error", type: 'error',
message: error?.message || "Unable to publish app", message: error?.message || 'Unable to publish app',
}); });
setOpenSnack(true); setOpenSnack(true);
} finally { } finally {
setIsLoading(""); setIsLoading('');
} }
}; };
return ( return (
<AppsLibraryContainer sx={{ <AppsLibraryContainer
height: !isMobile ? '100%' : 'auto', sx={{
paddingTop: !isMobile && '30px', alignItems: 'center',
alignItems: !isMobile && 'center' height: '100%',
}}> paddingTop: '30px',
<AppsWidthLimiter sx={{ }}
width: !isMobile ? 'auto' : '90%' >
}}> <AppsWidthLimiter
sx={{
width: 'auto',
}}
>
<AppLibrarySubTitle>Create Apps!</AppLibrarySubTitle> <AppLibrarySubTitle>Create Apps!</AppLibrarySubTitle>
<Spacer height="18px" /> <Spacer height="18px" />
<PublishQAppInfo> <PublishQAppInfo>
Note: Currently, only one App and Website is allowed per Name. Note: Currently, only one App and Website is allowed per Name.
</PublishQAppInfo> </PublishQAppInfo>
<Spacer height="18px" /> <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 <CustomSelect
placeholder="Select Name/App" placeholder="Select Name/App"
displayEmpty displayEmpty
@ -280,19 +278,26 @@ export const AppPublish = ({ names, categories }) => {
<CustomMenuItem value=""> <CustomMenuItem value="">
<em <em
style={{ style={{
color: "var(--50-white, #FFFFFF80)", color: 'var(--50-white, #FFFFFF80)',
}} }}
> >
Select Name/App Select Name/App
</em>{" "} </em>{' '}
{/* This is the placeholder item */} {/* This is the placeholder item */}
</CustomMenuItem> </CustomMenuItem>
{names.map((name) => { {names.map((name) => {
return <CustomMenuItem value={name}>{name}</CustomMenuItem>; return <CustomMenuItem value={name}>{name}</CustomMenuItem>;
})} })}
</CustomSelect> </CustomSelect>
<Spacer height="15px" /> <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 <CustomSelect
placeholder="SERVICE TYPE" placeholder="SERVICE TYPE"
displayEmpty displayEmpty
@ -302,59 +307,79 @@ export const AppPublish = ({ names, categories }) => {
<CustomMenuItem value=""> <CustomMenuItem value="">
<em <em
style={{ style={{
color: "var(--50-white, #FFFFFF80)", color: 'var(--50-white, #FFFFFF80)',
}} }}
> >
Select App Type Select App Type
</em>{" "} </em>{' '}
{/* This is the placeholder item */} {/* This is the placeholder item */}
</CustomMenuItem> </CustomMenuItem>
<CustomMenuItem value={"APP"}>App</CustomMenuItem> <CustomMenuItem value={'APP'}>App</CustomMenuItem>
<CustomMenuItem value={"WEBSITE"}>Website</CustomMenuItem> <CustomMenuItem value={'WEBSITE'}>Website</CustomMenuItem>
</CustomSelect> </CustomSelect>
<Spacer height="15px" /> <Spacer height="15px" />
<InputLabel sx={{ color: '#888', fontSize: '14px', marginBottom: '2px' }}>Title</InputLabel>
<InputLabel
sx={{ color: '#888', fontSize: '14px', marginBottom: '2px' }}
>
Title
</InputLabel>
<InputBase <InputBase
value={title} value={title}
onChange={(e) => setTitle(e.target.value)} onChange={(e) => setTitle(e.target.value)}
sx={{ sx={{
border: "0.5px solid var(--50-white, #FFFFFF80)", border: '0.5px solid var(--50-white, #FFFFFF80)',
padding: "0px 15px", padding: '0px 15px',
borderRadius: "5px", borderRadius: '5px',
height: "36px", height: '36px',
width: "100%", width: '100%',
maxWidth: "450px", maxWidth: '450px',
}} }}
placeholder="Title" placeholder="Title"
inputProps={{ inputProps={{
"aria-label": "Title", 'aria-label': 'Title',
fontSize: "14px", 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",
fontWeight: 400, fontWeight: 400,
}} }}
/> />
<Spacer height="15px" /> <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 <CustomSelect
displayEmpty displayEmpty
placeholder="Select Category" placeholder="Select Category"
@ -364,11 +389,11 @@ export const AppPublish = ({ names, categories }) => {
<CustomMenuItem value=""> <CustomMenuItem value="">
<em <em
style={{ style={{
color: "var(--50-white, #FFFFFF80)", color: 'var(--50-white, #FFFFFF80)',
}} }}
> >
Select Category Select Category
</em>{" "} </em>{' '}
{/* This is the placeholder item */} {/* This is the placeholder item */}
</CustomMenuItem> </CustomMenuItem>
{categories?.map((category) => { {categories?.map((category) => {
@ -379,23 +404,30 @@ export const AppPublish = ({ names, categories }) => {
); );
})} })}
</CustomSelect> </CustomSelect>
<Spacer height="15px" /> <Spacer height="15px" />
<InputLabel sx={{ color: '#888', fontSize: '14px', marginBottom: '2px' }}>Tags</InputLabel>
<InputLabel
sx={{ color: '#888', fontSize: '14px', marginBottom: '2px' }}
>
Tags
</InputLabel>
<AppPublishTagsContainer> <AppPublishTagsContainer>
<InputBase <InputBase
value={tag1} value={tag1}
onChange={(e) => setTag1(e.target.value)} onChange={(e) => setTag1(e.target.value)}
sx={{ sx={{
border: "0.5px solid var(--50-white, #FFFFFF80)", border: '0.5px solid var(--50-white, #FFFFFF80)',
padding: "0px 15px", padding: '0px 15px',
borderRadius: "5px", borderRadius: '5px',
height: "36px", height: '36px',
width: "100px", width: '100px',
}} }}
placeholder="Tag 1" placeholder="Tag 1"
inputProps={{ inputProps={{
"aria-label": "Tag 1", 'aria-label': 'Tag 1',
fontSize: "14px", fontSize: '14px',
fontWeight: 400, fontWeight: 400,
}} }}
/> />
@ -403,16 +435,16 @@ export const AppPublish = ({ names, categories }) => {
value={tag2} value={tag2}
onChange={(e) => setTag2(e.target.value)} onChange={(e) => setTag2(e.target.value)}
sx={{ sx={{
border: "0.5px solid var(--50-white, #FFFFFF80)", border: '0.5px solid var(--50-white, #FFFFFF80)',
padding: "0px 15px", padding: '0px 15px',
borderRadius: "5px", borderRadius: '5px',
height: "36px", height: '36px',
width: "100px", width: '100px',
}} }}
placeholder="Tag 2" placeholder="Tag 2"
inputProps={{ inputProps={{
"aria-label": "Tag 2", 'aria-label': 'Tag 2',
fontSize: "14px", fontSize: '14px',
fontWeight: 400, fontWeight: 400,
}} }}
/> />
@ -420,16 +452,16 @@ export const AppPublish = ({ names, categories }) => {
value={tag3} value={tag3}
onChange={(e) => setTag3(e.target.value)} onChange={(e) => setTag3(e.target.value)}
sx={{ sx={{
border: "0.5px solid var(--50-white, #FFFFFF80)", border: '0.5px solid var(--50-white, #FFFFFF80)',
padding: "0px 15px", padding: '0px 15px',
borderRadius: "5px", borderRadius: '5px',
height: "36px", height: '36px',
width: "100px", width: '100px',
}} }}
placeholder="Tag 3" placeholder="Tag 3"
inputProps={{ inputProps={{
"aria-label": "Tag 3", 'aria-label': 'Tag 3',
fontSize: "14px", fontSize: '14px',
fontWeight: 400, fontWeight: 400,
}} }}
/> />
@ -437,16 +469,16 @@ export const AppPublish = ({ names, categories }) => {
value={tag4} value={tag4}
onChange={(e) => setTag4(e.target.value)} onChange={(e) => setTag4(e.target.value)}
sx={{ sx={{
border: "0.5px solid var(--50-white, #FFFFFF80)", border: '0.5px solid var(--50-white, #FFFFFF80)',
padding: "0px 15px", padding: '0px 15px',
borderRadius: "5px", borderRadius: '5px',
height: "36px", height: '36px',
width: "100px", width: '100px',
}} }}
placeholder="Tag 4" placeholder="Tag 4"
inputProps={{ inputProps={{
"aria-label": "Tag 4", 'aria-label': 'Tag 4',
fontSize: "14px", fontSize: '14px',
fontWeight: 400, fontWeight: 400,
}} }}
/> />
@ -454,27 +486,31 @@ export const AppPublish = ({ names, categories }) => {
value={tag5} value={tag5}
onChange={(e) => setTag5(e.target.value)} onChange={(e) => setTag5(e.target.value)}
sx={{ sx={{
border: "0.5px solid var(--50-white, #FFFFFF80)", border: '0.5px solid var(--50-white, #FFFFFF80)',
padding: "0px 15px", padding: '0px 15px',
borderRadius: "5px", borderRadius: '5px',
height: "36px", height: '36px',
width: "100px", width: '100px',
}} }}
placeholder="Tag 5" placeholder="Tag 5"
inputProps={{ inputProps={{
"aria-label": "Tag 5", 'aria-label': 'Tag 5',
fontSize: "14px", fontSize: '14px',
fontWeight: 400, fontWeight: 400,
}} }}
/> />
</AppPublishTagsContainer> </AppPublishTagsContainer>
<Spacer height="30px" /> <Spacer height="30px" />
<PublishQAppInfo> <PublishQAppInfo>
Select .zip file containing static content:{" "} Select .zip file containing static content:{' '}
</PublishQAppInfo> </PublishQAppInfo>
<Spacer height="10px" /> <Spacer height="10px" />
<PublishQAppInfo>{`(${ <PublishQAppInfo>{`(${
appType === "APP" ? "50mb" : "400mb" appType === 'APP' ? '50mb' : '400mb'
} MB maximum)`}</PublishQAppInfo> } MB maximum)`}</PublishQAppInfo>
{file && ( {file && (
<> <>
@ -484,21 +520,25 @@ export const AppPublish = ({ names, categories }) => {
)} )}
<Spacer height="18px" /> <Spacer height="18px" />
<PublishQAppChoseFile {...getRootProps()}> <PublishQAppChoseFile {...getRootProps()}>
{" "} {' '}
<input {...getInputProps()} /> <input {...getInputProps()} />
Choose File Choose File
</PublishQAppChoseFile> </PublishQAppChoseFile>
<Spacer height="35px" /> <Spacer height="35px" />
<PublishQAppCTAButton <PublishQAppCTAButton
sx={{ sx={{
alignSelf: "center", alignSelf: 'center',
}} }}
onClick={publishApp} onClick={publishApp}
> >
Publish Publish
</PublishQAppCTAButton> </PublishQAppCTAButton>
</AppsWidthLimiter> </AppsWidthLimiter>
<LoadingSnackbar <LoadingSnackbar
open={!!isLoading} open={!!isLoading}
info={{ info={{
@ -512,7 +552,6 @@ export const AppPublish = ({ names, categories }) => {
info={infoSnack} info={infoSnack}
setInfo={setInfoSnack} setInfo={setInfoSnack}
/> />
</AppsLibraryContainer> </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"; export const AppViewer = React.forwardRef(
import { MyContext, getBaseApiReact, isMobile } from "../../App"; ({ 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"; useEffect(() => {
import { useFrame } from "react-frame-component"; if (app?.isPreview) return;
import { useQortalMessageListener } from "./useQortalMessageListener"; if (isDevMode) {
import { useThemeContext } from "../Theme/ThemeContext"; 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) => {
export const AppViewer = React.forwardRef(({ app , hide, isDevMode, skipAuth}, iframeRef) => { const { tabId } = e.detail;
const { rootHeight } = useContext(MyContext); if (tabId === app?.tabId) {
// const iframeRef = useRef(null); if (isDevMode) {
const { window: frameWindow } = useFrame(); resetHistory();
const {path, history, changeCurrentIndex, resetHistory} = useQortalMessageListener(frameWindow, iframeRef, app?.tabId, isDevMode, app?.name, app?.service, skipAuth) if (!app?.isPreview || app?.isPrivate) {
const [url, setUrl] = useState('') setUrl(app?.url + `?time=${Date.now()}`);
const { themeMode } = useThemeContext(); }
return;
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()}`)
} }
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(()=> { useEffect(() => {
if(!iframeRef?.current) return subscribeToEvent('refreshApp', refreshAppFunc);
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])
const removeTrailingSlash = (str) => str.replace(/\/$/, ''); return () => {
const copyLinkFunc = (e) => { unsubscribeFromEvent('refreshApp', refreshAppFunc);
const {tabId} = e.detail };
if(tabId === app?.tabId){ }, [app, path, isDevMode]);
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);
});
}
};
useEffect(() => { useEffect(() => {
subscribeToEvent("copyLink", copyLinkFunc); if (!iframeRef?.current) return;
const targetOrigin = iframeRef.current
return () => { ? new URL(iframeRef.current.src).origin
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 : "*";
// Send the navigation command after setting up the listener and timeout // Send the navigation command after setting up the listener and timeout
iframeRef.current.contentWindow.postMessage( 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 const removeTrailingSlash = (str) => str.replace(/\/$/, '');
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) => { const copyLinkFunc = (e) => {
const { tabId } = e.detail;
navigateBackInIframe() if (tabId === app?.tabId) {
}; let link = 'qortal://' + app?.service + '/' + app?.name;
if (path && path.startsWith('/')) {
useEffect(() => { link = link + removeTrailingSlash(path);
if(!app?.tabId) return }
subscribeToEvent(`navigateBackApp-${app?.tabId}`, navigateBackAppFunc); if (path && !path.startsWith('/')) {
link = link + '/' + removeTrailingSlash(path);
return () => { }
unsubscribeFromEvent(`navigateBackApp-${app?.tabId}`, navigateBackAppFunc); 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 return () => {
const navigateForwardInIframe = async () => { unsubscribeFromEvent('copyLink', copyLinkFunc);
};
}, [app, path]);
// Function to navigate back in iframe
if (iframeRef.current && iframeRef.current.contentWindow) { const navigateBackInIframe = async () => {
const targetOrigin = iframeRef.current ? new URL(iframeRef.current.src).origin : "*"; if (
iframeRef.current.contentWindow.postMessage( iframeRef.current &&
{ action: 'NAVIGATE_FORWARD'}, 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 targetOrigin
); );
} else { // Update the current index locally
console.log('Iframe not accessible or does not have a content window.'); 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 { AppViewer } from './AppViewer';
import Frame from 'react-frame-component'; 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 AppViewerContainer = React.forwardRef(
const { rootHeight } = useContext(MyContext); ({ app, isSelected, hide, isDevMode, customHeight, skipAuth }, ref) => {
const { rootHeight } = useContext(MyContext);
return (
return ( <Frame
<Frame id={`browser-iframe-${app?.tabId}`}
id={`browser-iframe-${app?.tabId}`} head={
<>
head={ <style>
<> {`
<style>
{`
body { body {
margin: 0; margin: 0;
padding: 0; padding: 0;
@ -28,24 +27,31 @@ const AppViewerContainer = React.forwardRef(({ app, isSelected, hide, isDevMode,
} }
.frame-content { .frame-content {
overflow: hidden; overflow: hidden;
height: ${!isMobile ? '100vh' : `calc(${rootHeight} - 60px - 45px)`}; height: '100vh';
} }
`} `}
</style> </style>
</> </>
} }
style={{ style={{
position: (!isSelected || hide) && 'fixed', border: 'none',
left: (!isSelected || hide) && '-200vw', height: '100vh',
height: customHeight ? customHeight : !isMobile ? '100vh' : `calc(${rootHeight} - 60px - 45px)`, left: (!isSelected || hide) && '-200vw',
border: 'none', overflow: 'hidden',
width: '100%', position: (!isSelected || hide) && 'fixed',
overflow: 'hidden', width: '100%',
}} }}
> >
<AppViewer skipAuth={skipAuth} app={app} ref={ref} hide={!isSelected || hide} isDevMode={isDevMode} /> <AppViewer
</Frame> skipAuth={skipAuth}
); app={app}
}); ref={ref}
hide={!isSelected || hide}
isDevMode={isDevMode}
/>
</Frame>
);
}
);
export default AppViewerContainer; export default AppViewerContainer;

View File

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

View File

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

View File

@ -1,17 +1,12 @@
import React, { useMemo, useState } from 'react';
import { import {
AppCircle, AppCircle,
AppCircleContainer, AppCircleContainer,
AppCircleLabel, AppCircleLabel,
AppLibrarySubTitle, AppLibrarySubTitle,
AppsContainer, AppsContainer,
AppsParent,
} from './Apps-styles'; } from './Apps-styles';
import { Avatar, ButtonBase } from '@mui/material'; import { ButtonBase } from '@mui/material';
import { Add } from '@mui/icons-material'; import { Add } from '@mui/icons-material';
import { getBaseApiReact, isMobile } from '../../App';
import LogoSelected from '../../assets/svgs/LogoSelected.svg';
import { executeEvent } from '../../utils/events';
import { SortablePinnedApps } from './SortablePinnedApps'; import { SortablePinnedApps } from './SortablePinnedApps';
import { Spacer } from '../../common/Spacer'; import { Spacer } from '../../common/Spacer';
@ -35,7 +30,7 @@ export const AppsHome = ({ setMode, myApp, myWebsite, availableQapps }) => {
> >
<AppCircleContainer <AppCircleContainer
sx={{ sx={{
gap: !isMobile ? '10px' : '5px', gap: '10px',
}} }}
> >
<AppCircle> <AppCircle>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -20,7 +20,6 @@ import { CustomizedSnackbars } from '../Snackbar/Snackbar';
import { import {
getBaseApiReact, getBaseApiReact,
getBaseApiReactSocket, getBaseApiReactSocket,
isMobile,
pauseAllQueues, pauseAllQueues,
resumeAllQueues, resumeAllQueues,
} from '../../App'; } from '../../App';
@ -410,16 +409,6 @@ export const ChatDirect = ({
if (editorRef.current) { if (editorRef.current) {
setMessageSize(0); setMessageSize(0);
editorRef.current.chain().focus().clearContent().run(); 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(() => { useEffect(() => {
@ -547,108 +536,41 @@ export const ChatDirect = ({
background: theme.palette.background.default, background: theme.palette.background.default,
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
height: isMobile ? '100%' : '100vh', height: '100vh',
width: '100%', width: '100%',
}} }}
> >
{!isMobile && ( <Box
<Box onClick={close}
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={{ sx={{
alignItems: 'center', color: theme.palette.text.primary,
alignSelf: 'center', fontSize: '20px',
background: theme.palette.background.default, }}
borderRadius: '3px', />
cursor: 'pointer', <Typography
display: 'flex', sx={{
gap: '5px', color: theme.palette.text.primary,
margin: '10px 0px', fontSize: '14px',
padding: '4px 6px',
width: 'fit-content',
}} }}
> >
<ArrowBackIcon Close Direct Chat
sx={{ </Typography>
color: theme.palette.text.primary, </Box>
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>
)}
{isNewChat && ( {isNewChat && (
<> <>
<Spacer height="30px" /> <Spacer height="30px" />
@ -685,7 +607,7 @@ export const ChatDirect = ({
flexShrink: 0, flexShrink: 0,
minHeight: '150px', minHeight: '150px',
overflow: 'hidden', overflow: 'hidden',
padding: isMobile ? '10px' : '20px', padding: '20px',
position: isFocusedParent ? 'fixed' : 'relative', position: isFocusedParent ? 'fixed' : 'relative',
top: isFocusedParent ? '0px' : 'unset', top: isFocusedParent ? '0px' : 'unset',
width: '100%', width: '100%',
@ -753,7 +675,7 @@ export const ChatDirect = ({
setEditorRef={setEditorRef} setEditorRef={setEditorRef}
onEnter={sendMessage} onEnter={sendMessage}
isChat isChat
disableEnter={isMobile ? true : false} disableEnter={false}
setIsFocusedParent={setIsFocusedParent} setIsFocusedParent={setIsFocusedParent}
/> />
{messageSize > 750 && ( {messageSize > 750 && (

View File

@ -20,7 +20,6 @@ import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar';
import { import {
getBaseApiReact, getBaseApiReact,
getBaseApiReactSocket, getBaseApiReactSocket,
isMobile,
MyContext, MyContext,
pauseAllQueues, pauseAllQueues,
resumeAllQueues, resumeAllQueues,
@ -746,16 +745,6 @@ export const ChatGroup = ({
if (editorRef.current) { if (editorRef.current) {
setMessageSize(0); setMessageSize(0);
editorRef.current.chain().focus().clearContent().run(); 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} setEditorRef={setEditorRef}
onEnter={sendMessage} onEnter={sendMessage}
isChat isChat
disableEnter={isMobile ? true : false} disableEnter={false}
isFocusedParent={isFocusedParent} isFocusedParent={isFocusedParent}
setIsFocusedParent={setIsFocusedParent} setIsFocusedParent={setIsFocusedParent}
membersWithNames={members} membersWithNames={members}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,244 +1,277 @@
import * as React from "react"; import * as React from 'react';
import List from "@mui/material/List"; import List from '@mui/material/List';
import ListItem from "@mui/material/ListItem"; import ListItem from '@mui/material/ListItem';
import ListItemButton from "@mui/material/ListItemButton"; import ListItemButton from '@mui/material/ListItemButton';
import ListItemIcon from "@mui/material/ListItemIcon"; import ListItemText from '@mui/material/ListItemText';
import ListItemText from "@mui/material/ListItemText"; import IconButton from '@mui/material/IconButton';
import Checkbox from "@mui/material/Checkbox"; import { RequestQueueWithPromise } from '../../utils/queue/queue';
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 GroupAddIcon from '@mui/icons-material/GroupAdd'; import GroupAddIcon from '@mui/icons-material/GroupAdd';
import { executeEvent } from "../../utils/events"; import { executeEvent } from '../../utils/events';
import { Box, ButtonBase, Collapse, Typography } from "@mui/material"; import { Box, ButtonBase, Collapse, Typography } from '@mui/material';
import { Spacer } from "../../common/Spacer"; import { CustomLoader } from '../../common/CustomLoader';
import { CustomLoader } from "../../common/CustomLoader"; import { MyContext, getBaseApiReact } from '../../App';
import { getBaseApi } from "../../background"; import { myGroupsWhereIAmAdminAtom } from '../../atoms/global';
import { MyContext, getBaseApiReact, isMobile } from "../../App"; import { useSetRecoilState } from 'recoil';
import { myGroupsWhereIAmAdminAtom } from "../../atoms/global";
import { useSetRecoilState } from "recoil";
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import ExpandLessIcon from '@mui/icons-material/ExpandLess'; 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 }) => { export const GroupJoinRequests = ({
const [isExpanded, setIsExpanded] = React.useState(false) myAddress,
groups,
const [groupsWithJoinRequests, setGroupsWithJoinRequests] = React.useState([]) setOpenManageMembers,
const [loading, setLoading] = React.useState(true) getTimestampEnterChat,
const {txList, setTxList} = React.useContext(MyContext) setSelectedGroup,
const setMyGroupsWhereIAmAdmin = useSetRecoilState( setGroupSection,
myGroupsWhereIAmAdminAtom 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 { try {
setLoading(true) setLoading(true);
let groupsAsAdmin = [] let groupsAsAdmin = [];
const getAllGroupsAsAdmin = groups.filter((item)=> item.groupId !== '0').map(async (group)=> { const getAllGroupsAsAdmin = groups
.filter((item) => item.groupId !== '0')
const isAdminResponse = await requestQueueGroupJoinRequests.enqueue(()=> { .map(async (group) => {
return fetch( const isAdminResponse = await requestQueueGroupJoinRequests.enqueue(
`${getBaseApiReact()}/groups/members/${group.groupId}?limit=0&onlyAdmins=true` () => {
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) const findMyself = isAdminData?.members?.find(
(member) => member.member === myAddress
if(findMyself){ );
groupsAsAdmin.push(group)
}
return true
})
if (findMyself) {
await Promise.all(getAllGroupsAsAdmin) groupsAsAdmin.push(group);
setMyGroupsWhereIAmAdmin(groupsAsAdmin) }
const res = await Promise.all(groupsAsAdmin.map(async (group)=> { return true;
});
const joinRequestResponse = await requestQueueGroupJoinRequests.enqueue(()=> { await Promise.all(getAllGroupsAsAdmin);
return fetch( setMyGroupsWhereIAmAdmin(groupsAsAdmin);
`${getBaseApiReact()}/groups/joinrequests/${group.groupId}` 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() const joinRequestData = await joinRequestResponse.json();
return { return {
group, group,
data: joinRequestData data: joinRequestData,
} };
})) })
setGroupsWithJoinRequests(res) );
setGroupsWithJoinRequests(res);
} catch (error) { } catch (error) {
} finally { } finally {
setLoading(false) setLoading(false);
} }
} };
React.useEffect(() => { React.useEffect(() => {
if (myAddress && groups.length > 0) { if (myAddress && groups.length > 0) {
getJoinRequests() getJoinRequests();
} else { } else {
setLoading(false) setLoading(false);
} }
}, [myAddress, groups]); }, [myAddress, groups]);
const filteredJoinRequests = React.useMemo(()=> { const filteredJoinRequests = React.useMemo(() => {
return groupsWithJoinRequests.map((group)=> { return groupsWithJoinRequests.map((group) => {
const filteredGroupRequests = group?.data?.filter((gd)=> { 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 findJoinRequsetInTxList = txList?.find(
(tx) =>
tx?.groupId === group?.group?.groupId &&
tx?.qortalAddress === gd?.joiner &&
tx?.type === 'join-request-accept'
);
if(findJoinRequsetInTxList) return false if (findJoinRequsetInTxList) return false;
return true return true;
}) });
return { return {
...group, ...group,
data: filteredGroupRequests data: filteredGroupRequests,
} };
}) });
}, [groupsWithJoinRequests, txList]) }, [groupsWithJoinRequests, txList]);
return ( return (
<Box sx={{ <Box
width: "100%", sx={{
display: "flex", width: '100%',
flexDirection: "column", display: 'flex',
alignItems: 'center' flexDirection: 'column',
}}> alignItems: 'center',
}}
>
<ButtonBase <ButtonBase
sx={{ sx={{
width: "322px", width: '322px',
display: "flex", display: 'flex',
flexDirection: "row", flexDirection: 'row',
padding: '0px 20px', padding: '0px 20px',
gap: '10px', gap: '10px',
justifyContent: 'flex-start' justifyContent: 'flex-start',
}} }}
onClick={()=> setIsExpanded((prev)=> !prev)} onClick={() => setIsExpanded((prev) => !prev)}
> >
<Typography <Typography
sx={{ 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> </Typography>
{isExpanded ? <ExpandLessIcon sx={{ {isExpanded ? (
marginLeft: 'auto' <ExpandLessIcon
}} /> : ( sx={{
<ExpandMoreIcon sx={{ marginLeft: 'auto',
marginLeft: 'auto' }}
}}/> />
)} ) : (
<ExpandMoreIcon
sx={{
marginLeft: 'auto',
}}
/>
)}
</ButtonBase> </ButtonBase>
<Collapse in={isExpanded} timeout="auto" unmountOnExit> <Collapse in={isExpanded} timeout="auto" unmountOnExit>
<Box <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);
}}
sx={{ sx={{
marginBottom: '20px' width: '322px',
}} height: '250px',
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>
)
})} display: 'flex',
flexDirection: 'column',
bgcolor: 'background.paper',
padding: '20px',
</List> borderRadius: '19px',
</Box> }}
</Collapse> >
{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> </Box>
); );
}; };

View File

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

View File

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

View File

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

View File

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

View File

@ -1,28 +1,23 @@
import * as React from "react"; import * as React from 'react';
import List from "@mui/material/List"; import List from '@mui/material/List';
import ListItem from "@mui/material/ListItem"; import ListItem from '@mui/material/ListItem';
import ListItemButton from "@mui/material/ListItemButton"; import ListItemButton from '@mui/material/ListItemButton';
import ListItemIcon from "@mui/material/ListItemIcon"; import ListItemIcon from '@mui/material/ListItemIcon';
import ListItemText from "@mui/material/ListItemText"; import ListItemText from '@mui/material/ListItemText';
import Checkbox from "@mui/material/Checkbox"; import { Box, Typography } from '@mui/material';
import IconButton from "@mui/material/IconButton"; import { Spacer } from '../../common/Spacer';
import CommentIcon from "@mui/icons-material/Comment"; import { QMailMessages } from './QMailMessages';
import InfoIcon from "@mui/icons-material/Info"; import { executeEvent } from '../../utils/events';
import { Box, Typography } from "@mui/material";
import { Spacer } from "../../common/Spacer";
import { isMobile } from "../../App";
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 [checked1, setChecked1] = React.useState(false);
const [checked2, setChecked2] = React.useState(false); const [checked2, setChecked2] = React.useState(false);
// const [checked3, setChecked3] = React.useState(false);
// React.useEffect(() => {
// if (hasGroups) setChecked3(true);
// }, [hasGroups]);
React.useEffect(() => { React.useEffect(() => {
if (balance && +balance >= 6) { if (balance && +balance >= 6) {
@ -30,111 +25,114 @@ export const ThingsToDoInitial = ({ myAddress, name, hasGroups, balance, userInf
} }
}, [balance]); }, [balance]);
React.useEffect(() => { React.useEffect(() => {
if (name) setChecked2(true); if (name) setChecked2(true);
}, [name]); }, [name]);
const isLoaded = React.useMemo(() => {
if (userInfo !== null) return true;
return false;
}, [userInfo]);
const isLoaded = React.useMemo(()=> { const hasDoneNameAndBalanceAndIsLoaded = React.useMemo(() => {
if(userInfo !== null) return true if (isLoaded && checked1 && checked2) return true;
return false return false;
}, [ userInfo]) }, [checked1, isLoaded, checked2]);
const hasDoneNameAndBalanceAndIsLoaded = React.useMemo(()=> { if (hasDoneNameAndBalanceAndIsLoaded) {
if(isLoaded && checked1 && checked2) return true return (
return false <QMailMessages
}, [checked1, isLoaded, checked2]) userAddress={userInfo?.address}
userName={userInfo?.name}
if(hasDoneNameAndBalanceAndIsLoaded){ />
return ( );
<QMailMessages userAddress={userInfo?.address} userName={userInfo?.name} /> }
); if (!isLoaded) return null;
}
if(!isLoaded) return null
return ( return (
<Box <Box
sx={{ sx={{
width: "100%", width: '100%',
display: "flex", display: 'flex',
flexDirection: "column", flexDirection: 'column',
alignItems: "center", alignItems: 'center',
}} }}
> >
<Box <Box
sx={{ sx={{
width: "322px", width: '322px',
display: "flex", display: 'flex',
flexDirection: "column", flexDirection: 'column',
padding: "0px 20px", padding: '0px 20px',
}} }}
> >
<Typography <Typography
sx={{ sx={{
fontSize: "1rem", fontSize: '1rem',
fontWeight: 600, fontWeight: 600,
}} }}
> >
{!isLoaded ? 'Loading...' : 'Getting Started' } {!isLoaded ? 'Loading...' : 'Getting Started'}
</Typography> </Typography>
<Spacer height="10px" /> <Spacer height="10px" />
</Box> </Box>
<Box <Box
sx={{ sx={{
width: "322px", width: '322px',
display: "flex", display: 'flex',
flexDirection: "column", flexDirection: 'column',
bgcolor: "background.paper", bgcolor: 'background.paper',
padding: "20px", padding: '20px',
borderRadius: "19px", borderRadius: '19px',
}} }}
> >
{isLoaded && ( {isLoaded && (
<List sx={{ width: "100%", maxWidth: 360 }}> <List sx={{ width: '100%', maxWidth: 360 }}>
<ListItem <ListItem
disablePadding
disablePadding sx={{
sx={{ marginBottom: '20px',
marginBottom: '20px' }}
}} >
> <ListItemButton
<ListItemButton sx={{
sx={{ padding: '0px',
padding: "0px", }}
}} disableRipple
disableRipple role={undefined}
role={undefined} dense
dense onClick={() => {
onClick={()=> { executeEvent('openBuyQortInfo', {});
executeEvent("openBuyQortInfo", {}) }}
}} >
> <ListItemText
<ListItemText sx={{
sx={{ '& .MuiTypography-root': {
"& .MuiTypography-root": { fontSize: '1rem',
fontSize: "1rem", fontWeight: 400,
fontWeight: 400, },
}, }}
}} primary={`Have at least 6 QORT in your wallet`}
primary={`Have at least 6 QORT in your wallet`} />
/> <ListItemIcon
<ListItemIcon sx={{
sx={{ justifyContent: 'flex-end',
justifyContent: "flex-end", }}
}} >
> <Box
<Box sx={{
sx={{ height: '18px',
height: "18px", width: '18px',
width: "18px", borderRadius: '50%',
borderRadius: "50%", backgroundColor: checked1
backgroundColor: checked1 ? "rgba(9, 182, 232, 1)" : "transparent", ? 'rgba(9, 182, 232, 1)'
outline: "1px solid rgba(9, 182, 232, 1)", : 'transparent',
}} outline: '1px solid rgba(9, 182, 232, 1)',
/> }}
{/* <Checkbox />
{/* <Checkbox
edge="start" edge="start"
checked={checked1} checked={checked1}
tabIndex={-1} tabIndex={-1}
@ -149,52 +147,64 @@ if(!isLoaded) return null
}, },
}} }}
/> */} /> */}
</ListItemIcon> </ListItemIcon>
</ListItemButton> </ListItemButton>
</ListItem> </ListItem>
<ListItem <ListItem
sx={{ sx={{
marginBottom: '20px' marginBottom: '20px',
}} }}
// secondaryAction={ // secondaryAction={
// <IconButton edge="end" aria-label="comments"> // <IconButton edge="end" aria-label="comments">
// <InfoIcon // <InfoIcon
// sx={{ // sx={{
// color: "white", // color: "white",
// }} // }}
// /> // />
// </IconButton> // </IconButton>
// } // }
disablePadding disablePadding
> >
<ListItemButton sx={{ <ListItemButton
padding: "0px", sx={{
}} disableRipple role={undefined} dense> padding: '0px',
}}
<ListItemText onClick={() => { disableRipple
executeEvent('openRegisterName', {}) role={undefined}
}} sx={{ dense
"& .MuiTypography-root": { >
fontSize: "1rem", <ListItemText
fontWeight: 400, onClick={() => {
}, executeEvent('openRegisterName', {});
}} primary={`Register a name`} /> }}
<ListItemIcon sx={{ sx={{
justifyContent: "flex-end", '& .MuiTypography-root': {
}}> fontSize: '1rem',
<Box fontWeight: 400,
sx={{ },
height: "18px", }}
width: "18px", primary={`Register a name`}
borderRadius: "50%", />
backgroundColor: checked2 ? "rgba(9, 182, 232, 1)" : "transparent", <ListItemIcon
outline: "1px solid rgba(9, 182, 232, 1)", sx={{
}} justifyContent: 'flex-end',
/> }}
</ListItemIcon> >
</ListItemButton> <Box
</ListItem> sx={{
{/* <ListItem 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 disablePadding
> >
<ListItemButton sx={{ <ListItemButton sx={{
@ -222,9 +232,8 @@ if(!isLoaded) return null
</ListItemIcon> </ListItemIcon>
</ListItemButton> </ListItemButton>
</ListItem> */} </ListItem> */}
</List> </List>
)} )}
</Box> </Box>
</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 ReactDOM from 'react-dom';
import Picker, { EmojiStyle, Theme } from 'emoji-picker-react'; import Picker, { EmojiStyle, Theme } from 'emoji-picker-react';
import './ReactionPicker.css'; import './ReactionPicker.css';
import { ButtonBase } from '@mui/material'; import { ButtonBase } from '@mui/material';
import { isMobile } from '../App';
export const ReactionPicker = ({ onReaction }) => { export const ReactionPicker = ({ onReaction }) => {
const [showPicker, setShowPicker] = useState(false); const [showPicker, setShowPicker] = useState(false);
@ -30,7 +29,7 @@ export const ReactionPicker = ({ onReaction }) => {
} else { } else {
// Get the button's position // Get the button's position
const buttonRect = buttonRef.current.getBoundingClientRect(); 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 // Calculate position to align the right edge of the picker with the button's right edge
setPickerPosition({ setPickerPosition({
@ -90,15 +89,15 @@ export const ReactionPicker = ({ onReaction }) => {
}} }}
> >
<Picker <Picker
height={isMobile ? 350 : 450}
width={isMobile ? 300 : 350}
reactionsDefaultOpen={true}
onReactionClick={handleReaction}
onEmojiClick={handlePicker}
allowExpandReactions={true} allowExpandReactions={true}
autoFocusSearch={false} autoFocusSearch={false}
theme={Theme.DARK}
emojiStyle={EmojiStyle.NATIVE} emojiStyle={EmojiStyle.NATIVE}
height="450"
onEmojiClick={handlePicker}
onReactionClick={handleReaction}
reactionsDefaultOpen={true}
theme={Theme.DARK}
width="350"
/> />
</div>, </div>,
document.body document.body

View File

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

View File

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