Skip to content
Snippets Groups Projects
menu.provider.tsx 4.97 KiB
Newer Older
  • Learn to ignore specific revisions
  • Matthias Feyll's avatar
    Matthias Feyll committed
    import { faRightFromBracket, IconDefinition } from "@fortawesome/free-solid-svg-icons";
    
    matthiasf's avatar
    matthiasf committed
    import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
    
    Matthias Feyll's avatar
    Matthias Feyll committed
    import { BasicProp } from "@helper/interfaces";
    import { useAuth } from "@provider/auth.provider";
    
    matthiasf's avatar
    matthiasf committed
    import React, { createContext, useContext, useEffect, useMemo, useState } from "react";
    
    Matthias Feyll's avatar
    Matthias Feyll committed
    import { useTranslation } from "react-i18next";
    
    matthiasf's avatar
    matthiasf committed
    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>({
    
    Matthias Feyll's avatar
    Matthias Feyll committed
        subscribe: function (): MenuSubscription {
    
    matthiasf's avatar
    matthiasf committed
            throw new Error("Function not implemented.");
        }
    })
    
    interface SubscriptionValue {
        target: HTMLElement,
        actions: Array<Action>
    }
    
    
    
    Matthias Feyll's avatar
    Matthias Feyll committed
    export const MenuProvider: React.FC<BasicProp> = ({ children }) => {
    
    matthiasf's avatar
    matthiasf committed
        const [menuPosition, setMenuPosition] = useState({ top: 0, left: 0 });
        const [showMenu, setShowMenu] = useState(false);
        const [subscribedTargets, setSubscribedTargets] = useState<Array<SubscriptionValue>>([])
    
    Matthias Feyll's avatar
    Matthias Feyll committed
        const { logout } = useAuth()
    
    matthiasf's avatar
    matthiasf committed
    
    
    Matthias Feyll's avatar
    Matthias Feyll committed
        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>>([]);
    
    matthiasf's avatar
    matthiasf committed
        const [clickedHtmlElement, setClickedHtmlElement] = useState<HTMLElement>()
    
    
    Matthias Feyll's avatar
    Matthias Feyll committed
    
        const setMenuItems = (menuItems: Array<SubscriptionValue>) => {
            _setMenuItems([...menuItems, globalMenuItems])
        }
    
        const handleContextMenu = (event: React.MouseEvent<HTMLElement>) => {
    
    matthiasf's avatar
    matthiasf committed
            event.preventDefault();
    
    Matthias Feyll's avatar
    Matthias Feyll committed
            const targets = subscribedTargets.filter(({ target }) => target.contains(event.target as HTMLElement))
    
    matthiasf's avatar
    matthiasf committed
    
            setMenuPosition({ top: event.pageY, left: event.pageX });
            setMenuItems(targets)
    
    Matthias Feyll's avatar
    Matthias Feyll committed
            setClickedHtmlElement(event.target as HTMLElement)
    
    matthiasf's avatar
    matthiasf committed
            displayMenu()
        };
    
        const displayMenu = () => {
            setShowMenu(true);
        }
    
        const hideMenu = () => {
            setShowMenu(false);
            setMenuItems([]);
        }
    
        const handleClick = () => hideMenu();
    
        useEffect(() => {
            document.addEventListener('keyup', (e) => {
                if (e.code === "Escape") {
                    hideMenu();
                }
            });
    
    Matthias Feyll's avatar
    Matthias Feyll committed
        }, [hideMenu])
    
    matthiasf's avatar
    matthiasf committed
    
        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
    
    Matthias Feyll's avatar
    Matthias Feyll committed
                                const deviderHTML = (<li key={i}><hr className="dropdown-divider"></hr></li>);
                                const seperator = i < menuItems.length - 1 ? deviderHTML : (<React.Fragment key={i}></React.Fragment>)
    
    matthiasf's avatar
    matthiasf committed
    
                                const dropdownItems = item.actions.map(({ action, icon, key }) => {
    
    
    Matthias Feyll's avatar
    Matthias Feyll committed
                                    const disabled = !(clickedHtmlElement instanceof HTMLElement)
    
    matthiasf's avatar
    matthiasf committed
    
                                    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)
    }