mirror of
https://github.com/Qortal/Qortal-Hub.git
synced 2025-07-24 10:41:24 +00:00
ability to publish apps
This commit is contained in:
@@ -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>
|
||||
|
||||
|
||||
|
512
src/components/Apps/AppPublish.tsx
Normal file
512
src/components/Apps/AppPublish.tsx
Normal 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>
|
||||
);
|
||||
};
|
14
src/components/Apps/AppRating.tsx
Normal file
14
src/components/Apps/AppRating.tsx
Normal 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>
|
||||
)
|
||||
}
|
@@ -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'
|
||||
}));
|
@@ -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>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@@ -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={{
|
||||
|
@@ -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>
|
||||
|
@@ -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",
|
||||
|
Reference in New Issue
Block a user