feat: negentropy progress
This commit is contained in:
@@ -5,6 +5,22 @@
|
||||
|
||||
|
||||
export const commands = {
|
||||
async syncAccount(id: string, reader: TAURI_CHANNEL<number>) : Promise<Result<null, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("sync_account", { id, reader }) };
|
||||
} catch (e) {
|
||||
if(e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async isAccountSync(id: string) : Promise<Result<boolean, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("is_account_sync", { id }) };
|
||||
} catch (e) {
|
||||
if(e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async getRelays(id: string) : Promise<Result<Relays, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("get_relays", { id }) };
|
||||
@@ -96,22 +112,6 @@ async resetPassword(key: string, password: string) : Promise<Result<null, string
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async isNewAccount(id: string) : Promise<Result<boolean, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("is_new_account", { id }) };
|
||||
} catch (e) {
|
||||
if(e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async toggleNewAccount(id: string) : Promise<Result<null, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("toggle_new_account", { id }) };
|
||||
} catch (e) {
|
||||
if(e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async hasSigner(id: string) : Promise<Result<boolean, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("has_signer", { id }) };
|
||||
@@ -312,9 +312,9 @@ async verifyNip05(id: string, nip05: string) : Promise<Result<boolean, string>>
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async getEventMeta(content: string) : Promise<Result<Meta, null>> {
|
||||
async getMetaFromEvent(content: string) : Promise<Result<Meta, null>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("get_event_meta", { content }) };
|
||||
return { status: "ok", data: await TAURI_INVOKE("get_meta_from_event", { content }) };
|
||||
} catch (e) {
|
||||
if(e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
@@ -328,14 +328,6 @@ async getEvent(id: string) : Promise<Result<RichEvent, string>> {
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async getEventFrom(id: string, relayHint: string) : Promise<Result<RichEvent, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("get_event_from", { id, relayHint }) };
|
||||
} catch (e) {
|
||||
if(e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async getReplies(id: string) : Promise<Result<RichEvent[], string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("get_replies", { id }) };
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { appSettings, cn } from "@/commons";
|
||||
import { useStore } from "@tanstack/react-store";
|
||||
import { nanoid } from "nanoid";
|
||||
import { type ReactNode, memo, useMemo, useState } from "react";
|
||||
import { type ReactNode, useMemo, useState } from "react";
|
||||
import reactStringReplace from "react-string-replace";
|
||||
import { Hashtag } from "./mentions/hashtag";
|
||||
import { MentionNote } from "./mentions/note";
|
||||
@@ -22,7 +22,6 @@ export function NoteContent({
|
||||
}) {
|
||||
const event = useNoteContext();
|
||||
const visible = useStore(appSettings, (state) => state.display_media);
|
||||
const warning = useMemo(() => event.warning, [event]);
|
||||
const content = useMemo(() => {
|
||||
try {
|
||||
// Get parsed meta
|
||||
@@ -88,52 +87,48 @@ export function NoteContent({
|
||||
}
|
||||
}, [event.content]);
|
||||
|
||||
const [blurred, setBlurred] = useState(() =>
|
||||
event.warning ? event.warning.length > 0 : false,
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="relative flex flex-col gap-2">
|
||||
<ContentWarning warning={warning} />
|
||||
<div
|
||||
className={cn(
|
||||
"select-text text-pretty content-break overflow-hidden",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{content}
|
||||
</div>
|
||||
{visible ? (
|
||||
event.meta?.images.length ? (
|
||||
<Images urls={event.meta.images} />
|
||||
) : null
|
||||
) : null}
|
||||
{!blurred ? (
|
||||
<>
|
||||
<div
|
||||
className={cn(
|
||||
"select-text text-pretty content-break overflow-hidden",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{content}
|
||||
</div>
|
||||
{visible ? (
|
||||
event.meta?.images.length ? (
|
||||
<Images urls={event.meta.images} />
|
||||
) : null
|
||||
) : null}
|
||||
</>
|
||||
) : (
|
||||
<div
|
||||
className={cn(
|
||||
"select-text text-pretty content-break overflow-hidden",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<p className="text-yellow-600 dark:text-yellow-400">
|
||||
The content is hidden because the author marked it with a warning
|
||||
for a reason: <span className="font-semibold">{event.warning}</span>
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setBlurred(false)}
|
||||
className="font-medium text-sm text-blue-500 hover:text-blue-600"
|
||||
>
|
||||
View anyway
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const ContentWarning = memo(function ContentWarning({
|
||||
warning,
|
||||
}: { warning: string }) {
|
||||
const [blurred, setBlurred] = useState(() => warning?.length > 0);
|
||||
|
||||
if (!blurred) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="absolute inset-0 z-10 flex items-center justify-center w-full bg-black/80 backdrop-blur-lg">
|
||||
<div className="flex flex-col items-center justify-center gap-2 text-center">
|
||||
<p className="text-sm text-white/60">
|
||||
The content is hidden because the author
|
||||
<br />
|
||||
marked it with a warning for a reason:
|
||||
</p>
|
||||
<p className="text-sm font-medium text-white">{warning}</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setBlurred(false)}
|
||||
className="inline-flex items-center justify-center px-2 mt-4 text-sm font-medium border rounded-lg text-white/70 h-9 w-max bg-white/20 hover:bg-white/30 border-white/5"
|
||||
>
|
||||
View anyway
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -17,16 +17,19 @@ export const MentionNote = memo(function MentionNote({
|
||||
|
||||
return (
|
||||
<div className="relative my-2">
|
||||
<div className="min-h-[64px] pl-3 before:content-[''] before:absolute before:top-1.5 before:bottom-1.5 before:left-0 before:border-l-[2px] before:border-black/10 dark:before:border-white/10">
|
||||
<div className="pl-3 before:content-[''] before:absolute before:top-1.5 before:bottom-1.5 before:left-0 before:border-l-[2px] before:border-black/10 dark:before:border-white/10">
|
||||
{isLoading ? (
|
||||
<div className="h-[64px] flex items-center">
|
||||
<div className="h-[32px] flex items-center gap-2 text-sm">
|
||||
<Spinner />
|
||||
Loadng note
|
||||
</div>
|
||||
) : isError || !event ? (
|
||||
<div className="h-[64px] flex items-center">
|
||||
<div className="flex flex-col break-all">
|
||||
<p className="text-sm font-medium text-red-500">
|
||||
{error.message || "Note can be found with your current relay set"}
|
||||
{error?.message ??
|
||||
"Cannot found this note within your current relay set"}
|
||||
</p>
|
||||
<p className="text-sm">{eventId}</p>
|
||||
</div>
|
||||
) : (
|
||||
<Note.Provider event={event}>
|
||||
|
||||
@@ -39,10 +39,6 @@ export function ImagePreview({ url }: { url: string }) {
|
||||
decoding="async"
|
||||
style={{ contentVisibility: "auto" }}
|
||||
className="max-h-[400px] w-full h-auto object-cover rounded-lg outline outline-1 -outline-offset-1 outline-black/15"
|
||||
onError={({ currentTarget }) => {
|
||||
currentTarget.onerror = null;
|
||||
currentTarget.src = "/404.jpg";
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -21,9 +21,15 @@ export function Images({ urls }: { urls: string[] }) {
|
||||
let newUrls: string[];
|
||||
|
||||
if (urls.length === 1) {
|
||||
newUrls = urls.map(
|
||||
(url) => `${service}?url=${url}&ll&af&default=1&n=-1`,
|
||||
);
|
||||
newUrls = urls.map((url) => {
|
||||
if (url.includes("_next/")) {
|
||||
return url;
|
||||
}
|
||||
if (url.includes("bsky.network")) {
|
||||
return url;
|
||||
}
|
||||
return `${service}?url=${url}&ll&af&default=1&n=-1`;
|
||||
});
|
||||
} else {
|
||||
newUrls = urls.map(
|
||||
(url) => `${service}?url=${url}&w=480&h=640&ll&af&default=1&n=-1`,
|
||||
@@ -83,10 +89,6 @@ export function Images({ urls }: { urls: string[] }) {
|
||||
className="max-h-[400px] w-auto object-cover rounded-lg outline outline-1 -outline-offset-1 outline-black/15"
|
||||
onClick={() => urls[0]}
|
||||
onKeyDown={() => urls[0]}
|
||||
onError={({ currentTarget }) => {
|
||||
currentTarget.onerror = null;
|
||||
currentTarget.src = "/404.jpg";
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -162,10 +164,6 @@ function LazyImage({ url, inView }: { url: string; inView: boolean }) {
|
||||
onClick={() => open(url)}
|
||||
onKeyDown={() => open(url)}
|
||||
onLoad={setLoaded}
|
||||
onError={({ currentTarget }) => {
|
||||
currentTarget.onerror = null;
|
||||
currentTarget.src = "/404.jpg";
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -18,6 +18,9 @@ export function UserAvatar({ className }: { className?: string }) {
|
||||
if (user.profile?.picture.includes("_next/")) {
|
||||
return user.profile?.picture;
|
||||
}
|
||||
if (user.profile?.picture.includes("bsky.network")) {
|
||||
return user.profile?.picture;
|
||||
}
|
||||
return `${service}?url=${user.profile?.picture}&w=100&h=100&n=-1&default=${user.profile?.picture}`;
|
||||
} else {
|
||||
return user.profile?.picture;
|
||||
|
||||
@@ -51,9 +51,6 @@ const ColumnsLayoutSearchLazyImport = createFileRoute(
|
||||
const ColumnsLayoutOnboardingLazyImport = createFileRoute(
|
||||
'/columns/_layout/onboarding',
|
||||
)()
|
||||
const ColumnsLayoutLaunchpadLazyImport = createFileRoute(
|
||||
'/columns/_layout/launchpad',
|
||||
)()
|
||||
const ColumnsLayoutUsersIdLazyImport = createFileRoute(
|
||||
'/columns/_layout/users/$id',
|
||||
)()
|
||||
@@ -63,6 +60,9 @@ const ColumnsLayoutRepliesIdLazyImport = createFileRoute(
|
||||
const ColumnsLayoutNotificationIdLazyImport = createFileRoute(
|
||||
'/columns/_layout/notification/$id',
|
||||
)()
|
||||
const ColumnsLayoutLaunchpadIdLazyImport = createFileRoute(
|
||||
'/columns/_layout/launchpad/$id',
|
||||
)()
|
||||
const ColumnsLayoutEventsIdLazyImport = createFileRoute(
|
||||
'/columns/_layout/events/$id',
|
||||
)()
|
||||
@@ -171,15 +171,6 @@ const ColumnsLayoutOnboardingLazyRoute =
|
||||
import('./routes/columns/_layout/onboarding.lazy').then((d) => d.Route),
|
||||
)
|
||||
|
||||
const ColumnsLayoutLaunchpadLazyRoute = ColumnsLayoutLaunchpadLazyImport.update(
|
||||
{
|
||||
path: '/launchpad',
|
||||
getParentRoute: () => ColumnsLayoutRoute,
|
||||
} as any,
|
||||
).lazy(() =>
|
||||
import('./routes/columns/_layout/launchpad.lazy').then((d) => d.Route),
|
||||
)
|
||||
|
||||
const SettingsIdWalletRoute = SettingsIdWalletImport.update({
|
||||
path: '/wallet',
|
||||
getParentRoute: () => SettingsIdLazyRoute,
|
||||
@@ -245,6 +236,14 @@ const ColumnsLayoutNotificationIdLazyRoute =
|
||||
),
|
||||
)
|
||||
|
||||
const ColumnsLayoutLaunchpadIdLazyRoute =
|
||||
ColumnsLayoutLaunchpadIdLazyImport.update({
|
||||
path: '/launchpad/$id',
|
||||
getParentRoute: () => ColumnsLayoutRoute,
|
||||
} as any).lazy(() =>
|
||||
import('./routes/columns/_layout/launchpad.$id.lazy').then((d) => d.Route),
|
||||
)
|
||||
|
||||
const ColumnsLayoutEventsIdLazyRoute = ColumnsLayoutEventsIdLazyImport.update({
|
||||
path: '/events/$id',
|
||||
getParentRoute: () => ColumnsLayoutRoute,
|
||||
@@ -436,13 +435,6 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof SettingsIdWalletImport
|
||||
parentRoute: typeof SettingsIdLazyImport
|
||||
}
|
||||
'/columns/_layout/launchpad': {
|
||||
id: '/columns/_layout/launchpad'
|
||||
path: '/launchpad'
|
||||
fullPath: '/columns/launchpad'
|
||||
preLoaderRoute: typeof ColumnsLayoutLaunchpadLazyImport
|
||||
parentRoute: typeof ColumnsLayoutImport
|
||||
}
|
||||
'/columns/_layout/onboarding': {
|
||||
id: '/columns/_layout/onboarding'
|
||||
path: '/onboarding'
|
||||
@@ -513,6 +505,13 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof ColumnsLayoutEventsIdLazyImport
|
||||
parentRoute: typeof ColumnsLayoutImport
|
||||
}
|
||||
'/columns/_layout/launchpad/$id': {
|
||||
id: '/columns/_layout/launchpad/$id'
|
||||
path: '/launchpad/$id'
|
||||
fullPath: '/columns/launchpad/$id'
|
||||
preLoaderRoute: typeof ColumnsLayoutLaunchpadIdLazyImport
|
||||
parentRoute: typeof ColumnsLayoutImport
|
||||
}
|
||||
'/columns/_layout/notification/$id': {
|
||||
id: '/columns/_layout/notification/$id'
|
||||
path: '/notification/$id'
|
||||
@@ -569,7 +568,6 @@ const ColumnsLayoutCreateNewsfeedRouteWithChildren =
|
||||
interface ColumnsLayoutRouteChildren {
|
||||
ColumnsLayoutCreateNewsfeedRoute: typeof ColumnsLayoutCreateNewsfeedRouteWithChildren
|
||||
ColumnsLayoutGlobalRoute: typeof ColumnsLayoutGlobalRoute
|
||||
ColumnsLayoutLaunchpadLazyRoute: typeof ColumnsLayoutLaunchpadLazyRoute
|
||||
ColumnsLayoutOnboardingLazyRoute: typeof ColumnsLayoutOnboardingLazyRoute
|
||||
ColumnsLayoutSearchLazyRoute: typeof ColumnsLayoutSearchLazyRoute
|
||||
ColumnsLayoutTrendingLazyRoute: typeof ColumnsLayoutTrendingLazyRoute
|
||||
@@ -578,6 +576,7 @@ interface ColumnsLayoutRouteChildren {
|
||||
ColumnsLayoutNewsfeedIdRoute: typeof ColumnsLayoutNewsfeedIdRoute
|
||||
ColumnsLayoutStoriesIdRoute: typeof ColumnsLayoutStoriesIdRoute
|
||||
ColumnsLayoutEventsIdLazyRoute: typeof ColumnsLayoutEventsIdLazyRoute
|
||||
ColumnsLayoutLaunchpadIdLazyRoute: typeof ColumnsLayoutLaunchpadIdLazyRoute
|
||||
ColumnsLayoutNotificationIdLazyRoute: typeof ColumnsLayoutNotificationIdLazyRoute
|
||||
ColumnsLayoutRepliesIdLazyRoute: typeof ColumnsLayoutRepliesIdLazyRoute
|
||||
ColumnsLayoutUsersIdLazyRoute: typeof ColumnsLayoutUsersIdLazyRoute
|
||||
@@ -587,7 +586,6 @@ const ColumnsLayoutRouteChildren: ColumnsLayoutRouteChildren = {
|
||||
ColumnsLayoutCreateNewsfeedRoute:
|
||||
ColumnsLayoutCreateNewsfeedRouteWithChildren,
|
||||
ColumnsLayoutGlobalRoute: ColumnsLayoutGlobalRoute,
|
||||
ColumnsLayoutLaunchpadLazyRoute: ColumnsLayoutLaunchpadLazyRoute,
|
||||
ColumnsLayoutOnboardingLazyRoute: ColumnsLayoutOnboardingLazyRoute,
|
||||
ColumnsLayoutSearchLazyRoute: ColumnsLayoutSearchLazyRoute,
|
||||
ColumnsLayoutTrendingLazyRoute: ColumnsLayoutTrendingLazyRoute,
|
||||
@@ -596,6 +594,7 @@ const ColumnsLayoutRouteChildren: ColumnsLayoutRouteChildren = {
|
||||
ColumnsLayoutNewsfeedIdRoute: ColumnsLayoutNewsfeedIdRoute,
|
||||
ColumnsLayoutStoriesIdRoute: ColumnsLayoutStoriesIdRoute,
|
||||
ColumnsLayoutEventsIdLazyRoute: ColumnsLayoutEventsIdLazyRoute,
|
||||
ColumnsLayoutLaunchpadIdLazyRoute: ColumnsLayoutLaunchpadIdLazyRoute,
|
||||
ColumnsLayoutNotificationIdLazyRoute: ColumnsLayoutNotificationIdLazyRoute,
|
||||
ColumnsLayoutRepliesIdLazyRoute: ColumnsLayoutRepliesIdLazyRoute,
|
||||
ColumnsLayoutUsersIdLazyRoute: ColumnsLayoutUsersIdLazyRoute,
|
||||
@@ -654,7 +653,6 @@ export interface FileRoutesByFullPath {
|
||||
'/settings/$id/profile': typeof SettingsIdProfileRoute
|
||||
'/settings/$id/relay': typeof SettingsIdRelayRoute
|
||||
'/settings/$id/wallet': typeof SettingsIdWalletRoute
|
||||
'/columns/launchpad': typeof ColumnsLayoutLaunchpadLazyRoute
|
||||
'/columns/onboarding': typeof ColumnsLayoutOnboardingLazyRoute
|
||||
'/columns/search': typeof ColumnsLayoutSearchLazyRoute
|
||||
'/columns/trending': typeof ColumnsLayoutTrendingLazyRoute
|
||||
@@ -665,6 +663,7 @@ export interface FileRoutesByFullPath {
|
||||
'/columns/newsfeed/$id': typeof ColumnsLayoutNewsfeedIdRoute
|
||||
'/columns/stories/$id': typeof ColumnsLayoutStoriesIdRoute
|
||||
'/columns/events/$id': typeof ColumnsLayoutEventsIdLazyRoute
|
||||
'/columns/launchpad/$id': typeof ColumnsLayoutLaunchpadIdLazyRoute
|
||||
'/columns/notification/$id': typeof ColumnsLayoutNotificationIdLazyRoute
|
||||
'/columns/replies/$id': typeof ColumnsLayoutRepliesIdLazyRoute
|
||||
'/columns/users/$id': typeof ColumnsLayoutUsersIdLazyRoute
|
||||
@@ -689,7 +688,6 @@ export interface FileRoutesByTo {
|
||||
'/settings/$id/profile': typeof SettingsIdProfileRoute
|
||||
'/settings/$id/relay': typeof SettingsIdRelayRoute
|
||||
'/settings/$id/wallet': typeof SettingsIdWalletRoute
|
||||
'/columns/launchpad': typeof ColumnsLayoutLaunchpadLazyRoute
|
||||
'/columns/onboarding': typeof ColumnsLayoutOnboardingLazyRoute
|
||||
'/columns/search': typeof ColumnsLayoutSearchLazyRoute
|
||||
'/columns/trending': typeof ColumnsLayoutTrendingLazyRoute
|
||||
@@ -700,6 +698,7 @@ export interface FileRoutesByTo {
|
||||
'/columns/newsfeed/$id': typeof ColumnsLayoutNewsfeedIdRoute
|
||||
'/columns/stories/$id': typeof ColumnsLayoutStoriesIdRoute
|
||||
'/columns/events/$id': typeof ColumnsLayoutEventsIdLazyRoute
|
||||
'/columns/launchpad/$id': typeof ColumnsLayoutLaunchpadIdLazyRoute
|
||||
'/columns/notification/$id': typeof ColumnsLayoutNotificationIdLazyRoute
|
||||
'/columns/replies/$id': typeof ColumnsLayoutRepliesIdLazyRoute
|
||||
'/columns/users/$id': typeof ColumnsLayoutUsersIdLazyRoute
|
||||
@@ -727,7 +726,6 @@ export interface FileRoutesById {
|
||||
'/settings/$id/profile': typeof SettingsIdProfileRoute
|
||||
'/settings/$id/relay': typeof SettingsIdRelayRoute
|
||||
'/settings/$id/wallet': typeof SettingsIdWalletRoute
|
||||
'/columns/_layout/launchpad': typeof ColumnsLayoutLaunchpadLazyRoute
|
||||
'/columns/_layout/onboarding': typeof ColumnsLayoutOnboardingLazyRoute
|
||||
'/columns/_layout/search': typeof ColumnsLayoutSearchLazyRoute
|
||||
'/columns/_layout/trending': typeof ColumnsLayoutTrendingLazyRoute
|
||||
@@ -738,6 +736,7 @@ export interface FileRoutesById {
|
||||
'/columns/_layout/newsfeed/$id': typeof ColumnsLayoutNewsfeedIdRoute
|
||||
'/columns/_layout/stories/$id': typeof ColumnsLayoutStoriesIdRoute
|
||||
'/columns/_layout/events/$id': typeof ColumnsLayoutEventsIdLazyRoute
|
||||
'/columns/_layout/launchpad/$id': typeof ColumnsLayoutLaunchpadIdLazyRoute
|
||||
'/columns/_layout/notification/$id': typeof ColumnsLayoutNotificationIdLazyRoute
|
||||
'/columns/_layout/replies/$id': typeof ColumnsLayoutRepliesIdLazyRoute
|
||||
'/columns/_layout/users/$id': typeof ColumnsLayoutUsersIdLazyRoute
|
||||
@@ -765,7 +764,6 @@ export interface FileRouteTypes {
|
||||
| '/settings/$id/profile'
|
||||
| '/settings/$id/relay'
|
||||
| '/settings/$id/wallet'
|
||||
| '/columns/launchpad'
|
||||
| '/columns/onboarding'
|
||||
| '/columns/search'
|
||||
| '/columns/trending'
|
||||
@@ -776,6 +774,7 @@ export interface FileRouteTypes {
|
||||
| '/columns/newsfeed/$id'
|
||||
| '/columns/stories/$id'
|
||||
| '/columns/events/$id'
|
||||
| '/columns/launchpad/$id'
|
||||
| '/columns/notification/$id'
|
||||
| '/columns/replies/$id'
|
||||
| '/columns/users/$id'
|
||||
@@ -799,7 +798,6 @@ export interface FileRouteTypes {
|
||||
| '/settings/$id/profile'
|
||||
| '/settings/$id/relay'
|
||||
| '/settings/$id/wallet'
|
||||
| '/columns/launchpad'
|
||||
| '/columns/onboarding'
|
||||
| '/columns/search'
|
||||
| '/columns/trending'
|
||||
@@ -810,6 +808,7 @@ export interface FileRouteTypes {
|
||||
| '/columns/newsfeed/$id'
|
||||
| '/columns/stories/$id'
|
||||
| '/columns/events/$id'
|
||||
| '/columns/launchpad/$id'
|
||||
| '/columns/notification/$id'
|
||||
| '/columns/replies/$id'
|
||||
| '/columns/users/$id'
|
||||
@@ -835,7 +834,6 @@ export interface FileRouteTypes {
|
||||
| '/settings/$id/profile'
|
||||
| '/settings/$id/relay'
|
||||
| '/settings/$id/wallet'
|
||||
| '/columns/_layout/launchpad'
|
||||
| '/columns/_layout/onboarding'
|
||||
| '/columns/_layout/search'
|
||||
| '/columns/_layout/trending'
|
||||
@@ -846,6 +844,7 @@ export interface FileRouteTypes {
|
||||
| '/columns/_layout/newsfeed/$id'
|
||||
| '/columns/_layout/stories/$id'
|
||||
| '/columns/_layout/events/$id'
|
||||
| '/columns/_layout/launchpad/$id'
|
||||
| '/columns/_layout/notification/$id'
|
||||
| '/columns/_layout/replies/$id'
|
||||
| '/columns/_layout/users/$id'
|
||||
@@ -938,7 +937,6 @@ export const routeTree = rootRoute
|
||||
"children": [
|
||||
"/columns/_layout/create-newsfeed",
|
||||
"/columns/_layout/global",
|
||||
"/columns/_layout/launchpad",
|
||||
"/columns/_layout/onboarding",
|
||||
"/columns/_layout/search",
|
||||
"/columns/_layout/trending",
|
||||
@@ -947,6 +945,7 @@ export const routeTree = rootRoute
|
||||
"/columns/_layout/newsfeed/$id",
|
||||
"/columns/_layout/stories/$id",
|
||||
"/columns/_layout/events/$id",
|
||||
"/columns/_layout/launchpad/$id",
|
||||
"/columns/_layout/notification/$id",
|
||||
"/columns/_layout/replies/$id",
|
||||
"/columns/_layout/users/$id"
|
||||
@@ -1008,10 +1007,6 @@ export const routeTree = rootRoute
|
||||
"filePath": "settings.$id/wallet.tsx",
|
||||
"parent": "/settings/$id"
|
||||
},
|
||||
"/columns/_layout/launchpad": {
|
||||
"filePath": "columns/_layout/launchpad.lazy.tsx",
|
||||
"parent": "/columns/_layout"
|
||||
},
|
||||
"/columns/_layout/onboarding": {
|
||||
"filePath": "columns/_layout/onboarding.lazy.tsx",
|
||||
"parent": "/columns/_layout"
|
||||
@@ -1052,6 +1047,10 @@ export const routeTree = rootRoute
|
||||
"filePath": "columns/_layout/events.$id.lazy.tsx",
|
||||
"parent": "/columns/_layout"
|
||||
},
|
||||
"/columns/_layout/launchpad/$id": {
|
||||
"filePath": "columns/_layout/launchpad.$id.lazy.tsx",
|
||||
"parent": "/columns/_layout"
|
||||
},
|
||||
"/columns/_layout/notification/$id": {
|
||||
"filePath": "columns/_layout/notification.$id.lazy.tsx",
|
||||
"parent": "/columns/_layout"
|
||||
|
||||
@@ -60,26 +60,24 @@ function Topbar() {
|
||||
data-tauri-drag-region
|
||||
className="relative z-[200] flex-1 flex items-center justify-end gap-4"
|
||||
>
|
||||
{accounts?.length ? (
|
||||
<div className="inline-flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => LumeWindow.openEditor()}
|
||||
className="inline-flex items-center justify-center h-7 gap-1 px-2 text-sm font-medium bg-black/5 dark:bg-white/5 rounded-full w-max hover:bg-blue-500 hover:text-white"
|
||||
>
|
||||
<PublishIcon className="size-4" />
|
||||
New Post
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => LumeWindow.openSearch()}
|
||||
className="inline-flex items-center justify-center size-7 bg-black/5 dark:bg-white/5 rounded-full hover:bg-blue-500 hover:text-white"
|
||||
>
|
||||
<MagnifyingGlass className="size-4" />
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
<div id="toolbar" className="inline-flex items-center gap-2" />
|
||||
<div className="inline-flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => LumeWindow.openEditor()}
|
||||
className="inline-flex items-center justify-center h-7 gap-1 px-2 text-sm font-medium bg-black/5 dark:bg-white/5 rounded-full w-max hover:bg-blue-500 hover:text-white"
|
||||
>
|
||||
<PublishIcon className="size-4" />
|
||||
New Post
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => LumeWindow.openSearch()}
|
||||
className="inline-flex items-center justify-center size-7 bg-black/5 dark:bg-white/5 rounded-full hover:bg-blue-500 hover:text-white"
|
||||
>
|
||||
<MagnifyingGlass className="size-4" />
|
||||
</button>
|
||||
</div>
|
||||
<div id="toolbar" className="inline-flex items-center gap-1" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { appColumns } from "@/commons";
|
||||
import { commands } from "@/commands.gen";
|
||||
import { appColumns, displayNpub } from "@/commons";
|
||||
import { Column, Spinner } from "@/components";
|
||||
import { LumeWindow } from "@/system";
|
||||
import type { ColumnEvent, LumeColumn } from "@/types";
|
||||
import { ArrowLeft, ArrowRight, Plus, StackPlus } from "@phosphor-icons/react";
|
||||
import type { ColumnEvent, LumeColumn, Metadata } from "@/types";
|
||||
import { ArrowLeft, ArrowRight, Plus } from "@phosphor-icons/react";
|
||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||
import { useStore } from "@tanstack/react-store";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { Menu, MenuItem } from "@tauri-apps/api/menu";
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||
import useEmblaCarousel from "embla-carousel-react";
|
||||
import { nanoid } from "nanoid";
|
||||
import {
|
||||
type ReactNode,
|
||||
useCallback,
|
||||
@@ -45,11 +46,14 @@ function Screen() {
|
||||
}, []);
|
||||
|
||||
const add = useDebouncedCallback((column: LumeColumn) => {
|
||||
column.label = `${column.label}-${nanoid()}`; // update col label
|
||||
appColumns.setState((prev) => [column, ...prev]);
|
||||
const exist = columns.find((col) => col.label === column.label);
|
||||
|
||||
if (emblaApi) {
|
||||
emblaApi.scrollTo(0, true);
|
||||
if (!exist) {
|
||||
appColumns.setState((prev) => [column, ...prev]);
|
||||
|
||||
if (emblaApi) {
|
||||
emblaApi.scrollTo(0, true);
|
||||
}
|
||||
}
|
||||
}, 150);
|
||||
|
||||
@@ -141,47 +145,79 @@ function Screen() {
|
||||
<Column key={column.label} column={column} />
|
||||
))
|
||||
)}
|
||||
<div className="shrink-0 p-2 h-full w-[440px]">
|
||||
<div className="size-full flex items-center justify-center">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => LumeWindow.openLaunchpad()}
|
||||
className="inline-flex items-center justify-center gap-1 rounded-full text-sm font-medium h-8 w-max pl-2 pr-3 bg-black/5 dark:bg-white/5 hover:bg-black/10 dark:hover:bg-white/10"
|
||||
>
|
||||
<Plus className="size-4" />
|
||||
Add Column
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<OpenLaunchpad />
|
||||
</div>
|
||||
</div>
|
||||
<Toolbar>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => LumeWindow.openLaunchpad()}
|
||||
className="inline-flex items-center justify-center rounded-full size-7 hover:bg-black/5 dark:hover:bg-white/5"
|
||||
>
|
||||
<StackPlus className="size-4" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => scrollPrev()}
|
||||
className="inline-flex items-center justify-center rounded-full size-7 hover:bg-black/5 dark:hover:bg-white/5"
|
||||
>
|
||||
<ArrowLeft className="size-4" />
|
||||
<ArrowLeft className="size-4" weight="bold" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => scrollNext()}
|
||||
className="inline-flex items-center justify-center rounded-full size-7 hover:bg-black/5 dark:hover:bg-white/5"
|
||||
>
|
||||
<ArrowRight className="size-4" />
|
||||
<ArrowRight className="size-4" weight="bold" />
|
||||
</button>
|
||||
</Toolbar>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function OpenLaunchpad() {
|
||||
const { accounts } = Route.useRouteContext();
|
||||
|
||||
const showContextMenu = useCallback(
|
||||
async (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
const list: Promise<MenuItem>[] = [];
|
||||
|
||||
for (const account of accounts) {
|
||||
const res = await commands.getProfile(account);
|
||||
let name = "unknown";
|
||||
|
||||
if (res.status === "ok") {
|
||||
const profile: Metadata = JSON.parse(res.data);
|
||||
name = profile.display_name ?? profile.name ?? "unknown";
|
||||
}
|
||||
|
||||
list.push(
|
||||
MenuItem.new({
|
||||
text: `Open Launchpad for ${name} (${displayNpub(account, 16)})`,
|
||||
action: () => LumeWindow.openLaunchpad(account),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const items = await Promise.all(list);
|
||||
const menu = await Menu.new({ items });
|
||||
|
||||
await menu.popup().catch((e) => console.error(e));
|
||||
},
|
||||
[accounts],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="shrink-0 p-2 h-full w-[440px]">
|
||||
<div className="size-full flex items-center justify-center">
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => showContextMenu(e)}
|
||||
className="inline-flex items-center justify-center gap-1 rounded-full text-sm font-medium h-8 w-max pl-2.5 pr-3 bg-black/5 dark:bg-white/5 hover:bg-black/10 dark:hover:bg-white/10"
|
||||
>
|
||||
<Plus className="size-3" weight="bold" />
|
||||
Add Column
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Toolbar({ children }: { children: ReactNode[] }) {
|
||||
const [domReady, setDomReady] = useState(false);
|
||||
|
||||
|
||||
@@ -1,26 +1,37 @@
|
||||
import type { LumeColumn } from '@/types'
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { resolveResource } from '@tauri-apps/api/path'
|
||||
import { readTextFile } from '@tauri-apps/plugin-fs'
|
||||
import type { LumeColumn } from "@/types";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
export const Route = createFileRoute('/_app/')({
|
||||
loader: async ({ context }) => {
|
||||
const prevColumns = window.localStorage.getItem('columns')
|
||||
export const Route = createFileRoute("/_app/")({
|
||||
loader: async ({ context }) => {
|
||||
const accounts = context.accounts;
|
||||
const prevColumns = window.localStorage.getItem("columns");
|
||||
|
||||
if (!prevColumns) {
|
||||
const resourcePath = await resolveResource('resources/columns.json')
|
||||
const resourceFile = await readTextFile(resourcePath)
|
||||
const content: LumeColumn[] = JSON.parse(resourceFile)
|
||||
const initialAppColumns = content.filter((col) => col.default)
|
||||
let initialAppColumns: LumeColumn[] = [];
|
||||
|
||||
return initialAppColumns
|
||||
} else {
|
||||
const parsed: LumeColumn[] = JSON.parse(prevColumns)
|
||||
const initialAppColumns = parsed.filter((item) =>
|
||||
item.account ? context.accounts.includes(item.account) : item,
|
||||
)
|
||||
if (!prevColumns || prevColumns.length < 1) {
|
||||
initialAppColumns.push({
|
||||
label: "onboarding",
|
||||
name: "Onboarding",
|
||||
url: "/columns/onboarding",
|
||||
});
|
||||
|
||||
return initialAppColumns
|
||||
}
|
||||
},
|
||||
})
|
||||
for (const account of accounts) {
|
||||
initialAppColumns.push({
|
||||
label: `launchpad-${nanoid()}`,
|
||||
name: "Launchpad",
|
||||
url: `/columns/launchpad/${account}`,
|
||||
account,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const parsed: LumeColumn[] = JSON.parse(prevColumns);
|
||||
|
||||
initialAppColumns = parsed.filter((item) =>
|
||||
item.account ? context.accounts.includes(item.account) : item,
|
||||
);
|
||||
}
|
||||
|
||||
return initialAppColumns;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -4,19 +4,35 @@ import { Spinner, User } from "@/components";
|
||||
import { LumeWindow } from "@/system";
|
||||
import type { LumeColumn, NostrEvent } from "@/types";
|
||||
import { ArrowClockwise, Plus } from "@phosphor-icons/react";
|
||||
import * as Progress from "@radix-ui/react-progress";
|
||||
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||
import { Channel } from "@tauri-apps/api/core";
|
||||
import { resolveResource } from "@tauri-apps/api/path";
|
||||
import { readTextFile } from "@tauri-apps/plugin-fs";
|
||||
import { nanoid } from "nanoid";
|
||||
import { useCallback } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
export const Route = createLazyFileRoute("/columns/_layout/launchpad")({
|
||||
export const Route = createLazyFileRoute("/columns/_layout/launchpad/$id")({
|
||||
component: Screen,
|
||||
});
|
||||
|
||||
function Screen() {
|
||||
const { id } = Route.useParams();
|
||||
const { data: isSync } = useQuery({
|
||||
queryKey: ["is-sync", id],
|
||||
queryFn: async () => {
|
||||
const res = await commands.isAccountSync(id);
|
||||
|
||||
if (res.status === "ok") {
|
||||
return res.data;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<ScrollArea.Root
|
||||
type={"scroll"}
|
||||
@@ -24,10 +40,15 @@ function Screen() {
|
||||
className="overflow-hidden size-full"
|
||||
>
|
||||
<ScrollArea.Viewport className="relative h-full px-3 pb-3">
|
||||
<Groups />
|
||||
<Interests />
|
||||
<Accounts />
|
||||
<Core />
|
||||
{!isSync ? (
|
||||
<SyncProgress />
|
||||
) : (
|
||||
<>
|
||||
<Groups />
|
||||
<Interests />
|
||||
<Core />
|
||||
</>
|
||||
)}
|
||||
</ScrollArea.Viewport>
|
||||
<ScrollArea.Scrollbar
|
||||
className="flex select-none touch-none p-0.5 duration-[160ms] ease-out data-[orientation=vertical]:w-2"
|
||||
@@ -40,8 +61,74 @@ function Screen() {
|
||||
);
|
||||
}
|
||||
|
||||
function SyncProgress() {
|
||||
const { id } = Route.useParams();
|
||||
const { queryClient } = Route.useRouteContext();
|
||||
|
||||
const [error, setError] = useState("");
|
||||
const [progress, setProgress] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (progress >= 100) {
|
||||
await queryClient.invalidateQueries();
|
||||
}
|
||||
})();
|
||||
}, [progress]);
|
||||
|
||||
useEffect(() => {
|
||||
const channel = new Channel<number>();
|
||||
|
||||
channel.onmessage = (message) => {
|
||||
setProgress(message);
|
||||
};
|
||||
|
||||
(async () => {
|
||||
const res = await commands.syncAccount(id, channel);
|
||||
|
||||
if (res.status === "error") {
|
||||
setError(res.error);
|
||||
}
|
||||
})();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="size-full">
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="h-32 flex flex-col items-center justify-center rounded-xl overflow-hidden bg-white dark:bg-neutral-800/50 shadow-lg shadow-primary dark:ring-1 dark:ring-neutral-800">
|
||||
<div className="w-2/3 flex flex-col gap-2">
|
||||
<Progress.Root
|
||||
className="relative overflow-hidden bg-black/20 dark:bg-white/20 rounded-full w-full h-1"
|
||||
style={{
|
||||
transform: "translateZ(0)",
|
||||
}}
|
||||
value={progress}
|
||||
>
|
||||
<Progress.Indicator
|
||||
className="bg-blue-500 size-full transition-transform duration-[660ms] ease-[cubic-bezier(0.65, 0, 0.35, 1)]"
|
||||
style={{ transform: `translateX(-${100 - progress}%)` }}
|
||||
/>
|
||||
</Progress.Root>
|
||||
<span className="text-center text-xs">
|
||||
{error ? error : "Syncing in Progress..."}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<a
|
||||
href="https://github.com/hoytech/strfry/blob/nextneg/docs/negentropy.md"
|
||||
target="_blank"
|
||||
className="text-center !underline text-xs font-medium text-blue-500"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Learn more about Negentropy
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Groups() {
|
||||
const { isLoading, data, refetch, isRefetching } = useQuery({
|
||||
const { isLoading, isError, error, data, refetch, isRefetching } = useQuery({
|
||||
queryKey: ["others", "groups"],
|
||||
queryFn: async () => {
|
||||
const res = await commands.getAllGroups();
|
||||
@@ -72,7 +159,7 @@ function Groups() {
|
||||
className="group flex flex-col rounded-xl overflow-hidden bg-white dark:bg-neutral-800/50 shadow-lg shadow-primary dark:ring-1 dark:ring-neutral-800"
|
||||
>
|
||||
<div className="px-2 pt-2">
|
||||
<div className="p-3 h-16 bg-neutral-100 rounded-lg flex flex-wrap items-center justify-center gap-2 overflow-y-auto">
|
||||
<div className="p-3 h-16 bg-neutral-100 dark:bg-neutral-800 rounded-lg flex flex-wrap items-center justify-center gap-2 overflow-y-auto">
|
||||
{item.tags
|
||||
.filter((tag) => tag[0] === "p")
|
||||
.map((tag) => (
|
||||
@@ -148,12 +235,16 @@ function Groups() {
|
||||
<Spinner className="size-4" />
|
||||
Loading...
|
||||
</div>
|
||||
) : !data.length ? (
|
||||
) : isError ? (
|
||||
<div className="flex flex-col items-center justify-center h-16 w-full rounded-xl overflow-hidden bg-neutral-200/50 dark:bg-neutral-800/50">
|
||||
<p className="text-center">{error?.message ?? "Error"}</p>
|
||||
</div>
|
||||
) : !data?.length ? (
|
||||
<div className="flex flex-col items-center justify-center h-16 w-full rounded-xl overflow-hidden bg-neutral-200/50 dark:bg-neutral-800/50">
|
||||
<p className="text-center">You don't have any groups yet.</p>
|
||||
</div>
|
||||
) : (
|
||||
data.map((item) => renderItem(item))
|
||||
data?.map((item) => renderItem(item))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -161,7 +252,7 @@ function Groups() {
|
||||
}
|
||||
|
||||
function Interests() {
|
||||
const { isLoading, data, refetch, isRefetching } = useQuery({
|
||||
const { isLoading, isError, error, data, refetch, isRefetching } = useQuery({
|
||||
queryKey: ["others", "interests"],
|
||||
queryFn: async () => {
|
||||
const res = await commands.getAllInterests();
|
||||
@@ -193,7 +284,7 @@ function Interests() {
|
||||
className="group flex flex-col rounded-xl overflow-hidden bg-white dark:bg-neutral-800/50 shadow-lg shadow-primary dark:ring-1 dark:ring-neutral-800"
|
||||
>
|
||||
<div className="px-2 pt-2">
|
||||
<div className="p-3 h-16 bg-neutral-100 rounded-lg flex flex-wrap items-center justify-center gap-4 overflow-y-auto">
|
||||
<div className="p-3 h-16 bg-neutral-100 dark:bg-neutral-800 rounded-lg flex flex-wrap items-center justify-center gap-4 overflow-y-auto">
|
||||
{item.tags
|
||||
.filter((tag) => tag[0] === "t")
|
||||
.map((tag) => (
|
||||
@@ -267,87 +358,16 @@ function Interests() {
|
||||
<Spinner className="size-4" />
|
||||
Loading...
|
||||
</div>
|
||||
) : !data.length ? (
|
||||
) : isError ? (
|
||||
<div className="flex flex-col items-center justify-center h-16 w-full rounded-xl overflow-hidden bg-neutral-200/50 dark:bg-neutral-800/50">
|
||||
<p className="text-center">{error?.message ?? "Error"}</p>
|
||||
</div>
|
||||
) : !data?.length ? (
|
||||
<div className="flex flex-col items-center justify-center h-16 w-full rounded-xl overflow-hidden bg-neutral-200/50 dark:bg-neutral-800/50">
|
||||
<p className="text-center">You don't have any interests yet.</p>
|
||||
</div>
|
||||
) : (
|
||||
data.map((item) => renderItem(item))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Accounts() {
|
||||
const { isLoading, data: accounts } = useQuery({
|
||||
queryKey: ["accounts"],
|
||||
queryFn: async () => {
|
||||
const res = await commands.getAccounts();
|
||||
return res;
|
||||
},
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="mb-12 flex flex-col gap-3">
|
||||
<div className="flex items-center justify-between px-2">
|
||||
<h3 className="font-semibold">Accounts</h3>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3">
|
||||
{isLoading ? (
|
||||
<div className="inline-flex items-center gap-1.5 text-sm">
|
||||
<Spinner className="size-4" />
|
||||
Loading...
|
||||
</div>
|
||||
) : (
|
||||
accounts.map((account) => (
|
||||
<div
|
||||
key={account}
|
||||
className="group flex flex-col rounded-xl overflow-hidden bg-white dark:bg-neutral-800/50 shadow-lg shadow-primary dark:ring-1 dark:ring-neutral-800"
|
||||
>
|
||||
<div className="px-2 pt-2">
|
||||
<User.Provider pubkey={account}>
|
||||
<User.Root className="inline-flex items-center gap-2">
|
||||
<User.Avatar className="size-7 rounded-full" />
|
||||
<User.Name className="text-xs font-medium" />
|
||||
</User.Root>
|
||||
</User.Provider>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 p-2">
|
||||
<div className="px-3 flex items-center justify-between h-11 rounded-lg bg-neutral-100 dark:bg-neutral-800">
|
||||
<div className="text-sm font-medium">Newsfeed</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => LumeWindow.openNewsfeed(account)}
|
||||
className="h-6 w-16 inline-flex items-center justify-center gap-1 text-xs font-semibold rounded-full bg-neutral-200 dark:bg-neutral-700 hover:bg-blue-500 hover:text-white"
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
<div className="px-3 flex items-center justify-between h-11 rounded-lg bg-neutral-100 dark:bg-neutral-800">
|
||||
<div className="text-sm font-medium">Stories</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => LumeWindow.openStory(account)}
|
||||
className="h-6 w-16 inline-flex items-center justify-center gap-1 text-xs font-semibold rounded-full bg-neutral-200 dark:bg-neutral-700 hover:bg-blue-500 hover:text-white"
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
<div className="px-3 flex items-center justify-between h-11 rounded-lg bg-neutral-100 dark:bg-neutral-800">
|
||||
<div className="text-sm font-medium">Notification</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => LumeWindow.openNotification(account)}
|
||||
className="h-6 w-16 inline-flex items-center justify-center gap-1 text-xs font-semibold rounded-full bg-neutral-200 dark:bg-neutral-700 hover:bg-blue-500 hover:text-white"
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
data?.map((item) => renderItem(item))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -355,8 +375,9 @@ function Accounts() {
|
||||
}
|
||||
|
||||
function Core() {
|
||||
const { isLoading, data } = useQuery({
|
||||
queryKey: ["other-columns"],
|
||||
const { id } = Route.useParams();
|
||||
const { data } = useQuery({
|
||||
queryKey: ["core-columns"],
|
||||
queryFn: async () => {
|
||||
const systemPath = "resources/columns.json";
|
||||
const resourcePath = await resolveResource(systemPath);
|
||||
@@ -373,38 +394,56 @@ function Core() {
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex items-center justify-between px-2">
|
||||
<h3 className="font-semibold">Others</h3>
|
||||
<h3 className="font-semibold">Core</h3>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3">
|
||||
{isLoading ? (
|
||||
<div className="inline-flex items-center gap-1.5 text-sm">
|
||||
<Spinner className="size-4" />
|
||||
Loading...
|
||||
<div className="group flex flex-col rounded-xl overflow-hidden bg-white dark:bg-neutral-800/50 shadow-lg shadow-primary dark:ring-1 dark:ring-neutral-800">
|
||||
<div className="flex flex-col gap-2 p-2">
|
||||
<div className="px-3 flex items-center justify-between h-11 rounded-lg bg-neutral-100 dark:bg-neutral-800">
|
||||
<div className="text-sm font-medium">Newsfeed</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => LumeWindow.openNewsfeed(id)}
|
||||
className="h-6 w-16 inline-flex items-center justify-center gap-1 text-xs font-semibold rounded-full bg-neutral-200 dark:bg-neutral-700 hover:bg-blue-500 hover:text-white"
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
data.map((column) => (
|
||||
<div className="px-3 flex items-center justify-between h-11 rounded-lg bg-neutral-100 dark:bg-neutral-800">
|
||||
<div className="text-sm font-medium">Stories</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => LumeWindow.openStory(id)}
|
||||
className="h-6 w-16 inline-flex items-center justify-center gap-1 text-xs font-semibold rounded-full bg-neutral-200 dark:bg-neutral-700 hover:bg-blue-500 hover:text-white"
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
<div className="px-3 flex items-center justify-between h-11 rounded-lg bg-neutral-100 dark:bg-neutral-800">
|
||||
<div className="text-sm font-medium">Notification</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => LumeWindow.openNotification(id)}
|
||||
className="h-6 w-16 inline-flex items-center justify-center gap-1 text-xs font-semibold rounded-full bg-neutral-200 dark:bg-neutral-700 hover:bg-blue-500 hover:text-white"
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
{data?.map((column) => (
|
||||
<div
|
||||
key={column.label}
|
||||
className="group flex px-4 items-center justify-between h-16 rounded-xl bg-white dark:bg-black border-[.5px] border-neutral-300 dark:border-neutral-700"
|
||||
className="px-3 flex items-center justify-between h-11 rounded-lg bg-neutral-100 dark:bg-neutral-800"
|
||||
>
|
||||
<div className="text-sm">
|
||||
<div className="mb-px leading-tight font-semibold">
|
||||
{column.name}
|
||||
</div>
|
||||
<div className="leading-tight text-neutral-500 dark:text-neutral-400">
|
||||
{column.description}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-sm font-medium">{column.name}</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => LumeWindow.openColumn(column)}
|
||||
className="text-xs font-semibold w-16 h-7 hidden group-hover:inline-flex items-center justify-center rounded-full bg-neutral-200 hover:bg-blue-500 hover:text-white dark:bg-black/10"
|
||||
className="h-6 w-16 inline-flex items-center justify-center gap-1 text-xs font-semibold rounded-full bg-neutral-200 dark:bg-neutral-700 hover:bg-blue-500 hover:text-white"
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -52,7 +52,11 @@ function Screen() {
|
||||
|
||||
if (rootId) {
|
||||
if (reactions.has(rootId)) {
|
||||
reactions.get(rootId).push(event);
|
||||
const ev = reactions.get(rootId);
|
||||
|
||||
if (ev) {
|
||||
ev.push(event);
|
||||
}
|
||||
} else {
|
||||
reactions.set(rootId, [event]);
|
||||
}
|
||||
@@ -64,7 +68,11 @@ function Screen() {
|
||||
|
||||
if (rootId) {
|
||||
if (zaps.has(rootId)) {
|
||||
zaps.get(rootId).push(event);
|
||||
const ev = zaps.get(rootId);
|
||||
|
||||
if (ev) {
|
||||
ev.push(event);
|
||||
}
|
||||
} else {
|
||||
zaps.set(rootId, [event]);
|
||||
}
|
||||
|
||||
@@ -157,7 +157,7 @@ export class LumeEvent {
|
||||
}
|
||||
|
||||
static async build(event: NostrEvent) {
|
||||
const query = await commands.getEventMeta(event.content);
|
||||
const query = await commands.getMetaFromEvent(event.content);
|
||||
|
||||
if (query.status === "ok") {
|
||||
event.meta = query.data;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { commands } from "@/commands.gen";
|
||||
import type { NostrEvent } from "@/types";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { nip19 } from "nostr-tools";
|
||||
import { LumeEvent } from "./event";
|
||||
|
||||
export function useEvent(id: string, repost?: string) {
|
||||
@@ -10,7 +11,7 @@ export function useEvent(id: string, repost?: string) {
|
||||
try {
|
||||
if (repost?.length) {
|
||||
const nostrEvent: NostrEvent = JSON.parse(repost);
|
||||
const res = await commands.getEventMeta(nostrEvent.content);
|
||||
const res = await commands.getMetaFromEvent(nostrEvent.content);
|
||||
|
||||
if (res.status === "ok") {
|
||||
nostrEvent.meta = res.data;
|
||||
@@ -19,12 +20,17 @@ export function useEvent(id: string, repost?: string) {
|
||||
return new LumeEvent(nostrEvent);
|
||||
}
|
||||
|
||||
// Validate ID
|
||||
const normalizeId: string = id
|
||||
.replace("nostr:", "")
|
||||
.replace(/[^\w\s]/gi, "");
|
||||
let normalizedId = id.replace("nostr:", "").replace(/[^\w\s]/gi, "");
|
||||
|
||||
const res = await commands.getEvent(normalizeId);
|
||||
if (normalizedId.startsWith("nevent")) {
|
||||
const decoded = nip19.decode(normalizedId);
|
||||
|
||||
if (decoded.type === "nevent") {
|
||||
normalizedId = decoded.data.id;
|
||||
}
|
||||
}
|
||||
|
||||
const res = await commands.getEvent(normalizedId);
|
||||
|
||||
if (res.status === "ok") {
|
||||
const data = res.data;
|
||||
|
||||
@@ -16,17 +16,17 @@ export function useProfile(pubkey: string, embed?: string) {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
let normalizeId = pubkey.replace("nostr:", "").replace(/[^\w\s]/gi, "");
|
||||
let normalizedId = pubkey.replace("nostr:", "").replace(/[^\w\s]/gi, "");
|
||||
|
||||
if (normalizeId.startsWith("nprofile")) {
|
||||
const decoded = nip19.decode(normalizeId);
|
||||
if (normalizedId.startsWith("nprofile")) {
|
||||
const decoded = nip19.decode(normalizedId);
|
||||
|
||||
if (decoded.type === "nprofile") {
|
||||
normalizeId = decoded.data.pubkey;
|
||||
normalizedId = decoded.data.pubkey;
|
||||
}
|
||||
}
|
||||
|
||||
const query = await commands.getProfile(normalizeId);
|
||||
const query = await commands.getProfile(normalizedId);
|
||||
|
||||
if (query.status === "ok") {
|
||||
return JSON.parse(query.data) as Metadata;
|
||||
|
||||
@@ -11,13 +11,14 @@ export const LumeWindow = {
|
||||
column,
|
||||
});
|
||||
},
|
||||
openLaunchpad: async () => {
|
||||
openLaunchpad: async (account: string) => {
|
||||
await getCurrentWindow().emit("columns", {
|
||||
type: "add",
|
||||
column: {
|
||||
label: "launchpad",
|
||||
name: "Launchpad",
|
||||
url: "/columns/launchpad",
|
||||
url: `/columns/launchpad/${account}`,
|
||||
account,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user