Better POW UX
This commit is contained in:
parent
1c5e61e020
commit
a5be4da2e8
@ -36,6 +36,7 @@ import { LoginStore } from "Login";
|
|||||||
import { getCurrentSubscription } from "Subscription";
|
import { getCurrentSubscription } from "Subscription";
|
||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
import { System } from "index";
|
import { System } from "index";
|
||||||
|
import AsyncButton from "Element/AsyncButton";
|
||||||
|
|
||||||
interface NotePreviewProps {
|
interface NotePreviewProps {
|
||||||
note: TaggedNostrEvent;
|
note: TaggedNostrEvent;
|
||||||
@ -174,9 +175,9 @@ export function NoteCreator() {
|
|||||||
dispatch(reset());
|
dispatch(reset());
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSubmit(ev: React.MouseEvent<HTMLButtonElement>) {
|
async function onSubmit(ev: React.MouseEvent<HTMLButtonElement>) {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
sendNote().catch(console.warn);
|
await sendNote();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadPreview() {
|
async function loadPreview() {
|
||||||
@ -374,9 +375,9 @@ export function NoteCreator() {
|
|||||||
<button className="secondary" onClick={cancel}>
|
<button className="secondary" onClick={cancel}>
|
||||||
<FormattedMessage {...messages.Cancel} />
|
<FormattedMessage {...messages.Cancel} />
|
||||||
</button>
|
</button>
|
||||||
<button onClick={onSubmit}>
|
<AsyncButton onClick={onSubmit}>
|
||||||
{replyTo ? <FormattedMessage {...messages.Reply} /> : <FormattedMessage {...messages.Send} />}
|
{replyTo ? <FormattedMessage {...messages.Reply} /> : <FormattedMessage {...messages.Send} />}
|
||||||
</button>
|
</AsyncButton>
|
||||||
</div>
|
</div>
|
||||||
{showAdvanced && (
|
{showAdvanced && (
|
||||||
<div>
|
<div>
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { HTMLProps, useEffect, useState } from "react";
|
||||||
import { useSelector, useDispatch } from "react-redux";
|
import { useSelector, useDispatch } from "react-redux";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
import { useLongPress } from "use-long-press";
|
import { useLongPress } from "use-long-press";
|
||||||
import { TaggedNostrEvent, HexKey, u256, ParsedZap } from "@snort/system";
|
import { TaggedNostrEvent, HexKey, u256, ParsedZap, countLeadingZeros } from "@snort/system";
|
||||||
import { LNURL } from "@snort/shared";
|
import { LNURL } from "@snort/shared";
|
||||||
import { useUserProfile } from "@snort/system-react";
|
import { useUserProfile } from "@snort/system-react";
|
||||||
|
|
||||||
@ -11,7 +11,7 @@ import Spinner from "Icons/Spinner";
|
|||||||
|
|
||||||
import { formatShort } from "Number";
|
import { formatShort } from "Number";
|
||||||
import useEventPublisher from "Feed/EventPublisher";
|
import useEventPublisher from "Feed/EventPublisher";
|
||||||
import { delay, normalizeReaction, unwrap } from "SnortUtils";
|
import { delay, findTag, normalizeReaction, unwrap } from "SnortUtils";
|
||||||
import { NoteCreator } from "Element/NoteCreator";
|
import { NoteCreator } from "Element/NoteCreator";
|
||||||
import SendSats from "Element/SendSats";
|
import SendSats from "Element/SendSats";
|
||||||
import { ZapsSummary } from "Element/Zap";
|
import { ZapsSummary } from "Element/Zap";
|
||||||
@ -173,16 +173,26 @@ export default function NoteFooter(props: NoteFooterProps) {
|
|||||||
}
|
}
|
||||||
}, [prefs.autoZap, author, zapping]);
|
}, [prefs.autoZap, author, zapping]);
|
||||||
|
|
||||||
|
function powIcon() {
|
||||||
|
const pow = findTag(ev, "nonce") ? countLeadingZeros(ev.id) : undefined;
|
||||||
|
if (pow) {
|
||||||
|
return (
|
||||||
|
<AsyncFooterIcon title={formatMessage({ defaultMessage: "Proof of Work" })} iconName="diamond" value={pow} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function tipButton() {
|
function tipButton() {
|
||||||
const service = getLNURL();
|
const service = getLNURL();
|
||||||
if (service) {
|
if (service) {
|
||||||
return (
|
return (
|
||||||
<>
|
<AsyncFooterIcon
|
||||||
<div className={`reaction-pill ${didZap ? "reacted" : ""}`} {...longPress()} onClick={e => fastZap(e)}>
|
className={didZap ? "reacted" : ""}
|
||||||
{zapping ? <Spinner /> : wallet?.isReady() ? <Icon name="zapFast" /> : <Icon name="zap" />}
|
{...longPress()}
|
||||||
{zapTotal > 0 && <div className="reaction-pill-number">{formatShort(zapTotal)}</div>}
|
iconName={wallet?.isReady() ? "zapFast" : "zap"}
|
||||||
</div>
|
value={zapTotal}
|
||||||
</>
|
onClick={e => fastZap(e)}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@ -190,10 +200,12 @@ export default function NoteFooter(props: NoteFooterProps) {
|
|||||||
|
|
||||||
function repostIcon() {
|
function repostIcon() {
|
||||||
return (
|
return (
|
||||||
<div className={`reaction-pill ${hasReposted() ? "reacted" : ""}`} onClick={() => repost()}>
|
<AsyncFooterIcon
|
||||||
<Icon name="repeat" size={18} />
|
className={hasReposted() ? "reacted" : ""}
|
||||||
{reposts.length > 0 && <div className="reaction-pill-number">{formatShort(reposts.length)}</div>}
|
iconName="repeat"
|
||||||
</div>
|
value={reposts.length}
|
||||||
|
onClick={() => repost()}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,12 +215,12 @@ export default function NoteFooter(props: NoteFooterProps) {
|
|||||||
}
|
}
|
||||||
const reacted = hasReacted("+");
|
const reacted = hasReacted("+");
|
||||||
return (
|
return (
|
||||||
<>
|
<AsyncFooterIcon
|
||||||
<div className={`reaction-pill ${reacted ? "reacted" : ""} `} onClick={() => react(prefs.reactionEmoji)}>
|
className={reacted ? "reacted" : ""}
|
||||||
<Icon name={reacted ? "heart-solid" : "heart"} size={18} />
|
iconName={reacted ? "heart-solid" : "heart"}
|
||||||
<div className="reaction-pill-number">{formatShort(positive.length)}</div>
|
value={positive.length}
|
||||||
</div>
|
onClick={() => react(prefs.reactionEmoji)}
|
||||||
</>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,9 +240,12 @@ export default function NoteFooter(props: NoteFooterProps) {
|
|||||||
{tipButton()}
|
{tipButton()}
|
||||||
{reactionIcons()}
|
{reactionIcons()}
|
||||||
{repostIcon()}
|
{repostIcon()}
|
||||||
<div className={`reaction-pill ${showNoteCreatorModal ? "reacted" : ""}`} onClick={handleReplyButtonClick}>
|
<AsyncFooterIcon
|
||||||
<Icon name="reply" size={17} />
|
className={showNoteCreatorModal ? "reacted" : ""}
|
||||||
</div>
|
iconName="reply"
|
||||||
|
onClick={async () => handleReplyButtonClick()}
|
||||||
|
/>
|
||||||
|
{powIcon()}
|
||||||
</div>
|
</div>
|
||||||
{willRenderNoteCreator && <NoteCreator />}
|
{willRenderNoteCreator && <NoteCreator />}
|
||||||
<SendSats
|
<SendSats
|
||||||
@ -247,3 +262,36 @@ export default function NoteFooter(props: NoteFooterProps) {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface AsyncFooterIconProps extends HTMLProps<HTMLDivElement> {
|
||||||
|
iconName: string;
|
||||||
|
value?: number;
|
||||||
|
loading?: boolean;
|
||||||
|
onClick?: (e: React.MouseEvent<HTMLDivElement>) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function AsyncFooterIcon(props: AsyncFooterIconProps) {
|
||||||
|
const [loading, setLoading] = useState(props.loading ?? false);
|
||||||
|
|
||||||
|
async function handleClick(e: React.MouseEvent<HTMLDivElement>) {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
if (props.onClick) {
|
||||||
|
await props.onClick(e);
|
||||||
|
}
|
||||||
|
} catch (ex) {
|
||||||
|
console.error(ex);
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
{...props}
|
||||||
|
className={`reaction-pill${props.className ? ` ${props.className}` : ""}`}
|
||||||
|
onClick={e => handleClick(e)}>
|
||||||
|
{loading ? <Spinner /> : <Icon name={props.iconName} size={18} />}
|
||||||
|
{props.value && <div className="reaction-pill-number">{formatShort(props.value)}</div>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -22,6 +22,7 @@ export * from "./zaps";
|
|||||||
export * from "./signer";
|
export * from "./signer";
|
||||||
export * from "./text";
|
export * from "./text";
|
||||||
export * from "./pow";
|
export * from "./pow";
|
||||||
|
export * from "./pow-util";
|
||||||
|
|
||||||
export * from "./impl/nip4";
|
export * from "./impl/nip4";
|
||||||
export * from "./impl/nip44";
|
export * from "./impl/nip44";
|
||||||
|
@ -30,7 +30,7 @@ export function minePow(e: NostrPowEvent, target: number) {
|
|||||||
e.tags[nonceTagIdx][1] = (++ctr).toString();
|
e.tags[nonceTagIdx][1] = (++ctr).toString();
|
||||||
|
|
||||||
e.id = createId(e);
|
e.id = createId(e);
|
||||||
} while (countLeadingZeroes(e.id) < target);
|
} while (countLeadingZeros(e.id) < target);
|
||||||
|
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
@ -40,7 +40,7 @@ function createId(e: NostrPowEvent) {
|
|||||||
return bytesToHex(sha256(JSON.stringify(payload)));
|
return bytesToHex(sha256(JSON.stringify(payload)));
|
||||||
}
|
}
|
||||||
|
|
||||||
function countLeadingZeroes(hex: string) {
|
export function countLeadingZeros(hex: string) {
|
||||||
let count = 0;
|
let count = 0;
|
||||||
|
|
||||||
for (let i = 0; i < hex.length; i++) {
|
for (let i = 0; i < hex.length; i++) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user