mirror of
https://github.com/Qortal/Qortal-Hub.git
synced 2025-07-23 04:36:52 +00:00
started app browser
This commit is contained in:
@@ -1,22 +1,102 @@
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import {
|
||||
|
||||
AppCircle,
|
||||
AppCircleContainer,
|
||||
AppCircleLabel,
|
||||
AppDownloadButton,
|
||||
AppDownloadButtonText,
|
||||
AppInfoAppName,
|
||||
AppInfoSnippetContainer,
|
||||
AppInfoSnippetLeft,
|
||||
AppInfoSnippetMiddle,
|
||||
AppInfoSnippetRight,
|
||||
AppInfoUserName,
|
||||
AppsLibraryContainer,
|
||||
|
||||
AppsParent,
|
||||
} from "./Apps-styles";
|
||||
import { Avatar, Box, ButtonBase, InputBase } from "@mui/material";
|
||||
import { Add } from "@mui/icons-material";
|
||||
import { getBaseApiReact } from "../../App";
|
||||
import LogoSelected from "../../assets/svgs/LogoSelected.svg";
|
||||
|
||||
|
||||
import { Spacer } from "../../common/Spacer";
|
||||
import { executeEvent } from "../../utils/events";
|
||||
|
||||
export const AppInfo = ({ app }) => {
|
||||
|
||||
|
||||
|
||||
const isInstalled = app?.status?.status === 'READY'
|
||||
return (
|
||||
<AppsLibraryContainer>
|
||||
<AppInfoSnippetContainer>
|
||||
<AppInfoSnippetLeft sx={{
|
||||
flexGrow: 1,
|
||||
gap: '18px'
|
||||
}}>
|
||||
|
||||
<AppCircleContainer sx={{
|
||||
width: 'auto'
|
||||
}}>
|
||||
<AppCircle
|
||||
sx={{
|
||||
border: "none",
|
||||
height: '100px',
|
||||
width: '100px'
|
||||
}}
|
||||
>
|
||||
<Avatar
|
||||
sx={{
|
||||
height: "43px",
|
||||
width: "43px",
|
||||
'& img': {
|
||||
objectFit: 'fill',
|
||||
}
|
||||
}}
|
||||
alt={app?.name}
|
||||
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
|
||||
app?.name
|
||||
}/qortal_avatar?async=true`}
|
||||
>
|
||||
<img
|
||||
style={{
|
||||
width: "43px",
|
||||
height: "auto",
|
||||
}}
|
||||
src={LogoSelected}
|
||||
alt="center-icon"
|
||||
/>
|
||||
</Avatar>
|
||||
</AppCircle>
|
||||
</AppCircleContainer>
|
||||
<AppInfoSnippetMiddle>
|
||||
|
||||
</AppsLibraryContainer>
|
||||
<AppInfoAppName >
|
||||
{app?.metadata?.title || app?.name}
|
||||
</AppInfoAppName>
|
||||
<Spacer height="6px" />
|
||||
<AppInfoUserName>
|
||||
{ app?.name}
|
||||
</AppInfoUserName>
|
||||
<Spacer height="3px" />
|
||||
|
||||
</AppInfoSnippetMiddle>
|
||||
|
||||
</AppInfoSnippetLeft>
|
||||
<AppInfoSnippetRight>
|
||||
|
||||
</AppInfoSnippetRight>
|
||||
</AppInfoSnippetContainer>
|
||||
<Spacer height="11px" />
|
||||
<AppDownloadButton sx={{
|
||||
backgroundColor: isInstalled ? '#0091E1' : '#247C0E',
|
||||
width: '100%',
|
||||
maxWidth: '320px',
|
||||
height: '29px'
|
||||
}}>
|
||||
<AppDownloadButtonText>{isInstalled ? 'Open' : 'Download'}</AppDownloadButtonText>
|
||||
</AppDownloadButton>
|
||||
</AppsLibraryContainer>
|
||||
|
||||
|
||||
);
|
||||
};
|
||||
|
100
src/components/Apps/AppInfoSnippet.tsx
Normal file
100
src/components/Apps/AppInfoSnippet.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import {
|
||||
AppCircle,
|
||||
AppCircleContainer,
|
||||
AppCircleLabel,
|
||||
AppDownloadButton,
|
||||
AppDownloadButtonText,
|
||||
AppInfoAppName,
|
||||
AppInfoSnippetContainer,
|
||||
AppInfoSnippetLeft,
|
||||
AppInfoSnippetMiddle,
|
||||
AppInfoSnippetRight,
|
||||
AppInfoUserName,
|
||||
AppsLibraryContainer,
|
||||
} from "./Apps-styles";
|
||||
import { Avatar, Box, ButtonBase, InputBase } from "@mui/material";
|
||||
import { Add } from "@mui/icons-material";
|
||||
import { getBaseApiReact } from "../../App";
|
||||
import LogoSelected from "../../assets/svgs/LogoSelected.svg";
|
||||
|
||||
import { Spacer } from "../../common/Spacer";
|
||||
import { executeEvent } from "../../utils/events";
|
||||
|
||||
export const AppInfoSnippet = ({ app }) => {
|
||||
|
||||
|
||||
const isInstalled = app?.status?.status === 'READY'
|
||||
return (
|
||||
<AppInfoSnippetContainer>
|
||||
<AppInfoSnippetLeft>
|
||||
<ButtonBase
|
||||
sx={{
|
||||
height: "80px",
|
||||
width: "60px",
|
||||
}}
|
||||
onClick={()=> {
|
||||
executeEvent("selectedAppInfo", {
|
||||
data: app,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<AppCircleContainer>
|
||||
<AppCircle
|
||||
sx={{
|
||||
border: "none",
|
||||
}}
|
||||
>
|
||||
<Avatar
|
||||
sx={{
|
||||
height: "31px",
|
||||
width: "31px",
|
||||
'& 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={()=> {
|
||||
executeEvent("selectedAppInfo", {
|
||||
data: app,
|
||||
});
|
||||
}}>
|
||||
<AppInfoAppName >
|
||||
{app?.metadata?.title || app?.name}
|
||||
</AppInfoAppName>
|
||||
</ButtonBase>
|
||||
<Spacer height="6px" />
|
||||
<AppInfoUserName>
|
||||
{ app?.name}
|
||||
</AppInfoUserName>
|
||||
<Spacer height="3px" />
|
||||
</AppInfoSnippetMiddle>
|
||||
</AppInfoSnippetLeft>
|
||||
<AppInfoSnippetRight>
|
||||
<AppDownloadButton sx={{
|
||||
backgroundColor: isInstalled ? '#0091E1' : '#247C0E',
|
||||
|
||||
}}>
|
||||
<AppDownloadButtonText>{isInstalled ? 'Open' : 'Download'}</AppDownloadButtonText>
|
||||
</AppDownloadButton>
|
||||
</AppInfoSnippetRight>
|
||||
</AppInfoSnippetContainer>
|
||||
);
|
||||
};
|
46
src/components/Apps/AppViewer.tsx
Normal file
46
src/components/Apps/AppViewer.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import React, { useContext, useEffect, useMemo, useRef, useState } from "react";
|
||||
import {
|
||||
AppCircle,
|
||||
AppCircleContainer,
|
||||
AppCircleLabel,
|
||||
AppDownloadButton,
|
||||
AppDownloadButtonText,
|
||||
AppInfoAppName,
|
||||
AppInfoSnippetContainer,
|
||||
AppInfoSnippetLeft,
|
||||
AppInfoSnippetMiddle,
|
||||
AppInfoSnippetRight,
|
||||
AppInfoUserName,
|
||||
AppsLibraryContainer,
|
||||
AppsParent,
|
||||
} from "./Apps-styles";
|
||||
import { Avatar, Box, ButtonBase, InputBase } from "@mui/material";
|
||||
import { Add } from "@mui/icons-material";
|
||||
import { MyContext, getBaseApiReact } from "../../App";
|
||||
import LogoSelected from "../../assets/svgs/LogoSelected.svg";
|
||||
|
||||
import { Spacer } from "../../common/Spacer";
|
||||
import { executeEvent } from "../../utils/events";
|
||||
|
||||
export const AppViewer = ({ app }) => {
|
||||
const { rootHeight } = useContext(MyContext);
|
||||
const iframeRef = useRef(null);
|
||||
|
||||
|
||||
const url = useMemo(()=> {
|
||||
return `${getBaseApiReact()}/render/${app?.service}/${app?.name}${app?.path != null ? app?.path : ''}?theme=dark&identifier=${(app?.identifier != null && app?.identifier != 'null') ? app?.identifier : ''}`
|
||||
}, [app?.service, app?.name, app?.identifier, app?.path])
|
||||
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<iframe ref={iframeRef} style={{
|
||||
height: `calc(${rootHeight} - 60px - 45px)`,
|
||||
border: 'none',
|
||||
width: '100%'
|
||||
}} id="browser-iframe" src={url} sandbox="allow-scripts allow-same-origin allow-forms allow-downloads allow-modals" allow="fullscreen">
|
||||
|
||||
</iframe>
|
||||
);
|
||||
};
|
40
src/components/Apps/AppViewerContainer.tsx
Normal file
40
src/components/Apps/AppViewerContainer.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import React, { useContext, useRef } from 'react'
|
||||
import { AppViewer } from './AppViewer'
|
||||
import Frame from 'react-frame-component';
|
||||
import { MyContext } from '../../App';
|
||||
|
||||
const AppViewerContainer = ({app, isSelected}) => {
|
||||
const { rootHeight } = useContext(MyContext);
|
||||
const frameRef = useRef(null);
|
||||
|
||||
return (
|
||||
<Frame id={`browser-iframe-${app?.tabId}` } ref={frameRef} head={
|
||||
<>
|
||||
{/* Inject styles directly into the iframe */}
|
||||
<style>
|
||||
{`
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
/* Hide scrollbars for all elements */
|
||||
* {
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
*::-webkit-scrollbar {
|
||||
display: none; /* Chrome, Safari, Opera */
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
</>
|
||||
} style={{
|
||||
height: `calc(${rootHeight} - 60px - 45px)`,
|
||||
border: 'none',
|
||||
width: '100%',
|
||||
display: !isSelected && 'none'
|
||||
}} ><AppViewer app={app} /></Frame>
|
||||
)
|
||||
}
|
||||
|
||||
export default AppViewerContainer
|
@@ -6,6 +6,7 @@ import {
|
||||
Box,
|
||||
TextField,
|
||||
InputLabel,
|
||||
ButtonBase,
|
||||
} from "@mui/material";
|
||||
import { styled } from "@mui/system";
|
||||
|
||||
@@ -15,7 +16,7 @@ import {
|
||||
flexDirection: "column",
|
||||
height: "100%",
|
||||
alignItems: "center",
|
||||
overflow: 'auto',
|
||||
overflow: 'auto',
|
||||
// For WebKit-based browsers (Chrome, Safari, etc.)
|
||||
"::-webkit-scrollbar": {
|
||||
width: "0px", // Set the width to 0 to hide the scrollbar
|
||||
@@ -35,6 +36,7 @@ import {
|
||||
gap: '24px',
|
||||
flexWrap: 'wrap',
|
||||
alignItems: 'flex-start',
|
||||
alignSelf: 'center'
|
||||
|
||||
}));
|
||||
export const AppsLibraryContainer = styled(Box)(({ theme }) => ({
|
||||
@@ -59,13 +61,16 @@ import {
|
||||
width: "90%",
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'center',
|
||||
gap: '10px'
|
||||
gap: '10px',
|
||||
flexGrow: 1,
|
||||
flexShrink: 0
|
||||
}));
|
||||
export const AppsSearchRight = styled(Box)(({ theme }) => ({
|
||||
display: "flex",
|
||||
width: "90%",
|
||||
justifyContent: 'flex-end',
|
||||
alignItems: 'center',
|
||||
flexShrink: 1
|
||||
}));
|
||||
export const AppCircleContainer = styled(Box)(({ theme }) => ({
|
||||
display: "flex",
|
||||
@@ -105,4 +110,85 @@ import {
|
||||
borderRadius: '50%',
|
||||
backgroundColor: "var(--apps-circle)",
|
||||
border: '1px solid #FFFFFF'
|
||||
}));
|
||||
|
||||
export const AppInfoSnippetContainer = styled(Box)(({ theme }) => ({
|
||||
display: "flex",
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
width: '100%'
|
||||
}));
|
||||
|
||||
export const AppInfoSnippetLeft = styled(Box)(({ theme }) => ({
|
||||
display: "flex",
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'center',
|
||||
gap: '12px'
|
||||
}));
|
||||
export const AppInfoSnippetRight = styled(Box)(({ theme }) => ({
|
||||
display: "flex",
|
||||
justifyContent: 'flex-end',
|
||||
alignItems: 'center',
|
||||
}));
|
||||
|
||||
export const AppDownloadButton = styled(ButtonBase)(({ theme }) => ({
|
||||
backgroundColor: "#247C0E",
|
||||
width: '101px',
|
||||
height: '29px',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
borderRadius: '25px'
|
||||
}));
|
||||
|
||||
export const AppDownloadButtonText = styled(Typography)(({ theme }) => ({
|
||||
fontSize: '14px',
|
||||
fontWeight: 500,
|
||||
lineHeight: 1.2,
|
||||
}));
|
||||
|
||||
|
||||
|
||||
export const AppInfoSnippetMiddle = styled(Box)(({ theme }) => ({
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: 'center',
|
||||
alignItems: 'flex-start',
|
||||
}));
|
||||
|
||||
export const AppInfoAppName = styled(Typography)(({ theme }) => ({
|
||||
fontSize: '16px',
|
||||
fontWeight: 500,
|
||||
lineHeight: 1.2,
|
||||
}));
|
||||
export const AppInfoUserName = styled(Typography)(({ theme }) => ({
|
||||
fontSize: '13px',
|
||||
fontWeight: 400,
|
||||
lineHeight: 1.2,
|
||||
color: '#8D8F93'
|
||||
}));
|
||||
|
||||
|
||||
export const AppsNavBarParent = styled(Box)(({ theme }) => ({
|
||||
display: "flex",
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
height: '60px',
|
||||
backgroundColor: '#1F2023',
|
||||
padding: '0px 10px',
|
||||
position: "fixed",
|
||||
bottom: 0,
|
||||
zIndex: 1,
|
||||
}));
|
||||
|
||||
export const AppsNavBarLeft = styled(Box)(({ theme }) => ({
|
||||
display: "flex",
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'center',
|
||||
}));
|
||||
export const AppsNavBarRight = styled(Box)(({ theme }) => ({
|
||||
display: "flex",
|
||||
justifyContent: 'flex-end',
|
||||
alignItems: 'center',
|
||||
}));
|
@@ -1,14 +1,26 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import React, { useContext, useEffect, useRef, useState } from 'react'
|
||||
import { AppsHome } from './AppsHome'
|
||||
import { Spacer } from '../../common/Spacer'
|
||||
import { getBaseApiReact } from '../../App'
|
||||
import { MyContext, getBaseApiReact } from '../../App'
|
||||
import { AppsLibrary } from './AppsLibrary'
|
||||
import { AppInfo } from './AppInfo'
|
||||
import { subscribeToEvent, unsubscribeFromEvent } from '../../utils/events'
|
||||
import { AppsNavBar } from './AppsNavBar'
|
||||
import { AppsParent } from './Apps-styles'
|
||||
import { AppViewer } from './AppViewer'
|
||||
import AppViewerContainer from './AppViewerContainer'
|
||||
import ShortUniqueId from "short-unique-id";
|
||||
|
||||
export const Apps = () => {
|
||||
const [mode, setMode] = useState('home')
|
||||
const uid = new ShortUniqueId({ length: 8 });
|
||||
|
||||
|
||||
export const Apps = ({mode, setMode}) => {
|
||||
const [availableQapps, setAvailableQapps] = useState([])
|
||||
const [downloadedQapps, setDownloadedQapps] = useState([])
|
||||
const [selectedAppInfo, setSelectedAppInfo] = useState(null)
|
||||
const [tabs, setTabs] = useState([])
|
||||
const [selectedTab, setSelectedTab] = useState(null)
|
||||
|
||||
|
||||
const getQapps = React.useCallback(
|
||||
async () => {
|
||||
@@ -52,11 +64,95 @@ export const Apps = () => {
|
||||
getQapps()
|
||||
}, [getQapps])
|
||||
|
||||
|
||||
const selectedAppInfoFunc = (e) => {
|
||||
const data = e.detail?.data;
|
||||
setSelectedAppInfo(data)
|
||||
setMode('appInfo')
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
subscribeToEvent("selectedAppInfo", selectedAppInfoFunc);
|
||||
|
||||
return () => {
|
||||
unsubscribeFromEvent("selectedAppInfo", selectedAppInfoFunc);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const navigateBackFunc = (e) => {
|
||||
if(mode === 'appInfo'){
|
||||
setMode('library')
|
||||
} else if(mode === 'library'){
|
||||
setMode('home')
|
||||
} else {
|
||||
|
||||
const iframe = document.getElementById('browser-iframe2');
|
||||
console.log('iframe', iframe)
|
||||
// Go Back in the iframe's history
|
||||
if (iframe) {
|
||||
console.log(iframe.contentWindow)
|
||||
if (iframe && iframe.contentWindow) {
|
||||
const iframeWindow = iframe.contentWindow;
|
||||
if (iframeWindow && iframeWindow.history) {
|
||||
iframeWindow.history.back();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
subscribeToEvent("navigateBack", navigateBackFunc);
|
||||
|
||||
return () => {
|
||||
unsubscribeFromEvent("navigateBack", navigateBackFunc);
|
||||
};
|
||||
}, [mode]);
|
||||
|
||||
|
||||
const addTabFunc = (e) => {
|
||||
const data = e.detail?.data;
|
||||
const newTab = {
|
||||
...data,
|
||||
tabId: uid.rnd()
|
||||
}
|
||||
setTabs((prev)=> [...prev, newTab])
|
||||
setSelectedTab(newTab)
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
subscribeToEvent("addTab", addTabFunc);
|
||||
|
||||
return () => {
|
||||
unsubscribeFromEvent("addTab", addTabFunc);
|
||||
};
|
||||
}, [mode]);
|
||||
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<Spacer height="30px" />
|
||||
<AppsParent>
|
||||
{mode !== 'viewer' && (
|
||||
<Spacer height="30px" />
|
||||
|
||||
)}
|
||||
{mode === 'home' && <AppsHome downloadedQapps={downloadedQapps} setMode={setMode} />}
|
||||
{mode === 'library' && <AppsLibrary downloadedQapps={downloadedQapps} availableQapps={availableQapps} />}
|
||||
</>
|
||||
{mode === 'appInfo' && <AppInfo app={selectedAppInfo} />}
|
||||
{mode === 'viewer' && (
|
||||
<>
|
||||
{tabs.map((tab)=> {
|
||||
return <AppViewerContainer isSelected={tab?.tabId === selectedTab?.tabId} app={tab} />
|
||||
})}
|
||||
</>
|
||||
) }
|
||||
|
||||
{mode !== 'viewer' && (
|
||||
<Spacer height="180px" />
|
||||
|
||||
)}
|
||||
</AppsParent>
|
||||
)
|
||||
}
|
||||
|
@@ -1,56 +1,81 @@
|
||||
import React, { useMemo, useState } from 'react'
|
||||
import { AppCircle, AppCircleContainer, AppCircleLabel, AppsContainer, AppsParent } from './Apps-styles'
|
||||
import { Avatar, ButtonBase } from '@mui/material'
|
||||
import { Add } from '@mui/icons-material'
|
||||
import { getBaseApiReact } from '../../App'
|
||||
import React, { useMemo, useState } from "react";
|
||||
import {
|
||||
AppCircle,
|
||||
AppCircleContainer,
|
||||
AppCircleLabel,
|
||||
AppsContainer,
|
||||
AppsParent,
|
||||
} from "./Apps-styles";
|
||||
import { Avatar, ButtonBase } from "@mui/material";
|
||||
import { Add } from "@mui/icons-material";
|
||||
import { getBaseApiReact } from "../../App";
|
||||
import LogoSelected from "../../assets/svgs/LogoSelected.svg";
|
||||
import { executeEvent } from "../../utils/events";
|
||||
|
||||
export const AppsHome = ({downloadedQapps, setMode}) => {
|
||||
|
||||
|
||||
export const AppsHome = ({ downloadedQapps, setMode }) => {
|
||||
return (
|
||||
<AppsParent>
|
||||
<AppsContainer>
|
||||
<ButtonBase onClick={()=> {
|
||||
setMode('library')
|
||||
}}>
|
||||
<AppCircleContainer>
|
||||
<AppsContainer>
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
setMode("library");
|
||||
}}
|
||||
>
|
||||
<AppCircleContainer>
|
||||
<AppCircle>
|
||||
<Add>+</Add>
|
||||
<Add>+</Add>
|
||||
</AppCircle>
|
||||
<AppCircleLabel>Add</AppCircleLabel>
|
||||
</AppCircleContainer>
|
||||
</AppCircleContainer>
|
||||
</ButtonBase>
|
||||
{downloadedQapps?.map((app) => {
|
||||
return (
|
||||
<ButtonBase
|
||||
sx={{
|
||||
height: "80px",
|
||||
width: "60px",
|
||||
}}
|
||||
onClick={()=> {
|
||||
executeEvent("addTab", {
|
||||
data: app
|
||||
})
|
||||
}}
|
||||
>
|
||||
<AppCircleContainer>
|
||||
<AppCircle
|
||||
sx={{
|
||||
border: "none",
|
||||
}}
|
||||
>
|
||||
<Avatar
|
||||
sx={{
|
||||
height: "31px",
|
||||
width: "31px",
|
||||
'& 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>
|
||||
<AppCircleLabel>
|
||||
{app?.name}
|
||||
</AppCircleLabel>
|
||||
</AppCircleContainer>
|
||||
</ButtonBase>
|
||||
{downloadedQapps?.map((qapp)=> {
|
||||
return (
|
||||
<ButtonBase sx={{
|
||||
height: '80px',
|
||||
width: '60px'
|
||||
}}>
|
||||
<AppCircleContainer>
|
||||
<AppCircle sx={{
|
||||
border: 'none'
|
||||
}}>
|
||||
<Avatar
|
||||
sx={{
|
||||
height: "31px",
|
||||
width: "31px",
|
||||
}}
|
||||
alt={qapp?.name}
|
||||
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${qapp?.name}/qortal_avatar?async=true`}
|
||||
>
|
||||
<img style={{
|
||||
width: '31px',
|
||||
height: 'auto'
|
||||
}} src={LogoSelected} alt="center-icon" />
|
||||
</Avatar>
|
||||
</AppCircle>
|
||||
<AppCircleLabel>{qapp?.metadata?.title || qapp?.name}</AppCircleLabel>
|
||||
</AppCircleContainer>
|
||||
</ButtonBase>
|
||||
)
|
||||
})}
|
||||
</AppsContainer>
|
||||
</AppsParent>
|
||||
)
|
||||
}
|
||||
);
|
||||
})}
|
||||
</AppsContainer>
|
||||
);
|
||||
};
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import React, { useContext, useEffect, useMemo, useRef, useState } from "react";
|
||||
import {
|
||||
AppCircle,
|
||||
AppCircleContainer,
|
||||
@@ -11,14 +11,16 @@ import {
|
||||
AppsSearchLeft,
|
||||
AppsSearchRight,
|
||||
} from "./Apps-styles";
|
||||
import { Avatar, Box, ButtonBase, InputBase } from "@mui/material";
|
||||
import { Avatar, Box, ButtonBase, InputBase, styled } 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 IconSearch from "../../assets/svgs/Search.svg";
|
||||
import IconClearInput from "../../assets/svgs/ClearInput.svg";
|
||||
|
||||
import { Spacer } from "../../common/Spacer";
|
||||
import { AppInfoSnippet } from "./AppInfoSnippet";
|
||||
import { Virtuoso } from "react-virtuoso";
|
||||
const officialAppList = [
|
||||
"q-tube",
|
||||
"q-blog",
|
||||
@@ -30,21 +32,41 @@ const officialAppList = [
|
||||
"q-shop",
|
||||
];
|
||||
|
||||
const ScrollerStyled = styled('div')({
|
||||
// Hide scrollbar for WebKit browsers (Chrome, Safari)
|
||||
"::-webkit-scrollbar": {
|
||||
width: "0px",
|
||||
height: "0px",
|
||||
},
|
||||
|
||||
// Hide scrollbar for Firefox
|
||||
scrollbarWidth: "none",
|
||||
|
||||
// Hide scrollbar for IE and older Edge
|
||||
"-ms-overflow-style": "none",
|
||||
});
|
||||
|
||||
|
||||
export const AppsLibrary = ({ downloadedQapps, availableQapps }) => {
|
||||
const [searchValue, setSearchValue] = useState('')
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
const virtuosoRef = useRef();
|
||||
const { rootHeight } = useContext(MyContext);
|
||||
|
||||
const officialApps = useMemo(() => {
|
||||
return availableQapps.filter((app) => app.service === 'APP' &&
|
||||
officialAppList.includes(app?.name?.toLowerCase())
|
||||
return availableQapps.filter(
|
||||
(app) =>
|
||||
app.service === "APP" &&
|
||||
officialAppList.includes(app?.name?.toLowerCase())
|
||||
);
|
||||
}, [availableQapps]);
|
||||
|
||||
const [debouncedValue, setDebouncedValue] = useState(''); // Debounced value
|
||||
const [debouncedValue, setDebouncedValue] = useState(""); // Debounced value
|
||||
|
||||
// Debounce logic
|
||||
useEffect(() => {
|
||||
const handler = setTimeout(() => {
|
||||
setDebouncedValue(searchValue); // Update debounced value after delay
|
||||
}, 500); // 500ms debounce time (adjustable)
|
||||
setDebouncedValue(searchValue);
|
||||
}, 250);
|
||||
|
||||
// Cleanup timeout if searchValue changes before the timeout completes
|
||||
return () => {
|
||||
@@ -53,111 +75,147 @@ export const AppsLibrary = ({ downloadedQapps, availableQapps }) => {
|
||||
}, [searchValue]); // Runs effect when searchValue changes
|
||||
|
||||
// Example: Perform search or other actions based on debouncedValue
|
||||
|
||||
const searchedList = useMemo(()=> {
|
||||
if(!debouncedValue) return []
|
||||
return availableQapps.filter((app)=> app.name.toLowerCase().includes(debouncedValue.toLowerCase()))
|
||||
}, [debouncedValue])
|
||||
console.log('officialApps', searchedList)
|
||||
|
||||
|
||||
return (
|
||||
<AppsParent>
|
||||
<AppsLibraryContainer>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
justifyContent: 'center'
|
||||
}}>
|
||||
<AppsSearchContainer>
|
||||
<AppsSearchLeft>
|
||||
<img src={IconSearch} />
|
||||
<InputBase
|
||||
value={searchValue}
|
||||
onChange={(e)=> setSearchValue(e.target.value)}
|
||||
sx={{ ml: 1, flex: 1 }}
|
||||
placeholder="Search for apps"
|
||||
inputProps={{ 'aria-label': 'Search for apps', fontSize: '16px', fontWeight: 400 }}
|
||||
/>
|
||||
</AppsSearchLeft>
|
||||
<AppsSearchRight>
|
||||
{searchValue && (
|
||||
<ButtonBase onClick={()=> {
|
||||
setSearchValue('')
|
||||
}}>
|
||||
<img src={IconClearInput} />
|
||||
</ButtonBase>
|
||||
)}
|
||||
|
||||
</AppsSearchRight>
|
||||
</AppsSearchContainer>
|
||||
</Box>
|
||||
<Spacer height="25px" />
|
||||
{searchedList?.length > 0 ? (
|
||||
<>
|
||||
{searchedList.map((app)=> {
|
||||
|
||||
return (
|
||||
<AppInfo app={app} />
|
||||
)
|
||||
})}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<AppLibrarySubTitle>Official Apps</AppLibrarySubTitle>
|
||||
<Spacer height="18px" />
|
||||
<AppsContainer>
|
||||
{officialApps?.map((qapp) => {
|
||||
return (
|
||||
<ButtonBase
|
||||
sx={{
|
||||
height: "80px",
|
||||
width: "60px",
|
||||
const searchedList = useMemo(() => {
|
||||
if (!debouncedValue) return [];
|
||||
return availableQapps.filter((app) =>
|
||||
app.name.toLowerCase().includes(debouncedValue.toLowerCase())
|
||||
);
|
||||
}, [debouncedValue]);
|
||||
console.log("officialApps", searchedList);
|
||||
|
||||
const rowRenderer = (index) => {
|
||||
|
||||
let app = searchedList[index];
|
||||
console.log('appi', app)
|
||||
return <AppInfoSnippet key={`${app?.service}-${app?.name}`} app={app} />;
|
||||
};
|
||||
|
||||
const StyledVirtuosoContainer = styled('div')({
|
||||
position: 'relative',
|
||||
height: rootHeight,
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
|
||||
// Hide scrollbar for WebKit browsers (Chrome, Safari)
|
||||
"::-webkit-scrollbar": {
|
||||
width: "0px",
|
||||
height: "0px",
|
||||
},
|
||||
|
||||
// Hide scrollbar for Firefox
|
||||
scrollbarWidth: "none",
|
||||
|
||||
// Hide scrollbar for IE and older Edge
|
||||
"-ms-overflow-style": "none",
|
||||
});
|
||||
|
||||
return (
|
||||
<AppsLibraryContainer>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
width: "100%",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<AppsSearchContainer>
|
||||
<AppsSearchLeft>
|
||||
<img src={IconSearch} />
|
||||
<InputBase
|
||||
value={searchValue}
|
||||
onChange={(e) => setSearchValue(e.target.value)}
|
||||
sx={{ ml: 1, flex: 1 }}
|
||||
placeholder="Search for apps"
|
||||
inputProps={{
|
||||
"aria-label": "Search for apps",
|
||||
fontSize: "16px",
|
||||
fontWeight: 400,
|
||||
}}
|
||||
>
|
||||
<AppCircleContainer>
|
||||
<AppCircle
|
||||
/>
|
||||
</AppsSearchLeft>
|
||||
<AppsSearchRight>
|
||||
{searchValue && (
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
setSearchValue("");
|
||||
}}
|
||||
>
|
||||
<img src={IconClearInput} />
|
||||
</ButtonBase>
|
||||
)}
|
||||
</AppsSearchRight>
|
||||
</AppsSearchContainer>
|
||||
</Box>
|
||||
<Spacer height="25px" />
|
||||
{searchedList?.length > 0 ? (
|
||||
<StyledVirtuosoContainer>
|
||||
<Virtuoso
|
||||
ref={virtuosoRef}
|
||||
data={searchedList}
|
||||
itemContent={rowRenderer}
|
||||
atBottomThreshold={50}
|
||||
followOutput="smooth"
|
||||
components={{
|
||||
Scroller: ScrollerStyled // Use the styled scroller component
|
||||
}}
|
||||
/>
|
||||
</StyledVirtuosoContainer>
|
||||
) : (
|
||||
<>
|
||||
<AppLibrarySubTitle>Official Apps</AppLibrarySubTitle>
|
||||
<Spacer height="18px" />
|
||||
<AppsContainer>
|
||||
{officialApps?.map((qapp) => {
|
||||
return (
|
||||
<ButtonBase
|
||||
sx={{
|
||||
border: "none",
|
||||
height: "80px",
|
||||
width: "60px",
|
||||
}}
|
||||
>
|
||||
<Avatar
|
||||
sx={{
|
||||
height: "31px",
|
||||
width: "31px",
|
||||
}}
|
||||
alt={qapp?.name}
|
||||
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
|
||||
qapp?.name
|
||||
}/qortal_avatar?async=true`}
|
||||
>
|
||||
<img
|
||||
style={{
|
||||
width: "31px",
|
||||
height: "auto",
|
||||
<AppCircleContainer>
|
||||
<AppCircle
|
||||
sx={{
|
||||
border: "none",
|
||||
}}
|
||||
src={LogoSelected}
|
||||
alt="center-icon"
|
||||
/>
|
||||
</Avatar>
|
||||
</AppCircle>
|
||||
<AppCircleLabel>
|
||||
{qapp?.metadata?.title || qapp?.name}
|
||||
</AppCircleLabel>
|
||||
</AppCircleContainer>
|
||||
</ButtonBase>
|
||||
);
|
||||
})}
|
||||
</AppsContainer>
|
||||
<Spacer height="18px" />
|
||||
<AppLibrarySubTitle>Featured</AppLibrarySubTitle>
|
||||
>
|
||||
<Avatar
|
||||
sx={{
|
||||
height: "31px",
|
||||
width: "31px",
|
||||
}}
|
||||
alt={qapp?.name}
|
||||
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
|
||||
qapp?.name
|
||||
}/qortal_avatar?async=true`}
|
||||
>
|
||||
<img
|
||||
style={{
|
||||
width: "31px",
|
||||
height: "auto",
|
||||
}}
|
||||
src={LogoSelected}
|
||||
alt="center-icon"
|
||||
/>
|
||||
</Avatar>
|
||||
</AppCircle>
|
||||
<AppCircleLabel>
|
||||
{qapp?.metadata?.title || qapp?.name}
|
||||
</AppCircleLabel>
|
||||
</AppCircleContainer>
|
||||
</ButtonBase>
|
||||
);
|
||||
})}
|
||||
</AppsContainer>
|
||||
<Spacer height="18px" />
|
||||
<AppLibrarySubTitle>Featured</AppLibrarySubTitle>
|
||||
|
||||
<Spacer height="18px" />
|
||||
<AppLibrarySubTitle>Categories</AppLibrarySubTitle>
|
||||
</>
|
||||
<Spacer height="18px" />
|
||||
<AppLibrarySubTitle>Categories</AppLibrarySubTitle>
|
||||
</>
|
||||
)}
|
||||
|
||||
</AppsLibraryContainer>
|
||||
</AppsParent>
|
||||
);
|
||||
};
|
||||
|
43
src/components/Apps/AppsNavBar.tsx
Normal file
43
src/components/Apps/AppsNavBar.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import React from "react";
|
||||
import {
|
||||
AppsNavBarLeft,
|
||||
AppsNavBarParent,
|
||||
AppsNavBarRight,
|
||||
} from "./Apps-styles";
|
||||
import NavBack from "../../assets/svgs/NavBack.svg";
|
||||
import NavCloseTab from "../../assets/svgs/NavCloseTab.svg";
|
||||
import NavAdd from "../../assets/svgs/NavAdd.svg";
|
||||
import NavMoreMenu from "../../assets/svgs/NavMoreMenu.svg";
|
||||
import { ButtonBase } from "@mui/material";
|
||||
import { executeEvent } from "../../utils/events";
|
||||
|
||||
export const AppsNavBar = () => {
|
||||
return (
|
||||
<AppsNavBarParent>
|
||||
<AppsNavBarLeft>
|
||||
<ButtonBase onClick={()=> {
|
||||
executeEvent("navigateBack", {
|
||||
});
|
||||
}}>
|
||||
<img src={NavBack} />
|
||||
</ButtonBase>
|
||||
</AppsNavBarLeft>
|
||||
<AppsNavBarRight sx={{
|
||||
gap: '10px'
|
||||
}}>
|
||||
<ButtonBase>
|
||||
<img style={{
|
||||
height: '40px',
|
||||
width: '40px'
|
||||
}} src={NavAdd} />
|
||||
</ButtonBase>
|
||||
<ButtonBase>
|
||||
<img style={{
|
||||
height: '34px',
|
||||
width: '34px'
|
||||
}} src={NavMoreMenu} />
|
||||
</ButtonBase>
|
||||
</AppsNavBarRight>
|
||||
</AppsNavBarParent>
|
||||
);
|
||||
};
|
@@ -89,6 +89,7 @@ import { HomeDesktop } from "./HomeDesktop";
|
||||
import { DesktopFooter } from "../Desktop/DesktopFooter";
|
||||
import { DesktopHeader } from "../Desktop/DesktopHeader";
|
||||
import { Apps } from "../Apps/Apps";
|
||||
import { AppsNavBar } from "../Apps/AppsNavBar";
|
||||
|
||||
// let touchStartY = 0;
|
||||
// let disablePullToRefresh = false;
|
||||
@@ -432,6 +433,7 @@ export const Group = ({
|
||||
const { clearStatesMessageQueueProvider } = useMessageQueue();
|
||||
const initiatedGetMembers = useRef(false);
|
||||
const [groupChatTimestamps, setGroupChatTimestamps] = React.useState({});
|
||||
const [appsMode, setAppsMode] = useState('viewer')
|
||||
|
||||
useEffect(()=> {
|
||||
timestampEnterDataRef.current = timestampEnterData
|
||||
@@ -2229,7 +2231,7 @@ export const Group = ({
|
||||
isThin={
|
||||
mobileViewMode === "groups" ||
|
||||
mobileViewMode === "group" ||
|
||||
mobileViewModeKeepOpen === "messaging"
|
||||
mobileViewModeKeepOpen === "messaging" || (mobileViewMode === "apps" && appsMode !== 'home')
|
||||
}
|
||||
logoutFunc={logoutFunc}
|
||||
goToHome={goToHome}
|
||||
@@ -2735,7 +2737,7 @@ export const Group = ({
|
||||
/>
|
||||
)}
|
||||
{isMobile && mobileViewMode === "apps" && (
|
||||
<Apps />
|
||||
<Apps mode={appsMode} setMode={setAppsMode} />
|
||||
)}
|
||||
{
|
||||
!isMobile && !selectedGroup &&
|
||||
@@ -2962,7 +2964,7 @@ export const Group = ({
|
||||
/>
|
||||
</div>
|
||||
|
||||
{(isMobile && mobileViewMode === "home" || isMobile && mobileViewMode === "apps") && !mobileViewModeKeepOpen && (
|
||||
{(isMobile && mobileViewMode === "home" || (isMobile && mobileViewMode === "apps" && appsMode === 'home')) && !mobileViewModeKeepOpen && (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
@@ -3004,6 +3006,11 @@ export const Group = ({
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{(isMobile && isMobile && mobileViewMode === "apps" && appsMode !== 'home') && !mobileViewModeKeepOpen && (
|
||||
<>
|
||||
<AppsNavBar />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
Reference in New Issue
Block a user