services/tabs.jsxjavascript
/*
Manages external browser tabs/windows opened from within the app (e.g. links
that should open outside the kiosk view), keyed by URL so a given URL only
ever has one tab open at a time.
TabContext: holds no state directly, only exposes openTab/closeTab.
useTabs: hook to access openTab/closeTab anywhere in the tree.
TabProvider: tracks open WindowProxy references per URL in state.
*/
/* eslint-disable react-refresh/only-export-components */
import React, { createContext, useContext, useState, useCallback, useMemo } from "react";
import PropTypes from "prop-types";
const TabContext = createContext();
/**
* Provides openTab/closeTab (see useTabs) to the component tree, tracking
* open external tabs/windows per URL.
*/
export const TabProvider = ({ children }) => {
const [tabs, setTabs] = useState({});
/**
* Opens a new tab for `url`, or focuses/reopens the existing one if it's
* already open (or was closed by the user in the meantime).
*
* @param {string} url - Also used as the tabs' cache key.
* @returns {Window|null} The (possibly newly opened) tab, or null if the
* browser blocked the popup.
*/
const openTab = useCallback(
(url) => {
const existingTab = tabs[url];
// `existingTab.closed` is false as long as the WindowProxy is still open -
// becomes true if the user closed the tab manually, in which case we
// treat it as gone and open a fresh one below.
if (!existingTab || existingTab.closed) {
// Neues Fenster öffnen
const newTab = window.open(url, "_blank");
setTabs((prev) => ({ ...prev, [url]: newTab }));
return newTab;
} else {
existingTab.focus();
return existingTab;
}
},
[tabs]
);
/**
* Closes the tab tracked for `url`, if it's still open, and forgets it.
*
* @param {string} url
*/
const closeTab = useCallback((url) => {
setTabs((prev) => {
const existingTab = prev[url];
if (existingTab && !existingTab.closed) {
existingTab.close();
}
const updated = { ...prev };
delete updated[url];
return updated;
});
}, []);
const value = useMemo(() => ({ openTab, closeTab }), [openTab, closeTab]);
return <TabContext.Provider value={value}>{children}</TabContext.Provider>;
};
TabProvider.propTypes = {
children: PropTypes.node.isRequired,
};
/**
* React hook to access openTab/closeTab. Must be used within a `<TabProvider>`.
*/
export const useTabs = () => useContext(TabContext);