forked from Kieran/snort
Remove Users store
Performance improvements for profile loader
This commit is contained in:
parent
f456c09dbe
commit
514616b170
32
src/db/User.ts
Normal file
32
src/db/User.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { HexKey, TaggedRawEvent, UserMetadata } from "../nostr";
|
||||
|
||||
export interface MetadataCache extends UserMetadata {
|
||||
/**
|
||||
* When the object was saved in cache
|
||||
*/
|
||||
loaded: number,
|
||||
|
||||
/**
|
||||
* When the source metadata event was created
|
||||
*/
|
||||
created: number,
|
||||
|
||||
/**
|
||||
* The pubkey of the owner of this metadata
|
||||
*/
|
||||
pubkey: HexKey
|
||||
};
|
||||
|
||||
export function mapEventToProfile(ev: TaggedRawEvent) {
|
||||
try {
|
||||
let data: UserMetadata = JSON.parse(ev.content);
|
||||
return {
|
||||
pubkey: ev.pubkey,
|
||||
created: ev.created_at,
|
||||
loaded: new Date().getTime(),
|
||||
...data
|
||||
} as MetadataCache;
|
||||
} catch (e) {
|
||||
console.error("Failed to parse JSON", ev, e);
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import Dexie, { Table } from 'dexie';
|
||||
import { MetadataCache } from './state/Users';
|
||||
import { MetadataCache } from './User';
|
||||
|
||||
|
||||
export class SnortDB extends Dexie {
|
||||
users!: Table<MetadataCache>;
|
@ -1,5 +1,5 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import {
|
||||
ServiceProvider,
|
||||
@ -15,14 +15,10 @@ import AsyncButton from "./AsyncButton";
|
||||
import LNURLTip from "./LNURLTip";
|
||||
// @ts-ignore
|
||||
import Copy from "./Copy";
|
||||
// @ts-ignore
|
||||
import useProfile from "../feed/ProfileFeed";
|
||||
// @ts-ignore
|
||||
import useEventPublisher from "../feed/EventPublisher";
|
||||
// @ts-ignore
|
||||
import { resetProfile } from "../state/Users";
|
||||
// @ts-ignore
|
||||
import { hexToBech32 } from "../Util";
|
||||
import { UserMetadata } from "../nostr";
|
||||
|
||||
type Nip05ServiceProps = {
|
||||
name: string,
|
||||
@ -35,10 +31,9 @@ type Nip05ServiceProps = {
|
||||
type ReduxStore = any;
|
||||
|
||||
export default function Nip5Service(props: Nip05ServiceProps) {
|
||||
const dispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
const pubkey = useSelector<ReduxStore, string>(s => s.login.publicKey);
|
||||
const user: any = useProfile(pubkey);
|
||||
const user = useProfile(pubkey);
|
||||
const publisher = useEventPublisher();
|
||||
const svc = new ServiceProvider(props.service);
|
||||
const [serviceConfig, setServiceConfig] = useState<ServiceConfig>();
|
||||
@ -71,11 +66,11 @@ export default function Nip5Service(props: Nip05ServiceProps) {
|
||||
setError(undefined);
|
||||
setAvailabilityResponse(undefined);
|
||||
if (handle && domain) {
|
||||
if(handle.length < (domainConfig?.length[0] ?? 2)) {
|
||||
if (handle.length < (domainConfig?.length[0] ?? 2)) {
|
||||
setAvailabilityResponse({ available: false, why: "TOO_SHORT" });
|
||||
return;
|
||||
}
|
||||
if(handle.length > (domainConfig?.length[1] ?? 20)) {
|
||||
if (handle.length > (domainConfig?.length[1] ?? 20)) {
|
||||
setAvailabilityResponse({ available: false, why: "TOO_LONG" });
|
||||
return;
|
||||
}
|
||||
@ -149,17 +144,15 @@ export default function Nip5Service(props: Nip05ServiceProps) {
|
||||
}
|
||||
|
||||
async function updateProfile(handle: string, domain: string) {
|
||||
let newProfile = {
|
||||
...user,
|
||||
nip05: `${handle}@${domain}`
|
||||
};
|
||||
delete newProfile["loaded"];
|
||||
delete newProfile["fromEvent"];
|
||||
delete newProfile["pubkey"];
|
||||
let ev = await publisher.metadata(newProfile);
|
||||
dispatch(resetProfile(pubkey));
|
||||
publisher.broadcast(ev);
|
||||
navigate("/settings");
|
||||
if (user) {
|
||||
let newProfile = {
|
||||
...user,
|
||||
nip05: `${handle}@${domain}`
|
||||
} as UserMetadata;
|
||||
let ev = await publisher.metadata(newProfile);
|
||||
publisher.broadcast(ev);
|
||||
navigate("/settings");
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -1,6 +1,5 @@
|
||||
import "./Note.css";
|
||||
import { useCallback } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import Event from "../nostr/Event";
|
||||
@ -10,15 +9,15 @@ import { eventLink, hexToBech32 } from "../Util";
|
||||
import NoteFooter from "./NoteFooter";
|
||||
import NoteTime from "./NoteTime";
|
||||
import EventKind from "../nostr/EventKind";
|
||||
import useProfile from "../feed/ProfileFeed";
|
||||
|
||||
export default function Note(props) {
|
||||
const navigate = useNavigate();
|
||||
const opt = props.options;
|
||||
const dataEvent = props["data-ev"];
|
||||
const { data, isThread, reactions, deletion, hightlight } = props
|
||||
const { data, isThread, reactions, deletion, hightlight, options: opt, ["data-ev"]: parsedEvent } = props
|
||||
const ev = useMemo(() => parsedEvent ?? new Event(data), [data]);
|
||||
const pubKeys = useMemo(() => ev.Thread?.PubKeys || [], [ev]);
|
||||
|
||||
const users = useSelector(s => s.users?.users);
|
||||
const ev = dataEvent ?? new Event(data);
|
||||
const users = useProfile(pubKeys);
|
||||
|
||||
const options = {
|
||||
showHeader: true,
|
||||
@ -32,8 +31,8 @@ export default function Note(props) {
|
||||
if (deletion?.length > 0) {
|
||||
return (<b className="error">Deleted</b>);
|
||||
}
|
||||
return <Text content={body} tags={ev.Tags} users={users} />;
|
||||
}, [data, dataEvent, reactions, deletion]);
|
||||
return <Text content={body} tags={ev.Tags} users={users || []} />;
|
||||
}, [props]);
|
||||
|
||||
function goToEvent(e, id) {
|
||||
if (!window.location.pathname.startsWith("/e/")) {
|
||||
@ -49,7 +48,7 @@ export default function Note(props) {
|
||||
|
||||
const maxMentions = 2;
|
||||
let replyId = ev.Thread?.ReplyTo?.Event ?? ev.Thread?.Root?.Event;
|
||||
let mentions = ev.Thread?.PubKeys?.map(a => [a, users[a]])?.map(a => (a[1]?.name?.length ?? 0) > 0 ? a[1].name : hexToBech32("npub", a[0]).substring(0, 12))
|
||||
let mentions = ev.Thread?.PubKeys?.map(a => [a, users ? users[a] : null])?.map(a => (a[1]?.name?.length ?? 0) > 0 ? a[1].name : hexToBech32("npub", a[0]).substring(0, 12))
|
||||
.sort((a, b) => a.startsWith("npub") ? 1 : -1);
|
||||
let othersLength = mentions.length - maxMentions
|
||||
let pubMentions = mentions.length > maxMentions ? `${mentions?.slice(0, maxMentions).join(", ")} & ${othersLength} other${othersLength > 1 ? 's' : ''}` : mentions?.join(", ");
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { useState, Component } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useState } from "react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faPaperclip } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
@ -20,7 +19,6 @@ export function NoteCreator(props) {
|
||||
const [note, setNote] = useState("");
|
||||
const [error, setError] = useState("");
|
||||
const [active, setActive] = useState(false);
|
||||
const users = useSelector((state) => state.users.users)
|
||||
|
||||
async function sendNote() {
|
||||
let ev = replyTo ? await publisher.reply(replyTo, note) : await publisher.note(note);
|
||||
@ -71,7 +69,6 @@ export function NoteCreator(props) {
|
||||
<Textarea
|
||||
autoFocus={autoFocus}
|
||||
className={`textarea ${active ? "textarea--focused" : ""}`}
|
||||
users={users}
|
||||
onChange={onChange}
|
||||
value={note}
|
||||
onFocus={() => setActive(true)}
|
||||
|
@ -7,13 +7,14 @@ import useEventPublisher from "../feed/EventPublisher";
|
||||
import { normalizeReaction, Reaction } from "../Util";
|
||||
import { NoteCreator } from "./NoteCreator";
|
||||
import LNURLTip from "./LNURLTip";
|
||||
import useProfile from "../feed/ProfileFeed";
|
||||
|
||||
export default function NoteFooter(props) {
|
||||
const reactions = props.reactions;
|
||||
const ev = props.ev;
|
||||
|
||||
const login = useSelector(s => s.login.publicKey);
|
||||
const author = useSelector(s => s.users.users[ev.RootPubKey]);
|
||||
const author = useProfile(ev.RootPubKey);
|
||||
const publisher = useEventPublisher();
|
||||
const [reply, setReply] = useState(false);
|
||||
const [tip, setTip] = useState(false);
|
||||
@ -105,10 +106,10 @@ export default function NoteFooter(props) {
|
||||
})}
|
||||
</div>
|
||||
<NoteCreator
|
||||
autoFocus={true}
|
||||
replyTo={ev}
|
||||
onSend={(e) => setReply(false)}
|
||||
show={reply}
|
||||
autoFocus={true}
|
||||
replyTo={ev}
|
||||
onSend={(e) => setReply(false)}
|
||||
show={reply}
|
||||
/>
|
||||
<LNURLTip svc={author?.lud16 || author?.lud06} onClose={(e) => setTip(false)} show={tip} />
|
||||
</>
|
||||
|
@ -38,11 +38,11 @@ function transformHttpLink(a) {
|
||||
return <a key={url} href={url} onClick={(e) => e.stopPropagation()}>{url.toString()}</a>
|
||||
}
|
||||
} else if (tweetId) {
|
||||
return (
|
||||
<div className="tweet" key={tweetId}>
|
||||
<TwitterTweetEmbed tweetId={tweetId} />
|
||||
</div>
|
||||
)
|
||||
return (
|
||||
<div className="tweet" key={tweetId}>
|
||||
<TwitterTweetEmbed tweetId={tweetId} />
|
||||
</div>
|
||||
)
|
||||
} else if (youtubeId) {
|
||||
return (
|
||||
<>
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { useSelector } from "react-redux";
|
||||
import { useLiveQuery } from "dexie-react-hooks";
|
||||
|
||||
import ReactTextareaAutocomplete from "@webscopeio/react-textarea-autocomplete";
|
||||
@ -12,7 +11,7 @@ import "./Textarea.css";
|
||||
import Nostrich from "../nostrich.jpg";
|
||||
import { hexToBech32 } from "../Util";
|
||||
import { db } from "../db";
|
||||
import { MetadataCache } from "../state/Users";
|
||||
import { MetadataCache } from "../db/User";
|
||||
|
||||
function searchUsers(query: string, users: MetadataCache[]) {
|
||||
const q = query.toLowerCase()
|
||||
@ -38,40 +37,28 @@ const UserItem = ({ pubkey, display_name, picture, nip05, ...rest }: MetadataCac
|
||||
)
|
||||
}
|
||||
|
||||
function normalizeUser({ pubkey, picture, nip05, name, display_name }: MetadataCache) {
|
||||
return { pubkey, nip05, name, picture, display_name }
|
||||
}
|
||||
|
||||
const Textarea = ({ users, onChange, ...rest }: any) => {
|
||||
const normalizedUsers = Object.keys(users).reduce((acc, pk) => {
|
||||
return { ...acc, [pk]: normalizeUser(users[pk]) }
|
||||
}, {})
|
||||
const dbUsers = useLiveQuery(
|
||||
() => db.users.toArray().then(usrs => {
|
||||
return usrs.reduce((acc, usr) => {
|
||||
return { ...acc, [usr.pubkey]: normalizeUser(usr) }
|
||||
}, {})
|
||||
})
|
||||
)
|
||||
const allUsers: MetadataCache[] = Object.values({ ...normalizedUsers, ...dbUsers })
|
||||
const allUsers = useLiveQuery(
|
||||
() => db.users.toArray()
|
||||
);
|
||||
|
||||
return (
|
||||
<ReactTextareaAutocomplete
|
||||
{...rest}
|
||||
loadingComponent={() => <span>Loading....</span>}
|
||||
placeholder="Say something!"
|
||||
onChange={onChange}
|
||||
textAreaComponent={TextareaAutosize}
|
||||
trigger={{
|
||||
"@": {
|
||||
afterWhitespace: true,
|
||||
dataProvider: token => dbUsers ? searchUsers(token, allUsers) : [],
|
||||
component: (props: any) => <UserItem {...props.entity} />,
|
||||
output: (item: any) => `@${hexToBech32("npub", item.pubkey)}`
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
return (
|
||||
<ReactTextareaAutocomplete
|
||||
{...rest}
|
||||
loadingComponent={() => <span>Loading....</span>}
|
||||
placeholder="Say something!"
|
||||
onChange={onChange}
|
||||
textAreaComponent={TextareaAutosize}
|
||||
trigger={{
|
||||
"@": {
|
||||
afterWhitespace: true,
|
||||
dataProvider: token => allUsers ? searchUsers(token, allUsers) : [],
|
||||
component: (props: any) => <UserItem {...props.entity} />,
|
||||
output: (item: any) => `@${hexToBech32("npub", item.pubkey)}`
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default Textarea
|
||||
|
@ -5,10 +5,9 @@ import EventKind from "../nostr/EventKind";
|
||||
import { Subscriptions } from "../nostr/Subscriptions";
|
||||
import { addDirectMessage, addNotifications, setFollows, setRelays } from "../state/Login";
|
||||
import { RootState } from "../state/Store";
|
||||
import { setUserData } from "../state/Users";
|
||||
import { db } from "../db";
|
||||
import useSubscription from "./Subscription";
|
||||
import { mapEventToProfile } from "./UsersFeed";
|
||||
import { mapEventToProfile } from "../db/User";
|
||||
|
||||
/**
|
||||
* Managed loading data for the current logged in user
|
||||
@ -63,7 +62,6 @@ export default function useLoginFeed() {
|
||||
}
|
||||
}
|
||||
dispatch(addNotifications(notifications));
|
||||
dispatch(setUserData(profiles));
|
||||
db.users.bulkPut(profiles);
|
||||
dispatch(addDirectMessage(dms));
|
||||
}, [main]);
|
||||
|
@ -1,16 +1,26 @@
|
||||
import { useLiveQuery } from "dexie-react-hooks";
|
||||
import { useEffect } from "react";
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { db } from "../db";
|
||||
import { HexKey } from "../nostr";
|
||||
import { System } from "../nostr/System";
|
||||
|
||||
export default function useProfile(pubKey: HexKey) {
|
||||
export default function useProfile(pubKey: HexKey | Array<HexKey>) {
|
||||
const user = useLiveQuery(async () => {
|
||||
return await db.users.get(pubKey);
|
||||
if (pubKey) {
|
||||
if (Array.isArray(pubKey)) {
|
||||
let ret = await db.users.bulkGet(pubKey);
|
||||
return ret.filter(a => a !== undefined).map(a => a!);
|
||||
} else {
|
||||
return await db.users.get(pubKey);
|
||||
}
|
||||
}
|
||||
}, [pubKey]);
|
||||
|
||||
useEffect(() => {
|
||||
System.GetMetadata(pubKey);
|
||||
if (pubKey) {
|
||||
System.TrackMetadata(pubKey);
|
||||
return () => System.UntrackMetadata(pubKey);
|
||||
}
|
||||
}, [pubKey]);
|
||||
|
||||
return user;
|
||||
|
@ -1,63 +0,0 @@
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { ProfileCacheExpire } from "../Const";
|
||||
import { HexKey, TaggedRawEvent, UserMetadata } from "../nostr";
|
||||
import EventKind from "../nostr/EventKind";
|
||||
import { db } from "../db";
|
||||
import { Subscriptions } from "../nostr/Subscriptions";
|
||||
import { RootState } from "../state/Store";
|
||||
import { MetadataCache, setUserData } from "../state/Users";
|
||||
import useSubscription from "./Subscription";
|
||||
|
||||
export default function useUsersCache() {
|
||||
const dispatch = useDispatch();
|
||||
const pKeys = useSelector<RootState, HexKey[]>(s => s.users.pubKeys);
|
||||
const users = useSelector<RootState, any>(s => s.users.users);
|
||||
|
||||
function isUserCached(id: HexKey) {
|
||||
let expire = new Date().getTime() - ProfileCacheExpire;
|
||||
let u = users[id];
|
||||
return u !== undefined && u.loaded > expire;
|
||||
}
|
||||
|
||||
const sub = useMemo(() => {
|
||||
let needProfiles = pKeys.filter(a => !isUserCached(a));
|
||||
if (needProfiles.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let sub = new Subscriptions();
|
||||
sub.Id = `profiles:${sub.Id}`;
|
||||
sub.Authors = new Set(needProfiles.slice(0, 20));
|
||||
sub.Kinds = new Set([EventKind.SetMetadata]);
|
||||
|
||||
return sub;
|
||||
}, [pKeys]);
|
||||
|
||||
const results = useSubscription(sub);
|
||||
|
||||
useEffect(() => {
|
||||
let profiles: MetadataCache[] = results.notes
|
||||
.map(a => mapEventToProfile(a))
|
||||
.filter(a => a !== undefined)
|
||||
.map(a => a!);
|
||||
dispatch(setUserData(profiles));
|
||||
db.users.bulkPut(profiles);
|
||||
}, [results]);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
export function mapEventToProfile(ev: TaggedRawEvent) {
|
||||
try {
|
||||
let data: UserMetadata = JSON.parse(ev.content);
|
||||
return {
|
||||
pubkey: ev.pubkey,
|
||||
created: ev.created_at,
|
||||
loaded: new Date().getTime(),
|
||||
...data
|
||||
} as MetadataCache;
|
||||
} catch (e) {
|
||||
console.error("Failed to parse JSON", ev, e);
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import { HexKey, TaggedRawEvent } from ".";
|
||||
import { ProfileCacheExpire } from "../Const";
|
||||
import { db } from "../db";
|
||||
import { mapEventToProfile } from "../feed/UsersFeed";
|
||||
import { mapEventToProfile, MetadataCache } from "../db/User";
|
||||
import Connection, { RelaySettings } from "./Connection";
|
||||
import Event from "./Event";
|
||||
import EventKind from "./EventKind";
|
||||
@ -90,9 +90,25 @@ export class NostrSystem {
|
||||
}
|
||||
}
|
||||
|
||||
GetMetadata(pk: HexKey) {
|
||||
if (pk.length > 0) {
|
||||
this.WantsMetadata.add(pk);
|
||||
/**
|
||||
* Request profile metadata for a set of pubkeys
|
||||
*/
|
||||
TrackMetadata(pk: HexKey | Array<HexKey>) {
|
||||
for (let p of Array.isArray(pk) ? pk : [pk]) {
|
||||
if (p.length > 0) {
|
||||
this.WantsMetadata.add(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop tracking metadata for a set of pubkeys
|
||||
*/
|
||||
UntrackMetadata(pk: HexKey | Array<HexKey>) {
|
||||
for (let p of Array.isArray(pk) ? pk : [pk]) {
|
||||
if (p.length > 0) {
|
||||
this.WantsMetadata.delete(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -139,11 +155,11 @@ export class NostrSystem {
|
||||
|
||||
async _FetchMetadata() {
|
||||
let missing = new Set<HexKey>();
|
||||
let meta = await db.users.bulkGet(Array.from(this.WantsMetadata));
|
||||
let now = new Date().getTime();
|
||||
for (let pk of this.WantsMetadata) {
|
||||
let meta = await db.users.get(pk);
|
||||
let now = new Date().getTime();
|
||||
this.WantsMetadata.delete(pk); // always remove from wants list
|
||||
if (!meta || meta.loaded < (now - ProfileCacheExpire)) {
|
||||
let m = meta.find(a => a?.pubkey === pk);
|
||||
if (!m || m.loaded < (now - ProfileCacheExpire)) {
|
||||
missing.add(pk);
|
||||
// cap 100 missing profiles
|
||||
if (missing.size >= 100) {
|
||||
@ -153,19 +169,27 @@ export class NostrSystem {
|
||||
}
|
||||
|
||||
if (missing.size > 0) {
|
||||
console.debug("Wants: ", missing);
|
||||
console.debug("Wants profiles: ", missing);
|
||||
|
||||
let sub = new Subscriptions();
|
||||
sub.Id = `profiles:${sub.Id}`;
|
||||
sub.Kinds = new Set([EventKind.SetMetadata]);
|
||||
sub.Authors = missing;
|
||||
sub.OnEvent = (e) => {
|
||||
sub.OnEvent = async (e) => {
|
||||
let profile = mapEventToProfile(e);
|
||||
if (profile) {
|
||||
db.users.put(profile);
|
||||
await db.users.put(profile);
|
||||
}
|
||||
}
|
||||
await this.RequestSubscription(sub);
|
||||
let results = await this.RequestSubscription(sub);
|
||||
let couldNotFetch = Array.from(missing).filter(a => !results.some(b => b.pubkey === a));
|
||||
console.debug("No profiles: ", couldNotFetch);
|
||||
await db.users.bulkPut(couldNotFetch.map(a => {
|
||||
return {
|
||||
pubkey: a,
|
||||
loaded: new Date().getTime()
|
||||
} as MetadataCache;
|
||||
}));
|
||||
}
|
||||
|
||||
setTimeout(() => this._FetchMetadata(), 500);
|
||||
|
@ -9,7 +9,6 @@ import { System } from "../nostr/System"
|
||||
import ProfileImage from "../element/ProfileImage";
|
||||
import { init } from "../state/Login";
|
||||
import useLoginFeed from "../feed/LoginFeed";
|
||||
import useUsersCache from "../feed/UsersFeed";
|
||||
|
||||
export default function Layout(props) {
|
||||
const dispatch = useDispatch();
|
||||
@ -19,7 +18,6 @@ export default function Layout(props) {
|
||||
const relays = useSelector(s => s.login.relays);
|
||||
const notifications = useSelector(s => s.login.notifications);
|
||||
const readNotifications = useSelector(s => s.login.readNotifications);
|
||||
useUsersCache();
|
||||
useLoginFeed();
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -11,7 +11,6 @@ import useEventPublisher from "../feed/EventPublisher";
|
||||
import useProfile from "../feed/ProfileFeed";
|
||||
import VoidUpload from "../feed/VoidUpload";
|
||||
import { logout, setRelays } from "../state/Login";
|
||||
import { resetProfile } from "../state/Users";
|
||||
import { hexToBech32, openFile } from "../Util";
|
||||
import Relay from "../element/Relay";
|
||||
import Copy from "../element/Copy";
|
||||
@ -87,7 +86,6 @@ export default function SettingsPage(props) {
|
||||
|
||||
let ev = await publisher.metadata(userCopy);
|
||||
console.debug(ev);
|
||||
dispatch(resetProfile(id));
|
||||
publisher.broadcast(ev);
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,8 @@
|
||||
import { configureStore } from "@reduxjs/toolkit";
|
||||
import { reducer as UsersReducer } from "./Users";
|
||||
import { reducer as LoginReducer } from "./Login";
|
||||
|
||||
const store = configureStore({
|
||||
reducer: {
|
||||
users: UsersReducer,
|
||||
login: LoginReducer
|
||||
}
|
||||
});
|
||||
|
@ -1,86 +0,0 @@
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
|
||||
import { HexKey, UserMetadata } from '../nostr';
|
||||
|
||||
export interface MetadataCache extends UserMetadata {
|
||||
/**
|
||||
* When the object was saved in cache
|
||||
*/
|
||||
loaded: number,
|
||||
|
||||
/**
|
||||
* When the source metadata event was created
|
||||
*/
|
||||
created: number,
|
||||
|
||||
/**
|
||||
* The pubkey of the owner of this metadata
|
||||
*/
|
||||
pubkey: HexKey
|
||||
};
|
||||
|
||||
export interface UsersStore {
|
||||
pubKeys: HexKey[],
|
||||
users: any
|
||||
};
|
||||
|
||||
const UsersSlice = createSlice({
|
||||
name: "Users",
|
||||
initialState: {
|
||||
pubKeys: [],
|
||||
users: {},
|
||||
} as UsersStore,
|
||||
reducers: {
|
||||
addPubKey: (state, action: PayloadAction<string | Array<string>>) => {
|
||||
let keys = action.payload;
|
||||
if (!Array.isArray(keys)) {
|
||||
keys = [keys];
|
||||
}
|
||||
let changes = false;
|
||||
let temp = new Set(state.pubKeys);
|
||||
for (let k of keys) {
|
||||
if (!temp.has(k)) {
|
||||
changes = true;
|
||||
temp.add(k);
|
||||
}
|
||||
}
|
||||
if (changes) {
|
||||
state.pubKeys = Array.from(temp);
|
||||
}
|
||||
},
|
||||
setUserData: (state, action: PayloadAction<MetadataCache | Array<MetadataCache>>) => {
|
||||
let ud = action.payload;
|
||||
if (!Array.isArray(ud)) {
|
||||
ud = [ud];
|
||||
}
|
||||
|
||||
for (let x of ud) {
|
||||
let existing = state.users[x.pubkey];
|
||||
if (existing) {
|
||||
if (existing.created > x.created) {
|
||||
// prevent patching with older metadata
|
||||
continue;
|
||||
}
|
||||
x = {
|
||||
...existing,
|
||||
...x
|
||||
};
|
||||
}
|
||||
state.users[x.pubkey] = x;
|
||||
state.users = {
|
||||
...state.users
|
||||
};
|
||||
}
|
||||
},
|
||||
resetProfile: (state, action: PayloadAction<HexKey>) => {
|
||||
if (action.payload in state.users) {
|
||||
delete state.users[action.payload];
|
||||
state.users = {
|
||||
...state.users
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export const { addPubKey, setUserData, resetProfile } = UsersSlice.actions;
|
||||
export const reducer = UsersSlice.reducer;
|
Loading…
Reference in New Issue
Block a user