2023-02-11 03:37:42 +03:00
|
|
|
import './app.css';
|
|
|
|
|
2023-10-21 07:26:28 +03:00
|
|
|
import debounce from 'just-debounce-it';
|
2023-01-20 20:04:27 +03:00
|
|
|
import {
|
|
|
|
useEffect,
|
|
|
|
useLayoutEffect,
|
|
|
|
useMemo,
|
|
|
|
useRef,
|
|
|
|
useState,
|
|
|
|
} from 'preact/hooks';
|
2023-09-12 06:27:54 +03:00
|
|
|
import { matchPath, Route, Routes, useLocation } from 'react-router-dom';
|
2023-04-25 15:41:08 +03:00
|
|
|
import 'swiped-events';
|
2023-10-21 12:40:03 +03:00
|
|
|
import { subscribe, useSnapshot } from 'valtio';
|
2022-12-10 12:14:48 +03:00
|
|
|
|
2023-09-02 13:19:09 +03:00
|
|
|
import BackgroundService from './components/background-service';
|
2023-09-05 13:49:16 +03:00
|
|
|
import ComposeButton from './components/compose-button';
|
|
|
|
import { ICONS } from './components/icon';
|
2023-09-06 17:54:05 +03:00
|
|
|
import KeyboardShortcutsHelp from './components/keyboard-shortcuts-help';
|
2022-12-10 12:14:48 +03:00
|
|
|
import Loader from './components/loader';
|
2023-09-12 06:27:54 +03:00
|
|
|
import Modals from './components/modals';
|
2023-09-02 13:19:09 +03:00
|
|
|
import NotificationService from './components/notification-service';
|
2023-09-04 09:49:39 +03:00
|
|
|
import SearchCommand from './components/search-command';
|
2023-02-16 12:51:54 +03:00
|
|
|
import Shortcuts from './components/shortcuts';
|
2023-01-28 13:52:18 +03:00
|
|
|
import NotFound from './pages/404';
|
2023-01-29 18:37:13 +03:00
|
|
|
import AccountStatuses from './pages/account-statuses';
|
2023-01-20 19:23:59 +03:00
|
|
|
import Bookmarks from './pages/bookmarks';
|
2023-01-28 13:52:18 +03:00
|
|
|
import Favourites from './pages/favourites';
|
2023-02-11 11:48:47 +03:00
|
|
|
import FollowedHashtags from './pages/followed-hashtags';
|
2023-02-03 16:08:08 +03:00
|
|
|
import Following from './pages/following';
|
2023-02-18 15:48:24 +03:00
|
|
|
import Hashtag from './pages/hashtag';
|
2022-12-10 12:14:48 +03:00
|
|
|
import Home from './pages/home';
|
2023-09-02 13:19:09 +03:00
|
|
|
import HttpRoute from './pages/http-route';
|
2023-02-10 19:05:18 +03:00
|
|
|
import List from './pages/list';
|
2023-01-28 13:52:18 +03:00
|
|
|
import Lists from './pages/lists';
|
2022-12-10 12:14:48 +03:00
|
|
|
import Login from './pages/login';
|
2023-04-06 14:32:26 +03:00
|
|
|
import Mentions from './pages/mentions';
|
2022-12-10 12:14:48 +03:00
|
|
|
import Notifications from './pages/notifications';
|
2023-01-28 13:52:18 +03:00
|
|
|
import Public from './pages/public';
|
2023-02-10 17:10:13 +03:00
|
|
|
import Search from './pages/search';
|
2023-09-02 13:19:09 +03:00
|
|
|
import StatusRoute from './pages/status-route';
|
2023-04-05 20:14:38 +03:00
|
|
|
import Trending from './pages/trending';
|
2022-12-10 12:14:48 +03:00
|
|
|
import Welcome from './pages/welcome';
|
2023-02-09 18:59:57 +03:00
|
|
|
import {
|
|
|
|
api,
|
|
|
|
initAccount,
|
|
|
|
initClient,
|
|
|
|
initInstance,
|
|
|
|
initPreferences,
|
|
|
|
} from './utils/api';
|
2022-12-10 12:14:48 +03:00
|
|
|
import { getAccessToken } from './utils/auth';
|
2023-09-12 06:27:54 +03:00
|
|
|
import focusDeck from './utils/focus-deck';
|
2023-09-02 13:19:09 +03:00
|
|
|
import states, { initStates } from './utils/states';
|
2022-12-10 12:14:48 +03:00
|
|
|
import store from './utils/store';
|
2023-09-02 13:19:09 +03:00
|
|
|
import { getCurrentAccount } from './utils/store-utils';
|
2023-09-01 10:40:00 +03:00
|
|
|
import './utils/toast-alert';
|
2022-12-10 12:14:48 +03:00
|
|
|
|
2022-12-13 15:42:09 +03:00
|
|
|
window.__STATES__ = states;
|
2023-10-28 06:07:35 +03:00
|
|
|
window.__STATES_STATS__ = () => {
|
|
|
|
const keys = [
|
|
|
|
'statuses',
|
|
|
|
'accounts',
|
|
|
|
'spoilers',
|
|
|
|
'unfurledLinks',
|
|
|
|
'statusQuotes',
|
|
|
|
];
|
|
|
|
const counts = {};
|
|
|
|
keys.forEach((key) => {
|
|
|
|
counts[key] = Object.keys(states[key]).length;
|
|
|
|
});
|
|
|
|
console.warn('STATE stats', counts);
|
|
|
|
};
|
2022-12-10 12:14:48 +03:00
|
|
|
|
2023-08-13 12:15:49 +03:00
|
|
|
// Preload icons
|
|
|
|
// There's probably a better way to do this
|
|
|
|
// Related: https://github.com/vitejs/vite/issues/10600
|
|
|
|
setTimeout(() => {
|
|
|
|
for (const icon in ICONS) {
|
|
|
|
if (Array.isArray(ICONS[icon])) {
|
|
|
|
ICONS[icon][0]?.();
|
|
|
|
} else {
|
|
|
|
ICONS[icon]?.();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}, 5000);
|
|
|
|
|
2023-10-21 07:26:28 +03:00
|
|
|
(() => {
|
2023-10-30 19:38:59 +03:00
|
|
|
window.__IDLE__ = true;
|
2023-10-21 07:26:28 +03:00
|
|
|
const nonIdleEvents = [
|
|
|
|
'mousemove',
|
|
|
|
'mousedown',
|
|
|
|
'resize',
|
|
|
|
'keydown',
|
|
|
|
'touchstart',
|
|
|
|
'pointerdown',
|
|
|
|
'pointermove',
|
|
|
|
'wheel',
|
|
|
|
];
|
2023-10-30 19:38:59 +03:00
|
|
|
const setIdle = () => {
|
2023-10-21 07:26:28 +03:00
|
|
|
window.__IDLE__ = true;
|
2023-10-30 19:38:59 +03:00
|
|
|
};
|
|
|
|
const IDLE_TIME = 3_000; // 3 seconds
|
|
|
|
const debouncedSetIdle = debounce(setIdle, IDLE_TIME);
|
2023-10-21 07:26:28 +03:00
|
|
|
const onNonIdle = () => {
|
|
|
|
window.__IDLE__ = false;
|
2023-10-30 19:38:59 +03:00
|
|
|
debouncedSetIdle();
|
2023-10-21 07:26:28 +03:00
|
|
|
};
|
|
|
|
nonIdleEvents.forEach((event) => {
|
|
|
|
window.addEventListener(event, onNonIdle, {
|
|
|
|
passive: true,
|
|
|
|
capture: true,
|
|
|
|
});
|
|
|
|
});
|
2023-10-30 19:38:59 +03:00
|
|
|
window.addEventListener('blur', setIdle, {
|
|
|
|
passive: true,
|
|
|
|
});
|
|
|
|
// When cursor leaves the window, set idle
|
|
|
|
document.documentElement.addEventListener(
|
|
|
|
'mouseleave',
|
|
|
|
(e) => {
|
|
|
|
if (!e.relatedTarget && !e.toElement) {
|
|
|
|
setIdle();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
passive: true,
|
|
|
|
},
|
|
|
|
);
|
2023-10-21 07:26:28 +03:00
|
|
|
// document.addEventListener(
|
|
|
|
// 'visibilitychange',
|
|
|
|
// () => {
|
|
|
|
// if (document.visibilityState === 'visible') {
|
|
|
|
// onNonIdle();
|
|
|
|
// }
|
|
|
|
// },
|
|
|
|
// {
|
|
|
|
// passive: true,
|
|
|
|
// },
|
|
|
|
// );
|
|
|
|
})();
|
|
|
|
|
2023-10-21 12:40:03 +03:00
|
|
|
subscribe(states, (changes) => {
|
|
|
|
for (const [action, path, value, prevValue] of changes) {
|
|
|
|
// Change #app dataset based on settings.shortcutsViewMode
|
|
|
|
if (path.join('.') === 'settings.shortcutsViewMode') {
|
|
|
|
const $app = document.getElementById('app');
|
|
|
|
if ($app) {
|
|
|
|
$app.dataset.shortcutsViewMode = states.shortcuts?.length ? value : '';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add/Remove cloak class to body
|
|
|
|
if (path.join('.') === 'settings.cloakMode') {
|
|
|
|
const $body = document.body;
|
|
|
|
$body.classList.toggle('cloak', value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2022-12-31 20:46:08 +03:00
|
|
|
function App() {
|
2022-12-10 12:14:48 +03:00
|
|
|
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
2022-12-21 15:34:24 +03:00
|
|
|
const [uiState, setUIState] = useState('loading');
|
2022-12-10 12:14:48 +03:00
|
|
|
|
|
|
|
useLayoutEffect(() => {
|
|
|
|
const theme = store.local.get('theme');
|
|
|
|
if (theme) {
|
|
|
|
document.documentElement.classList.add(`is-${theme}`);
|
|
|
|
document
|
|
|
|
.querySelector('meta[name="color-scheme"]')
|
2023-02-05 19:17:19 +03:00
|
|
|
.setAttribute('content', theme === 'auto' ? 'dark light' : theme);
|
2022-12-10 12:14:48 +03:00
|
|
|
}
|
2023-03-08 12:17:23 +03:00
|
|
|
const textSize = store.local.get('textSize');
|
|
|
|
if (textSize) {
|
|
|
|
document.documentElement.style.setProperty(
|
|
|
|
'--text-size',
|
|
|
|
`${textSize}px`,
|
|
|
|
);
|
|
|
|
}
|
2022-12-10 12:14:48 +03:00
|
|
|
}, []);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
const instanceURL = store.local.get('instanceURL');
|
2023-05-20 05:08:41 +03:00
|
|
|
const code = decodeURIComponent(
|
|
|
|
(window.location.search.match(/code=([^&]+)/) || [, ''])[1],
|
|
|
|
);
|
2022-12-10 12:14:48 +03:00
|
|
|
|
|
|
|
if (code) {
|
|
|
|
console.log({ code });
|
|
|
|
// Clear the code from the URL
|
2023-08-30 12:46:22 +03:00
|
|
|
window.history.replaceState({}, document.title, location.pathname || '/');
|
2022-12-10 12:14:48 +03:00
|
|
|
|
|
|
|
const clientID = store.session.get('clientID');
|
|
|
|
const clientSecret = store.session.get('clientSecret');
|
2023-09-01 10:40:00 +03:00
|
|
|
const vapidKey = store.session.get('vapidKey');
|
2022-12-10 12:14:48 +03:00
|
|
|
|
|
|
|
(async () => {
|
|
|
|
setUIState('loading');
|
2023-02-05 19:17:19 +03:00
|
|
|
const { access_token: accessToken } = await getAccessToken({
|
2022-12-10 12:14:48 +03:00
|
|
|
instanceURL,
|
|
|
|
client_id: clientID,
|
|
|
|
client_secret: clientSecret,
|
|
|
|
code,
|
|
|
|
});
|
|
|
|
|
2023-10-12 07:48:09 +03:00
|
|
|
const client = initClient({ instance: instanceURL, accessToken });
|
2023-02-05 19:17:19 +03:00
|
|
|
await Promise.allSettled([
|
2023-10-12 07:48:09 +03:00
|
|
|
initInstance(client, instanceURL),
|
|
|
|
initAccount(client, instanceURL, accessToken, vapidKey),
|
2023-02-05 19:17:19 +03:00
|
|
|
]);
|
2023-08-30 12:42:33 +03:00
|
|
|
initStates();
|
2023-10-12 07:48:09 +03:00
|
|
|
initPreferences(client);
|
2022-12-10 12:14:48 +03:00
|
|
|
|
|
|
|
setIsLoggedIn(true);
|
|
|
|
setUIState('default');
|
|
|
|
})();
|
2022-12-21 15:34:24 +03:00
|
|
|
} else {
|
2023-10-11 14:07:36 +03:00
|
|
|
window.__IGNORE_GET_ACCOUNT_ERROR__ = true;
|
2023-02-05 19:17:19 +03:00
|
|
|
const account = getCurrentAccount();
|
|
|
|
if (account) {
|
|
|
|
store.session.set('currentAccount', account.info.id);
|
2023-10-12 07:48:09 +03:00
|
|
|
const { client } = api({ account });
|
|
|
|
const { instance } = client;
|
|
|
|
// console.log('masto', masto);
|
2023-10-11 14:07:36 +03:00
|
|
|
initStates();
|
2023-10-12 07:48:09 +03:00
|
|
|
initPreferences(client);
|
2023-02-10 06:35:06 +03:00
|
|
|
setUIState('loading');
|
2023-02-07 19:31:46 +03:00
|
|
|
(async () => {
|
2023-02-10 06:35:06 +03:00
|
|
|
try {
|
2023-10-12 07:48:09 +03:00
|
|
|
await initInstance(client, instance);
|
2023-02-10 06:35:06 +03:00
|
|
|
} catch (e) {
|
|
|
|
} finally {
|
|
|
|
setIsLoggedIn(true);
|
|
|
|
setUIState('default');
|
|
|
|
}
|
2023-02-07 19:31:46 +03:00
|
|
|
})();
|
2023-02-10 07:29:07 +03:00
|
|
|
} else {
|
|
|
|
setUIState('default');
|
2023-02-05 19:17:19 +03:00
|
|
|
}
|
2022-12-10 12:14:48 +03:00
|
|
|
}
|
|
|
|
}, []);
|
|
|
|
|
2023-01-20 19:23:59 +03:00
|
|
|
let location = useLocation();
|
2023-10-22 14:25:22 +03:00
|
|
|
states.currentLocation = location.pathname;
|
|
|
|
// useLayoutEffect(() => {
|
|
|
|
// states.currentLocation = location.pathname;
|
|
|
|
// }, [location.pathname]);
|
2023-01-27 15:54:18 +03:00
|
|
|
|
2023-09-07 13:44:12 +03:00
|
|
|
useEffect(focusDeck, [location, isLoggedIn]);
|
2022-12-10 12:14:48 +03:00
|
|
|
|
2023-10-21 12:40:03 +03:00
|
|
|
if (/\/https?:/.test(location.pathname)) {
|
|
|
|
return <HttpRoute />;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<>
|
|
|
|
<PrimaryRoutes isLoggedIn={isLoggedIn} loading={uiState === 'loading'} />
|
|
|
|
<SecondaryRoutes isLoggedIn={isLoggedIn} />
|
|
|
|
{uiState === 'default' && (
|
|
|
|
<Routes>
|
|
|
|
<Route path="/:instance?/s/:id" element={<StatusRoute />} />
|
|
|
|
</Routes>
|
|
|
|
)}
|
|
|
|
{isLoggedIn && <ComposeButton />}
|
|
|
|
{isLoggedIn && <Shortcuts />}
|
|
|
|
<Modals />
|
|
|
|
{isLoggedIn && <NotificationService />}
|
|
|
|
<BackgroundService isLoggedIn={isLoggedIn} />
|
|
|
|
{uiState !== 'loading' && <SearchCommand onClose={focusDeck} />}
|
|
|
|
<KeyboardShortcutsHelp />
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function PrimaryRoutes({ isLoggedIn, loading }) {
|
|
|
|
const location = useLocation();
|
|
|
|
const nonRootLocation = useMemo(() => {
|
|
|
|
const { pathname } = location;
|
|
|
|
return !/^\/(login|welcome)/.test(pathname);
|
|
|
|
}, [location]);
|
|
|
|
|
|
|
|
return (
|
|
|
|
<Routes location={nonRootLocation || location}>
|
|
|
|
<Route
|
|
|
|
path="/"
|
|
|
|
element={
|
|
|
|
isLoggedIn ? (
|
|
|
|
<Home />
|
|
|
|
) : loading ? (
|
|
|
|
<Loader id="loader-root" />
|
|
|
|
) : (
|
|
|
|
<Welcome />
|
|
|
|
)
|
|
|
|
}
|
|
|
|
/>
|
|
|
|
<Route path="/login" element={<Login />} />
|
|
|
|
<Route path="/welcome" element={<Welcome />} />
|
|
|
|
</Routes>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-10-23 11:23:33 +03:00
|
|
|
function getPrevLocation() {
|
|
|
|
return states.prevLocation || null;
|
|
|
|
}
|
2023-10-21 12:40:03 +03:00
|
|
|
function SecondaryRoutes({ isLoggedIn }) {
|
2023-10-23 11:23:33 +03:00
|
|
|
// const snapStates = useSnapshot(states);
|
2023-10-21 12:40:03 +03:00
|
|
|
const location = useLocation();
|
2023-10-23 11:23:33 +03:00
|
|
|
// const prevLocation = snapStates.prevLocation;
|
|
|
|
const backgroundLocation = useRef(getPrevLocation());
|
2023-10-21 12:40:03 +03:00
|
|
|
|
2023-09-10 10:30:04 +03:00
|
|
|
const isModalPage = useMemo(() => {
|
|
|
|
return (
|
|
|
|
matchPath('/:instance/s/:id', location.pathname) ||
|
|
|
|
matchPath('/s/:id', location.pathname)
|
|
|
|
);
|
|
|
|
}, [location.pathname, matchPath]);
|
2023-01-21 05:08:55 +03:00
|
|
|
if (isModalPage) {
|
2023-10-23 11:23:33 +03:00
|
|
|
if (!backgroundLocation.current)
|
|
|
|
backgroundLocation.current = getPrevLocation();
|
2023-01-21 05:08:55 +03:00
|
|
|
} else {
|
|
|
|
backgroundLocation.current = null;
|
|
|
|
}
|
|
|
|
console.debug({
|
|
|
|
backgroundLocation: backgroundLocation.current,
|
|
|
|
location,
|
|
|
|
});
|
2023-01-20 19:23:59 +03:00
|
|
|
|
2022-12-10 12:14:48 +03:00
|
|
|
return (
|
2023-10-21 12:40:03 +03:00
|
|
|
<Routes location={backgroundLocation.current || location}>
|
|
|
|
{isLoggedIn && (
|
|
|
|
<>
|
2023-01-20 19:23:59 +03:00
|
|
|
<Route path="/notifications" element={<Notifications />} />
|
2023-10-21 12:40:03 +03:00
|
|
|
<Route path="/mentions" element={<Mentions />} />
|
|
|
|
<Route path="/following" element={<Following />} />
|
|
|
|
<Route path="/b" element={<Bookmarks />} />
|
|
|
|
<Route path="/f" element={<Favourites />} />
|
2023-02-10 19:05:18 +03:00
|
|
|
<Route path="/l">
|
|
|
|
<Route index element={<Lists />} />
|
|
|
|
<Route path=":id" element={<List />} />
|
|
|
|
</Route>
|
2023-10-21 12:40:03 +03:00
|
|
|
<Route path="/ft" element={<FollowedHashtags />} />
|
|
|
|
</>
|
2023-04-18 14:47:21 +03:00
|
|
|
)}
|
2023-10-21 12:40:03 +03:00
|
|
|
<Route path="/:instance?/t/:hashtag" element={<Hashtag />} />
|
|
|
|
<Route path="/:instance?/a/:id" element={<AccountStatuses />} />
|
|
|
|
<Route path="/:instance?/p">
|
|
|
|
<Route index element={<Public />} />
|
|
|
|
<Route path="l" element={<Public local />} />
|
|
|
|
</Route>
|
|
|
|
<Route path="/:instance?/trending" element={<Trending />} />
|
|
|
|
<Route path="/:instance?/search" element={<Search />} />
|
|
|
|
{/* <Route path="/:anything" element={<NotFound />} /> */}
|
|
|
|
</Routes>
|
2022-12-10 12:14:48 +03:00
|
|
|
);
|
|
|
|
}
|
2022-12-31 20:46:08 +03:00
|
|
|
|
|
|
|
export { App };
|