import './shortcuts-settings.css';
import mem from 'mem';
import { useEffect, useState } from 'preact/hooks';
import { useSnapshot } from 'valtio';
import floatingButtonUrl from '../assets/floating-button.svg';
import multiColumnUrl from '../assets/multi-column.svg';
import tabMenuBarUrl from '../assets/tab-menu-bar.svg';
import { api } from '../utils/api';
import states from '../utils/states';
import AsyncText from './AsyncText';
import Icon from './icon';
import Modal from './modal';
const SHORTCUTS_LIMIT = 9;
const TYPES = [
'following',
'notifications',
'list',
'public',
// NOTE: Hide for now
// 'search', // Search on Mastodon ain't great
// 'account-statuses', // Need @acct search first
'bookmarks',
'favourites',
'hashtag',
'trending',
];
const TYPE_TEXT = {
following: 'Home / Following',
notifications: 'Notifications',
list: 'List',
public: 'Public (Local / Federated)',
search: 'Search',
'account-statuses': 'Account',
bookmarks: 'Bookmarks',
favourites: 'Favourites',
hashtag: 'Hashtag',
trending: 'Trending',
};
const TYPE_PARAMS = {
list: [
{
text: 'List ID',
name: 'id',
},
],
public: [
{
text: 'Local only',
name: 'local',
type: 'checkbox',
},
{
text: 'Instance',
name: 'instance',
type: 'text',
placeholder: 'e.g. mastodon.social',
},
],
trending: [
{
text: 'Instance',
name: 'instance',
type: 'text',
placeholder: 'e.g. mastodon.social',
},
],
search: [
{
text: 'Search term',
name: 'query',
type: 'text',
},
],
'account-statuses': [
{
text: '@',
name: 'id',
type: 'text',
placeholder: 'cheeaun@mastodon.social',
},
],
hashtag: [
{
text: '#',
name: 'hashtag',
type: 'text',
placeholder: 'e.g. PixelArt (Max 5, space-separated)',
pattern: '[^#]+',
},
],
};
export const SHORTCUTS_META = {
following: {
id: 'home',
title: (_, index) => (index === 0 ? 'Home' : 'Following'),
path: '/',
icon: 'home',
},
notifications: {
id: 'notifications',
title: 'Notifications',
path: '/notifications',
icon: 'notification',
},
list: {
id: 'list',
title: mem(
async ({ id }) => {
const list = await api().masto.v1.lists.fetch(id);
return list.title;
},
{
cacheKey: ([{ id }]) => id,
},
),
path: ({ id }) => `/l/${id}`,
icon: 'list',
},
public: {
id: 'public',
title: ({ local, instance }) =>
`${local ? 'Local' : 'Federated'} (${instance})`,
path: ({ local, instance }) => `/${instance}/p${local ? '/l' : ''}`,
icon: ({ local }) => (local ? 'group' : 'earth'),
},
trending: {
id: 'trending',
title: 'Trending',
path: ({ instance }) => `/${instance}/trending`,
icon: 'chart',
},
search: {
id: 'search',
title: ({ query }) => query,
path: ({ query }) => `/search?q=${query}`,
icon: 'search',
},
'account-statuses': {
id: 'account-statuses',
title: mem(
async ({ id }) => {
const account = await api().masto.v1.accounts.fetch(id);
return account.username || account.acct || account.displayName;
},
{
cacheKey: ([{ id }]) => id,
},
),
path: ({ id }) => `/a/${id}`,
icon: 'user',
},
bookmarks: {
id: 'bookmarks',
title: 'Bookmarks',
path: '/b',
icon: 'bookmark',
},
favourites: {
id: 'favourites',
title: 'Favourites',
path: '/f',
icon: 'heart',
},
hashtag: {
id: 'hashtag',
title: ({ hashtag }) => hashtag,
path: ({ hashtag }) => `/t/${hashtag.split(/\s+/).join('+')}`,
icon: 'hashtag',
},
};
function ShortcutsSettings() {
const snapStates = useSnapshot(states);
const { masto } = api();
const { shortcuts } = snapStates;
const [lists, setLists] = useState([]);
const [followedHashtags, setFollowedHashtags] = useState([]);
const [showForm, setShowForm] = useState(false);
useEffect(() => {
(async () => {
try {
const lists = await masto.v1.lists.list();
setLists(lists);
} catch (e) {
console.error(e);
}
})();
(async () => {
try {
const iterator = masto.v1.followedTags.list();
const tags = [];
do {
const { value, done } = await iterator.next();
if (done || value?.length === 0) break;
tags.push(...value);
} while (true);
setFollowedHashtags(tags);
} catch (e) {
console.error(e);
}
})();
}, []);
return (
Specify a list of shortcuts that'll appear as:
{[
{
value: 'float-button',
label: 'Floating button',
imgURL: floatingButtonUrl,
},
{
value: 'tab-menu-bar',
label: 'Tab/Menu bar',
imgURL: tabMenuBarUrl,
},
{
value: 'multi-column',
label: 'Multi-column',
imgURL: multiColumnUrl,
},
].map(({ value, label, imgURL }) => (
))}
{/* */}
{/*
Experimental Multi-column mode
*/}
{shortcuts.length > 0 ? (
{shortcuts.map((shortcut, i) => {
const key = i + Object.values(shortcut);
const { type } = shortcut;
if (!SHORTCUTS_META[type]) return null;
let { icon, title } = SHORTCUTS_META[type];
if (typeof title === 'function') {
title = title(shortcut, i);
}
if (typeof icon === 'function') {
icon = icon(shortcut, i);
}
return (
-
{title}
);
})}
) : (
No shortcuts yet. Add one from the form below.
)}
{shortcuts.length >= SHORTCUTS_LIMIT &&
`Max ${SHORTCUTS_LIMIT} shortcuts`}
{showForm && (
{
if (e.target === e.currentTarget) {
setShowForm(false);
}
}}
>
= SHORTCUTS_LIMIT}
lists={lists}
followedHashtags={followedHashtags}
onSubmit={(data) => {
console.log('onSubmit', data);
states.shortcuts.push(data);
}}
onClose={() => setShowForm(false)}
/>
)}
);
}
function ShortcutForm({
type,
lists,
followedHashtags,
onSubmit,
disabled,
onClose = () => {},
}) {
const [currentType, setCurrentType] = useState(type);
return (
);
}
export default ShortcutsSettings;