import { ReactNode, useEffect, useMemo, useRef } from 'react' import { StyledText } from '../../elements/StyledText' import { Box, HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives' import { Button } from '../../elements/Button' import { Circle, X } from '@phosphor-icons/react' import { Subscription, SubscriptionType, useGetSubscriptionsQuery, } from '../../../lib/networking/queries/useGetSubscriptionsQuery' import { Label } from '../../../lib/networking/fragments/labelFragment' import { theme } from '../../tokens/stitches.config' import { useRegisterActions } from 'kbar' import { LogoBox } from '../../elements/LogoBox' import { usePersistedState } from '../../../lib/hooks/usePersistedState' import { SavedSearch } from '../../../lib/networking/fragments/savedSearchFragment' import { ToggleCaretDownIcon } from '../../elements/icons/ToggleCaretDownIcon' import Link from 'next/link' import { ToggleCaretRightIcon } from '../../elements/icons/ToggleCaretRightIcon' import { NavMenuFooter } from './Footer' import { escapeQuotes } from '../../../utils/helper' import { useGetLabels } from '../../../lib/networking/labels/useLabels' import { useGetSavedSearches } from '../../../lib/networking/savedsearches/useSavedSearches' export const LIBRARY_LEFT_MENU_WIDTH = '275px' type LibraryFilterMenuProps = { setShowAddLinkModal: (show: boolean) => void searchTerm: string | undefined applySearchQuery: (searchTerm: string) => void showFilterMenu: boolean setShowFilterMenu: (show: boolean) => void } export function LibraryLegacyMenu(props: LibraryFilterMenuProps): JSX.Element { const [labels, setLabels] = usePersistedState({ key: 'menu-labels', isSessionStorage: false, initialValue: [], }) const [savedSearches, setSavedSearches] = usePersistedState({ key: 'menu-searches', isSessionStorage: false, initialValue: [], }) const [subscriptions, setSubscriptions] = usePersistedState({ key: 'menu-subscriptions', isSessionStorage: false, initialValue: [], }) const labelsResponse = useGetLabels() const searchesResponse = useGetSavedSearches() const subscriptionsResponse = useGetSubscriptionsQuery() useEffect(() => { if ( !labelsResponse.error && !labelsResponse.isLoading && labelsResponse.data ) { setLabels(labelsResponse.data) } }, [setLabels, labelsResponse]) useEffect(() => { if ( !subscriptionsResponse.error && !subscriptionsResponse.isLoading && subscriptionsResponse.subscriptions ) { setSubscriptions(subscriptionsResponse.subscriptions) } }, [setSubscriptions, subscriptionsResponse]) useEffect(() => { if ( !searchesResponse.error && !searchesResponse.isLoading && searchesResponse?.data ) { setSavedSearches(searchesResponse.data) } }, [setSavedSearches, searchesResponse]) return ( <> {/* This spacer pushes library content to the right of the fixed left side menu. */} ) } function SavedSearches( props: LibraryFilterMenuProps & { savedSearches: SavedSearch[] | undefined } ): JSX.Element { const sortedSearches = useMemo(() => { return props.savedSearches ?.filter((it) => it.visible) ?.sort( (left: SavedSearch, right: SavedSearch) => left.position - right.position ) }, [props.savedSearches]) useRegisterActions( (sortedSearches ?? []).map((item, idx) => { const key = String(idx + 1) return { id: `saved_search_${key}`, name: item.name, shortcut: [key], section: 'Saved Searches', keywords: '?' + item.name, perform: () => { props.applySearchQuery(item.filter) }, } }), [props.savedSearches] ) const [collapsed, setCollapsed] = usePersistedState({ key: `--saved-searches-collapsed`, initialValue: false, }) return ( {!collapsed && sortedSearches && sortedSearches?.map((item) => ( ))} {!collapsed && sortedSearches !== undefined && ( )} ) } function Subscriptions( props: LibraryFilterMenuProps & { subscriptions: Subscription[] | undefined } ): JSX.Element { const [collapsed, setCollapsed] = usePersistedState({ key: `--subscriptions-collapsed`, initialValue: false, }) const sortedSubscriptions = useMemo(() => { if (!props.subscriptions) { return [] } return props.subscriptions .filter((s) => s.status == 'ACTIVE') .sort((a, b) => a.name.localeCompare(b.name)) }, [props.subscriptions]) useRegisterActions( (sortedSubscriptions ?? []).map((subscription, idx) => { const key = String(idx + 1) const name = subscription.name return { id: `subscription_${key}`, section: 'Subscriptions', name: name, keywords: '*' + name, perform: () => { props.applySearchQuery(`subscription:\"${escapeQuotes(name)}\"`) }, } }), [sortedSubscriptions] ) return ( {!collapsed ? ( <> {(sortedSubscriptions ?? []).map((item) => { switch (item.type) { case SubscriptionType.NEWSLETTER: return ( ) case SubscriptionType.RSS: return ( ) } })} ) : ( )} ) } function Labels( props: LibraryFilterMenuProps & { labels: Label[] } ): JSX.Element { const [collapsed, setCollapsed] = usePersistedState({ key: `--labels-collapsed`, initialValue: false, }) const sortedLabels = useMemo(() => { return props.labels.sort((left: Label, right: Label) => left.name.localeCompare(right.name) ) }, [props.labels]) return ( {!collapsed && ( <> {sortedLabels.map((item) => { return })} )} ) } type MenuPanelProps = { title: string children: ReactNode editFunc?: () => void editTitle?: string hideBottomBorder?: boolean collapsed: boolean setCollapsed: (collapsed: boolean) => void } function MenuPanel(props: MenuPanelProps): JSX.Element { return ( {props.title} {props.children} ) } type FilterButtonProps = { text: string filterTerm: string searchTerm: string | undefined applySearchQuery: (searchTerm: string) => void setShowFilterMenu: (show: boolean) => void } function FilterButton(props: FilterButtonProps): JSX.Element { const isInboxFilter = (filter: string) => { return filter === '' || filter === 'in:inbox' } const selected = useMemo(() => { if (isInboxFilter(props.filterTerm) && !props.searchTerm) { return true } return props.searchTerm === props.filterTerm }, [props.searchTerm, props.filterTerm]) return ( { props.applySearchQuery(props.filterTerm) props.setShowFilterMenu(false) e.preventDefault() }} > {props.text} ) } type LabelButtonProps = { label: Label searchTerm: string | undefined applySearchQuery: (searchTerm: string) => void } function LabelButton(props: LabelButtonProps): JSX.Element { const labelId = `checkbox-label-${props.label.id}` const checkboxRef = useRef(null) const state = useMemo(() => { const term = props.searchTerm ?? '' if (term.indexOf(`label:\"${escapeQuotes(props.label.name)}\"`) >= 0) { return 'on' } return 'off' }, [props.searchTerm, props.label]) return ( { const escapedName = escapeQuotes(props.label.name) if (e.target.checked) { props.applySearchQuery( `${props.searchTerm ?? ''} label:\"${escapedName}\"` ) } else { const query = props.searchTerm?.replace(`label:\"${escapedName}\"`, '') ?? '' props.applySearchQuery(query) } }} /> ) } type EditButtonProps = { title: string destination: string } function EditButton(props: EditButtonProps): JSX.Element { return ( {props.title} ) }