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;