fix navigation of app tabs

This commit is contained in:
PhilReact 2024-10-27 14:53:28 +02:00
parent 033de5816c
commit 1b44f26713
9 changed files with 318 additions and 126 deletions

View File

@ -2,6 +2,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.ico" /> <link rel="icon" type="image/svg+xml" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Qortal Extension</title> <title>Qortal Extension</title>

View File

@ -57,3 +57,8 @@ export const hasSettingsChangedAtom = atom({
key: 'hasSettingsChangedAtom', key: 'hasSettingsChangedAtom',
default: false, default: false,
}); });
export const navigationControllerAtom = atom({
key: 'navigationControllerAtom',
default: {},
});

View File

@ -1,25 +1,9 @@
import React, { useContext, useEffect, useMemo, useRef, useState } from "react"; import React, { useContext, useEffect, useMemo, useState } from "react";
import {
AppCircle, import { Avatar, Box, } from "@mui/material";
AppCircleContainer,
AppCircleLabel,
AppDownloadButton,
AppDownloadButtonText,
AppInfoAppName,
AppInfoSnippetContainer,
AppInfoSnippetLeft,
AppInfoSnippetMiddle,
AppInfoSnippetRight,
AppInfoUserName,
AppsLibraryContainer,
AppsParent,
} from "./Apps-styles";
import { Avatar, Box, ButtonBase, InputBase } from "@mui/material";
import { Add } from "@mui/icons-material"; import { Add } from "@mui/icons-material";
import { MyContext, getBaseApiReact, isMobile } from "../../App"; import { MyContext, getBaseApiReact, isMobile } from "../../App";
import LogoSelected from "../../assets/svgs/LogoSelected.svg";
import { Spacer } from "../../common/Spacer";
import { executeEvent, subscribeToEvent, unsubscribeFromEvent } from "../../utils/events"; import { executeEvent, subscribeToEvent, unsubscribeFromEvent } from "../../utils/events";
import { useFrame } from "react-frame-component"; import { useFrame } from "react-frame-component";
import { useQortalMessageListener } from "./useQortalMessageListener"; import { useQortalMessageListener } from "./useQortalMessageListener";
@ -27,15 +11,16 @@ import { useQortalMessageListener } from "./useQortalMessageListener";
export const AppViewer = ({ app }) => { export const AppViewer = React.forwardRef(({ app , hide}, iframeRef) => {
const { rootHeight } = useContext(MyContext); const { rootHeight } = useContext(MyContext);
const iframeRef = useRef(null); // const iframeRef = useRef(null);
const { document, window } = useFrame(); const { document, window: frameWindow } = useFrame();
const {path} = useQortalMessageListener(window) const {path, history, changeCurrentIndex} = useQortalMessageListener(frameWindow, iframeRef, app?.tabId)
const [url, setUrl] = useState('') const [url, setUrl] = useState('')
console.log('historyreact', history)
useEffect(()=> { useEffect(()=> {
setUrl(`${getBaseApiReact()}/render/${app?.service}/${app?.name}${app?.path != null ? app?.path : ''}?theme=dark&identifier=${(app?.identifier != null && app?.identifier != 'null') ? app?.identifier : ''}`) setUrl(`${getBaseApiReact()}/render/${app?.service}/${app?.name}${app?.path != null ? `/${app?.path}` : ''}?theme=dark&identifier=${(app?.identifier != null && app?.identifier != 'null') ? app?.identifier : ''}`)
}, [app?.service, app?.name, app?.identifier, app?.path]) }, [app?.service, app?.name, app?.identifier, app?.path])
const defaultUrl = useMemo(()=> { const defaultUrl = useMemo(()=> {
return url return url
@ -59,13 +44,106 @@ export const AppViewer = ({ app }) => {
}; };
}, [app, path]); }, [app, path]);
// Function to navigate back in iframe
const navigateBackInIframe = async () => {
if (iframeRef.current && iframeRef.current.contentWindow && history?.currentIndex > 0) {
// Calculate the previous index and path
const previousPageIndex = history.currentIndex - 1;
const previousPath = history.customQDNHistoryPaths[previousPageIndex];
// Signal non-manual navigation
iframeRef.current.contentWindow.postMessage(
{ action: 'PERFORMING_NON_MANUAL' }, '*'
);
console.log('previousPageIndex', previousPageIndex)
// Update the current index locally
changeCurrentIndex(previousPageIndex);
// Create a navigation promise with a 200ms timeout
const navigationPromise = new Promise((resolve, reject) => {
function handleNavigationSuccess(event) {
console.log('listeninghandlenav', event)
if (event.data?.action === 'NAVIGATION_SUCCESS' && event.data.path === previousPath) {
frameWindow.removeEventListener('message', handleNavigationSuccess);
resolve();
}
}
frameWindow.addEventListener('message', handleNavigationSuccess);
// Timeout after 200ms if no response
setTimeout(() => {
window.removeEventListener('message', handleNavigationSuccess);
reject(new Error("Navigation timeout"));
}, 200);
// Send the navigation command after setting up the listener and timeout
iframeRef.current.contentWindow.postMessage(
{ action: 'NAVIGATE_TO_PATH', path: previousPath, requestedHandler: 'UI' }, '*'
);
});
// Execute navigation promise and handle timeout fallback
try {
await navigationPromise;
console.log('Navigation succeeded within 200ms.');
} catch (error) {
iframeRef.current.contentWindow.postMessage(
{ action: 'PERFORMING_NON_MANUAL' }, '*'
);
setUrl(`${getBaseApiReact()}/render/${app?.service}/${app?.name}${app?.previousPath != null ? previousPath : ''}?theme=dark&identifier=${(app?.identifier != null && app?.identifier != 'null') ? app?.identifier : ''}&time=${new Date().getMilliseconds()}&isManualNavigation=false`)
// iframeRef.current.contentWindow.location.href = previousPath; // Fallback URL update
}
} else {
console.log('Iframe not accessible or does not have a content window.');
}
};
const navigateBackAppFunc = (e) => {
navigateBackInIframe()
};
useEffect(() => {
if(!app?.tabId) return
subscribeToEvent(`navigateBackApp-${app?.tabId}`, navigateBackAppFunc);
return () => {
unsubscribeFromEvent(`navigateBackApp-${app?.tabId}`, navigateBackAppFunc);
};
}, [app, history]);
// Function to navigate back in iframe
const navigateForwardInIframe = async () => {
if (iframeRef.current && iframeRef.current.contentWindow) {
console.log('iframeRef.contentWindow', iframeRef.current.contentWindow);
iframeRef.current.contentWindow.postMessage(
{ action: 'NAVIGATE_FORWARD'},
'*'
);
} else {
console.log('Iframe not accessible or does not have a content window.');
}
};
return ( return (
<iframe ref={iframeRef} style={{ <Box sx={{
display: 'flex',
flexDirection: 'column',
}}>
<iframe ref={iframeRef} style={{
height: !isMobile ? '100vh' : `calc(${rootHeight} - 60px - 45px )`, height: !isMobile ? '100vh' : `calc(${rootHeight} - 60px - 45px )`,
border: 'none', border: 'none',
width: '100%' width: '100%'
}} id="browser-iframe" src={defaultUrl} sandbox="allow-scripts allow-same-origin allow-forms allow-downloads allow-modals" allow="fullscreen"> }} id="browser-iframe" src={defaultUrl} sandbox="allow-scripts allow-same-origin allow-forms allow-downloads allow-modals" allow="fullscreen">
</iframe> </iframe>
</Box>
); );
}; });

