Newer
Older
import { faRightFromBracket, IconDefinition } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { BasicProp } from "@helper/interfaces";
import { useAuth } from "@provider/auth.provider";
import React, { createContext, useContext, useEffect, useMemo, useState } from "react";
import './menu.provider.scss';
interface MenuSubscription {
unsubscribe: () => void
}
// describes a action decorated with a item name
// on click the action is getting executed
type Action = {
key: string,
icon: IconDefinition,
action: (clickedHtmlElement: HTMLElement | undefined) => void
}
interface MenuProviderType {
subscribe: (value: SubscriptionValue) => MenuSubscription
}
const MenuContext = createContext<MenuProviderType>({
throw new Error("Function not implemented.");
}
})
interface SubscriptionValue {
target: HTMLElement,
actions: Array<Action>
}
export const MenuProvider: React.FC<BasicProp> = ({ children }) => {
const [menuPosition, setMenuPosition] = useState({ top: 0, left: 0 });
const [showMenu, setShowMenu] = useState(false);
const [subscribedTargets, setSubscribedTargets] = useState<Array<SubscriptionValue>>([])
const { t } = useTranslation('common')
const globalMenuItems: SubscriptionValue = {
actions: [{
key: t('global.menu_item.logout'),
icon: faRightFromBracket,
action: logout
}],
target: document.getRootNode() as HTMLElement
}
const [menuItems, _setMenuItems] = useState<Array<SubscriptionValue>>([]);
const [clickedHtmlElement, setClickedHtmlElement] = useState<HTMLElement>()
const setMenuItems = (menuItems: Array<SubscriptionValue>) => {
_setMenuItems([...menuItems, globalMenuItems])
}
const handleContextMenu = (event: React.MouseEvent<HTMLElement>) => {
const targets = subscribedTargets.filter(({ target }) => target.contains(event.target as HTMLElement))
setMenuPosition({ top: event.pageY, left: event.pageX });
setMenuItems(targets)
setClickedHtmlElement(event.target as HTMLElement)
displayMenu()
};
const displayMenu = () => {
setShowMenu(true);
}
const hideMenu = () => {
setShowMenu(false);
setMenuItems([]);
}
const handleClick = () => hideMenu();
useEffect(() => {
document.addEventListener('keyup', (e) => {
if (e.code === "Escape") {
hideMenu();
}
});
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
const value = useMemo<MenuProviderType>(() => {
return {
subscribe(target) {
const index = subscribedTargets.length;
setSubscribedTargets([...subscribedTargets, target])
const subscription: MenuSubscription = {
unsubscribe() {
setSubscribedTargets([...subscribedTargets.splice(index, 1)])
},
}
return subscription
},
} as MenuProviderType
}, [])
return (
<MenuContext.Provider value={value}>
<div onContextMenu={handleContextMenu} onClick={handleClick} style={{ height: "100vh" }}>
<div
className={`menu-container dropdown-menu ${showMenu ? "show" : ""}`}
style={{ position: "absolute", top: `${menuPosition.top}px`, left: `${menuPosition.left}px` }}
>
{
menuItems.map((item, i) => {
// for each new action array (for each new subscription entity) draw a seperator line except the last action
const deviderHTML = (<li key={i}><hr className="dropdown-divider"></hr></li>);
const seperator = i < menuItems.length - 1 ? deviderHTML : (<React.Fragment key={i}></React.Fragment>)
const dropdownItems = item.actions.map(({ action, icon, key }) => {
const disabled = !(clickedHtmlElement instanceof HTMLElement)
return (
<button className="menu-button dropdown-item" key={key + " " + i} disabled={disabled} onClick={() => action(clickedHtmlElement)}>
<FontAwesomeIcon icon={icon} size="sm" />
<span>{key}</span>
</button>
)
})
return [...dropdownItems, seperator]
})
}
</div>
{children}
</div>
</MenuContext.Provider>
)
}
export const useMenu = () => {
return useContext(MenuContext)
}