2023-05-20 12:46:58 +03:00
|
|
|
import './nav-menu.css';
|
|
|
|
|
2023-10-03 10:07:47 +03:00
|
|
|
import { ControlledMenu, MenuDivider, MenuItem } from '@szhsin/react-menu';
|
2023-06-07 14:37:47 +03:00
|
|
|
import { useEffect, useRef, useState } from 'preact/hooks';
|
2023-03-23 04:51:52 +03:00
|
|
|
import { useLongPress } from 'use-long-press';
|
2023-02-12 12:38:50 +03:00
|
|
|
import { useSnapshot } from 'valtio';
|
2023-02-10 17:10:13 +03:00
|
|
|
|
2023-02-10 19:05:18 +03:00
|
|
|
import { api } from '../utils/api';
|
2023-06-15 13:03:37 +03:00
|
|
|
import safeBoundingBoxPadding from '../utils/safe-bounding-box-padding';
|
2023-02-10 17:10:13 +03:00
|
|
|
import states from '../utils/states';
|
2023-03-23 04:51:52 +03:00
|
|
|
import store from '../utils/store';
|
2023-02-10 17:10:13 +03:00
|
|
|
|
2023-03-23 04:51:52 +03:00
|
|
|
import Avatar from './avatar';
|
2023-02-10 17:10:13 +03:00
|
|
|
import Icon from './icon';
|
2023-03-28 10:59:20 +03:00
|
|
|
import MenuLink from './menu-link';
|
2023-02-10 17:10:13 +03:00
|
|
|
|
|
|
|
function NavMenu(props) {
|
2023-02-12 12:38:50 +03:00
|
|
|
const snapStates = useSnapshot(states);
|
2023-10-02 12:51:36 +03:00
|
|
|
const { masto, instance, authenticated } = api();
|
2023-06-07 14:37:47 +03:00
|
|
|
|
|
|
|
const [currentAccount, setCurrentAccount] = useState();
|
|
|
|
const [moreThanOneAccount, setMoreThanOneAccount] = useState(false);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
const accounts = store.local.getJSON('accounts') || [];
|
|
|
|
const acc = accounts.find(
|
|
|
|
(account) => account.info.id === store.session.get('currentAccount'),
|
|
|
|
);
|
|
|
|
if (acc) setCurrentAccount(acc);
|
|
|
|
setMoreThanOneAccount(accounts.length > 1);
|
|
|
|
}, []);
|
2023-02-18 16:37:34 +03:00
|
|
|
|
|
|
|
// Home = Following
|
|
|
|
// But when in multi-column mode, Home becomes columns of anything
|
|
|
|
// User may choose pin or not to pin Following
|
|
|
|
// If user doesn't pin Following, we show it in the menu
|
|
|
|
const showFollowing =
|
2023-11-02 07:59:52 +03:00
|
|
|
(snapStates.settings.shortcutsViewMode === 'multi-column' ||
|
|
|
|
(!snapStates.settings.shortcutsViewMode &&
|
|
|
|
snapStates.settings.shortcutsColumnsMode)) &&
|
2023-02-18 16:37:34 +03:00
|
|
|
!snapStates.shortcuts.find((pin) => pin.type === 'following');
|
|
|
|
|
2023-03-23 04:51:52 +03:00
|
|
|
const bindLongPress = useLongPress(
|
|
|
|
() => {
|
|
|
|
states.showAccounts = true;
|
|
|
|
},
|
|
|
|
{
|
2023-04-24 16:36:03 +03:00
|
|
|
threshold: 600,
|
2023-03-23 04:51:52 +03:00
|
|
|
detect: 'touch',
|
|
|
|
cancelOnMovement: true,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2023-04-26 08:23:54 +03:00
|
|
|
const buttonRef = useRef();
|
2023-05-20 12:08:20 +03:00
|
|
|
const [menuState, setMenuState] = useState(undefined);
|
2023-04-26 08:23:54 +03:00
|
|
|
|
2023-06-15 13:03:37 +03:00
|
|
|
const boundingBoxPadding = safeBoundingBoxPadding([
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
snapStates.settings.shortcutsViewMode === 'tab-menu-bar' ? 50 : 0,
|
|
|
|
0,
|
|
|
|
]);
|
|
|
|
|
2023-10-02 12:51:36 +03:00
|
|
|
const mutesIterator = useRef();
|
|
|
|
async function fetchMutes(firstLoad) {
|
|
|
|
if (firstLoad || !mutesIterator.current) {
|
|
|
|
mutesIterator.current = masto.v1.mutes.list({
|
|
|
|
limit: 80,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
const results = await mutesIterator.current.next();
|
|
|
|
return results;
|
|
|
|
}
|
|
|
|
|
|
|
|
const blocksIterator = useRef();
|
|
|
|
async function fetchBlocks(firstLoad) {
|
|
|
|
if (firstLoad || !blocksIterator.current) {
|
|
|
|
blocksIterator.current = masto.v1.blocks.list({
|
|
|
|
limit: 80,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
const results = await blocksIterator.current.next();
|
|
|
|
return results;
|
|
|
|
}
|
|
|
|
|
2024-02-25 08:37:29 +03:00
|
|
|
const buttonClickTS = useRef();
|
2023-02-10 17:10:13 +03:00
|
|
|
return (
|
2023-04-26 08:23:54 +03:00
|
|
|
<>
|
|
|
|
<button
|
|
|
|
ref={buttonRef}
|
|
|
|
type="button"
|
|
|
|
class={`button plain nav-menu-button ${
|
|
|
|
moreThanOneAccount ? 'with-avatar' : ''
|
|
|
|
} ${open ? 'active' : ''}`}
|
|
|
|
style={{ position: 'relative' }}
|
2023-05-20 12:08:20 +03:00
|
|
|
onClick={() => {
|
2024-02-25 08:37:29 +03:00
|
|
|
buttonClickTS.current = Date.now();
|
2023-05-20 12:08:20 +03:00
|
|
|
setMenuState((state) => (!state ? 'open' : undefined));
|
|
|
|
}}
|
2023-04-26 08:23:54 +03:00
|
|
|
onContextMenu={(e) => {
|
|
|
|
e.preventDefault();
|
|
|
|
states.showAccounts = true;
|
|
|
|
}}
|
|
|
|
{...bindLongPress()}
|
|
|
|
>
|
|
|
|
{moreThanOneAccount && (
|
|
|
|
<Avatar
|
|
|
|
url={
|
|
|
|
currentAccount?.info?.avatar || currentAccount?.info?.avatarStatic
|
|
|
|
}
|
|
|
|
size="l"
|
|
|
|
squircle={currentAccount?.info?.bot}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
<Icon icon="menu" size={moreThanOneAccount ? 's' : 'l'} />
|
|
|
|
</button>
|
|
|
|
<ControlledMenu
|
2023-05-20 12:46:58 +03:00
|
|
|
menuClassName="nav-menu"
|
2023-05-20 12:08:20 +03:00
|
|
|
state={menuState}
|
2023-04-26 08:23:54 +03:00
|
|
|
anchorRef={buttonRef}
|
|
|
|
onClose={() => {
|
2023-05-20 12:08:20 +03:00
|
|
|
setMenuState(undefined);
|
2023-04-26 08:23:54 +03:00
|
|
|
}}
|
|
|
|
containerProps={{
|
2023-04-26 08:59:54 +03:00
|
|
|
style: {
|
|
|
|
zIndex: 10,
|
|
|
|
},
|
2023-04-26 08:23:54 +03:00
|
|
|
onClick: () => {
|
2024-02-25 08:37:29 +03:00
|
|
|
if (Date.now() - buttonClickTS.current < 300) {
|
|
|
|
return;
|
|
|
|
}
|
2023-05-20 12:08:20 +03:00
|
|
|
setMenuState(undefined);
|
2023-04-26 08:23:54 +03:00
|
|
|
},
|
|
|
|
}}
|
|
|
|
portal={{
|
|
|
|
target: document.body,
|
|
|
|
}}
|
|
|
|
{...props}
|
|
|
|
overflow="auto"
|
|
|
|
viewScroll="close"
|
2023-04-30 16:03:09 +03:00
|
|
|
position="anchor"
|
|
|
|
align="center"
|
2023-06-15 13:03:37 +03:00
|
|
|
boundingBoxPadding={boundingBoxPadding}
|
2023-04-26 08:23:54 +03:00
|
|
|
unmountOnClose
|
|
|
|
>
|
2023-09-03 13:41:36 +03:00
|
|
|
{!!snapStates.appVersion?.commitHash &&
|
|
|
|
__COMMIT_HASH__ !== snapStates.appVersion.commitHash && (
|
|
|
|
<div class="top-menu">
|
|
|
|
<MenuItem
|
|
|
|
onClick={() => {
|
|
|
|
const yes = confirm('Reload page now to update?');
|
|
|
|
if (yes) {
|
|
|
|
(async () => {
|
|
|
|
try {
|
|
|
|
location.reload();
|
|
|
|
} catch (e) {}
|
|
|
|
})();
|
|
|
|
}
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<Icon icon="sparkles" class="sparkle-icon" size="l" />{' '}
|
|
|
|
<span>New update available…</span>
|
|
|
|
</MenuItem>
|
|
|
|
<MenuDivider />
|
|
|
|
</div>
|
|
|
|
)}
|
2023-05-20 12:46:58 +03:00
|
|
|
<section>
|
|
|
|
<MenuLink to="/">
|
|
|
|
<Icon icon="home" size="l" /> <span>Home</span>
|
|
|
|
</MenuLink>
|
|
|
|
{authenticated && (
|
2023-04-26 08:23:54 +03:00
|
|
|
<>
|
2023-05-20 12:46:58 +03:00
|
|
|
{showFollowing && (
|
|
|
|
<MenuLink to="/following">
|
|
|
|
<Icon icon="following" size="l" /> <span>Following</span>
|
|
|
|
</MenuLink>
|
|
|
|
)}
|
|
|
|
<MenuLink to="/mentions">
|
|
|
|
<Icon icon="at" size="l" /> <span>Mentions</span>
|
|
|
|
</MenuLink>
|
|
|
|
<MenuLink to="/notifications">
|
|
|
|
<Icon icon="notification" size="l" /> <span>Notifications</span>
|
|
|
|
{snapStates.notificationsShowNew && (
|
|
|
|
<sup title="New" style={{ opacity: 0.5 }}>
|
|
|
|
{' '}
|
|
|
|
•
|
|
|
|
</sup>
|
|
|
|
)}
|
|
|
|
</MenuLink>
|
|
|
|
<MenuDivider />
|
|
|
|
<MenuLink to="/l">
|
|
|
|
<Icon icon="list" size="l" /> <span>Lists</span>
|
|
|
|
</MenuLink>
|
|
|
|
<MenuLink to="/ft">
|
|
|
|
<Icon icon="hashtag" size="l" /> <span>Followed Hashtags</span>
|
|
|
|
</MenuLink>
|
|
|
|
<MenuLink to="/b">
|
|
|
|
<Icon icon="bookmark" size="l" /> <span>Bookmarks</span>
|
|
|
|
</MenuLink>
|
|
|
|
<MenuLink to="/f">
|
2023-10-25 08:55:12 +03:00
|
|
|
<Icon icon="heart" size="l" /> <span>Likes</span>
|
2023-05-20 12:46:58 +03:00
|
|
|
</MenuLink>
|
|
|
|
</>
|
|
|
|
)}
|
|
|
|
<MenuDivider />
|
|
|
|
<MenuLink to={`/search`}>
|
|
|
|
<Icon icon="search" size="l" /> <span>Search</span>
|
|
|
|
</MenuLink>
|
|
|
|
<MenuLink to={`/${instance}/p/l`}>
|
2023-12-28 06:57:48 +03:00
|
|
|
<Icon icon="building" size="l" /> <span>Local</span>
|
2023-05-20 12:46:58 +03:00
|
|
|
</MenuLink>
|
|
|
|
<MenuLink to={`/${instance}/p`}>
|
|
|
|
<Icon icon="earth" size="l" /> <span>Federated</span>
|
|
|
|
</MenuLink>
|
|
|
|
<MenuLink to={`/${instance}/trending`}>
|
|
|
|
<Icon icon="chart" size="l" /> <span>Trending</span>
|
|
|
|
</MenuLink>
|
|
|
|
</section>
|
|
|
|
<section>
|
2023-06-20 08:30:26 +03:00
|
|
|
{authenticated ? (
|
2023-05-20 12:46:58 +03:00
|
|
|
<>
|
|
|
|
<MenuDivider />
|
|
|
|
{currentAccount?.info?.id && (
|
|
|
|
<MenuLink to={`/${instance}/a/${currentAccount.info.id}`}>
|
|
|
|
<Icon icon="user" size="l" /> <span>Profile</span>
|
|
|
|
</MenuLink>
|
|
|
|
)}
|
2023-04-26 08:23:54 +03:00
|
|
|
<MenuItem
|
|
|
|
onClick={() => {
|
2023-05-20 12:46:58 +03:00
|
|
|
states.showAccounts = true;
|
2023-04-26 08:23:54 +03:00
|
|
|
}}
|
|
|
|
>
|
2023-05-20 12:46:58 +03:00
|
|
|
<Icon icon="group" size="l" /> <span>Accounts…</span>
|
|
|
|
</MenuItem>
|
2023-10-02 12:51:36 +03:00
|
|
|
<MenuItem
|
|
|
|
onClick={() => {
|
|
|
|
states.showGenericAccounts = {
|
|
|
|
id: 'mute',
|
|
|
|
heading: 'Muted users',
|
|
|
|
fetchAccounts: fetchMutes,
|
2023-12-20 08:55:56 +03:00
|
|
|
excludeRelationshipAttrs: ['muting'],
|
2023-10-02 12:51:36 +03:00
|
|
|
};
|
|
|
|
}}
|
|
|
|
>
|
2023-10-02 16:20:47 +03:00
|
|
|
<Icon icon="mute" size="l" /> Muted users…
|
2023-10-02 12:51:36 +03:00
|
|
|
</MenuItem>
|
|
|
|
<MenuItem
|
|
|
|
onClick={() => {
|
|
|
|
states.showGenericAccounts = {
|
|
|
|
id: 'block',
|
|
|
|
heading: 'Blocked users',
|
|
|
|
fetchAccounts: fetchBlocks,
|
2023-12-20 08:55:56 +03:00
|
|
|
excludeRelationshipAttrs: ['blocking'],
|
2023-10-02 12:51:36 +03:00
|
|
|
};
|
|
|
|
}}
|
|
|
|
>
|
2023-10-02 16:20:47 +03:00
|
|
|
<Icon icon="block" size="l" />
|
2023-10-02 12:51:36 +03:00
|
|
|
Blocked users…
|
|
|
|
</MenuItem>
|
2023-10-19 16:07:00 +03:00
|
|
|
<MenuDivider className="divider-grow" />
|
2023-09-06 17:54:05 +03:00
|
|
|
<MenuItem
|
|
|
|
onClick={() => {
|
|
|
|
states.showKeyboardShortcutsHelp = true;
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<Icon icon="keyboard" size="l" />{' '}
|
|
|
|
<span>Keyboard shortcuts</span>
|
|
|
|
</MenuItem>
|
2023-05-20 12:46:58 +03:00
|
|
|
<MenuItem
|
|
|
|
onClick={() => {
|
|
|
|
states.showShortcutsSettings = true;
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<Icon icon="shortcut" size="l" />{' '}
|
2023-10-19 15:19:55 +03:00
|
|
|
<span>Shortcuts / Columns…</span>
|
2023-05-20 12:46:58 +03:00
|
|
|
</MenuItem>
|
|
|
|
<MenuItem
|
|
|
|
onClick={() => {
|
|
|
|
states.showSettings = true;
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<Icon icon="gear" size="l" /> <span>Settings…</span>
|
2023-04-26 08:23:54 +03:00
|
|
|
</MenuItem>
|
|
|
|
</>
|
2023-06-20 08:30:26 +03:00
|
|
|
) : (
|
|
|
|
<>
|
|
|
|
<MenuDivider />
|
|
|
|
<MenuLink to="/login">
|
|
|
|
<Icon icon="user" size="l" /> <span>Log in</span>
|
|
|
|
</MenuLink>
|
2023-09-14 19:28:20 +03:00
|
|
|
<MenuItem
|
|
|
|
onClick={() => {
|
|
|
|
states.showSettings = true;
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<Icon icon="gear" size="l" /> <span>Settings…</span>
|
|
|
|
</MenuItem>
|
2023-06-20 08:30:26 +03:00
|
|
|
</>
|
2023-03-23 04:51:52 +03:00
|
|
|
)}
|
2023-05-20 12:46:58 +03:00
|
|
|
</section>
|
2023-04-26 08:23:54 +03:00
|
|
|
</ControlledMenu>
|
|
|
|
</>
|
2023-02-10 17:10:13 +03:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
export default NavMenu;
|