fix profile state (#420)

This commit is contained in:
BlowaterNostr 2024-03-18 01:05:32 +08:00 committed by GitHub
parent 1bc41d6852
commit 83a81fdf81
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 50 additions and 195 deletions

View File

@ -240,12 +240,6 @@ export class App {
forever(sync_kind_1(this.pool, this.database)); forever(sync_kind_1(this.pool, this.database));
} }
/* my profile */
const myProfileEvent = this.database.getProfilesByPublicKey(this.ctx.publicKey);
if (myProfileEvent != undefined) {
this.model.myProfile = myProfileEvent.profile;
}
// Database // Database
(async () => { (async () => {
let i = 0; let i = 0;
@ -415,7 +409,7 @@ export class AppComponent extends Component<AppProps> {
> >
<EditProfile <EditProfile
ctx={model.app.ctx} ctx={model.app.ctx}
profileGetter={app.database} profile={app.database.getProfilesByPublicKey(myAccountCtx.publicKey)?.profile}
emit={props.eventBus.emit} emit={props.eventBus.emit}
/> />
</div> </div>

View File

@ -15,7 +15,6 @@ export type Model = {
social: Social_Model; social: Social_Model;
// profile // profile
myProfile?: ProfileData;
newProfileField: { newProfileField: {
key: string; key: string;
value: string; value: string;
@ -42,6 +41,5 @@ export function initialModel(): Model {
navigationModel: { navigationModel: {
activeNav: "Public", activeNav: "Public",
}, },
myProfile: undefined,
}; };
} }

View File

@ -32,7 +32,6 @@ import { OtherConfig } from "./config-other.ts";
import { DM_List } from "./conversation-list.ts"; import { DM_List } from "./conversation-list.ts";
import { ContactUpdate } from "./conversation-list.tsx"; import { ContactUpdate } from "./conversation-list.tsx";
import { StartInvite } from "./dm.tsx"; import { StartInvite } from "./dm.tsx";
import { EditGroup, StartEditGroupChatProfile } from "./edit-group.tsx";
import { SaveProfile } from "./edit-profile.tsx"; import { SaveProfile } from "./edit-profile.tsx";
import { EditorEvent, SendMessage } from "./editor.tsx"; import { EditorEvent, SendMessage } from "./editor.tsx";
import { EventDetail, EventDetailItem } from "./event-detail.tsx"; import { EventDetail, EventDetailItem } from "./event-detail.tsx";
@ -70,7 +69,6 @@ export type UI_Interaction_Event =
| UnpinConversation | UnpinConversation
| SignInEvent | SignInEvent
| RelayConfigChange | RelayConfigChange
| StartEditGroupChatProfile
| StartInvite | StartInvite
| ViewRelayDetail | ViewRelayDetail
| ViewRecommendedRelaysList | ViewRecommendedRelaysList
@ -280,24 +278,23 @@ const handle_update_event = async (chan: PutChannel<true>, args: {
// Profile // Profile
// //
else if (event.type == "SaveProfile") { else if (event.type == "SaveProfile") {
if (current_relay.status() != "Open") { if (event.profile == undefined) {
app.toastInputChan.put(() => `${current_relay.url} is not connected yet`);
} else if (event.profile == undefined) {
app.toastInputChan.put(() => "profile is empty"); app.toastInputChan.put(() => "profile is empty");
} else { } else {
const result = await saveProfile( saveProfile(
event.profile, event.profile,
event.ctx, event.ctx,
current_relay, current_relay,
); ).then((result) => {
app.popOverInputChan.put({ children: undefined }); app.popOverInputChan.put({ children: undefined });
if (result instanceof Error) { if (result instanceof Error) {
app.toastInputChan.put( app.toastInputChan.put(
SendingEventRejection(eventBus.emit, current_relay.url, result.message), SendingEventRejection(eventBus.emit, current_relay.url, result.message),
); );
} else { } else {
app.toastInputChan.put(() => "profile has been updated"); app.toastInputChan.put(() => "profile has been updated");
} }
});
} }
} // } //
// //
@ -336,16 +333,6 @@ const handle_update_event = async (chan: PutChannel<true>, args: {
); );
} else if (event.type == "OpenNote") { } else if (event.type == "OpenNote") {
open(`https://nostrapp.link/#${NoteID.FromHex(event.event.id).bech32()}?select=true`); open(`https://nostrapp.link/#${NoteID.FromHex(event.event.id).bech32()}?select=true`);
} else if (event.type == "StartEditGroupChatProfile") {
app.popOverInputChan.put({
children: (
<EditGroup
emit={eventBus.emit}
ctx={event.ctx}
profileGetter={app.database}
/>
),
});
} else if (event.type == "StartInvite") { } else if (event.type == "StartInvite") {
app.popOverInputChan.put({ app.popOverInputChan.put({
children: <div></div>, children: <div></div>,
@ -508,16 +495,7 @@ export async function* Database_Update(
lamport.set(t); lamport.set(t);
} }
if (e.kind == NostrKind.META_DATA || e.kind == NostrKind.DIRECT_MESSAGE) { if (e.kind == NostrKind.META_DATA || e.kind == NostrKind.DIRECT_MESSAGE) {
if (e.kind == NostrKind.META_DATA) { if (e.kind == NostrKind.DIRECT_MESSAGE) {
// my profile update
if (ctx && e.pubkey == ctx.publicKey.hex) {
const newProfile = database.getProfilesByPublicKey(ctx.publicKey);
if (newProfile == undefined) {
throw new Error("impossible");
}
model.myProfile = newProfile.profile;
}
} else if (e.kind == NostrKind.DIRECT_MESSAGE) {
console.log("add event"); console.log("add event");
const err = await dmController.addEvent({ const err = await dmController.addEvent({
...e, ...e,

View File

@ -11,9 +11,9 @@ export type RelayRecord = {
export class DexieDatabase extends Dexie implements EventsAdapter, RelayRecorder, EventMarker, EventRemover { export class DexieDatabase extends Dexie implements EventsAdapter, RelayRecorder, EventMarker, EventRemover {
// 'events' is added by dexie when declaring the stores() // 'events' is added by dexie when declaring the stores()
// We just tell the typing system this is the case // We just tell the typing system this is the case
events!: Table<NostrEvent>; private events!: Table<NostrEvent>;
relayRecords!: Table<RelayRecord>; private relayRecords!: Table<RelayRecord>;
eventMarks!: Table<EventMark>; private eventMarks!: Table<EventMark>;
constructor() { constructor() {
super("Events"); super("Events");
@ -28,7 +28,8 @@ export class DexieDatabase extends Dexie implements EventsAdapter, RelayRecorder
return this.events.toArray(); return this.events.toArray();
} }
async get(keys: Indices) { async get(keys: Indices) {
return this.events.get(keys); const e = await this.events.get(keys);
return e;
} }
async put(e: NostrEvent<NostrKind, Tag>): Promise<void> { async put(e: NostrEvent<NostrKind, Tag>): Promise<void> {
await this.events.put(e); await this.events.put(e);

View File

@ -1,21 +0,0 @@
import { h, render } from "https://esm.sh/preact@10.17.1";
import { InMemoryAccountContext } from "../../libs/nostr.ts/nostr.ts";
import { test_db_view, testEventBus } from "./_setup.test.ts";
import { EditGroup } from "./edit-group.tsx";
const database = await test_db_view();
const ctx = InMemoryAccountContext.Generate();
render(
<EditGroup
emit={testEventBus.emit}
ctx={ctx}
profileGetter={database}
/>,
document.body,
);
for await (const e of testEventBus.onChange()) {
console.log(e);
}

View File

@ -1,37 +0,0 @@
/** @jsx h */
import { h } from "https://esm.sh/preact@10.17.1";
import { PrimaryTextColor, SecondaryBackgroundColor, TitleIconColor } from "./style/colors.ts";
import { GroupIcon } from "./icons/group-icon.tsx";
import { emitFunc } from "../event-bus.ts";
import { ProfileGetter } from "./search.tsx";
import { EditProfile, SaveProfile } from "./edit-profile.tsx";
import { NostrAccountContext } from "../../libs/nostr.ts/nostr.ts";
export type StartEditGroupChatProfile = {
type: "StartEditGroupChatProfile";
ctx: NostrAccountContext;
};
export function EditGroup(props: {
ctx: NostrAccountContext;
profileGetter: ProfileGetter;
emit: emitFunc<SaveProfile>;
}) {
const styles = {
container: `py-6 px-4 bg-[${SecondaryBackgroundColor}]`,
header: {
container: `text-[${PrimaryTextColor}] text-xl flex`,
icon: `w-8 h-8 mr-4 text-[${TitleIconColor}] fill-current`,
},
};
return (
<div class={styles.container}>
<p class={styles.header.container}>
<GroupIcon class={styles.header.icon} />
Update Group
</p>
<EditProfile ctx={props.ctx} profileGetter={props.profileGetter} emit={props.emit} />
</div>
);
}

View File

@ -38,16 +38,21 @@ type profileItem = {
type Props = { type Props = {
ctx: NostrAccountContext; ctx: NostrAccountContext;
profileGetter: ProfileGetter; profile: ProfileData | undefined;
emit: emitFunc<SaveProfile>; emit: emitFunc<SaveProfile>;
}; };
type State = { type State = {
profile: ProfileData | undefined; profileData: ProfileData;
newFieldKeyError: string; newFieldKeyError: string;
}; };
export class EditProfile extends Component<Props, State> { export class EditProfile extends Component<Props, State> {
state: Readonly<State> = {
newFieldKeyError: "",
profileData: {},
};
styles = { styles = {
container: `py-4 bg-[${SecondaryBackgroundColor}]`, container: `py-4 bg-[${SecondaryBackgroundColor}]`,
banner: { banner: {
@ -73,17 +78,6 @@ export class EditProfile extends Component<Props, State> {
}, },
}; };
componentDidMount() {
const { ctx, profileGetter } = this.props;
this.setState({
profile: profileGetter.getProfilesByPublicKey(ctx.publicKey)?.profile,
});
}
shouldComponentUpdate(_: Readonly<Props>, nextState: Readonly<State>, __: any): boolean {
return JSON.stringify(this.state.profile) != JSON.stringify(nextState.profile);
}
newFieldKey = createRef<HTMLInputElement>(); newFieldKey = createRef<HTMLInputElement>();
newFieldValue = createRef<HTMLTextAreaElement>(); newFieldValue = createRef<HTMLTextAreaElement>();
@ -96,8 +90,8 @@ export class EditProfile extends Component<Props, State> {
if (key) { if (key) {
const value = e.currentTarget.value; const value = e.currentTarget.value;
this.setState({ this.setState({
profile: { profileData: {
...this.state.profile, ...this.state.profileData,
[key]: value, [key]: value,
}, },
}); });
@ -117,8 +111,8 @@ export class EditProfile extends Component<Props, State> {
} }
this.setState({ this.setState({
profile: { profileData: {
...this.state.profile, ...this.state.profileData,
[this.newFieldKey.current.value]: this.newFieldValue.current.value, [this.newFieldKey.current.value]: this.newFieldValue.current.value,
}, },
newFieldKeyError: "", newFieldKeyError: "",
@ -132,7 +126,7 @@ export class EditProfile extends Component<Props, State> {
this.props.emit({ this.props.emit({
type: "SaveProfile", type: "SaveProfile",
ctx: this.props.ctx, ctx: this.props.ctx,
profile: this.state.profile, profile: this.state.profileData,
}); });
}; };
@ -140,15 +134,15 @@ export class EditProfile extends Component<Props, State> {
const profileItems: profileItem[] = [ const profileItems: profileItem[] = [
{ {
key: "name", key: "name",
value: this.state.profile?.name, value: this.state.profileData.name || this.props.profile?.name,
}, },
{ {
key: "banner", key: "banner",
value: this.state.profile?.banner, value: this.state.profileData.banner || this.props.profile?.banner,
}, },
{ {
key: "picture", key: "picture",
value: this.state.profile?.picture, value: this.state.profileData.picture || this.props.profile?.picture,
hint: ( hint: (
<span class={this.styles.field.hint.text}> <span class={this.styles.field.hint.text}>
You can upload your images on websites like{" "} You can upload your images on websites like{" "}
@ -160,50 +154,14 @@ export class EditProfile extends Component<Props, State> {
}, },
{ {
key: "about", key: "about",
value: this.state.profile?.about, value: this.state.profileData.about || this.props.profile?.about,
}, },
{ {
key: "website", key: "website",
value: this.state.profile?.website, value: this.state.profileData.website || this.props.profile?.website,
}, },
]; ];
if (this.state.profile) {
for (const [key, value] of Object.entries(this.state.profile)) {
if (["name", "picture", "about", "website", "banner"].includes(key) || !value) {
continue;
}
profileItems.push({
key: key,
value: value,
});
}
}
const banner = this.state.profile?.banner
? (
<div
class={this.styles.banner.container}
style={{
background: `url(${
this.state.profile?.banner ? this.state.profile.banner : "default-bg.png"
}) no-repeat center center / cover`,
}}
>
<Avatar
picture={this.state.profile?.picture || robohash(this.props.ctx.publicKey.hex)}
class={`w-24 h-24 m-auto absolute top-60 left-1/2 rounded-full box-border border-2 border-[${PrimaryTextColor}] -translate-x-2/4`}
/>
</div>
)
: (
<Avatar
picture={this.state.profile?.picture || robohash(this.props.ctx.publicKey.hex)}
class={`w-24 h-24 m-auto box-border border-2 border-[${PrimaryTextColor}]`}
/>
);
const items = profileItems.map((item) => ( const items = profileItems.map((item) => (
<Fragment> <Fragment>
<h3 class={this.styles.field.title} style={{ textTransform: "capitalize" }}> <h3 class={this.styles.field.title} style={{ textTransform: "capitalize" }}>
@ -224,7 +182,6 @@ export class EditProfile extends Component<Props, State> {
return ( return (
<div class={this.styles.container}> <div class={this.styles.container}>
{banner}
{items} {items}
<div class={this.styles.divider}></div> <div class={this.styles.divider}></div>

View File

@ -2,7 +2,6 @@ import { NostrAccountContext } from "../../libs/nostr.ts/nostr.ts";
import { ConnectionPool } from "../../libs/nostr.ts/relay-pool.ts"; import { ConnectionPool } from "../../libs/nostr.ts/relay-pool.ts";
import { parseJSON } from "../features/profile.ts"; import { parseJSON } from "../features/profile.ts";
import { SingleRelayConnection } from "../../libs/nostr.ts/relay-single.ts"; import { SingleRelayConnection } from "../../libs/nostr.ts/relay-single.ts";
import { damus } from "../../libs/nostr.ts/relay-list.test.ts";
export const default_blowater_relay = "wss://blowater.nostr1.com"; export const default_blowater_relay = "wss://blowater.nostr1.com";
@ -43,11 +42,6 @@ export class RelayConfig {
console.error(res); console.error(res);
} }
}); });
config.add(damus).then((res) => {
if (res instanceof Error) {
console.error(res);
}
});
return config; return config;
} }

View File

@ -14,7 +14,7 @@ Deno.test("Database", async () => {
const stream = db.subscribe(); const stream = db.subscribe();
const event_to_add = await prepareNormalNostrEvent(ctx, { kind: NostrKind.TEXT_NOTE, content: "1" }); const event_to_add = await prepareNormalNostrEvent(ctx, { kind: NostrKind.TEXT_NOTE, content: "1" });
await db.addEvent(event_to_add); await db.addEvent(event_to_add);
const e1 = db.get({ id: event_to_add.id }); const e1 = db.getEventByID(event_to_add.id);
if (!e1) { if (!e1) {
fail(); fail();
} }
@ -115,20 +115,20 @@ Deno.test("mark removed event", async () => {
const event_to_add = await prepareNormalNostrEvent(ctx, { kind: NostrKind.TEXT_NOTE, content: "1" }); const event_to_add = await prepareNormalNostrEvent(ctx, { kind: NostrKind.TEXT_NOTE, content: "1" });
const parsed_event = await db.addEvent(event_to_add); const parsed_event = await db.addEvent(event_to_add);
const retrieved_event = db.get({ id: event_to_add.id }); const retrieved_event = db.getEventByID(event_to_add.id);
if (retrieved_event == undefined) fail(); if (retrieved_event == undefined) fail();
assertEquals(parsed_event, retrieved_event); assertEquals(parsed_event, retrieved_event);
assertEquals(retrieved_event.id, event_to_add.id); assertEquals(retrieved_event.id, event_to_add.id);
await db.remove(event_to_add.id); await db.remove(event_to_add.id);
const retrieved_event_2 = db.get({ id: event_to_add.id }); const retrieved_event_2 = db.getEventByID(event_to_add.id);
assertEquals(retrieved_event_2, undefined); assertEquals(retrieved_event_2, undefined);
const added_event = await db.addEvent(event_to_add); const added_event = await db.addEvent(event_to_add);
assertEquals(added_event, false); assertEquals(added_event, false);
const retrieved_event_3 = db.get({ id: event_to_add.id }); const retrieved_event_3 = db.getEventByID(event_to_add.id);
assertEquals(retrieved_event_3, undefined); assertEquals(retrieved_event_3, undefined);
}); });

View File

@ -61,10 +61,8 @@ export interface RelayRecordGetter {
getRelayRecord: (eventID: string) => Set<string>; getRelayRecord: (eventID: string) => Set<string>;
} }
export class Database_View export class Database_View implements ProfileSetter, ProfileGetter, EventRemover, RelayRecordGetter {
implements ProfileSetter, ProfileGetter, EventGetter, EventRemover, RelayRecordGetter { private readonly sourceOfChange = csp.chan<{ event: Parsed_Event; relay?: string }>(buffer_size);
//
public readonly sourceOfChange = csp.chan<{ event: Parsed_Event; relay?: string }>(buffer_size);
private readonly caster = csp.multi<{ event: Parsed_Event; relay?: string }>(this.sourceOfChange); private readonly caster = csp.multi<{ event: Parsed_Event; relay?: string }>(this.sourceOfChange);
private readonly profiles = new Map<string, Profile_Nostr_Event>(); private readonly profiles = new Map<string, Profile_Nostr_Event>();
@ -131,13 +129,6 @@ export class Database_View
return db; return db;
} }
get(keys: Indices): Parsed_Event | undefined {
if (this.removedEvents.has(keys.id)) {
return;
}
return this.events.get(keys.id);
}
getEventByID = (id: string | NoteID) => { getEventByID = (id: string | NoteID) => {
if (id instanceof NoteID) { if (id instanceof NoteID) {
id = id.hex; id = id.hex;
@ -232,12 +223,14 @@ export class Database_View
}; };
// check if the event exists // check if the event exists
const storedEvent = await this.eventsAdapter.get({ id: event.id }); {
if (storedEvent) { // event exist const storedEvent = this.getEventByID(event.id);
if (new_relay_record) { if (storedEvent) { // event exist
this.sourceOfChange.put({ event: parsedEvent, relay: url }); if (new_relay_record) {
this.sourceOfChange.put({ event: parsedEvent, relay: url });
}
return false;
} }
return false;
} }
// add event to database and notify subscribers // add event to database and notify subscribers
@ -246,8 +239,7 @@ export class Database_View
this.events.set(parsedEvent.id, parsedEvent); this.events.set(parsedEvent.id, parsedEvent);
if (parsedEvent.kind == NostrKind.META_DATA) { if (parsedEvent.kind == NostrKind.META_DATA) {
// @ts-ignore const pEvent = parseProfileEvent(parsedEvent as NostrEvent<NostrKind.META_DATA>);
const pEvent = parseProfileEvent(parsedEvent);
if (pEvent instanceof Error) { if (pEvent instanceof Error) {
return pEvent; return pEvent;
} }

View File

@ -1,7 +1,6 @@
import { Parsed_Event, Profile_Nostr_Event } from "../nostr.ts"; import { Parsed_Event, Profile_Nostr_Event } from "../nostr.ts";
import { prepareNormalNostrEvent } from "../../libs/nostr.ts/event.ts"; import { prepareNormalNostrEvent } from "../../libs/nostr.ts/event.ts";
import { ConnectionPool } from "../../libs/nostr.ts/relay-pool.ts";
import { NostrAccountContext, NostrKind } from "../../libs/nostr.ts/nostr.ts"; import { NostrAccountContext, NostrKind } from "../../libs/nostr.ts/nostr.ts";
import { SingleRelayConnection } from "../../libs/nostr.ts/relay-single.ts"; import { SingleRelayConnection } from "../../libs/nostr.ts/relay-single.ts";