diff --git a/src/js/components/feed/Feed.tsx b/src/js/components/feed/Feed.tsx index fc17cc47..66ba38de 100644 --- a/src/js/components/feed/Feed.tsx +++ b/src/js/components/feed/Feed.tsx @@ -1,47 +1,20 @@ import { memo, useEffect, useMemo, useRef, useState } from 'react'; -import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/20/solid'; import { Bars3Icon, Squares2X2Icon } from '@heroicons/react/24/outline'; -import { Filter } from 'nostr-tools'; import Image from '@/components/embed/Image'; import Video from '@/components/embed/Video'; -import EventComponent, { EventComponentProps } from '@/components/events/EventComponent'; -import Modal from '@/components/modal/Modal'; -import ProxyImg from '@/components/SafeImg'; +import EventComponent from '@/components/events/EventComponent'; +import ImageGridItem from '@/components/feed/ImageGridItem'; +import ImageModal from '@/components/feed/ImageModal'; +import { DisplayAs, FeedProps, ImageOrVideo } from '@/components/feed/types'; +import Show from '@/components/helpers/Show'; import useSubscribe from '@/hooks/useSubscribe'; import { useLocalState } from '@/LocalState'; const PAGE_SIZE = 6; const LOAD_MORE_MARGIN = '0px 0px 2000px 0px'; -const VideoIcon = ( - - - -); - -type Props = { - filterOptions: FilterOption[]; - showDisplayAs?: boolean; - filterFn?: (event: any) => boolean; - emptyMessage?: string; -}; - -type DisplayAs = 'feed' | 'grid'; - -type ImageOrVideo = { - type: 'image' | 'video'; - url: string; -}; - -export type FilterOption = { - name: string; - filter: Filter; - filterFn?: (event: any) => boolean; - eventProps?: Partial; -}; - -const Feed = ({ showDisplayAs, filterOptions, emptyMessage }: Props) => { +const Feed = ({ showDisplayAs, filterOptions, emptyMessage }: FeedProps) => { if (!filterOptions || filterOptions.length === 0) { throw new Error('Feed requires at least one filter option'); } @@ -54,25 +27,20 @@ const Feed = ({ showDisplayAs, filterOptions, emptyMessage }: Props) => { const { events: allEvents, loadMore } = useSubscribe({ filter: filterOption.filter, - // in keyword search, relays should be queried for all events, not just sinceLastOpened - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - //@ts-ignore sinceLastOpened: false, }); // deduplicate const events = useMemo(() => { - const filtered = allEvents - .filter((event) => { - if (mutedUsers[event.pubkey]) { - return false; - } - if (filterOption.filterFn) { - return filterOption.filterFn(event); - } - return true; - }) - .sort((a, b) => b.created_at - a.created_at); + const filtered = allEvents.filter((event) => { + if (mutedUsers[event.pubkey]) { + return false; + } + if (filterOption.filterFn) { + return filterOption.filterFn(event); + } + return true; + }); return filtered; }, [allEvents, filterOption]); @@ -129,34 +97,6 @@ const Feed = ({ showDisplayAs, filterOptions, emptyMessage }: Props) => { .slice(0, displayCount); }, [events, displayCount, displayAs]) as ImageOrVideo[]; - const goToPrevImage = () => { - if (modalItemIndex === null) return; - const prevImageIndex = (modalItemIndex - 1 + imagesAndVideos.length) % imagesAndVideos.length; - setModalImageIndex(prevImageIndex); - }; - - const goToNextImage = () => { - if (modalItemIndex === null) return; - const nextImageIndex = (modalItemIndex + 1) % imagesAndVideos.length; - setModalImageIndex(nextImageIndex); - }; - - useEffect(() => { - const handleKeyDown = (e: KeyboardEvent) => { - if (e.key === 'ArrowRight') { - goToNextImage(); - } else if (e.key === 'ArrowLeft') { - goToPrevImage(); - } - }; - - window.addEventListener('keydown', handleKeyDown); - - return () => { - window.removeEventListener('keydown', handleKeyDown); - }; - }, [modalItemIndex]); - const renderFilterOptions = () => { return (
@@ -211,94 +151,31 @@ const Feed = ({ showDisplayAs, filterOptions, emptyMessage }: Props) => { const renderGrid = () => { return (
- {imagesAndVideos.map((item, index) => renderGridItem(item, index))} + {imagesAndVideos.map((item, index) => ( + + ))}
); }; - const renderGridItem = (item: { url: string; type: 'image' | 'video' }, index: number) => { - const url = - item.type === 'video' ? `https://imgproxy.iris.to/thumbnail/638/${item.url}` : item.url; - return ( -
{ - setModalImageIndex(index); - }} - > - - {item.type === 'video' && ( -
{VideoIcon}
- )} -
- ); - }; - - const renderImageModal = () => { - return modalItemIndex !== null ? ( - setModalImageIndex(null)}> -
- {imagesAndVideos[modalItemIndex].type === 'video' ? ( -
-
- ) : ( - '' - ); - }; - return ( <> - {filterOptions.length > 1 && renderFilterOptions()} + 1}>{renderFilterOptions()} {renderDisplayAsSelector()} - {renderImageModal()} - {isEmpty &&

{emptyMessage || 'No Posts'}

} + + +
{emptyMessage || 'No Posts'}
+
{displayAs === 'grid' ? renderGrid() diff --git a/src/js/components/feed/ImageGridItem.tsx b/src/js/components/feed/ImageGridItem.tsx new file mode 100644 index 00000000..d8563720 --- /dev/null +++ b/src/js/components/feed/ImageGridItem.tsx @@ -0,0 +1,41 @@ +import { ImageOrVideo } from '@/components/feed/types'; +import SafeImg from '@/components/SafeImg'; +import Icons from '@/Icons'; + +type ImageGridItemProps = { + item: ImageOrVideo; + index: number; + setModalImageIndex: (index: number) => void; + imagesAndVideosLength: number; + lastElementRef?: React.MutableRefObject; +}; + +export const ImageGridItem = ({ + item, + index, + setModalImageIndex, + imagesAndVideosLength, + lastElementRef, +}: ImageGridItemProps) => { + const url = + item.type === 'video' ? `https://imgproxy.iris.to/thumbnail/638/${item.url}` : item.url; + const isLast = index === imagesAndVideosLength - 1; + + return ( +
{ + setModalImageIndex(index); + }} + > + + {item.type === 'video' && ( +
{Icons.video}
+ )} +
+ ); +}; + +export default ImageGridItem; diff --git a/src/js/components/feed/ImageModal.tsx b/src/js/components/feed/ImageModal.tsx new file mode 100644 index 00000000..9afcb4fe --- /dev/null +++ b/src/js/components/feed/ImageModal.tsx @@ -0,0 +1,94 @@ +import { useEffect } from 'react'; +import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/20/solid'; + +import Modal from '@/components/modal/Modal'; +import SafeImg from '@/components/SafeImg'; + +type ImageModalProps = { + imagesAndVideos: Array<{ + type: 'image' | 'video'; + url: string; + }>; + modalItemIndex: number | null; + setModalImageIndex: (index: number | null) => void; +}; + +const ImageModal = ({ imagesAndVideos, modalItemIndex, setModalImageIndex }: ImageModalProps) => { + const goToPrevImage = () => { + if (modalItemIndex === null) return; + const prevImageIndex = (modalItemIndex - 1 + imagesAndVideos.length) % imagesAndVideos.length; + setModalImageIndex(prevImageIndex); + }; + + const goToNextImage = () => { + if (modalItemIndex === null) return; + const nextImageIndex = (modalItemIndex + 1) % imagesAndVideos.length; + setModalImageIndex(nextImageIndex); + }; + + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'ArrowRight') { + goToNextImage(); + } else if (e.key === 'ArrowLeft') { + goToPrevImage(); + } + }; + + window.addEventListener('keydown', handleKeyDown); + + return () => { + window.removeEventListener('keydown', handleKeyDown); + }; + }, [modalItemIndex, imagesAndVideos]); + + return modalItemIndex !== null ? ( + setModalImageIndex(null)}> +
+ {imagesAndVideos[modalItemIndex].type === 'video' ? ( +
+
+ ) : null; +}; + +export default ImageModal; diff --git a/src/js/components/feed/types.ts b/src/js/components/feed/types.ts new file mode 100644 index 00000000..f0c4db9c --- /dev/null +++ b/src/js/components/feed/types.ts @@ -0,0 +1,24 @@ +import { Filter } from 'nostr-tools'; + +import { EventComponentProps } from '@/components/events/EventComponent'; + +export type FeedProps = { + filterOptions: FilterOption[]; + showDisplayAs?: boolean; + filterFn?: (event: any) => boolean; + emptyMessage?: string; +}; + +export type DisplayAs = 'feed' | 'grid'; + +export type ImageOrVideo = { + type: 'image' | 'video'; + url: string; +}; + +export type FilterOption = { + name: string; + filter: Filter; + filterFn?: (event: any) => boolean; + eventProps?: Partial; +}; diff --git a/src/js/views/EditProfile.tsx b/src/js/views/EditProfile.tsx index 1843aff1..a6539321 100644 --- a/src/js/views/EditProfile.tsx +++ b/src/js/views/EditProfile.tsx @@ -124,7 +124,7 @@ export default class EditProfile extends Component {

{val && (

- +

)}