mirror of
https://github.com/Qortal/chrome-extension.git
synced 2025-02-11 17:55:49 +00:00
started app section
This commit is contained in:
parent
e9860ae7be
commit
bd170d8481
@ -46,6 +46,6 @@
|
||||
|
||||
],
|
||||
"content_security_policy": {
|
||||
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'; connect-src 'self' https://api.qortal.org https://api2.qortal.org https://appnode.qortal.org https://apinode.qortalnodes.live https://apinode1.qortalnodes.live https://apinode2.qortalnodes.live https://apinode3.qortalnodes.live https://apinode4.qortalnodes.live https://ext-node.qortal.link wss://appnode.qortal.org wss://ext-node.qortal.link ws://127.0.0.1:12391 http://127.0.0.1:12391 https://ext-node.qortal.link; "
|
||||
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'; connect-src 'self' https://*:* http://*:* wss://*:* ws://*:*;"
|
||||
}
|
||||
}
|
||||
|
@ -141,7 +141,7 @@ const defaultValues: MyContextInterface = {
|
||||
message: "",
|
||||
},
|
||||
};
|
||||
export let isMobile = false;
|
||||
export let isMobile = true;
|
||||
|
||||
const isMobileDevice = () => {
|
||||
const userAgent = navigator.userAgent || navigator.vendor || window.opera;
|
||||
|
6
src/assets/svgs/ClearInput.svg
Normal file
6
src/assets/svgs/ClearInput.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 |
9
src/assets/svgs/LogoSelected.svg
Normal file
9
src/assets/svgs/LogoSelected.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 821 KiB |
3
src/assets/svgs/Search.svg
Normal file
3
src/assets/svgs/Search.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.08728 0.00158245C2.72507 0.00158245 0 2.7262 0 6.08784C0 9.44948 2.72507 12.1741 6.08728 12.1741C7.62099 12.1741 9.02317 11.6043 10.0947 10.6668L13.3088 13.8803C13.3881 13.9596 13.4911 14 13.595 14C13.6988 14 13.8018 13.9596 13.8811 13.8803C14.0396 13.7218 14.0396 13.4643 13.8811 13.3066L10.667 10.093C11.6047 9.02162 12.1746 7.62202 12.1746 6.08626C12.1746 2.72461 9.44951 0 6.0873 0L6.08728 0.00158245ZM6.08728 11.3626C3.17756 11.3626 0.811637 8.99707 0.811637 6.08784C0.811637 3.17861 3.17756 0.813083 6.08728 0.813083C8.997 0.813083 11.3629 3.17861 11.3629 6.08784C11.3629 8.99707 8.997 11.3626 6.08728 11.3626Z" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 748 B |
22
src/components/Apps/AppInfo.tsx
Normal file
22
src/components/Apps/AppInfo.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import {
|
||||
|
||||
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";
|
||||
|
||||
export const AppInfo = ({ app }) => {
|
||||
|
||||
return (
|
||||
<AppsLibraryContainer>
|
||||
|
||||
</AppsLibraryContainer>
|
||||
);
|
||||
};
|
108
src/components/Apps/Apps-styles.tsx
Normal file
108
src/components/Apps/Apps-styles.tsx
Normal file
@ -0,0 +1,108 @@
|
||||
import {
|
||||
AppBar,
|
||||
Button,
|
||||
Toolbar,
|
||||
Typography,
|
||||
Box,
|
||||
TextField,
|
||||
InputLabel,
|
||||
} from "@mui/material";
|
||||
import { styled } from "@mui/system";
|
||||
|
||||
export const AppsParent = styled(Box)(({ theme }) => ({
|
||||
display: "flex",
|
||||
width: "100%",
|
||||
flexDirection: "column",
|
||||
height: "100%",
|
||||
alignItems: "center",
|
||||
overflow: 'auto',
|
||||
// For WebKit-based browsers (Chrome, Safari, etc.)
|
||||
"::-webkit-scrollbar": {
|
||||
width: "0px", // Set the width to 0 to hide the scrollbar
|
||||
height: "0px", // Set the height to 0 for horizontal scrollbar
|
||||
},
|
||||
|
||||
// For Firefox
|
||||
scrollbarWidth: "none", // Hides the scrollbar in Firefox
|
||||
|
||||
// Optional for better cross-browser consistency
|
||||
"-ms-overflow-style": "none" // Hides scrollbar in IE and Edge
|
||||
}));
|
||||
export const AppsContainer = styled(Box)(({ theme }) => ({
|
||||
display: "flex",
|
||||
width: "90%",
|
||||
justifyContent: 'space-evenly',
|
||||
gap: '24px',
|
||||
flexWrap: 'wrap',
|
||||
alignItems: 'flex-start',
|
||||
|
||||
}));
|
||||
export const AppsLibraryContainer = styled(Box)(({ theme }) => ({
|
||||
display: "flex",
|
||||
width: "90%",
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'flex-start',
|
||||
}));
|
||||
export const AppsSearchContainer = styled(Box)(({ theme }) => ({
|
||||
display: "flex",
|
||||
width: "90%",
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#434343',
|
||||
borderRadius: '8px',
|
||||
padding: '0px 10px',
|
||||
height: '36px'
|
||||
}));
|
||||
export const AppsSearchLeft = styled(Box)(({ theme }) => ({
|
||||
display: "flex",
|
||||
width: "90%",
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'center',
|
||||
gap: '10px'
|
||||
}));
|
||||
export const AppsSearchRight = styled(Box)(({ theme }) => ({
|
||||
display: "flex",
|
||||
width: "90%",
|
||||
justifyContent: 'flex-end',
|
||||
alignItems: 'center',
|
||||
}));
|
||||
export const AppCircleContainer = styled(Box)(({ theme }) => ({
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: '5px',
|
||||
alignItems: 'center',
|
||||
width: '100%'
|
||||
}));
|
||||
export const Add = styled(Typography)(({ theme }) => ({
|
||||
fontSize: '36px',
|
||||
fontWeight: 500,
|
||||
lineHeight: '43.57px',
|
||||
textAlign: 'left'
|
||||
|
||||
}));
|
||||
export const AppCircleLabel = styled(Typography)(({ theme }) => ({
|
||||
fontSize: '12px',
|
||||
fontWeight: 500,
|
||||
lineHeight: 1.2,
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
width: '100%'
|
||||
}));
|
||||
export const AppLibrarySubTitle = styled(Typography)(({ theme }) => ({
|
||||
fontSize: '16px',
|
||||
fontWeight: 500,
|
||||
lineHeight: 1.2,
|
||||
}));
|
||||
export const AppCircle = styled(Box)(({ theme }) => ({
|
||||
display: "flex",
|
||||
width: "60px",
|
||||
flexDirection: "column",
|
||||
height: "60px",
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderRadius: '50%',
|
||||
backgroundColor: "var(--apps-circle)",
|
||||
border: '1px solid #FFFFFF'
|
||||
}));
|
62
src/components/Apps/Apps.tsx
Normal file
62
src/components/Apps/Apps.tsx
Normal file
@ -0,0 +1,62 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { AppsHome } from './AppsHome'
|
||||
import { Spacer } from '../../common/Spacer'
|
||||
import { getBaseApiReact } from '../../App'
|
||||
import { AppsLibrary } from './AppsLibrary'
|
||||
|
||||
export const Apps = () => {
|
||||
const [mode, setMode] = useState('home')
|
||||
|
||||
const [availableQapps, setAvailableQapps] = useState([])
|
||||
const [downloadedQapps, setDownloadedQapps] = useState([])
|
||||
|
||||
const getQapps = React.useCallback(
|
||||
async () => {
|
||||
try {
|
||||
let apps = []
|
||||
let websites = []
|
||||
// dispatch(setIsLoadingGlobal(true))
|
||||
const url = `${getBaseApiReact()}/arbitrary/resources/search?service=APP&mode=ALL&includestatus=true&limit=0&includemetadata=true`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
if(!response?.ok) return
|
||||
const responseData = await response.json();
|
||||
const urlWebsites = `${getBaseApiReact()}/arbitrary/resources/search?service=WEBSITE&mode=ALL&includestatus=true&limit=0&includemetadata=true`;
|
||||
|
||||
const responseWebsites = await fetch(urlWebsites, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
if(!responseWebsites?.ok) return
|
||||
const responseDataWebsites = await responseWebsites.json();
|
||||
apps = responseData
|
||||
websites = responseDataWebsites
|
||||
const combine = [...apps, ...websites]
|
||||
setAvailableQapps(combine)
|
||||
setDownloadedQapps(combine.filter((qapp)=> qapp?.status?.status === 'READY'))
|
||||
} catch (error) {
|
||||
} finally {
|
||||
// dispatch(setIsLoadingGlobal(false))
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
useEffect(()=> {
|
||||
getQapps()
|
||||
}, [getQapps])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Spacer height="30px" />
|
||||
{mode === 'home' && <AppsHome downloadedQapps={downloadedQapps} setMode={setMode} />}
|
||||
{mode === 'library' && <AppsLibrary downloadedQapps={downloadedQapps} availableQapps={availableQapps} />}
|
||||
</>
|
||||
)
|
||||
}
|
56
src/components/Apps/AppsHome.tsx
Normal file
56
src/components/Apps/AppsHome.tsx
Normal file
@ -0,0 +1,56 @@
|
||||
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";
|
||||
|
||||
export const AppsHome = ({downloadedQapps, setMode}) => {
|
||||
|
||||
|
||||
return (
|
||||
<AppsParent>
|
||||
<AppsContainer>
|
||||
<ButtonBase onClick={()=> {
|
||||
setMode('library')
|
||||
}}>
|
||||
<AppCircleContainer>
|
||||
<AppCircle>
|
||||
<Add>+</Add>
|
||||
</AppCircle>
|
||||
<AppCircleLabel>Add</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>
|
||||
)
|
||||
}
|
163
src/components/Apps/AppsLibrary.tsx
Normal file
163
src/components/Apps/AppsLibrary.tsx
Normal file
@ -0,0 +1,163 @@
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import {
|
||||
AppCircle,
|
||||
AppCircleContainer,
|
||||
AppCircleLabel,
|
||||
AppLibrarySubTitle,
|
||||
AppsContainer,
|
||||
AppsLibraryContainer,
|
||||
AppsParent,
|
||||
AppsSearchContainer,
|
||||
AppsSearchLeft,
|
||||
AppsSearchRight,
|
||||
} 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 IconSearch from "../../assets/svgs/Search.svg";
|
||||
import IconClearInput from "../../assets/svgs/ClearInput.svg";
|
||||
|
||||
import { Spacer } from "../../common/Spacer";
|
||||
const officialAppList = [
|
||||
"q-tube",
|
||||
"q-blog",
|
||||
"q-share",
|
||||
"q-support",
|
||||
"q-mail",
|
||||
"qombo",
|
||||
"q-fund",
|
||||
"q-shop",
|
||||
];
|
||||
|
||||
export const AppsLibrary = ({ downloadedQapps, availableQapps }) => {
|
||||
const [searchValue, setSearchValue] = useState('')
|
||||
const officialApps = useMemo(() => {
|
||||
return availableQapps.filter((app) => app.service === 'APP' &&
|
||||
officialAppList.includes(app?.name?.toLowerCase())
|
||||
);
|
||||
}, [availableQapps]);
|
||||
|
||||
const [debouncedValue, setDebouncedValue] = useState(''); // Debounced value
|
||||
|
||||
// Debounce logic
|
||||
useEffect(() => {
|
||||
const handler = setTimeout(() => {
|
||||
setDebouncedValue(searchValue); // Update debounced value after delay
|
||||
}, 500); // 500ms debounce time (adjustable)
|
||||
|
||||
// Cleanup timeout if searchValue changes before the timeout completes
|
||||
return () => {
|
||||
clearTimeout(handler);
|
||||
};
|
||||
}, [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",
|
||||
}}
|
||||
>
|
||||
<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>
|
||||
<Spacer height="18px" />
|
||||
<AppLibrarySubTitle>Featured</AppLibrarySubTitle>
|
||||
|
||||
<Spacer height="18px" />
|
||||
<AppLibrarySubTitle>Categories</AppLibrarySubTitle>
|
||||
</>
|
||||
)}
|
||||
|
||||
</AppsLibraryContainer>
|
||||
</AppsParent>
|
||||
);
|
||||
};
|
@ -88,6 +88,7 @@ import { ExitIcon } from "../../assets/Icons/ExitIcon";
|
||||
import { HomeDesktop } from "./HomeDesktop";
|
||||
import { DesktopFooter } from "../Desktop/DesktopFooter";
|
||||
import { DesktopHeader } from "../Desktop/DesktopHeader";
|
||||
import { Apps } from "../Apps/Apps";
|
||||
|
||||
// let touchStartY = 0;
|
||||
// let disablePullToRefresh = false;
|
||||
@ -2733,6 +2734,9 @@ export const Group = ({
|
||||
setMobileViewMode={setMobileViewMode}
|
||||
/>
|
||||
)}
|
||||
{isMobile && mobileViewMode === "apps" && (
|
||||
<Apps />
|
||||
)}
|
||||
{
|
||||
!isMobile && !selectedGroup &&
|
||||
groupSection === "home" && (
|
||||
@ -2958,7 +2962,7 @@ export const Group = ({
|
||||
/>
|
||||
</div>
|
||||
|
||||
{isMobile && mobileViewMode === "home" && !mobileViewModeKeepOpen && (
|
||||
{(isMobile && mobileViewMode === "home" || isMobile && mobileViewMode === "apps") && !mobileViewModeKeepOpen && (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
|
@ -2,11 +2,14 @@ import * as React from "react";
|
||||
import {
|
||||
BottomNavigation,
|
||||
BottomNavigationAction,
|
||||
ButtonBase,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import { Home, Groups, Message, ShowChart } from "@mui/icons-material";
|
||||
import Box from "@mui/material/Box";
|
||||
import BottomLogo from "../../assets/svgs/BottomLogo5.svg";
|
||||
import LogoSelected from "../../assets/svgs/LogoSelected.svg";
|
||||
|
||||
import { CustomSvg } from "../../common/CustomSvg";
|
||||
import { WalletIcon } from "../../assets/Icons/WalletIcon";
|
||||
import { HubsIcon } from "../../assets/Icons/HubsIcon";
|
||||
@ -132,6 +135,15 @@ export const MobileFooter = ({
|
||||
zIndex: 3,
|
||||
}}
|
||||
>
|
||||
<ButtonBase onClick={()=> {
|
||||
if(mobileViewMode === 'home'){
|
||||
setMobileViewMode('apps')
|
||||
|
||||
} else {
|
||||
setMobileViewMode('home')
|
||||
|
||||
}
|
||||
}}>
|
||||
<Box
|
||||
sx={{
|
||||
width: "49px", // Slightly smaller inner circle
|
||||
@ -144,8 +156,9 @@ export const MobileFooter = ({
|
||||
}}
|
||||
>
|
||||
{/* Custom Center Icon */}
|
||||
<img src={BottomLogo} alt="center-icon" />
|
||||
<img src={mobileViewMode === 'apps' ? LogoSelected : BottomLogo} alt="center-icon" />
|
||||
</Box>
|
||||
</ButtonBase>
|
||||
</Box>
|
||||
|
||||
<BottomNavigation
|
||||
|
@ -35,6 +35,7 @@
|
||||
--bg-2: #27282c;
|
||||
--bg-3: rgba(0, 0, 0, 0.1);
|
||||
--unread: rgba(255, 0, 0, 1);
|
||||
--apps-circle: #1F2023
|
||||
}
|
||||
|
||||
body {
|
||||
|
Loading…
x
Reference in New Issue
Block a user