From ef4667c8793d6720130294d8d8372e881eb3adc2 Mon Sep 17 00:00:00 2001 From: Martti Malmi Date: Tue, 28 Nov 2023 21:41:53 +0200 Subject: [PATCH] add feed / grid selector to feeds --- packages/app/src/Element/Feed/Timeline.tsx | 8 +++- .../app/src/Element/Feed/TimelineFollows.tsx | 7 +++- .../app/src/Element/Feed/TimelineRenderer.tsx | 41 +++++++++++++++---- packages/app/src/Pages/DeckLayout.tsx | 36 +++++----------- packages/app/src/lang.json | 6 +++ packages/app/src/translations/en.json | 2 + packages/app/tailwind.config.js | 2 + 7 files changed, 66 insertions(+), 36 deletions(-) diff --git a/packages/app/src/Element/Feed/Timeline.tsx b/packages/app/src/Element/Feed/Timeline.tsx index 91148e2a3..c5458942a 100644 --- a/packages/app/src/Element/Feed/Timeline.tsx +++ b/packages/app/src/Element/Feed/Timeline.tsx @@ -1,6 +1,6 @@ import "./Timeline.css"; import { FormattedMessage } from "react-intl"; -import { useCallback, useMemo } from "react"; +import { useCallback, useMemo, useState } from "react"; import { TaggedNostrEvent, EventKind } from "@snort/system"; import { dedupeByPubkey, findTag } from "@/SnortUtils"; @@ -8,7 +8,7 @@ import useTimelineFeed, { TimelineFeed, TimelineSubject } from "@/Feed/TimelineF import useModeration from "@/Hooks/useModeration"; import { LiveStreams } from "@/Element/LiveStreams"; import { unixNow } from "@snort/shared"; -import { TimelineRenderer } from "@/Element/Feed/TimelineRenderer"; +import { DisplayAs, DisplayAsSelector, TimelineRenderer } from "@/Element/Feed/TimelineRenderer"; export interface TimelineProps { postsOnly: boolean; @@ -19,6 +19,7 @@ export interface TimelineProps { now?: number; loadMore?: boolean; noSort?: boolean; + displayAs?: DisplayAs; } /** @@ -33,6 +34,7 @@ const Timeline = (props: TimelineProps) => { }; }, [props]); const feed: TimelineFeed = useTimelineFeed(props.subject, feedOptions); + const [displayAs, setDisplayAs] = useState("feed"); const { muted, isEventMuted } = useModeration(); const filterPosts = useCallback( @@ -70,6 +72,7 @@ const Timeline = (props: TimelineProps) => { return ( <> + setDisplayAs(displayAs)} /> { related={feed.related ?? []} latest={latestAuthors} showLatest={t => onShowLatest(t)} + displayAs={displayAs} /> {(props.loadMore === undefined || props.loadMore === true) && (
diff --git a/packages/app/src/Element/Feed/TimelineFollows.tsx b/packages/app/src/Element/Feed/TimelineFollows.tsx index 0c327b5e7..4cf10cde2 100644 --- a/packages/app/src/Element/Feed/TimelineFollows.tsx +++ b/packages/app/src/Element/Feed/TimelineFollows.tsx @@ -12,7 +12,7 @@ import { LiveStreams } from "@/Element/LiveStreams"; import useLogin from "@/Hooks/useLogin"; import useHashtagsFeed from "@/Feed/HashtagsFeed"; import { ShowMoreInView } from "@/Element/Event/ShowMore"; -import { TimelineRenderer } from "@/Element/Feed/TimelineRenderer"; +import { DisplayAs, DisplayAsSelector, TimelineRenderer } from "@/Element/Feed/TimelineRenderer"; export interface TimelineFollowsProps { postsOnly: boolean; @@ -20,12 +20,14 @@ export interface TimelineFollowsProps { noteFilter?: (ev: NostrEvent) => boolean; noteRenderer?: (ev: NostrEvent) => ReactNode; noteOnClick?: (ev: NostrEvent) => void; + displayAs?: DisplayAs; } /** * A list of notes by "subject" */ const TimelineFollows = (props: TimelineFollowsProps) => { + const [displayAs, setDisplayAs] = useState<"feed" | "grid">("feed"); const [latest, setLatest] = useState(unixNow()); const feed = useSyncExternalStore( cb => FollowsFeed.hook(cb, "*"), @@ -105,6 +107,7 @@ const TimelineFollows = (props: TimelineFollowsProps) => { return ( <> {(props.liveStreams ?? true) && } + setDisplayAs(displayAs)} /> { return {`#${e.context}`}; } }} + displayAs={displayAs} /> {sortedFeed.length > 0 && ( await FollowsFeed.loadMore(system, login, oldest ?? unixNow())} /> @@ -124,4 +128,5 @@ const TimelineFollows = (props: TimelineFollowsProps) => { ); }; + export default TimelineFollows; diff --git a/packages/app/src/Element/Feed/TimelineRenderer.tsx b/packages/app/src/Element/Feed/TimelineRenderer.tsx index df6676c34..82d59c991 100644 --- a/packages/app/src/Element/Feed/TimelineRenderer.tsx +++ b/packages/app/src/Element/Feed/TimelineRenderer.tsx @@ -2,12 +2,14 @@ import { useInView } from "react-intersection-observer"; import ProfileImage from "@/Element/User/ProfileImage"; import { FormattedMessage } from "react-intl"; import Icon from "@/Icons/Icon"; -import {TaggedNostrEvent} from "@snort/system"; +import { TaggedNostrEvent } from "@snort/system"; import { ReactNode } from "react"; import { TimelineFragment } from "@/Element/Feed/TimelineFragment"; -import {transformTextCached} from "@/Hooks/useTextTransformCache"; +import { transformTextCached } from "@/Hooks/useTextTransformCache"; import useImgProxy from "@/Hooks/useImgProxy"; +export type DisplayAs = "grid" | "feed"; + export interface TimelineRendererProps { frags: Array; related: Array; @@ -19,7 +21,7 @@ export interface TimelineRendererProps { noteRenderer?: (ev: TaggedNostrEvent) => ReactNode; noteOnClick?: (ev: TaggedNostrEvent) => void; noteContext?: (ev: TaggedNostrEvent) => ReactNode; - displayAs?: "grid" | "feed"; + displayAs?: DisplayAs; } export function TimelineRenderer(props: TimelineRendererProps) { @@ -49,17 +51,14 @@ export function TimelineRenderer(props: TimelineRendererProps) { className="aspect-square bg-center bg-cover cursor-pointer" key={e.id} style={{ backgroundImage: `url(${proxy(images[0].content)})` }} - onClick={() => props.noteOnClick?.(e)} - >
+ onClick={() => props.noteOnClick?.(e)}> ); }; const noteRenderer = props.noteRenderer || noteImageRenderer; return props.frags.map(frag => ( -
- {frag.events.map(event => noteRenderer(event))} -
+
{frag.events.map(event => noteRenderer(event))}
)); }; @@ -99,3 +98,29 @@ export function TimelineRenderer(props: TimelineRendererProps) { ); } + +type DisplaySelectorProps = { + activeSelection: DisplayAs; + onSelect: (display: DisplayAs) => void; +}; + +export const DisplayAsSelector = ({ activeSelection, onSelect }: DisplaySelectorProps) => { + return ( +
+
onSelect("feed")}> + +
+
onSelect("grid")}> + +
+
+ ); +}; diff --git a/packages/app/src/Pages/DeckLayout.tsx b/packages/app/src/Pages/DeckLayout.tsx index 2e610cc30..d80e13cb1 100644 --- a/packages/app/src/Pages/DeckLayout.tsx +++ b/packages/app/src/Pages/DeckLayout.tsx @@ -12,7 +12,6 @@ import TimelineFollows from "@/Element/Feed/TimelineFollows"; import { transformTextCached } from "@/Hooks/useTextTransformCache"; import Icon from "@/Icons/Icon"; import NotificationsPage from "./Notifications/Notifications"; -import useImgProxy from "@/Hooks/useImgProxy"; import Modal from "@/Element/Modal"; import { Thread } from "@/Element/Event/Thread"; import { RootTabs } from "@/Element/Feed/RootTabs"; @@ -159,36 +158,23 @@ function ArticlesCol() { } function MediaCol({ setThread }: { setThread: (e: NostrLink) => void }) { - const { proxy } = useImgProxy(); return (
-
- { - const parsed = transformTextCached(e.id, e.content, e.tags); - const images = parsed.filter(a => a.type === "media" && a.mimeType?.startsWith("image/")); - return images.length > 0; - }} - noteRenderer={e => { - const parsed = transformTextCached(e.id, e.content, e.tags); - const images = parsed.filter(a => a.type === "media" && a.mimeType?.startsWith("image/")); - - return ( -
setThread(NostrLink.fromEvent(e))}>
- ); - }} - /> -
+ { + const parsed = transformTextCached(e.id, e.content, e.tags); + const images = parsed.filter(a => a.type === "media" && a.mimeType?.startsWith("image/")); + return images.length > 0; + }} + displayAs="grid" + noteOnClick={e => setThread(NostrLink.fromEvent(e))} + />
); } diff --git a/packages/app/src/lang.json b/packages/app/src/lang.json index a0c4dfb2f..605106384 100644 --- a/packages/app/src/lang.json +++ b/packages/app/src/lang.json @@ -534,6 +534,9 @@ "HhcAVH": { "defaultMessage": "You don't follow this person, click here to load media from {link}, or update your preferences to always load media from everybody." }, + "HzfrYu": { + "defaultMessage": "Grid" + }, "IEwZvs": { "defaultMessage": "Are you sure you want to unpin this note?" }, @@ -1059,6 +1062,9 @@ "eSzf2G": { "defaultMessage": "A single zap of {nIn} sats will allocate {nOut} sats to the zap pool." }, + "eW/Bj9": { + "defaultMessage": "Feed" + }, "eXT2QQ": { "defaultMessage": "Group Chat" }, diff --git a/packages/app/src/translations/en.json b/packages/app/src/translations/en.json index 6f2282bdf..e6c03e2a8 100644 --- a/packages/app/src/translations/en.json +++ b/packages/app/src/translations/en.json @@ -176,6 +176,7 @@ "HWbkEK": "Clear cache and reload", "HbefNb": "Open Wallet", "HhcAVH": "You don't follow this person, click here to load media from {link}, or update your preferences to always load media from everybody.", + "HzfrYu": "Grid", "IEwZvs": "Are you sure you want to unpin this note?", "IKKHqV": "Follows", "IVbtTS": "Zap all {n} sats", @@ -348,6 +349,7 @@ "eHAneD": "Reaction emoji", "eJj8HD": "Get Verified", "eSzf2G": "A single zap of {nIn} sats will allocate {nOut} sats to the zap pool.", + "eW/Bj9": "Feed", "eXT2QQ": "Group Chat", "egib+2": "{n,plural,=1{& {n} other} other{& {n} others}}", "fBI91o": "Zap", diff --git a/packages/app/tailwind.config.js b/packages/app/tailwind.config.js index 81a995b05..9f1ad46f7 100644 --- a/packages/app/tailwind.config.js +++ b/packages/app/tailwind.config.js @@ -7,6 +7,7 @@ module.exports = { colors: { "nearly-bg-color": "var(--nearly-bg-color)", "border-color": "var(--border-color)", + highlight: "var(--highlight)", }, textColor: { "nostr-blue": "var(--repost)", @@ -14,6 +15,7 @@ module.exports = { "nostr-orange": "var(--zap)", "nostr-red": "var(--heart)", "nostr-purple": "var(--highlight)", + secondary: "var(--font-secondary-color)", }, spacing: { px: "1px",