This commit is contained in:
Alejandro
2023-02-06 22:42:47 +01:00
committed by GitHub
parent 72ab0e25b4
commit a230b2ce61
33 changed files with 842 additions and 311 deletions

View File

@ -27,10 +27,18 @@ header .pfp .avatar-wrapper {
.header-actions {
display: flex;
flex-direction: row;
align-items: center;
}
.header-actions .btn-rnd {
position: relative;
margin-right: 8px;
}
@media (min-width: 520px) {
.header-actions .btn-rnd {
margin-right: 16px;
}
}
.header-actions .btn-rnd .has-unread {

View File

@ -38,6 +38,7 @@ export default function Layout() {
System.nip42Auth = pub.nip42Auth
}, [pub])
useEffect(() => {
System.UserDb = usingDb;
}, [usingDb])
@ -47,7 +48,7 @@ export default function Layout() {
for (let [k, v] of Object.entries(relays)) {
System.ConnectToRelay(k, v);
}
for (let [k, v] of System.Sockets) {
for (let [k] of System.Sockets) {
if (!relays[k] && !SearchRelays.has(k)) {
System.DisconnectRelay(k);
}

View File

@ -14,7 +14,7 @@
.profile .profile-actions {
position: absolute;
top: 80px;
top: 72px;
right: 0;
display: flex;
flex-direction: row;
@ -27,12 +27,6 @@
align-items: center;
}
@media (min-width: 520px) {
.profile .profile-actions {
top: 120px;
}
}
.profile .profile-actions button:not(:last-child) {
margin-right: 8px;
}
@ -46,7 +40,7 @@
.profile .banner {
width: 100%;
max-width: 720px;
height: 300px;
height: 280px;
}
.profile .profile-actions button.icon:not(:last-child) {
margin-right: 2px;
@ -60,6 +54,7 @@
flex-direction: column;
align-items: flex-start;
position: relative;
overflow: hidden;
}
@ -80,11 +75,6 @@
margin: 0 0 12px 0;
}
.profile .nip05 .nick {
font-weight: normal;
color: var(--gray-light);
}
.profile .avatar-wrapper {
z-index: 1;
}
@ -92,6 +82,8 @@
.profile .avatar-wrapper .avatar {
width: 120px;
height: 120px;
background-image: var(--img-url);
border: 3px solid var(--bg-color);
}
.profile .name {
@ -138,6 +130,7 @@
}
.profile .links {
font-size: 14px;
margin-top: 4px;
margin-left: 2px;
margin-bottom: 12px;
@ -150,6 +143,12 @@
align-items: center;
}
@media (max-width: 720px) {
.profile .lnurl {
display: none;
}
}
.profile .website a {
color: var(--font-color);
}
@ -199,6 +198,34 @@
.qr-modal .modal-body {
width: unset;
margin-top: -120px;
}
.qr-modal .pfp {
flex-direction: column;
align-items: center;
justify-content: center;
}
.qr-modal .pfp .avatar {
width: 48px;
height: 48px;
}
.qr-modal .pfp .avatar-wrapper {
margin: 0 0 8px 0;
}
.qr-modal .pfp .avatar-wrapper .avatar {
border: none;
}
.qr-modal .pfp .username {
text-align: center;
}
.qr-modal canvas {
border-radius: 10px;
}
.profile .zap-amount {

View File

@ -5,6 +5,7 @@ import { useSelector } from "react-redux";
import { useNavigate, useParams } from "react-router-dom";
import { formatShort } from "Number";
import { Tab, TabElement } from "Element/Tabs";
import Link from "Icons/Link";
import Qr from "Icons/Qr";
import Zap from "Icons/Zap";
@ -22,6 +23,7 @@ import LNURLTip from "Element/LNURLTip";
import Nip05 from "Element/Nip05";
import Copy from "Element/Copy";
import ProfilePreview from "Element/ProfilePreview";
import ProfileImage from "Element/ProfileImage";
import FollowersList from "Element/FollowersList";
import BlockList from "Element/BlockList";
import MutedList from "Element/MutedList";
@ -34,15 +36,15 @@ import QrCode from "Element/QrCode";
import Modal from "Element/Modal";
import { ProxyImg } from "Element/ProxyImg"
enum ProfileTab {
Notes = "Notes",
Reactions = "Reactions",
Followers = "Followers",
Follows = "Follows",
Zaps = "Zaps",
Muted = "Muted",
Blocked = "Blocked"
};
const ProfileTab = {
Notes: { text: "Notes", value: 0 },
Reactions: { text: "Reactions", value: 1 },
Followers: { text: "Followers", value: 2 },
Follows: { text: "Follows", value: 3 },
Zaps: { text: "Zaps", value: 4 },
Muted: { text: "Muted", value: 5 },
Blocked: { text: "Blocked", value: 6 },
}
export default function ProfilePage() {
const params = useParams();
@ -54,7 +56,7 @@ export default function ProfilePage() {
const follows = useSelector<RootState, HexKey[]>(s => s.login.follows);
const isMe = loginPubKey === id;
const [showLnQr, setShowLnQr] = useState<boolean>(false);
const [tab, setTab] = useState(ProfileTab.Notes);
const [tab, setTab] = useState<Tab>(ProfileTab.Notes);
const [showProfileQr, setShowProfileQr] = useState<boolean>(false);
const aboutText = user?.about || ''
const about = Text({ content: aboutText, tags: [], users: new Map(), creator: "" })
@ -100,6 +102,15 @@ export default function ProfilePage() {
</div>
)}
{lnurl && (
<div className="lnurl f-ellipsis" onClick={() => setShowLnQr(true)}>
<span className="link-icon">
<Zap />
</span>
{lnurl}
</div>
)}
<LNURLTip svc={lnurl} show={showLnQr} onClose={() => setShowLnQr(false)} author={id} />
</div>
)
@ -168,6 +179,7 @@ export default function ProfilePage() {
</IconButton>
{showProfileQr && (
<Modal className="qr-modal" onClose={() => setShowProfileQr(false)}>
<ProfileImage pubkey={id} />
<QrCode data={`nostr:${hexToBech32("npub", id)}`} link={undefined} className="m10" />
</Modal>
)}
@ -211,8 +223,8 @@ export default function ProfilePage() {
)
}
function renderTab(v: ProfileTab) {
return <div className={`tab f-1${tab === v ? " active" : ""}`} key={v} onClick={() => setTab(v)}>{v}</div>
function renderTab(v: Tab) {
return <TabElement t={v} tab={tab} setTab={setTab} />
}
const w = window.document.querySelector(".page")?.clientWidth;
@ -225,7 +237,7 @@ export default function ProfilePage() {
{userDetails()}
</div>
</div>
<div className="tabs">
<div className="tabs main-content">
{[ProfileTab.Notes, ProfileTab.Followers, ProfileTab.Follows, ProfileTab.Zaps, ProfileTab.Muted].map(renderTab)}
{isMe && renderTab(ProfileTab.Blocked)}
</div>

View File

@ -3,20 +3,21 @@ import { useState } from "react";
import { useSelector } from "react-redux";
import { Link } from "react-router-dom";
import Tabs, { Tab } from "Element/Tabs";
import { RootState } from "State/Store";
import Timeline from "Element/Timeline";
import { HexKey } from "Nostr";
import { TimelineSubject } from "Feed/TimelineFeed";
const RootTab = {
Posts: 0,
PostsAndReplies: 1,
Global: 2
const RootTab: Record<string, Tab> = {
Posts: { text: 'Posts', value: 0, },
PostsAndReplies: { text: 'Conversations', value: 1, },
Global: { text: 'Global', value: 2 },
};
export default function RootPage() {
const [loggedOut, pubKey, follows] = useSelector<RootState, [boolean | undefined, HexKey | undefined, HexKey[]]>(s => [s.login.loggedOut, s.login.publicKey, s.login.follows]);
const [tab, setTab] = useState(RootTab.Posts);
const [tab, setTab] = useState<Tab>(RootTab.Posts);
function followHints() {
if (follows?.length === 0 && pubKey && tab !== RootTab.Global) {
@ -26,24 +27,21 @@ export default function RootPage() {
}
}
const isGlobal = loggedOut || tab === RootTab.Global;
const isGlobal = loggedOut || tab.value === RootTab.Global.value;
const timelineSubect: TimelineSubject = isGlobal ? { type: "global", items: [], discriminator: "all" } : { type: "pubkey", items: follows, discriminator: "follows" };
return (
<>
{pubKey ? <>
<div className="tabs">
<div className={`tab f-1 ${tab === RootTab.Posts ? "active" : ""}`} onClick={() => setTab(RootTab.Posts)}>
Posts
</div>
<div className={`tab f-1 ${tab === RootTab.PostsAndReplies ? "active" : ""}`} onClick={() => setTab(RootTab.PostsAndReplies)}>
Conversations
</div>
<div className={`tab f-1 ${tab === RootTab.Global ? "active" : ""}`} onClick={() => setTab(RootTab.Global)}>
Global
</div>
</div></> : null}
<div className="main-content">
{pubKey && <Tabs tabs={[RootTab.Posts, RootTab.PostsAndReplies, RootTab.Global]} tab={tab} setTab={setTab} />}
</div>
{followHints()}
<Timeline key={tab} subject={timelineSubect} postsOnly={tab === RootTab.Posts} method={"TIME_RANGE"} window={tab === RootTab.Global ? 60 : undefined} />
<Timeline
key={tab.value}
subject={timelineSubect}
postsOnly={tab.value === RootTab.Posts.value}
method={"TIME_RANGE"}
window={tab.value === RootTab.Global.value ? 60 : undefined}
/>
</>
);
}

View File

@ -38,14 +38,14 @@ const SearchPage = () => {
}, []);
return (
<>
<div className="main-content">
<h2>Search</h2>
<div className="flex mb10">
<input type="text" className="f-grow mr10" placeholder="Search.." value={search} onChange={e => setSearch(e.target.value)} />
</div>
{keyword && <Timeline key={keyword} subject={{ type: "keyword", items: [keyword], discriminator: keyword }} postsOnly={false} method={"LIMIT_UNTIL"} />}
</>
</div>
)
}
export default SearchPage;
export default SearchPage;