diff --git a/packages/app/config/default.json b/packages/app/config/default.json index 3b718f53f..2831ea65c 100644 --- a/packages/app/config/default.json +++ b/packages/app/config/default.json @@ -10,7 +10,6 @@ "publicDir": "public/snort", "httpCache": "", "animalNamePlaceholders": false, - "showNoteBroadcaster": true, "defaultZapPoolFee": 0.5, "bypassImgProxyError": false, "features": { @@ -22,6 +21,7 @@ "signUp": { "moderation": true }, + "noteCreatorToast": true, "hideFromNavbar": ["/graph"], "deckSubKind": 1, "eventLinkPrefix": "nevent", diff --git a/packages/app/config/iris.json b/packages/app/config/iris.json index 51b754600..14d94ccef 100644 --- a/packages/app/config/iris.json +++ b/packages/app/config/iris.json @@ -10,7 +10,6 @@ "publicDir": "public/iris", "httpCache": "https://api.iris.to", "animalNamePlaceholders": true, - "showNoteBroadcaster": false, "defaultZapPoolFee": 0.5, "bypassImgProxyError": true, "features": { diff --git a/packages/app/custom.d.ts b/packages/app/custom.d.ts index 9f870541f..c5be172ab 100644 --- a/packages/app/custom.d.ts +++ b/packages/app/custom.d.ts @@ -51,7 +51,6 @@ declare const CONFIG: { navLogo: string | null; httpCache: string; animalNamePlaceholders: boolean; - showNoteBroadcaster: boolean; defaultZapPoolFee: number; bypassImgProxyError: boolean; features: { @@ -67,6 +66,8 @@ declare const CONFIG: { hideFromNavbar: Array; // Limit deck to certain subscriber tier deckSubKind?: number; + // Create toast notifications when publishing notes + noteCreatorToast?: boolean; eventLinkPrefix: NostrPrefix; profileLinkPrefix: NostrPrefix; defaultRelays: Record; diff --git a/packages/app/src/Element/Event/Create/NoteBroadcaster.tsx b/packages/app/src/Element/Event/Create/NoteBroadcaster.tsx deleted file mode 100644 index d59dd5385..000000000 --- a/packages/app/src/Element/Event/Create/NoteBroadcaster.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import { useEffect, useState } from "react"; -import { FormattedMessage, useIntl } from "react-intl"; -import { unwrap } from "@snort/shared"; -import { NostrEvent, OkResponse } from "@snort/system"; - -import AsyncButton from "@/Element/Button/AsyncButton"; -import Icon from "@/Icons/Icon"; -import { getRelayName, sanitizeRelayUrl } from "@/SnortUtils"; -import { removeRelay } from "@/Login"; -import useLogin from "@/Hooks/useLogin"; -import useEventPublisher from "@/Hooks/useEventPublisher"; -import { saveRelays } from "@/Pages/settings/Relays"; -import { sendEventToRelays } from "@/Element/Event/Create/util"; - -export function NoteBroadcaster({ - evs, - onClose, - customRelays, -}: { - evs: Array; - onClose: () => void; - customRelays?: Array; -}) { - const [results, setResults] = useState>([]); - const { formatMessage } = useIntl(); - const login = useLogin(); - const { publisher, system } = useEventPublisher(); - - useEffect(() => { - Promise.all(evs.map(a => sendEventToRelays(system, a, customRelays, setResults)).flat()).catch(console.error); - }, []); - - async function removeRelayFromResult(r: OkResponse) { - if (publisher) { - removeRelay(login, unwrap(sanitizeRelayUrl(r.relay))); - await saveRelays(system, publisher, login.relays.item); - setResults(s => s.filter(a => a.relay !== r.relay)); - } - } - - async function retryPublish(r: OkResponse) { - const ev = evs.find(a => a.id === r.id); - if (ev) { - const rsp = await system.WriteOnceToRelay(unwrap(sanitizeRelayUrl(r.relay)), ev); - setResults(s => - s.map(x => { - if (x.relay === r.relay && x.id === r.id) { - return rsp; //replace with new response - } - return x; - }), - ); - } - } - - return ( -
-

- -

- {results - .filter(a => a.message !== "Duplicate request") - .sort(a => (a.ok ? -1 : 1)) - .map(r => ( -
- -
- {getRelayName(r.relay)} - {r.message && {r.message}} -
- {!r.ok && ( -
- retryPublish(r)} - className="p4 br-compact flex items-center secondary" - title={formatMessage({ - defaultMessage: "Retry publishing", - id: "9kSari", - })}> - - - removeRelayFromResult(r)} - className="p4 br-compact flex items-center secondary" - title={formatMessage({ - defaultMessage: "Remove from my relays", - id: "UJTWqI", - })}> - - -
- )} -
- ))} -
- -
-
- ); -} diff --git a/packages/app/src/Element/Event/Create/NoteCreator.tsx b/packages/app/src/Element/Event/Create/NoteCreator.tsx index e3b0f2c93..f11928da1 100644 --- a/packages/app/src/Element/Event/Create/NoteCreator.tsx +++ b/packages/app/src/Element/Event/Create/NoteCreator.tsx @@ -18,14 +18,15 @@ import useLogin from "@/Hooks/useLogin"; import { GetPowWorker } from "@/index"; import AsyncButton from "@/Element/Button/AsyncButton"; import { AsyncIcon } from "@/Element/Button/AsyncIcon"; -import { fetchNip05Pubkey } from "@snort/shared"; +import { fetchNip05Pubkey, unixNow } from "@snort/shared"; import { ZapTarget } from "@/Zapper"; import { useNoteCreator } from "@/State/NoteCreator"; -import { NoteBroadcaster } from "@/Element/Event/Create/NoteBroadcaster"; import FileUploadProgress from "../FileUpload"; import { ToggleSwitch } from "@/Icons/Toggle"; import { sendEventToRelays } from "@/Element/Event/Create/util"; import { TrendingHashTagsLine } from "@/Element/Event/Create/TrendingHashTagsLine"; +import { Toastore } from "@/Toaster"; +import { OkResponseRow } from "./OkResponseRow"; export function NoteCreator() { const { formatMessage } = useIntl(); @@ -151,15 +152,17 @@ export function NoteCreator() { const ev = await buildNote(); if (ev) { const events = (note.otherEvents ?? []).concat(ev); - note.update(n => { - n.sending = events; - }); - if (!CONFIG.showNoteBroadcaster) { - Promise.all(events.map(a => sendEventToRelays(system, a, note.selectedCustomRelays)).flat()).catch( - console.error, - ); - reset(); - } + events.map(a => sendEventToRelays(system, a, note.selectedCustomRelays, r => { + if (CONFIG.noteCreatorToast) { + r.forEach(rr => { + Toastore.push({ + element: , + expire: unixNow() + (rr.ok ? 5 : 55555) + }) + }); + } + })); + note.update(n => n.reset()); } } @@ -324,18 +327,18 @@ export function NoteCreator() { onChange={e => { note.update( v => - (v.selectedCustomRelays = - // set false if all relays selected - e.target.checked && + (v.selectedCustomRelays = + // set false if all relays selected + e.target.checked && note.selectedCustomRelays && note.selectedCustomRelays.length == a.length - 1 - ? undefined - : // otherwise return selectedCustomRelays with target relay added / removed - a.filter(el => - el === r - ? e.target.checked - : !note.selectedCustomRelays || note.selectedCustomRelays.includes(el), - )), + ? undefined + : // otherwise return selectedCustomRelays with target relay added / removed + a.filter(el => + el === r + ? e.target.checked + : !note.selectedCustomRelays || note.selectedCustomRelays.includes(el), + )), ); }} /> @@ -404,9 +407,9 @@ export function NoteCreator() { onChange={e => note.update( v => - (v.zapSplits = arr.map((vv, ii) => - ii === i ? { ...vv, weight: Number(e.target.value) } : vv, - )), + (v.zapSplits = arr.map((vv, ii) => + ii === i ? { ...vv, weight: Number(e.target.value) } : vv, + )), ) } /> @@ -650,8 +653,7 @@ export function NoteCreator() { if (!note.show) return null; return ( - {note.sending && } - {!note.sending && noteCreatorForm()} + {noteCreatorForm()} ); -} +} \ No newline at end of file diff --git a/packages/app/src/Element/Event/Create/OkResponseRow.tsx b/packages/app/src/Element/Event/Create/OkResponseRow.tsx new file mode 100644 index 000000000..f34435c9c --- /dev/null +++ b/packages/app/src/Element/Event/Create/OkResponseRow.tsx @@ -0,0 +1,60 @@ +import AsyncButton from "@/Element/Button/AsyncButton"; +import useEventPublisher from "@/Hooks/useEventPublisher"; +import useLogin from "@/Hooks/useLogin"; +import Icon from "@/Icons/Icon"; +import { removeRelay } from "@/Login"; +import { saveRelays } from "@/Pages/settings/Relays"; +import { getRelayName } from "@/SnortUtils"; +import { unwrap, sanitizeRelayUrl } from "@snort/shared"; +import { OkResponse } from "@snort/system"; +import { useState } from "react"; +import { useIntl } from "react-intl"; + +export function OkResponseRow({ rsp }: { rsp: OkResponse }) { + const [r, setResult] = useState(rsp); + const { formatMessage } = useIntl(); + const { publisher, system } = useEventPublisher(); + const login = useLogin(); + + async function removeRelayFromResult(r: OkResponse) { + if (publisher) { + removeRelay(login, unwrap(sanitizeRelayUrl(r.relay))); + await saveRelays(system, publisher, login.relays.item); + } + } + + async function retryPublish(r: OkResponse) { + const rsp = await system.WriteOnceToRelay(unwrap(sanitizeRelayUrl(r.relay)), r.event); + setResult(rsp); + } + + return
+ +
+ {getRelayName(r.relay)} + {r.message && {r.message}} +
+ {!r.ok && ( +
+ retryPublish(r)} + className="p4 br-compact flex items-center secondary" + title={formatMessage({ + defaultMessage: "Retry publishing", + id: "9kSari", + })}> + + + removeRelayFromResult(r)} + className="p4 br-compact flex items-center secondary" + title={formatMessage({ + defaultMessage: "Remove from my relays", + id: "UJTWqI", + })}> + + +
+ )} +
+} \ No newline at end of file diff --git a/packages/app/src/Feed/LoginFeed.ts b/packages/app/src/Feed/LoginFeed.ts index 13b7dfd76..941ce2cc4 100644 --- a/packages/app/src/Feed/LoginFeed.ts +++ b/packages/app/src/Feed/LoginFeed.ts @@ -75,7 +75,6 @@ export default function useLoginFeed() { EventKind.InterestsList, EventKind.PublicChatsList, ]); - b.withFilter().authors([pubKey]).kinds([]); if (CONFIG.features.subscriptions && !login.readonly) { b.withFilter().authors([pubKey]).kinds([EventKind.AppData]).tag("d", ["snort"]); b.withFilter() diff --git a/packages/app/src/Toaster.css b/packages/app/src/Toaster.css index a5ada87d1..4cfe2df7d 100644 --- a/packages/app/src/Toaster.css +++ b/packages/app/src/Toaster.css @@ -1,12 +1,9 @@ .toaster { position: fixed; - bottom: 0; - left: 0; + bottom: 2px; + left: 2px; display: flex; flex-direction: column-reverse; z-index: 9999; -} - -.toaster > .card { - border: 1px solid var(--gray); -} + gap: 4px; +} \ No newline at end of file diff --git a/packages/app/src/Toaster.tsx b/packages/app/src/Toaster.tsx index ccfbfd230..8e143b96e 100644 --- a/packages/app/src/Toaster.tsx +++ b/packages/app/src/Toaster.tsx @@ -18,7 +18,7 @@ class ToasterSlots extends ExternalStore> { #cleanup = setInterval(() => this.#eatToast(), 1000); push(n: ToastNotification) { - n.expire ??= unixNow() + 3; + n.expire ??= unixNow() + 10; n.id ??= uuid(); this.#stack.push(n); this.notifyChange(); @@ -46,8 +46,8 @@ export default function Toaster() { return createPortal(
{toast.map(a => ( -
- +
+ {a.icon && } {a.element}
))} diff --git a/packages/app/src/index.css b/packages/app/src/index.css index b5005e9b6..3d4859ac2 100644 --- a/packages/app/src/index.css +++ b/packages/app/src/index.css @@ -785,7 +785,7 @@ button.tall { opacity: 1; animation-name: fadeInOpacity; animation-timing-function: ease-in; - animation-duration: 1s; + animation-duration: 0.5s; } @keyframes fadeInOpacity { diff --git a/packages/system/src/connection.ts b/packages/system/src/connection.ts index 43a83f419..ec51ce999 100644 --- a/packages/system/src/connection.ts +++ b/packages/system/src/connection.ts @@ -23,6 +23,7 @@ export interface OkResponse { id: string; relay: string; message?: string; + event: NostrEvent; } /** @@ -284,6 +285,7 @@ export class Connection extends EventEmitter { id: e.id, relay: this.Address, message: "Duplicate request", + event: e, }); return; } @@ -294,6 +296,7 @@ export class Connection extends EventEmitter { id: e.id, relay: this.Address, message: "Timeout waiting for OK response", + event: e, }); }, timeout); @@ -305,6 +308,7 @@ export class Connection extends EventEmitter { id: id as string, relay: this.Address, message: message as string | undefined, + event: e, }); });