import React, { ReactElement, useEffect, useRef } from 'react'; import { registerTabsListeners } from '../chrome/chromeTabsListeners'; import store from '../store/store'; import { loadPrefsFromDB } from '../db/prefsLoader'; import { shouldIgnore } from '../db/urlNormalizationPrefs'; import { initializeSearchIndex } from '../services/pageSearchIndex'; // Initialize on app start (both fire-and-forget; search falls back to full scan until ready) loadPrefsFromDB(); initializeSearchIndex(); import './Main.less'; import { useAppDispatch, useAppSelector } from '../store/hooks'; import { clearSelection, SEARCH_TAB_INDEX, selectSearchInput, selectTabIndex, setSearchInput, setTabIndex, } from '../store/uiSlice'; import Windows from './windows/Windows'; import { selectTabsCount, selectWindowsCount, updateWindowsAsync } from '../store/windowsSlice'; import { fetchStashCount, fetchFlagCount, fetchUntaggedCount, fetchAllCount, fetchPendingTodosCount } from '../db/pages'; import TodoView from './todo/TodoView'; import { useLiveQuery } from 'dexie-react-hooks'; import { getTabvanaWindowId } from '../db/prefs'; import SearchResult from './SearchResult'; import { faCartShopping, faEarth, faEye, faFlag, faQuestion } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import FlexView from './flexView/FlexView'; import '@fontsource/inter'; import { CssBaseline, CssVarsProvider, Input, ListItemDecorator, Sheet, Tab, TabList, TabPanel, Tabs, } from '@mui/joy'; import GlobalButtons from './GlobalButtons'; import { fetchTags, fetchTagsCount } from '../db/tags'; import { FlagTag, StashTag, SystemTags } from '../db/constants'; import { getSimilarityScaler, getTagSimilarityScaler } from '../db/prefs'; // Isolate state changes and liveQueries to avoid re-rendering of the whole component const TabsCount = (): ReactElement => { const tabvanaWindowId = useLiveQuery(getTabvanaWindowId); const windowsCount = useAppSelector((state) => selectWindowsCount(state, tabvanaWindowId)); const tabsCount = useAppSelector((state) => selectTabsCount(state, tabvanaWindowId)); return <>{windowsCount} / {tabsCount}; }; const TodoCount = (): ReactElement => <>{useLiveQuery(fetchPendingTodosCount)}; const TagsCount = (): ReactElement => <>{useLiveQuery(fetchTagsCount)}; const StashCount = (): ReactElement => <>{useLiveQuery(fetchStashCount)}; const FlagCount = (): ReactElement => <>{useLiveQuery(fetchFlagCount)}; const UntaggedCount = (): ReactElement => <>{useLiveQuery(fetchUntaggedCount)}; const AllCount = (): ReactElement => <>{useLiveQuery(fetchAllCount)}; const AllTagsPanel = (): ReactElement => { const allTags = useLiveQuery(() => fetchTags(SystemTags)); return ; }; interface SearchInputProps { searchInputRef: React.RefObject, }; const SearchInput = ({searchInputRef}: SearchInputProps): ReactElement => { const dispatch = useAppDispatch(); const searchInput = useAppSelector(selectSearchInput); return ( ) => dispatch(setSearchInput(event.target.value)) } value={searchInput} onFocus={() => dispatch(setTabIndex(7))} onKeyUp={(event) => { if (event.key === 'Escape') { dispatch(setSearchInput('')); } }} /> ); }; const Main = (): ReactElement => { // const log = useLog('Main'); // log('call'); const dispatch = useAppDispatch(); const tabIndex = useAppSelector(selectTabIndex); const searchInputRef = useRef(null); if (tabIndex === SEARCH_TAB_INDEX) { searchInputRef.current?.focus(); } useEffect(() => { registerTabsListeners(store, { shouldIgnoreUrl: shouldIgnore }); dispatch(updateWindowsAsync()); // Listen for re-normalization or other external updates const handleMessage = (message: any) => { if (message === 'renormalizeDone' || message === 'reloadPrefs') { dispatch(updateWindowsAsync()); } }; chrome.runtime.onMessage.addListener(handleMessage); return () => chrome.runtime.onMessage.removeListener(handleMessage); }, []); // refresh only on mount const similarityScaler = useLiveQuery(getSimilarityScaler); const tagSimilarityScaler = useLiveQuery(getTagSimilarityScaler); useEffect(() => { if (similarityScaler !== undefined) { document.documentElement.style.setProperty('--similarityScaler', '' + similarityScaler); } }, [similarityScaler]); useEffect(() => { if (tagSimilarityScaler !== undefined) { document.documentElement.style.setProperty('--tagSimilarityScaler', '' + tagSimilarityScaler); } }, [tagSimilarityScaler]); useEffect(() => { const keyDownHandler = (event: KeyboardEvent) => { if (event.key === 'Escape') { event.preventDefault(); dispatch(clearSelection()); } }; document.addEventListener('keydown', keyDownHandler); // 👇️ clean up event listener return () => { document.removeEventListener('keydown', keyDownHandler); }; }, []); return ( { dispatch(setTabIndex(index as number)); if (index === 7) { searchInputRef.current?.focus(); } }} className="fill flexContainer" sx={{ minWidth: 1300 }} > Todo () Tags () Open () Flagged () Stash () Untagged () All () ); }; export default Main;