mirror of
https://github.com/luminous-devs/lume.git
synced 2024-09-18 11:13:30 +00:00
improve startup time
This commit is contained in:
parent
b339e842ca
commit
2b50fc438f
@ -16,7 +16,7 @@ export function ImportStep3Screen() {
|
|||||||
const setStep = useOnboarding((state) => state.setStep);
|
const setStep = useOnboarding((state) => state.setStep);
|
||||||
|
|
||||||
const { db } = useStorage();
|
const { db } = useStorage();
|
||||||
const { fetchUserData, prefetchEvents } = useNostr();
|
const { fetchUserData } = useNostr();
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
@ -27,16 +27,14 @@ export function ImportStep3Screen() {
|
|||||||
|
|
||||||
// prefetch data
|
// prefetch data
|
||||||
const user = await fetchUserData();
|
const user = await fetchUserData();
|
||||||
const data = await prefetchEvents();
|
|
||||||
|
|
||||||
// create default widget
|
// create default widget
|
||||||
await db.createWidget(WidgetKinds.other.learnNostr, 'Learn Nostr', '');
|
await db.createWidget(WidgetKinds.other.learnNostr, 'Learn Nostr', '');
|
||||||
|
|
||||||
// redirect to next step
|
// redirect to next step
|
||||||
if (user.status === 'ok' && data.status === 'ok') {
|
if (user.status === 'ok') {
|
||||||
navigate('/auth/onboarding/step-2', { replace: true });
|
navigate('/auth/onboarding/step-2', { replace: true });
|
||||||
} else {
|
} else {
|
||||||
console.log('error: ', data.message);
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -82,8 +80,8 @@ export function ImportStep3Screen() {
|
|||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
<span className="text-center text-sm text-white/50">
|
<span className="text-center text-sm text-white/50">
|
||||||
By clicking 'Continue', Lume will download your old relay list and
|
By clicking 'Continue', Lume will sync your old relay list and
|
||||||
all events from the last 24 hours. It may take a bit
|
metadata. It may take a bit
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -16,7 +16,7 @@ export function OnboardStep1Screen() {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const setStep = useOnboarding((state) => state.setStep);
|
const setStep = useOnboarding((state) => state.setStep);
|
||||||
|
|
||||||
const { publish, fetchUserData, prefetchEvents } = useNostr();
|
const { publish, fetchUserData } = useNostr();
|
||||||
const { db } = useStorage();
|
const { db } = useStorage();
|
||||||
const { status, data } = useQuery(['trending-profiles-widget'], async () => {
|
const { status, data } = useQuery(['trending-profiles-widget'], async () => {
|
||||||
const res = await fetch('https://api.nostr.band/v0/trending/profiles');
|
const res = await fetch('https://api.nostr.band/v0/trending/profiles');
|
||||||
@ -46,14 +46,12 @@ export function OnboardStep1Screen() {
|
|||||||
|
|
||||||
// prefetch data
|
// prefetch data
|
||||||
const user = await fetchUserData(follows);
|
const user = await fetchUserData(follows);
|
||||||
const data = await prefetchEvents();
|
|
||||||
|
|
||||||
// redirect to next step
|
// redirect to next step
|
||||||
if (event && user.status === 'ok' && data.status === 'ok') {
|
if (event && user.status === 'ok') {
|
||||||
navigate('/auth/onboarding/step-2', { replace: true });
|
navigate('/auth/onboarding/step-2', { replace: true });
|
||||||
} else {
|
} else {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
console.log('error: ', data.message);
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@ -70,7 +68,7 @@ export function OnboardStep1Screen() {
|
|||||||
<div className="flex h-full w-full flex-col justify-center">
|
<div className="flex h-full w-full flex-col justify-center">
|
||||||
<div className="mx-auto mb-4 w-full max-w-md border-b border-white/10 pb-4">
|
<div className="mx-auto mb-4 w-full max-w-md border-b border-white/10 pb-4">
|
||||||
<h1 className="mb-2 text-center text-2xl font-semibold text-white">
|
<h1 className="mb-2 text-center text-2xl font-semibold text-white">
|
||||||
{loading ? 'Prefetching data...' : 'Enrich your network'}
|
{loading ? 'Loading...' : 'Enrich your network'}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-white/70">
|
<p className="text-white/70">
|
||||||
Choose the account you want to follow. These accounts are trending in the last
|
Choose the account you want to follow. These accounts are trending in the last
|
||||||
@ -127,19 +125,12 @@ export function OnboardStep1Screen() {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
{!loading ? (
|
<Link
|
||||||
<Link
|
to="/auth/onboarding/step-2"
|
||||||
to="/auth/onboarding/step-2"
|
className="inline-flex h-12 w-full items-center justify-center rounded-lg border-t border-white/10 bg-white/20 px-6 font-medium leading-none text-white backdrop-blur-xl hover:bg-white/30 focus:outline-none"
|
||||||
className="inline-flex h-12 w-full items-center justify-center rounded-lg border-t border-white/10 bg-white/20 px-6 font-medium leading-none text-white backdrop-blur-xl hover:bg-white/30 focus:outline-none"
|
>
|
||||||
>
|
Skip, you can add later
|
||||||
Skip, you can add later
|
</Link>
|
||||||
</Link>
|
|
||||||
) : (
|
|
||||||
<span className="text-center text-sm text-white/50">
|
|
||||||
By clicking 'Continue', Lume will download all events related to
|
|
||||||
your follows from the last 24 hours. It may take a bit
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -65,9 +65,9 @@ export function SpaceScreen() {
|
|||||||
case WidgetKinds.nostrBand.trendingNotes:
|
case WidgetKinds.nostrBand.trendingNotes:
|
||||||
return <TrendingNotesWidget key={widget.id} params={widget} />;
|
return <TrendingNotesWidget key={widget.id} params={widget} />;
|
||||||
case WidgetKinds.tmp.xfeed:
|
case WidgetKinds.tmp.xfeed:
|
||||||
return <XhashtagWidget key={widget.id} params={widget} />;
|
|
||||||
case WidgetKinds.tmp.xhashtag:
|
|
||||||
return <XfeedsWidget key={widget.id} params={widget} />;
|
return <XfeedsWidget key={widget.id} params={widget} />;
|
||||||
|
case WidgetKinds.tmp.xhashtag:
|
||||||
|
return <XhashtagWidget key={widget.id} params={widget} />;
|
||||||
case WidgetKinds.tmp.list:
|
case WidgetKinds.tmp.list:
|
||||||
return <WidgetList key={widget.id} params={widget} />;
|
return <WidgetList key={widget.id} params={widget} />;
|
||||||
case WidgetKinds.other.learnNostr:
|
case WidgetKinds.other.learnNostr:
|
||||||
|
@ -12,7 +12,7 @@ import { useNostr } from '@utils/hooks/useNostr';
|
|||||||
export function SplashScreen() {
|
export function SplashScreen() {
|
||||||
const { db } = useStorage();
|
const { db } = useStorage();
|
||||||
const { ndk } = useNDK();
|
const { ndk } = useNDK();
|
||||||
const { fetchUserData, prefetchEvents } = useNostr();
|
const { fetchUserData } = useNostr();
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||||
|
|
||||||
@ -20,27 +20,8 @@ export function SplashScreen() {
|
|||||||
await invoke('close_splashscreen');
|
await invoke('close_splashscreen');
|
||||||
};
|
};
|
||||||
|
|
||||||
const prefetch = async () => {
|
|
||||||
try {
|
|
||||||
const [user, events] = await Promise.all([fetchUserData(), prefetchEvents()]);
|
|
||||||
|
|
||||||
if (user.status === 'ok' && events.status === 'ok') {
|
|
||||||
// update last login = current time
|
|
||||||
await db.updateLastLogin();
|
|
||||||
// close splash screen and open main app screen
|
|
||||||
await invoke('close_splashscreen');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
setIsLoading(false);
|
|
||||||
await message(e, {
|
|
||||||
title: 'An unexpected error has occurred',
|
|
||||||
type: 'error',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function initial() {
|
async function syncUserData() {
|
||||||
if (!db.account) {
|
if (!db.account) {
|
||||||
await invoke('close_splashscreen');
|
await invoke('close_splashscreen');
|
||||||
} else {
|
} else {
|
||||||
@ -50,31 +31,47 @@ export function SplashScreen() {
|
|||||||
if (step) {
|
if (step) {
|
||||||
await invoke('close_splashscreen');
|
await invoke('close_splashscreen');
|
||||||
} else {
|
} else {
|
||||||
console.log('prefetching...');
|
try {
|
||||||
prefetch();
|
const userData = await fetchUserData();
|
||||||
|
if (userData.status === 'ok') {
|
||||||
|
// update last login = current time
|
||||||
|
await db.updateLastLogin();
|
||||||
|
// close splash screen and open main app screen
|
||||||
|
await invoke('close_splashscreen');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
setIsLoading(false);
|
||||||
|
await message(e, {
|
||||||
|
title: 'An unexpected error has occurred',
|
||||||
|
type: 'error',
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ndk) {
|
if (ndk) {
|
||||||
initial();
|
syncUserData();
|
||||||
}
|
}
|
||||||
}, [ndk, db.account]);
|
}, [ndk, db.account]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative flex h-screen w-screen items-center justify-center bg-black">
|
<div className="relative flex h-screen w-screen items-center justify-center bg-black">
|
||||||
<div data-tauri-drag-region className="absolute left-0 top-0 z-10 h-11 w-full" />
|
<div data-tauri-drag-region className="absolute left-0 top-0 z-10 h-11 w-full" />
|
||||||
<div className="flex min-h-0 w-full flex-1 items-center justify-center">
|
<div className="flex min-h-0 w-full flex-1 items-center justify-center px-8">
|
||||||
<div className="flex flex-col items-center justify-center gap-4">
|
<div className="flex flex-col items-center justify-center gap-4">
|
||||||
<LoaderIcon className="h-6 w-6 animate-spin text-white" />
|
<LoaderIcon className="h-6 w-6 animate-spin text-white" />
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="flex flex-col gap-1 text-center">
|
<div className="flex flex-col gap-2 text-center">
|
||||||
<h3 className="text-lg font-semibold leading-none text-white">
|
<h3 className="text-lg font-semibold leading-none text-white">
|
||||||
{!ndk ? 'Connecting to relay...' : 'Fetching events from the last login.'}
|
{!ndk ? 'Connecting to relay...' : 'Syncing user data...'}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-sm text-white/50">
|
{ndk ? (
|
||||||
This may take a few seconds, please don't close app.
|
<p className="text-sm text-white/50">
|
||||||
</p>
|
Ensure all your data is sync across all Nostr clients, it may take a few
|
||||||
|
seconds, please don't close app.
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="mt-2 flex flex-col gap-1 text-center">
|
<div className="mt-2 flex flex-col gap-1 text-center">
|
||||||
|
@ -21,49 +21,42 @@ export const NDKInstance = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// TODO: fully support NIP-11
|
// TODO: fully support NIP-11
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
async function getExplicitRelays() {
|
async function getExplicitRelays() {
|
||||||
try {
|
try {
|
||||||
// get relays
|
// get relays
|
||||||
const relays = await db.getExplicitRelayUrls();
|
const relays = await db.getExplicitRelayUrls();
|
||||||
const requests = relays.map((relay) => {
|
const onlineRelays = new Set(relays);
|
||||||
|
|
||||||
|
for (const relay of relays) {
|
||||||
const url = new URL(relay);
|
const url = new URL(relay);
|
||||||
return fetch(`https://${url.hostname + url.pathname}`, {
|
try {
|
||||||
method: 'GET',
|
const res = await fetch(`https://${url.hostname}`, {
|
||||||
timeout: 10,
|
method: 'GET',
|
||||||
headers: {
|
timeout: { secs: 5, nanos: 0 },
|
||||||
Accept: 'application/nostr+json',
|
headers: {
|
||||||
},
|
Accept: 'application/nostr+json',
|
||||||
});
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const responses = await Promise.all(requests);
|
if (!res.ok) {
|
||||||
const successes = responses.filter((res) => res.ok);
|
console.info(`${relay} is not working, skipping...`);
|
||||||
|
onlineRelays.delete(relay);
|
||||||
const verifiedRelays: string[] = successes.map((res) => {
|
}
|
||||||
const url = new URL(res.url);
|
} catch {
|
||||||
|
console.warn(`${relay} is not working, skipping...`);
|
||||||
// @ts-expect-error, not have type yet
|
onlineRelays.delete(relay);
|
||||||
if (res.data?.limitation?.payment_required) {
|
|
||||||
if (url.protocol === 'http:')
|
|
||||||
return `ws://${url.hostname + url.pathname + db.account.npub}`;
|
|
||||||
if (url.protocol === 'https:')
|
|
||||||
return `wss://${url.hostname + url.pathname + db.account.npub}`;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (url.protocol === 'http:') return `ws://${url.hostname + url.pathname}`;
|
// return all online relays
|
||||||
if (url.protocol === 'https:') return `wss://${url.hostname + url.pathname}`;
|
return [...onlineRelays];
|
||||||
});
|
|
||||||
|
|
||||||
// return all validated relays
|
|
||||||
return verifiedRelays;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function initNDK() {
|
async function initNDK() {
|
||||||
const explicitRelayUrls = await db.getExplicitRelayUrls();
|
const explicitRelayUrls = await getExplicitRelays();
|
||||||
const instance = new NDK({
|
const instance = new NDK({
|
||||||
explicitRelayUrls,
|
explicitRelayUrls,
|
||||||
cacheAdapter,
|
cacheAdapter,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { NDKEvent, NDKUserProfile } from '@nostr-dev-kit/ndk';
|
import { NDKEvent, NDKUserProfile } from '@nostr-dev-kit/ndk';
|
||||||
import { BaseDirectory, removeFile } from '@tauri-apps/api/fs';
|
import { BaseDirectory, removeFile } from '@tauri-apps/api/fs';
|
||||||
|
import { Platform } from '@tauri-apps/api/os';
|
||||||
import Database from 'tauri-plugin-sql-api';
|
import Database from 'tauri-plugin-sql-api';
|
||||||
import { Stronghold } from 'tauri-plugin-stronghold-api';
|
import { Stronghold } from 'tauri-plugin-stronghold-api';
|
||||||
|
|
||||||
@ -11,12 +12,14 @@ import { Account, DBEvent, Relays, Widget } from '@utils/types';
|
|||||||
export class LumeStorage {
|
export class LumeStorage {
|
||||||
public db: Database;
|
public db: Database;
|
||||||
public secureDB: Stronghold;
|
public secureDB: Stronghold;
|
||||||
public account: Account | null = null;
|
public account: Account | null;
|
||||||
|
public platform: Platform | null;
|
||||||
|
|
||||||
constructor(sqlite: Database, stronghold?: Stronghold) {
|
constructor(sqlite: Database, platform?: Platform, stronghold?: Stronghold) {
|
||||||
this.db = sqlite;
|
this.db = sqlite;
|
||||||
this.secureDB = stronghold ?? undefined;
|
this.secureDB = stronghold ?? undefined;
|
||||||
this.account = null;
|
this.account = null;
|
||||||
|
this.platform = platform ?? undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getSecureClient(key?: string) {
|
private async getSecureClient(key?: string) {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { message } from '@tauri-apps/api/dialog';
|
import { message } from '@tauri-apps/api/dialog';
|
||||||
|
import { platform } from '@tauri-apps/api/os';
|
||||||
import { PropsWithChildren, createContext, useContext, useEffect, useState } from 'react';
|
import { PropsWithChildren, createContext, useContext, useEffect, useState } from 'react';
|
||||||
import Database from 'tauri-plugin-sql-api';
|
import Database from 'tauri-plugin-sql-api';
|
||||||
|
|
||||||
@ -18,7 +19,8 @@ const StorageProvider = ({ children }: PropsWithChildren<object>) => {
|
|||||||
const initLumeStorage = async () => {
|
const initLumeStorage = async () => {
|
||||||
try {
|
try {
|
||||||
const sqlite = await Database.load('sqlite:lume.db');
|
const sqlite = await Database.load('sqlite:lume.db');
|
||||||
const lumeStorage = new LumeStorage(sqlite);
|
const platformName = await platform();
|
||||||
|
const lumeStorage = new LumeStorage(sqlite, platformName);
|
||||||
|
|
||||||
if (!lumeStorage.account) await lumeStorage.getActiveAccount();
|
if (!lumeStorage.account) await lumeStorage.getActiveAccount();
|
||||||
setDB(lumeStorage);
|
setDB(lumeStorage);
|
||||||
|
61
src/shared/widgets/emptyList.tsx
Normal file
61
src/shared/widgets/emptyList.tsx
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { useStorage } from '@libs/storage/provider';
|
||||||
|
|
||||||
|
import { useNostr } from '@utils/hooks/useNostr';
|
||||||
|
|
||||||
|
export function EmptyList() {
|
||||||
|
const { db } = useStorage();
|
||||||
|
const { getAllEventsSinceLastLogin } = useNostr();
|
||||||
|
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const [progress, setProgress] = useState(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function getEvents() {
|
||||||
|
const events = await getAllEventsSinceLastLogin();
|
||||||
|
const promises = await Promise.all(
|
||||||
|
events.data.map(async (event) => await db.createEvent(event))
|
||||||
|
);
|
||||||
|
|
||||||
|
if (promises) {
|
||||||
|
setProgress(100);
|
||||||
|
// invalidate queries
|
||||||
|
queryClient.invalidateQueries(['local-network-widget']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// only start download if progress === 0
|
||||||
|
if (progress === 0) getEvents();
|
||||||
|
|
||||||
|
// auto increase progress after 2 secs
|
||||||
|
setInterval(() => setProgress((prev) => (prev += 8)), 2000);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="px-3">
|
||||||
|
<div className="h-max w-full rounded-lg border-t border-white/10 bg-white/20 p-3">
|
||||||
|
<div className="flex flex-col items-center gap-5">
|
||||||
|
<div>
|
||||||
|
<span className="text-4xl">👋</span>
|
||||||
|
<h3 className="mt-2 font-semibold leading-tight">
|
||||||
|
Hello, this is the first time you're using Lume
|
||||||
|
</h3>
|
||||||
|
<p className="text-sm text-white/70">
|
||||||
|
Lume is downloading all events since the last 24 hours. It will auto refresh
|
||||||
|
when it done, please be patient
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex h-1.5 w-full overflow-hidden rounded-full bg-white/20">
|
||||||
|
<div
|
||||||
|
className="flex flex-col justify-center overflow-hidden bg-fuchsia-500 transition-all duration-1000 ease-smooth"
|
||||||
|
role="progressbar"
|
||||||
|
style={{ width: `${progress}%` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -14,3 +14,5 @@ export * from './nostrBand/trendingAccounts';
|
|||||||
export * from './tmp/feeds';
|
export * from './tmp/feeds';
|
||||||
export * from './tmp/hashtag';
|
export * from './tmp/hashtag';
|
||||||
export * from './other/learnNostr';
|
export * from './other/learnNostr';
|
||||||
|
export * from './emptyList';
|
||||||
|
export * from './loadLatestEvents';
|
||||||
|
61
src/shared/widgets/loadLatestEvents.tsx
Normal file
61
src/shared/widgets/loadLatestEvents.tsx
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { useStorage } from '@libs/storage/provider';
|
||||||
|
|
||||||
|
import { useStronghold } from '@stores/stronghold';
|
||||||
|
|
||||||
|
import { useNostr } from '@utils/hooks/useNostr';
|
||||||
|
|
||||||
|
export function LoadLatestEvents() {
|
||||||
|
const { db } = useStorage();
|
||||||
|
const { getAllEventsSinceLastLogin } = useNostr();
|
||||||
|
|
||||||
|
const setIsFetched = useStronghold((state) => state.setIsFetched);
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
const [progress, setProgress] = useState(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function getEvents() {
|
||||||
|
const events = await getAllEventsSinceLastLogin();
|
||||||
|
const promises = await Promise.all(
|
||||||
|
events.data.map(async (event) => await db.createEvent(event))
|
||||||
|
);
|
||||||
|
|
||||||
|
if (promises) {
|
||||||
|
setProgress(100);
|
||||||
|
setIsFetched();
|
||||||
|
// invalidate queries
|
||||||
|
queryClient.invalidateQueries(['local-network-widget']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// only start download if progress === 0
|
||||||
|
if (progress === 0) getEvents();
|
||||||
|
|
||||||
|
// auto increase progress after 2 secs
|
||||||
|
setInterval(() => setProgress((prev) => (prev += 8)), 2000);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mb-3 px-3">
|
||||||
|
<div className="h-max w-full rounded-lg border-t border-white/10 bg-white/20 p-3">
|
||||||
|
<div className="flex flex-col items-center gap-3">
|
||||||
|
<div className="text-center">
|
||||||
|
<h3 className="font-semibold leading-tight">
|
||||||
|
Downloading all events from your last login...
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div className="flex h-1.5 w-full overflow-hidden rounded-full bg-white/20">
|
||||||
|
<div
|
||||||
|
className="flex flex-col justify-center overflow-hidden bg-fuchsia-500 transition-all duration-1000 ease-smooth"
|
||||||
|
role="progressbar"
|
||||||
|
style={{ width: `${progress}%` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -16,7 +16,9 @@ import {
|
|||||||
} from '@shared/notes';
|
} from '@shared/notes';
|
||||||
import { NoteSkeleton } from '@shared/notes/skeleton';
|
import { NoteSkeleton } from '@shared/notes/skeleton';
|
||||||
import { TitleBar } from '@shared/titleBar';
|
import { TitleBar } from '@shared/titleBar';
|
||||||
import { WidgetWrapper } from '@shared/widgets';
|
import { EmptyList, LoadLatestEvents, WidgetWrapper } from '@shared/widgets';
|
||||||
|
|
||||||
|
import { useStronghold } from '@stores/stronghold';
|
||||||
|
|
||||||
import { useNostr } from '@utils/hooks/useNostr';
|
import { useNostr } from '@utils/hooks/useNostr';
|
||||||
import { toRawEvent } from '@utils/rawEvent';
|
import { toRawEvent } from '@utils/rawEvent';
|
||||||
@ -34,6 +36,7 @@ export function LocalNetworkWidget() {
|
|||||||
getNextPageParam: (lastPage) => lastPage.nextCursor,
|
getNextPageParam: (lastPage) => lastPage.nextCursor,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const isFetched = useStronghold((state) => state.isFetched);
|
||||||
const dbEvents = useMemo(
|
const dbEvents = useMemo(
|
||||||
() => (data ? data.pages.flatMap((d: { data: DBEvent[] }) => d.data) : []),
|
() => (data ? data.pages.flatMap((d: { data: DBEvent[] }) => d.data) : []),
|
||||||
[data]
|
[data]
|
||||||
@ -83,7 +86,7 @@ export function LocalNetworkWidget() {
|
|||||||
// subscribe for new event
|
// subscribe for new event
|
||||||
// sub will be managed by lru-cache
|
// sub will be managed by lru-cache
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (db.account && db.account.network) {
|
if (db.account && db.account.network && dbEvents.length > 0) {
|
||||||
const filter: NDKFilter = {
|
const filter: NDKFilter = {
|
||||||
kinds: [NDKKind.Text, NDKKind.Repost],
|
kinds: [NDKKind.Text, NDKKind.Repost],
|
||||||
authors: db.account.network,
|
authors: db.account.network,
|
||||||
@ -95,11 +98,11 @@ export function LocalNetworkWidget() {
|
|||||||
await db.createEvent(rawEvent);
|
await db.createEvent(rawEvent);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, []);
|
}, [data]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WidgetWrapper>
|
<WidgetWrapper>
|
||||||
<TitleBar title="👋 Network" />
|
<TitleBar title="Network" />
|
||||||
<div className="h-full">
|
<div className="h-full">
|
||||||
{status === 'loading' ? (
|
{status === 'loading' ? (
|
||||||
<div className="px-3 py-1.5">
|
<div className="px-3 py-1.5">
|
||||||
@ -108,21 +111,10 @@ export function LocalNetworkWidget() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : dbEvents.length === 0 ? (
|
) : dbEvents.length === 0 ? (
|
||||||
<div className="flex h-full w-full flex-col items-center justify-center px-3">
|
<EmptyList />
|
||||||
<div className="flex flex-col items-center gap-4">
|
|
||||||
<img src="/ghost.png" alt="empty feeds" className="h-16 w-16" />
|
|
||||||
<div className="text-center">
|
|
||||||
<h3 className="text-xl font-semibold leading-tight">
|
|
||||||
Your newsfeed is empty
|
|
||||||
</h3>
|
|
||||||
<p className="text-center text-white/50">
|
|
||||||
Connect more people to explore more content
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
) : (
|
||||||
<VList className="scrollbar-hide h-full">
|
<VList className="scrollbar-hide h-full">
|
||||||
|
{!isFetched ? <LoadLatestEvents /> : null}
|
||||||
{dbEvents.map((item) => renderItem(item))}
|
{dbEvents.map((item) => renderItem(item))}
|
||||||
<div className="flex items-center justify-center px-3 py-1.5">
|
<div className="flex items-center justify-center px-3 py-1.5">
|
||||||
{dbEvents.length > 0 ? (
|
{dbEvents.length > 0 ? (
|
||||||
|
@ -4,9 +4,11 @@ import { createJSONStorage, persist } from 'zustand/middleware';
|
|||||||
interface StrongholdState {
|
interface StrongholdState {
|
||||||
privkey: null | string;
|
privkey: null | string;
|
||||||
walletConnectURL: null | string;
|
walletConnectURL: null | string;
|
||||||
|
isFetched: null | boolean;
|
||||||
setPrivkey: (privkey: string) => void;
|
setPrivkey: (privkey: string) => void;
|
||||||
setWalletConnectURL: (uri: string) => void;
|
setWalletConnectURL: (uri: string) => void;
|
||||||
clearPrivkey: () => void;
|
clearPrivkey: () => void;
|
||||||
|
setIsFetched: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useStronghold = create<StrongholdState>()(
|
export const useStronghold = create<StrongholdState>()(
|
||||||
@ -14,6 +16,7 @@ export const useStronghold = create<StrongholdState>()(
|
|||||||
(set) => ({
|
(set) => ({
|
||||||
privkey: null,
|
privkey: null,
|
||||||
walletConnectURL: null,
|
walletConnectURL: null,
|
||||||
|
isFetched: false,
|
||||||
setPrivkey: (privkey: string) => {
|
setPrivkey: (privkey: string) => {
|
||||||
set({ privkey: privkey });
|
set({ privkey: privkey });
|
||||||
},
|
},
|
||||||
@ -23,6 +26,9 @@ export const useStronghold = create<StrongholdState>()(
|
|||||||
clearPrivkey: () => {
|
clearPrivkey: () => {
|
||||||
set({ privkey: null });
|
set({ privkey: null });
|
||||||
},
|
},
|
||||||
|
setIsFetched: () => {
|
||||||
|
set({ isFetched: true });
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: 'stronghold',
|
name: 'stronghold',
|
||||||
|
@ -54,7 +54,7 @@ export function useNostr() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
subManager.set(JSON.stringify(filter), subEvent);
|
subManager.set(JSON.stringify(filter), subEvent);
|
||||||
console.log(subManager.keys());
|
console.log('current active sub: ', subManager.size);
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchUserData = async (preFollows?: string[]) => {
|
const fetchUserData = async (preFollows?: string[]) => {
|
||||||
@ -144,41 +144,6 @@ export function useNostr() {
|
|||||||
publish({ content: '', kind: NDKKind.Contacts, tags: tags });
|
publish({ content: '', kind: NDKKind.Contacts, tags: tags });
|
||||||
};
|
};
|
||||||
|
|
||||||
const prefetchEvents = async () => {
|
|
||||||
try {
|
|
||||||
const dbEventsEmpty = await db.isEventsEmpty();
|
|
||||||
|
|
||||||
let since: number;
|
|
||||||
if (dbEventsEmpty || db.account.last_login_at === 0) {
|
|
||||||
since = db.account.network.length > 400 ? nHoursAgo(12) : nHoursAgo(24);
|
|
||||||
} else {
|
|
||||||
since = db.account.last_login_at;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("prefetching events with user's network: ", db.account.network.length);
|
|
||||||
console.log('prefetching events since: ', since);
|
|
||||||
|
|
||||||
const events = (await fetcher.fetchAllEvents(
|
|
||||||
relayUrls,
|
|
||||||
{
|
|
||||||
kinds: [NDKKind.Text, NDKKind.Repost, 1063, NDKKind.Article],
|
|
||||||
authors: db.account.network,
|
|
||||||
},
|
|
||||||
{ since: since }
|
|
||||||
)) as unknown as NDKEvent[];
|
|
||||||
|
|
||||||
// save all events to database
|
|
||||||
const promises = await Promise.all(
|
|
||||||
events.map(async (event) => await db.createEvent(event))
|
|
||||||
);
|
|
||||||
|
|
||||||
if (promises) return { status: 'ok', message: 'prefetch completed' };
|
|
||||||
} catch (e) {
|
|
||||||
console.error('prefetch events failed, error: ', e);
|
|
||||||
return { status: 'failed', message: e };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchActivities = async () => {
|
const fetchActivities = async () => {
|
||||||
try {
|
try {
|
||||||
const events = await fetcher.fetchAllEvents(
|
const events = await fetcher.fetchAllEvents(
|
||||||
@ -293,6 +258,37 @@ export function useNostr() {
|
|||||||
return events;
|
return events;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getAllEventsSinceLastLogin = async (customSince?: number) => {
|
||||||
|
try {
|
||||||
|
let since: number;
|
||||||
|
const dbEventsEmpty = await db.isEventsEmpty();
|
||||||
|
|
||||||
|
if (!customSince) {
|
||||||
|
if (dbEventsEmpty || db.account.last_login_at === 0) {
|
||||||
|
since = db.account.network.length > 400 ? nHoursAgo(12) : nHoursAgo(24);
|
||||||
|
} else {
|
||||||
|
since = db.account.last_login_at;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
since = customSince;
|
||||||
|
}
|
||||||
|
|
||||||
|
const events = (await fetcher.fetchAllEvents(
|
||||||
|
relayUrls,
|
||||||
|
{
|
||||||
|
kinds: [NDKKind.Text, NDKKind.Repost, 1063, NDKKind.Article],
|
||||||
|
authors: db.account.network,
|
||||||
|
},
|
||||||
|
{ since: since }
|
||||||
|
)) as unknown as NDKEvent[];
|
||||||
|
|
||||||
|
return { status: 'ok', message: 'fetch completed', data: events };
|
||||||
|
} catch (e) {
|
||||||
|
console.error('prefetch events failed, error: ', e);
|
||||||
|
return { status: 'failed', message: e };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const getContactsByPubkey = async (pubkey: string) => {
|
const getContactsByPubkey = async (pubkey: string) => {
|
||||||
const user = ndk.getUser({ hexpubkey: pubkey });
|
const user = ndk.getUser({ hexpubkey: pubkey });
|
||||||
const follows = [...(await user.follows())].map((user) => user.hexpubkey);
|
const follows = [...(await user.follows())].map((user) => user.hexpubkey);
|
||||||
@ -444,7 +440,7 @@ export function useNostr() {
|
|||||||
fetchUserData,
|
fetchUserData,
|
||||||
addContact,
|
addContact,
|
||||||
removeContact,
|
removeContact,
|
||||||
prefetchEvents,
|
getAllEventsSinceLastLogin,
|
||||||
fetchActivities,
|
fetchActivities,
|
||||||
fetchNIP04Chats,
|
fetchNIP04Chats,
|
||||||
fetchNIP04Messages,
|
fetchNIP04Messages,
|
||||||
|
Loading…
Reference in New Issue
Block a user