From 4295e8228d30f3525db1342015b5207b2f90e5fc Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Thu, 6 Jun 2024 17:10:55 +0800 Subject: [PATCH] Start adding allotment cleaning up navigation --- .../patterns/LibraryCards/LibraryGridCard.tsx | 2 +- .../patterns/LibraryCards/LibraryListCard.tsx | 26 +- .../templates/{homeFeed => }/AddLinkModal.tsx | 28 +- .../components/templates/NavigationLayout.tsx | 76 +- .../discoverFeed/DiscoverContainer.tsx | 46 +- .../templates/homeFeed/HomeFeedContainer.tsx | 6 +- .../templates/homeFeed/LibraryHeader.tsx | 2 +- .../homeFeed/MultiSelectControls.tsx | 29 +- .../templates/library/LibraryContainer.tsx | 1275 +++++++++++++++++ .../templates/library/LibraryHeader.tsx | 418 ++++++ .../library/LibraryItemsContainer.tsx | 23 + .../templates/library/LibrarySideBar.tsx | 62 + .../templates/navMenu/NavigationMenu.tsx | 21 +- packages/web/package.json | 1 + packages/web/pages/justread/index.tsx | 2 +- packages/web/pages/library/index.tsx | 35 +- yarn.lock | 46 +- 17 files changed, 1971 insertions(+), 127 deletions(-) rename packages/web/components/templates/{homeFeed => }/AddLinkModal.tsx (95%) create mode 100644 packages/web/components/templates/library/LibraryContainer.tsx create mode 100644 packages/web/components/templates/library/LibraryHeader.tsx create mode 100644 packages/web/components/templates/library/LibraryItemsContainer.tsx create mode 100644 packages/web/components/templates/library/LibrarySideBar.tsx diff --git a/packages/web/components/patterns/LibraryCards/LibraryGridCard.tsx b/packages/web/components/patterns/LibraryCards/LibraryGridCard.tsx index c824c82e9..9c260b5cc 100644 --- a/packages/web/components/patterns/LibraryCards/LibraryGridCard.tsx +++ b/packages/web/components/patterns/LibraryCards/LibraryGridCard.tsx @@ -64,7 +64,7 @@ export function LibraryGridCard(props: LinkedItemCardProps): JSX.Element { css={{ pl: '0px', padding: '0px', - width: '293px', + width: '100%', height: '100%', minHeight: '270px', background: 'white', diff --git a/packages/web/components/patterns/LibraryCards/LibraryListCard.tsx b/packages/web/components/patterns/LibraryCards/LibraryListCard.tsx index 00374e9f4..007b71919 100644 --- a/packages/web/components/patterns/LibraryCards/LibraryListCard.tsx +++ b/packages/web/components/patterns/LibraryCards/LibraryListCard.tsx @@ -68,19 +68,19 @@ export function LibraryListCard(props: LinkedItemCardProps): JSX.Element { borderStyle: 'none', borderBottom: 'none', borderRadius: '6px', - width: '100vw', - '@media (min-width: 768px)': { - width: `calc(100vw - ${LIBRARY_LEFT_MENU_WIDTH})`, - }, - '@media (min-width: 930px)': { - width: '580px', - }, - '@media (min-width: 1280px)': { - width: '890px', - }, - '@media (min-width: 1600px)': { - width: '1200px', - }, + width: '100%', + // '@media (min-width: 768px)': { + // width: `calc(100vw - ${LIBRARY_LEFT_MENU_WIDTH})`, + // }, + // '@media (min-width: 930px)': { + // width: '580px', + // }, + // '@media (min-width: 1280px)': { + // width: '890px', + // }, + // '@media (min-width: 1600px)': { + // width: '1200px', + // }, '@media (max-width: 930px)': { borderRadius: '0px', }, diff --git a/packages/web/components/templates/homeFeed/AddLinkModal.tsx b/packages/web/components/templates/AddLinkModal.tsx similarity index 95% rename from packages/web/components/templates/homeFeed/AddLinkModal.tsx rename to packages/web/components/templates/AddLinkModal.tsx index a4a6b0dce..063406ebb 100644 --- a/packages/web/components/templates/homeFeed/AddLinkModal.tsx +++ b/packages/web/components/templates/AddLinkModal.tsx @@ -1,17 +1,17 @@ import { useCallback, useRef, useState } from 'react' import * as Progress from '@radix-ui/react-progress' import { File, Info } from 'phosphor-react' -import { locale, timeZone } from '../../../lib/dateFormatting' -import { showErrorToast, showSuccessToast } from '../../../lib/toastHelpers' -import { Button } from '../../elements/Button' -import { FormInput } from '../../elements/FormElements' -import { Box, HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives' +import { locale, timeZone } from '../../lib/dateFormatting' +import { showErrorToast, showSuccessToast } from '../../lib/toastHelpers' +import { Button } from '../elements/Button' +import { FormInput } from '../elements/FormElements' +import { Box, HStack, SpanBox, VStack } from '../elements/LayoutPrimitives' import { ModalContent, ModalOverlay, ModalRoot, -} from '../../elements/ModalPrimitives' -import { CloseButton } from '../../elements/CloseButton' +} from '../elements/ModalPrimitives' +import { CloseButton } from '../elements/CloseButton' import { styled } from '@stitches/react' import Dropzone, { Accept, @@ -20,17 +20,17 @@ import Dropzone, { FileRejection, } from 'react-dropzone' import { v4 as uuidv4 } from 'uuid' -import { validateCsvFile } from '../../../utils/csvValidator' +import { validateCsvFile } from '../../utils/csvValidator' import { uploadImportFileRequestMutation, UploadImportFileType, -} from '../../../lib/networking/mutations/uploadImportFileMutation' -import { uploadFileRequestMutation } from '../../../lib/networking/mutations/uploadFileMutation' +} from '../../lib/networking/mutations/uploadImportFileMutation' +import { uploadFileRequestMutation } from '../../lib/networking/mutations/uploadFileMutation' import axios from 'axios' -import { theme } from '../../tokens/stitches.config' -import { formatMessage } from '../../../locales/en/messages' -import { subscribeMutation } from '../../../lib/networking/mutations/subscribeMutation' -import { SubscriptionType } from '../../../lib/networking/queries/useGetSubscriptionsQuery' +import { theme } from '../tokens/stitches.config' +import { formatMessage } from '../../locales/en/messages' +import { subscribeMutation } from '../../lib/networking/mutations/subscribeMutation' +import { SubscriptionType } from '../../lib/networking/queries/useGetSubscriptionsQuery' type TabName = 'link' | 'feed' | 'opml' | 'pdf' | 'import' diff --git a/packages/web/components/templates/NavigationLayout.tsx b/packages/web/components/templates/NavigationLayout.tsx index a52290ca2..d892364ad 100644 --- a/packages/web/components/templates/NavigationLayout.tsx +++ b/packages/web/components/templates/NavigationLayout.tsx @@ -1,5 +1,5 @@ import { PageMetaData, PageMetaDataProps } from '../patterns/PageMetaData' -import { Box, VStack } from '../elements/LayoutPrimitives' +import { Box, HStack, VStack } from '../elements/LayoutPrimitives' import { ReactNode, useEffect, useState, useCallback } from 'react' import { useGetViewerQuery } from '../../lib/networking/queries/useGetViewerQuery' import { navigationCommands } from '../../lib/keyboardShortcuts/navigationShortcuts' @@ -19,6 +19,10 @@ import { DEFAULT_HEADER_HEIGHT } from './homeFeed/HeaderSpacer' import { Button } from '../elements/Button' import { List } from 'phosphor-react' import { usePersistedState } from '../../lib/hooks/usePersistedState' +import { Allotment } from 'allotment' +import 'allotment/dist/style.css' +import { LibrarySideBar } from './library/LibrarySideBar' +import NoSsr from './NoSsr' export type NavigationSection = | 'justread' @@ -29,6 +33,7 @@ export type NavigationSection = type NavigationLayoutProps = { children: ReactNode + rightPane?: ReactNode section: NavigationSection pageMetaDataProps?: PageMetaDataProps } @@ -41,7 +46,8 @@ export function NavigationLayout(props: NavigationLayoutProps): JSX.Element { const [showLogoutConfirmation, setShowLogoutConfirmation] = useState(false) const [showKeyboardCommandsModal, setShowKeyboardCommandsModal] = useState(false) - const [showLeftMenu, setShowLeftMenu] = usePersistedState({ + + const [showNavMenu, setShowNavMenu] = usePersistedState({ key: 'nav-show-menu', isSessionStorage: false, initialValue: true, @@ -109,51 +115,37 @@ export function NavigationLayout(props: NavigationLayoutProps): JSX.Element { }, [showLogout]) return ( - <> + {props.pageMetaDataProps ? ( ) : null} - { + setShowNavMenu(!showNavMenu) }} - > -
{ - setShowLeftMenu(!showLeftMenu) - }} + /> + {}} + searchTerm="" + applySearchQuery={() => {}} + showFilterMenu={showNavMenu} + setShowFilterMenu={setShowNavMenu} + /> + {props.children} + {showLogoutConfirmation ? ( + setShowLogoutConfirmation(false)} /> - {showLeftMenu && ( - {}} - searchTerm={''} - // eslint-disable-next-line @typescript-eslint/no-empty-function - applySearchQuery={(searchQuery: string) => {}} - showFilterMenu={showLeftMenu} - setShowFilterMenu={(show) => { - setShowLeftMenu(show) - }} - /> - )} - {props.children} - {showLogoutConfirmation ? ( - setShowLogoutConfirmation(false)} - /> - ) : null} - {showKeyboardCommandsModal ? ( - setShowKeyboardCommandsModal(false)} - /> - ) : null} - - + ) : null} + {showKeyboardCommandsModal ? ( + setShowKeyboardCommandsModal(false)} + /> + ) : null} + ) } diff --git a/packages/web/components/templates/discoverFeed/DiscoverContainer.tsx b/packages/web/components/templates/discoverFeed/DiscoverContainer.tsx index e3ab2dafd..76174e877 100644 --- a/packages/web/components/templates/discoverFeed/DiscoverContainer.tsx +++ b/packages/web/components/templates/discoverFeed/DiscoverContainer.tsx @@ -2,7 +2,7 @@ import { Box, HStack, VStack } from '../../elements/LayoutPrimitives' import { LibraryFilterMenu } from '../navMenu/LibraryMenu' import { DiscoverHeader } from './DiscoverHeader/DiscoverHeader' import { useRouter } from 'next/router' -import React, { useCallback, useEffect, useState } from "react" +import React, { useCallback, useEffect, useState } from 'react' import { DiscoverItemFeed } from './DiscoverFeed/DiscoverFeed' import { useGetViewerQuery } from '../../../lib/networking/queries/useGetViewerQuery' import toast from 'react-hot-toast' @@ -10,13 +10,13 @@ import { Button } from '../../elements/Button' import { showErrorToast } from '../../../lib/toastHelpers' import { saveDiscoverArticleMutation, - SaveDiscoverArticleOutput -} from "../../../lib/networking/mutations/saveDiscoverArticle" -import { saveUrlMutation } from "../../../lib/networking/mutations/saveUrlMutation" -import { useFetchMore } from "../../../lib/hooks/useFetchMoreScroll" -import { AddLinkModal } from "../homeFeed/AddLinkModal" -import { useGetDiscoverFeedItems } from "../../../lib/networking/queries/useGetDiscoverFeedItems" -import { useGetDiscoverFeeds } from "../../../lib/networking/queries/useGetDiscoverFeeds" + SaveDiscoverArticleOutput, +} from '../../../lib/networking/mutations/saveDiscoverArticle' +import { saveUrlMutation } from '../../../lib/networking/mutations/saveUrlMutation' +import { useFetchMore } from '../../../lib/hooks/useFetchMoreScroll' +import { AddLinkModal } from '../AddLinkModal' +import { useGetDiscoverFeedItems } from '../../../lib/networking/queries/useGetDiscoverFeedItems' +import { useGetDiscoverFeeds } from '../../../lib/networking/queries/useGetDiscoverFeeds' export type LayoutType = 'LIST_LAYOUT' | 'GRID_LAYOUT' @@ -27,8 +27,8 @@ export function DiscoverContainer(): JSX.Element { const viewer = useGetViewerQuery() const [showFilterMenu, setShowFilterMenu] = useState(false) const [layoutType, setLayoutType] = useState('GRID_LAYOUT') - const [showAddLinkModal, setShowAddLinkModal] = useState(false); - const {feeds, revalidate, isValidating} = useGetDiscoverFeeds() + const [showAddLinkModal, setShowAddLinkModal] = useState(false) + const { feeds, revalidate, isValidating } = useGetDiscoverFeeds() const topics = [ { title: 'Popular', @@ -73,8 +73,16 @@ export function DiscoverContainer(): JSX.Element { }, ] - const [selectedFeed, setSelectedFeed] = useState("All Feeds"); - const { discoverItems, setTopic, activeTopic, isLoading, hasMore, setPage, page } = useGetDiscoverFeedItems(topics[1], selectedFeed) + const [selectedFeed, setSelectedFeed] = useState('All Feeds') + const { + discoverItems, + setTopic, + activeTopic, + isLoading, + hasMore, + setPage, + page, + } = useGetDiscoverFeedItems(topics[1], selectedFeed) const handleFetchMore = useCallback(() => { if (isLoading || !hasMore) { return @@ -88,7 +96,11 @@ export function DiscoverContainer(): JSX.Element { timezone: string, locale: string ): Promise => { - const result = await saveDiscoverArticleMutation({discoverArticleId, timezone, locale}) + const result = await saveDiscoverArticleMutation({ + discoverArticleId, + timezone, + locale, + }) if (result?.saveDiscoverArticle) { toast( () => ( @@ -160,8 +172,8 @@ export function DiscoverContainer(): JSX.Element { }, []) const setTopicAndReturnToTop = (topic: TopicTabData) => { - window.scroll(0,0); - setTopic(topic); + window.scroll(0, 0) + setTopic(topic) } return ( @@ -204,12 +216,12 @@ export function DiscoverContainer(): JSX.Element { items={discoverItems ?? []} viewer={viewer.viewerData?.me} /> - { showAddLinkModal && + {showAddLinkModal && ( setShowAddLinkModal(false)} /> - } + )} ) diff --git a/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx b/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx index cc6014c41..0d221e34f 100644 --- a/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx +++ b/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx @@ -32,13 +32,13 @@ import { ConfirmationModal } from '../../patterns/ConfirmationModal' import { LinkedItemCardAction } from '../../patterns/LibraryCards/CardTypes' import { LinkedItemCard } from '../../patterns/LibraryCards/LinkedItemCard' import { Box, HStack, SpanBox, VStack } from './../../elements/LayoutPrimitives' -import { AddLinkModal } from './AddLinkModal' +import { AddLinkModal } from '../AddLinkModal' import { EditLibraryItemModal } from './EditItemModals' import { EmptyLibrary } from './EmptyLibrary' import { HighlightItemsLayout } from './HighlightsLayout' import { LibraryFilterMenu } from '../navMenu/LibraryMenu' import { LibraryLegacyMenu } from '../navMenu/LibraryLegacyMenu' -import { LibraryHeader, MultiSelectMode } from './LibraryHeader' +import { LegacyLibraryHeader, MultiSelectMode } from './LibraryHeader' import { UploadModal } from '../UploadModal' import { BulkAction } from '../../../lib/networking/mutations/bulkActionMutation' import { bulkActionMutation } from '../../../lib/networking/mutations/bulkActionMutation' @@ -975,7 +975,7 @@ function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element { }} > {props.mode != 'highlights' && ( - { diff --git a/packages/web/components/templates/homeFeed/MultiSelectControls.tsx b/packages/web/components/templates/homeFeed/MultiSelectControls.tsx index e1cbb2113..fd624edcc 100644 --- a/packages/web/components/templates/homeFeed/MultiSelectControls.tsx +++ b/packages/web/components/templates/homeFeed/MultiSelectControls.tsx @@ -9,12 +9,29 @@ import { TrashIcon } from '../../elements/icons/TrashIcon' import { ConfirmationModal } from '../../patterns/ConfirmationModal' import { AddBulkLabelsModal } from '../article/AddBulkLabelsModal' import { X } from 'phosphor-react' -import { LibraryHeaderProps } from './LibraryHeader' +import { MultiSelectMode } from './LibraryHeader' import { HeaderCheckboxIcon } from '../../elements/icons/HeaderCheckboxIcon' import { Label } from '../../../lib/networking/fragments/labelFragment' import { MarkAsReadIcon } from '../../elements/icons/MarkAsReadIcon' +import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery' -export const MultiSelectControls = (props: LibraryHeaderProps): JSX.Element => { +export type MultiSelectProps = { + viewer: UserBasicData | undefined + + searchTerm: string | undefined + applySearchQuery: (searchQuery: string) => void + + showFilterMenu: boolean + setShowFilterMenu: (show: boolean) => void + + numItemsSelected: number + multiSelectMode: MultiSelectMode + setMultiSelectMode: (mode: MultiSelectMode) => void + + performMultiSelectAction: (action: BulkAction, labelIds?: string[]) => void +} + +export const MultiSelectControls = (props: MultiSelectProps): JSX.Element => { const [showConfirmDelete, setShowConfirmDelete] = useState(false) const [showLabelsModal, setShowLabelsModal] = useState(false) // Don't change on immediate hover, the button has to be blurred at least once @@ -146,7 +163,7 @@ export const MultiSelectControls = (props: LibraryHeaderProps): JSX.Element => { ) } -export const CheckBoxButton = (props: LibraryHeaderProps): JSX.Element => { +export const CheckBoxButton = (props: MultiSelectProps): JSX.Element => { return ( + ) : ( + + )} + + + + {props.showEditTitleModal && ( + + props.actionHandler('update-item', item) + } + onOpenChange={() => { + props.setShowEditTitleModal(false) + props.setLinkToEdit(undefined) + }} + item={props.linkToEdit as LibraryItem} + /> + )} + {showUnsubscribeConfirmation && ( + setShowUnsubscribeConfirmation(false)} + /> + )} + {props.labelsTarget?.node.id && ( + { + if (props.labelsTarget) { + const activate = props.labelsTarget + props.setActiveItem(activate) + props.setLabelsTarget(undefined) + } + }} + /> + )} + {props.viewer && props.notebookTarget?.node.id && ( + { + // onClose={(highlights: Highlight[]) => { + // if (props.notebookTarget?.node.highlights) { + // props.notebookTarget.node.highlights = highlights + // } + props.setNotebookTarget(open ? props.notebookTarget : undefined) + }} + /> + )} + {showUploadModal && ( + setShowUploadModal(false)} /> + )} + + ) +} + +type LibraryItemsProps = { + items: LibraryItem[] + layout: LayoutType + viewer: UserBasicData | undefined + + gridContainerRef: React.RefObject + + setShowEditTitleModal: (show: boolean) => void + setLinkToEdit: (set: LibraryItem | undefined) => void + setShowUnsubscribeConfirmation: (show: true) => void + setLinkToUnsubscribe: (set: LibraryItem | undefined) => void + + isChecked: (itemId: string) => boolean + setIsChecked: (itemId: string, set: boolean) => void + multiSelectMode: MultiSelectMode + + actionHandler: ( + action: LinkedItemCardAction, + item: LibraryItem | undefined + ) => Promise +} + +function LibraryItems(props: LibraryItemsProps): JSX.Element { + return ( + + {props.items.map((linkedItem) => ( + div': { + bg: '$thLeftMenuBackground', + // bg: '$thLibraryBackground', + }, + '&:focus': { + outline: 'none', + '> div': { + outline: 'none', + bg: '$thBackgroundActive', + }, + }, + '&:hover': { + '> div': { + bg: '$thBackgroundActive', + boxShadow: '$cardBoxShadow', + }, + '> a': { + bg: '$thBackgroundActive', + }, + }, + }} + > + {props.viewer && ( + { + if (action === 'editTitle') { + props.setShowEditTitleModal(true) + props.setLinkToEdit(linkedItem) + } else if (action == 'unsubscribe') { + props.setShowUnsubscribeConfirmation(true) + props.setLinkToUnsubscribe(linkedItem) + } else { + props.actionHandler(action, linkedItem) + } + document.body.style.removeProperty('pointer-events') + }} + /> + )} + + ))} + + ) +} diff --git a/packages/web/components/templates/library/LibraryHeader.tsx b/packages/web/components/templates/library/LibraryHeader.tsx new file mode 100644 index 000000000..26318bb6a --- /dev/null +++ b/packages/web/components/templates/library/LibraryHeader.tsx @@ -0,0 +1,418 @@ +import { useEffect, useRef, useState } from 'react' +import { Box, HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives' +import { theme } from '../../tokens/stitches.config' +import { FormInput } from '../../elements/FormElements' +import { searchBarCommands } from '../../../lib/keyboardShortcuts/navigationShortcuts' +import { useKeyboardShortcuts } from '../../../lib/keyboardShortcuts/useKeyboardShortcuts' +import { Button, IconButton } from '../../elements/Button' +import { FunnelSimple, X } from 'phosphor-react' +import { LayoutType, LibraryMode } from '../homeFeed/HomeFeedContainer' +import { OmnivoreSmallLogo } from '../../elements/images/OmnivoreNameLogo' +import { DEFAULT_HEADER_HEIGHT, HeaderSpacer } from '../homeFeed/HeaderSpacer' +import { LIBRARY_LEFT_MENU_WIDTH } from '../navMenu/LibraryMenu' +import { BulkAction } from '../../../lib/networking/mutations/bulkActionMutation' +import { HeaderToggleGridIcon } from '../../elements/icons/HeaderToggleGridIcon' +import { HeaderToggleListIcon } from '../../elements/icons/HeaderToggleListIcon' +import { HeaderToggleTLDRIcon } from '../../elements/icons/HeaderToggleTLDRIcon' +import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery' +import { userHasFeature } from '../../../lib/featureFlag' +import { + MultiSelectControls, + CheckBoxButton, +} from '../homeFeed/MultiSelectControls' + +export type MultiSelectMode = 'off' | 'none' | 'some' | 'visible' | 'search' + +export type LibraryHeaderProps = { + viewer: UserBasicData | undefined + + layout: LayoutType + updateLayout: (layout: LayoutType) => void + + searchTerm: string | undefined + applySearchQuery: (searchQuery: string) => void + + showFilterMenu: boolean + setShowFilterMenu: (show: boolean) => void + + numItemsSelected: number + multiSelectMode: MultiSelectMode + setMultiSelectMode: (mode: MultiSelectMode) => void + + performMultiSelectAction: (action: BulkAction, labelIds?: string[]) => void +} + +export const headerControlWidths = ( + layout: LayoutType, + multiSelectMode: MultiSelectMode +) => { + return { + width: '95%', + '@mdDown': { + width: '100%', + }, + '@media (min-width: 930px)': { + width: '620px', + }, + '@media (min-width: 1280px)': { + width: '940px', + }, + '@media (min-width: 1600px)': { + width: '1232px', + }, + } +} + +export function LibraryHeader(props: LibraryHeaderProps): JSX.Element { + const [small, setSmall] = useState(false) + + useEffect(() => { + const handleScroll = () => { + setSmall(window.scrollY > 40) + } + if (typeof window !== 'undefined') { + window.addEventListener('scroll', handleScroll) + } + return () => { + window.removeEventListener('scroll', handleScroll) + } + }, []) + + return ( + <> + + + + + {/* This spacer is put in to push library content down + below the fixed header height. */} + + + ) +} + +function LargeHeaderLayout(props: LibraryHeaderProps): JSX.Element { + return ( + + {props.multiSelectMode !== 'off' ? ( + <> + + + ) : ( + + )} + + ) +} + +const HeaderControls = (props: LibraryHeaderProps): JSX.Element => { + const [searchBoxFocused, setSearchBoxFocused] = useState(false) + + return ( + <> + {!searchBoxFocused && ( + + + + )} + + + + + {/* {userHasFeature(props.viewer, 'ai-summaries') && ( + + )} */} + + + + + ) +} + +type MenuHeaderButtonProps = { + showFilterMenu: boolean + setShowFilterMenu: (show: boolean) => void +} + +export function MenuHeaderButton(props: MenuHeaderButtonProps): JSX.Element { + return ( + { + props.setShowFilterMenu(!props.showFilterMenu) + }} + > + + + + ) +} + +type SearchBoxProps = LibraryHeaderProps & { + searchBoxFocused: boolean + setSearchBoxFocused: (show: boolean) => void +} + +export function SearchBox(props: SearchBoxProps): JSX.Element { + const inputRef = useRef(null) + const [searchTerm, setSearchTerm] = useState(props.searchTerm ?? '') + + useEffect(() => { + setSearchTerm(props.searchTerm ?? '') + }, [props.searchTerm]) + + useKeyboardShortcuts( + searchBarCommands((action) => { + if (action === 'focusSearchBar' && inputRef.current) { + inputRef.current.select() + } + if (action == 'clearSearch' && inputRef.current) { + setSearchTerm('') + props.applySearchQuery('') + } + }) + ) + + return ( + + + + + + +
{ + event.preventDefault() + props.applySearchQuery(searchTerm || '') + inputRef.current?.blur() + }} + style={{ width: '100%' }} + > + { + event.target.select() + props.setSearchBoxFocused(true) + }} + onBlur={() => { + props.setSearchBoxFocused(false) + }} + onChange={(event) => { + setSearchTerm(event.target.value) + }} + onKeyDown={(event) => { + const key = event.key.toLowerCase() + if (key == 'escape') { + event.currentTarget.blur() + } + }} + /> + + + { + setSearchTerm('in:inbox') + props.applySearchQuery('') + inputRef.current?.blur() + }} + /> + +
+
+
+ ) +} + +type CancelSearchButtonProps = { + onClick: () => void +} + +const CancelSearchButton = (props: CancelSearchButtonProps): JSX.Element => { + const [color, setColor] = useState( + theme.colors.thTextContrast2.toString() + ) + return ( + + ) +} diff --git a/packages/web/components/templates/library/LibraryItemsContainer.tsx b/packages/web/components/templates/library/LibraryItemsContainer.tsx new file mode 100644 index 000000000..26b806515 --- /dev/null +++ b/packages/web/components/templates/library/LibraryItemsContainer.tsx @@ -0,0 +1,23 @@ +import { Allotment } from 'allotment' +import 'allotment/dist/style.css' +import { useGetViewerQuery } from '../../../lib/networking/queries/useGetViewerQuery' +import { useRouter } from 'next/router' +import { useKBar } from 'kbar' +import { useState } from 'react' +import { LibraryContainer } from './LibraryContainer' +import { LibrarySideBar } from './LibrarySideBar' + +export function LibraryItemsContainer(): JSX.Element { + const router = useRouter() + + return ( + + + + + + + + + ) +} diff --git a/packages/web/components/templates/library/LibrarySideBar.tsx b/packages/web/components/templates/library/LibrarySideBar.tsx new file mode 100644 index 000000000..d59b5a1f1 --- /dev/null +++ b/packages/web/components/templates/library/LibrarySideBar.tsx @@ -0,0 +1,62 @@ +import { Action, createAction, useKBar, useRegisterActions } from 'kbar' +import debounce from 'lodash/debounce' +import { useRouter } from 'next/router' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { Toaster } from 'react-hot-toast' +import TopBarProgress from 'react-topbar-progress-indicator' +import { useFetchMore } from '../../../lib/hooks/useFetchMoreScroll' +import { usePersistedState } from '../../../lib/hooks/usePersistedState' +import { libraryListCommands } from '../../../lib/keyboardShortcuts/navigationShortcuts' +import { useKeyboardShortcuts } from '../../../lib/keyboardShortcuts/useKeyboardShortcuts' +import { + PageType, + State, +} from '../../../lib/networking/fragments/articleFragment' +import { + SearchItem, + TypeaheadSearchItemsData, + typeaheadSearchQuery, +} from '../../../lib/networking/queries/typeaheadSearch' +import type { + LibraryItem, + LibraryItemsQueryInput, +} from '../../../lib/networking/queries/useGetLibraryItemsQuery' +import { useGetLibraryItemsQuery } from '../../../lib/networking/queries/useGetLibraryItemsQuery' +import { + useGetViewerQuery, + UserBasicData, +} from '../../../lib/networking/queries/useGetViewerQuery' +import { Button } from '../../elements/Button' +import { StyledText } from '../../elements/StyledText' +import { ConfirmationModal } from '../../patterns/ConfirmationModal' +import { LinkedItemCardAction } from '../../patterns/LibraryCards/CardTypes' +import { LinkedItemCard } from '../../patterns/LibraryCards/LinkedItemCard' +import { Box, HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives' +import { AddLinkModal } from '../AddLinkModal' +import { EditLibraryItemModal } from '../homeFeed/EditItemModals' +import { EmptyLibrary } from '../homeFeed/EmptyLibrary' +import { LegacyLibraryHeader, MultiSelectMode } from '../homeFeed/LibraryHeader' +import { UploadModal } from '../UploadModal' +import { BulkAction } from '../../../lib/networking/mutations/bulkActionMutation' +import { bulkActionMutation } from '../../../lib/networking/mutations/bulkActionMutation' +import { + showErrorToast, + showSuccessToast, + showSuccessToastWithAction, +} from '../../../lib/toastHelpers' +import { SetPageLabelsModalPresenter } from '../article/SetLabelsModalPresenter' +import { NotebookPresenter } from '../article/NotebookPresenter' +import { saveUrlMutation } from '../../../lib/networking/mutations/saveUrlMutation' +import { articleQuery } from '../../../lib/networking/queries/useGetArticleQuery' +import { PinnedButtons } from '../homeFeed/PinnedButtons' +import { PinnedSearch } from '../../../pages/settings/pinned-searches' +import { FetchItemsError } from '../homeFeed/FetchItemsError' +import { LibraryHeader } from './LibraryHeader' + +type LibrarySideBarProps = { + text: string +} + +export function LibrarySideBar(props: LibrarySideBarProps): JSX.Element { + return {props.text} +} diff --git a/packages/web/components/templates/navMenu/NavigationMenu.tsx b/packages/web/components/templates/navMenu/NavigationMenu.tsx index 72ab6d15a..87db74d30 100644 --- a/packages/web/components/templates/navMenu/NavigationMenu.tsx +++ b/packages/web/components/templates/navMenu/NavigationMenu.tsx @@ -105,9 +105,9 @@ export function NavigationMenu(props: LibraryFilterMenuProps): JSX.Element { left: '0px', top: '0px', position: 'fixed', - bg: '$thLeftMenuBackground', height: '100%', width: LIBRARY_LEFT_MENU_WIDTH, + bg: '$thLeftMenuBackground', overflowY: 'auto', overflowX: 'hidden', '&::-webkit-scrollbar': { @@ -200,7 +200,7 @@ const LibraryNav = (props: LibraryFilterMenuProps): JSX.Element => { gap: '5px', width: '100%', borderBottom: '1px solid $thBorderColor', - px: '15px', + px: '0px', pb: '25px', }} alignment="start" @@ -213,13 +213,6 @@ const LibraryNav = (props: LibraryFilterMenuProps): JSX.Element => { isSelected={props.section == 'home'} icon={} /> - } - /> { isSelected={props.section == 'library'} icon={} /> + } + /> { m: '0px', gap: '8px', width: '100%', - px: '15px', + px: '0px', pb: '25px', }} alignment="start" @@ -815,6 +815,7 @@ function NavButton(props: NavButtonProps): JSX.Element { width: '100%', maxWidth: '100%', height: '34px', + px: '15px', backgroundColor: props.isSelected ? '$thLibrarySelectionColor' diff --git a/packages/web/package.json b/packages/web/package.json index 2afe9b389..cfdc9f0ae 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -31,6 +31,7 @@ "@radix-ui/react-switch": "^1.0.1", "@sentry/nextjs": "^7.42.0", "@stitches/react": "^1.2.5", + "allotment": "^1.20.2", "antd": "4.24.3", "axios": "^1.2.0", "cookie": "^0.5.0", diff --git a/packages/web/pages/justread/index.tsx b/packages/web/pages/justread/index.tsx index c5d6c41df..a7edcb344 100644 --- a/packages/web/pages/justread/index.tsx +++ b/packages/web/pages/justread/index.tsx @@ -1,7 +1,7 @@ import * as HoverCard from '@radix-ui/react-hover-card' import { styled } from '@stitches/react' import { useRouter } from 'next/router' -import { useCallback, useMemo, useState } from 'react' +import { useMemo, useState } from 'react' import { Button } from '../../components/elements/Button' import { AddToLibraryActionIcon } from '../../components/elements/icons/home/AddToLibraryActionIcon' import { ArchiveActionIcon } from '../../components/elements/icons/home/ArchiveActionIcon' diff --git a/packages/web/pages/library/index.tsx b/packages/web/pages/library/index.tsx index 7d7eb06ad..67d14c46b 100644 --- a/packages/web/pages/library/index.tsx +++ b/packages/web/pages/library/index.tsx @@ -1,13 +1,14 @@ import { NavigationLayout } from '../../components/templates/NavigationLayout' import { PrimaryLayout } from '../../components/templates/PrimaryLayout' import { HomeFeedContainer } from '../../components/templates/homeFeed/HomeFeedContainer' -import { VStack } from '../../components/elements/LayoutPrimitives' +import { Box, VStack } from '../../components/elements/LayoutPrimitives' +import { LibraryContainer } from '../../components/templates/library/LibraryContainer' +import { LibraryItemsContainer } from '../../components/templates/library/LibraryItemsContainer' +import { LibrarySideBar } from '../../components/templates/library/LibrarySideBar' +import { Allotment, LayoutPriority } from 'allotment' +import 'allotment/dist/style.css' -export default function Home(): JSX.Element { - return -} - -function LoadedContent(): JSX.Element { +export default function Library(): JSX.Element { return ( - - - + {/* + */} + + + + {/* + + + + */} ) } diff --git a/yarn.lock b/yarn.lock index 76355e48b..6a2fe7a16 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3793,6 +3793,11 @@ dependencies: lodash "^4.17.21" +"@juggle/resize-observer@^3.3.1": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@juggle/resize-observer/-/resize-observer-3.4.0.tgz#08d6c5e20cf7e4cc02fd181c4b0c225cd31dbb60" + integrity sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA== + "@langchain/anthropic@^0.1.16": version "0.1.16" resolved "https://registry.yarnpkg.com/@langchain/anthropic/-/anthropic-0.1.16.tgz#c2a9d3dd4e02df7118dd97cf2503c9bd1a4de5ad" @@ -9649,6 +9654,18 @@ ajv@^8.11.0: require-from-string "^2.0.2" uri-js "^4.2.2" +allotment@^1.20.2: + version "1.20.2" + resolved "https://registry.yarnpkg.com/allotment/-/allotment-1.20.2.tgz#5ea3a630b3265479debb69156658244711f83843" + integrity sha512-TaCuHfYNcsJS9EPk04M7TlG5Rl3vbAdHeAyrTE9D5vbpzV+wxnRoUrulDbfnzaQcPIZKpHJNixDOoZNuzliKEA== + dependencies: + classnames "^2.3.0" + eventemitter3 "^5.0.0" + lodash.clamp "^4.0.0" + lodash.debounce "^4.0.0" + lodash.isequal "^4.5.0" + use-resize-observer "^9.0.0" + ansi-align@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.0.tgz#b536b371cf687caaef236c18d3e21fe3797467cb" @@ -12147,6 +12164,11 @@ classnames@^2.2.6: resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e" integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA== +classnames@^2.3.0: + version "2.5.1" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b" + integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow== + cld@^2.9.1: version "2.9.1" resolved "https://registry.yarnpkg.com/cld/-/cld-2.9.1.tgz#0c6685672d9f4612dfeb75eabfdd17bf282a87a6" @@ -15330,6 +15352,11 @@ eventemitter3@^4.0.0, eventemitter3@^4.0.4: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== +eventemitter3@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" + integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== + eventid@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/eventid/-/eventid-2.0.1.tgz#574e860149457a79a2efe788c459f0c3062d02ec" @@ -21076,12 +21103,17 @@ lodash.capitalize@^4.2.1: resolved "https://registry.yarnpkg.com/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz#f826c9b4e2a8511d84e3aca29db05e1a4f3b72a9" integrity sha512-kZzYOKspf8XVX5AvmQF94gQW0lejFVgb80G85bU4ZWzoJ6C03PQg3coYAUpSTpQWelrZELd3XWgHzw4Ck5kaIw== +lodash.clamp@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/lodash.clamp/-/lodash.clamp-4.0.3.tgz#5c24bedeeeef0753560dc2b4cb4671f90a6ddfaa" + integrity sha512-HvzRFWjtcguTW7yd8NJBshuNaCa8aqNFtnswdT7f/cMd/1YKy5Zzoq4W/Oxvnx9l7aeY258uSdDfM793+eLsVg== + lodash.clonedeep@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= -lodash.debounce@^4.0.8: +lodash.debounce@^4.0.0, lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= @@ -21126,6 +21158,11 @@ lodash.isboolean@^3.0.3: resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY= +lodash.isequal@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ== + lodash.isfunction@^3.0.9: version "3.0.9" resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz#06de25df4db327ac931981d1bdb067e5af68d051" @@ -31088,6 +31125,13 @@ use-latest@^1.0.0: dependencies: use-isomorphic-layout-effect "^1.0.0" +use-resize-observer@^9.0.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/use-resize-observer/-/use-resize-observer-9.1.0.tgz#14735235cf3268569c1ea468f8a90c5789fc5c6c" + integrity sha512-R25VqO9Wb3asSD4eqtcxk8sJalvIOYBqS8MNZlpDSQ4l4xMQxC/J7Id9HoTqPq8FwULIn0PVW+OAqF2dyYbjow== + dependencies: + "@juggle/resize-observer" "^3.3.1" + use-sidecar@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.1.2.tgz#2f43126ba2d7d7e117aa5855e5d8f0276dfe73c2"