mirror of
https://github.com/luminous-devs/lume.git
synced 2024-09-18 03:03:31 +00:00
wip: migrate to zustand
This commit is contained in:
parent
5c7b18bf29
commit
671b857077
@ -37,7 +37,8 @@
|
||||
"swr": "^2.1.5",
|
||||
"tailwind-merge": "^1.12.0",
|
||||
"tauri-plugin-sql-api": "github:tauri-apps/tauri-plugin-sql",
|
||||
"vidstack": "^0.4.5"
|
||||
"vidstack": "^0.4.5",
|
||||
"zustand": "^4.3.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/typography": "^0.5.9",
|
||||
|
@ -76,6 +76,9 @@ dependencies:
|
||||
vidstack:
|
||||
specifier: ^0.4.5
|
||||
version: 0.4.5
|
||||
zustand:
|
||||
specifier: ^4.3.8
|
||||
version: 4.3.8(react@18.2.0)
|
||||
|
||||
devDependencies:
|
||||
'@tailwindcss/typography':
|
||||
@ -3082,6 +3085,22 @@ packages:
|
||||
engines: {node: '>= 14', npm: '>= 7'}
|
||||
dev: true
|
||||
|
||||
/zustand@4.3.8(react@18.2.0):
|
||||
resolution: {integrity: sha512-4h28KCkHg5ii/wcFFJ5Fp+k1J3gJoasaIbppdgZFO4BPJnsNxL0mQXBSFgOgAdCdBj35aDTPvdAJReTMntFPGg==}
|
||||
engines: {node: '>=12.7.0'}
|
||||
peerDependencies:
|
||||
immer: '>=9.0'
|
||||
react: '>=16.8'
|
||||
peerDependenciesMeta:
|
||||
immer:
|
||||
optional: true
|
||||
react:
|
||||
optional: true
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
use-sync-external-store: 1.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/zwitch@2.0.4:
|
||||
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
|
||||
dev: false
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { AvatarUploader } from "@shared/avatarUploader";
|
||||
import { Image } from "@shared/image";
|
||||
import { RelayContext } from "@shared/relayProvider";
|
||||
import { useActiveAccount } from "@stores/accounts";
|
||||
import { DEFAULT_AVATAR, WRITEONLY_RELAYS } from "@stores/constants";
|
||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
||||
import { getEventHash, getSignature } from "nostr-tools";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
@ -11,8 +11,10 @@ import { navigate } from "vite-plugin-ssr/client/router";
|
||||
export function Page() {
|
||||
const pool: any = useContext(RelayContext);
|
||||
|
||||
const { account } = useActiveAccount();
|
||||
|
||||
const [account, fetchAccount] = useActiveAccount((state: any) => [
|
||||
state.account,
|
||||
state.fetch,
|
||||
]);
|
||||
const [image, setImage] = useState(DEFAULT_AVATAR);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
@ -50,6 +52,10 @@ export function Page() {
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchAccount();
|
||||
}, [fetchAccount]);
|
||||
|
||||
useEffect(() => {
|
||||
setValue("picture", image);
|
||||
}, [setValue, image]);
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { User } from "@app/auth/components/user";
|
||||
import CheckCircleIcon from "@icons/checkCircle";
|
||||
import { RelayContext } from "@shared/relayProvider";
|
||||
import { useActiveAccount } from "@stores/accounts";
|
||||
import { WRITEONLY_RELAYS } from "@stores/constants";
|
||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
||||
import { updateAccount } from "@utils/storage";
|
||||
import { arrayToNIP02 } from "@utils/transform";
|
||||
import { getEventHash, getSignature } from "nostr-tools";
|
||||
@ -111,8 +111,10 @@ const initialList = [
|
||||
export function Page() {
|
||||
const pool: any = useContext(RelayContext);
|
||||
|
||||
const { account } = useActiveAccount();
|
||||
|
||||
const [account, updateFollows] = useActiveAccount((state: any) => [
|
||||
state.account,
|
||||
state.updateFollows,
|
||||
]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [follows, setFollows] = useState([]);
|
||||
|
||||
@ -128,9 +130,12 @@ export function Page() {
|
||||
const submit = async () => {
|
||||
setLoading(true);
|
||||
|
||||
// update account follows
|
||||
// update account follows in database
|
||||
updateAccount("follows", follows, account.pubkey);
|
||||
|
||||
// update account follows in state
|
||||
updateFollows(JSON.stringify(follows));
|
||||
|
||||
const tags = arrayToNIP02(follows);
|
||||
|
||||
const event: any = {
|
||||
|
@ -1,18 +1,19 @@
|
||||
import { User } from "@app/auth/components/user";
|
||||
import { RelayContext } from "@shared/relayProvider";
|
||||
import { useActiveAccount } from "@stores/accounts";
|
||||
import { READONLY_RELAYS } from "@stores/constants";
|
||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
||||
import { updateAccount } from "@utils/storage";
|
||||
import { nip02ToArray } from "@utils/transform";
|
||||
import { useContext, useState } from "react";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import useSWRSubscription from "swr/subscription";
|
||||
import { navigate } from "vite-plugin-ssr/client/router";
|
||||
|
||||
export function Page() {
|
||||
const pool: any = useContext(RelayContext);
|
||||
|
||||
const { account } = useActiveAccount();
|
||||
|
||||
const [account, fetchAccount, updateFollows] = useActiveAccount(
|
||||
(state: any) => [state.account, state.fetch, state.updateFollows],
|
||||
);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [follows, setFollows] = useState([]);
|
||||
|
||||
@ -42,9 +43,12 @@ export function Page() {
|
||||
// follows as list
|
||||
const followsList = nip02ToArray(follows);
|
||||
|
||||
// update account follows
|
||||
// update account follows in database
|
||||
updateAccount("follows", followsList, account.pubkey);
|
||||
|
||||
// update account follows in store
|
||||
updateFollows(JSON.stringify(followsList));
|
||||
|
||||
// redirect to home
|
||||
setTimeout(
|
||||
() => navigate("/app/prefetch", { overwriteLastHistoryEntry: true }),
|
||||
@ -52,6 +56,10 @@ export function Page() {
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchAccount();
|
||||
}, [fetchAccount]);
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<div className="mx-auto w-full max-w-md">
|
||||
|
@ -1,28 +1,21 @@
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import CancelIcon from "@icons/cancel";
|
||||
import PlusIcon from "@icons/plus";
|
||||
import { AvatarUploader } from "@shared/avatarUploader";
|
||||
import { Image } from "@shared/image";
|
||||
import { RelayContext } from "@shared/relayProvider";
|
||||
|
||||
import CancelIcon from "@icons/cancel";
|
||||
import PlusIcon from "@icons/plus";
|
||||
|
||||
import { useActiveAccount } from "@stores/accounts";
|
||||
import { DEFAULT_AVATAR, WRITEONLY_RELAYS } from "@stores/constants";
|
||||
|
||||
import { dateToUnix } from "@utils/date";
|
||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
||||
import { createChannel } from "@utils/storage";
|
||||
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { getEventHash, getSignature } from "nostr-tools";
|
||||
import { Fragment, useContext, useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useSWRConfig } from "swr";
|
||||
import { navigate } from "vite-plugin-ssr/client/router";
|
||||
|
||||
export default function ChannelCreateModal() {
|
||||
const pool: any = useContext(RelayContext);
|
||||
|
||||
const { account, isError, isLoading } = useActiveAccount();
|
||||
const { mutate } = useSWRConfig();
|
||||
const account = useActiveAccount((state: any) => state.account);
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [image, setImage] = useState(DEFAULT_AVATAR);
|
||||
@ -47,7 +40,7 @@ export default function ChannelCreateModal() {
|
||||
const onSubmit = (data: any) => {
|
||||
setLoading(true);
|
||||
|
||||
if (!isError && !isLoading && account) {
|
||||
if (account) {
|
||||
const event: any = {
|
||||
content: JSON.stringify(data),
|
||||
created_at: dateToUnix(),
|
||||
@ -62,8 +55,6 @@ export default function ChannelCreateModal() {
|
||||
pool.publish(event, WRITEONLY_RELAYS);
|
||||
// insert to database
|
||||
createChannel(event.id, event.pubkey, event.content, event.created_at);
|
||||
// update channe llist
|
||||
mutate("channels");
|
||||
// reset form
|
||||
reset();
|
||||
setTimeout(() => {
|
||||
|
@ -1,18 +1,19 @@
|
||||
import ChannelCreateModal from "@app/channel/components/createModal";
|
||||
import ChannelsListItem from "@app/channel/components/item";
|
||||
|
||||
import { getChannels } from "@utils/storage";
|
||||
|
||||
import useSWR from "swr";
|
||||
|
||||
const fetcher = () => getChannels(10, 0);
|
||||
import { useChannels } from "@stores/channels";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export default function ChannelsList() {
|
||||
const { data, error }: any = useSWR("channels", fetcher);
|
||||
const channels = useChannels((state: any) => state.channels);
|
||||
const fetchChannels = useChannels((state: any) => state.fetch);
|
||||
|
||||
useEffect(() => {
|
||||
fetchChannels();
|
||||
}, [fetchChannels]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-1">
|
||||
{!data || error ? (
|
||||
{!channels ? (
|
||||
<>
|
||||
<div className="inline-flex h-8 items-center gap-2 rounded-md px-2.5">
|
||||
<div className="relative h-5 w-5 shrink-0 animate-pulse rounded bg-zinc-800" />
|
||||
@ -24,7 +25,7 @@ export default function ChannelsList() {
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
data.map((item: { event_id: string }) => (
|
||||
channels.map((item: { event_id: string }) => (
|
||||
<ChannelsListItem key={item.event_id} data={item} />
|
||||
))
|
||||
)}
|
||||
|
@ -1,16 +1,11 @@
|
||||
import UserReply from "@app/channel/components/messages/userReply";
|
||||
|
||||
import CancelIcon from "@icons/cancel";
|
||||
import { ImagePicker } from "@shared/form/imagePicker";
|
||||
import { RelayContext } from "@shared/relayProvider";
|
||||
|
||||
import CancelIcon from "@icons/cancel";
|
||||
|
||||
import { useActiveAccount } from "@stores/accounts";
|
||||
import { channelContentAtom, channelReplyAtom } from "@stores/channel";
|
||||
import { WRITEONLY_RELAYS } from "@stores/constants";
|
||||
|
||||
import { dateToUnix } from "@utils/date";
|
||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
||||
|
||||
import { useAtom, useAtomValue } from "jotai";
|
||||
import { useResetAtom } from "jotai/utils";
|
||||
import { getEventHash, getSignature } from "nostr-tools";
|
||||
@ -20,7 +15,7 @@ export default function ChannelMessageForm({
|
||||
channelID,
|
||||
}: { channelID: string | string[] }) {
|
||||
const pool: any = useContext(RelayContext);
|
||||
const { account, isLoading, isError } = useActiveAccount();
|
||||
const account = useActiveAccount((state: any) => state.account);
|
||||
|
||||
const [value, setValue] = useAtom(channelContentAtom);
|
||||
const resetValue = useResetAtom(channelContentAtom);
|
||||
@ -41,7 +36,7 @@ export default function ChannelMessageForm({
|
||||
tags = [["e", channelID, "", "root"]];
|
||||
}
|
||||
|
||||
if (!isError && !isLoading && account) {
|
||||
if (account) {
|
||||
const event: any = {
|
||||
content: value,
|
||||
created_at: dateToUnix(),
|
||||
@ -49,11 +44,13 @@ export default function ChannelMessageForm({
|
||||
pubkey: account.pubkey,
|
||||
tags: tags,
|
||||
};
|
||||
|
||||
event.id = getEventHash(event);
|
||||
event.sig = getSignature(event, account.privkey);
|
||||
|
||||
// publish note
|
||||
pool.publish(event, WRITEONLY_RELAYS);
|
||||
|
||||
// reset state
|
||||
resetValue();
|
||||
// reset channel reply
|
||||
|
@ -1,23 +1,19 @@
|
||||
import { RelayContext } from "@shared/relayProvider";
|
||||
import { Tooltip } from "@shared/tooltip";
|
||||
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import CancelIcon from "@icons/cancel";
|
||||
import HideIcon from "@icons/hide";
|
||||
|
||||
import { RelayContext } from "@shared/relayProvider";
|
||||
import { Tooltip } from "@shared/tooltip";
|
||||
import { useActiveAccount } from "@stores/accounts";
|
||||
import { channelMessagesAtom } from "@stores/channel";
|
||||
import { WRITEONLY_RELAYS } from "@stores/constants";
|
||||
|
||||
import { dateToUnix } from "@utils/date";
|
||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
||||
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { useAtom } from "jotai";
|
||||
import { getEventHash, getSignature } from "nostr-tools";
|
||||
import { Fragment, useContext, useState } from "react";
|
||||
|
||||
export default function MessageHideButton({ id }: { id: string }) {
|
||||
const pool: any = useContext(RelayContext);
|
||||
const { account, isError, isLoading } = useActiveAccount();
|
||||
const account = useActiveAccount((state: any) => state.account);
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [messages, setMessages] = useAtom(channelMessagesAtom);
|
||||
@ -31,7 +27,7 @@ export default function MessageHideButton({ id }: { id: string }) {
|
||||
};
|
||||
|
||||
const hideMessage = () => {
|
||||
if (!isError && !isLoading && account) {
|
||||
if (account) {
|
||||
const event: any = {
|
||||
content: "",
|
||||
created_at: dateToUnix(),
|
||||
@ -44,13 +40,16 @@ export default function MessageHideButton({ id }: { id: string }) {
|
||||
|
||||
// publish note
|
||||
pool.publish(event, WRITEONLY_RELAYS);
|
||||
|
||||
// update local state
|
||||
const cloneMessages = [...messages];
|
||||
const targetMessage = cloneMessages.findIndex(
|
||||
(message) => message.id === id,
|
||||
);
|
||||
|
||||
cloneMessages[targetMessage]["hide"] = true;
|
||||
setMessages(cloneMessages);
|
||||
|
||||
// close modal
|
||||
closeModal();
|
||||
} else {
|
||||
|
@ -1,23 +1,19 @@
|
||||
import { RelayContext } from "@shared/relayProvider";
|
||||
import { Tooltip } from "@shared/tooltip";
|
||||
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import CancelIcon from "@icons/cancel";
|
||||
import MuteIcon from "@icons/mute";
|
||||
|
||||
import { RelayContext } from "@shared/relayProvider";
|
||||
import { Tooltip } from "@shared/tooltip";
|
||||
import { useActiveAccount } from "@stores/accounts";
|
||||
import { channelMessagesAtom } from "@stores/channel";
|
||||
import { WRITEONLY_RELAYS } from "@stores/constants";
|
||||
|
||||
import { dateToUnix } from "@utils/date";
|
||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
||||
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { useAtom } from "jotai";
|
||||
import { getEventHash, getSignature } from "nostr-tools";
|
||||
import { Fragment, useContext, useState } from "react";
|
||||
|
||||
export default function MessageMuteButton({ pubkey }: { pubkey: string }) {
|
||||
const pool: any = useContext(RelayContext);
|
||||
const { account, isError, isLoading } = useActiveAccount();
|
||||
const account = useActiveAccount((state: any) => state.account);
|
||||
|
||||
const [messages, setMessages] = useAtom(channelMessagesAtom);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
@ -31,7 +27,7 @@ export default function MessageMuteButton({ pubkey }: { pubkey: string }) {
|
||||
};
|
||||
|
||||
const muteUser = () => {
|
||||
if (!isError && !isLoading && account) {
|
||||
if (account) {
|
||||
const event: any = {
|
||||
content: "",
|
||||
created_at: dateToUnix(),
|
||||
@ -44,6 +40,7 @@ export default function MessageMuteButton({ pubkey }: { pubkey: string }) {
|
||||
|
||||
// publish note
|
||||
pool.publish(event, WRITEONLY_RELAYS);
|
||||
|
||||
// update local state
|
||||
const cloneMessages = [...messages];
|
||||
const finalMessages = cloneMessages.filter(
|
||||
|
@ -1,24 +1,20 @@
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import CancelIcon from "@icons/cancel";
|
||||
import EditIcon from "@icons/edit";
|
||||
import { AvatarUploader } from "@shared/avatarUploader";
|
||||
import { Image } from "@shared/image";
|
||||
import { RelayContext } from "@shared/relayProvider";
|
||||
|
||||
import CancelIcon from "@icons/cancel";
|
||||
import EditIcon from "@icons/edit";
|
||||
|
||||
import { useActiveAccount } from "@stores/accounts";
|
||||
import { DEFAULT_AVATAR, WRITEONLY_RELAYS } from "@stores/constants";
|
||||
|
||||
import { dateToUnix } from "@utils/date";
|
||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
||||
import { getChannel } from "@utils/storage";
|
||||
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { getEventHash, getSignature } from "nostr-tools";
|
||||
import { Fragment, useContext, useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
export default function ChannelUpdateModal({ id }: { id: string }) {
|
||||
const pool: any = useContext(RelayContext);
|
||||
const { account, isError, isLoading } = useActiveAccount();
|
||||
const account = useActiveAccount((state: any) => state.account);
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [image, setImage] = useState(DEFAULT_AVATAR);
|
||||
@ -52,7 +48,7 @@ export default function ChannelUpdateModal({ id }: { id: string }) {
|
||||
const onSubmit = (data: any) => {
|
||||
setLoading(true);
|
||||
|
||||
if (!isError && !isLoading && account) {
|
||||
if (account) {
|
||||
const event: any = {
|
||||
content: JSON.stringify(data),
|
||||
created_at: dateToUnix(),
|
||||
@ -60,11 +56,13 @@ export default function ChannelUpdateModal({ id }: { id: string }) {
|
||||
pubkey: account.pubkey,
|
||||
tags: [["e", id]],
|
||||
};
|
||||
|
||||
event.id = getEventHash(event);
|
||||
event.sig = getSignature(event, account.privkey);
|
||||
|
||||
// publish channel
|
||||
pool.publish(event, WRITEONLY_RELAYS);
|
||||
|
||||
// reset form
|
||||
reset();
|
||||
// close modal
|
||||
|
@ -3,18 +3,14 @@ import ChannelMembers from "@app/channel/components/members";
|
||||
import ChannelMessageForm from "@app/channel/components/messages/form";
|
||||
import ChannelMetadata from "@app/channel/components/metadata";
|
||||
import ChannelUpdateModal from "@app/channel/components/updateModal";
|
||||
|
||||
import { RelayContext } from "@shared/relayProvider";
|
||||
|
||||
import { useActiveAccount } from "@stores/accounts";
|
||||
import { channelMessagesAtom, channelReplyAtom } from "@stores/channel";
|
||||
import { READONLY_RELAYS } from "@stores/constants";
|
||||
|
||||
import { dateToUnix, getHourAgo } from "@utils/date";
|
||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
||||
import { usePageContext } from "@utils/hooks/usePageContext";
|
||||
import { getActiveBlacklist, getBlacklist } from "@utils/storage";
|
||||
import { arrayObjToPureArr } from "@utils/transform";
|
||||
|
||||
import { useSetAtom } from "jotai";
|
||||
import { useResetAtom } from "jotai/utils";
|
||||
import { Suspense, lazy, useContext, useEffect, useRef } from "react";
|
||||
@ -39,19 +35,20 @@ const ChannelMessageList = lazy(
|
||||
|
||||
export function Page() {
|
||||
const pool: any = useContext(RelayContext);
|
||||
const account: any = useActiveAccount((state: any) => state.account);
|
||||
|
||||
const pageContext = usePageContext();
|
||||
const searchParams: any = pageContext.urlParsed.search;
|
||||
|
||||
const channelID = searchParams.id;
|
||||
const channelPubkey = searchParams.channelpub;
|
||||
|
||||
const { account, isLoading, isError } = useActiveAccount();
|
||||
const { data: muted } = useSWR(
|
||||
!isLoading && !isError && account ? ["muted", account.id] : null,
|
||||
account ? ["muted", account.id] : null,
|
||||
fetchMuted,
|
||||
);
|
||||
const { data: hided } = useSWR(
|
||||
!isLoading && !isError && account ? ["hided", account.id] : null,
|
||||
account ? ["hided", account.id] : null,
|
||||
fetchHided,
|
||||
);
|
||||
|
||||
@ -118,7 +115,7 @@ export function Page() {
|
||||
<div className="flex items-center gap-2">
|
||||
<ChannelMembers />
|
||||
{!muted ? <></> : <ChannelBlackList blacklist={muted.original} />}
|
||||
{!isLoading && !isError && account ? (
|
||||
{account ? (
|
||||
account.pubkey === channelPubkey && (
|
||||
<ChannelUpdateModal id={channelID} />
|
||||
)
|
||||
|
@ -1,25 +1,23 @@
|
||||
import ChatsListItem from "@app/chat/components/item";
|
||||
import ChatsListSelfItem from "@app/chat/components/self";
|
||||
|
||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
||||
import { getChatsByPubkey } from "@utils/storage";
|
||||
|
||||
import useSWR from "swr";
|
||||
|
||||
const fetcher = ([, pubkey]) => getChatsByPubkey(pubkey);
|
||||
import { useActiveAccount } from "@stores/accounts";
|
||||
import { useChats } from "@stores/chats";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export default function ChatsList() {
|
||||
const { account, isLoading, isError } = useActiveAccount();
|
||||
const account = useActiveAccount((state: any) => state.account);
|
||||
const chats = useChats((state: any) => state.chats);
|
||||
const fetchChats = useChats((state: any) => state.fetch);
|
||||
|
||||
const { data: chats, error }: any = useSWR(
|
||||
!isLoading && !isError && account ? ["chats", account.pubkey] : null,
|
||||
fetcher,
|
||||
);
|
||||
useEffect(() => {
|
||||
if (!account) return;
|
||||
fetchChats(account.pubkey);
|
||||
}, [fetchChats]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-1">
|
||||
<ChatsListSelfItem />
|
||||
{!chats || error ? (
|
||||
{!chats ? (
|
||||
<>
|
||||
<div className="inline-flex h-8 items-center gap-2 rounded-md px-2.5">
|
||||
<div className="relative h-5 w-5 shrink-0 animate-pulse rounded bg-zinc-800" />
|
||||
|
@ -1,15 +1,12 @@
|
||||
import { ChatMessageItem } from "@app/chat/components/messages/item";
|
||||
|
||||
import { useActiveAccount } from "@stores/accounts";
|
||||
import { sortedChatMessagesAtom } from "@stores/chat";
|
||||
|
||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
||||
|
||||
import { useAtomValue } from "jotai";
|
||||
import { useCallback, useRef } from "react";
|
||||
import { Virtuoso } from "react-virtuoso";
|
||||
|
||||
export default function ChatMessageList() {
|
||||
const { account } = useActiveAccount();
|
||||
const account = useActiveAccount((state: any) => state.account);
|
||||
|
||||
const virtuosoRef = useRef(null);
|
||||
const data = useAtomValue(sortedChatMessagesAtom);
|
||||
|
@ -1,12 +1,9 @@
|
||||
import { ImagePicker } from "@shared/form/imagePicker";
|
||||
import { RelayContext } from "@shared/relayProvider";
|
||||
|
||||
import { useActiveAccount } from "@stores/accounts";
|
||||
import { chatContentAtom } from "@stores/chat";
|
||||
import { WRITEONLY_RELAYS } from "@stores/constants";
|
||||
|
||||
import { dateToUnix } from "@utils/date";
|
||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
||||
|
||||
import { useAtom } from "jotai";
|
||||
import { useResetAtom } from "jotai/utils";
|
||||
import { getEventHash, getSignature, nip04 } from "nostr-tools";
|
||||
@ -16,7 +13,7 @@ export default function ChatMessageForm({
|
||||
receiverPubkey,
|
||||
}: { receiverPubkey: string }) {
|
||||
const pool: any = useContext(RelayContext);
|
||||
const { account, isLoading, isError } = useActiveAccount();
|
||||
const account = useActiveAccount((state: any) => state.account);
|
||||
|
||||
const [value, setValue] = useAtom(chatContentAtom);
|
||||
const resetValue = useResetAtom(chatContentAtom);
|
||||
@ -29,25 +26,23 @@ export default function ChatMessageForm({
|
||||
);
|
||||
|
||||
const submitEvent = () => {
|
||||
if (!isError && !isLoading && account) {
|
||||
encryptMessage(account.privkey)
|
||||
.then((encryptedContent) => {
|
||||
const event: any = {
|
||||
content: encryptedContent,
|
||||
created_at: dateToUnix(),
|
||||
kind: 4,
|
||||
pubkey: account.pubkey,
|
||||
tags: [["p", receiverPubkey]],
|
||||
};
|
||||
event.id = getEventHash(event);
|
||||
event.sig = getSignature(event, account.privkey);
|
||||
// publish note
|
||||
pool.publish(event, WRITEONLY_RELAYS);
|
||||
// reset state
|
||||
resetValue();
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
encryptMessage(account.privkey)
|
||||
.then((encryptedContent) => {
|
||||
const event: any = {
|
||||
content: encryptedContent,
|
||||
created_at: dateToUnix(),
|
||||
kind: 4,
|
||||
pubkey: account.pubkey,
|
||||
tags: [["p", receiverPubkey]],
|
||||
};
|
||||
event.id = getEventHash(event);
|
||||
event.sig = getSignature(event, account.privkey);
|
||||
// publish note
|
||||
pool.publish(event, WRITEONLY_RELAYS);
|
||||
// reset state
|
||||
resetValue();
|
||||
})
|
||||
.catch(console.error);
|
||||
};
|
||||
|
||||
const handleEnterPress = (e) => {
|
||||
|
@ -1,11 +1,8 @@
|
||||
import { Image } from "@shared/image";
|
||||
|
||||
import { useActiveAccount } from "@stores/accounts";
|
||||
import { DEFAULT_AVATAR } from "@stores/constants";
|
||||
|
||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
||||
import { usePageContext } from "@utils/hooks/usePageContext";
|
||||
import { shortenKey } from "@utils/shortenKey";
|
||||
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export default function ChatsListSelfItem() {
|
||||
@ -14,12 +11,11 @@ export default function ChatsListSelfItem() {
|
||||
const searchParams: any = pageContext.urlParsed.search;
|
||||
const pagePubkey = searchParams.pubkey;
|
||||
|
||||
const { account, isLoading, isError } = useActiveAccount();
|
||||
const account = useActiveAccount((state: any) => state.account);
|
||||
|
||||
return (
|
||||
<>
|
||||
{isError && <div>error</div>}
|
||||
{isLoading && !account ? (
|
||||
{!account ? (
|
||||
<div className="inline-flex h-8 items-center gap-2.5 rounded-md px-2.5">
|
||||
<div className="relative h-5 w-5 shrink-0 animate-pulse rounded bg-zinc-800" />
|
||||
<div>
|
||||
|
@ -1,13 +1,9 @@
|
||||
import ChatMessageForm from "@app/chat/components/messages/form";
|
||||
|
||||
import { RelayContext } from "@shared/relayProvider";
|
||||
|
||||
import { useActiveAccount } from "@stores/accounts";
|
||||
import { chatMessagesAtom } from "@stores/chat";
|
||||
import { READONLY_RELAYS } from "@stores/constants";
|
||||
|
||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
||||
import { usePageContext } from "@utils/hooks/usePageContext";
|
||||
|
||||
import { useSetAtom } from "jotai";
|
||||
import { useResetAtom } from "jotai/utils";
|
||||
import { Suspense, lazy, useContext, useEffect } from "react";
|
||||
@ -17,13 +13,12 @@ const ChatMessageList = lazy(() => import("@app/chat/components/messageList"));
|
||||
|
||||
export function Page() {
|
||||
const pool: any = useContext(RelayContext);
|
||||
const account = useActiveAccount((state: any) => state.account);
|
||||
|
||||
const pageContext = usePageContext();
|
||||
const searchParams: any = pageContext.urlParsed.search;
|
||||
|
||||
const pubkey = searchParams.pubkey;
|
||||
|
||||
const { account } = useActiveAccount();
|
||||
|
||||
const setChatMessages = useSetAtom(chatMessagesAtom);
|
||||
const resetChatMessages = useResetAtom(chatMessagesAtom);
|
||||
|
||||
|
@ -1,17 +1,23 @@
|
||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
||||
import { useActiveAccount } from "@stores/accounts";
|
||||
import { useEffect } from "react";
|
||||
import { navigate } from "vite-plugin-ssr/client/router";
|
||||
|
||||
export function Page() {
|
||||
const { account, isLoading } = useActiveAccount();
|
||||
const fetchAccount = useActiveAccount((state: any) => state.fetch);
|
||||
const account = useActiveAccount((state: any) => state.account);
|
||||
|
||||
if (!isLoading && !account) {
|
||||
if (!account) {
|
||||
navigate("/app/auth", { overwriteLastHistoryEntry: true });
|
||||
}
|
||||
|
||||
if (!isLoading && account) {
|
||||
if (account) {
|
||||
navigate("/app/prefetch", { overwriteLastHistoryEntry: true });
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchAccount();
|
||||
}, [fetchAccount]);
|
||||
|
||||
return (
|
||||
<div className="h-screen w-screen bg-zinc-50 text-zinc-900 dark:bg-black dark:text-white" />
|
||||
);
|
||||
|
@ -1,12 +1,8 @@
|
||||
import { RelayContext } from "@shared/relayProvider";
|
||||
|
||||
import LikeIcon from "@icons/like";
|
||||
|
||||
import { RelayContext } from "@shared/relayProvider";
|
||||
import { useActiveAccount } from "@stores/accounts";
|
||||
import { WRITEONLY_RELAYS } from "@stores/constants";
|
||||
|
||||
import { dateToUnix } from "@utils/date";
|
||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
||||
|
||||
import { getEventHash, getSignature } from "nostr-tools";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
|
||||
@ -16,33 +12,31 @@ export default function NoteLike({
|
||||
likes,
|
||||
}: { id: string; pubkey: string; likes: number }) {
|
||||
const pool: any = useContext(RelayContext);
|
||||
const { account, isLoading, isError } = useActiveAccount();
|
||||
const account = useActiveAccount((state: any) => state.account);
|
||||
|
||||
const [count, setCount] = useState(0);
|
||||
|
||||
const submitEvent = (e: any) => {
|
||||
e.stopPropagation();
|
||||
|
||||
if (!isLoading && !isError && account) {
|
||||
const event: any = {
|
||||
content: "+",
|
||||
kind: 7,
|
||||
tags: [
|
||||
["e", id],
|
||||
["p", pubkey],
|
||||
],
|
||||
created_at: dateToUnix(),
|
||||
pubkey: account.pubkey,
|
||||
};
|
||||
event.id = getEventHash(event);
|
||||
event.sig = getSignature(event, account.privkey);
|
||||
// publish event to all relays
|
||||
pool.publish(event, WRITEONLY_RELAYS);
|
||||
// update state
|
||||
setCount(count + 1);
|
||||
} else {
|
||||
console.log("error");
|
||||
}
|
||||
const event: any = {
|
||||
content: "+",
|
||||
kind: 7,
|
||||
tags: [
|
||||
["e", id],
|
||||
["p", pubkey],
|
||||
],
|
||||
created_at: dateToUnix(),
|
||||
pubkey: account.pubkey,
|
||||
};
|
||||
|
||||
event.id = getEventHash(event);
|
||||
event.sig = getSignature(event, account.privkey);
|
||||
|
||||
// publish event to all relays
|
||||
pool.publish(event, WRITEONLY_RELAYS);
|
||||
// update state
|
||||
setCount(count + 1);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -1,14 +1,10 @@
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import ReplyIcon from "@icons/reply";
|
||||
import { Image } from "@shared/image";
|
||||
import { RelayContext } from "@shared/relayProvider";
|
||||
|
||||
import ReplyIcon from "@icons/reply";
|
||||
|
||||
import { useActiveAccount } from "@stores/accounts";
|
||||
import { WRITEONLY_RELAYS } from "@stores/constants";
|
||||
|
||||
import { dateToUnix } from "@utils/date";
|
||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
||||
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { compactNumber } from "@utils/number";
|
||||
import { getEventHash, getSignature } from "nostr-tools";
|
||||
import { Fragment, useContext, useEffect, useState } from "react";
|
||||
@ -18,13 +14,12 @@ export default function NoteReply({
|
||||
replies,
|
||||
}: { id: string; replies: number }) {
|
||||
const pool: any = useContext(RelayContext);
|
||||
const account = useActiveAccount((state: any) => state.account);
|
||||
|
||||
const [count, setCount] = useState(0);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [value, setValue] = useState("");
|
||||
|
||||
const { account, isLoading, isError } = useActiveAccount();
|
||||
|
||||
const closeModal = () => {
|
||||
setIsOpen(false);
|
||||
};
|
||||
@ -34,25 +29,24 @@ export default function NoteReply({
|
||||
};
|
||||
|
||||
const submitEvent = () => {
|
||||
if (!isLoading && !isError && account) {
|
||||
const event: any = {
|
||||
content: value,
|
||||
created_at: dateToUnix(),
|
||||
kind: 1,
|
||||
pubkey: account.pubkey,
|
||||
tags: [["e", id]],
|
||||
};
|
||||
event.id = getEventHash(event);
|
||||
event.sig = getSignature(event, account.privkey);
|
||||
const event: any = {
|
||||
content: value,
|
||||
created_at: dateToUnix(),
|
||||
kind: 1,
|
||||
pubkey: account.pubkey,
|
||||
tags: [["e", id]],
|
||||
};
|
||||
|
||||
// publish event
|
||||
pool.publish(event, WRITEONLY_RELAYS);
|
||||
// close modal
|
||||
setIsOpen(false);
|
||||
setCount(count + 1);
|
||||
} else {
|
||||
console.log("error");
|
||||
}
|
||||
event.id = getEventHash(event);
|
||||
event.sig = getSignature(event, account.privkey);
|
||||
|
||||
// publish event
|
||||
pool.publish(event, WRITEONLY_RELAYS);
|
||||
|
||||
// close modal
|
||||
setIsOpen(false);
|
||||
// increment replies
|
||||
setCount(count + 1);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -1,12 +1,8 @@
|
||||
import { RelayContext } from "@shared/relayProvider";
|
||||
|
||||
import RepostIcon from "@icons/repost";
|
||||
|
||||
import { RelayContext } from "@shared/relayProvider";
|
||||
import { useActiveAccount } from "@stores/accounts";
|
||||
import { WRITEONLY_RELAYS } from "@stores/constants";
|
||||
|
||||
import { dateToUnix } from "@utils/date";
|
||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
||||
|
||||
import { compactNumber } from "@utils/number";
|
||||
import { getEventHash, getSignature } from "nostr-tools";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
@ -17,33 +13,32 @@ export default function NoteRepost({
|
||||
reposts,
|
||||
}: { id: string; pubkey: string; reposts: number }) {
|
||||
const pool: any = useContext(RelayContext);
|
||||
const { account, isLoading, isError } = useActiveAccount();
|
||||
const account = useActiveAccount((state: any) => state.account);
|
||||
|
||||
const [count, setCount] = useState(0);
|
||||
|
||||
const submitEvent = (e: any) => {
|
||||
e.stopPropagation();
|
||||
|
||||
if (!isLoading && !isError && account) {
|
||||
const event: any = {
|
||||
content: "",
|
||||
kind: 6,
|
||||
tags: [
|
||||
["e", id],
|
||||
["p", pubkey],
|
||||
],
|
||||
created_at: dateToUnix(),
|
||||
pubkey: account.pubkey,
|
||||
};
|
||||
event.id = getEventHash(event);
|
||||
event.sig = getSignature(event, account.privkey);
|
||||
// publish event to all relays
|
||||
pool.publish(event, WRITEONLY_RELAYS);
|
||||
// update state
|
||||
setCount(count + 1);
|
||||
} else {
|
||||
console.log("error");
|
||||
}
|
||||
const event: any = {
|
||||
content: "",
|
||||
kind: 6,
|
||||
tags: [
|
||||
["e", id],
|
||||
["p", pubkey],
|
||||
],
|
||||
created_at: dateToUnix(),
|
||||
pubkey: account.pubkey,
|
||||
};
|
||||
|
||||
event.id = getEventHash(event);
|
||||
event.sig = getSignature(event, account.privkey);
|
||||
|
||||
// publish event to all relays
|
||||
pool.publish(event, WRITEONLY_RELAYS);
|
||||
|
||||
// update state
|
||||
setCount(count + 1);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -1,39 +1,34 @@
|
||||
import { Image } from "@shared/image";
|
||||
import { RelayContext } from "@shared/relayProvider";
|
||||
|
||||
import { useActiveAccount } from "@stores/accounts";
|
||||
import { WRITEONLY_RELAYS } from "@stores/constants";
|
||||
|
||||
import { dateToUnix } from "@utils/date";
|
||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
||||
|
||||
import { getEventHash, getSignature } from "nostr-tools";
|
||||
import { useContext, useState } from "react";
|
||||
|
||||
export default function NoteReplyForm({ id }: { id: string }) {
|
||||
const pool: any = useContext(RelayContext);
|
||||
const account = useActiveAccount((state: any) => state.account);
|
||||
|
||||
const { account, isLoading, isError } = useActiveAccount();
|
||||
const [value, setValue] = useState("");
|
||||
|
||||
const submitEvent = () => {
|
||||
if (!isLoading && !isError && account) {
|
||||
const event: any = {
|
||||
content: value,
|
||||
created_at: dateToUnix(),
|
||||
kind: 1,
|
||||
pubkey: account.pubkey,
|
||||
tags: [["e", id]],
|
||||
};
|
||||
event.id = getEventHash(event);
|
||||
event.sig = getSignature(event, account.privkey);
|
||||
const event: any = {
|
||||
content: value,
|
||||
created_at: dateToUnix(),
|
||||
kind: 1,
|
||||
pubkey: account.pubkey,
|
||||
tags: [["e", id]],
|
||||
};
|
||||
|
||||
// publish note
|
||||
pool.publish(event, WRITEONLY_RELAYS);
|
||||
// reset form
|
||||
setValue("");
|
||||
} else {
|
||||
console.log("error");
|
||||
}
|
||||
event.id = getEventHash(event);
|
||||
event.sig = getSignature(event, account.privkey);
|
||||
|
||||
// publish note
|
||||
pool.publish(event, WRITEONLY_RELAYS);
|
||||
|
||||
// reset form
|
||||
setValue("");
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -1,8 +1,8 @@
|
||||
import LumeIcon from "@icons/lume";
|
||||
import { RelayContext } from "@shared/relayProvider";
|
||||
import { useActiveAccount } from "@stores/accounts";
|
||||
import { READONLY_RELAYS } from "@stores/constants";
|
||||
import { dateToUnix, getHourAgo } from "@utils/date";
|
||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
||||
import {
|
||||
addToBlacklist,
|
||||
countTotalNotes,
|
||||
@ -15,15 +15,6 @@ import { useCallback, useContext, useRef } from "react";
|
||||
import useSWRSubscription from "swr/subscription";
|
||||
import { navigate } from "vite-plugin-ssr/client/router";
|
||||
|
||||
function isJSON(str: string) {
|
||||
try {
|
||||
JSON.parse(str);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
let lastLogin: string;
|
||||
let totalNotes: number;
|
||||
|
||||
@ -34,12 +25,11 @@ if (typeof window !== "undefined") {
|
||||
|
||||
export function Page() {
|
||||
const pool: any = useContext(RelayContext);
|
||||
const account = useActiveAccount((state: any) => state.account);
|
||||
|
||||
const now = useRef(new Date());
|
||||
const eose = useRef(0);
|
||||
|
||||
const { account, isLoading, isError } = useActiveAccount();
|
||||
|
||||
const getQuery = useCallback(() => {
|
||||
const query = [];
|
||||
const follows = JSON.parse(account.follows);
|
||||
@ -79,98 +69,95 @@ export function Page() {
|
||||
});
|
||||
|
||||
return query;
|
||||
}, [account.follows]);
|
||||
}, [account]);
|
||||
|
||||
useSWRSubscription(
|
||||
!isLoading && !isError && account ? "prefetch" : null,
|
||||
() => {
|
||||
const query = getQuery();
|
||||
const unsubscribe = pool.subscribe(
|
||||
query,
|
||||
READONLY_RELAYS,
|
||||
(event: any) => {
|
||||
switch (event.kind) {
|
||||
// short text note
|
||||
case 1: {
|
||||
const parentID = getParentID(event.tags, event.id);
|
||||
// insert event to local database
|
||||
createNote(
|
||||
event.id,
|
||||
account.id,
|
||||
event.pubkey,
|
||||
event.kind,
|
||||
event.tags,
|
||||
event.content,
|
||||
event.created_at,
|
||||
parentID,
|
||||
);
|
||||
break;
|
||||
useSWRSubscription(account ? "prefetch" : null, () => {
|
||||
const query = getQuery();
|
||||
const unsubscribe = pool.subscribe(
|
||||
query,
|
||||
READONLY_RELAYS,
|
||||
(event: any) => {
|
||||
switch (event.kind) {
|
||||
// short text note
|
||||
case 1: {
|
||||
const parentID = getParentID(event.tags, event.id);
|
||||
// insert event to local database
|
||||
createNote(
|
||||
event.id,
|
||||
account.id,
|
||||
event.pubkey,
|
||||
event.kind,
|
||||
event.tags,
|
||||
event.content,
|
||||
event.created_at,
|
||||
parentID,
|
||||
);
|
||||
break;
|
||||
}
|
||||
// chat
|
||||
case 4:
|
||||
createChat(
|
||||
event.id,
|
||||
account.pubkey,
|
||||
event.pubkey,
|
||||
event.content,
|
||||
event.created_at,
|
||||
);
|
||||
break;
|
||||
// repost
|
||||
case 6:
|
||||
createNote(
|
||||
event.id,
|
||||
account.id,
|
||||
event.pubkey,
|
||||
event.kind,
|
||||
event.tags,
|
||||
event.content,
|
||||
event.created_at,
|
||||
event.id,
|
||||
);
|
||||
break;
|
||||
// hide message (channel only)
|
||||
case 43:
|
||||
if (event.tags[0][0] === "e") {
|
||||
addToBlacklist(account.id, event.tags[0][1], 43, 1);
|
||||
}
|
||||
// chat
|
||||
case 4:
|
||||
createChat(
|
||||
event.id,
|
||||
account.pubkey,
|
||||
event.pubkey,
|
||||
event.content,
|
||||
event.created_at,
|
||||
);
|
||||
break;
|
||||
// repost
|
||||
case 6:
|
||||
createNote(
|
||||
event.id,
|
||||
account.id,
|
||||
event.pubkey,
|
||||
event.kind,
|
||||
event.tags,
|
||||
event.content,
|
||||
event.created_at,
|
||||
event.id,
|
||||
);
|
||||
break;
|
||||
// hide message (channel only)
|
||||
case 43:
|
||||
if (event.tags[0][0] === "e") {
|
||||
addToBlacklist(account.id, event.tags[0][1], 43, 1);
|
||||
}
|
||||
break;
|
||||
// mute user (channel only)
|
||||
case 44:
|
||||
if (event.tags[0][0] === "p") {
|
||||
addToBlacklist(account.id, event.tags[0][1], 44, 1);
|
||||
}
|
||||
break;
|
||||
case 1063:
|
||||
createNote(
|
||||
event.id,
|
||||
account.id,
|
||||
event.pubkey,
|
||||
event.kind,
|
||||
event.tags,
|
||||
event.content,
|
||||
event.created_at,
|
||||
"",
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
undefined,
|
||||
() => {
|
||||
eose.current += 1;
|
||||
if (eose.current === READONLY_RELAYS.length) {
|
||||
navigate("/app/space", { overwriteLastHistoryEntry: true });
|
||||
}
|
||||
},
|
||||
);
|
||||
break;
|
||||
// mute user (channel only)
|
||||
case 44:
|
||||
if (event.tags[0][0] === "p") {
|
||||
addToBlacklist(account.id, event.tags[0][1], 44, 1);
|
||||
}
|
||||
break;
|
||||
case 1063:
|
||||
createNote(
|
||||
event.id,
|
||||
account.id,
|
||||
event.pubkey,
|
||||
event.kind,
|
||||
event.tags,
|
||||
event.content,
|
||||
event.created_at,
|
||||
"",
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
undefined,
|
||||
() => {
|
||||
eose.current += 1;
|
||||
if (eose.current === READONLY_RELAYS.length) {
|
||||
navigate("/app/space", { overwriteLastHistoryEntry: true });
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
},
|
||||
);
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="h-screen w-screen bg-zinc-50 text-zinc-900 dark:bg-black dark:text-white">
|
||||
|
@ -3,19 +3,15 @@ import { Dialog, Transition } from "@headlessui/react";
|
||||
import CancelIcon from "@icons/cancel";
|
||||
import PlusIcon from "@icons/plus";
|
||||
import { Image } from "@shared/image";
|
||||
import { useActiveAccount } from "@stores/accounts";
|
||||
import { DEFAULT_AVATAR } from "@stores/constants";
|
||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
||||
import { createBlock, getPlebs } from "@utils/storage";
|
||||
import { createBlock } from "@utils/storage";
|
||||
import { Fragment, useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import useSWR from "swr";
|
||||
|
||||
const fetcher = () => getPlebs();
|
||||
|
||||
export function CreateBlockModal() {
|
||||
const { account } = useActiveAccount();
|
||||
const account = useActiveAccount((state: any) => state.account);
|
||||
const { register, handleSubmit, reset, watch, setValue } = useForm();
|
||||
const { data: plebs } = useSWR("plebs", fetcher);
|
||||
|
||||
const [image, setImage] = useState(DEFAULT_AVATAR);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
@ -1,24 +1,18 @@
|
||||
import { Post } from "@shared/composer/types/post";
|
||||
import { User } from "@shared/composer/user";
|
||||
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import CancelIcon from "@icons/cancel";
|
||||
import ChevronDownIcon from "@icons/chevronDown";
|
||||
import ChevronRightIcon from "@icons/chevronRight";
|
||||
import ComposeIcon from "@icons/compose";
|
||||
|
||||
import { composerAtom } from "@stores/composer";
|
||||
|
||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
||||
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { useAtom } from "jotai";
|
||||
import { Post } from "@shared/composer/types/post";
|
||||
import { User } from "@shared/composer/user";
|
||||
import { useActiveAccount } from "@stores/accounts";
|
||||
import { Fragment, useState } from "react";
|
||||
|
||||
export function ComposerModal() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [composer] = useAtom(composerAtom);
|
||||
const [composer] = useState({ type: "post" });
|
||||
|
||||
const { account, isLoading, isError } = useActiveAccount();
|
||||
const account = useActiveAccount((state: any) => state.account);
|
||||
|
||||
const closeModal = () => {
|
||||
setIsOpen(false);
|
||||
@ -64,11 +58,7 @@ export function ComposerModal() {
|
||||
<Dialog.Panel className="relative h-min w-full max-w-xl rounded-lg border border-zinc-800 bg-zinc-900">
|
||||
<div className="flex items-center justify-between px-4 py-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<div>
|
||||
{!isLoading && !isError && account && (
|
||||
<User data={account} />
|
||||
)}
|
||||
</div>
|
||||
<div>{account && <User data={account} />}</div>
|
||||
<span>
|
||||
<ChevronRightIcon
|
||||
width={14}
|
||||
|
@ -1,141 +1,119 @@
|
||||
import { RelayContext } from "@shared/relayProvider";
|
||||
|
||||
import HeartBeatIcon from "@icons/heartbeat";
|
||||
|
||||
import { RelayContext } from "@shared/relayProvider";
|
||||
import { useActiveAccount } from "@stores/accounts";
|
||||
import { READONLY_RELAYS } from "@stores/constants";
|
||||
import { hasNewerNoteAtom } from "@stores/note";
|
||||
|
||||
import { TauriEvent } from "@tauri-apps/api/event";
|
||||
import { appWindow, getCurrent } from "@tauri-apps/api/window";
|
||||
import { dateToUnix } from "@utils/date";
|
||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
||||
import { createChat, createNote, updateAccount } from "@utils/storage";
|
||||
import {
|
||||
createChat,
|
||||
createNote,
|
||||
updateAccount,
|
||||
updateLastLogin,
|
||||
} from "@utils/storage";
|
||||
import { getParentID, nip02ToArray } from "@utils/transform";
|
||||
|
||||
import { useSetAtom } from "jotai";
|
||||
import { useContext, useRef } from "react";
|
||||
import { useContext, useEffect, useRef } from "react";
|
||||
import useSWRSubscription from "swr/subscription";
|
||||
|
||||
function isJSON(str: string) {
|
||||
try {
|
||||
JSON.parse(str);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export default function EventCollector() {
|
||||
const pool: any = useContext(RelayContext);
|
||||
|
||||
const setHasNewerNote = useSetAtom(hasNewerNoteAtom);
|
||||
|
||||
const account = useActiveAccount((state: any) => state.account);
|
||||
const now = useRef(new Date());
|
||||
|
||||
const { account, isLoading, isError } = useActiveAccount();
|
||||
|
||||
useSWRSubscription(
|
||||
!isLoading && !isError && account ? ["eventCollector", account] : null,
|
||||
([, key]) => {
|
||||
const follows = JSON.parse(key.follows);
|
||||
const followsAsArray = nip02ToArray(follows);
|
||||
const unsubscribe = pool.subscribe(
|
||||
[
|
||||
{
|
||||
kinds: [1, 6],
|
||||
authors: followsAsArray,
|
||||
since: dateToUnix(now.current),
|
||||
},
|
||||
{
|
||||
kinds: [3],
|
||||
authors: [key.pubkey],
|
||||
},
|
||||
{
|
||||
kinds: [4],
|
||||
"#p": [key.pubkey],
|
||||
since: dateToUnix(now.current),
|
||||
},
|
||||
{
|
||||
kinds: [30023],
|
||||
since: dateToUnix(now.current),
|
||||
},
|
||||
],
|
||||
READONLY_RELAYS,
|
||||
(event: any) => {
|
||||
switch (event.kind) {
|
||||
// short text note
|
||||
case 1: {
|
||||
const parentID = getParentID(event.tags, event.id);
|
||||
createNote(
|
||||
event.id,
|
||||
account.id,
|
||||
event.pubkey,
|
||||
event.kind,
|
||||
event.tags,
|
||||
event.content,
|
||||
event.created_at,
|
||||
parentID,
|
||||
);
|
||||
// notify user reload to get newer note
|
||||
setHasNewerNote(true);
|
||||
break;
|
||||
}
|
||||
// contacts
|
||||
case 3: {
|
||||
const follows = nip02ToArray(event.tags);
|
||||
// update account's folllows with NIP-02 tag list
|
||||
updateAccount("follows", follows, event.pubkey);
|
||||
break;
|
||||
}
|
||||
// chat
|
||||
case 4:
|
||||
createChat(
|
||||
event.id,
|
||||
key.pubkey,
|
||||
event.pubkey,
|
||||
event.content,
|
||||
event.created_at,
|
||||
);
|
||||
break;
|
||||
// repost
|
||||
case 6:
|
||||
createNote(
|
||||
event.id,
|
||||
key.id,
|
||||
event.pubkey,
|
||||
event.kind,
|
||||
event.tags,
|
||||
event.content,
|
||||
event.created_at,
|
||||
event.id,
|
||||
);
|
||||
break;
|
||||
// long post
|
||||
case 30023: {
|
||||
const verifyMetadata = isJSON(event.tags);
|
||||
if (verifyMetadata) {
|
||||
// insert event to local database
|
||||
createNote(
|
||||
event.id,
|
||||
account.id,
|
||||
event.pubkey,
|
||||
event.kind,
|
||||
event.tags,
|
||||
event.content,
|
||||
event.created_at,
|
||||
"",
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
useSWRSubscription(account ? "eventCollector" : null, () => {
|
||||
const follows = JSON.parse(account.follows);
|
||||
const unsubscribe = pool.subscribe(
|
||||
[
|
||||
{
|
||||
kinds: [1, 6],
|
||||
authors: follows,
|
||||
since: dateToUnix(now.current),
|
||||
},
|
||||
);
|
||||
{
|
||||
kinds: [3],
|
||||
authors: [account.pubkey],
|
||||
},
|
||||
{
|
||||
kinds: [4],
|
||||
"#p": [account.pubkey],
|
||||
since: dateToUnix(now.current),
|
||||
},
|
||||
],
|
||||
READONLY_RELAYS,
|
||||
(event: any) => {
|
||||
switch (event.kind) {
|
||||
// short text note
|
||||
case 1: {
|
||||
const parentID = getParentID(event.tags, event.id);
|
||||
createNote(
|
||||
event.id,
|
||||
account.id,
|
||||
event.pubkey,
|
||||
event.kind,
|
||||
event.tags,
|
||||
event.content,
|
||||
event.created_at,
|
||||
parentID,
|
||||
);
|
||||
// notify user reload to get newer note
|
||||
setHasNewerNote(true);
|
||||
break;
|
||||
}
|
||||
// contacts
|
||||
case 3: {
|
||||
const follows = nip02ToArray(event.tags);
|
||||
// update account's folllows with NIP-02 tag list
|
||||
updateAccount("follows", follows, event.pubkey);
|
||||
break;
|
||||
}
|
||||
// chat
|
||||
case 4:
|
||||
createChat(
|
||||
event.id,
|
||||
account.pubkey,
|
||||
event.pubkey,
|
||||
event.content,
|
||||
event.created_at,
|
||||
);
|
||||
break;
|
||||
// repost
|
||||
case 6:
|
||||
createNote(
|
||||
event.id,
|
||||
account.id,
|
||||
event.pubkey,
|
||||
event.kind,
|
||||
event.tags,
|
||||
event.content,
|
||||
event.created_at,
|
||||
event.id,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
},
|
||||
);
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
// listen window close event
|
||||
getCurrent().listen(TauriEvent.WINDOW_CLOSE_REQUESTED, () => {
|
||||
// update last login time
|
||||
updateLastLogin(dateToUnix(now.current));
|
||||
// close window
|
||||
appWindow.close();
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="inline-flex h-6 w-6 items-center justify-center rounded text-zinc-500 hover:bg-zinc-900 hover:text-green-500">
|
||||
|
13
src/stores/accounts.tsx
Normal file
13
src/stores/accounts.tsx
Normal file
@ -0,0 +1,13 @@
|
||||
import { getActiveAccount } from "@utils/storage";
|
||||
import { create } from "zustand";
|
||||
|
||||
export const useActiveAccount = create((set) => ({
|
||||
account: null,
|
||||
fetch: async () => {
|
||||
const response = await getActiveAccount();
|
||||
set({ account: response });
|
||||
},
|
||||
updateFollows: (list: any) => {
|
||||
set((state: any) => ({ account: { ...state.account, follows: list } }));
|
||||
},
|
||||
}));
|
17
src/stores/channels.tsx
Normal file
17
src/stores/channels.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import { getChannels } from "@utils/storage";
|
||||
import { create } from "zustand";
|
||||
|
||||
export const useChannels = create((set) => ({
|
||||
channels: [],
|
||||
fetch: async () => {
|
||||
const response = await getChannels(10, 0);
|
||||
set({ channels: response });
|
||||
},
|
||||
}));
|
||||
|
||||
export const useChannelMessage = create((set) => ({
|
||||
messages: [],
|
||||
add: (message: any) => {
|
||||
set((state: any) => ({ messages: [...state.messages, message] }));
|
||||
},
|
||||
}));
|
21
src/stores/chats.tsx
Normal file
21
src/stores/chats.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import { getChatMessages, getChatsByPubkey } from "@utils/storage";
|
||||
import { create } from "zustand";
|
||||
|
||||
export const useChats = create((set) => ({
|
||||
chats: [],
|
||||
fetch: async (pubkey: string) => {
|
||||
const response = await getChatsByPubkey(pubkey);
|
||||
set({ chats: response });
|
||||
},
|
||||
}));
|
||||
|
||||
export const useChatMessages = create((set) => ({
|
||||
messages: [],
|
||||
fetch: async (receiver_pubkey: string, sender_pubkey: string) => {
|
||||
const response = await getChatMessages(receiver_pubkey, sender_pubkey);
|
||||
set({ messages: response });
|
||||
},
|
||||
add: (message: any) => {
|
||||
set((state: any) => ({ messages: [...state.messages, message] }));
|
||||
},
|
||||
}));
|
@ -1,3 +0,0 @@
|
||||
import { atom } from "jotai";
|
||||
|
||||
export const composerAtom = atom({ type: "post" });
|
@ -1,8 +0,0 @@
|
||||
import { atom } from "jotai";
|
||||
import { atomWithReset } from "jotai/utils";
|
||||
|
||||
// note content
|
||||
export const noteContentAtom = atomWithReset("");
|
||||
|
||||
// notify user that connector has receive newer note
|
||||
export const hasNewerNoteAtom = atom(false);
|
@ -1,8 +0,0 @@
|
||||
import { atom } from "jotai";
|
||||
|
||||
export const onboardingAtom = atom({
|
||||
pubkey: null,
|
||||
privkey: null,
|
||||
metadata: null,
|
||||
follows: null,
|
||||
});
|
@ -1,14 +0,0 @@
|
||||
import { getActiveAccount } from "@utils/storage";
|
||||
import useSWR from "swr";
|
||||
|
||||
const fetcher = () => getActiveAccount();
|
||||
|
||||
export function useActiveAccount() {
|
||||
const { data, error, isLoading } = useSWR("activeAcount", fetcher);
|
||||
|
||||
return {
|
||||
account: data,
|
||||
isLoading,
|
||||
isError: error,
|
||||
};
|
||||
}
|
@ -272,7 +272,7 @@ export async function updateChannelMetadata(event_id: string, value: string) {
|
||||
);
|
||||
}
|
||||
|
||||
// get all chats
|
||||
// get all chats by pubkey
|
||||
export async function getChatsByPubkey(pubkey: string) {
|
||||
const db = await connect();
|
||||
return await db.select(
|
||||
@ -280,6 +280,17 @@ export async function getChatsByPubkey(pubkey: string) {
|
||||
);
|
||||
}
|
||||
|
||||
// get chat messages
|
||||
export async function getChatMessages(
|
||||
receiver_pubkey: string,
|
||||
sender_pubkey: string,
|
||||
) {
|
||||
const db = await connect();
|
||||
return await db.select(
|
||||
`SELECT * FROM chats WHERE receiver_pubkey = "${receiver_pubkey}" AND sender_pubkey = "${sender_pubkey}" ORDER BY created_at ASC;`,
|
||||
);
|
||||
}
|
||||
|
||||
// create chat
|
||||
export async function createChat(
|
||||
event_id: string,
|
||||
|
Loading…
Reference in New Issue
Block a user