mirror of
https://github.com/Qortal/Qortal-Hub.git
synced 2025-04-24 20:07:51 +00:00
added private app feature
This commit is contained in:
parent
02b9b05419
commit
519a0bb652
@ -46,7 +46,7 @@ export const AppViewer = React.forwardRef(({ app , hide, isDevMode}, iframeRef)
|
|||||||
if(isDevMode){
|
if(isDevMode){
|
||||||
|
|
||||||
resetHistory()
|
resetHistory()
|
||||||
if(!app?.isPreview){
|
if(!app?.isPreview || app?.isPrivate){
|
||||||
setUrl(app?.url + `?time=${Date.now()}`)
|
setUrl(app?.url + `?time=${Date.now()}`)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
@ -450,7 +450,7 @@ export const AppsDesktop = ({ mode, setMode, show , myName, goToHome, setDesktop
|
|||||||
}}>
|
}}>
|
||||||
|
|
||||||
<Spacer height="30px" />
|
<Spacer height="30px" />
|
||||||
<AppsHomeDesktop availableQapps={availableQapps} setMode={setMode} myApp={myApp} myWebsite={myWebsite} />
|
<AppsHomeDesktop myName={myName} availableQapps={availableQapps} setMode={setMode} myApp={myApp} myWebsite={myWebsite} />
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -479,6 +479,7 @@ export const AppsDesktop = ({ mode, setMode, show , myName, goToHome, setDesktop
|
|||||||
isSelected={tab?.tabId === selectedTab?.tabId}
|
isSelected={tab?.tabId === selectedTab?.tabId}
|
||||||
app={tab}
|
app={tab}
|
||||||
ref={iframeRefs.current[tab.tabId]}
|
ref={iframeRefs.current[tab.tabId]}
|
||||||
|
isDevMode={tab?.service ? false : true}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@ -494,7 +495,7 @@ export const AppsDesktop = ({ mode, setMode, show , myName, goToHome, setDesktop
|
|||||||
}}>
|
}}>
|
||||||
|
|
||||||
<Spacer height="30px" />
|
<Spacer height="30px" />
|
||||||
<AppsHomeDesktop availableQapps={availableQapps} setMode={setMode} myApp={myApp} myWebsite={myWebsite} />
|
<AppsHomeDesktop myName={myName} availableQapps={availableQapps} setMode={setMode} myApp={myApp} myWebsite={myWebsite} />
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
@ -16,11 +16,13 @@ import { Spacer } from "../../common/Spacer";
|
|||||||
import { SortablePinnedApps } from "./SortablePinnedApps";
|
import { SortablePinnedApps } from "./SortablePinnedApps";
|
||||||
import { extractComponents } from "../Chat/MessageDisplay";
|
import { extractComponents } from "../Chat/MessageDisplay";
|
||||||
import ArrowOutwardIcon from '@mui/icons-material/ArrowOutward';
|
import ArrowOutwardIcon from '@mui/icons-material/ArrowOutward';
|
||||||
|
import { AppsPrivate } from "./AppsPrivate";
|
||||||
export const AppsHomeDesktop = ({
|
export const AppsHomeDesktop = ({
|
||||||
setMode,
|
setMode,
|
||||||
myApp,
|
myApp,
|
||||||
myWebsite,
|
myWebsite,
|
||||||
availableQapps,
|
availableQapps,
|
||||||
|
myName
|
||||||
}) => {
|
}) => {
|
||||||
const [qortalUrl, setQortalUrl] = useState('')
|
const [qortalUrl, setQortalUrl] = useState('')
|
||||||
|
|
||||||
@ -135,7 +137,7 @@ export const AppsHomeDesktop = ({
|
|||||||
<AppCircleLabel>Library</AppCircleLabel>
|
<AppCircleLabel>Library</AppCircleLabel>
|
||||||
</AppCircleContainer>
|
</AppCircleContainer>
|
||||||
</ButtonBase>
|
</ButtonBase>
|
||||||
|
<AppsPrivate myName={myName} />
|
||||||
<SortablePinnedApps
|
<SortablePinnedApps
|
||||||
isDesktop={true}
|
isDesktop={true}
|
||||||
availableQapps={availableQapps}
|
availableQapps={availableQapps}
|
||||||
|
@ -133,10 +133,19 @@ export const AppsNavBarDesktop = ({disableBack}) => {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
const isSelectedAppPinned = !!sortablePinnedApps?.find(
|
const isSelectedAppPinned = useMemo(()=> {
|
||||||
(item) =>
|
if(selectedTab?.isPrivate){
|
||||||
item?.name === selectedTab?.name && item?.service === selectedTab?.service
|
return !!sortablePinnedApps?.find(
|
||||||
);
|
(item) =>
|
||||||
|
item?.privateAppProperties?.name === selectedTab?.privateAppProperties?.name && item?.privateAppProperties?.service === selectedTab?.privateAppProperties?.service && item?.privateAppProperties?.identifier === selectedTab?.privateAppProperties?.identifier
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return !!sortablePinnedApps?.find(
|
||||||
|
(item) =>
|
||||||
|
item?.name === selectedTab?.name && item?.service === selectedTab?.service
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [selectedTab,sortablePinnedApps])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppsNavBarParent
|
<AppsNavBarParent
|
||||||
@ -282,22 +291,49 @@ export const AppsNavBarDesktop = ({disableBack}) => {
|
|||||||
|
|
||||||
if (isSelectedAppPinned) {
|
if (isSelectedAppPinned) {
|
||||||
// Remove the selected app if it is pinned
|
// Remove the selected app if it is pinned
|
||||||
updatedApps = prev.filter(
|
if(selectedTab?.isPrivate){
|
||||||
(item) =>
|
updatedApps = prev.filter(
|
||||||
!(
|
(item) =>
|
||||||
item?.name === selectedTab?.name &&
|
!(
|
||||||
item?.service === selectedTab?.service
|
item?.privateAppProperties?.name === selectedTab?.privateAppProperties?.name &&
|
||||||
)
|
item?.privateAppProperties?.service === selectedTab?.privateAppProperties?.service &&
|
||||||
);
|
item?.privateAppProperties?.identifier === selectedTab?.privateAppProperties?.identifier
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
updatedApps = prev.filter(
|
||||||
|
(item) =>
|
||||||
|
!(
|
||||||
|
item?.name === selectedTab?.name &&
|
||||||
|
item?.service === selectedTab?.service
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Add the selected app if it is not pinned
|
// Add the selected app if it is not pinned
|
||||||
updatedApps = [
|
if(selectedTab?.isPrivate){
|
||||||
|
updatedApps = [
|
||||||
...prev,
|
...prev,
|
||||||
{
|
{
|
||||||
name: selectedTab?.name,
|
isPreview: true,
|
||||||
service: selectedTab?.service,
|
isPrivate: true,
|
||||||
|
privateAppProperties: {
|
||||||
|
...(selectedTab?.privateAppProperties || {})
|
||||||
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
} else {
|
||||||
|
updatedApps = [
|
||||||
|
...prev,
|
||||||
|
{
|
||||||
|
name: selectedTab?.name,
|
||||||
|
service: selectedTab?.service,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
saveToLocalStorage(
|
saveToLocalStorage(
|
||||||
@ -338,9 +374,15 @@ export const AppsNavBarDesktop = ({disableBack}) => {
|
|||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
executeEvent("refreshApp", {
|
if (selectedTab?.refreshFunc) {
|
||||||
tabId: selectedTab?.tabId,
|
selectedTab.refreshFunc(selectedTab?.tabId);
|
||||||
});
|
|
||||||
|
} else {
|
||||||
|
executeEvent("refreshApp", {
|
||||||
|
tabId: selectedTab?.tabId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
handleClose();
|
handleClose();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -368,38 +410,40 @@ export const AppsNavBarDesktop = ({disableBack}) => {
|
|||||||
primary="Refresh"
|
primary="Refresh"
|
||||||
/>
|
/>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
{!selectedTab?.isPrivate && (
|
||||||
onClick={() => {
|
<MenuItem
|
||||||
executeEvent("copyLink", {
|
onClick={() => {
|
||||||
tabId: selectedTab?.tabId,
|
executeEvent("copyLink", {
|
||||||
});
|
tabId: selectedTab?.tabId,
|
||||||
handleClose();
|
});
|
||||||
}}
|
handleClose();
|
||||||
>
|
|
||||||
<ListItemIcon
|
|
||||||
sx={{
|
|
||||||
minWidth: "24px !important",
|
|
||||||
marginRight: "5px",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ContentCopyIcon
|
<ListItemIcon
|
||||||
height={20}
|
|
||||||
sx={{
|
sx={{
|
||||||
color: "rgba(250, 250, 250, 0.5)",
|
minWidth: "24px !important",
|
||||||
|
marginRight: "5px",
|
||||||
}}
|
}}
|
||||||
|
>
|
||||||
|
<ContentCopyIcon
|
||||||
|
height={20}
|
||||||
|
sx={{
|
||||||
|
color: "rgba(250, 250, 250, 0.5)",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText
|
||||||
|
sx={{
|
||||||
|
"& .MuiTypography-root": {
|
||||||
|
fontSize: "12px",
|
||||||
|
fontWeight: 600,
|
||||||
|
color: "rgba(250, 250, 250, 0.5)",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
primary="Copy link"
|
||||||
/>
|
/>
|
||||||
</ListItemIcon>
|
</MenuItem>
|
||||||
<ListItemText
|
)}
|
||||||
sx={{
|
|
||||||
"& .MuiTypography-root": {
|
|
||||||
fontSize: "12px",
|
|
||||||
fontWeight: 600,
|
|
||||||
color: "rgba(250, 250, 250, 0.5)",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
primary="Copy link"
|
|
||||||
/>
|
|
||||||
</MenuItem>
|
|
||||||
</Menu>
|
</Menu>
|
||||||
</AppsNavBarParent>
|
</AppsNavBarParent>
|
||||||
);
|
);
|
||||||
|
542
src/components/Apps/AppsPrivate.tsx
Normal file
542
src/components/Apps/AppsPrivate.tsx
Normal file
@ -0,0 +1,542 @@
|
|||||||
|
import React, { useContext, useState } from "react";
|
||||||
|
import {
|
||||||
|
Avatar,
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
ButtonBase,
|
||||||
|
Dialog,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
|
Input,
|
||||||
|
MenuItem,
|
||||||
|
Select,
|
||||||
|
Tab,
|
||||||
|
Tabs,
|
||||||
|
Typography,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { useDropzone } from "react-dropzone";
|
||||||
|
import { useHandlePrivateApps } from "./useHandlePrivateApps";
|
||||||
|
import { useRecoilState, useSetRecoilState } from "recoil";
|
||||||
|
import { myGroupsWhereIAmAdminAtom } from "../../atoms/global";
|
||||||
|
import { Label } from "../Group/AddGroup";
|
||||||
|
import { Spacer } from "../../common/Spacer";
|
||||||
|
import {
|
||||||
|
Add,
|
||||||
|
AppCircle,
|
||||||
|
AppCircleContainer,
|
||||||
|
AppCircleLabel,
|
||||||
|
PublishQAppChoseFile,
|
||||||
|
PublishQAppInfo,
|
||||||
|
} from "./Apps-styles";
|
||||||
|
import ImageUploader from "../../common/ImageUploader";
|
||||||
|
import { isMobile, MyContext } from "../../App";
|
||||||
|
import { fileToBase64 } from "../../utils/fileReading";
|
||||||
|
import { objectToBase64 } from "../../qdn/encryption/group-encryption";
|
||||||
|
import { getFee } from "../../background";
|
||||||
|
|
||||||
|
const maxFileSize = 50 * 1024 * 1024; // 50MB
|
||||||
|
|
||||||
|
export const AppsPrivate = ({myName}) => {
|
||||||
|
const { openApp } = useHandlePrivateApps();
|
||||||
|
const [file, setFile] = useState(null);
|
||||||
|
const [logo, setLogo] = useState(null);
|
||||||
|
const [qortalUrl, setQortalUrl] = useState("");
|
||||||
|
const [selectedGroup, setSelectedGroup] = useState(0);
|
||||||
|
|
||||||
|
const [valueTabPrivateApp, setValueTabPrivateApp] = useState(0);
|
||||||
|
const [myGroupsWhereIAmAdmin, setMyGroupsWhereIAmAdmin] = useRecoilState(
|
||||||
|
myGroupsWhereIAmAdminAtom
|
||||||
|
);
|
||||||
|
const [isOpenPrivateModal, setIsOpenPrivateModal] = useState(false);
|
||||||
|
const { show, setInfoSnackCustom, setOpenSnackGlobal, memberGroups } = useContext(MyContext);
|
||||||
|
|
||||||
|
const [privateAppValues, setPrivateAppValues] = useState({
|
||||||
|
name: "",
|
||||||
|
service: "DOCUMENT",
|
||||||
|
identifier: "",
|
||||||
|
groupId: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [newPrivateAppValues, setNewPrivateAppValues] = useState({
|
||||||
|
service: "DOCUMENT",
|
||||||
|
identifier: "",
|
||||||
|
name: "",
|
||||||
|
});
|
||||||
|
const { getRootProps, getInputProps } = useDropzone({
|
||||||
|
accept: {
|
||||||
|
"application/zip": [".zip"], // Only accept zip files
|
||||||
|
},
|
||||||
|
maxSize: maxFileSize,
|
||||||
|
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 addPrivateApp = async () => {
|
||||||
|
try {
|
||||||
|
if (privateAppValues?.groupId === 0) return;
|
||||||
|
|
||||||
|
await openApp(privateAppValues, true);
|
||||||
|
} catch (error) {
|
||||||
|
console.log('error', error?.message)
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearFields = () => {
|
||||||
|
setPrivateAppValues({
|
||||||
|
name: "",
|
||||||
|
service: "DOCUMENT",
|
||||||
|
identifier: "",
|
||||||
|
groupId: 0,
|
||||||
|
});
|
||||||
|
setNewPrivateAppValues({
|
||||||
|
service: "DOCUMENT",
|
||||||
|
identifier: "",
|
||||||
|
name: "",
|
||||||
|
});
|
||||||
|
setFile(null);
|
||||||
|
setValueTabPrivateApp(0);
|
||||||
|
setSelectedGroup(0);
|
||||||
|
setLogo(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const publishPrivateApp = async () => {
|
||||||
|
try {
|
||||||
|
if (selectedGroup === 0) return;
|
||||||
|
if (!logo) throw new Error("Please select an image for a logo");
|
||||||
|
if (!myName) throw new Error("You need a Qortal name to publish");
|
||||||
|
if (!newPrivateAppValues?.name) throw new Error("Your app needs a name");
|
||||||
|
const base64Logo = await fileToBase64(logo);
|
||||||
|
const base64App = await fileToBase64(file);
|
||||||
|
const objectToSave = {
|
||||||
|
app: base64App,
|
||||||
|
logo: base64Logo,
|
||||||
|
name: newPrivateAppValues.name,
|
||||||
|
};
|
||||||
|
const object64 = await objectToBase64(objectToSave);
|
||||||
|
const decryptedData = await window.sendMessage(
|
||||||
|
"ENCRYPT_QORTAL_GROUP_DATA",
|
||||||
|
|
||||||
|
{
|
||||||
|
base64: object64,
|
||||||
|
groupId: selectedGroup,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (decryptedData?.error) {
|
||||||
|
throw new Error(
|
||||||
|
decryptedData?.error || "Unable to encrypt app. App not published"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const fee = await getFee("ARBITRARY");
|
||||||
|
|
||||||
|
await show({
|
||||||
|
message: "Would you like to publish this app?",
|
||||||
|
publishFee: fee.fee + " QORT",
|
||||||
|
});
|
||||||
|
await new Promise((res, rej) => {
|
||||||
|
window
|
||||||
|
.sendMessage("publishOnQDN", {
|
||||||
|
data: decryptedData,
|
||||||
|
identifier: newPrivateAppValues?.identifier,
|
||||||
|
service: newPrivateAppValues?.service,
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (!response?.error) {
|
||||||
|
res(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
rej(response.error);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
rej(error.message || "An error occurred");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
openApp(
|
||||||
|
{
|
||||||
|
identifier: newPrivateAppValues?.identifier,
|
||||||
|
service: newPrivateAppValues?.service,
|
||||||
|
name: myName,
|
||||||
|
groupId: selectedGroup,
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
clearFields();
|
||||||
|
} catch (error) {
|
||||||
|
setOpenSnackGlobal(true)
|
||||||
|
setInfoSnackCustom({
|
||||||
|
type: "error",
|
||||||
|
message: error?.message || "Unable to publish app",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChange = (event: React.SyntheticEvent, newValue: number) => {
|
||||||
|
setValueTabPrivateApp(newValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
function a11yProps(index: number) {
|
||||||
|
return {
|
||||||
|
id: `simple-tab-${index}`,
|
||||||
|
"aria-controls": `simple-tabpanel-${index}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ButtonBase
|
||||||
|
onClick={() => {
|
||||||
|
setIsOpenPrivateModal(true);
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
width: "80px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AppCircleContainer
|
||||||
|
sx={{
|
||||||
|
gap: !isMobile ? "10px" : "5px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AppCircle>
|
||||||
|
<Add>+</Add>
|
||||||
|
</AppCircle>
|
||||||
|
<AppCircleLabel>Private</AppCircleLabel>
|
||||||
|
</AppCircleContainer>
|
||||||
|
</ButtonBase>
|
||||||
|
{isOpenPrivateModal && (
|
||||||
|
<Dialog
|
||||||
|
open={isOpenPrivateModal}
|
||||||
|
aria-labelledby="alert-dialog-title"
|
||||||
|
aria-describedby="alert-dialog-description"
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
if (valueTabPrivateApp === 0) {
|
||||||
|
if (
|
||||||
|
!privateAppValues.name ||
|
||||||
|
!privateAppValues.service ||
|
||||||
|
!privateAppValues.identifier ||
|
||||||
|
!privateAppValues?.groupId
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
addPrivateApp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
maxWidth="md"
|
||||||
|
fullWidth={true}
|
||||||
|
>
|
||||||
|
<DialogTitle id="alert-dialog-title">
|
||||||
|
{valueTabPrivateApp === 0
|
||||||
|
? "Access private app"
|
||||||
|
: "Publish private app"}
|
||||||
|
</DialogTitle>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Tabs
|
||||||
|
value={valueTabPrivateApp}
|
||||||
|
onChange={handleChange}
|
||||||
|
aria-label="basic tabs example"
|
||||||
|
variant={isMobile ? "scrollable" : "fullWidth"} // Scrollable on mobile, full width on desktop
|
||||||
|
scrollButtons="auto"
|
||||||
|
allowScrollButtonsMobile
|
||||||
|
sx={{
|
||||||
|
"& .MuiTabs-indicator": {
|
||||||
|
backgroundColor: "white",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tab
|
||||||
|
label="Access app"
|
||||||
|
{...a11yProps(0)}
|
||||||
|
sx={{
|
||||||
|
"&.Mui-selected": {
|
||||||
|
color: "white",
|
||||||
|
},
|
||||||
|
fontSize: isMobile ? "0.75rem" : "1rem", // Adjust font size for mobile
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Tab
|
||||||
|
label="Publish app"
|
||||||
|
{...a11yProps(1)}
|
||||||
|
sx={{
|
||||||
|
"&.Mui-selected": {
|
||||||
|
color: "white",
|
||||||
|
},
|
||||||
|
fontSize: isMobile ? "0.75rem" : "1rem", // Adjust font size for mobile
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Tabs>
|
||||||
|
</Box>
|
||||||
|
{valueTabPrivateApp === 0 && (
|
||||||
|
<>
|
||||||
|
<DialogContent>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: "5px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Label>Select a group</Label>
|
||||||
|
<Label>Only private groups will be shown</Label>
|
||||||
|
<Select
|
||||||
|
labelId="demo-simple-select-label"
|
||||||
|
id="demo-simple-select"
|
||||||
|
value={privateAppValues?.groupId}
|
||||||
|
label="Groups"
|
||||||
|
onChange={(e) => {
|
||||||
|
setPrivateAppValues((prev) => {
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
groupId: e.target.value,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MenuItem value={0}>No group selected</MenuItem>
|
||||||
|
|
||||||
|
{memberGroups
|
||||||
|
?.filter((item) => !item?.isOpen)
|
||||||
|
.map((group) => {
|
||||||
|
return (
|
||||||
|
<MenuItem key={group?.groupId} value={group?.groupId}>
|
||||||
|
{group?.groupName}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Select>
|
||||||
|
</Box>
|
||||||
|
<Spacer height="10px" />
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: "5px",
|
||||||
|
marginTop: "15px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Label>name</Label>
|
||||||
|
<Input
|
||||||
|
placeholder="name"
|
||||||
|
value={privateAppValues?.name}
|
||||||
|
onChange={(e) =>
|
||||||
|
setPrivateAppValues((prev) => {
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
name: e.target.value,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: "5px",
|
||||||
|
marginTop: "15px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Label>identifier</Label>
|
||||||
|
<Input
|
||||||
|
placeholder="identifier"
|
||||||
|
value={privateAppValues?.identifier}
|
||||||
|
onChange={(e) =>
|
||||||
|
setPrivateAppValues((prev) => {
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
identifier: e.target.value,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={() => {
|
||||||
|
setIsOpenPrivateModal(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
disabled={
|
||||||
|
!privateAppValues.name ||
|
||||||
|
!privateAppValues.service ||
|
||||||
|
!privateAppValues.identifier ||
|
||||||
|
!privateAppValues?.groupId
|
||||||
|
}
|
||||||
|
variant="contained"
|
||||||
|
onClick={() => addPrivateApp()}
|
||||||
|
autoFocus
|
||||||
|
>
|
||||||
|
Access
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{valueTabPrivateApp === 1 && (
|
||||||
|
<>
|
||||||
|
<DialogContent>
|
||||||
|
<PublishQAppInfo
|
||||||
|
sx={{
|
||||||
|
fontSize: "14px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Select .zip file containing static content:{" "}
|
||||||
|
</PublishQAppInfo>
|
||||||
|
<Spacer height="10px" />
|
||||||
|
<PublishQAppInfo
|
||||||
|
sx={{
|
||||||
|
fontSize: "14px",
|
||||||
|
}}
|
||||||
|
>{`
|
||||||
|
50mb MB maximum`}</PublishQAppInfo>
|
||||||
|
{file && (
|
||||||
|
<>
|
||||||
|
<Spacer height="5px" />
|
||||||
|
<PublishQAppInfo>{`Selected: (${file?.name})`}</PublishQAppInfo>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Spacer height="18px" />
|
||||||
|
<PublishQAppChoseFile {...getRootProps()}>
|
||||||
|
{" "}
|
||||||
|
<input {...getInputProps()} />
|
||||||
|
{file ? "Change" : "Choose"} File
|
||||||
|
</PublishQAppChoseFile>
|
||||||
|
<Spacer height="20px" />
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: "5px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Label>Select a group</Label>
|
||||||
|
<Label>
|
||||||
|
Only groups where you are an admin will be shown
|
||||||
|
</Label>
|
||||||
|
<Select
|
||||||
|
labelId="demo-simple-select-label"
|
||||||
|
id="demo-simple-select"
|
||||||
|
value={selectedGroup}
|
||||||
|
label="Groups where you are an admin"
|
||||||
|
onChange={(e) => setSelectedGroup(e.target.value)}
|
||||||
|
>
|
||||||
|
<MenuItem value={0}>No group selected</MenuItem>
|
||||||
|
{myGroupsWhereIAmAdmin
|
||||||
|
?.filter((item) => !item?.isOpen)
|
||||||
|
.map((group) => {
|
||||||
|
return (
|
||||||
|
<MenuItem key={group?.groupId} value={group?.groupId}>
|
||||||
|
{group?.groupName}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Select>
|
||||||
|
</Box>
|
||||||
|
<Spacer height="20px" />
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: "5px",
|
||||||
|
marginTop: "15px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Label>identifier</Label>
|
||||||
|
<Input
|
||||||
|
placeholder="identifier"
|
||||||
|
value={newPrivateAppValues?.identifier}
|
||||||
|
onChange={(e) =>
|
||||||
|
setNewPrivateAppValues((prev) => {
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
identifier: e.target.value,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Spacer height="10px" />
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: "5px",
|
||||||
|
marginTop: "15px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Label>App name</Label>
|
||||||
|
<Input
|
||||||
|
placeholder="App name"
|
||||||
|
value={newPrivateAppValues?.name}
|
||||||
|
onChange={(e) =>
|
||||||
|
setNewPrivateAppValues((prev) => {
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
name: e.target.value,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Spacer height="10px" />
|
||||||
|
<ImageUploader onPick={(file) => setLogo(file)}>
|
||||||
|
<Button variant="contained">Choose logo</Button>
|
||||||
|
</ImageUploader>
|
||||||
|
{logo?.name}
|
||||||
|
<Spacer height="25px" />
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={() => {
|
||||||
|
setIsOpenPrivateModal(false);
|
||||||
|
clearFields();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
disabled={
|
||||||
|
!newPrivateAppValues.name ||
|
||||||
|
!newPrivateAppValues.service ||
|
||||||
|
!newPrivateAppValues.identifier ||
|
||||||
|
!selectedGroup
|
||||||
|
}
|
||||||
|
variant="contained"
|
||||||
|
onClick={() => publishPrivateApp()}
|
||||||
|
autoFocus
|
||||||
|
>
|
||||||
|
Publish
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Dialog>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -1,18 +1,21 @@
|
|||||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
import React, { useContext, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { DndContext, closestCenter } from '@dnd-kit/core';
|
import { DndContext, closestCenter } from '@dnd-kit/core';
|
||||||
import { arrayMove, SortableContext, sortableKeyboardCoordinates, useSortable } from '@dnd-kit/sortable';
|
import { arrayMove, SortableContext, sortableKeyboardCoordinates, useSortable } from '@dnd-kit/sortable';
|
||||||
import { KeyboardSensor, PointerSensor, TouchSensor, useSensor, useSensors } from '@dnd-kit/core';
|
import { KeyboardSensor, PointerSensor, TouchSensor, useSensor, useSensors } from '@dnd-kit/core';
|
||||||
import { CSS } from '@dnd-kit/utilities';
|
import { CSS } from '@dnd-kit/utilities';
|
||||||
import { Avatar, ButtonBase } from '@mui/material';
|
import { Avatar, ButtonBase } from '@mui/material';
|
||||||
import { AppCircle, AppCircleContainer, AppCircleLabel } from './Apps-styles';
|
import { AppCircle, AppCircleContainer, AppCircleLabel } from './Apps-styles';
|
||||||
import { getBaseApiReact } from '../../App';
|
import { getBaseApiReact, MyContext } from '../../App';
|
||||||
import { executeEvent } from '../../utils/events';
|
import { executeEvent } from '../../utils/events';
|
||||||
import { settingsLocalLastUpdatedAtom, sortablePinnedAppsAtom } from '../../atoms/global';
|
import { settingsLocalLastUpdatedAtom, sortablePinnedAppsAtom } from '../../atoms/global';
|
||||||
import { useRecoilState, useSetRecoilState } from 'recoil';
|
import { useRecoilState, useSetRecoilState } from 'recoil';
|
||||||
import { saveToLocalStorage } from './AppsNavBar';
|
import { saveToLocalStorage } from './AppsNavBar';
|
||||||
import { ContextMenuPinnedApps } from '../ContextMenuPinnedApps';
|
import { ContextMenuPinnedApps } from '../ContextMenuPinnedApps';
|
||||||
|
import LockIcon from "@mui/icons-material/Lock";
|
||||||
|
import { useHandlePrivateApps } from './useHandlePrivateApps';
|
||||||
const SortableItem = ({ id, name, app, isDesktop }) => {
|
const SortableItem = ({ id, name, app, isDesktop }) => {
|
||||||
|
const {openApp} = useHandlePrivateApps()
|
||||||
|
|
||||||
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id });
|
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id });
|
||||||
const style = {
|
const style = {
|
||||||
transform: CSS.Transform.toString(transform),
|
transform: CSS.Transform.toString(transform),
|
||||||
@ -28,17 +31,27 @@ const SortableItem = ({ id, name, app, isDesktop }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ContextMenuPinnedApps app={app} isMine={!!app?.isMine}>
|
<ContextMenuPinnedApps app={app} isMine={!!app?.isMine}>
|
||||||
<ButtonBase
|
<ButtonBase
|
||||||
ref={setNodeRef} {...attributes} {...listeners}
|
ref={setNodeRef} {...attributes} {...listeners}
|
||||||
sx={{
|
sx={{
|
||||||
width: "80px",
|
width: "80px",
|
||||||
transform: CSS.Transform.toString(transform),
|
transform: CSS.Transform.toString(transform),
|
||||||
transition,
|
transition,
|
||||||
}}
|
}}
|
||||||
onClick={()=> {
|
onClick={async ()=> {
|
||||||
executeEvent("addTab", {
|
if(app?.isPrivate){
|
||||||
data: app
|
try {
|
||||||
})
|
await openApp(app?.privateAppProperties)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
executeEvent("addTab", {
|
||||||
|
data: app
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<AppCircleContainer sx={{
|
<AppCircleContainer sx={{
|
||||||
@ -50,7 +63,15 @@ const SortableItem = ({ id, name, app, isDesktop }) => {
|
|||||||
border: "none",
|
border: "none",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Avatar
|
{app?.isPrivate && !app?.privateAppProperties?.logo ? (
|
||||||
|
<LockIcon
|
||||||
|
sx={{
|
||||||
|
height: "42px",
|
||||||
|
width: "42px",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Avatar
|
||||||
sx={{
|
sx={{
|
||||||
height: "42px",
|
height: "42px",
|
||||||
width: "42px",
|
width: "42px",
|
||||||
@ -59,7 +80,7 @@ const SortableItem = ({ id, name, app, isDesktop }) => {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
alt={app?.metadata?.title || app?.name}
|
alt={app?.metadata?.title || app?.name}
|
||||||
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
|
src={ app?.privateAppProperties?.logo ? app?.privateAppProperties?.logo :`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
|
||||||
app?.name
|
app?.name
|
||||||
}/qortal_avatar?async=true`}
|
}/qortal_avatar?async=true`}
|
||||||
>
|
>
|
||||||
@ -72,10 +93,19 @@ const SortableItem = ({ id, name, app, isDesktop }) => {
|
|||||||
alt="center-icon"
|
alt="center-icon"
|
||||||
/>
|
/>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
|
)}
|
||||||
|
|
||||||
</AppCircle>
|
</AppCircle>
|
||||||
<AppCircleLabel>
|
{app?.isPrivate ? (
|
||||||
|
<AppCircleLabel>
|
||||||
|
{`${app?.privateAppProperties?.appName || "Private"}`}
|
||||||
|
</AppCircleLabel>
|
||||||
|
) : (
|
||||||
|
<AppCircleLabel>
|
||||||
{app?.metadata?.title || app?.name}
|
{app?.metadata?.title || app?.name}
|
||||||
</AppCircleLabel>
|
</AppCircleLabel>
|
||||||
|
)}
|
||||||
|
|
||||||
</AppCircleContainer>
|
</AppCircleContainer>
|
||||||
</ButtonBase>
|
</ButtonBase>
|
||||||
</ContextMenuPinnedApps>
|
</ContextMenuPinnedApps>
|
||||||
|
@ -5,6 +5,7 @@ import { getBaseApiReact } from '../../App';
|
|||||||
import { Avatar, ButtonBase } from '@mui/material';
|
import { Avatar, ButtonBase } from '@mui/material';
|
||||||
import LogoSelected from "../../assets/svgs/LogoSelected.svg";
|
import LogoSelected from "../../assets/svgs/LogoSelected.svg";
|
||||||
import { executeEvent } from '../../utils/events';
|
import { executeEvent } from '../../utils/events';
|
||||||
|
import LockIcon from "@mui/icons-material/Lock";
|
||||||
|
|
||||||
const TabComponent = ({isSelected, app}) => {
|
const TabComponent = ({isSelected, app}) => {
|
||||||
return (
|
return (
|
||||||
@ -34,25 +35,34 @@ const TabComponent = ({isSelected, app}) => {
|
|||||||
} src={NavCloseTab}/>
|
} src={NavCloseTab}/>
|
||||||
|
|
||||||
) }
|
) }
|
||||||
<Avatar
|
{app?.isPrivate && !app?.privateAppProperties?.logo ? (
|
||||||
sx={{
|
<LockIcon
|
||||||
height: "28px",
|
sx={{
|
||||||
width: "28px",
|
height: "28px",
|
||||||
}}
|
width: "28px",
|
||||||
alt={app?.name}
|
}}
|
||||||
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
|
/>
|
||||||
app?.name
|
) : (
|
||||||
}/qortal_avatar?async=true`}
|
<Avatar
|
||||||
>
|
sx={{
|
||||||
<img
|
height: "28px",
|
||||||
style={{
|
width: "28px",
|
||||||
width: "28px",
|
}}
|
||||||
height: "auto",
|
alt={app?.name}
|
||||||
}}
|
src={app?.privateAppProperties?.logo ? app?.privateAppProperties?.logo :`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
|
||||||
src={LogoSelected}
|
app?.name
|
||||||
alt="center-icon"
|
}/qortal_avatar?async=true`}
|
||||||
/>
|
>
|
||||||
</Avatar>
|
<img
|
||||||
|
style={{
|
||||||
|
width: "28px",
|
||||||
|
height: "auto",
|
||||||
|
}}
|
||||||
|
src={LogoSelected}
|
||||||
|
alt="center-icon"
|
||||||
|
/>
|
||||||
|
</Avatar>
|
||||||
|
)}
|
||||||
</TabParent>
|
</TabParent>
|
||||||
</ButtonBase>
|
</ButtonBase>
|
||||||
)
|
)
|
||||||
|
237
src/components/Apps/useHandlePrivateApps.tsx
Normal file
237
src/components/Apps/useHandlePrivateApps.tsx
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
import React, { useContext, useState } from "react";
|
||||||
|
import { executeEvent } from "../../utils/events";
|
||||||
|
import { getBaseApiReact, MyContext } from "../../App";
|
||||||
|
import { createEndpoint } from "../../background";
|
||||||
|
import { useRecoilState, useSetRecoilState } from "recoil";
|
||||||
|
import {
|
||||||
|
settingsLocalLastUpdatedAtom,
|
||||||
|
sortablePinnedAppsAtom,
|
||||||
|
} from "../../atoms/global";
|
||||||
|
import { saveToLocalStorage } from "./AppsNavBarDesktop";
|
||||||
|
import { base64ToBlobUrl } from "../../utils/fileReading";
|
||||||
|
import { base64ToUint8Array } from "../../qdn/encryption/group-encryption";
|
||||||
|
import { uint8ArrayToObject } from "../../backgroundFunctions/encryption";
|
||||||
|
|
||||||
|
export const useHandlePrivateApps = () => {
|
||||||
|
const [status, setStatus] = useState("");
|
||||||
|
const {
|
||||||
|
openSnackGlobal,
|
||||||
|
setOpenSnackGlobal,
|
||||||
|
infoSnackCustom,
|
||||||
|
setInfoSnackCustom,
|
||||||
|
} = useContext(MyContext);
|
||||||
|
const [sortablePinnedApps, setSortablePinnedApps] = useRecoilState(
|
||||||
|
sortablePinnedAppsAtom
|
||||||
|
);
|
||||||
|
const setSettingsLocalLastUpdated = useSetRecoilState(
|
||||||
|
settingsLocalLastUpdatedAtom
|
||||||
|
);
|
||||||
|
const openApp = async (
|
||||||
|
privateAppProperties,
|
||||||
|
addToPinnedApps,
|
||||||
|
setLoadingStatePrivateApp
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
|
||||||
|
|
||||||
|
if(setLoadingStatePrivateApp){
|
||||||
|
setLoadingStatePrivateApp(`Downloading and decrypting private app.`);
|
||||||
|
|
||||||
|
}
|
||||||
|
setOpenSnackGlobal(true);
|
||||||
|
|
||||||
|
setInfoSnackCustom({
|
||||||
|
type: "info",
|
||||||
|
message: "Fetching app data",
|
||||||
|
duration: null
|
||||||
|
});
|
||||||
|
const urlData = `${getBaseApiReact()}/arbitrary/${
|
||||||
|
privateAppProperties?.service
|
||||||
|
}/${privateAppProperties?.name}/${
|
||||||
|
privateAppProperties?.identifier
|
||||||
|
}?encoding=base64`;
|
||||||
|
let data;
|
||||||
|
try {
|
||||||
|
const responseData = await fetch(urlData, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if(!responseData?.ok){
|
||||||
|
if(setLoadingStatePrivateApp){
|
||||||
|
setLoadingStatePrivateApp("Error! Unable to download private app.");
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("Unable to fetch app");
|
||||||
|
}
|
||||||
|
|
||||||
|
data = await responseData.text();
|
||||||
|
if (data?.error) {
|
||||||
|
if(setLoadingStatePrivateApp){
|
||||||
|
|
||||||
|
setLoadingStatePrivateApp("Error! Unable to download private app.");
|
||||||
|
}
|
||||||
|
throw new Error("Unable to fetch app");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if(setLoadingStatePrivateApp){
|
||||||
|
|
||||||
|
setLoadingStatePrivateApp("Error! Unable to download private app.");
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
let decryptedData;
|
||||||
|
// eslint-disable-next-line no-useless-catch
|
||||||
|
try {
|
||||||
|
decryptedData = await window.sendMessage(
|
||||||
|
"DECRYPT_QORTAL_GROUP_DATA",
|
||||||
|
|
||||||
|
{
|
||||||
|
base64: data,
|
||||||
|
groupId: privateAppProperties?.groupId,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (decryptedData?.error) {
|
||||||
|
if(setLoadingStatePrivateApp){
|
||||||
|
|
||||||
|
setLoadingStatePrivateApp("Error! Unable to decrypt private app.");
|
||||||
|
}
|
||||||
|
throw new Error(decryptedData?.error);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if(setLoadingStatePrivateApp){
|
||||||
|
|
||||||
|
setLoadingStatePrivateApp("Error! Unable to decrypt private app.");
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const convertToUint = base64ToUint8Array(decryptedData);
|
||||||
|
const UintToObject = uint8ArrayToObject(convertToUint);
|
||||||
|
|
||||||
|
if (decryptedData) {
|
||||||
|
setInfoSnackCustom({
|
||||||
|
type: "info",
|
||||||
|
message: "Building app",
|
||||||
|
});
|
||||||
|
const endpoint = await createEndpoint(
|
||||||
|
`/arbitrary/APP/${privateAppProperties?.name}/zip?preview=true`
|
||||||
|
);
|
||||||
|
const response = await fetch(endpoint, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "text/plain",
|
||||||
|
},
|
||||||
|
body: UintToObject?.app,
|
||||||
|
});
|
||||||
|
const previewPath = await response.text();
|
||||||
|
const refreshfunc = async (tabId, privateAppProperties) => {
|
||||||
|
const checkIfPreviewLinkStillWorksUrl = await createEndpoint(
|
||||||
|
`/render/hash/HmtnZpcRPwisMfprUXuBp27N2xtv5cDiQjqGZo8tbZS?secret=E39WTiG4qBq3MFcMPeRZabtQuzyfHg9ZuR5SgY7nW1YH`
|
||||||
|
);
|
||||||
|
const res = await fetch(checkIfPreviewLinkStillWorksUrl);
|
||||||
|
if (res.ok) {
|
||||||
|
executeEvent("refreshApp", {
|
||||||
|
tabId: tabId,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const endpoint = await createEndpoint(
|
||||||
|
`/arbitrary/APP/${privateAppProperties?.name}/zip?preview=true`
|
||||||
|
);
|
||||||
|
const response = await fetch(endpoint, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "text/plain",
|
||||||
|
},
|
||||||
|
body: UintToObject?.app,
|
||||||
|
});
|
||||||
|
const previewPath = await response.text();
|
||||||
|
executeEvent("updateAppUrl", {
|
||||||
|
tabId: tabId,
|
||||||
|
url: await createEndpoint(previewPath),
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
executeEvent("refreshApp", {
|
||||||
|
tabId: tabId,
|
||||||
|
});
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const appName = UintToObject?.name;
|
||||||
|
const logo = UintToObject?.logo
|
||||||
|
? `data:image/png;base64,${UintToObject?.logo}`
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const dataBody = {
|
||||||
|
url: await createEndpoint(previewPath),
|
||||||
|
isPreview: true,
|
||||||
|
isPrivate: true,
|
||||||
|
privateAppProperties: { ...privateAppProperties, logo, appName },
|
||||||
|
filePath: "",
|
||||||
|
refreshFunc: (tabId) => {
|
||||||
|
refreshfunc(tabId, privateAppProperties);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
executeEvent("addTab", {
|
||||||
|
data: dataBody,
|
||||||
|
});
|
||||||
|
setInfoSnackCustom({
|
||||||
|
type: "success",
|
||||||
|
message: "Opened",
|
||||||
|
});
|
||||||
|
if(setLoadingStatePrivateApp){
|
||||||
|
|
||||||
|
setLoadingStatePrivateApp(``);
|
||||||
|
}
|
||||||
|
if (addToPinnedApps) {
|
||||||
|
setSortablePinnedApps((prev) => {
|
||||||
|
const updatedApps = [
|
||||||
|
...prev,
|
||||||
|
{
|
||||||
|
isPrivate: true,
|
||||||
|
isPreview: true,
|
||||||
|
privateAppProperties: {
|
||||||
|
...privateAppProperties,
|
||||||
|
logo,
|
||||||
|
appName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
saveToLocalStorage(
|
||||||
|
"ext_saved_settings",
|
||||||
|
"sortablePinnedApps",
|
||||||
|
updatedApps
|
||||||
|
);
|
||||||
|
return updatedApps;
|
||||||
|
});
|
||||||
|
setSettingsLocalLastUpdated(Date.now());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if(setLoadingStatePrivateApp){
|
||||||
|
|
||||||
|
setLoadingStatePrivateApp(`Error! ${error?.message || 'Unable to build private app.'}`);
|
||||||
|
}
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
setInfoSnackCustom({
|
||||||
|
type: "error",
|
||||||
|
message: error?.message || "Unable to fetch app",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
openApp,
|
||||||
|
status,
|
||||||
|
};
|
||||||
|
};
|
@ -124,11 +124,19 @@ export const ContextMenuPinnedApps = ({ children, app, isMine }) => {
|
|||||||
<MenuItem onClick={(e) => {
|
<MenuItem onClick={(e) => {
|
||||||
handleClose(e);
|
handleClose(e);
|
||||||
setSortablePinnedApps((prev) => {
|
setSortablePinnedApps((prev) => {
|
||||||
const updatedApps = prev.filter(
|
if(app?.isPrivate){
|
||||||
(item) => !(item?.name === app?.name && item?.service === app?.service)
|
const updatedApps = prev.filter(
|
||||||
);
|
(item) => !(item?.privateAppProperties?.name === app?.privateAppProperties?.name && item?.privateAppProperties?.service === app?.privateAppProperties?.service && item?.privateAppProperties?.identifier === app?.privateAppProperties?.identifier)
|
||||||
saveToLocalStorage('ext_saved_settings', 'sortablePinnedApps', updatedApps);
|
);
|
||||||
return updatedApps;
|
saveToLocalStorage('ext_saved_settings', 'sortablePinnedApps', updatedApps);
|
||||||
|
return updatedApps;
|
||||||
|
} else {
|
||||||
|
const updatedApps = prev.filter(
|
||||||
|
(item) => !(item?.name === app?.name && item?.service === app?.service)
|
||||||
|
);
|
||||||
|
saveToLocalStorage('ext_saved_settings', 'sortablePinnedApps', updatedApps);
|
||||||
|
return updatedApps;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}}>
|
}}>
|
||||||
<ListItemIcon sx={{ minWidth: '32px' }}>
|
<ListItemIcon sx={{ minWidth: '32px' }}>
|
||||||
|
@ -22,7 +22,7 @@ export const CustomizedSnackbars = ({open, setOpen, info, setInfo, duration}) =
|
|||||||
if(!open) return null
|
if(!open) return null
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Snackbar anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }} open={open} autoHideDuration={duration || 6000} onClose={handleClose}>
|
<Snackbar anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }} open={open} autoHideDuration={info?.duration === null ? null : (duration || 6000)} onClose={handleClose}>
|
||||||
<Alert
|
<Alert
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user