mirror of
https://github.com/BlowaterNostr/blowater.git
synced 2024-10-18 07:33:22 +00:00
more responsive rendering on new event from relays & auto scroll to posted message on public feed (#418)
This commit is contained in:
parent
8fd31d1663
commit
9d2901fe39
@ -25,3 +25,12 @@ export function* filter<X>(iter: Iterable<X>, filterer: (item: X) => boolean) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// f should not resolve, if it does resolve, it should only throw an error
|
||||
export async function forever(f: Promise<Error | undefined | void>) {
|
||||
const r = await f;
|
||||
if (r == undefined) {
|
||||
throw new Error(`${f} should not resolve`);
|
||||
}
|
||||
throw r;
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ import { Component } from "https://esm.sh/preact@10.17.1";
|
||||
import { SingleRelayConnection } from "../../libs/nostr.ts/relay-single.ts";
|
||||
import { ChannelContainer } from "./channel-container.tsx";
|
||||
import { ChatMessage } from "./message.ts";
|
||||
import { filter, map } from "./_helper.ts";
|
||||
import { filter, forever, map } from "./_helper.ts";
|
||||
import { RightPanel } from "./components/right-panel.tsx";
|
||||
import { ComponentChildren } from "https://esm.sh/preact@10.17.1";
|
||||
import { SignIn } from "./sign-in.tsx";
|
||||
@ -108,7 +108,7 @@ export async function Start(database: DexieDatabase) {
|
||||
);
|
||||
|
||||
for await (
|
||||
let ok of UI_Interaction_Update({
|
||||
let _ of UI_Interaction_Update({
|
||||
model,
|
||||
eventBus,
|
||||
dbView: dbView,
|
||||
@ -121,9 +121,6 @@ export async function Start(database: DexieDatabase) {
|
||||
toastInputChan: toastInputChan,
|
||||
})
|
||||
) {
|
||||
if (ok == false) {
|
||||
continue;
|
||||
}
|
||||
const t = Date.now();
|
||||
{
|
||||
render(
|
||||
@ -396,8 +393,6 @@ export class AppComponent extends Component<AppProps> {
|
||||
);
|
||||
}
|
||||
|
||||
console.debug("AppComponent:2", Date.now() - t);
|
||||
|
||||
const final = (
|
||||
<div class={`h-screen w-full flex`}>
|
||||
<NavBar
|
||||
@ -551,12 +546,3 @@ const sync_client_specific_data = async (
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// f should not resolve, if it does resolve, it should only throw an error
|
||||
async function forever(f: Promise<Error | undefined | void>) {
|
||||
const r = await f;
|
||||
if (r == undefined) {
|
||||
throw new Error(`${f} should not resolve`);
|
||||
}
|
||||
throw r;
|
||||
}
|
||||
|
@ -1,6 +1,12 @@
|
||||
/** @jsx h */
|
||||
import { ComponentChildren, h } from "https://esm.sh/preact@10.17.1";
|
||||
import { Channel, closed, sleep } from "https://raw.githubusercontent.com/BlowaterNostr/csp/master/csp.ts";
|
||||
import {
|
||||
Channel,
|
||||
closed,
|
||||
PopChannel,
|
||||
PutChannel,
|
||||
sleep,
|
||||
} from "https://raw.githubusercontent.com/BlowaterNostr/csp/master/csp.ts";
|
||||
import { prepareNormalNostrEvent } from "../../libs/nostr.ts/event.ts";
|
||||
import { PublicKey } from "../../libs/nostr.ts/key.ts";
|
||||
import { NoteID } from "../../libs/nostr.ts/nip19.ts";
|
||||
@ -49,6 +55,7 @@ import { SyncEvent } from "./message-panel.tsx";
|
||||
import { SendingEventRejection, ToastChannel } from "./components/toast.tsx";
|
||||
import { SingleRelayConnection } from "../../libs/nostr.ts/relay-single.ts";
|
||||
import { default_blowater_relay } from "./relay-config.ts";
|
||||
import { forever } from "./_helper.ts";
|
||||
|
||||
export type UI_Interaction_Event =
|
||||
| SearchUpdate
|
||||
@ -92,7 +99,7 @@ export type UserBlocker = {
|
||||
/////////////////////
|
||||
// UI Interfaction //
|
||||
/////////////////////
|
||||
export async function* UI_Interaction_Update(args: {
|
||||
export function UI_Interaction_Update(args: {
|
||||
model: Model;
|
||||
eventBus: AppEventBus;
|
||||
dbView: Database_View;
|
||||
@ -103,7 +110,24 @@ export async function* UI_Interaction_Update(args: {
|
||||
lamport: LamportTime;
|
||||
installPrompt: InstallPrompt;
|
||||
toastInputChan: ToastChannel;
|
||||
}) {
|
||||
}): Channel<true> {
|
||||
const chan = new Channel<true>();
|
||||
forever(handle_update_event(chan, args));
|
||||
return chan;
|
||||
}
|
||||
|
||||
const handle_update_event = async (chan: PutChannel<true>, args: {
|
||||
model: Model;
|
||||
eventBus: AppEventBus;
|
||||
dbView: Database_View;
|
||||
pool: ConnectionPool;
|
||||
popOver: PopOverInputChannel;
|
||||
rightPanel: Channel<() => ComponentChildren>;
|
||||
newNostrEventChannel: Channel<NostrEvent>;
|
||||
lamport: LamportTime;
|
||||
installPrompt: InstallPrompt;
|
||||
toastInputChan: ToastChannel;
|
||||
}) => {
|
||||
const { model, dbView, eventBus, pool, installPrompt } = args;
|
||||
for await (const event of eventBus.onChange()) {
|
||||
console.log(event);
|
||||
@ -133,7 +157,6 @@ export async function* UI_Interaction_Update(args: {
|
||||
} else {
|
||||
console.error("failed to sign in");
|
||||
}
|
||||
yield model;
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -244,6 +267,8 @@ export async function* UI_Interaction_Update(args: {
|
||||
app.toastInputChan.put(
|
||||
SendingEventRejection(eventBus.emit, current_relay.url, res.message),
|
||||
);
|
||||
} else {
|
||||
chan.put(true);
|
||||
}
|
||||
});
|
||||
} else if (event.type == "UpdateMessageFiles") {
|
||||
@ -298,7 +323,8 @@ export async function* UI_Interaction_Update(args: {
|
||||
() => {
|
||||
return (
|
||||
<UserDetail
|
||||
targetUserProfile={app.database.getProfilesByPublicKey(event.pubkey)?.profile ||
|
||||
targetUserProfile={app.database.getProfilesByPublicKey(event.pubkey)
|
||||
?.profile ||
|
||||
{}}
|
||||
pubkey={event.pubkey}
|
||||
emit={eventBus.emit}
|
||||
@ -426,16 +452,14 @@ export async function* UI_Interaction_Update(args: {
|
||||
}
|
||||
});
|
||||
}
|
||||
yield false; // do not update UI
|
||||
continue;
|
||||
} else {
|
||||
console.log(event, "is not handled");
|
||||
yield false;
|
||||
continue;
|
||||
}
|
||||
yield true;
|
||||
await chan.put(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export type DirectMessageGetter = ChatMessagesGetter & {
|
||||
getDirectMessageStream(publicKey: string): Channel<ChatMessage>;
|
||||
@ -474,7 +498,7 @@ export async function* Database_Update(
|
||||
console.error("unreachable: db changes channel should never close");
|
||||
break;
|
||||
}
|
||||
changes_events.push(e);
|
||||
changes_events.push(e.event);
|
||||
}
|
||||
|
||||
convoLists.addEvents(changes_events, true);
|
||||
|
@ -51,6 +51,18 @@ export class MessageList extends Component<MessageListProps, MessageListState> {
|
||||
|
||||
jitter = new JitterPrevention(100);
|
||||
|
||||
async componentDidUpdate(previousProps: Readonly<MessageListProps>) {
|
||||
const newest = last(this.props.messages);
|
||||
const pre_newest = last(previousProps.messages);
|
||||
if (
|
||||
newest && pre_newest && newest.author.hex == this.props.myPublicKey.hex &&
|
||||
newest.event.id != pre_newest.event.id
|
||||
) {
|
||||
await this.goToLastPage();
|
||||
this.goToButtom(false);
|
||||
}
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
const offset = this.props.messages.length - ItemsOfPerPage;
|
||||
await setState(this, { offset: offset <= 0 ? 0 : offset });
|
||||
@ -58,7 +70,6 @@ export class MessageList extends Component<MessageListProps, MessageListState> {
|
||||
|
||||
render() {
|
||||
const messages_to_render = this.sortAndSliceMessage();
|
||||
console.log(messages_to_render);
|
||||
const groups = groupContinuousMessages(messages_to_render, (pre, cur) => {
|
||||
const sameAuthor = pre.event.pubkey == cur.event.pubkey;
|
||||
const _66sec = Math.abs(cur.created_at.getTime() - pre.created_at.getTime()) <
|
||||
@ -88,7 +99,7 @@ export class MessageList extends Component<MessageListProps, MessageListState> {
|
||||
}}
|
||||
>
|
||||
<button
|
||||
onClick={this.goToButtom}
|
||||
onClick={() => this.goToButtom(true)}
|
||||
class={`${IconButtonClass} fixed z-10 bottom-8 right-4 h-10 w-10 rotate-[-90deg] bg-[#42464D] hover:bg-[#2F3136]`}
|
||||
>
|
||||
<LeftArrowIcon
|
||||
@ -136,15 +147,23 @@ export class MessageList extends Component<MessageListProps, MessageListState> {
|
||||
}
|
||||
};
|
||||
|
||||
goToButtom = () => {
|
||||
goToButtom = (smooth: boolean) => {
|
||||
if (this.messagesULElement.current) {
|
||||
this.messagesULElement.current.scrollTo({
|
||||
top: this.messagesULElement.current.scrollHeight,
|
||||
left: 0,
|
||||
behavior: "smooth",
|
||||
behavior: smooth ? "smooth" : undefined,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
goToLastPage = async () => {
|
||||
const newOffset = this.props.messages.length - ItemsOfPerPage / 2;
|
||||
await setState(this, {
|
||||
offset: newOffset > 0 ? newOffset : 0,
|
||||
});
|
||||
console.log("goToLastPage", this.state.offset);
|
||||
};
|
||||
}
|
||||
|
||||
export class MessageList_V0 extends Component<MessageListProps> {
|
||||
@ -379,3 +398,11 @@ function MessageActions(
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function last<T>(array: Array<T>): T | undefined {
|
||||
if (array.length == 0) {
|
||||
return undefined;
|
||||
} else {
|
||||
return array[array.length - 1];
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,11 @@
|
||||
import { not_cancelled, sleep } from "https://raw.githubusercontent.com/BlowaterNostr/csp/master/csp.ts";
|
||||
import { prepareNormalNostrEvent } from "../libs/nostr.ts/event.ts";
|
||||
import { PrivateKey } from "../libs/nostr.ts/key.ts";
|
||||
import { InMemoryAccountContext, NostrEvent, NostrKind } from "../libs/nostr.ts/nostr.ts";
|
||||
import { assertEquals, fail } from "https://deno.land/std@0.202.0/testing/asserts.ts";
|
||||
import { InMemoryAccountContext, NostrKind } from "../libs/nostr.ts/nostr.ts";
|
||||
import { test_db_view } from "./UI/_setup.test.ts";
|
||||
import { Parsed_Event } from "./nostr.ts";
|
||||
import { assertEquals } from "https://deno.land/std@0.202.0/assert/assert_equals.ts";
|
||||
import { fail } from "https://deno.land/std@0.202.0/assert/fail.ts";
|
||||
|
||||
Deno.test("Database", async () => {
|
||||
const ctx = InMemoryAccountContext.New(PrivateKey.Generate());
|
||||
@ -29,7 +31,11 @@ Deno.test("Database", async () => {
|
||||
event_to_add,
|
||||
);
|
||||
|
||||
const e = await stream.pop() as NostrEvent;
|
||||
const res = await stream.pop() as {
|
||||
event: Parsed_Event;
|
||||
relay?: string | undefined;
|
||||
};
|
||||
const e = res.event;
|
||||
assertEquals(
|
||||
{
|
||||
content: e.content,
|
||||
@ -49,8 +55,18 @@ Deno.test("Database", async () => {
|
||||
const event_to_add2 = await prepareNormalNostrEvent(ctx, { kind: NostrKind.TEXT_NOTE, content: "2" });
|
||||
// console.log(event_to_add2.id, event_to_add.id)
|
||||
await db.addEvent(event_to_add2);
|
||||
const e2 = await stream.pop() as NostrEvent;
|
||||
assertEquals(e2, await stream2.pop() as NostrEvent);
|
||||
const res_2 = await stream.pop() as {
|
||||
event: Parsed_Event;
|
||||
relay?: string | undefined;
|
||||
};
|
||||
const e2 = res_2.event;
|
||||
assertEquals(
|
||||
res_2,
|
||||
await stream2.pop() as {
|
||||
event: Parsed_Event;
|
||||
relay?: string | undefined;
|
||||
},
|
||||
);
|
||||
assertEquals({
|
||||
content: e2.content,
|
||||
created_at: e2.created_at,
|
||||
@ -70,10 +86,10 @@ Deno.test("Relay Record", async () => {
|
||||
const event_to_add = await prepareNormalNostrEvent(ctx, { kind: NostrKind.TEXT_NOTE, content: "1" });
|
||||
const event_to_add_2 = await prepareNormalNostrEvent(ctx, { kind: NostrKind.TEXT_NOTE, content: "2" });
|
||||
await db.addEvent(event_to_add); // send by client
|
||||
assertEquals(await db.getRelayRecord(event_to_add.id), new Set<string>());
|
||||
assertEquals(db.getRelayRecord(event_to_add.id), new Set<string>());
|
||||
|
||||
await db.addEvent(event_to_add_2, "wss://relay.blowater.app"); // receiver from relay
|
||||
assertEquals(await db.getRelayRecord(event_to_add_2.id), new Set(["wss://relay.blowater.app"]));
|
||||
assertEquals(db.getRelayRecord(event_to_add_2.id), new Set(["wss://relay.blowater.app"]));
|
||||
|
||||
await db.addEvent(event_to_add_2, "wss://relay.test.app");
|
||||
assertEquals(
|
||||
@ -86,6 +102,7 @@ Deno.test("Relay Record", async () => {
|
||||
),
|
||||
);
|
||||
|
||||
await stream.pop();
|
||||
await stream.pop();
|
||||
await stream.pop();
|
||||
|
||||
|
@ -64,8 +64,8 @@ export interface RelayRecordGetter {
|
||||
export class Database_View
|
||||
implements ProfileSetter, ProfileGetter, EventGetter, EventRemover, RelayRecordGetter {
|
||||
//
|
||||
public readonly sourceOfChange = csp.chan<Parsed_Event>(buffer_size);
|
||||
private readonly caster = csp.multi<Parsed_Event>(this.sourceOfChange);
|
||||
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 profiles = new Map<string, Profile_Nostr_Event>();
|
||||
|
||||
private constructor(
|
||||
@ -218,12 +218,6 @@ export class Database_View
|
||||
await this.recordRelay(event.id, url);
|
||||
}
|
||||
|
||||
// check if the event exists
|
||||
const storedEvent = await this.eventsAdapter.get({ id: event.id });
|
||||
if (storedEvent) { // event exist
|
||||
return false;
|
||||
}
|
||||
|
||||
// parse the event to desired format
|
||||
const pubkey = PublicKey.FromHex(event.pubkey);
|
||||
if (pubkey instanceof Error) {
|
||||
@ -236,6 +230,15 @@ export class Database_View
|
||||
publicKey: pubkey,
|
||||
};
|
||||
|
||||
// check if the event exists
|
||||
const storedEvent = await this.eventsAdapter.get({ id: event.id });
|
||||
if (storedEvent) { // event exist
|
||||
if (url) {
|
||||
this.sourceOfChange.put({ event: parsedEvent, relay: url });
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// add event to database and notify subscribers
|
||||
console.log("Database.addEvent", event);
|
||||
|
||||
@ -251,7 +254,7 @@ export class Database_View
|
||||
}
|
||||
|
||||
await this.eventsAdapter.put(event);
|
||||
/* not await */ this.sourceOfChange.put(parsedEvent);
|
||||
/* not await */ this.sourceOfChange.put({ event: parsedEvent, relay: url });
|
||||
return parsedEvent;
|
||||
}
|
||||
|
||||
@ -260,7 +263,7 @@ export class Database_View
|
||||
//////////////////
|
||||
subscribe() {
|
||||
const c = this.caster.copy();
|
||||
const res = csp.chan<Parsed_Event>(buffer_size);
|
||||
const res = csp.chan<{ event: Parsed_Event; relay?: string }>(buffer_size);
|
||||
(async () => {
|
||||
for await (const newE of c) {
|
||||
const err = await res.put(newE);
|
||||
|
Loading…
Reference in New Issue
Block a user