mirror of
https://github.com/Qortal/Qortal-Hub.git
synced 2025-04-27 21:37:51 +00:00
private apps
This commit is contained in:
parent
3515c0b6c9
commit
16b7ed2dbc
@ -40,15 +40,17 @@ export const AppViewer = React.forwardRef(({ app , hide, isDevMode}, iframeRef)
|
||||
}, [url, isDevMode])
|
||||
|
||||
|
||||
|
||||
const refreshAppFunc = (e) => {
|
||||
const {tabId} = e.detail
|
||||
if(tabId === app?.tabId){
|
||||
if(isDevMode){
|
||||
|
||||
resetHistory()
|
||||
if(!app?.isPreview){
|
||||
if(!app?.isPreview || app?.isPrivate){
|
||||
setUrl(app?.url + `?time=${Date.now()}`)
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
@ -310,6 +310,7 @@ export const AppsDesktop = ({ mode, setMode, show , myName, goToHome, setDesktop
|
||||
};
|
||||
}, [tabs]);
|
||||
|
||||
|
||||
return (
|
||||
<AppsParent
|
||||
sx={{
|
||||
@ -450,7 +451,7 @@ export const AppsDesktop = ({ mode, setMode, show , myName, goToHome, setDesktop
|
||||
}}>
|
||||
|
||||
<Spacer height="30px" />
|
||||
<AppsHomeDesktop availableQapps={availableQapps} setMode={setMode} myApp={myApp} myWebsite={myWebsite} />
|
||||
<AppsHomeDesktop availableQapps={availableQapps} setMode={setMode} myApp={myApp} myWebsite={myWebsite} myName={myName}/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
@ -479,6 +480,7 @@ export const AppsDesktop = ({ mode, setMode, show , myName, goToHome, setDesktop
|
||||
isSelected={tab?.tabId === selectedTab?.tabId}
|
||||
app={tab}
|
||||
ref={iframeRefs.current[tab.tabId]}
|
||||
isDevMode={tab?.service ? false : true}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
@ -494,7 +496,7 @@ export const AppsDesktop = ({ mode, setMode, show , myName, goToHome, setDesktop
|
||||
}}>
|
||||
|
||||
<Spacer height="30px" />
|
||||
<AppsHomeDesktop availableQapps={availableQapps} setMode={setMode} myApp={myApp} myWebsite={myWebsite} />
|
||||
<AppsHomeDesktop availableQapps={availableQapps} setMode={setMode} myApp={myApp} myWebsite={myWebsite} myName={myName} />
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useMemo, useState } from "react";
|
||||
import React, { useContext, useMemo, useState } from "react";
|
||||
import {
|
||||
AppCircle,
|
||||
AppCircleContainer,
|
||||
@ -6,24 +6,175 @@ import {
|
||||
AppLibrarySubTitle,
|
||||
AppsContainer,
|
||||
AppsParent,
|
||||
PublishQAppChoseFile,
|
||||
PublishQAppInfo,
|
||||
} from "./Apps-styles";
|
||||
import { Avatar, Box, ButtonBase, Input } from "@mui/material";
|
||||
import { Avatar, Box, Button, ButtonBase, Dialog, DialogActions, DialogContent, DialogTitle, Input, MenuItem, Select, Tab, Tabs } from "@mui/material";
|
||||
import { Add } from "@mui/icons-material";
|
||||
import { getBaseApiReact, isMobile } from "../../App";
|
||||
import { getBaseApiReact, isMobile, MyContext } from "../../App";
|
||||
import LogoSelected from "../../assets/svgs/LogoSelected.svg";
|
||||
import { executeEvent } from "../../utils/events";
|
||||
import { Spacer } from "../../common/Spacer";
|
||||
import { SortablePinnedApps } from "./SortablePinnedApps";
|
||||
import { extractComponents } from "../Chat/MessageDisplay";
|
||||
import ArrowOutwardIcon from '@mui/icons-material/ArrowOutward';
|
||||
import { createEndpoint, getFee } from "../../background";
|
||||
import { useRecoilState, useSetRecoilState } from "recoil";
|
||||
import { myGroupsWhereIAmAdminAtom, settingsLocalLastUpdatedAtom, sortablePinnedAppsAtom } from "../../atoms/global";
|
||||
import { saveToLocalStorage } from "./AppsNavBarDesktop";
|
||||
import { Label } from "../Group/AddGroup";
|
||||
import { useHandlePrivateApps } from "./useHandlePrivateApps";
|
||||
import { useDropzone } from "react-dropzone";
|
||||
|
||||
const maxFileSize = 50 * 1024 * 1024 ; // 50MB or 400MB
|
||||
|
||||
export const AppsHomeDesktop = ({
|
||||
setMode,
|
||||
myApp,
|
||||
myWebsite,
|
||||
availableQapps,
|
||||
myName
|
||||
}) => {
|
||||
const [qortalUrl, setQortalUrl] = useState('')
|
||||
const {openApp} = useHandlePrivateApps()
|
||||
const [file, setFile] = useState(null)
|
||||
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 {
|
||||
show,
|
||||
setInfoSnackCustom,
|
||||
memberGroups
|
||||
} = useContext(MyContext);
|
||||
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 [sortablePinnedApps, setSortablePinnedApps] = useRecoilState(
|
||||
sortablePinnedAppsAtom
|
||||
);
|
||||
const setSettingsLocalLastUpdated = useSetRecoilState(
|
||||
settingsLocalLastUpdatedAtom
|
||||
);
|
||||
const [privateAppValues, setPrivateAppValues] = useState({
|
||||
name: 'a-test',
|
||||
service: 'DOCUMENT',
|
||||
identifier: 'qortal_test_private',
|
||||
groupId: 0
|
||||
})
|
||||
|
||||
const [newPrivateAppValues, setNewPrivateAppValues] = useState({
|
||||
service: 'DOCUMENT',
|
||||
identifier: ''
|
||||
})
|
||||
|
||||
const addPrivateApp = async ()=> {
|
||||
try {
|
||||
if(privateAppValues?.groupId === 0) return
|
||||
openApp(privateAppValues, true)
|
||||
|
||||
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
const clearFields = ()=> {
|
||||
setPrivateAppValues({
|
||||
name: '',
|
||||
service: 'DOCUMENT',
|
||||
identifier: '',
|
||||
groupId: 0
|
||||
})
|
||||
setNewPrivateAppValues({
|
||||
service: 'DOCUMENT',
|
||||
identifier: ''
|
||||
})
|
||||
setFile(null)
|
||||
setValueTabPrivateApp(0)
|
||||
setSelectedGroup(null)
|
||||
|
||||
}
|
||||
|
||||
const publishPrivateApp = async ()=> {
|
||||
try {
|
||||
if(selectedGroup === 0) return
|
||||
if(!myName) throw new Error('You need a Qortal name to publish')
|
||||
const decryptedData = await window.sendMessage(
|
||||
"ENCRYPT_QORTAL_GROUP_DATA",
|
||||
|
||||
{
|
||||
file: file,
|
||||
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) {
|
||||
setInfoSnackCustom({
|
||||
type: "error",
|
||||
message: error?.message || "Unable to publish app",
|
||||
});
|
||||
}
|
||||
}
|
||||
const openQortalUrl = ()=> {
|
||||
try {
|
||||
if(!qortalUrl) return
|
||||
@ -38,6 +189,18 @@ export const AppsHomeDesktop = ({
|
||||
|
||||
}
|
||||
}
|
||||
const handleChange = (event: React.SyntheticEvent, newValue: number) => {
|
||||
setValueTabPrivateApp(newValue);
|
||||
};
|
||||
|
||||
function a11yProps(index: number) {
|
||||
return {
|
||||
id: `simple-tab-${index}`,
|
||||
"aria-controls": `simple-tabpanel-${index}`,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<AppsContainer
|
||||
@ -123,6 +286,9 @@ export const AppsHomeDesktop = ({
|
||||
onClick={() => {
|
||||
setMode("library");
|
||||
}}
|
||||
sx={{
|
||||
width: "80px",
|
||||
}}
|
||||
>
|
||||
<AppCircleContainer
|
||||
sx={{
|
||||
@ -135,7 +301,25 @@ export const AppsHomeDesktop = ({
|
||||
<AppCircleLabel>Library</AppCircleLabel>
|
||||
</AppCircleContainer>
|
||||
</ButtonBase>
|
||||
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
setIsOpenPrivateModal(true);
|
||||
}}
|
||||
sx={{
|
||||
width: "80px",
|
||||
}}
|
||||
>
|
||||
<AppCircleContainer
|
||||
sx={{
|
||||
gap: !isMobile ? "10px" : "5px",
|
||||
}}
|
||||
>
|
||||
<AppCircle>
|
||||
<Add>+</Add>
|
||||
</AppCircle>
|
||||
<AppCircleLabel>Private</AppCircleLabel>
|
||||
</AppCircleContainer>
|
||||
</ButtonBase>
|
||||
<SortablePinnedApps
|
||||
isDesktop={true}
|
||||
availableQapps={availableQapps}
|
||||
@ -143,6 +327,299 @@ export const AppsHomeDesktop = ({
|
||||
myApp={myApp}
|
||||
/>
|
||||
</AppsContainer>
|
||||
|
||||
{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>service</Label>
|
||||
<Input
|
||||
placeholder="service"
|
||||
value={privateAppValues?.service}
|
||||
onChange={(e) => setPrivateAppValues((prev)=> {
|
||||
return {
|
||||
...prev,
|
||||
service: e.target.value
|
||||
}
|
||||
})}
|
||||
/>
|
||||
</Box> */}
|
||||
<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",
|
||||
}}
|
||||
>
|
||||
<Label>service</Label>
|
||||
<Input
|
||||
placeholder="service"
|
||||
value={privateAppValues?.service}
|
||||
onChange={(e) => setPrivateAppValues((prev)=> {
|
||||
return {
|
||||
...prev,
|
||||
service: e.target.value
|
||||
}
|
||||
})}
|
||||
/>
|
||||
</Box> */}
|
||||
<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>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button variant="contained" onClick={()=> {
|
||||
setIsOpenPrivateModal(false)
|
||||
clearFields()
|
||||
}}>
|
||||
Close
|
||||
</Button>
|
||||
<Button
|
||||
disabled={!privateAppValues.name || !privateAppValues.service || !privateAppValues.identifier || !selectedGroup}
|
||||
variant="contained"
|
||||
onClick={() => publishPrivateApp()}
|
||||
autoFocus
|
||||
>
|
||||
Publish
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</>
|
||||
)}
|
||||
|
||||
</Dialog>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -31,6 +31,7 @@ import {
|
||||
settingsLocalLastUpdatedAtom,
|
||||
sortablePinnedAppsAtom,
|
||||
} from "../../atoms/global";
|
||||
import { useHandlePrivateApps } from "./useHandlePrivateApps";
|
||||
|
||||
export function saveToLocalStorage(key, subKey, newValue) {
|
||||
try {
|
||||
@ -133,10 +134,24 @@ export const AppsNavBarDesktop = ({disableBack}) => {
|
||||
|
||||
|
||||
|
||||
const isSelectedAppPinned = !!sortablePinnedApps?.find(
|
||||
// const isSelectedAppPinned = !!sortablePinnedApps?.find(
|
||||
// (item) =>
|
||||
// item?.name === selectedTab?.name && item?.service === selectedTab?.service
|
||||
// );
|
||||
|
||||
const isSelectedAppPinned = useMemo(()=> {
|
||||
if(selectedTab?.isPrivate){
|
||||
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 (
|
||||
<AppsNavBarParent
|
||||
@ -282,6 +297,16 @@ export const AppsNavBarDesktop = ({disableBack}) => {
|
||||
|
||||
if (isSelectedAppPinned) {
|
||||
// Remove the selected app if it is pinned
|
||||
if(selectedTab?.isPrivate){
|
||||
updatedApps = prev.filter(
|
||||
(item) =>
|
||||
!(
|
||||
item?.privateAppProperties?.name === selectedTab?.privateAppProperties?.name &&
|
||||
item?.privateAppProperties?.service === selectedTab?.privateAppProperties?.service &&
|
||||
item?.privateAppProperties?.identifier === selectedTab?.privateAppProperties?.identifier
|
||||
)
|
||||
);
|
||||
} else {
|
||||
updatedApps = prev.filter(
|
||||
(item) =>
|
||||
!(
|
||||
@ -289,8 +314,25 @@ export const AppsNavBarDesktop = ({disableBack}) => {
|
||||
item?.service === selectedTab?.service
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
} else {
|
||||
// Add the selected app if it is not pinned
|
||||
if(selectedTab?.isPrivate){
|
||||
updatedApps = [
|
||||
...prev,
|
||||
{
|
||||
isPreview: true,
|
||||
isPrivate: true,
|
||||
privateAppProperties: {
|
||||
name: selectedTab?.privateAppProperties?.name,
|
||||
service: selectedTab?.privateAppProperties?.service,
|
||||
identifier: selectedTab?.privateAppProperties?.identifier,
|
||||
}
|
||||
|
||||
},
|
||||
];
|
||||
} else {
|
||||
updatedApps = [
|
||||
...prev,
|
||||
{
|
||||
@ -300,6 +342,8 @@ export const AppsNavBarDesktop = ({disableBack}) => {
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
saveToLocalStorage(
|
||||
"ext_saved_settings",
|
||||
"sortablePinnedApps",
|
||||
@ -338,6 +382,10 @@ export const AppsNavBarDesktop = ({disableBack}) => {
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
if(selectedTab?.refreshFunc){
|
||||
selectedTab.refreshFunc(selectedTab?.tabId)
|
||||
return
|
||||
}
|
||||
executeEvent("refreshApp", {
|
||||
tabId: selectedTab?.tabId,
|
||||
});
|
||||
@ -368,6 +416,7 @@ export const AppsNavBarDesktop = ({disableBack}) => {
|
||||
primary="Refresh"
|
||||
/>
|
||||
</MenuItem>
|
||||
{!selectedTab?.isPrivate && (
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
executeEvent("copyLink", {
|
||||
@ -400,6 +449,8 @@ export const AppsNavBarDesktop = ({disableBack}) => {
|
||||
primary="Copy link"
|
||||
/>
|
||||
</MenuItem>
|
||||
)}
|
||||
|
||||
</Menu>
|
||||
</AppsNavBarParent>
|
||||
);
|
||||
|
@ -11,8 +11,11 @@ import { settingsLocalLastUpdatedAtom, sortablePinnedAppsAtom } from '../../atom
|
||||
import { useRecoilState, useSetRecoilState } from 'recoil';
|
||||
import { saveToLocalStorage } from './AppsNavBar';
|
||||
import { ContextMenuPinnedApps } from '../ContextMenuPinnedApps';
|
||||
import LockIcon from "@mui/icons-material/Lock";
|
||||
import { useHandlePrivateApps } from './useHandlePrivateApps';
|
||||
|
||||
const SortableItem = ({ id, name, app, isDesktop }) => {
|
||||
const {openApp} = useHandlePrivateApps()
|
||||
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id });
|
||||
const style = {
|
||||
transform: CSS.Transform.toString(transform),
|
||||
@ -36,9 +39,14 @@ const SortableItem = ({ id, name, app, isDesktop }) => {
|
||||
transition,
|
||||
}}
|
||||
onClick={()=> {
|
||||
if(app?.isPrivate){
|
||||
openApp(app?.privateAppProperties)
|
||||
} else {
|
||||
executeEvent("addTab", {
|
||||
data: app
|
||||
})
|
||||
}
|
||||
|
||||
}}
|
||||
>
|
||||
<AppCircleContainer sx={{
|
||||
@ -50,6 +58,14 @@ const SortableItem = ({ id, name, app, isDesktop }) => {
|
||||
border: "none",
|
||||
}}
|
||||
>
|
||||
{app?.isPrivate ? (
|
||||
<LockIcon
|
||||
sx={{
|
||||
height: "42px",
|
||||
width: "42px",
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Avatar
|
||||
sx={{
|
||||
height: "42px",
|
||||
@ -72,10 +88,19 @@ const SortableItem = ({ id, name, app, isDesktop }) => {
|
||||
alt="center-icon"
|
||||
/>
|
||||
</Avatar>
|
||||
)}
|
||||
|
||||
</AppCircle>
|
||||
{app?.isPrivate ? (
|
||||
<AppCircleLabel>
|
||||
Private
|
||||
</AppCircleLabel>
|
||||
) : (
|
||||
<AppCircleLabel>
|
||||
{app?.metadata?.title || app?.name}
|
||||
</AppCircleLabel>
|
||||
)}
|
||||
|
||||
</AppCircleContainer>
|
||||
</ButtonBase>
|
||||
</ContextMenuPinnedApps>
|
||||
@ -85,7 +110,6 @@ const SortableItem = ({ id, name, app, isDesktop }) => {
|
||||
export const SortablePinnedApps = ({ isDesktop, myWebsite, myApp, availableQapps = [] }) => {
|
||||
const [pinnedApps, setPinnedApps] = useRecoilState(sortablePinnedAppsAtom);
|
||||
const setSettingsLocalLastUpdated = useSetRecoilState(settingsLocalLastUpdatedAtom);
|
||||
|
||||
const transformPinnedApps = useMemo(() => {
|
||||
|
||||
// Clone the existing pinned apps list
|
||||
|
@ -1,39 +1,50 @@
|
||||
import React from 'react'
|
||||
import { TabParent } from './Apps-styles'
|
||||
import React from "react";
|
||||
import { TabParent } from "./Apps-styles";
|
||||
import NavCloseTab from "../../assets/svgs/NavCloseTab.svg";
|
||||
import { getBaseApiReact } from '../../App';
|
||||
import { Avatar, ButtonBase } from '@mui/material';
|
||||
import { getBaseApiReact } from "../../App";
|
||||
import { Avatar, ButtonBase } from "@mui/material";
|
||||
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 }) => {
|
||||
return (
|
||||
<ButtonBase onClick={()=> {
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
if (isSelected) {
|
||||
executeEvent('removeTab', {
|
||||
data: app
|
||||
})
|
||||
return
|
||||
executeEvent("removeTab", {
|
||||
data: app,
|
||||
});
|
||||
return;
|
||||
}
|
||||
executeEvent('setSelectedTab', {
|
||||
data: app
|
||||
})
|
||||
}}>
|
||||
<TabParent sx={{
|
||||
border: isSelected && '1px solid #FFFFFF'
|
||||
}}>
|
||||
executeEvent("setSelectedTab", {
|
||||
data: app,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<TabParent
|
||||
sx={{
|
||||
border: isSelected && "1px solid #FFFFFF",
|
||||
}}
|
||||
>
|
||||
{isSelected && (
|
||||
|
||||
<img style={
|
||||
{
|
||||
position: 'absolute',
|
||||
top: '-5px',
|
||||
right: '-5px',
|
||||
zIndex: 1
|
||||
}
|
||||
} src={NavCloseTab}/>
|
||||
|
||||
<img
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: "-5px",
|
||||
right: "-5px",
|
||||
zIndex: 1,
|
||||
}}
|
||||
src={NavCloseTab}
|
||||
/>
|
||||
)}
|
||||
{app?.isPrivate ? (
|
||||
<LockIcon
|
||||
sx={{
|
||||
height: "28px",
|
||||
width: "28px",
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Avatar
|
||||
sx={{
|
||||
height: "28px",
|
||||
@ -53,9 +64,10 @@ const TabComponent = ({isSelected, app}) => {
|
||||
alt="center-icon"
|
||||
/>
|
||||
</Avatar>
|
||||
)}
|
||||
</TabParent>
|
||||
</ButtonBase>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default TabComponent
|
||||
export default TabComponent;
|
||||
|
129
src/components/Apps/useHandlePrivateApps.tsx
Normal file
129
src/components/Apps/useHandlePrivateApps.tsx
Normal file
@ -0,0 +1,129 @@
|
||||
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";
|
||||
|
||||
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) => {
|
||||
try {
|
||||
setOpenSnackGlobal(true);
|
||||
|
||||
setInfoSnackCustom({
|
||||
type: "info",
|
||||
message: "Fetching app data",
|
||||
});
|
||||
const urlData = `${getBaseApiReact()}/arbitrary/${
|
||||
privateAppProperties?.service
|
||||
}/${privateAppProperties?.name}/${
|
||||
privateAppProperties?.identifier
|
||||
}?encoding=base64`;
|
||||
|
||||
const responseData = await fetch(urlData, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
const data = await responseData.text();
|
||||
|
||||
setInfoSnackCustom({
|
||||
type: "info",
|
||||
message: "Decrypting app",
|
||||
});
|
||||
const decryptedData = await window.sendMessage(
|
||||
"DECRYPT_QORTAL_GROUP_DATA",
|
||||
|
||||
{
|
||||
base64: data,
|
||||
groupId: privateAppProperties?.groupId,
|
||||
}
|
||||
);
|
||||
if(decryptedData?.error) throw new Error(decryptedData?.error)
|
||||
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: decryptedData,
|
||||
});
|
||||
const previewPath = await response.text();
|
||||
setOpenSnackGlobal(false);
|
||||
const refreshfunc = async (tabId) => {
|
||||
executeEvent("refreshApp", {
|
||||
tabId: tabId,
|
||||
});
|
||||
};
|
||||
|
||||
executeEvent("addTab", {
|
||||
data: {
|
||||
url: await createEndpoint(previewPath),
|
||||
isPreview: true,
|
||||
isPrivate: true,
|
||||
privateAppProperties: { ...privateAppProperties },
|
||||
filePath: "",
|
||||
refreshFunc: (tabId) => {
|
||||
refreshfunc(tabId);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (addToPinnedApps) {
|
||||
setSortablePinnedApps((prev) => {
|
||||
const updatedApps = [
|
||||
...prev,
|
||||
{
|
||||
isPrivate: true,
|
||||
isPreview: true,
|
||||
privateAppProperties: { ...privateAppProperties },
|
||||
},
|
||||
];
|
||||
|
||||
saveToLocalStorage(
|
||||
"ext_saved_settings",
|
||||
"sortablePinnedApps",
|
||||
updatedApps
|
||||
);
|
||||
return updatedApps;
|
||||
});
|
||||
setSettingsLocalLastUpdated(Date.now());
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
setInfoSnackCustom({
|
||||
type: "error",
|
||||
message: error?.message || "Unable to access app",
|
||||
});
|
||||
}
|
||||
};
|
||||
return {
|
||||
openApp,
|
||||
status,
|
||||
};
|
||||
};
|
@ -124,11 +124,20 @@ export const ContextMenuPinnedApps = ({ children, app, isMine }) => {
|
||||
<MenuItem onClick={(e) => {
|
||||
handleClose(e);
|
||||
setSortablePinnedApps((prev) => {
|
||||
if(app?.isPrivate){
|
||||
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;
|
||||
} 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' }}>
|
||||
|
Loading…
x
Reference in New Issue
Block a user