Skip to content

Commit

Permalink
add local detection
Browse files Browse the repository at this point in the history
  • Loading branch information
dskvr committed Jan 26, 2025
1 parent bf58876 commit 1726b48
Show file tree
Hide file tree
Showing 56 changed files with 791 additions and 386 deletions.
2 changes: 2 additions & 0 deletions apps/gui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@
"@nostrwatch/memory-relay": "workspace:^",
"@nostrwatch/nocap": "workspace:^",
"@nostrwatch/nocap-websocket-adapter-default": "workspace:^",
"@nostrwatch/nocap-dns-adapter-default": "workspace:^",
"@nostrwatch/nocap-info-adapter-default": "workspace:^",
"@nostrwatch/route66": "workspace:^",
"@nostrwatch/route66-cacheadapter-nostrsqlite": "workspace:^",
"@nostrwatch/route66-wsadapter-nostrtools": "workspace:^",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,8 @@ export class DataTable<T> {
const aVal = this.#getValue(a, columnId);
const bVal = this.#getValue(b, columnId);

if (aVal === undefined || aVal === null) return direction === 'asc' ? 1 : -1;
if (bVal === undefined || bVal === null) return direction === 'asc' ? -1 : 1;
if (aVal === undefined || aVal === null || aVal < 0 || aVal == '') return direction === 'asc' ? 1 : -1;
if (bVal === undefined || bVal === null || bVal < 0 || aVal == '') return direction === 'asc' ? -1 : 1;

if (colDef && colDef.sorter) {
return direction === 'asc'
Expand Down
102 changes: 102 additions & 0 deletions apps/gui/src/lib/components/partials/Feed.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<script lang="ts">
import { onDestroy, onMount } from 'svelte';
import { get, writable, type Readable, type Writable } from 'svelte/store';
import Masonry from 'svelte-bricks';
import type { Filter } from 'nostr-tools';
import { route66 } from '$lib/stores';
import { User } from '$lib/models/User.js';
import type { UserFeed } from '$lib/services/UserService';
import { FeedService } from '$lib/services/FeedService';
import { deterministicHash } from '@nostrwatch/route66/utils';
import type { NostrEvent } from '@nostrwatch/route66/models';
import FeedNote from './FeedNote.svelte';
import { observeViewport } from '$lib/utils/ux';
import { pauseLiveSync } from '$lib/utils/lifecycle';
export let filters: Filter[];
let user: User | undefined;
let resumer: Function | undefined;
const feedService: Writable<FeedService | null> = writable(null);
const feed: Writable<UserFeed> = writable([]);
const until: Writable<number> = writable();
const busy: Writable<boolean> = writable(false);
let items: Readable<NostrEvent[]> | undefined;
let [minColWidth, maxColWidth, gap] = [300, 500, 21]
let width:number, height: number
const lastItemId = () => {
return $items![$feed.length - 1].id;
}
const middleItemId = () => {
return $items![Math.floor($feed.length / 2)].id;
}
const lowItemId = (id: string): boolean => {
return $items!.slice(-4).map( item => item.id).includes(id);
}
const mount = async () => {
resumer = await pauseLiveSync();
if(!$route66) return console.warn('route66 does not exist.');
await $route66.ready();
feedService.set(new FeedService($route66.adapters, filters));
$feedService!.populate();
items = $feedService!.memoryRelay.$req(deterministicHash(filters), filters)
}
const destroy = () => {
$feedService!.unsubscribeAll();
resumer?.()
}
onMount(mount);
onDestroy(destroy);
$: count = $feedService? $feedService?.memoryRelay.count([{}]): 0;
</script>

<section id="operator-feed" class="block relative">
{#if $items?.length === 0}
<div class="flex flex-col text-center items-center justify-center h-[600px]">
<span class="text-2xl text-center">loading</span>
</div>
{/if}

{$items?.length}

{#if $feedService && $items?.length}
<Masonry
items={$items as NostrEvent[]}
{minColWidth}
{maxColWidth}
{gap}
let:item
bind:width
bind:height >

{#if lowItemId(item.id)}
<div
use:observeViewport
on:viewportchange={(event: any) => {
if(event.detail.isIntersecting) $feedService!.populate();
}}></div>
{/if}

<FeedNote note={item} memoryRelay={$feedService.memoryRelay} />
</Masonry>
{/if}

</section>




Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,28 @@
import type { UserFeedItem } from '$lib/services/UserService';
import { parseNote } from '$lib/utils/notes';
import { timeAgo } from '$lib/utils/time';
import type { IEvent } from '@nostrwatch/route66/models/Event';
import type { IEvent, NostrEvent } from '@nostrwatch/route66/models';
import type { UserFeedItemRelatives } from '$lib/services/UserService';
import { observeViewport } from '$lib/utils/ux';
import { onDestroy, onMount } from 'svelte';
import { writable, type Writable } from 'svelte/store';
import { writable, type Readable, type Writable } from 'svelte/store';
import Bolt11 from 'light-bolt11-decoder';
export let noteExtended: UserFeedItem;
import { pubkeyUserInstance } from '$stores/helpers/helpers-pubkey';
import type { SvelteMemoryRelay } from '@nostrwatch/memory-relay';
import { noteCommentsCount$, noteReactionsCount$, noteZaps$ } from '$stores/helpers/helpers-notes';
import { activeMonitorChecksCount } from '$stores/monitors';
const _comments: Writable<IEvent[]> = writable([]);
const _reactions: Writable<IEvent[]> = writable([]);
const _zaps: Writable<IEvent[]> = writable([]);
const subscriptions: Set<string> = new Set();
export let note: NostrEvent;
export let memoryRelay: SvelteMemoryRelay<IEvent, NostrEvent>;
const relativesFetched: Writable<boolean> = writable(false);
const comments: Readable<number> = noteCommentsCount$<NostrEvent>(note.id, memoryRelay);
const zaps: Readable<NostrEvent[]> = noteZaps$<NostrEvent>(note.id, memoryRelay);
const reactions: Readable<number> = noteReactionsCount$<NostrEvent>(note.id, memoryRelay);
// Initialize content as Writable<string>
let content: Writable<string>;
const fetchRelatives = async (noteId: string) => {
if(subscriptions.has(`relatives-${noteId}`)) return;
subscriptions.add(`relatives-${noteId}`);
noteExtended.fetchRelatives().then( ({comments, reactions, zaps}: UserFeedItemRelatives) => {
_comments.set(comments);
_reactions.set(reactions);
_zaps.set(zaps);
relativesFetched.set(true);
});
}
const mount = () => {
content = parseNote(noteExtended.note.content, {
content = parseNote(note.content, {
removeHashtags: true,
nip19: true,
markdown: true,
Expand All @@ -53,36 +43,47 @@
onMount(mount)
onDestroy(destroy)
$: isComment = noteExtended.note.isComment
$: user = noteExtended.user
$: isComment = note.isComment
$: user = pubkeyUserInstance(note.pubkey)
$: name = user?.name || user?.pubkey
$: animationClass = $relativesFetched? 'animate' : ''
$: bolt11s = $_zaps.map(zap => {
// $: animationClass = $relativesFetched? 'animate' : ''
$: bolt11s = $zaps.map(zap => {
const b11 = zap.tags.find(tag => tag[0] === 'bolt11')?.[1]
if(!b11) return null
return Bolt11.decode(b11)
}).filter( b11 => b11 !== null )
$: zapSum = abbrNum(Math.round(bolt11s.reduce((acc, b11) => acc += parseInt(b11.sections.find( section => section?.name === 'amount')?.value || "0"), 0)/1000));
$: zapSum = isVisible? abbrNum(
Math
.round(
bolt11s
.reduce((acc, b11) => acc += parseInt(
b11.sections.find(
section => section?.name === 'amount')?.value || "0"
)
, 0)
/1000
)
): '';
let isVisible: boolean = true;
$: actionsClass = isVisible? '' : 'opacity-0';
function abbrNum(num: number): string {
if(num === 0) return '';
if (num < 1000) return num.toString();
const units = ["", "K", "M", "B", "T", "P", "E"];
const magnitude = Math.floor(Math.log10(num) / 3);
const precision = magnitude - 1;
const scaled = num / Math.pow(1000, magnitude);
return `${scaled.toFixed(precision + 1)}${units[magnitude]}`;
}
</script>
</script>

<section
tabindex="-1"
id="note-{noteExtended.note.id}"
id="note-{note.id}"
class="note px-8 py-5 rounded-lg bg-black/5 dark:bg-white/5 text-md block mb-3"
use:observeViewport
on:viewportchange={(event: any) => {
if(event.detail.isIntersecting) fetchRelatives(noteExtended.note.id)
}}
>
<div class="text-xs text-gray-400">
<span class="text-xs text-gray-400">
Expand All @@ -94,29 +95,31 @@
{/if}
</span>

{#if noteExtended.note?.created_at}
<span class="">{timeAgo(noteExtended.note.created_at*1000)}</span>
{#if note?.created_at}
<span class="">{timeAgo(note.created_at*1000)}</span>
{/if}
|
<a href="https://njump.me/{noteExtended.note.reference}" target="_blank">link</a>
<a href="https://njump.me/{note.reference}" target="_blank">link</a>
</div>

<div class="content text-black/55 dark:text-white/55 text-xl my-6 overflow-hidden overflow-ellipsis">
{@html $content}
</div>
<div class="actions flex mt-2 hover:opacity-100 {animationClass}">
<div class="actions flex mt-2 hover:opacity-100 {actionsClass} min-h-6">
{#if isVisible}
<div class="flex-grow">
<a href="">♡</a>
{$_reactions.length}
{$reactions? $reactions : ''}
</div>
<div class="flex-grow">
<a href="">⚡</a>
{zapSum}
</div>
<div class="flex-grow">
<a href="">🗨</a>
{$_comments.length}
{$comments? $comments : ''}
</div>
{/if}
</div>

</section>
Expand Down Expand Up @@ -145,12 +148,12 @@
.note > .actions {
animation: none;
opacity: 0;
opacity: 0.6;
}
.note > .actions.animate {
/* .note > .actions.animate {
animation: fadeIn 0.5s ease forwards;
}
} */
.note > .actions > div > a {
@apply py-1 px-2 hover:bg-black/20 hover:rounded-full;
Expand Down
Loading

0 comments on commit 1726b48

Please sign in to comment.