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

View File

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

View File

@ -32,7 +32,6 @@ import { OtherConfig } from "./config-other.ts";
import { DM_List } from "./conversation-list.ts";
import { ContactUpdate } from "./conversation-list.tsx";
import { StartInvite } from "./dm.tsx";
import { EditGroup, StartEditGroupChatProfile } from "./edit-group.tsx";
import { SaveProfile } from "./edit-profile.tsx";
import { EditorEvent, SendMessage } from "./editor.tsx";
import { EventDetail, EventDetailItem } from "./event-detail.tsx";
@ -70,7 +69,6 @@ export type UI_Interaction_Event =
| UnpinConversation
| SignInEvent
| RelayConfigChange
| StartEditGroupChatProfile
| StartInvite
| ViewRelayDetail
| ViewRecommendedRelaysList
@ -280,24 +278,23 @@ const handle_update_event = async (chan: PutChannel<true>, args: {
// Profile
//
else if (event.type == "SaveProfile") {
if (current_relay.status() != "Open") {
app.toastInputChan.put(() => `${current_relay.url} is not connected yet`);
} else if (event.profile == undefined) {
if (event.profile == undefined) {
app.toastInputChan.put(() => "profile is empty");
} else {
const result = await saveProfile(
saveProfile(
event.profile,
event.ctx,
current_relay,
);
app.popOverInputChan.put({ children: undefined });
if (result instanceof Error) {
app.toastInputChan.put(
SendingEventRejection(eventBus.emit, current_relay.url, result.message),
);
} else {
app.toastInputChan.put(() => "profile has been updated");
}
).then((result) => {
app.popOverInputChan.put({ children: undefined });
if (result instanceof Error) {
app.toastInputChan.put(
SendingEventRejection(eventBus.emit, current_relay.url, result.message),
);
} else {
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") {
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") {
app.popOverInputChan.put({
children: <div></div>,
@ -508,16 +495,7 @@ export async function* Database_Update(
lamport.set(t);
}
if (e.kind == NostrKind.META_DATA || e.kind == NostrKind.DIRECT_MESSAGE) {
if (e.kind == NostrKind.META_DATA) {
// 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) {
if (e.kind == NostrKind.DIRECT_MESSAGE) {
console.log("add event");
const err = await dmController.addEvent({
...e,

View File

@ -11,9 +11,9 @@ export type RelayRecord = {
export class DexieDatabase extends Dexie implements EventsAdapter, RelayRecorder, EventMarker, EventRemover {
// 'events' is added by dexie when declaring the stores()
// We just tell the typing system this is the case
events!: Table<NostrEvent>;
relayRecords!: Table<RelayRecord>;
eventMarks!: Table<EventMark>;
private events!: Table<NostrEvent>;
private relayRecords!: Table<RelayRecord>;
private eventMarks!: Table<EventMark>;
constructor() {
super("Events");
@ -28,7 +28,8 @@ export class DexieDatabase extends Dexie implements EventsAdapter, RelayRecorder
return this.events.toArray();
}
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> {
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 = {
ctx: NostrAccountContext;
profileGetter: ProfileGetter;
profile: ProfileData | undefined;
emit: emitFunc<SaveProfile>;
};
type State = {
profile: ProfileData | undefined;
profileData: ProfileData;
newFieldKeyError: string;
};
export class EditProfile extends Component<Props, State> {
state: Readonly<State> = {
newFieldKeyError: "",
profileData: {},
};
styles = {
container: `py-4 bg-[${SecondaryBackgroundColor}]`,
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>();
newFieldValue = createRef<HTMLTextAreaElement>();
@ -96,8 +90,8 @@ export class EditProfile extends Component<Props, State> {
if (key) {
const value = e.currentTarget.value;
this.setState({
profile: {
...this.state.profile,
profileData: {
...this.state.profileData,
[key]: value,
},
});
@ -117,8 +111,8 @@ export class EditProfile extends Component<Props, State> {
}
this.setState({
profile: {
...this.state.profile,
profileData: {
...this.state.profileData,
[this.newFieldKey.current.value]: this.newFieldValue.current.value,
},
newFieldKeyError: "",
@ -132,7 +126,7 @@ export class EditProfile extends Component<Props, State> {
this.props.emit({
type: "SaveProfile",
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[] = [
{
key: "name",
value: this.state.profile?.name,
value: this.state.profileData.name || this.props.profile?.name,
},
{
key: "banner",
value: this.state.profile?.banner,
value: this.state.profileData.banner || this.props.profile?.banner,
},
{
key: "picture",
value: this.state.profile?.picture,
value: this.state.profileData.picture || this.props.profile?.picture,
hint: (
<span class={this.styles.field.hint.text}>
You can upload your images on websites like{" "}
@ -160,50 +154,14 @@ export class EditProfile extends Component<Props, State> {
},
{
key: "about",
value: this.state.profile?.about,
value: this.state.profileData.about || this.props.profile?.about,
},
{
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) => (
<Fragment>
<h3 class={this.styles.field.title} style={{ textTransform: "capitalize" }}>
@ -224,7 +182,6 @@ export class EditProfile extends Component<Props, State> {
return (
<div class={this.styles.container}>
{banner}
{items}
<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 { parseJSON } from "../features/profile.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";
@ -43,11 +42,6 @@ export class RelayConfig {
console.error(res);
}
});
config.add(damus).then((res) => {
if (res instanceof Error) {
console.error(res);
}
});
return config;
}

View File

@ -14,7 +14,7 @@ Deno.test("Database", async () => {
const stream = db.subscribe();
const event_to_add = await prepareNormalNostrEvent(ctx, { kind: NostrKind.TEXT_NOTE, content: "1" });
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) {
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 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();
assertEquals(parsed_event, retrieved_event);
assertEquals(retrieved_event.id, 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);
const added_event = await db.addEvent(event_to_add);
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);
});

View File

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

View File

@ -1,7 +1,6 @@
import { Parsed_Event, Profile_Nostr_Event } from "../nostr.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 { SingleRelayConnection } from "../../libs/nostr.ts/relay-single.ts";