mirror of
https://github.com/Qortal/chrome-extension.git
synced 2025-02-14 11:15:49 +00:00
started app browser
This commit is contained in:
parent
bd170d8481
commit
28c2dfbb7a
11
package-lock.json
generated
11
package-lock.json
generated
@ -45,6 +45,7 @@
|
||||
"react-countdown-circle-timer": "^3.2.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-dropzone": "^14.2.3",
|
||||
"react-frame-component": "^5.2.7",
|
||||
"react-infinite-scroller": "^1.2.6",
|
||||
"react-intersection-observer": "^9.13.0",
|
||||
"react-qr-code": "^2.0.15",
|
||||
@ -9256,6 +9257,16 @@
|
||||
"react": ">= 16.8 || 18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-frame-component": {
|
||||
"version": "5.2.7",
|
||||
"resolved": "https://registry.npmjs.org/react-frame-component/-/react-frame-component-5.2.7.tgz",
|
||||
"integrity": "sha512-ROjHtSLoSVYUBfTieazj/nL8jIX9rZFmHC0yXEU+dx6Y82OcBEGgU9o7VyHMrBFUN9FuQ849MtIPNNLsb4krbg==",
|
||||
"peerDependencies": {
|
||||
"prop-types": "^15.5.9",
|
||||
"react": ">= 16.3",
|
||||
"react-dom": ">= 16.3"
|
||||
}
|
||||
},
|
||||
"node_modules/react-infinite-scroller": {
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/react-infinite-scroller/-/react-infinite-scroller-1.2.6.tgz",
|
||||
|
@ -49,6 +49,7 @@
|
||||
"react-countdown-circle-timer": "^3.2.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-dropzone": "^14.2.3",
|
||||
"react-frame-component": "^5.2.7",
|
||||
"react-infinite-scroller": "^1.2.6",
|
||||
"react-intersection-observer": "^9.13.0",
|
||||
"react-qr-code": "^2.0.15",
|
||||
|
4
src/assets/svgs/NavAdd.svg
Normal file
4
src/assets/svgs/NavAdd.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="20" cy="19.9999" r="18" fill="#434343"/>
|
||||
<path d="M30 21.6666H21.6666V30C21.6666 30.9166 20.9166 31.6666 20 31.6666C19.0833 31.6666 18.3333 30.9166 18.3333 30V21.6666H9.99998C9.08331 21.6666 8.33331 20.9166 8.33331 20C8.33331 19.0833 9.08331 18.3333 9.99998 18.3333H18.3333V9.99995C18.3333 9.08328 19.0833 8.33328 20 8.33328C20.9166 8.33328 21.6666 9.08328 21.6666 9.99995V18.3333H30C30.9166 18.3333 31.6666 19.0833 31.6666 20C31.6666 20.9166 30.9166 21.6666 30 21.6666Z" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 602 B |
10
src/assets/svgs/NavBack.svg
Normal file
10
src/assets/svgs/NavBack.svg
Normal file
@ -0,0 +1,10 @@
|
||||
<svg width="34" height="34" viewBox="0 0 34 34" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_400_537)">
|
||||
<path d="M28.3334 15.5833H11.0925L19.0117 7.6641L17 5.6666L5.66669 16.9999L17 28.3333L18.9975 26.3358L11.0925 18.4166H28.3334V15.5833Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_400_537">
|
||||
<rect width="34" height="34" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 396 B |
6
src/assets/svgs/NavCloseTab.svg
Normal file
6
src/assets/svgs/NavCloseTab.svg
Normal file
@ -0,0 +1,6 @@
|
||||
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="8.5" cy="8.5" r="8.5" fill="white"/>
|
||||
<circle cx="8.5" cy="8.50003" r="6.61111" fill="#434343"/>
|
||||
<path d="M5.66675 5.66669L11.3334 11.3334" stroke="white" stroke-width="2"/>
|
||||
<path d="M11.3333 5.66675L5.66658 11.3334" stroke="white" stroke-width="2"/>
|
||||
</svg>
|
After Width: | Height: | Size: 365 B |
10
src/assets/svgs/NavMoreMenu.svg
Normal file
10
src/assets/svgs/NavMoreMenu.svg
Normal file
@ -0,0 +1,10 @@
|
||||
<svg width="34" height="34" viewBox="0 0 34 34" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_400_556)">
|
||||
<path d="M8.49996 14.1666C6.94163 14.1666 5.66663 15.4416 5.66663 16.9999C5.66663 18.5583 6.94163 19.8333 8.49996 19.8333C10.0583 19.8333 11.3333 18.5583 11.3333 16.9999C11.3333 15.4416 10.0583 14.1666 8.49996 14.1666ZM25.5 14.1666C23.9416 14.1666 22.6666 15.4416 22.6666 16.9999C22.6666 18.5583 23.9416 19.8333 25.5 19.8333C27.0583 19.8333 28.3333 18.5583 28.3333 16.9999C28.3333 15.4416 27.0583 14.1666 25.5 14.1666ZM17 14.1666C15.4416 14.1666 14.1666 15.4416 14.1666 16.9999C14.1666 18.5583 15.4416 19.8333 17 19.8333C18.5583 19.8333 19.8333 18.5583 19.8333 16.9999C19.8333 15.4416 18.5583 14.1666 17 14.1666Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_400_556">
|
||||
<rect width="34" height="34" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 874 B |
@ -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 />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user