ability to publish apps

This commit is contained in:
2024-10-19 04:54:59 +03:00
parent 020366477a
commit 9685a40e6a
16 changed files with 1102 additions and 255 deletions

View File

@@ -13,6 +13,7 @@ import {
AppInfoUserName,
AppsLibraryContainer,
AppsParent,
AppsWidthLimiter,
} from "./Apps-styles";
import { Avatar, Box, ButtonBase, InputBase } from "@mui/material";
import { Add } from "@mui/icons-material";
@@ -28,6 +29,7 @@ export const AppInfo = ({ app }) => {
const isInstalled = app?.status?.status === 'READY'
return (
<AppsLibraryContainer>
<AppsWidthLimiter>
<AppInfoSnippetContainer>
<AppInfoSnippetLeft sx={{
flexGrow: 1,
@@ -99,6 +101,7 @@ export const AppInfo = ({ app }) => {
}}>
<AppDownloadButtonText>{isInstalled ? 'Open' : 'Download'}</AppDownloadButtonText>
</AppDownloadButton>
</AppsWidthLimiter>
</AppsLibraryContainer>

View File

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

View File

@@ -0,0 +1,14 @@
import { Rating } from '@mui/material'
import React, { useState } from 'react'
export const AppRating = () => {
const [value, setValue] = useState(0)
return (
<div>
<Rating value={value}
onChange={(event, newValue) => {
}} precision={0.1} />
</div>
)
}

View File

@@ -40,6 +40,13 @@ import {
}));
export const AppsLibraryContainer = styled(Box)(({ theme }) => ({
display: "flex",
width: "100%",
flexDirection: 'column',
justifyContent: 'flex-start',
alignItems: 'center',
}));
export const AppsWidthLimiter = styled(Box)(({ theme }) => ({
display: "flex",
width: "90%",
flexDirection: 'column',
@@ -138,7 +145,8 @@ import {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
borderRadius: '25px'
borderRadius: '25px',
alignSelf: 'center'
}));
export const AppDownloadButtonText = styled(Typography)(({ theme }) => ({
@@ -147,6 +155,13 @@ import {
lineHeight: 1.2,
}));
export const AppPublishTagsContainer = styled(Box)(({theme})=> ({
gap: '10px',
flexWrap: 'wrap',
justifyContent: 'flex-start',
width: '100%',
display: 'flex'
}))
export const AppInfoSnippetMiddle = styled(Box)(({ theme }) => ({
@@ -205,4 +220,59 @@ import {
justifyContent: 'center'
}));
export const PublishQAppCTAParent = styled(Box)(({ theme }) => ({
display: "flex",
justifyContent: 'space-between',
alignItems: 'center',
width: '100%',
backgroundColor: '#181C23'
}));
export const PublishQAppCTALeft = styled(Box)(({ theme }) => ({
display: "flex",
justifyContent: 'flex-start',
alignItems: 'center',
}));
export const PublishQAppCTARight = styled(Box)(({ theme }) => ({
display: "flex",
justifyContent: 'flex-end',
alignItems: 'center',
}));
export const PublishQAppCTAButton = styled(ButtonBase)(({ theme }) => ({
width: '101px',
height: '29px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
borderRadius: '25px',
border: '1px solid #FFFFFF'
}));
export const PublishQAppDotsBG = styled(Box)(({ theme }) => ({
display: "flex",
justifyContent: 'center',
alignItems: 'center',
width: '60px',
height: '60px',
backgroundColor: '#4BBCFE'
}));
export const PublishQAppInfo = styled(Typography)(({ theme }) => ({
fontSize: '10px',
fontWeight: 400,
lineHeight: 1.2,
fontStyle: 'italic'
}));
export const PublishQAppChoseFile = styled(ButtonBase)(({ theme }) => ({
width: '101px',
height: '30px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
borderRadius: '5px',
backgroundColor: '#0091E1',
color: 'white',
fontWeight: 600,
fontSize: '10px'
}));

View File

@@ -1,241 +1,290 @@
import React, { useContext, useEffect, useRef, useState } from 'react'
import { AppsHome } from './AppsHome'
import { Spacer } from '../../common/Spacer'
import { MyContext, getBaseApiReact } from '../../App'
import { AppsLibrary } from './AppsLibrary'
import { AppInfo } from './AppInfo'
import { executeEvent, subscribeToEvent, unsubscribeFromEvent } from '../../utils/events'
import { AppsNavBar } from './AppsNavBar'
import { AppsParent } from './Apps-styles'
import { AppViewer } from './AppViewer'
import AppViewerContainer from './AppViewerContainer'
import React, { useContext, useEffect, useRef, useState } from "react";
import { AppsHome } from "./AppsHome";
import { Spacer } from "../../common/Spacer";
import { MyContext, getBaseApiReact } from "../../App";
import { AppsLibrary } from "./AppsLibrary";
import { AppInfo } from "./AppInfo";
import {
executeEvent,
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";
import { AppPublish } from "./AppPublish";
const uid = new ShortUniqueId({ length: 8 });
export const Apps = ({ mode, setMode, show , myName}) => {
const [availableQapps, setAvailableQapps] = useState([]);
const [downloadedQapps, setDownloadedQapps] = useState([]);
const [selectedAppInfo, setSelectedAppInfo] = useState(null);
const [tabs, setTabs] = useState([]);
const [selectedTab, setSelectedTab] = useState(null);
const [isNewTabWindow, setIsNewTabWindow] = useState(false);
const [categories, setCategories] = useState([])
const [myApp, setMyApp] = useState(null)
const [myWebsite, setMyWebsite] = useState(null)
export const Apps = ({mode, setMode, show}) => {
const [availableQapps, setAvailableQapps] = useState([])
const [downloadedQapps, setDownloadedQapps] = useState([])
const [selectedAppInfo, setSelectedAppInfo] = useState(null)
const [tabs, setTabs] = useState([])
const [selectedTab, setSelectedTab] = useState(null)
const [isNewTabWindow, setIsNewTabWindow] = useState(false)
useEffect(()=> {
setTimeout(() => {
executeEvent('setTabsToNav', {
data: {
tabs: tabs,
selectedTab: selectedTab
}
})
}, 100);
}, [show, tabs, selectedTab])
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(() => {
setTimeout(() => {
executeEvent("setTabsToNav", {
data: {
tabs: tabs,
selectedTab: selectedTab,
isNewTabWindow: isNewTabWindow,
},
[]
});
}, 100);
}, [show, tabs, selectedTab, isNewTabWindow]);
const getCategories = React.useCallback(async () => {
try {
const url = `${getBaseApiReact()}/arbitrary/categories`;
const response = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (!response?.ok) return;
const responseData = await response.json();
setCategories(responseData);
} catch (error) {
} finally {
// dispatch(setIsLoadingGlobal(false))
}
}, []);
const getQapps = React.useCallback(async (myName) => {
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();
const findMyWebsite = responseDataWebsites.find((web)=> web.name === myName)
if(findMyWebsite){
setMyWebsite(findMyWebsite)
}
const findMyApp = responseData.find((web)=> web.name === myName)
console.log('findMyApp', findMyApp)
if(findMyApp){
setMyWebsite(findMyApp)
}
apps = responseData;
websites = responseDataWebsites;
const combine = [...apps, ...websites];
setAvailableQapps(combine);
setDownloadedQapps(
combine.filter((qapp) => qapp?.status?.status === "READY")
);
useEffect(()=> {
getQapps()
}, [getQapps])
} catch (error) {
} finally {
// dispatch(setIsLoadingGlobal(false))
}
}, []);
useEffect(() => {
getQapps(myName);
getCategories()
}, [getQapps, getCategories, myName]);
const selectedAppInfoFunc = (e) => {
const data = e.detail?.data;
setSelectedAppInfo(data);
setMode("appInfo");
};
const selectedAppInfoFunc = (e) => {
const data = e.detail?.data;
setSelectedAppInfo(data)
setMode('appInfo')
};
useEffect(() => {
subscribeToEvent("selectedAppInfo", selectedAppInfoFunc);
return () => {
unsubscribeFromEvent("selectedAppInfo", selectedAppInfoFunc);
};
}, []);
useEffect(() => {
subscribeToEvent("selectedAppInfo", selectedAppInfoFunc);
const navigateBackFunc = (e) => {
if(mode === 'appInfo'){
setMode('library')
} else if(mode === 'library'){
setMode('home')
} else {
const iframeId = `browser-iframe-${selectedTab?.tabId}`
const iframe = document.getElementById(iframeId);
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();
return () => {
unsubscribeFromEvent("selectedAppInfo", selectedAppInfoFunc);
};
}, []);
const navigateBackFunc = (e) => {
if (mode === "appInfo") {
setMode("library");
} else if (mode === "library") {
if (isNewTabWindow) {
setMode("viewer");
} else {
setMode("home");
}
} else if(mode === 'publish'){
setMode('library')
} else {
const iframeId = `browser-iframe-${selectedTab?.tabId}`;
const iframe = document.getElementById(iframeId);
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, selectedTab]);
useEffect(() => {
subscribeToEvent("navigateBack", navigateBackFunc);
return () => {
unsubscribeFromEvent("navigateBack", navigateBackFunc);
};
}, [mode, selectedTab]);
const addTabFunc = (e) => {
const data = e.detail?.data;
const newTab = {
...data,
tabId: uid.rnd()
}
setTabs((prev)=> [...prev, newTab])
setSelectedTab(newTab)
setMode('viewer')
setIsNewTabWindow(false)
};
useEffect(() => {
subscribeToEvent("addTab", addTabFunc);
return () => {
unsubscribeFromEvent("addTab", addTabFunc);
};
}, [tabs]);
const addTabFunc = (e) => {
const data = e.detail?.data;
const newTab = {
...data,
tabId: uid.rnd(),
};
setTabs((prev) => [...prev, newTab]);
setSelectedTab(newTab);
setMode("viewer");
const setSelectedTabFunc = (e) => {
const data = e.detail?.data;
setSelectedTab(data)
setTimeout(() => {
executeEvent('setTabsToNav', {
data: {
tabs: tabs,
selectedTab: data
}
})
}, 100);
setIsNewTabWindow(false)
};
useEffect(() => {
subscribeToEvent("setSelectedTab", setSelectedTabFunc);
return () => {
unsubscribeFromEvent("setSelectedTab", setSelectedTabFunc);
};
}, [tabs]);
setIsNewTabWindow(false);
};
const removeTabFunc = (e) => {
const data = e.detail?.data;
const copyTabs = [...tabs].filter((tab)=> tab?.tabId !== data?.tabId)
if(copyTabs?.length === 0){
setMode('home')
}
else{
setSelectedTab(copyTabs[0])
}
setTabs(copyTabs)
setSelectedTab(copyTabs[0])
setTimeout(() => {
executeEvent('setTabsToNav', {
data: {
tabs: copyTabs,
selectedTab: copyTabs[0]
}
})
}, 400);
};
useEffect(() => {
subscribeToEvent("removeTab", removeTabFunc);
return () => {
unsubscribeFromEvent("removeTab", removeTabFunc);
};
}, [tabs]);
useEffect(() => {
subscribeToEvent("addTab", addTabFunc);
const setNewTabWindowFunc = (e) => {
setIsNewTabWindow(true)
};
useEffect(() => {
subscribeToEvent("newTabWindow", setNewTabWindowFunc);
return () => {
unsubscribeFromEvent("newTabWindow", setNewTabWindowFunc);
};
}, [tabs]);
return () => {
unsubscribeFromEvent("addTab", addTabFunc);
};
}, [tabs]);
const setSelectedTabFunc = (e) => {
const data = e.detail?.data;
setSelectedTab(data);
setTimeout(() => {
executeEvent("setTabsToNav", {
data: {
tabs: tabs,
selectedTab: data,
isNewTabWindow: isNewTabWindow,
},
});
}, 100);
setIsNewTabWindow(false);
};
useEffect(() => {
subscribeToEvent("setSelectedTab", setSelectedTabFunc);
return () => {
unsubscribeFromEvent("setSelectedTab", setSelectedTabFunc);
};
}, [tabs, isNewTabWindow]);
const removeTabFunc = (e) => {
const data = e.detail?.data;
const copyTabs = [...tabs].filter((tab) => tab?.tabId !== data?.tabId);
if (copyTabs?.length === 0) {
setMode("home");
} else {
setSelectedTab(copyTabs[0]);
}
setTabs(copyTabs);
setSelectedTab(copyTabs[0]);
setTimeout(() => {
executeEvent("setTabsToNav", {
data: {
tabs: copyTabs,
selectedTab: copyTabs[0],
},
});
}, 400);
};
useEffect(() => {
subscribeToEvent("removeTab", removeTabFunc);
return () => {
unsubscribeFromEvent("removeTab", removeTabFunc);
};
}, [tabs]);
const setNewTabWindowFunc = (e) => {
setIsNewTabWindow(true);
};
useEffect(() => {
subscribeToEvent("newTabWindow", setNewTabWindowFunc);
return () => {
unsubscribeFromEvent("newTabWindow", setNewTabWindowFunc);
};
}, [tabs]);
return (
<AppsParent sx={{
display: !show && 'none'
}}>
{mode !== 'viewer' && (
<Spacer height="30px" />
<AppsParent
sx={{
display: !show && "none",
}}
>
{mode !== "viewer" && <Spacer height="30px" />}
{mode === "home" && (
<AppsHome myName={myName} downloadedQapps={downloadedQapps} setMode={setMode} myApp={myApp} myWebsite={myWebsite} />
)}
{mode === "library" && (
<AppsLibrary
downloadedQapps={downloadedQapps}
availableQapps={availableQapps}
setMode={setMode}
/>
)}
{mode === "appInfo" && <AppInfo app={selectedAppInfo} />}
{mode === "publish" && <AppPublish names={myName ? [myName] : []} categories={categories} />}
)}
{mode === 'home' && <AppsHome downloadedQapps={downloadedQapps} setMode={setMode} />}
{mode === 'library' && <AppsLibrary downloadedQapps={downloadedQapps} availableQapps={availableQapps} />}
{mode === 'appInfo' && <AppInfo app={selectedAppInfo} />}
{tabs.map((tab)=> {
return <AppViewerContainer hide={isNewTabWindow} isSelected={tab?.tabId === selectedTab?.tabId} app={tab} />
})}
{isNewTabWindow && (
<AppsHome downloadedQapps={downloadedQapps} setMode={setMode} />
)}
{mode !== 'viewer' && (
<Spacer height="180px" />
{tabs.map((tab) => {
return (
<AppViewerContainer
hide={isNewTabWindow}
isSelected={tab?.tabId === selectedTab?.tabId}
app={tab}
/>
);
})}
)}
{isNewTabWindow && mode === "viewer" && (
<>
<Spacer height="30px" />
<AppsHome downloadedQapps={downloadedQapps} setMode={setMode} />
</>
)}
{mode !== "viewer" && <Spacer height="180px" />}
</AppsParent>
)
}
);
};

View File

@@ -12,7 +12,7 @@ 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, myApp, myWebsite, myName }) => {
return (
<AppsContainer>
<ButtonBase
@@ -27,7 +27,101 @@ export const AppsHome = ({ downloadedQapps, setMode }) => {
<AppCircleLabel>Add</AppCircleLabel>
</AppCircleContainer>
</ButtonBase>
{downloadedQapps?.map((app) => {
{myApp &&(
<ButtonBase
sx={{
height: "80px",
width: "60px",
}}
onClick={()=> {
executeEvent("addTab", {
data: myApp
})
}}
>
<AppCircleContainer>
<AppCircle
sx={{
border: "none",
}}
>
<Avatar
sx={{
height: "31px",
width: "31px",
'& img': {
objectFit: 'fill',
}
}}
alt={myApp?.name}
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
myApp?.name
}/qortal_avatar?async=true`}
>
<img
style={{
width: "31px",
height: "auto",
}}
src={LogoSelected}
alt="center-icon"
/>
</Avatar>
</AppCircle>
<AppCircleLabel>
{myApp?.name}
</AppCircleLabel>
</AppCircleContainer>
</ButtonBase>
)}
{myWebsite &&(
<ButtonBase
sx={{
height: "80px",
width: "60px",
}}
onClick={()=> {
executeEvent("addTab", {
data: myWebsite
})
}}
>
<AppCircleContainer>
<AppCircle
sx={{
border: "none",
}}
>
<Avatar
sx={{
height: "31px",
width: "31px",
'& img': {
objectFit: 'fill',
}
}}
alt={myWebsite?.name}
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
myWebsite?.name
}/qortal_avatar?async=true`}
>
<img
style={{
width: "31px",
height: "auto",
}}
src={LogoSelected}
alt="center-icon"
/>
</Avatar>
</AppCircle>
<AppCircleLabel>
{myWebsite?.name}
</AppCircleLabel>
</AppCircleContainer>
</ButtonBase>
)}
{downloadedQapps?.filter((item)=> item?.name !== myName).map((app) => {
return (
<ButtonBase
sx={{

View File

@@ -10,6 +10,12 @@ import {
AppsSearchContainer,
AppsSearchLeft,
AppsSearchRight,
AppsWidthLimiter,
PublishQAppCTAButton,
PublishQAppCTALeft,
PublishQAppCTAParent,
PublishQAppCTARight,
PublishQAppDotsBG,
} from "./Apps-styles";
import { Avatar, Box, ButtonBase, InputBase, styled } from "@mui/material";
import { Add } from "@mui/icons-material";
@@ -17,10 +23,13 @@ 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 qappDevelopText from "../../assets/svgs/qappDevelopText.svg";
import qappDots from "../../assets/svgs/qappDots.svg";
import { Spacer } from "../../common/Spacer";
import { AppInfoSnippet } from "./AppInfoSnippet";
import { Virtuoso } from "react-virtuoso";
import { executeEvent } from "../../utils/events";
const officialAppList = [
"q-tube",
"q-blog",
@@ -47,7 +56,7 @@ const ScrollerStyled = styled('div')({
});
export const AppsLibrary = ({ downloadedQapps, availableQapps }) => {
export const AppsLibrary = ({ downloadedQapps, availableQapps, setMode }) => {
const [searchValue, setSearchValue] = useState("");
const virtuosoRef = useRef();
const { rootHeight } = useContext(MyContext);
@@ -113,6 +122,7 @@ export const AppsLibrary = ({ downloadedQapps, availableQapps }) => {
return (
<AppsLibraryContainer>
<AppsWidthLimiter>
<Box
sx={{
display: "flex",
@@ -148,8 +158,10 @@ export const AppsLibrary = ({ downloadedQapps, availableQapps }) => {
</AppsSearchRight>
</AppsSearchContainer>
</Box>
</AppsWidthLimiter>
<Spacer height="25px" />
{searchedList?.length > 0 ? (
<AppsWidthLimiter>
<StyledVirtuosoContainer>
<Virtuoso
ref={virtuosoRef}
@@ -162,8 +174,10 @@ export const AppsLibrary = ({ downloadedQapps, availableQapps }) => {
}}
/>
</StyledVirtuosoContainer>
</AppsWidthLimiter>
) : (
<>
<AppsWidthLimiter>
<AppLibrarySubTitle>Official Apps</AppLibrarySubTitle>
<Spacer height="18px" />
<AppsContainer>
@@ -174,6 +188,11 @@ export const AppsLibrary = ({ downloadedQapps, availableQapps }) => {
height: "80px",
width: "60px",
}}
onClick={()=> {
executeEvent("addTab", {
data: qapp
})
}}
>
<AppCircleContainer>
<AppCircle
@@ -209,11 +228,33 @@ export const AppsLibrary = ({ downloadedQapps, availableQapps }) => {
);
})}
</AppsContainer>
<Spacer height="30px" />
<AppLibrarySubTitle>Create Apps!</AppLibrarySubTitle>
<Spacer height="18px" />
<AppLibrarySubTitle>Featured</AppLibrarySubTitle>
</AppsWidthLimiter>
<PublishQAppCTAParent>
<PublishQAppCTALeft>
<PublishQAppDotsBG>
<img src={qappDots} />
</PublishQAppDotsBG>
<Spacer width="29px" />
<img src={qappDevelopText} />
</PublishQAppCTALeft>
<PublishQAppCTARight onClick={()=> {
setMode('publish')
}}>
<PublishQAppCTAButton>
Publish
</PublishQAppCTAButton>
<Spacer width="20px" />
</PublishQAppCTARight>
</PublishQAppCTAParent>
<AppsWidthLimiter>
<Spacer height="18px" />
<AppLibrarySubTitle>Categories</AppLibrarySubTitle>
</AppsWidthLimiter>
</>
)}
</AppsLibraryContainer>

View File

@@ -14,7 +14,7 @@ import TabComponent from "./TabComponent";
export const AppsNavBar = () => {
const [tabs, setTabs] = useState([])
const [selectedTab, setSelectedTab] = useState([])
const [isNewTabWindow, setIsNewTabWindow] = useState(false)
const tabsRef = useRef(null);
useEffect(() => {
@@ -30,10 +30,11 @@ export const AppsNavBar = () => {
}, [tabs.length]); // Dependency on the number of tabs
const setTabsToNav = (e) => {
const {tabs, selectedTab} = e.detail?.data;
const {tabs, selectedTab, isNewTabWindow} = e.detail?.data;
setTabs([...tabs])
setSelectedTab({...selectedTab})
setIsNewTabWindow(isNewTabWindow)
};
useEffect(() => {
@@ -68,7 +69,7 @@ export const AppsNavBar = () => {
{tabs?.map((tab) => (
<Tab
key={tab.tabId}
label={<TabComponent isSelected={tab?.tabId === selectedTab?.tabId} app={tab} />} // Pass custom component
label={<TabComponent isSelected={tab?.tabId === selectedTab?.tabId && !isNewTabWindow} app={tab} />} // Pass custom component
sx={{
"&.Mui-selected": {
color: "white",