diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index c6f8f4b3..6f4bf783 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -3595,6 +3595,7 @@ dependencies = [ "async-trait", "nostr", "thiserror", + "webln", ] [[package]] @@ -7152,6 +7153,19 @@ dependencies = [ "system-deps", ] +[[package]] +name = "webln" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75257015c2a40fc43c672fb03b70311f75e48b1020c8acff808ca628c46d87c" +dependencies = [ + "js-sys", + "secp256k1", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "webpki-roots" version = "0.26.6" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 81b43fc5..a90a7178 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -33,7 +33,7 @@ tauri-plugin-theme = "2.1.2" tauri-plugin-decorum = { git = "https://github.com/clearlysid/tauri-plugin-decorum" } tauri-specta = { version = "2.0.0-rc.15", features = ["derive", "typescript"] } -nostr-sdk = { git = "https://github.com/rust-nostr/nostr", features = ["lmdb"] } +nostr-sdk = { git = "https://github.com/rust-nostr/nostr", features = ["lmdb", "webln", "all-nips"] } nostr-connect = { git = "https://github.com/rust-nostr/nostr" } specta = "^2.0.0-rc.20" diff --git a/src-tauri/src/commands/event.rs b/src-tauri/src/commands/event.rs index 03a2872f..3d6237a3 100644 --- a/src-tauri/src/commands/event.rs +++ b/src-tauri/src/commands/event.rs @@ -239,6 +239,173 @@ pub async fn get_all_events_from( Ok(alt_events) } +#[tauri::command] +#[specta::specta] +pub async fn get_all_events_by_kind( + kind: u16, + until: Option, + state: State<'_, Nostr>, +) -> Result, String> { + let client = &state.client; + + let as_of = match until { + Some(until) => Timestamp::from_str(&until).map_err(|err| err.to_string())?, + None => Timestamp::now(), + }; + + let filter = Filter::new() + .kind(Kind::Custom(kind)) + .limit(FETCH_LIMIT) + .until(as_of); + + let mut events = Events::new(&[filter.clone()]); + + let mut rx = client + .stream_events(vec![filter], Some(Duration::from_secs(3))) + .await + .map_err(|e| e.to_string())?; + + while let Some(event) = rx.next().await { + events.insert(event); + } + + let alt_events: Vec = events.iter().map(|ev| ev.as_json()).collect(); + + Ok(alt_events) +} + +#[tauri::command] +#[specta::specta] +pub async fn get_all_providers(state: State<'_, Nostr>) -> Result, String> { + let client = &state.client; + + let filter = Filter::new() + .kind(Kind::Custom(31990)) + .custom_tag(SingleLetterTag::lowercase(Alphabet::K), vec!["5300"]); + + let mut events = Events::new(&[filter.clone()]); + + let mut rx = client + .stream_events(vec![filter], Some(Duration::from_secs(3))) + .await + .map_err(|e| e.to_string())?; + + while let Some(event) = rx.next().await { + events.insert(event); + } + + let alt_events: Vec = events.iter().map(|ev| ev.as_json()).collect(); + + Ok(alt_events) +} + +#[tauri::command] +#[specta::specta] +pub async fn request_events_from_provider( + provider: String, + state: State<'_, Nostr>, +) -> Result { + let client = &state.client; + let signer = client.signer().await.map_err(|err| err.to_string())?; + let public_key = signer + .get_public_key() + .await + .map_err(|err| err.to_string())?; + let provider = PublicKey::parse(&provider).map_err(|err| err.to_string())?; + + // Get current user's relay list + let relay_list = client + .database() + .relay_list(public_key) + .await + .map_err(|err| err.to_string())?; + + let relay_list: Vec = relay_list.iter().map(|item| item.0.to_string()).collect(); + + // Create job request + let builder = EventBuilder::job_request( + Kind::JobRequest(5300), + vec![ + Tag::public_key(provider), + Tag::custom(TagKind::Relays, relay_list), + ], + ) + .map_err(|err| err.to_string())?; + + match client.send_event_builder(builder).await { + Ok(output) => { + let filter = Filter::new() + .kind(Kind::JobResult(6300)) + .author(provider) + .pubkey(public_key) + .since(Timestamp::now()); + + let opts = SubscribeAutoCloseOptions::default() + .filter(FilterOptions::WaitDurationAfterEOSE(Duration::from_secs(2))); + + let _ = client.subscribe(vec![filter], Some(opts)).await; + + Ok(output.val.to_hex()) + } + Err(e) => Err(e.to_string()), + } +} + +#[tauri::command] +#[specta::specta] +pub async fn get_all_events_by_request( + id: String, + provider: String, + state: State<'_, Nostr>, +) -> Result, String> { + let client = &state.client; + let public_key = PublicKey::parse(&id).map_err(|err| err.to_string())?; + let provider = PublicKey::parse(&provider).map_err(|err| err.to_string())?; + + let filter = Filter::new() + .kind(Kind::JobResult(6300)) + .author(provider) + .pubkey(public_key) + .limit(1); + + let events = client + .database() + .query(vec![filter]) + .await + .map_err(|err| err.to_string())?; + + if let Some(event) = events.first() { + let parsed: Vec> = + serde_json::from_str(&event.content).map_err(|err| err.to_string())?; + + let vec: Vec = parsed + .into_iter() + .filter_map(|item| Tag::parse(&item).ok()) + .collect::>(); + + let tags = Tags::new(vec); + let ids: Vec = tags.event_ids().copied().collect(); + + let filter = Filter::new().ids(ids); + let mut events = Events::new(&[filter.clone()]); + + let mut rx = client + .stream_events(vec![filter], Some(Duration::from_secs(3))) + .await + .map_err(|e| e.to_string())?; + + while let Some(event) = rx.next().await { + events.insert(event); + } + + let alt_events = process_event(client, events, false).await; + + Ok(alt_events) + } else { + Err("Job result not found.".into()) + } +} + #[tauri::command] #[specta::specta] pub async fn get_local_events( diff --git a/src-tauri/src/commands/window.rs b/src-tauri/src/commands/window.rs index 85a55703..67dfbf93 100644 --- a/src-tauri/src/commands/window.rs +++ b/src-tauri/src/commands/window.rs @@ -71,11 +71,11 @@ pub async fn create_column( if let Ok(public_key) = PublicKey::parse(&id) { let is_newsfeed = payload.url().to_string().contains("newsfeed"); - tauri::async_runtime::spawn(async move { - let state = webview.state::(); - let client = &state.client; + if is_newsfeed { + tauri::async_runtime::spawn(async move { + let state = webview.state::(); + let client = &state.client; - if is_newsfeed { if let Ok(contact_list) = client.database().contacts_public_keys(public_key).await { @@ -102,27 +102,31 @@ pub async fn create_column( println!("Subscription error: {}", e); } } - } - }); + }); + } } else if let Ok(event_id) = EventId::parse(&id) { - tauri::async_runtime::spawn(async move { - let state = webview.state::(); - let client = &state.client; + let is_thread = payload.url().to_string().contains("events"); - let subscription_id = SubscriptionId::new(webview.label()); + if is_thread { + tauri::async_runtime::spawn(async move { + let state = webview.state::(); + let client = &state.client; - let filter = Filter::new() - .event(event_id) - .kinds(vec![Kind::TextNote, Kind::Custom(1111)]) - .since(Timestamp::now()); + let subscription_id = SubscriptionId::new(webview.label()); - if let Err(e) = client - .subscribe_with_id(subscription_id, vec![filter], None) - .await - { - println!("Subscription error: {}", e); - } - }); + let filter = Filter::new() + .event(event_id) + .kinds(vec![Kind::TextNote, Kind::Custom(1111)]) + .since(Timestamp::now()); + + if let Err(e) = client + .subscribe_with_id(subscription_id, vec![filter], None) + .await + { + println!("Subscription error: {}", e); + } + }); + } } } } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 840b177a..9c19b434 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -121,6 +121,10 @@ fn main() { get_all_events_by_authors, get_all_events_by_hashtags, get_all_events_from, + get_all_events_by_kind, + get_all_providers, + request_events_from_provider, + get_all_events_by_request, get_local_events, get_global_events, search, @@ -232,8 +236,7 @@ fn main() { // Config let opts = Options::new() .gossip(true) - .max_avg_latency(Duration::from_millis(300)) - .automatic_authentication(true) + .max_avg_latency(Duration::from_millis(500)) .timeout(Duration::from_secs(5)); // Setup nostr client @@ -546,6 +549,8 @@ fn main() { ) { println!("Emit error: {}", e) } + } else if event.kind == Kind::JobResult(6300) { + println!("Job result: {}", event.as_json()) } } } diff --git a/src/commands.gen.ts b/src/commands.gen.ts index 5911a16e..c836c74d 100644 --- a/src/commands.gen.ts +++ b/src/commands.gen.ts @@ -360,6 +360,38 @@ async getAllEventsFrom(url: string, until: string | null) : Promise> { + try { + return { status: "ok", data: await TAURI_INVOKE("get_all_events_by_kind", { kind, until }) }; +} catch (e) { + if(e instanceof Error) throw e; + else return { status: "error", error: e as any }; +} +}, +async getAllProviders() : Promise> { + try { + return { status: "ok", data: await TAURI_INVOKE("get_all_providers") }; +} catch (e) { + if(e instanceof Error) throw e; + else return { status: "error", error: e as any }; +} +}, +async requestEventsFromProvider(provider: string) : Promise> { + try { + return { status: "ok", data: await TAURI_INVOKE("request_events_from_provider", { provider }) }; +} catch (e) { + if(e instanceof Error) throw e; + else return { status: "error", error: e as any }; +} +}, +async getAllEventsByRequest(id: string, provider: string) : Promise> { + try { + return { status: "ok", data: await TAURI_INVOKE("get_all_events_by_request", { id, provider }) }; +} catch (e) { + if(e instanceof Error) throw e; + else return { status: "error", error: e as any }; +} +}, async getLocalEvents(until: string | null) : Promise> { try { return { status: "ok", data: await TAURI_INVOKE("get_local_events", { until }) }; diff --git a/src/components/column.tsx b/src/components/column.tsx index e6093ad1..eae49e9f 100644 --- a/src/components/column.tsx +++ b/src/components/column.tsx @@ -20,7 +20,7 @@ export function Column({ column }: { column: LumeColumn }) { y: rect.y, width: rect.width, height: rect.height, - url: `${column.url}?label=${column.label}&name=${column.name}`, + url: `${column.url}?label=${column.label}&name=${column.name}&account=${column.account}`, }); if (res.status === "error") { diff --git a/src/components/note/buttons/repost.tsx b/src/components/note/buttons/repost.tsx index e2e652ec..8840e843 100644 --- a/src/components/note/buttons/repost.tsx +++ b/src/components/note/buttons/repost.tsx @@ -105,13 +105,11 @@ export function NoteRepost({ if (signer.status === "ok") { if (!signer.data) { - if (!signer.data) { - const res = await commands.setSigner(account); + const res = await commands.setSigner(account); - if (res.status === "error") { - await message(res.error, { kind: "error" }); - return; - } + if (res.status === "error") { + await message(res.error, { kind: "error" }); + return; } } diff --git a/src/components/user/button.tsx b/src/components/user/button.tsx index 5d8c1ae9..165a8ad9 100644 --- a/src/components/user/button.tsx +++ b/src/components/user/button.tsx @@ -101,25 +101,19 @@ export function UserButton({ className }: { className?: string }) { const submit = (account: string) => { startTransition(async () => { - if (!status) { - const signer = await commands.hasSigner(account); + const signer = await commands.hasSigner(account); - if (signer.status === "ok") { - if (!signer.data) { - if (!signer.data) { - const res = await commands.setSigner(account); + if (signer.status === "ok") { + if (!signer.data) { + const res = await commands.setSigner(account); - if (res.status === "error") { - await message(res.error, { kind: "error" }); - return; - } - } + if (res.status === "error") { + await message(res.error, { kind: "error" }); + return; } - - toggleFollow.mutate(); - } else { - return; } + + toggleFollow.mutate(); } else { return; } diff --git a/src/routes.gen.ts b/src/routes.gen.ts index 160a9a87..8acccbfc 100644 --- a/src/routes.gen.ts +++ b/src/routes.gen.ts @@ -75,6 +75,9 @@ const ColumnsLayoutNotificationIdLazyImport = createFileRoute( const ColumnsLayoutLaunchpadIdLazyImport = createFileRoute( '/columns/_layout/launchpad/$id', )() +const ColumnsLayoutDvmIdLazyImport = createFileRoute( + '/columns/_layout/dvm/$id', +)() // Create/Update Routes @@ -315,6 +318,14 @@ const ColumnsLayoutLaunchpadIdLazyRoute = import('./routes/columns/_layout/launchpad.$id.lazy').then((d) => d.Route), ) +const ColumnsLayoutDvmIdLazyRoute = ColumnsLayoutDvmIdLazyImport.update({ + id: '/dvm/$id', + path: '/dvm/$id', + getParentRoute: () => ColumnsLayoutRoute, +} as any).lazy(() => + import('./routes/columns/_layout/dvm.$id.lazy').then((d) => d.Route), +) + const ColumnsLayoutStoriesIdRoute = ColumnsLayoutStoriesIdImport.update({ id: '/stories/$id', path: '/stories/$id', @@ -597,6 +608,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ColumnsLayoutStoriesIdImport parentRoute: typeof ColumnsLayoutImport } + '/columns/_layout/dvm/$id': { + id: '/columns/_layout/dvm/$id' + path: '/dvm/$id' + fullPath: '/columns/dvm/$id' + preLoaderRoute: typeof ColumnsLayoutDvmIdLazyImport + parentRoute: typeof ColumnsLayoutImport + } '/columns/_layout/launchpad/$id': { id: '/columns/_layout/launchpad/$id' path: '/launchpad/$id' @@ -694,6 +712,7 @@ interface ColumnsLayoutRouteChildren { ColumnsLayoutInterestsIdRoute: typeof ColumnsLayoutInterestsIdRoute ColumnsLayoutNewsfeedIdRoute: typeof ColumnsLayoutNewsfeedIdRoute ColumnsLayoutStoriesIdRoute: typeof ColumnsLayoutStoriesIdRoute + ColumnsLayoutDvmIdLazyRoute: typeof ColumnsLayoutDvmIdLazyRoute ColumnsLayoutLaunchpadIdLazyRoute: typeof ColumnsLayoutLaunchpadIdLazyRoute ColumnsLayoutNotificationIdLazyRoute: typeof ColumnsLayoutNotificationIdLazyRoute ColumnsLayoutRelaysUrlLazyRoute: typeof ColumnsLayoutRelaysUrlLazyRoute @@ -718,6 +737,7 @@ const ColumnsLayoutRouteChildren: ColumnsLayoutRouteChildren = { ColumnsLayoutInterestsIdRoute: ColumnsLayoutInterestsIdRoute, ColumnsLayoutNewsfeedIdRoute: ColumnsLayoutNewsfeedIdRoute, ColumnsLayoutStoriesIdRoute: ColumnsLayoutStoriesIdRoute, + ColumnsLayoutDvmIdLazyRoute: ColumnsLayoutDvmIdLazyRoute, ColumnsLayoutLaunchpadIdLazyRoute: ColumnsLayoutLaunchpadIdLazyRoute, ColumnsLayoutNotificationIdLazyRoute: ColumnsLayoutNotificationIdLazyRoute, ColumnsLayoutRelaysUrlLazyRoute: ColumnsLayoutRelaysUrlLazyRoute, @@ -772,6 +792,7 @@ export interface FileRoutesByFullPath { '/columns/interests/$id': typeof ColumnsLayoutInterestsIdRoute '/columns/newsfeed/$id': typeof ColumnsLayoutNewsfeedIdRoute '/columns/stories/$id': typeof ColumnsLayoutStoriesIdRoute + '/columns/dvm/$id': typeof ColumnsLayoutDvmIdLazyRoute '/columns/launchpad/$id': typeof ColumnsLayoutLaunchpadIdLazyRoute '/columns/notification/$id': typeof ColumnsLayoutNotificationIdLazyRoute '/columns/relays/$url': typeof ColumnsLayoutRelaysUrlLazyRoute @@ -810,6 +831,7 @@ export interface FileRoutesByTo { '/columns/interests/$id': typeof ColumnsLayoutInterestsIdRoute '/columns/newsfeed/$id': typeof ColumnsLayoutNewsfeedIdRoute '/columns/stories/$id': typeof ColumnsLayoutStoriesIdRoute + '/columns/dvm/$id': typeof ColumnsLayoutDvmIdLazyRoute '/columns/launchpad/$id': typeof ColumnsLayoutLaunchpadIdLazyRoute '/columns/notification/$id': typeof ColumnsLayoutNotificationIdLazyRoute '/columns/relays/$url': typeof ColumnsLayoutRelaysUrlLazyRoute @@ -851,6 +873,7 @@ export interface FileRoutesById { '/columns/_layout/interests/$id': typeof ColumnsLayoutInterestsIdRoute '/columns/_layout/newsfeed/$id': typeof ColumnsLayoutNewsfeedIdRoute '/columns/_layout/stories/$id': typeof ColumnsLayoutStoriesIdRoute + '/columns/_layout/dvm/$id': typeof ColumnsLayoutDvmIdLazyRoute '/columns/_layout/launchpad/$id': typeof ColumnsLayoutLaunchpadIdLazyRoute '/columns/_layout/notification/$id': typeof ColumnsLayoutNotificationIdLazyRoute '/columns/_layout/relays/$url': typeof ColumnsLayoutRelaysUrlLazyRoute @@ -892,6 +915,7 @@ export interface FileRouteTypes { | '/columns/interests/$id' | '/columns/newsfeed/$id' | '/columns/stories/$id' + | '/columns/dvm/$id' | '/columns/launchpad/$id' | '/columns/notification/$id' | '/columns/relays/$url' @@ -929,6 +953,7 @@ export interface FileRouteTypes { | '/columns/interests/$id' | '/columns/newsfeed/$id' | '/columns/stories/$id' + | '/columns/dvm/$id' | '/columns/launchpad/$id' | '/columns/notification/$id' | '/columns/relays/$url' @@ -968,6 +993,7 @@ export interface FileRouteTypes { | '/columns/_layout/interests/$id' | '/columns/_layout/newsfeed/$id' | '/columns/_layout/stories/$id' + | '/columns/_layout/dvm/$id' | '/columns/_layout/launchpad/$id' | '/columns/_layout/notification/$id' | '/columns/_layout/relays/$url' @@ -1081,6 +1107,7 @@ export const routeTree = rootRoute "/columns/_layout/interests/$id", "/columns/_layout/newsfeed/$id", "/columns/_layout/stories/$id", + "/columns/_layout/dvm/$id", "/columns/_layout/launchpad/$id", "/columns/_layout/notification/$id", "/columns/_layout/relays/$url", @@ -1183,6 +1210,10 @@ export const routeTree = rootRoute "filePath": "columns/_layout/stories.$id.tsx", "parent": "/columns/_layout" }, + "/columns/_layout/dvm/$id": { + "filePath": "columns/_layout/dvm.$id.lazy.tsx", + "parent": "/columns/_layout" + }, "/columns/_layout/launchpad/$id": { "filePath": "columns/_layout/launchpad.$id.lazy.tsx", "parent": "/columns/_layout" diff --git a/src/routes/columns/_layout/create-newsfeed.tsx b/src/routes/columns/_layout/create-newsfeed.tsx index 63df042f..73ff12c1 100644 --- a/src/routes/columns/_layout/create-newsfeed.tsx +++ b/src/routes/columns/_layout/create-newsfeed.tsx @@ -1,16 +1,8 @@ import { cn } from "@/commons"; -import type { ColumnRouteSearch } from "@/types"; import { Link, Outlet } from "@tanstack/react-router"; import { createFileRoute } from "@tanstack/react-router"; export const Route = createFileRoute("/columns/_layout/create-newsfeed")({ - validateSearch: (search: Record): ColumnRouteSearch => { - return { - account: search.account, - label: search.label, - name: search.name, - }; - }, component: Screen, }); diff --git a/src/routes/columns/_layout/dvm.$id.lazy.tsx b/src/routes/columns/_layout/dvm.$id.lazy.tsx new file mode 100644 index 00000000..4b715f35 --- /dev/null +++ b/src/routes/columns/_layout/dvm.$id.lazy.tsx @@ -0,0 +1,108 @@ +import { commands } from "@/commands.gen"; +import { toLumeEvents } from "@/commons"; +import { RepostNote, Spinner, TextNote } from "@/components"; +import type { LumeEvent } from "@/system"; +import { Kind } from "@/types"; +import * as ScrollArea from "@radix-ui/react-scroll-area"; +import { useQuery } from "@tanstack/react-query"; +import { createLazyFileRoute } from "@tanstack/react-router"; +import { type RefObject, useCallback, useRef } from "react"; +import { Virtualizer } from "virtua"; + +export const Route = createLazyFileRoute("/columns/_layout/dvm/$id")({ + component: Screen, +}); + +function Screen() { + const { id } = Route.useParams(); + const { account } = Route.useSearch(); + const { isLoading, isError, error, data } = useQuery({ + queryKey: ["job-result", id], + queryFn: async () => { + if (!account) { + throw new Error("Account is required"); + } + + const res = await commands.getAllEventsByRequest(account, id); + + if (res.status === "error") { + throw new Error(res.error); + } + + return toLumeEvents(res.data); + }, + refetchOnWindowFocus: false, + }); + + const ref = useRef(null); + + const renderItem = useCallback( + (event: LumeEvent) => { + if (!event) { + return; + } + + switch (event.kind) { + case Kind.Repost: { + const repostId = event.repostId; + + return ( + + ); + } + default: + return ( + + ); + } + }, + [data], + ); + + return ( + + + }> + {isLoading ? ( +
+ + Requesting events... +
+ ) : isError ? ( +
+ {error?.message} +
+ ) : !data?.length ? ( +
+ 🎉 Yo. You're catching up on all latest notes. +
+ ) : ( + data.map((item) => renderItem(item)) + )} +
+
+ + + + +
+ ); +} diff --git a/src/routes/columns/_layout/launchpad.$id.lazy.tsx b/src/routes/columns/_layout/launchpad.$id.lazy.tsx index 634bf665..d7187490 100644 --- a/src/routes/columns/_layout/launchpad.$id.lazy.tsx +++ b/src/routes/columns/_layout/launchpad.$id.lazy.tsx @@ -11,7 +11,8 @@ import { resolveResource } from "@tauri-apps/api/path"; import { message } from "@tauri-apps/plugin-dialog"; import { readTextFile } from "@tauri-apps/plugin-fs"; import { nanoid } from "nanoid"; -import { useCallback, useState, useTransition } from "react"; +import { memo, useCallback, useState, useTransition } from "react"; +import { minidenticon } from "minidenticons"; export const Route = createLazyFileRoute("/columns/_layout/launchpad/$id")({ component: Screen, @@ -28,6 +29,7 @@ function Screen() { +
{name}
-
- -
+ ); @@ -522,6 +522,132 @@ function Interests() { ); } +function ContentDiscovery() { + const { isLoading, isError, error, data } = useQuery({ + queryKey: ["content-discovery"], + queryFn: async () => { + const res = await commands.getAllProviders(); + + if (res.status === "ok") { + const events: NostrEvent[] = res.data.map((item) => JSON.parse(item)); + return events; + } else { + throw new Error(res.error); + } + }, + refetchOnWindowFocus: false, + }); + + return ( +
+
+

Content Discovery

+
+
+ {isLoading ? ( +
+ + Loading... +
+ ) : isError ? ( +
+

{error?.message ?? "Error"}

+
+ ) : !data ? ( +
+

Empty.

+
+ ) : ( +
+
+ {data?.map((item) => ( + + ))} +
+
+ )} +
+
+ ); +} + +const Provider = memo(function Provider({ event }: { event: NostrEvent }) { + const { id } = Route.useParams(); + const [isPending, startTransition] = useTransition(); + + const metadata: { [key: string]: string } = JSON.parse(event.content); + const fallback = `data:image/svg+xml;utf8,${encodeURIComponent( + minidenticon(event.id, 60, 50), + )}`; + + const request = (name: string | undefined, provider: string) => { + startTransition(async () => { + // Ensure signer + const signer = await commands.hasSigner(id); + + if (signer.status === "ok") { + if (!signer.data) { + const res = await commands.setSigner(id); + + if (res.status === "error") { + await message(res.error, { kind: "error" }); + return; + } + } + + // Send request event to provider + const res = await commands.requestEventsFromProvider(provider); + + if (res.status === "ok") { + // Open column + await LumeWindow.openColumn({ + label: `dvm_${provider.slice(0, 6)}`, + name: name || "Content Discovery", + account: id, + url: `/columns/dvm/${provider}`, + }); + return; + } else { + await message(res.error, { kind: "error" }); + return; + } + } else { + await message(signer.error, { kind: "error" }); + return; + } + }); + }; + + return ( +
+
+ {event.id} +
+
+
{metadata.name}
+

+ {metadata.about} +

+
+ +
+ ); +}); + function Core() { const { id } = Route.useParams(); const { data } = useQuery({