feat: improve official columns

This commit is contained in:
reya 2024-04-18 07:50:46 +07:00
parent f3c52237fa
commit cd31b99559
10 changed files with 59 additions and 77 deletions

View File

@ -1,6 +1,6 @@
import { CheckCircleIcon } from "@lume/icons"; import { CheckCircleIcon } from "@lume/icons";
import { ColumnRouteSearch } from "@lume/types"; import { ColumnRouteSearch } from "@lume/types";
import { Column, User } from "@lume/ui"; import { Column, Spinner, User } from "@lume/ui";
import { createFileRoute, useRouter } from "@tanstack/react-router"; import { createFileRoute, useRouter } from "@tanstack/react-router";
import { useState } from "react"; import { useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
@ -30,6 +30,7 @@ function Screen() {
const [title, setTitle] = useState<string>("Just a new group"); const [title, setTitle] = useState<string>("Just a new group");
const [users, setUsers] = useState<Array<string>>([]); const [users, setUsers] = useState<Array<string>>([]);
const [loading, setLoading] = useState(false);
const [isDone, setIsDone] = useState(false); const [isDone, setIsDone] = useState(false);
const toggleUser = (pubkey: string) => { const toggleUser = (pubkey: string) => {
@ -43,13 +44,22 @@ function Screen() {
try { try {
if (isDone) return router.history.push(redirect); if (isDone) return router.history.push(redirect);
// start loading
setLoading(true);
const groups = await ark.set_nstore( const groups = await ark.set_nstore(
`lume_group_${label}`, `lume_group_${label}`,
JSON.stringify(users), JSON.stringify(users),
); );
if (groups) setIsDone(true); if (groups) {
toast.success("Group has been created successfully.");
// start loading
setIsDone(true);
setLoading(false);
}
} catch (e) { } catch (e) {
setLoading(false);
toast.error(e); toast.error(e);
} }
}; };
@ -101,14 +111,14 @@ function Screen() {
</div> </div>
</div> </div>
</div> </div>
<div className="fixed z-10 flex items-center justify-center w-full bottom-3"> <div className="fixed z-10 flex items-center justify-center w-full bottom-6">
<button <button
type="button" type="button"
onClick={submit} onClick={submit}
disabled={users.length < 1} disabled={users.length < 1}
className="inline-flex items-center justify-center px-4 font-medium text-white transform bg-blue-500 rounded-full active:translate-y-1 w-36 h-11 hover:bg-blue-600 focus:outline-none disabled:cursor-not-allowed" className="inline-flex items-center justify-center px-4 font-medium text-white transform bg-blue-500 rounded-full active:translate-y-1 w-26 h-9 hover:bg-blue-600 focus:outline-none disabled:cursor-not-allowed"
> >
{isDone ? "Back" : "Update"} {isDone ? "Back" : loading ? <Spinner /> : "Update"}
</button> </button>
</div> </div>
</Column.Content> </Column.Content>

View File

@ -18,6 +18,7 @@ export const Route = createFileRoute("/foryou")({
beforeLoad: async ({ search, context }) => { beforeLoad: async ({ search, context }) => {
const ark = context.ark; const ark = context.ark;
const interests = await ark.get_interest(); const interests = await ark.get_interest();
const settings = await ark.get_settings();
if (!interests) { if (!interests) {
throw redirect({ throw redirect({
@ -31,6 +32,7 @@ export const Route = createFileRoute("/foryou")({
return { return {
interests, interests,
settings,
}; };
}, },
component: Screen, component: Screen,
@ -48,7 +50,6 @@ export function Screen() {
interests.hashtags, interests.hashtags,
20, 20,
pageParam, pageParam,
true,
); );
return events; return events;
}, },
@ -87,13 +88,12 @@ export function Screen() {
{data.map((item) => renderItem(item))} {data.map((item) => renderItem(item))}
</Virtualizer> </Virtualizer>
)} )}
{data?.length && hasNextPage ? ( {data?.length && hasNextPage ? (
<div className="flex h-20 items-center justify-center"> <div className="flex h-20 items-center justify-center">
<button <button
type="button" type="button"
onClick={() => fetchNextPage()} onClick={() => fetchNextPage()}
disabled={isFetchingNextPage || isFetchingNextPage} disabled={isFetchingNextPage}
className="inline-flex h-12 w-36 items-center justify-center gap-2 rounded-full bg-neutral-100 px-3 font-medium hover:bg-neutral-200 focus:outline-none dark:bg-neutral-900 dark:hover:bg-neutral-800" className="inline-flex h-12 w-36 items-center justify-center gap-2 rounded-full bg-neutral-100 px-3 font-medium hover:bg-neutral-200 focus:outline-none dark:bg-neutral-900 dark:hover:bg-neutral-800"
> >
{isFetchingNextPage ? ( {isFetchingNextPage ? (

View File

@ -17,7 +17,10 @@ export const Route = createFileRoute("/group")({
}, },
beforeLoad: async ({ search, context }) => { beforeLoad: async ({ search, context }) => {
const ark = context.ark; const ark = context.ark;
const groups = await ark.get_nstore(`lume_group_${search.label}`); const groups = (await ark.get_nstore(
`lume_group_${search.label}`,
)) as string[];
const settings = await ark.get_settings();
if (!groups) { if (!groups) {
throw redirect({ throw redirect({
@ -31,6 +34,7 @@ export const Route = createFileRoute("/group")({
return { return {
groups, groups,
settings,
}; };
}, },
component: Screen, component: Screen,
@ -38,13 +42,13 @@ export const Route = createFileRoute("/group")({
export function Screen() { export function Screen() {
const { label, name, account } = Route.useSearch(); const { label, name, account } = Route.useSearch();
const { ark } = Route.useRouteContext(); const { ark, groups } = Route.useRouteContext();
const { data, isLoading, isFetchingNextPage, hasNextPage, fetchNextPage } = const { data, isLoading, isFetchingNextPage, hasNextPage, fetchNextPage } =
useInfiniteQuery({ useInfiniteQuery({
queryKey: [name, account], queryKey: [name, account],
initialPageParam: 0, initialPageParam: 0,
queryFn: async ({ pageParam }: { pageParam: number }) => { queryFn: async ({ pageParam }: { pageParam: number }) => {
const events = await ark.get_events(20, pageParam); const events = await ark.get_events(20, pageParam, groups);
return events; return events;
}, },
getNextPageParam: (lastPage) => { getNextPageParam: (lastPage) => {

View File

@ -7,7 +7,6 @@ import { useTranslation } from "react-i18next";
import { toast } from "sonner"; import { toast } from "sonner";
export const Route = createFileRoute("/interests")({ export const Route = createFileRoute("/interests")({
component: Screen,
validateSearch: (search: Record<string, string>): ColumnRouteSearch => { validateSearch: (search: Record<string, string>): ColumnRouteSearch => {
return { return {
account: search.account, account: search.account,
@ -15,6 +14,7 @@ export const Route = createFileRoute("/interests")({
name: search.name, name: search.name,
}; };
}, },
component: Screen,
}); });
function Screen() { function Screen() {

View File

@ -6,8 +6,16 @@ export const Route = createFileRoute("/store/community")({
function Screen() { function Screen() {
return ( return (
<div className="flex flex-col gap-3 p-3"> <div className="flex h-full flex-col items-center justify-center gap-3 p-3">
<p>Coming Soon</p> <div className="size-24 bg-blue-100 flex flex-col items-center justify-end overflow-hidden dark:bg-blue-900 rounded-full">
<div className="w-12 h-16 bg-gradient-to-b from-blue-500 dark:from-blue-200 to-blue-50 dark:to-blue-900 rounded-t-lg" />
</div>
<div className="text-center">
<h1 className="font-semibold text-lg">Coming Soon</h1>
<p className="text-sm text-neutral-700 dark:text-neutral-300 leading-tight">
Enhance your experience <br /> by adding column shared by community.
</p>
</div>
</div> </div>
); );
} }

View File

@ -195,45 +195,39 @@ export class Ark {
hashtags: string[], hashtags: string[],
limit: number, limit: number,
asOf?: number, asOf?: number,
global?: boolean,
) { ) {
let until: string = undefined; let until: string = undefined;
if (asOf && asOf > 0) until = asOf.toString(); if (asOf && asOf > 0) until = asOf.toString();
const dedup = true;
const seenIds = new Set<string>(); const seenIds = new Set<string>();
const dedupQueue = new Set<string>(); const dedupQueue = new Set<string>();
const nostrTags = hashtags.map((tag) => tag.replace("#", "").toLowerCase());
const nostrEvents: Event[] = await invoke("get_events_from_interests", { const nostrEvents: Event[] = await invoke("get_events_from_interests", {
hashtags, hashtags: nostrTags,
limit, limit,
until, until,
global,
}); });
if (dedup) { for (const event of nostrEvents) {
for (const event of nostrEvents) { const tags = event.tags
const tags = event.tags .filter((el) => el[0] === "e")
.filter((el) => el[0] === "e") ?.map((item) => item[1]);
?.map((item) => item[1]);
if (tags.length) { if (tags.length) {
for (const tag of tags) { for (const tag of tags) {
if (seenIds.has(tag)) { if (seenIds.has(tag)) {
dedupQueue.add(event.id); dedupQueue.add(event.id);
break; break;
}
seenIds.add(tag);
} }
seenIds.add(tag);
} }
} }
return nostrEvents
.filter((event) => !dedupQueue.has(event.id))
.sort((a, b) => b.created_at - a.created_at);
} }
return nostrEvents.sort((a, b) => b.created_at - a.created_at); return nostrEvents
.filter((event) => !dedupQueue.has(event.id))
.sort((a, b) => b.created_at - a.created_at);
} }
public async publish( public async publish(

View File

@ -126,7 +126,7 @@ export function NoteContent({
); );
if (compact) { if (compact) {
parsedContent = reactStringReplace(parsedContent, /[\r\n]{2,}/g, () => ( parsedContent = reactStringReplace(parsedContent, /\n*\n/g, () => (
<div key={nanoid()} className="h-1.5" /> <div key={nanoid()} className="h-1.5" />
)); ));
} }

View File

@ -19,7 +19,7 @@ export function MentionNote({
return ( return (
<div <div
contentEditable={false} contentEditable={false}
className="my-1 flex w-full cursor-default items-center justify-between rounded-2xl border border-black/10 p-3 dark:border-white/10" className="my-1 flex w-full cursor-default items-center justify-between rounded-xl border border-black/10 p-3 dark:border-white/10"
> >
<p>Loading...</p> <p>Loading...</p>
</div> </div>
@ -30,7 +30,7 @@ export function MentionNote({
return ( return (
<div <div
contentEditable={false} contentEditable={false}
className="my-1 w-full cursor-default rounded-2xl border border-black/10 p-3 dark:border-white/10" className="my-1 w-full cursor-default rounded-xl border border-black/10 p-3 dark:border-white/10"
> >
{t("note.error")} {t("note.error")}
</div> </div>

View File

@ -19,16 +19,6 @@
"author": "Lume", "author": "Lume",
"description": "Collective of people you're interested in." "description": "Collective of people you're interested in."
}, },
{
"label": "sDbO6XxAGnW5XuUZEgZMn",
"name": "Antenas",
"content": "/antenas",
"logo": "",
"cover": "/antenas.png",
"coverRetina": "/antenas@2x.png",
"author": "Lume",
"description": "Keep track to specific content."
},
{ {
"label": "gxtcIbgD8YNPbeI5o92I8", "label": "gxtcIbgD8YNPbeI5o92I8",
"name": "Trending", "name": "Trending",

View File

@ -155,7 +155,6 @@ pub async fn get_events_from_interests(
hashtags: Vec<&str>, hashtags: Vec<&str>,
limit: usize, limit: usize,
until: Option<&str>, until: Option<&str>,
global: Option<bool>,
state: State<'_, Nostr>, state: State<'_, Nostr>,
) -> Result<Vec<Event>, String> { ) -> Result<Vec<Event>, String> {
let client = &state.client; let client = &state.client;
@ -163,34 +162,11 @@ pub async fn get_events_from_interests(
Some(until) => Timestamp::from_str(until).unwrap(), Some(until) => Timestamp::from_str(until).unwrap(),
None => Timestamp::now(), None => Timestamp::now(),
}; };
let authors = match global { let filter = Filter::new()
Some(val) => match val { .kinds(vec![Kind::TextNote, Kind::Repost])
true => None, .limit(limit)
false => { .until(as_of)
match client .hashtags(hashtags);
.get_contact_list_public_keys(Some(Duration::from_secs(10)))
.await
{
Ok(val) => Some(val),
Err(_) => None,
}
}
},
None => None,
};
let filter = match authors {
Some(val) => Filter::new()
.kinds(vec![Kind::TextNote, Kind::Repost])
.authors(val)
.limit(limit)
.until(as_of)
.hashtags(hashtags),
None => Filter::new()
.kinds(vec![Kind::TextNote, Kind::Repost])
.limit(limit)
.until(as_of)
.hashtags(hashtags),
};
if let Ok(events) = client if let Ok(events) = client
.get_events_of(vec![filter], Some(Duration::from_secs(15))) .get_events_of(vec![filter], Some(Duration::from_secs(15)))