private apps

This commit is contained in:
PhilReact 2025-02-05 18:54:15 +02:00
parent 3515c0b6c9
commit 16b7ed2dbc
8 changed files with 820 additions and 114 deletions

View File

@ -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
}

View File

@ -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>
</>
)}

View File

@ -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>
)}
</>
);
};

View File

@ -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>
);

View File

@ -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

View File

@ -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;

View 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,
};
};

View File

@ -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' }}>