final design (#184)

* feat: redesign

* feat: update other columns to new design

* chore: small fixes

* fix: better manage external webview

* feat: redesign note

* feat: update ui

* chore: update

* chore: update

* chore: polish ui

* chore: update auth ui

* feat: finalize note design

* chore: small fixes

* feat: add window management in rust

* chore: format

* feat: update ui for event screen

* feat: update event screen

* feat: final
This commit is contained in:
雨宮蓮
2024-05-03 15:15:48 +07:00
committed by GitHub
parent 61d1f095d4
commit a4aef25adb
250 changed files with 9359 additions and 9234 deletions
@@ -1,71 +0,0 @@
import { useEvent } from "@lume/ark";
import { Box, Container, Note, Spinner, User } from "@lume/ui";
import { createLazyFileRoute } from "@tanstack/react-router";
import { ReplyList } from "./-components/replyList";
import { WindowVirtualizer } from "virtua";
import { type Event } from "@lume/types";
export const Route = createLazyFileRoute("/events/$eventId")({
component: Event,
});
function Event() {
const { eventId } = Route.useParams();
const { isLoading, isError, data } = useEvent(eventId);
if (isLoading) {
return (
<div className="flex h-full w-full items-center justify-center">
<Spinner className="size-5 animate-spin" />
</div>
);
}
if (isError) {
<div className="flex h-full w-full items-center justify-center">
<p>Not found.</p>
</div>;
}
return (
<Container withDrag>
<Box className="px-3 pt-3 scrollbar-none">
<WindowVirtualizer>
<MainNote data={data} />
{data ? <ReplyList eventId={eventId} /> : null}
</WindowVirtualizer>
</Box>
</Container>
);
}
function MainNote({ data }: { data: Event }) {
return (
<Note.Provider event={data}>
<Note.Root className="flex flex-col pb-3">
<User.Provider pubkey={data.pubkey}>
<User.Root className="mb-3 flex flex-1 items-center gap-3">
<User.Avatar className="size-11 shrink-0 rounded-full object-cover ring-1 ring-neutral-200/50 dark:ring-neutral-800/50" />
<div className="flex flex-1 flex-col">
<User.Name className="font-semibold text-neutral-900 dark:text-neutral-100" />
<div className="inline-flex items-center gap-2 text-sm text-neutral-600 dark:text-neutral-400">
<User.Time time={data.created_at} />
<span>·</span>
<User.NIP05 />
</div>
</div>
</User.Root>
</User.Provider>
<Note.Thread className="mb-2" />
<Note.Content className="min-w-0" />
<div className="mt-4 flex items-center justify-between">
<div className="-ml-1 inline-flex items-center gap-4">
<Note.Repost />
<Note.Zap />
</div>
<Note.Menu />
</div>
</Note.Root>
</Note.Provider>
);
}
@@ -0,0 +1,71 @@
import { useEvent } from "@lume/ark";
import type { Event } from "@lume/types";
import { Box, Container, Note, Spinner } from "@lume/ui";
import { createFileRoute } from "@tanstack/react-router";
import { WindowVirtualizer } from "virtua";
import { ReplyList } from "./-components/replyList";
export const Route = createFileRoute("/events/$eventId")({
beforeLoad: async ({ context }) => {
const ark = context.ark;
const settings = await ark.get_settings();
return { settings };
},
component: Screen,
});
function Screen() {
const { eventId } = Route.useParams();
const { isLoading, isError, data } = useEvent(eventId);
if (isLoading) {
return (
<div className="flex h-full w-full items-center justify-center">
<Spinner className="size-5" />
</div>
);
}
if (isError) {
<div className="flex h-full w-full items-center justify-center">
<p>Not found.</p>
</div>;
}
return (
<Container withDrag>
<Box className="scrollbar-none">
<WindowVirtualizer>
<MainNote data={data} />
{data ? (
<ReplyList eventId={eventId} />
) : (
<div className="flex h-full w-full items-center justify-center">
<Spinner className="size-5" />
</div>
)}
</WindowVirtualizer>
</Box>
</Container>
);
}
function MainNote({ data }: { data: Event }) {
return (
<Note.Provider event={data}>
<Note.Root>
<div className="px-3 h-14 flex items-center justify-between">
<Note.User />
<Note.Menu />
</div>
<Note.ContentLarge className="px-3" />
<div className="mt-4 h-11 gap-2 flex items-center justify-end px-3">
<Note.Reply large />
<Note.Repost large />
<Note.Zap large />
</div>
</Note.Root>
</Note.Provider>
);
}
@@ -1,47 +1,36 @@
import { EventWithReplies } from "@lume/types";
import { cn } from "@lume/utils";
import type { EventWithReplies } from "@lume/types";
import { Note, User } from "@lume/ui";
import { cn } from "@lume/utils";
import { SubReply } from "./subReply";
export function Reply({ event }: { event: EventWithReplies }) {
return (
<Note.Provider event={event}>
<Note.Root className="border-t border-neutral-100 pt-3 dark:border-neutral-900">
<User.Provider pubkey={event.pubkey}>
<User.Root className="mb-2 flex items-center justify-between">
<div className="inline-flex gap-2">
<User.Avatar className="size-6 rounded-full" />
<div className="inline-flex items-center gap-2">
<User.Name className="font-semibold" />
<User.NIP05 className="text-base lowercase text-neutral-600 dark:text-neutral-400" />
</div>
</div>
<User.Time time={event.created_at} />
</User.Root>
</User.Provider>
<Note.Content />
<div className="mt-4 flex items-center justify-between">
<div className="-ml-1 inline-flex items-center gap-4">
<Note.Reply />
<Note.Repost />
<Note.Zap />
</div>
<Note.Menu />
</div>
<div
className={cn(
event.replies?.length > 0
? "my-3 mt-6 flex flex-col gap-3 divide-y divide-neutral-100 border-l-2 border-neutral-100 pl-6 dark:divide-neutral-900 dark:border-neutral-900"
: "",
)}
>
{event.replies?.length > 0
? event.replies?.map((childEvent) => (
<SubReply key={childEvent.id} event={childEvent} />
))
: null}
</div>
</Note.Root>
</Note.Provider>
);
return (
<Note.Provider event={event}>
<Note.Root className="border-t border-neutral-100 dark:border-neutral-900">
<div className="px-3 h-14 flex items-center justify-between">
<Note.User />
<Note.Menu />
</div>
<Note.ContentLarge className="px-3" />
<div className="mt-3 flex items-center gap-4 px-3 h-14">
<Note.Reply />
<Note.Repost />
<Note.Zap />
</div>
<div
className={cn(
event.replies?.length > 0
? "py-2 pl-3 flex flex-col gap-3 divide-y divide-neutral-100 bg-neutral-50 dark:bg-white/5 border-l-2 border-blue-500 dark:divide-neutral-900"
: "",
)}
>
{event.replies?.length > 0
? event.replies?.map((childEvent) => (
<SubReply key={childEvent.id} event={childEvent} />
))
: null}
</div>
</Note.Root>
</Note.Provider>
);
}
@@ -1,48 +1,51 @@
import type { EventWithReplies } from "@lume/types";
import { Spinner } from "@lume/ui";
import { cn } from "@lume/utils";
import { useRouteContext } from "@tanstack/react-router";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { EventWithReplies } from "@lume/types";
import { Reply } from "./reply";
import { useRouteContext } from "@tanstack/react-router";
import { Spinner } from "@lume/ui";
export function ReplyList({
eventId,
className,
eventId,
className,
}: {
eventId: string;
className?: string;
eventId: string;
className?: string;
}) {
const { ark } = useRouteContext({ strict: false });
const [t] = useTranslation();
const [data, setData] = useState<null | EventWithReplies[]>(null);
const { ark } = useRouteContext({ strict: false });
const [t] = useTranslation();
const [data, setData] = useState<null | EventWithReplies[]>(null);
useEffect(() => {
async function getReplies() {
const events = await ark.get_event_thread(eventId);
setData(events);
}
getReplies();
}, [eventId]);
useEffect(() => {
async function getReplies() {
const events = await ark.get_event_thread(eventId);
setData(events);
}
getReplies();
}, [eventId]);
return (
<div className={cn("flex flex-col gap-3", className)}>
{!data ? (
<div className="mt-4 flex h-16 items-center justify-center p-3">
<Spinner className="size-5" />
</div>
) : data.length === 0 ? (
<div className="mt-4 flex w-full items-center justify-center">
<div className="flex flex-col items-center justify-center gap-2 py-6">
<h3 className="text-3xl">👋</h3>
<p className="leading-none text-neutral-600 dark:text-neutral-400">
{t("note.reply.empty")}
</p>
</div>
</div>
) : (
data.map((event) => <Reply key={event.id} event={event} />)
)}
</div>
);
return (
<div className={cn("flex flex-col", className)}>
<div className="h-11 flex px-3 items-center text-sm font-semibold text-neutral-700 dark:text-neutral-300 border-t border-neutral-100 dark:border-neutral-900">
Replies ({data?.length ?? 0})
</div>
{!data ? (
<div className="flex h-16 items-center justify-center p-3">
<Spinner className="size-5" />
</div>
) : data.length === 0 ? (
<div className="flex w-full items-center justify-center">
<div className="flex flex-col items-center justify-center gap-2 py-6">
<h3 className="text-3xl">👋</h3>
<p className="leading-none text-neutral-600 dark:text-neutral-400">
{t("note.reply.empty")}
</p>
</div>
</div>
) : (
data.map((event) => <Reply key={event.id} event={event} />)
)}
</div>
);
}
@@ -1,32 +1,21 @@
import { Event } from "@lume/types";
import { Note, User } from "@lume/ui";
import type { Event } from "@lume/types";
import { Note } from "@lume/ui";
export function SubReply({ event }: { event: Event; rootEventId?: string }) {
return (
<Note.Provider event={event}>
<Note.Root className="pt-3">
<User.Provider pubkey={event.pubkey}>
<User.Root className="mb-2 flex items-center justify-between">
<div className="inline-flex gap-2">
<User.Avatar className="size-6 rounded-full" />
<div className="inline-flex items-center gap-2">
<User.Name className="font-semibold" />
<User.NIP05 className="text-base lowercase text-neutral-600 dark:text-neutral-400" />
</div>
</div>
<User.Time time={event.created_at} />
</User.Root>
</User.Provider>
<Note.Content />
<div className="mt-4 flex items-center justify-between">
<div className="-ml-1 inline-flex items-center gap-4">
<Note.Reply />
<Note.Repost />
<Note.Zap />
</div>
<Note.Menu />
</div>
</Note.Root>
</Note.Provider>
);
return (
<Note.Provider event={event}>
<Note.Root>
<div className="px-3 h-14 flex items-center justify-between">
<Note.User />
<Note.Menu />
</div>
<Note.ContentLarge className="px-3" />
<div className="mt-3 flex items-center gap-4 px-3">
<Note.Reply />
<Note.Repost />
<Note.Zap />
</div>
</Note.Root>
</Note.Provider>
);
}