2023-02-16 12:51:54 +03:00
|
|
|
import './shortcuts.css';
|
|
|
|
|
2024-03-23 18:52:05 +03:00
|
|
|
import { MenuDivider, SubMenu } from '@szhsin/react-menu';
|
2023-10-22 14:24:59 +03:00
|
|
|
import { memo } from 'preact/compat';
|
2024-03-23 18:52:05 +03:00
|
|
|
import { useRef, useState } from 'preact/hooks';
|
2023-02-16 12:51:54 +03:00
|
|
|
import { useHotkeys } from 'react-hotkeys-hook';
|
|
|
|
import { useNavigate } from 'react-router-dom';
|
|
|
|
import { useSnapshot } from 'valtio';
|
|
|
|
|
|
|
|
import { SHORTCUTS_META } from '../components/shortcuts-settings';
|
2023-04-14 06:13:14 +03:00
|
|
|
import { api } from '../utils/api';
|
2024-03-23 18:52:05 +03:00
|
|
|
import { getLists } from '../utils/lists';
|
2023-02-16 12:51:54 +03:00
|
|
|
import states from '../utils/states';
|
|
|
|
|
|
|
|
import AsyncText from './AsyncText';
|
|
|
|
import Icon from './icon';
|
2023-02-27 19:17:00 +03:00
|
|
|
import Link from './link';
|
2023-11-18 16:11:07 +03:00
|
|
|
import Menu2 from './menu2';
|
2023-03-28 10:59:20 +03:00
|
|
|
import MenuLink from './menu-link';
|
2023-02-16 12:51:54 +03:00
|
|
|
|
|
|
|
function Shortcuts() {
|
2023-04-14 06:13:14 +03:00
|
|
|
const { instance } = api();
|
2023-02-16 12:51:54 +03:00
|
|
|
const snapStates = useSnapshot(states);
|
2023-10-21 12:40:03 +03:00
|
|
|
const { shortcuts, settings } = snapStates;
|
2023-02-16 12:51:54 +03:00
|
|
|
|
|
|
|
if (!shortcuts.length) {
|
|
|
|
return null;
|
|
|
|
}
|
2023-10-21 12:40:03 +03:00
|
|
|
if (
|
2023-11-02 07:59:52 +03:00
|
|
|
settings.shortcutsViewMode === 'multi-column' ||
|
|
|
|
(!settings.shortcutsViewMode && settings.shortcutsColumnsMode)
|
2023-10-21 12:40:03 +03:00
|
|
|
) {
|
|
|
|
return null;
|
|
|
|
}
|
2023-02-16 12:51:54 +03:00
|
|
|
|
|
|
|
const menuRef = useRef();
|
|
|
|
|
2024-03-23 18:52:05 +03:00
|
|
|
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];
|
2023-02-16 12:51:54 +03:00
|
|
|
|
2024-03-23 18:52:05 +03:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
if (typeof subtitle === 'function') {
|
|
|
|
subtitle = subtitle(data, i);
|
|
|
|
}
|
|
|
|
if (typeof icon === 'function') {
|
|
|
|
icon = icon(data, i);
|
|
|
|
}
|
2023-02-16 12:51:54 +03:00
|
|
|
|
2024-03-23 18:52:05 +03:00
|
|
|
if (id === 'lists') {
|
|
|
|
hasLists.current = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
id,
|
|
|
|
path,
|
|
|
|
title,
|
|
|
|
subtitle,
|
|
|
|
icon,
|
|
|
|
};
|
|
|
|
})
|
|
|
|
.filter(Boolean);
|
2023-02-16 12:51:54 +03:00
|
|
|
|
|
|
|
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);
|
2023-02-19 17:07:15 +03:00
|
|
|
menuRef.current?.closeMenu?.();
|
2023-02-16 12:51:54 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2024-03-23 18:52:05 +03:00
|
|
|
const [lists, setLists] = useState([]);
|
|
|
|
|
2023-02-16 12:51:54 +03:00
|
|
|
return (
|
|
|
|
<div id="shortcuts">
|
2023-02-27 18:59:41 +03:00
|
|
|
{snapStates.settings.shortcutsViewMode === 'tab-menu-bar' ? (
|
2023-11-03 17:08:44 +03:00
|
|
|
<nav
|
|
|
|
class="tab-bar"
|
|
|
|
onContextMenu={(e) => {
|
|
|
|
e.preventDefault();
|
|
|
|
states.showShortcutsSettings = true;
|
|
|
|
}}
|
|
|
|
>
|
2023-02-27 18:59:41 +03:00
|
|
|
<ul>
|
2023-04-08 15:42:38 +03:00
|
|
|
{formattedShortcuts.map(
|
|
|
|
({ id, path, title, subtitle, icon }, i) => {
|
|
|
|
return (
|
2023-11-06 18:50:49 +03:00
|
|
|
<li key={`${i}-${id}-${title}-${subtitle}-${path}`}>
|
2023-04-08 15:42:38 +03:00
|
|
|
<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();
|
|
|
|
}
|
2023-02-27 18:59:41 +03:00
|
|
|
}
|
|
|
|
}
|
2023-04-08 15:42:38 +03:00
|
|
|
}}
|
|
|
|
>
|
|
|
|
<Icon icon={icon} size="xl" alt={title} />
|
|
|
|
<span>
|
|
|
|
<AsyncText>{title}</AsyncText>
|
|
|
|
{subtitle && (
|
|
|
|
<>
|
|
|
|
<br />
|
|
|
|
<small>{subtitle}</small>
|
|
|
|
</>
|
|
|
|
)}
|
|
|
|
</span>
|
|
|
|
</Link>
|
|
|
|
</li>
|
|
|
|
);
|
|
|
|
},
|
|
|
|
)}
|
2023-02-27 18:59:41 +03:00
|
|
|
</ul>
|
|
|
|
</nav>
|
|
|
|
) : (
|
2023-11-18 16:11:07 +03:00
|
|
|
<Menu2
|
2023-02-27 18:59:41 +03:00
|
|
|
instanceRef={menuRef}
|
|
|
|
overflow="auto"
|
|
|
|
viewScroll="close"
|
|
|
|
menuClassName="glass-menu shortcuts-menu"
|
2023-06-13 12:46:37 +03:00
|
|
|
gap={8}
|
2023-02-27 18:59:41 +03:00
|
|
|
position="anchor"
|
2024-03-23 18:52:05 +03:00
|
|
|
onMenuChange={(e) => {
|
|
|
|
if (e.open && hasLists.current) {
|
|
|
|
getLists().then(setLists);
|
|
|
|
}
|
|
|
|
}}
|
2023-02-27 18:59:41 +03:00
|
|
|
menuButton={
|
|
|
|
<button
|
|
|
|
type="button"
|
|
|
|
id="shortcuts-button"
|
|
|
|
class="plain"
|
2023-11-03 17:08:44 +03:00
|
|
|
onContextMenu={(e) => {
|
|
|
|
e.preventDefault();
|
|
|
|
states.showShortcutsSettings = true;
|
|
|
|
}}
|
2023-02-27 18:59:41 +03:00
|
|
|
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="Shortcuts" />
|
|
|
|
</button>
|
|
|
|
}
|
|
|
|
>
|
2023-11-06 18:50:49 +03:00
|
|
|
{formattedShortcuts.map(({ id, path, title, subtitle, icon }, i) => {
|
2024-03-23 18:52:05 +03:00
|
|
|
if (id === 'lists') {
|
|
|
|
return (
|
|
|
|
<SubMenu
|
|
|
|
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>All Lists</span>
|
|
|
|
</MenuLink>
|
|
|
|
<MenuDivider />
|
|
|
|
{lists?.map((list) => (
|
|
|
|
<MenuLink key={list.id} to={`/l/${list.id}`}>
|
|
|
|
<span>{list.title}</span>
|
|
|
|
</MenuLink>
|
|
|
|
))}
|
|
|
|
</SubMenu>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-02-27 18:59:41 +03:00
|
|
|
return (
|
2023-11-06 18:50:49 +03:00
|
|
|
<MenuLink
|
|
|
|
to={path}
|
|
|
|
key={`${i}-${id}-${title}-${subtitle}-${path}`}
|
|
|
|
class="glass-menu-item"
|
|
|
|
>
|
2023-02-27 18:59:41 +03:00
|
|
|
<Icon icon={icon} size="l" />{' '}
|
|
|
|
<span class="menu-grow">
|
2023-04-08 15:42:38 +03:00
|
|
|
<span>
|
|
|
|
<AsyncText>{title}</AsyncText>
|
|
|
|
</span>
|
|
|
|
{subtitle && (
|
|
|
|
<>
|
|
|
|
{' '}
|
|
|
|
<small class="more-insignificant">{subtitle}</small>
|
|
|
|
</>
|
|
|
|
)}
|
2023-02-27 18:59:41 +03:00
|
|
|
</span>
|
|
|
|
<span class="menu-shortcut hide-until-focus-visible">
|
|
|
|
{i + 1}
|
|
|
|
</span>
|
|
|
|
</MenuLink>
|
|
|
|
);
|
|
|
|
})}
|
2023-11-18 16:11:07 +03:00
|
|
|
</Menu2>
|
2023-02-27 18:59:41 +03:00
|
|
|
)}
|
2023-02-16 12:51:54 +03:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-10-22 14:24:59 +03:00
|
|
|
export default memo(Shortcuts);
|