Tabs progress

This commit is contained in:
Kieran 2023-01-10 10:30:33 +00:00
parent b58d29742b
commit 945302d1b9
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
13 changed files with 115 additions and 23 deletions

View File

@ -13,6 +13,7 @@
"qr-code-styling": "^1.6.0-rc.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-intersection-observer": "^9.4.1",
"react-redux": "^8.0.5",
"react-router-dom": "^6.5.0",
"react-scripts": "5.0.1",

View File

@ -3,6 +3,7 @@ import { Link } from "react-router-dom";
import Invoice from "./element/Invoice";
import { UrlRegex, FileExtensionRegex, MentionRegex, InvoiceRegex, YoutubeUrlRegex } from "./Const";
import { eventLink, hexToBech32, profileLink } from "./Util";
import LazyImage from "./element/LazyImage";
function transformHttpLink(a) {
try {
@ -17,7 +18,7 @@ function transformHttpLink(a) {
case "png":
case "bmp":
case "webp": {
return <img key={url} src={url} />;
return <LazyImage key={url} src={url} />;
}
case "mp4":
case "mov":

View File

@ -0,0 +1,19 @@
import { useMemo } from "react";
import useFollowersFeed from "../feed/FollowersFeed";
import EventKind from "../nostr/EventKind";
import ProfilePreview from "./ProfilePreview";
export default function FollowersList(props) {
const feed = useFollowersFeed(props.pubkey);
const pubKeys = useMemo(() => {
let contactLists = feed?.notes.filter(a => a.kind === EventKind.ContactList && a.tags.some(b => b[0] === "p" && b[1] === props.pubkey));
return [...new Set(contactLists?.map(a => a.pubkey))];
}, [feed]);
return (
<>
{pubKeys?.map(a => <ProfilePreview pubkey={a} key={a} options={{ about: false }}/>)}
</>
)
}

11
src/element/LazyImage.js Normal file
View File

@ -0,0 +1,11 @@
import { useInView } from 'react-intersection-observer';
export default function LazyImage(props) {
const { ref, inView, entry } = useInView();
return (
<div ref={ref}>
{inView ? <img {...props} /> : null}
</div>
)
}

View File

@ -29,7 +29,7 @@
word-break: normal;
}
.note > .body > img, .note > .body > video, .note > .body > iframe {
.note > .body img, .note > .body video, .note > .body iframe {
max-width: 100%;
max-height: 500px;
margin: 10px;
@ -38,7 +38,7 @@
display: block;
}
.note > .header > img:hover, .note > .header > .name > .reply:hover, .note > .body:hover {
.note > .header img:hover, .note > .header .name > .reply:hover, .note .body:hover {
cursor: pointer;
}

View File

@ -3,7 +3,7 @@
align-items: center;
}
.pfp > img {
.pfp img {
width: 40px;
height: 40px;
margin-right: 10px;

View File

@ -4,7 +4,8 @@ import Nostrich from "../nostrich.jpg";
import { useMemo } from "react";
import { Link, useNavigate } from "react-router-dom";
import useProfile from "../feed/ProfileFeed";
import { profileLink } from "../Util";
import { hexToBech32, profileLink } from "../Util";
import LazyImage from "./LazyImage";
export default function ProfileImage({ pubkey, subHeader, showUsername = true }) {
const navigate = useNavigate();
@ -12,7 +13,7 @@ export default function ProfileImage({ pubkey, subHeader, showUsername = true })
const hasImage = (user?.picture?.length ?? 0) > 0;
const name = useMemo(() => {
let name = pubkey.substring(0, 8);
let name = hexToBech32("npub", pubkey).substring(0, 12);
if (user?.display_name?.length > 0) {
name = user.display_name;
} else if (user?.name?.length > 0) {
@ -20,11 +21,11 @@ export default function ProfileImage({ pubkey, subHeader, showUsername = true })
}
return name;
}, [user]);
return (
<div className="pfp">
<img src={hasImage ? user.picture : Nostrich} onClick={() => navigate(profileLink(pubkey))} />
{showUsername && (
<div>
<LazyImage src={hasImage ? user.picture : Nostrich} onClick={() => navigate(profileLink(pubkey))} />
{showUsername && (<div>
<Link key={pubkey} to={profileLink(pubkey)}>{name}</Link>
{subHeader ? <div>{subHeader}</div> : null}
</div>

View File

@ -6,4 +6,5 @@
.profile-preview .pfp {
flex-grow: 1;
min-width: 200px;
}

View File

@ -1,19 +1,23 @@
import "./ProfilePreview.css";
import ProfileImage from "./ProfileImage";
import { useSelector } from "react-redux";
import FollowButton from "./FollowButton";
import useProfile from "../feed/ProfileFeed";
export default function ProfilePreview(props) {
const pubkey = props.pubkey;
const user = useSelector(s => s.users.users[pubkey]);
const user = useProfile(pubkey);
const options = {
about: true,
...props.options
};
return (
<div className="profile-preview">
<ProfileImage pubkey={pubkey}/>
<div className="f-ellipsis">
<ProfileImage pubkey={pubkey} />
{options.about ? <div className="f-ellipsis">
{user?.about}
</div>
<FollowButton pubkey={pubkey} className="ml5"/>
</div> : null}
<FollowButton pubkey={pubkey} className="ml5" />
</div>
)
}

17
src/feed/FollowersFeed.js Normal file
View File

@ -0,0 +1,17 @@
import { useMemo } from "react";
import EventKind from "../nostr/EventKind";
import { Subscriptions } from "../nostr/Subscriptions";
import useSubscription from "./Subscription";
export default function useFollowersFeed(pubkey) {
const sub = useMemo(() => {
let x = new Subscriptions();
x.Id = "followers";
x.Kinds.add(EventKind.ContactList);
x.PTags.add(pubkey);
return x;
}, [pubkey]);
return useSubscription(sub);
}

View File

@ -1,7 +1,7 @@
import "./ProfilePage.css";
import Nostrich from "../nostrich.jpg";
import { useState } from "react";
import { useEffect, useMemo, useState } from "react";
import { useSelector } from "react-redux";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faQrcode, faGear } from "@fortawesome/free-solid-svg-icons";
@ -15,15 +15,30 @@ import { extractLinks } from '../Text'
import LNURLTip from "../element/LNURLTip";
import Nip05 from "../element/Nip05";
import Copy from "../element/Copy";
import ProfilePreview from "../element/ProfilePreview";
import FollowersList from "../element/FollowersList";
const ProfileTab = {
Notes: 0,
Reactions: 1,
Followers: 2,
Follows: 3
};
export default function ProfilePage() {
const params = useParams();
const navigate = useNavigate();
const id = parseId(params.id);
const id = useMemo(() => parseId(params.id), [params]);
const user = useProfile(id);
const loginPubKey = useSelector(s => s.login.publicKey);
const follows = useSelector(s => s.login.follows);
const isMe = loginPubKey === id;
const [showLnQr, setShowLnQr] = useState(false);
const [tab, setTab] = useState(ProfileTab.Notes);
useEffect(() => {
setTab(ProfileTab.Notes);
}, [params]);
function details() {
const lnurl = extractLnAddress(user?.lud16 || user?.lud06 || "");
@ -63,6 +78,21 @@ export default function ProfilePage() {
)
}
function tabContent() {
switch (tab) {
case ProfileTab.Notes: return <Timeline pubkeys={id} />;
case ProfileTab.Follows: {
if (isMe) {
return follows.map(a => <ProfilePreview key={a} pubkey={a.toLowerCase()} options={{ about: false }} />)
}
}
case ProfileTab.Followers: {
return <FollowersList pubkey={id} />
}
}
return null;
}
return (
<>
<div className="profile flex">
@ -75,12 +105,14 @@ export default function ProfilePage() {
</div>
</div>
<div className="tabs">
<div className="tab f-1 active">Notes</div>
<div className="tab f-1">Reactions</div>
<div className="tab f-1">Followers</div>
<div className="tab f-1">Follows</div>
{
Object.entries(ProfileTab).map(([k, v]) => {
return <div className={`tab f-1${tab === v ? " active" : ""}`} key={k} onClick={() => setTab(v)}>{k}</div>
}
)
}
</div>
<Timeline pubkeys={id} />
{tabContent()}
</>
)
}

View File

@ -12,7 +12,7 @@ const UsersSlice = createSlice({
/**
* User objects for known pubKeys, populated async
*/
users: {}
users: {},
},
reducers: {
addPubKey: (state, action) => {

View File

@ -7222,6 +7222,11 @@ react-error-overlay@^6.0.11:
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb"
integrity sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==
react-intersection-observer@^9.4.1:
version "9.4.1"
resolved "https://registry.yarnpkg.com/react-intersection-observer/-/react-intersection-observer-9.4.1.tgz#4ccb21e16acd0b9cf5b28d275af7055bef878f6b"
integrity sha512-IXpIsPe6BleFOEHKzKh5UjwRUaz/JYS0lT/HPsupWEQou2hDqjhLMStc5zyE3eQVT4Fk3FufM8Fw33qW1uyeiw==
react-is@^16.13.1, react-is@^16.7.0:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"