View File

@ -1,26 +1,24 @@
import React, { useContext, useEffect, useRef } from 'react' import React, { useContext, } from 'react';
import { AppViewer } from './AppViewer' import { AppViewer } from './AppViewer';
import Frame from 'react-frame-component'; import Frame from 'react-frame-component';
import { MyContext, isMobile } from '../../App'; import { MyContext, isMobile } from '../../App';
import { subscribeToEvent, unsubscribeFromEvent } from '../../utils/events';
const AppViewerContainer = ({app, isSelected, hide}) => {
const { rootHeight } = useContext(MyContext);
const frameRef = useRef(null);
const AppViewerContainer = React.forwardRef(({ app, isSelected, hide }, ref) => {
const { rootHeight } = useContext(MyContext);
return ( return (
<Frame id={`browser-iframe-${app?.tabId}` } ref={frameRef} head={ <Frame
id={`browser-iframe-${app?.tabId}`}
head={
<> <>
{/* Inject styles directly into the iframe */}
<style> <style>
{` {`
body { body {
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
/* Hide scrollbars for all elements */
* { * {
-ms-overflow-style: none; /* IE and Edge */ -ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */ scrollbar-width: none; /* Firefox */
@ -30,19 +28,23 @@ const AppViewerContainer = ({app, isSelected, hide}) => {
} }
.frame-content { .frame-content {
overflow: hidden; overflow: hidden;
height: ${!isMobile ? '100vh' : `calc(${rootHeight} - 60px - 45px )`}; height: ${!isMobile ? '100vh' : `calc(${rootHeight} - 60px - 45px)`};
} }
`} `}
</style> </style>
</> </>
} style={{ }
height: !isMobile ? '100vh' : `calc(${rootHeight} - 60px - 45px )`, style={{
border: 'none', display: (!isSelected || hide) && 'none',
width: '100%', height: !isMobile ? '100vh' : `calc(${rootHeight} - 60px - 45px)`,
overflow: 'hidden', border: 'none',
display: (!isSelected || hide) && 'none' width: '100%',
}} ><AppViewer app={app} /></Frame> overflow: 'hidden',
) }}
} >
<AppViewer app={app} ref={ref} hide={!isSelected || hide} />
</Frame>
);
});
export default AppViewerContainer export default AppViewerContainer;

View File

@ -1,20 +1,17 @@
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react"; import React, { useEffect, useMemo, useRef, useState } from "react";
import { AppsHome } from "./AppsHome"; import { AppsHome } from "./AppsHome";
import { Spacer } from "../../common/Spacer"; import { Spacer } from "../../common/Spacer";
import { MyContext, getBaseApiReact } from "../../App"; import { getBaseApiReact } from "../../App";
import { AppInfo } from "./AppInfo"; import { AppInfo } from "./AppInfo";
import { import {
executeEvent, executeEvent,
subscribeToEvent, subscribeToEvent,
unsubscribeFromEvent, unsubscribeFromEvent,
} from "../../utils/events"; } from "../../utils/events";
import { AppsNavBar } from "./AppsNavBar";
import { AppsParent } from "./Apps-styles"; import { AppsParent } from "./Apps-styles";
import { AppViewer } from "./AppViewer";
import AppViewerContainer from "./AppViewerContainer"; import AppViewerContainer from "./AppViewerContainer";
import ShortUniqueId from "short-unique-id"; import ShortUniqueId from "short-unique-id";
import { AppPublish } from "./AppPublish"; import { AppPublish } from "./AppPublish";
import { useRecoilState } from "recoil";
import { AppsCategory } from "./AppsCategory"; import { AppsCategory } from "./AppsCategory";
import { AppsLibrary } from "./AppsLibrary"; import { AppsLibrary } from "./AppsLibrary";
@ -28,6 +25,7 @@ export const Apps = ({ mode, setMode, show , myName}) => {
const [selectedTab, setSelectedTab] = useState(null); const [selectedTab, setSelectedTab] = useState(null);
const [isNewTabWindow, setIsNewTabWindow] = useState(false); const [isNewTabWindow, setIsNewTabWindow] = useState(false);
const [categories, setCategories] = useState([]) const [categories, setCategories] = useState([])
const iframeRefs = useRef({});
const myApp = useMemo(()=> { const myApp = useMemo(()=> {
@ -158,33 +156,26 @@ export const Apps = ({ mode, setMode, show , myName}) => {
const navigateBackFunc = (e) => { const navigateBackFunc = (e) => {
if(mode === 'category'){ if (['category', 'appInfo-from-category', 'appInfo', 'library', 'publish'].includes(mode)) {
setMode("library"); // Handle the various modes as needed
setSelectedCategory(null) if (mode === 'category') {
} else if (mode === "appInfo-from-category") { setMode('library');
setMode("category"); setSelectedCategory(null);
} else if (mode === "appInfo") { } else if (mode === 'appInfo-from-category') {
setMode("library"); setMode('category');
} else if (mode === "library") { } else if (mode === 'appInfo') {
if (isNewTabWindow) { setMode('library');
setMode("viewer"); } else if (mode === 'library') {
} else { if (isNewTabWindow) {
setMode("home"); setMode('viewer');
} } else {
} else if(mode === 'publish'){ setMode('home');
setMode('library')
} else {
const iframeId = `browser-iframe-${selectedTab?.tabId}`;
const iframe = document.getElementById(iframeId);
// Go Back in the iframe's history
if (iframe) {
if (iframe && iframe.contentWindow) {
const iframeWindow = iframe.contentWindow;
if (iframeWindow && iframeWindow.history) {
iframeWindow.history.back();
}
} }
} else if (mode === 'publish') {
setMode('library');
} }
} else if(selectedTab?.tabId) {
executeEvent(`navigateBackApp-${selectedTab?.tabId}`, {})
} }
}; };
@ -309,11 +300,16 @@ export const Apps = ({ mode, setMode, show , myName}) => {
{mode === "publish" && !selectedTab && <AppPublish names={myName ? [myName] : []} categories={categories} />} {mode === "publish" && !selectedTab && <AppPublish names={myName ? [myName] : []} categories={categories} />}
{tabs.map((tab) => { {tabs.map((tab) => {
if (!iframeRefs.current[tab.tabId]) {
iframeRefs.current[tab.tabId] = React.createRef();
}
return ( return (
<AppViewerContainer <AppViewerContainer
key={tab?.tabId}
hide={isNewTabWindow} hide={isNewTabWindow}
isSelected={tab?.tabId === selectedTab?.tabId} isSelected={tab?.tabId === selectedTab?.tabId}
app={tab} app={tab}
ref={iframeRefs.current[tab.tabId]}
/> />
); );
})} })}

View File

@ -8,15 +8,10 @@ import {
subscribeToEvent, subscribeToEvent,
unsubscribeFromEvent, unsubscribeFromEvent,
} from "../../utils/events"; } from "../../utils/events";
import { AppsNavBar } from "./AppsNavBar";
import { AppsParent } from "./Apps-styles"; import { AppsParent } from "./Apps-styles";
import { AppViewer } from "./AppViewer";
import AppViewerContainer from "./AppViewerContainer"; import AppViewerContainer from "./AppViewerContainer";
import ShortUniqueId from "short-unique-id"; import ShortUniqueId from "short-unique-id";
import { AppPublish } from "./AppPublish"; import { AppPublish } from "./AppPublish";
import { useRecoilState } from "recoil";
import { AppsCategory } from "./AppsCategory";
import { AppsLibrary } from "./AppsLibrary";
import { AppsLibraryDesktop } from "./AppsLibraryDesktop"; import { AppsLibraryDesktop } from "./AppsLibraryDesktop";
import { AppsCategoryDesktop } from "./AppsCategoryDesktop"; import { AppsCategoryDesktop } from "./AppsCategoryDesktop";
import { AppsNavBarDesktop } from "./AppsNavBarDesktop"; import { AppsNavBarDesktop } from "./AppsNavBarDesktop";
@ -36,8 +31,7 @@ export const AppsDesktop = ({ mode, setMode, show , myName, goToHome, setDesktop
const [selectedTab, setSelectedTab] = useState(null); const [selectedTab, setSelectedTab] = useState(null);
const [isNewTabWindow, setIsNewTabWindow] = useState(false); const [isNewTabWindow, setIsNewTabWindow] = useState(false);
const [categories, setCategories] = useState([]) const [categories, setCategories] = useState([])
const iframeRefs = useRef({});
const myApp = useMemo(()=> { const myApp = useMemo(()=> {
return availableQapps.find((app)=> app.name === myName && app.service === 'APP') return availableQapps.find((app)=> app.name === myName && app.service === 'APP')
@ -165,37 +159,35 @@ export const AppsDesktop = ({ mode, setMode, show , myName, goToHome, setDesktop
}, []); }, []);
const navigateBackFunc = (e) => { const navigateBackFunc = (e) => {
if(mode === 'category'){ if (['category', 'appInfo-from-category', 'appInfo', 'library', 'publish'].includes(mode)) {
setMode("library"); // Handle the various modes as needed
setSelectedCategory(null) if (mode === 'category') {
} else if (mode === "appInfo-from-category") { setMode('library');
setMode("category"); setSelectedCategory(null);
} else if (mode === "appInfo") { } else if (mode === 'appInfo-from-category') {
setMode("library"); setMode('category');
} else if (mode === "library") { } else if (mode === 'appInfo') {
if (isNewTabWindow) { setMode('library');
setMode("viewer"); } else if (mode === 'library') {
} else { if (isNewTabWindow) {
setMode("home"); setMode('viewer');
} } else {
} else if(mode === 'publish'){ setMode('home');
setMode('library')
} else {
const iframeId = `browser-iframe-${selectedTab?.tabId}`;
const iframe = document.getElementById(iframeId);
// Go Back in the iframe's history
if (iframe) {
if (iframe && iframe.contentWindow) {
const iframeWindow = iframe.contentWindow;
if (iframeWindow && iframeWindow.history) {
iframeWindow.history.back();
}
} }
} else if (mode === 'publish') {
setMode('library');
} }
} else if(selectedTab?.tabId) {
executeEvent(`navigateBackApp-${selectedTab?.tabId}`, {})
} }
}; };
useEffect(() => { useEffect(() => {
subscribeToEvent("navigateBack", navigateBackFunc); subscribeToEvent("navigateBack", navigateBackFunc);
@ -217,6 +209,8 @@ export const AppsDesktop = ({ mode, setMode, show , myName, goToHome, setDesktop
setIsNewTabWindow(false); setIsNewTabWindow(false);
}; };
useEffect(() => { useEffect(() => {
subscribeToEvent("addTab", addTabFunc); subscribeToEvent("addTab", addTabFunc);
@ -224,7 +218,6 @@ export const AppsDesktop = ({ mode, setMode, show , myName, goToHome, setDesktop
unsubscribeFromEvent("addTab", addTabFunc); unsubscribeFromEvent("addTab", addTabFunc);
}; };
}, [tabs]); }, [tabs]);
const setSelectedTabFunc = (e) => { const setSelectedTabFunc = (e) => {
const data = e.detail?.data; const data = e.detail?.data;
@ -241,6 +234,7 @@ export const AppsDesktop = ({ mode, setMode, show , myName, goToHome, setDesktop
setIsNewTabWindow(false); setIsNewTabWindow(false);
}; };
useEffect(() => { useEffect(() => {
subscribeToEvent("setSelectedTab", setSelectedTabFunc); subscribeToEvent("setSelectedTab", setSelectedTabFunc);
@ -364,7 +358,7 @@ export const AppsDesktop = ({ mode, setMode, show , myName, goToHome, setDesktop
</ButtonBase> </ButtonBase>
<Save isDesktop /> <Save isDesktop />
{mode !== 'home' && ( {mode !== 'home' && (
<AppsNavBarDesktop /> <AppsNavBarDesktop />
)} )}
@ -398,13 +392,17 @@ export const AppsDesktop = ({ mode, setMode, show , myName, goToHome, setDesktop
{mode === "appInfo-from-category" && !selectedTab && <AppInfo app={selectedAppInfo} myName={myName} />} {mode === "appInfo-from-category" && !selectedTab && <AppInfo app={selectedAppInfo} myName={myName} />}
<AppsCategoryDesktop availableQapps={availableQapps} isShow={mode === 'category' && !selectedTab} category={selectedCategory} myName={myName} /> <AppsCategoryDesktop availableQapps={availableQapps} isShow={mode === 'category' && !selectedTab} category={selectedCategory} myName={myName} />
{mode === "publish" && !selectedTab && <AppPublish names={myName ? [myName] : []} categories={categories} />} {mode === "publish" && !selectedTab && <AppPublish names={myName ? [myName] : []} categories={categories} />}
{tabs.map((tab) => { {tabs.map((tab) => {
if (!iframeRefs.current[tab.tabId]) {
iframeRefs.current[tab.tabId] = React.createRef();
}
return ( return (
<AppViewerContainer <AppViewerContainer
key={tab?.tabId}
hide={isNewTabWindow} hide={isNewTabWindow}
isSelected={tab?.tabId === selectedTab?.tabId} isSelected={tab?.tabId === selectedTab?.tabId}
app={tab} app={tab}
ref={iframeRefs.current[tab.tabId]}
/> />
); );
})} })}

View File

@ -1,4 +1,4 @@
import React, { useEffect, useRef, useState } from "react"; import React, { useEffect, useMemo, useRef, useState } from "react";
import { import {
AppsNavBarLeft, AppsNavBarLeft,
AppsNavBarParent, AppsNavBarParent,
@ -26,6 +26,7 @@ import PushPinIcon from "@mui/icons-material/PushPin";
import RefreshIcon from "@mui/icons-material/Refresh"; import RefreshIcon from "@mui/icons-material/Refresh";
import { useRecoilState, useSetRecoilState } from "recoil"; import { useRecoilState, useSetRecoilState } from "recoil";
import { import {
navigationControllerAtom,
settingsLocalLastUpdatedAtom, settingsLocalLastUpdatedAtom,
sortablePinnedAppsAtom, sortablePinnedAppsAtom,
} from "../../atoms/global"; } from "../../atoms/global";
@ -71,6 +72,13 @@ export const AppsNavBar = () => {
const [sortablePinnedApps, setSortablePinnedApps] = useRecoilState( const [sortablePinnedApps, setSortablePinnedApps] = useRecoilState(
sortablePinnedAppsAtom sortablePinnedAppsAtom
); );
const [navigationController, setNavigationController] = useRecoilState(navigationControllerAtom)
const isDisableBackButton = useMemo(()=> {
if(selectedTab && navigationController[selectedTab?.tabId]?.hasBack) return false
if(selectedTab && !navigationController[selectedTab?.tabId]?.hasBack) return true
return false
}, [navigationController, selectedTab])
const setSettingsLocalLastUpdated = useSetRecoilState( const setSettingsLocalLastUpdated = useSetRecoilState(
settingsLocalLastUpdatedAtom settingsLocalLastUpdatedAtom
@ -103,7 +111,7 @@ export const AppsNavBar = () => {
const { tabs, selectedTab, isNewTabWindow } = e.detail?.data; const { tabs, selectedTab, isNewTabWindow } = e.detail?.data;
setTabs([...tabs]); setTabs([...tabs]);
setSelectedTab(!selectedTab ? nulll : { ...selectedTab }); setSelectedTab(!selectedTab ? null : { ...selectedTab });
setIsNewTabWindow(isNewTabWindow); setIsNewTabWindow(isNewTabWindow);
}; };
@ -123,8 +131,13 @@ export const AppsNavBar = () => {
<AppsNavBarParent> <AppsNavBarParent>
<AppsNavBarLeft> <AppsNavBarLeft>
<ButtonBase <ButtonBase
onClick={() => { onClick={() => {
executeEvent("navigateBack", {}); executeEvent("navigateBack", selectedTab?.tabId);
}}
disabled={isDisableBackButton}
sx={{
opacity: !isDisableBackButton ? 1 : 0.1,
cursor: !isDisableBackButton ? 'pointer': 'default'
}} }}
> >
<img src={NavBack} /> <img src={NavBack} />

View File

@ -1,4 +1,4 @@
import React, { useEffect, useRef, useState } from "react"; import React, { useEffect, useMemo, useRef, useState } from "react";
import { import {
AppsNavBarLeft, AppsNavBarLeft,
AppsNavBarParent, AppsNavBarParent,
@ -26,6 +26,7 @@ import PushPinIcon from "@mui/icons-material/PushPin";
import RefreshIcon from "@mui/icons-material/Refresh"; import RefreshIcon from "@mui/icons-material/Refresh";
import { useRecoilState, useSetRecoilState } from "recoil"; import { useRecoilState, useSetRecoilState } from "recoil";
import { import {
navigationControllerAtom,
settingsLocalLastUpdatedAtom, settingsLocalLastUpdatedAtom,
sortablePinnedAppsAtom, sortablePinnedAppsAtom,
} from "../../atoms/global"; } from "../../atoms/global";
@ -64,6 +65,8 @@ export function saveToLocalStorage(key, subKey, newValue) {
export const AppsNavBarDesktop = () => { export const AppsNavBarDesktop = () => {
const [tabs, setTabs] = useState([]); const [tabs, setTabs] = useState([]);
const [selectedTab, setSelectedTab] = useState(null); const [selectedTab, setSelectedTab] = useState(null);
const [navigationController, setNavigationController] = useRecoilState(navigationControllerAtom)
const [isNewTabWindow, setIsNewTabWindow] = useState(false); const [isNewTabWindow, setIsNewTabWindow] = useState(false);
const tabsRef = useRef(null); const tabsRef = useRef(null);
const [anchorEl, setAnchorEl] = useState(null); const [anchorEl, setAnchorEl] = useState(null);
@ -72,6 +75,7 @@ export const AppsNavBarDesktop = () => {
sortablePinnedAppsAtom sortablePinnedAppsAtom
); );
const setSettingsLocalLastUpdated = useSetRecoilState( const setSettingsLocalLastUpdated = useSetRecoilState(
settingsLocalLastUpdatedAtom settingsLocalLastUpdatedAtom
); );
@ -99,11 +103,22 @@ export const AppsNavBarDesktop = () => {
} }
}, [tabs.length]); // Dependency on the number of tabs }, [tabs.length]); // Dependency on the number of tabs
const isDisableBackButton = useMemo(()=> {
if(selectedTab && navigationController[selectedTab?.tabId]?.hasBack) return false
if(selectedTab && !navigationController[selectedTab?.tabId]?.hasBack) return true
return false
}, [navigationController, selectedTab])
const setTabsToNav = (e) => { const setTabsToNav = (e) => {
const { tabs, selectedTab, isNewTabWindow } = e.detail?.data; const { tabs, selectedTab, isNewTabWindow } = e.detail?.data;
setTabs([...tabs]); setTabs([...tabs]);
setSelectedTab(!selectedTab ? nulll : { ...selectedTab }); setSelectedTab(!selectedTab ? null : { ...selectedTab });
setIsNewTabWindow(isNewTabWindow); setIsNewTabWindow(isNewTabWindow);
}; };
@ -115,6 +130,8 @@ export const AppsNavBarDesktop = () => {
}; };
}, []); }, []);
const isSelectedAppPinned = !!sortablePinnedApps?.find( const isSelectedAppPinned = !!sortablePinnedApps?.find(
(item) => (item) =>
item?.name === selectedTab?.name && item?.service === selectedTab?.service item?.name === selectedTab?.name && item?.service === selectedTab?.service
@ -138,7 +155,12 @@ export const AppsNavBarDesktop = () => {
> >
<ButtonBase <ButtonBase
onClick={() => { onClick={() => {
executeEvent("navigateBack", {}); executeEvent("navigateBack", selectedTab?.tabId);
}}
disabled={isDisableBackButton}
sx={{
opacity: !isDisableBackButton ? 1 : 0.1,
cursor: !isDisableBackButton ? 'pointer': 'default'
}} }}
> >
<img src={NavBack} /> <img src={NavBack} />

View File

@ -1,5 +1,8 @@
import { useEffect, useState } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react';
import FileSaver from 'file-saver'; import FileSaver from 'file-saver';
import { executeEvent } from '../../utils/events';
import { useSetRecoilState } from 'recoil';
import { navigationControllerAtom } from '../../atoms/global';
class Semaphore { class Semaphore {
constructor(count) { constructor(count) {
this.count = count this.count = count
@ -313,13 +316,54 @@ const UIQortalRequests = [
return obj; // Updated object with references to stored files return obj; // Updated object with references to stored files
} }
export const useQortalMessageListener = (frameWindow) => { export const useQortalMessageListener = (frameWindow, iframeRef, tabId) => {
const [path, setPath] = useState('') const [path, setPath] = useState('')
const [history, setHistory] = useState({
customQDNHistoryPaths: [],
currentIndex: -1,
isDOMContentLoaded: false
})
const setHasSettingsChangedAtom = useSetRecoilState(navigationControllerAtom);
useEffect(()=> {
if(tabId && !isNaN(history?.currentIndex)){
setHasSettingsChangedAtom((prev)=> {
return {
...prev,
[tabId]: {
hasBack: history?.currentIndex > 0,
}
}
})
}
}, [history?.currentIndex, tabId])
const changeCurrentIndex = useCallback((value)=> {
setHistory((prev)=> {
return {
...prev,
currentIndex: value
}
})
}, [])
const resetHistory = useCallback(()=> {
setHistory({
customQDNHistoryPaths: [],
currentIndex: -1,
isManualNavigation: true,
isDOMContentLoaded: false
})
}, [])
useEffect(() => { useEffect(() => {
const listener = async (event) => { const listener = async (event) => {
event.preventDefault(); // Prevent default behavior console.log('eventreactt', event)
event.stopImmediatePropagation(); // Stop other listeners from firing // event.preventDefault(); // Prevent default behavior
// event.stopImmediatePropagation(); // Stop other listeners from firing
if (event?.data?.requestedHandler !== 'UI') return; if (event?.data?.requestedHandler !== 'UI') return;
@ -377,6 +421,39 @@ export const useQortalMessageListener = (frameWindow) => {
event?.data?.action === 'QDN_RESOURCE_DISPLAYED'){ event?.data?.action === 'QDN_RESOURCE_DISPLAYED'){
const pathUrl = event?.data?.path != null ? (event?.data?.path.startsWith('/') ? '' : '/') + event?.data?.path : null const pathUrl = event?.data?.path != null ? (event?.data?.path.startsWith('/') ? '' : '/') + event?.data?.path : null
setPath(pathUrl) setPath(pathUrl)
} else if(event?.data?.action === 'NAVIGATION_HISTORY'){
if(event?.data?.payload?.isDOMContentLoaded){
setHistory((prev)=> {
const copyPrev = {...prev}
if((copyPrev?.customQDNHistoryPaths || []).at(-1) === (event?.data?.payload?.customQDNHistoryPaths || []).at(-1)) {
console.log('customQDNHistoryPaths.length', prev?.customQDNHistoryPaths.length)
return {
...prev,
currentIndex: prev.customQDNHistoryPaths.length - 1 === -1 ? 0 : prev.customQDNHistoryPaths.length - 1
}
}
const copyHistory = {...prev}
const paths = [...(copyHistory?.customQDNHistoryPaths || []), ...(event?.data?.payload?.customQDNHistoryPaths || [])]
console.log('paths', paths)
return {
...prev,
customQDNHistoryPaths: paths,
currentIndex: paths.length - 1
}
})
} else {
setHistory(event?.data?.payload)
}
} else if(event?.data?.action === 'SET_TAB'){
executeEvent("addTab", {
data: event?.data?.payload
})
iframeRef.current.contentWindow.postMessage(
{ action: 'SET_TAB_SUCCESS', requestedHandler: 'UI',payload: {
name: event?.data?.payload?.name
} }, '*'
);
} }
}; };
@ -402,6 +479,6 @@ export const useQortalMessageListener = (frameWindow) => {
} }
}); });
return {path} return {path, history, resetHistory, changeCurrentIndex}
}; };