import './shortcuts.css'; import { t, Trans } from '@lingui/macro'; import { useLingui } from '@lingui/react'; import { MenuDivider } from '@szhsin/react-menu'; import { memo } from 'preact/compat'; import { useRef, useState } from 'preact/hooks'; import { useHotkeys } from 'react-hotkeys-hook'; import { useNavigate } from 'react-router-dom'; import { useSnapshot } from 'valtio'; import { SHORTCUTS_META } from '../components/shortcuts-settings'; import { api } from '../utils/api'; import { getLists } from '../utils/lists'; import states from '../utils/states'; import AsyncText from './AsyncText'; import Icon from './icon'; import Link from './link'; import MenuLink from './menu-link'; import Menu2 from './menu2'; import SubMenu2 from './submenu2'; function Shortcuts() { const { _ } = useLingui(); const { instance } = api(); const snapStates = useSnapshot(states); const { shortcuts, settings } = snapStates; if (!shortcuts.length) { return null; } if ( settings.shortcutsViewMode === 'multi-column' || (!settings.shortcutsViewMode && settings.shortcutsColumnsMode) ) { return null; } const menuRef = useRef(); const hasLists = useRef(false); const formattedShortcuts = shortcuts .map((pin, i) => { const { type, ...data } = pin; if (!SHORTCUTS_META[type]) return null; let { id, path, title, subtitle, icon } = SHORTCUTS_META[type]; if (typeof id === 'function') { id = id(data, i); } if (typeof path === 'function') { path = path( { ...data, instance: data.instance || instance, }, i, ); } if (typeof title === 'function') { title = title(data, i); } else { title = _(title); } if (typeof subtitle === 'function') { subtitle = subtitle(data, i); } else { subtitle = _(subtitle); } if (typeof icon === 'function') { icon = icon(data, i); } if (id === 'lists') { hasLists.current = true; } return { id, path, title, subtitle, icon, }; }) .filter(Boolean); const navigate = useNavigate(); useHotkeys(['1', '2', '3', '4', '5', '6', '7', '8', '9'], (e, handler) => { const index = parseInt(handler.keys[0], 10) - 1; if (index < formattedShortcuts.length) { const { path } = formattedShortcuts[index]; if (path) { navigate(path); menuRef.current?.closeMenu?.(); } } }); const [lists, setLists] = useState([]); return ( <div id="shortcuts"> {snapStates.settings.shortcutsViewMode === 'tab-menu-bar' ? ( <nav class="tab-bar" onContextMenu={(e) => { e.preventDefault(); states.showShortcutsSettings = true; }} > <ul> {formattedShortcuts.map( ({ id, path, title, subtitle, icon }, i) => { return ( <li key={`${i}-${id}-${title}-${subtitle}-${path}`}> <Link class={subtitle ? 'has-subtitle' : ''} to={path} onClick={(e) => { if (e.target.classList.contains('is-active')) { e.preventDefault(); const page = document.getElementById(`${id}-page`); console.log(id, page); if (page) { page.scrollTop = 0; const updatesButton = page.querySelector('.updates-button'); if (updatesButton) { updatesButton.click(); } } } }} > <Icon icon={icon} size="xl" alt={title} /> <span> <AsyncText>{title}</AsyncText> {subtitle && ( <> <br /> <small>{subtitle}</small> </> )} </span> </Link> </li> ); }, )} </ul> </nav> ) : ( <Menu2 instanceRef={menuRef} overflow="auto" viewScroll="close" menuClassName="glass-menu shortcuts-menu" gap={8} position="anchor" onMenuChange={(e) => { if (e.open && hasLists.current) { getLists().then(setLists); } }} menuButton={ <button type="button" id="shortcuts-button" class="plain" onContextMenu={(e) => { e.preventDefault(); states.showShortcutsSettings = true; }} onTransitionStart={(e) => { // Close menu if the button disappears try { const { target } = e; if (getComputedStyle(target).pointerEvents === 'none') { menuRef.current?.closeMenu?.(); } } catch (e) {} }} > <Icon icon="shortcut" size="xl" alt={t`Shortcuts`} /> </button> } > {formattedShortcuts.map(({ id, path, title, subtitle, icon }, i) => { if (id === 'lists') { return ( <SubMenu2 menuClassName="glass-menu" overflow="auto" gap={-8} label={ <> <Icon icon={icon} size="l" /> <span class="menu-grow"> <AsyncText>{title}</AsyncText> </span> <Icon icon="chevron-right" /> </> } > <MenuLink to="/l"> <span> <Trans>All Lists</Trans> </span> </MenuLink> <MenuDivider /> {lists?.map((list) => ( <MenuLink key={list.id} to={`/l/${list.id}`}> <span>{list.title}</span> </MenuLink> ))} </SubMenu2> ); } return ( <MenuLink to={path} key={`${i}-${id}-${title}-${subtitle}-${path}`} class="glass-menu-item" > <Icon icon={icon} size="l" />{' '} <span class="menu-grow"> <span> <AsyncText>{title}</AsyncText> </span> {subtitle && ( <> {' '} <small class="more-insignificant">{subtitle}</small> </> )} </span> <span class="menu-shortcut hide-until-focus-visible"> {i + 1} </span> </MenuLink> ); })} </Menu2> )} </div> ); } export default memo(Shortcuts);