Profile editor improvements

This commit is contained in:
Kieran 2023-01-03 12:06:53 +00:00
parent 08112a0b3e
commit f62c471848
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
8 changed files with 67 additions and 30 deletions

View File

@ -4,6 +4,11 @@
*/ */
export const DefaultConnectTimeout = 1000; export const DefaultConnectTimeout = 1000;
/**
* How long profile cache should be considered valid for
*/
export const ProfileCacheExpire = (1_000 * 60 * 5);
/** /**
* List of recommended follows for new users * List of recommended follows for new users
*/ */

View File

@ -3,7 +3,9 @@ import { useDispatch, useSelector } from "react-redux";
import EventKind from "../nostr/EventKind"; import EventKind from "../nostr/EventKind";
import { Subscriptions } from "../nostr/Subscriptions"; import { Subscriptions } from "../nostr/Subscriptions";
import { addNotifications, setFollows, setRelays } from "../state/Login"; import { addNotifications, setFollows, setRelays } from "../state/Login";
import { setUserData } from "../state/Users";
import useSubscription from "./Subscription"; import useSubscription from "./Subscription";
import { mapEventToProfile } from "./UsersFeed";
/** /**
* Managed loading data for the current logged in user * Managed loading data for the current logged in user
@ -21,6 +23,7 @@ export default function useLoginFeed() {
sub.Id = `login:${sub.Id}`; sub.Id = `login:${sub.Id}`;
sub.Authors.add(pubKey); sub.Authors.add(pubKey);
sub.Kinds.add(EventKind.ContactList); sub.Kinds.add(EventKind.ContactList);
sub.Kinds.add(EventKind.SetMetadata);
let notifications = new Subscriptions(); let notifications = new Subscriptions();
notifications.Kinds.add(EventKind.TextNote); notifications.Kinds.add(EventKind.TextNote);
@ -34,18 +37,20 @@ export default function useLoginFeed() {
const { notes } = useSubscription(sub, { leaveOpen: true }); const { notes } = useSubscription(sub, { leaveOpen: true });
useEffect(() => { useEffect(() => {
let metadatas = notes.filter(a => a.kind === EventKind.ContactList); let contactList = notes.filter(a => a.kind === EventKind.ContactList);
let others = notes.filter(a => a.kind !== EventKind.ContactList); let notifications = notes.filter(a => a.kind === EventKind.TextNote);
let metadata = notes.filter(a => a.kind === EventKind.SetMetadata).map(a => mapEventToProfile(a));
for(let md of metadatas) { for(let cl of contactList) {
if (md.content !== "") { if (cl.content !== "") {
let relays = JSON.parse(md.content); let relays = JSON.parse(cl.content);
dispatch(setRelays(relays)); dispatch(setRelays(relays));
} }
let pTags = md.tags.filter(a => a[0] === "p").map(a => a[1]); let pTags = cl.tags.filter(a => a[0] === "p").map(a => a[1]);
dispatch(setFollows(pTags)); dispatch(setFollows(pTags));
} }
dispatch(addNotifications(others)); dispatch(addNotifications(notifications));
dispatch(setUserData(metadata));
}, [notes]); }, [notes]);
} }

View File

@ -5,10 +5,9 @@ import { addPubKey } from "../state/Users";
export default function useProfile(pubKey) { export default function useProfile(pubKey) {
const dispatch = useDispatch(); const dispatch = useDispatch();
const user = useSelector(s => s.users.users[pubKey]); const user = useSelector(s => s.users.users[pubKey]);
const pubKeys = useSelector(s => s.users.pubKeys);
useEffect(() => { useEffect(() => {
if (pubKey !== "" && !pubKeys.includes(pubKey)) { if (pubKey !== "") {
dispatch(addPubKey(pubKey)); dispatch(addPubKey(pubKey));
} }
}, [pubKey]); }, [pubKey]);

View File

@ -1,5 +1,6 @@
import { useEffect, useMemo } from "react"; import { useEffect, useMemo } from "react";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { ProfileCacheExpire } from "../Const";
import Event from "../nostr/Event"; import Event from "../nostr/Event";
import EventKind from "../nostr/EventKind"; import EventKind from "../nostr/EventKind";
import { Subscriptions } from "../nostr/Subscriptions"; import { Subscriptions } from "../nostr/Subscriptions";
@ -12,22 +13,11 @@ export default function useUsersCache() {
const users = useSelector(s => s.users.users); const users = useSelector(s => s.users.users);
function isUserCached(id) { function isUserCached(id) {
let expire = new Date().getTime() - (1_000 * 60 * 5); // 5min expire let expire = new Date().getTime() - ProfileCacheExpire;
let u = users[id]; let u = users[id];
return u && u.loaded > expire; return u && u.loaded > expire;
} }
function mapEventToProfile(ev) {
let metaEvent = Event.FromObject(ev);
let data = JSON.parse(metaEvent.Content);
return {
pubkey: metaEvent.PubKey,
fromEvent: ev,
loaded: new Date().getTime(),
...data
};
}
const sub = useMemo(() => { const sub = useMemo(() => {
let needProfiles = pKeys.filter(a => !isUserCached(a)); let needProfiles = pKeys.filter(a => !isUserCached(a));
if (needProfiles.length === 0) { if (needProfiles.length === 0) {
@ -50,3 +40,13 @@ export default function useUsersCache() {
return results; return results;
} }
export function mapEventToProfile(ev) {
let data = JSON.parse(ev.content);
return {
pubkey: ev.pubkey,
fromEvent: ev,
loaded: new Date().getTime(),
...data
};
}

View File

@ -65,7 +65,7 @@ code {
border-radius: 25px; border-radius: 25px;
} }
input[type="text"], input[type="password"] { input[type="text"], input[type="password"], textarea {
padding: 10px; padding: 10px;
border-radius: 5px; border-radius: 5px;
border: 0; border: 0;
@ -91,6 +91,15 @@ input[type="text"], input[type="password"] {
padding: 0 15px; padding: 0 15px;
} }
.f-col {
flex-direction: column;
align-items: flex-start !important;
}
.w-max {
width: 100%;
}
a { a {
color: inherit; color: inherit;
line-height: 1.3em; line-height: 1.3em;

View File

@ -29,6 +29,13 @@
opacity: 0.5; opacity: 0.5;
} }
.profile .editor textarea {
resize: vertical;
width: calc(100% - 30px);
max-height: 300px;
min-height: 60px;
}
@media(max-width: 720px) { @media(max-width: 720px) {
.profile { .profile {
flex-direction: column; flex-direction: column;

View File

@ -89,6 +89,15 @@ export default function ProfilePage() {
}; };
delete userCopy["loaded"]; delete userCopy["loaded"];
delete userCopy["fromEvent"]; delete userCopy["fromEvent"];
// event top level props should not be copied into metadata (bug)
delete userCopy["pubkey"];
delete userCopy["sig"];
delete userCopy["pubkey"];
delete userCopy["tags"];
delete userCopy["content"];
delete userCopy["created_at"];
delete userCopy["id"];
delete userCopy["kind"]
// trim empty string fields // trim empty string fields
Object.keys(userCopy).forEach(k => { Object.keys(userCopy).forEach(k => {
@ -101,7 +110,6 @@ export default function ProfilePage() {
let ev = await publisher.metadata(userCopy); let ev = await publisher.metadata(userCopy);
console.debug(ev); console.debug(ev);
publisher.broadcast(ev); publisher.broadcast(ev);
dispatch(resetProfile(id));
} }
async function openFile() { async function openFile() {
@ -128,17 +136,17 @@ export default function ProfilePage() {
function editor() { function editor() {
return ( return (
<> <div className="editor">
<div className="form-group"> <div className="form-group">
<div>Name:</div> <div>Name:</div>
<div> <div>
<input type="text" value={name} onChange={(e) => setName(e.target.value)} /> <input type="text" value={name} onChange={(e) => setName(e.target.value)} />
</div> </div>
</div> </div>
<div className="form-group"> <div className="form-group f-col">
<div>About:</div> <div>About:</div>
<div> <div className="w-max">
<input type="text" value={about} onChange={(e) => setAbout(e.target.value)} /> <textarea onChange={(e) => setAbout(e.target.value)} value={about}></textarea>
</div> </div>
</div> </div>
<div className="form-group"> <div className="form-group">
@ -167,7 +175,7 @@ export default function ProfilePage() {
<div className="btn" onClick={() => saveProfile()}>Save</div> <div className="btn" onClick={() => saveProfile()}>Save</div>
</div> </div>
</div> </div>
</> </div>
) )
} }

View File

@ -44,6 +44,10 @@ const UsersSlice = createSlice({
for (let x of ud) { for (let x of ud) {
let existing = state.users[x.pubkey]; let existing = state.users[x.pubkey];
if (existing) { if (existing) {
if(existing.fromEvent.created_at > x.fromEvent.created_at) {
// prevent patching with older metadata
continue;
}
x = { x = {
...existing, ...existing,
...x ...x