diff --git a/src/components/Home/index.tsx b/src/components/Home/index.tsx index b8c45f4..3d384de 100644 --- a/src/components/Home/index.tsx +++ b/src/components/Home/index.tsx @@ -48,6 +48,7 @@ const Home = () => { className="topic" onClick={() => nav({ + ...currentSettings, tags: [], npubs: [], showReplies: false, diff --git a/src/components/MasonryView/MasonryImage.tsx b/src/components/MasonryView/MasonryImage.tsx index f1738b4..a89675c 100644 --- a/src/components/MasonryView/MasonryImage.tsx +++ b/src/components/MasonryView/MasonryImage.tsx @@ -76,7 +76,7 @@ const MasonryImage = ({ image, onClick, index }: MasonryImageProps) => { loading="lazy" key={image.url} onClick={onClick} - src={createImgProxyUrl(image.url, 320, -1)} // TODO make width dynamic, also with column sizes, and full screen image size + src={createImgProxyUrl(image.url, 320, -1)} > { !loaded &&
// Spacer when image is not loaded yet diff --git a/src/components/ScrollView/ScrollImage.tsx b/src/components/ScrollView/ScrollImage.tsx index 40e6bf3..053042d 100644 --- a/src/components/ScrollView/ScrollImage.tsx +++ b/src/components/ScrollView/ScrollImage.tsx @@ -6,6 +6,7 @@ import useOnScreen from '../../utils/useOnScreen'; import IconMicMuted from '../Icons/IconMicMuted'; import IconMicOn from '../Icons/IconMicOn'; import useWindowSize from '../../utils/useWindowSize'; +import useImageSizes from '../../utils/useImageSizes'; interface ScrollImageProps { image: NostrImage; @@ -24,21 +25,7 @@ const ScrollImage = ({ image, currentImage, setCurrentImage, index }: ScrollImag const isVisible = useOnScreen(containerRef); const nearCurrentImage = useMemo(() => Math.abs((currentImage || 0) - index) < 3, [currentImage, index]); const mediaIsVideo = useMemo(() => isVideo(image.url), [image.url]); - - const imageProxyUrl320 = useMemo(() => createImgProxyUrl(image.url, 320, -1), [image.url]); - - const currentImageProxyUrl = useMemo(() => { - const imageProxyUrl800 = createImgProxyUrl(image.url, 800, -1); - const imageProxyUrl1920 = createImgProxyUrl(image.url, 1920, -1); - - return width == undefined - ? imageProxyUrl320 - : width < 800 - ? imageProxyUrl320 - : width < 1920 - ? imageProxyUrl800 - : imageProxyUrl1920; - }, [image.url, imageProxyUrl320, width]); + const { optimalImageUrl, imageUrl320w } = useImageSizes(image.url); const blurBgUrl = useMemo(() => { if (mediaIsVideo) return ''; @@ -48,9 +35,9 @@ const ScrollImage = ({ image, currentImage, setCurrentImage, index }: ScrollImag return createImgProxyUrl(image.url, 200, 200); } else { // on Desktop use the 320x masonry image - return imageProxyUrl320; + return imageUrl320w; } - }, [image.url, imageProxyUrl320, isMobile, mediaIsVideo]); + }, [image.url, imageUrl320w, isMobile, mediaIsVideo]); /* const toggleVideoPause = (video: HTMLVideoElement | null) => { @@ -126,7 +113,7 @@ const ScrollImage = ({ image, currentImage, setCurrentImage, index }: ScrollImag className={`image`} loading="lazy" key={image.url} - src={currentImageProxyUrl} + src={optimalImageUrl} > ))} {isVisible && mediaIsVideo && ( diff --git a/src/components/ScrollView/ScrollView.tsx b/src/components/ScrollView/ScrollView.tsx index 70d7d5a..57a598c 100644 --- a/src/components/ScrollView/ScrollView.tsx +++ b/src/components/ScrollView/ScrollView.tsx @@ -23,11 +23,8 @@ const ScrollView = ({ settings, images, currentImage, setCurrentImage, setViewMo const containerRef = useRef(null); const [state, setState] = useGlobalState(); const [showInfoPanel, setShowInfoPanel] = useState(false); - - // const sortedImages = useMemo( - // () => images.sort((a, b) => (b.timestamp && a.timestamp ? b.timestamp - a.timestamp : 0)), // sort by timestamp descending - // [images] // settings is not used here, but we need to include it to trigger a re-render when it changes - // ); + const { activeProfile, activeNpub } = useActiveProfile(settings, state.activeImage); + const title = useTitle(settings, activeProfile); useEffect(() => { if (currentImage) { @@ -45,11 +42,8 @@ const ScrollView = ({ settings, images, currentImage, setCurrentImage, setViewMo } }, [images, currentImage, setState]); - const { activeProfile, activeNpub } = useActiveProfile(settings, state.activeImage); - const title = useTitle(settings, activeProfile); - const infoPanelAvailable = state.activeImage && (state.activeImage.content || state.activeImage.tags.length > 0); - // console.log(JSON.stringify([state?.activeImage?.content, state?.activeImage?.tags])); + return (
diff --git a/src/components/SlideShow.tsx b/src/components/SlideShow.tsx index cf66e65..e2a5888 100644 --- a/src/components/SlideShow.tsx +++ b/src/components/SlideShow.tsx @@ -17,7 +17,7 @@ import SlideView from './SlideView'; import { nip19 } from 'nostr-tools'; import uniqBy from 'lodash/uniqBy'; import AdultContentInfo from './AdultContentInfo'; -import useNav from '../utils/useNav'; +import useNav, { ContentType } from '../utils/useNav'; import { useGlobalState } from '../utils/globalState'; import ScrollView from './ScrollView/ScrollView'; import IconPlay from './Icons/IconPlay'; @@ -238,25 +238,27 @@ const SlideShow = () => { useEffect(() => { images.current = uniqBy( - posts.flatMap(p => { - return extractImageUrls(p.event.content) - .filter(url => isImage(url) || isVideo(url)) - .filter(url => !url.startsWith('https://creatr.nostr.wine/')) // Filter our creatr.nostr.wine content, since we don't have NIP-98 auth yet. - .map(url => ({ - post: p, - url, - author: nip19.npubEncode(p.event.pubkey), - authorId: p.event.pubkey, - content: prepareContent(p.event.content), - type: isVideo(url) ? 'video' : 'image', - timestamp: p.event.created_at, - noteId: p.event.id || '', - tags: p.event.tags?.filter((t: string[]) => t[0] === 't').map((t: string[]) => t[1].toLowerCase()) || [], - })); - }), + posts + .flatMap(p => { + return extractImageUrls(p.event.content) + .filter(url => isImage(url) || isVideo(url)) + .filter(url => !url.startsWith('https://creatr.nostr.wine/')) // Filter our creatr.nostr.wine content, since we don't have NIP-98 auth yet. + .map(url => ({ + post: p, + url, + author: nip19.npubEncode(p.event.pubkey), + authorId: p.event.pubkey, + content: prepareContent(p.event.content), + type: (isVideo(url) ? 'video' : 'image') as ContentType, + timestamp: p.event.created_at, + noteId: p.event.id || '', + tags: p.event.tags?.filter((t: string[]) => t[0] === 't').map((t: string[]) => t[1].toLowerCase()) || [], + })); + }) + .filter(i => settings.type == 'all' || settings.type == i.type), 'url' ); - }, [posts]); + }, [posts, settings.type]); const onKeyDown = (event: KeyboardEvent) => { if (showSettings) return; @@ -322,7 +324,9 @@ const SlideShow = () => { onClick={() => { if (viewMode == 'scroll' || viewMode == 'slideshow') { setViewMode('grid'); - } else navigate('/'); + } else { + navigate(`/${settings.type !== 'all' ? `?type=${settings.type}` : ''}`); + } }} > ✕ diff --git a/src/components/SlideView/Slide.tsx b/src/components/SlideView/Slide.tsx index 1fca347..335fecd 100644 --- a/src/components/SlideView/Slide.tsx +++ b/src/components/SlideView/Slide.tsx @@ -1,11 +1,12 @@ import { useEffect } from 'react'; import SlideImage from './SlideImage'; import SlideVideo from './SlideVideo'; +import { ContentType } from '@/utils/useNav'; type SlideProps = { url: string; paused: boolean; - type: 'image' | 'video'; + type: ContentType; onAnimationEnded?: () => void; animationDuration?: number; noteId: string; diff --git a/src/components/SlideView/SlideImage.tsx b/src/components/SlideView/SlideImage.tsx index a9c301c..f21e00c 100644 --- a/src/components/SlideView/SlideImage.tsx +++ b/src/components/SlideView/SlideImage.tsx @@ -9,6 +9,8 @@ type SlideImageProps = { const SlideImage = ({ url, paused, style, noteId }: SlideImageProps) => { const loaded = useImageLoaded(url); + // const { optimalImageUrl } = useImageSizes(url); + return ( loaded && (
{ if (text == undefined) return []; const urlRegex = /(https?:\/\/[^\s]+)/g; - const matchedUrls = (text.match(urlRegex) || []).map(u => urlFix(u)); + const matchedUrls = (text.match(urlRegex) || []) + .map(u => urlFix(u)) + .filter(u => !u.startsWith('https://commons.wikimedia.org/wiki/')); // the url ends with .jpg but these are wiki media web pages, not image urls return uniq(matchedUrls); }; diff --git a/src/utils/useImageSizes.ts b/src/utils/useImageSizes.ts new file mode 100644 index 0000000..d269066 --- /dev/null +++ b/src/utils/useImageSizes.ts @@ -0,0 +1,24 @@ +import { createImgProxyUrl } from '../components/nostrImageDownload'; +import useWindowSize from './useWindowSize'; +import { useMemo } from 'react'; + +const useImageSizes = (imageUrl: string) => { + const { width } = useWindowSize(); + + const imageUrl320w = useMemo(() => createImgProxyUrl(imageUrl, 320, -1), [imageUrl]); + const imageUrl800w = useMemo(() => createImgProxyUrl(imageUrl, 800, -1), [imageUrl]); + const imageUrl1920w = useMemo(() => createImgProxyUrl(imageUrl, 1920, -1), [imageUrl]); + + const optimalImageUrl = useMemo(() => { + return width == undefined ? imageUrl320w : width < 800 ? imageUrl320w : width < 1920 ? imageUrl800w : imageUrl1920w; + }, [width, imageUrl320w, imageUrl800w, imageUrl1920w]); + + return { + optimalImageUrl, + imageUrl320w, + imageUrl800w, + imageUrl1920w, + }; +}; + +export default useImageSizes; diff --git a/src/utils/useNav.ts b/src/utils/useNav.ts index edf3198..58ecff3 100644 --- a/src/utils/useNav.ts +++ b/src/utils/useNav.ts @@ -10,8 +10,11 @@ export type Settings = { follows: boolean; list?: string; topic?: string; + type: ContentType; }; +export type ContentType = 'image' | 'video' | 'all'; + const useNav = () => { const { tags, npub, list, topic } = useParams(); const [searchParams] = useSearchParams(); @@ -21,6 +24,8 @@ const useNav = () => { const adult = searchParams.get('adult') === 'true' || searchParams.get('nsfw') === 'true'; const replies = searchParams.get('replies') === 'true'; const reposts = searchParams.get('reposts') === 'true'; + const type = searchParams.get('type') as ContentType || 'all'; + const follows = window.location.pathname.startsWith('/follows'); const useTags = tags?.split(',') || []; @@ -31,6 +36,7 @@ const useNav = () => { showReplies: replies, showReposts: reposts, follows, + type, list, topic, }; @@ -50,6 +56,9 @@ const useNav = () => { if (settings.showReposts) { searchParams.push('reposts=true'); } + if (settings.type && settings.type != 'all') { + searchParams.push(`type=${settings.type}`); + } const postfix = searchParams.length > 0 ? `?${searchParams.join('&')}` : ''; console.log(settings);