This commit is contained in:
2023-09-22 16:55:26 +01:00
parent e8519daa47
commit 8c7fbed191
18 changed files with 276 additions and 106 deletions

View File

@ -14,6 +14,7 @@
"@snort/system-query": "workspace:*",
"@snort/system-react": "workspace:*",
"@szhsin/react-menu": "^3.3.1",
"@types/use-sync-external-store": "^0.0.4",
"@void-cat/api": "^1.0.4",
"debug": "^4.3.4",
"dexie": "^3.2.4",
@ -30,6 +31,7 @@
"react-textarea-autosize": "^8.4.0",
"react-twitter-embed": "^4.0.4",
"use-long-press": "^2.0.3",
"use-sync-external-store": "^1.2.0",
"uuid": "^9.0.0",
"workbox-core": "^6.4.2",
"workbox-precaching": "^7.0.0",

View File

@ -81,8 +81,8 @@ export default function CashuNuts({ token }: { token: string }) {
x2="7.11501"
y2="6.7213"
gradientUnits="userSpaceOnUse">
<stop stop-color="white" />
<stop offset="1" stop-color="white" stop-opacity="0.5" />
<stop stopColor="white" />
<stop offset="1" stopColor="white" stopOpacity="0.5" />
</linearGradient>
<linearGradient
id="paint1_linear_1976_19241"
@ -91,8 +91,8 @@ export default function CashuNuts({ token }: { token: string }) {
x2="7.11501"
y2="32.2311"
gradientUnits="userSpaceOnUse">
<stop stop-color="white" />
<stop offset="1" stop-color="white" stop-opacity="0.5" />
<stop stopColor="white" />
<stop offset="1" stopColor="white" stopOpacity="0.5" />
</linearGradient>
<linearGradient
id="paint2_linear_1976_19241"
@ -101,8 +101,8 @@ export default function CashuNuts({ token }: { token: string }) {
x2="22.2658"
y2="19.4576"
gradientUnits="userSpaceOnUse">
<stop stop-color="white" />
<stop offset="1" stop-color="white" stop-opacity="0.5" />
<stop stopColor="white" />
<stop offset="1" stopColor="white" stopOpacity="0.5" />
</linearGradient>
</defs>
</svg>

View File

@ -1,5 +1,5 @@
import "./Modal.css";
import { ReactNode } from "react";
import { ReactNode, useEffect } from "react";
export interface ModalProps {
id: string;
@ -9,6 +9,11 @@ export interface ModalProps {
}
export default function Modal(props: ModalProps) {
useEffect(() => {
document.body.classList.add("scroll-lock");
return () => document.body.classList.remove("scroll-lock");
}, []);
return (
<div className={`modal${props.className ? ` ${props.className}` : ""}`} onClick={props.onClose}>
<div className="modal-body" onClick={e => e.stopPropagation()}>

View File

@ -52,7 +52,7 @@ export default function NoteFooter(props: NoteFooterProps) {
const author = useUserProfile(ev.pubkey);
const interactionCache = useInteractionCache(publicKey, ev.id);
const publisher = useEventPublisher();
const note = useNoteCreator();
const note = useNoteCreator(n => ({ show: n.show, replyTo: n.replyTo, update: n.update }));
const willRenderNoteCreator = note.show && note.replyTo?.id === ev.id;
const [tip, setTip] = useState(false);
const [zapping, setZapping] = useState(false);
@ -263,7 +263,7 @@ export default function NoteFooter(props: NoteFooterProps) {
{tipButton()}
{powIcon()}
</div>
{willRenderNoteCreator && <NoteCreator />}
{willRenderNoteCreator && <NoteCreator key={`note-creator-${ev.id}`} />}
<SendSats targets={getZapTarget()} onClose={() => setTip(false)} show={tip} note={ev.id} allocatePool={true} />
</div>
<ZapsSummary zaps={zaps} />

View File

@ -68,7 +68,10 @@ export class MultiAccountStore extends ExternalStore<LoginSession> {
}
getSessions() {
return [...this.#accounts.values()].map(v => unwrap(v.publicKey));
return [...this.#accounts.values()].map(v => ({
pubkey: unwrap(v.publicKey),
id: v.id,
}));
}
allSubscriptions() {

View File

@ -25,19 +25,12 @@ import { LoginUnlock } from "Element/PinPrompt";
export default function Layout() {
const location = useLocation();
const note = useNoteCreator();
const isReplyNoteCreatorShowing = note.replyTo && note.show;
const [pageClass, setPageClass] = useState("page");
useLoginFeed();
useTheme();
useLoginRelays();
const shouldHideNoteCreator = useMemo(() => {
const hideOn = ["/settings", "/messages", "/new", "/login", "/donate", "/e", "/subscribe"];
return isReplyNoteCreatorShowing || hideOn.some(a => location.pathname.startsWith(a));
}, [location, isReplyNoteCreatorShowing]);
const shouldHideHeader = useMemo(() => {
const hideOn = ["/login", "/new"];
return hideOn.some(a => location.pathname.startsWith(a));
@ -63,21 +56,7 @@ export default function Layout() {
</header>
)}
<Outlet />
{!shouldHideNoteCreator && (
<>
<button
className="primary note-create-button"
onClick={() =>
note.update(v => {
v.replyTo = undefined;
v.show = true;
})
}>
<Icon name="plus" size={16} />
</button>
<NoteCreator />
</>
)}
<NoteCreatorButton />
<Toaster />
</div>
<LoginUnlock />
@ -85,6 +64,34 @@ export default function Layout() {
);
}
const NoteCreatorButton = () => {
const location = useLocation();
const { show, replyTo, update } = useNoteCreator(v => ({ show: v.show, replyTo: v.replyTo, update: v.update }));
const shouldHideNoteCreator = useMemo(() => {
const isReplyNoteCreatorShowing = replyTo && show;
const hideOn = ["/settings", "/messages", "/new", "/login", "/donate", "/e", "/subscribe"];
return isReplyNoteCreatorShowing || hideOn.some(a => location.pathname.startsWith(a));
}, [location]);
if (shouldHideNoteCreator) return;
return (
<>
<button
className="primary note-create-button"
onClick={() =>
update(v => {
v.replyTo = undefined;
v.show = true;
})
}>
<Icon name="plus" size={16} />
</button>
<NoteCreator key="global-note-creator" />
</>
);
};
const AccountHeader = () => {
const navigate = useNavigate();
const { formatMessage } = useIntl();

View File

@ -1,57 +1,32 @@
import { useState } from "react";
import { FormattedMessage, useIntl } from "react-intl";
import { FormattedMessage } from "react-intl";
import { Link } from "react-router-dom";
import ProfilePreview from "Element/ProfilePreview";
import { LoginStore } from "Login";
import useLoginHandler from "Hooks/useLoginHandler";
import AsyncButton from "Element/AsyncButton";
import { getActiveSubscriptions } from "Subscription";
export default function AccountsPage() {
const { formatMessage } = useIntl();
const [key, setKey] = useState("");
const [error, setError] = useState("");
const loginHandler = useLoginHandler();
const logins = LoginStore.getSessions();
const sub = getActiveSubscriptions(LoginStore.allSubscriptions());
async function doLogin() {
try {
setError("");
await loginHandler.doLogin(key);
setKey("");
} catch (e) {
if (e instanceof Error) {
setError(e.message);
} else {
setError(
formatMessage({
defaultMessage: "Unknown login error",
}),
);
}
console.error(e);
}
}
return (
<>
<div className="flex-column g12">
<h3>
<FormattedMessage defaultMessage="Logins" />
</h3>
{logins.map(a => (
<div className="card flex" key={a}>
<div className="card flex" key={a.id}>
<ProfilePreview
pubkey={a}
pubkey={a.pubkey}
options={{
about: false,
}}
actions={
<div className="f-1">
<button className="mb10" onClick={() => LoginStore.switchAccount(a)}>
<button className="mb10" onClick={() => LoginStore.switchAccount(a.id)}>
<FormattedMessage defaultMessage="Switch" />
</button>
<button onClick={() => LoginStore.removeSession(a)}>
<button onClick={() => LoginStore.removeSession(a.id)}>
<FormattedMessage defaultMessage="Logout" />
</button>
</div>
@ -61,27 +36,12 @@ export default function AccountsPage() {
))}
{sub && (
<>
<h3>
<Link to={"/login"}>
<button type="button">
<FormattedMessage defaultMessage="Add Account" />
</h3>
<div className="flex">
<input
dir="auto"
type="text"
placeholder={formatMessage({
defaultMessage: "nsec, npub, nip-05, hex, mnemonic",
})}
className="f-grow mr10"
onChange={e => setKey(e.target.value)}
/>
<AsyncButton onClick={() => doLogin()}>
<FormattedMessage defaultMessage="Login" />
</AsyncButton>
</div>
</>
</button>
</Link>
)}
{error && <b className="error">{error}</b>}
</>
</div>
);
}

View File

@ -2,6 +2,7 @@ import { ExternalStore } from "@snort/shared";
import { NostrEvent, TaggedNostrEvent } from "@snort/system";
import { ZapTarget } from "Zapper";
import { useSyncExternalStore } from "react";
import { useSyncExternalStoreWithSelector } from "use-sync-external-store/with-selector";
interface NoteCreatorDataSnapshot {
show: boolean;
@ -33,11 +34,11 @@ class NoteCreatorStore extends ExternalStore<NoteCreatorDataSnapshot> {
advanced: false,
reset: () => {
this.#reset(this.#data);
this.notifyChange();
this.notifyChange(this.#data);
},
update: (fn: (v: NoteCreatorDataSnapshot) => void) => {
fn(this.#data);
this.notifyChange();
this.notifyChange(this.#data);
},
};
}
@ -65,8 +66,7 @@ class NoteCreatorStore extends ExternalStore<NoteCreatorDataSnapshot> {
},
update: (fn: (v: NoteCreatorDataSnapshot) => void) => {
fn(this.#data);
console.debug(this.#data);
this.notifyChange();
this.notifyChange(this.#data);
},
} as NoteCreatorDataSnapshot;
return sn;
@ -75,9 +75,20 @@ class NoteCreatorStore extends ExternalStore<NoteCreatorDataSnapshot> {
const NoteCreatorState = new NoteCreatorStore();
export function useNoteCreator() {
return useSyncExternalStore(
c => NoteCreatorState.hook(c),
() => NoteCreatorState.snapshot(),
);
export function useNoteCreator<T extends object = NoteCreatorDataSnapshot>(
selector?: (v: NoteCreatorDataSnapshot) => T,
) {
if (selector) {
return useSyncExternalStoreWithSelector<NoteCreatorDataSnapshot, T>(
c => NoteCreatorState.hook(c),
() => NoteCreatorState.snapshot(),
undefined,
selector,
);
} else {
return useSyncExternalStore<T>(
c => NoteCreatorState.hook(c),
() => NoteCreatorState.snapshot() as T,
);
}
}

View File

@ -38,7 +38,6 @@ import { preload, RelayMetrics, UserCache, UserRelays } from "Cache";
import { LoginStore } from "Login";
import { SnortDeckLayout } from "Pages/DeckLayout";
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const WasmQueryOptimizer = {
expandFilter: (f: ReqFilter) => {
return expand_filter(f) as Array<FlatReqFilter>;
@ -61,7 +60,7 @@ export const System = new NostrSystem({
relayCache: UserRelays,
profileCache: UserCache,
relayMetrics: RelayMetrics,
//queryOptimizer: WasmQueryOptimizer,
queryOptimizer: WasmQueryOptimizer,
authHandler: async (c, r) => {
const { id } = LoginStore.snapshot();
const pub = LoginStore.getPublisher(id);