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;
/**
* How long profile cache should be considered valid for
*/
export const ProfileCacheExpire = (1_000 * 60 * 5);
/**
* 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 { Subscriptions } from "../nostr/Subscriptions";
import { addNotifications, setFollows, setRelays } from "../state/Login";
import { setUserData } from "../state/Users";
import useSubscription from "./Subscription";
import { mapEventToProfile } from "./UsersFeed";
/**
* Managed loading data for the current logged in user
@ -21,6 +23,7 @@ export default function useLoginFeed() {
sub.Id = `login:${sub.Id}`;
sub.Authors.add(pubKey);
sub.Kinds.add(EventKind.ContactList);
sub.Kinds.add(EventKind.SetMetadata);
let notifications = new Subscriptions();
notifications.Kinds.add(EventKind.TextNote);
@ -34,18 +37,20 @@ export default function useLoginFeed() {
const { notes } = useSubscription(sub, { leaveOpen: true });
useEffect(() => {
let metadatas = notes.filter(a => a.kind === EventKind.ContactList);
let others = notes.filter(a => a.kind !== EventKind.ContactList);
let contactList = 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) {
if (md.content !== "") {
let relays = JSON.parse(md.content);
for(let cl of contactList) {
if (cl.content !== "") {
let relays = JSON.parse(cl.content);
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(addNotifications(others));
dispatch(addNotifications(notifications));
dispatch(setUserData(metadata));
}, [notes]);
}

View File

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

View File

@ -1,5 +1,6 @@
import { useEffect, useMemo } from "react";
import { useDispatch, useSelector } from "react-redux";
import { ProfileCacheExpire } from "../Const";
import Event from "../nostr/Event";
import EventKind from "../nostr/EventKind";
import { Subscriptions } from "../nostr/Subscriptions";
@ -12,22 +13,11 @@ export default function useUsersCache() {
const users = useSelector(s => s.users.users);
function isUserCached(id) {
let expire = new Date().getTime() - (1_000 * 60 * 5); // 5min expire
let expire = new Date().getTime() - ProfileCacheExpire;
let u = users[id];
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(() => {
let needProfiles = pKeys.filter(a => !isUserCached(a));
if (needProfiles.length === 0) {
@ -49,4 +39,14 @@ export default function useUsersCache() {
}, [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;
}
input[type="text"], input[type="password"] {
input[type="text"], input[type="password"], textarea {
padding: 10px;
border-radius: 5px;
border: 0;
@ -91,6 +91,15 @@ input[type="text"], input[type="password"] {
padding: 0 15px;
}
.f-col {
flex-direction: column;
align-items: flex-start !important;
}
.w-max {
width: 100%;
}
a {
color: inherit;
line-height: 1.3em;

View File

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

View File

@ -89,6 +89,15 @@ export default function ProfilePage() {
};
delete userCopy["loaded"];
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
Object.keys(userCopy).forEach(k => {
@ -101,7 +110,6 @@ export default function ProfilePage() {
let ev = await publisher.metadata(userCopy);
console.debug(ev);
publisher.broadcast(ev);
dispatch(resetProfile(id));
}
async function openFile() {
@ -128,17 +136,17 @@ export default function ProfilePage() {
function editor() {
return (
<>
<div className="editor">
<div className="form-group">
<div>Name:</div>
<div>
<input type="text" value={name} onChange={(e) => setName(e.target.value)} />
</div>
</div>
<div className="form-group">
<div className="form-group f-col">
<div>About:</div>
<div>
<input type="text" value={about} onChange={(e) => setAbout(e.target.value)} />
<div className="w-max">
<textarea onChange={(e) => setAbout(e.target.value)} value={about}></textarea>
</div>
</div>
<div className="form-group">
@ -167,7 +175,7 @@ export default function ProfilePage() {
<div className="btn" onClick={() => saveProfile()}>Save</div>
</div>
</div>
</>
</div>
)
}

View File

@ -40,10 +40,14 @@ const UsersSlice = createSlice({
if (!Array.isArray(ud)) {
ud = [ud];
}
for (let x of ud) {
let existing = state.users[x.pubkey];
if (existing) {
if(existing.fromEvent.created_at > x.fromEvent.created_at) {
// prevent patching with older metadata
continue;
}
x = {
...existing,
...x
@ -58,7 +62,7 @@ const UsersSlice = createSlice({
}
},
resetProfile: (state, action) => {
if(state.users[action.payload]) {
if (state.users[action.payload]) {
delete state.users[action.payload];
state.users = {
...state.users