feat: added video filter

This commit is contained in:
florian 2024-03-15 21:55:23 +01:00
parent 9c2388c2b1
commit d10d514ebf
11 changed files with 77 additions and 52 deletions

View File

@ -48,6 +48,7 @@ const Home = () => {
className="topic"
onClick={() =>
nav({
...currentSettings,
tags: [],
npubs: [],
showReplies: false,

View File

@ -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)}
></img>
{
!loaded && <div style={{ height: 200 }}></div> // Spacer when image is not loaded yet

View File

@ -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}
></img>
))}
{isVisible && mediaIsVideo && (

View File

@ -23,11 +23,8 @@ const ScrollView = ({ settings, images, currentImage, setCurrentImage, setViewMo
const containerRef = useRef<HTMLDivElement>(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 (
<div ref={containerRef} className="scrollview" tabIndex={0}>
<Helmet>

View File

@ -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}` : ''}`);
}
}}
>

View File

@ -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;

View File

@ -9,6 +9,8 @@ type SlideImageProps = {
const SlideImage = ({ url, paused, style, noteId }: SlideImageProps) => {
const loaded = useImageLoaded(url);
// const { optimalImageUrl } = useImageSizes(url);
return (
loaded && (
<div

View File

@ -1,6 +1,6 @@
import { nip19 } from 'nostr-tools';
export const imageProxy = import.meta.env.VITE_IMAGE_PROXY || 'https://imgproxy.slidestr.net';
export const imageProxy = import.meta.env.VITE_IMAGE_PROXY || 'https://images.slidestr.net';
export const appName = import.meta.env.VITE_APP_NAME || 'slidestr.net';
@ -90,7 +90,7 @@ export const topics: { [key: string]: Topic } = {
'stablediffusion',
],
},
bitcoin: { name: '₿itcoin', tags: ['bitcoin', 'plebchain'] },
bitcoin: { name: '₿itcoin', tags: ['bitcoin', 'plebchain', 'hfsp', 'btfd', 'buythedip'] },
nostr: { name: 'Nostr', tags: ['coffeechain', 'nostr', 'zapathon', 'grownostr', 'freedom', 'purple'] },
animals: { name: 'Animals', tags: ['catstr', 'dogstr', 'animal', 'animals', 'bird', 'birds', 'pets'] },
photography: {

View File

@ -2,6 +2,7 @@ import { NDKEvent, NDKFilter, NDKTag } from '@nostr-dev-kit/ndk';
import { adultContentTags, adultPublicKeys, imageProxy, mixedAdultNPubs } from './env';
import uniq from 'lodash/uniq';
import { unixNow } from '../ngine/time';
import { ContentType } from '../utils/useNav';
export type Post = {
event: NDKEvent;
@ -17,7 +18,7 @@ export type NostrImage = {
content?: string;
timestamp?: number;
noteId: string;
type: 'image' | 'video';
type: ContentType;
post: Post;
};
@ -71,7 +72,9 @@ export const extractImageUrls = (text: string): string[] => {
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);
};

View File

@ -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;

View File

@ -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);