ability to publish apps

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

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -0,0 +1,4 @@
<svg width="38" height="38" viewBox="0 0 38 38" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 18.9889C0 16.643 1.8685 14.7397 4.17153 14.7397C6.47456 14.7397 8.34305 16.643 8.34305 18.9889C8.34305 21.3349 6.47456 23.2382 4.17153 23.2382C1.8685 23.2382 0 21.3349 0 18.9889ZM33.8285 23.2382C36.1315 23.2382 38 21.3349 38 18.9889C38 16.643 36.1315 14.7397 33.8285 14.7397C31.5254 14.7397 29.6569 16.643 29.6569 18.9889C29.6569 21.3349 31.5254 23.2382 33.8285 23.2382ZM19.0109 23.2382C21.3139 23.2382 23.1824 21.3349 23.1824 18.9889C23.1824 16.643 21.3139 14.7397 19.0109 14.7397C16.7078 14.7397 14.8393 16.643 14.8393 18.9889C14.8393 21.3349 16.7078 23.2382 19.0109 23.2382ZM4.17153 8.49854C6.47456 8.49854 8.34305 6.59522 8.34305 4.24927C8.34305 1.90332 6.47456 0 4.17153 0C1.8685 0 0 1.90332 0 4.24927C0 6.59522 1.8685 8.49854 4.17153 8.49854ZM33.8285 8.49854C36.1315 8.49854 38 6.59522 38 4.24927C38 1.90332 36.1315 0 33.8285 0C31.5254 0 29.6569 1.90332 29.6569 4.24927C29.6569 6.59522 31.5254 8.49854 33.8285 8.49854ZM19.0109 8.49854C21.3139 8.49854 23.1824 6.59522 23.1824 4.24927C23.1824 1.90332 21.3139 0 19.0109 0C16.7078 0 14.8393 1.90332 14.8393 4.24927C14.8393 6.59522 16.7078 8.49854 19.0109 8.49854ZM4.17153 38C6.47456 38 8.34305 36.0967 8.34305 33.7507C8.34305 31.4048 6.47456 29.5015 4.17153 29.5015C1.8685 29.5015 0 31.4048 0 33.7507C0 36.0967 1.8685 38 4.17153 38ZM19.0109 38C21.3139 38 23.1824 36.0967 23.1824 33.7507C23.1824 31.4048 21.3139 29.5015 19.0109 29.5015C16.7078 29.5015 14.8393 31.4048 14.8393 33.7507C14.8393 36.0967 16.7078 38 19.0109 38Z" fill="#181C23"/>
<path d="M33.8285 38C36.1315 38 38 36.0967 38 33.7507C38 31.4048 36.1315 29.5015 33.8285 29.5015C31.5254 29.5015 29.6569 31.4048 29.6569 33.7507C29.6569 36.0967 31.5254 38 33.8285 38Z" fill="#181C23"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -3905,19 +3905,35 @@ chrome?.runtime?.onMessage.addListener((request, sender, sendResponse) => {
break;
}
case "publishOnQDN": {
const { data, identifier, service } = request.payload;
const { data, identifier, service, title,
description,
category,
tag1,
tag2,
tag3,
tag4,
tag5, uploadType } = request.payload;
publishOnQDN({
data,
identifier,
service
service,
title,
description,
category,
tag1,
tag2,
tag3,
tag4,
tag5,
uploadType
})
.then((data) => {
sendResponse(data);
})
.catch((error) => {
console.error(error.message);
sendResponse({ error: error.message });
console.error(error?.message);
sendResponse({ error: error?.message || 'Unable to publish' });
});
return true;
break;

View File

@ -152,23 +152,40 @@ export const publishGroupEncryptedResource = async ({encryptedData, identifier})
throw new Error(error.message);
}
}
export const publishOnQDN = async ({data, identifier, service}) => {
try {
if(data && identifier && service){
export const publishOnQDN = async ({data, identifier, service, title,
description,
category,
tag1,
tag2,
tag3,
tag4,
tag5,
uploadType = 'file'
}) => {
if(data && service){
const registeredName = await getNameInfo()
if(!registeredName) throw new Error('You need a name to publish')
const res = await publishData({
registeredName, file: data, service, identifier, uploadType: 'file', isBase64: true, withFee: true
const res = await publishData({
registeredName, file: data, service, identifier, uploadType, isBase64: true, withFee: true, title,
description,
category,
tag1,
tag2,
tag3,
tag4,
tag5
})
return res
} else {
throw new Error('Cannot encrypt content')
throw new Error('Cannot publish content')
}
} catch (error: any) {
throw new Error(error.message);
}
}
export function uint8ArrayToBase64(uint8Array: any) {

View File

@ -1,12 +1,13 @@
import { Box } from "@mui/material";
export const Spacer = ({ height }: any) => {
export const Spacer = ({ height, width }: any) => {
return (
<Box
sx={{
height: height,
height: height ? height : '0px',
display: 'flex',
flexShrink: 0
flexShrink: 0,
width: width ? width : '0px'
}}
/>
);

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",

View File

@ -2737,7 +2737,7 @@ export const Group = ({
/>
)}
{isMobile && (
<Apps mode={appsMode} setMode={setAppsMode} show={mobileViewMode === "apps"} />
<Apps mode={appsMode} setMode={setAppsMode} show={mobileViewMode === "apps"} myName={userInfo?.name} />
)}
{
!isMobile && !selectedGroup &&

View File

@ -3,7 +3,7 @@ import Button from '@mui/material/Button';
import Snackbar, { SnackbarCloseReason } from '@mui/material/Snackbar';
import Alert from '@mui/material/Alert';
export const CustomizedSnackbars = ({open, setOpen, info, setInfo}) => {
export const CustomizedSnackbars = ({open, setOpen, info, setInfo, duration}) => {
@ -19,9 +19,10 @@ export const CustomizedSnackbars = ({open, setOpen, info, setInfo}) => {
setInfo(null)
};
if(!open) return null
return (
<div>
<Snackbar anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }} open={open} autoHideDuration={6000} onClose={handleClose}>
<Snackbar anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }} open={open} autoHideDuration={duration || 6000} onClose={handleClose}>
<Alert

View File

@ -61,7 +61,25 @@ export const publishData = async ({
tag5,
feeAmount
}: any) => {
console.log({
registeredName,
file,
service,
identifier,
uploadType,
isBase64,
filename,
withFee,
title,
description,
category,
tag1,
tag2,
tag3,
tag4,
tag5,
feeAmount
})
const validateName = async (receiverName: string) => {
return await reusableGet(`/names/${receiverName}`)
}
@ -153,7 +171,7 @@ export const publishData = async ({
fee = feeAmount
} else if (withFee) {
const res = await getArbitraryFee()
console.log('res', res)
if (res.fee) {
fee = res.fee
} else {
@ -162,9 +180,9 @@ export const publishData = async ({
}
let transactionBytes = await uploadData(registeredName, file, fee)
if (transactionBytes.error) {
throw new Error(transactionBytes.message || 'Error when uploading')
console.log('transactionBytes', transactionBytes)
if (!transactionBytes || transactionBytes.error) {
throw new Error(transactionBytes?.message || 'Error when uploading')
} else if (transactionBytes.includes('Error 500 Internal Server Error')) {
throw new Error('Error when uploading')
}
@ -183,7 +201,8 @@ export const publishData = async ({
}
const uploadData = async (registeredName: string, file:any, fee: number) => {
if (identifier != null && identifier.trim().length > 0) {
console.log('uploadData', registeredName, file, fee)
let postBody = ''
let urlSuffix = ''
@ -211,8 +230,8 @@ export const publishData = async ({
}
let uploadDataUrl = `/arbitrary/${service}/${registeredName}${urlSuffix}`
if (identifier.trim().length > 0) {
console.log('uploadDataUrl', uploadDataUrl)
if (identifier?.trim().length > 0) {
uploadDataUrl = `/arbitrary/${service}/${registeredName}/${identifier}${urlSuffix}`
}
@ -254,14 +273,16 @@ export const publishData = async ({
if (tag5 != null && tag5 != 'undefined') {
uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag5)
}
console.log('uploadDataUrl2', uploadDataUrl)
return await reusablePost(uploadDataUrl, postBody)
}
}
try {
return await validate()
} catch (error: any) {
console.log('error2', error)
throw new Error(error?.message)
}
}