refactor: app settings
This commit is contained in:
@@ -1,10 +1,15 @@
|
||||
import { commands } from "@/commands.gen";
|
||||
import { appSettings, cn, displayNpub } from "@/commons";
|
||||
import { cn, displayNpub } from "@/commons";
|
||||
import { RepostIcon, Spinner } from "@/components";
|
||||
import { settingsQueryOptions } from "@/routes/__root";
|
||||
import type { Metadata } from "@/types";
|
||||
import * as Tooltip from "@radix-ui/react-tooltip";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { useStore } from "@tanstack/react-store";
|
||||
import {
|
||||
useMutation,
|
||||
useQuery,
|
||||
useQueryClient,
|
||||
useSuspenseQuery,
|
||||
} from "@tanstack/react-query";
|
||||
import { Menu, MenuItem } from "@tauri-apps/api/menu";
|
||||
import { message } from "@tauri-apps/plugin-dialog";
|
||||
import { useCallback, useTransition } from "react";
|
||||
@@ -15,7 +20,7 @@ export function NoteRepost({
|
||||
smol = false,
|
||||
}: { label?: boolean; smol?: boolean }) {
|
||||
const event = useNoteContext();
|
||||
const visible = useStore(appSettings, (state) => state.display_repost_button);
|
||||
const settings = useSuspenseQuery(settingsQueryOptions);
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { isLoading, data: status } = useQuery({
|
||||
@@ -28,7 +33,7 @@ export function NoteRepost({
|
||||
return false;
|
||||
}
|
||||
},
|
||||
enabled: visible,
|
||||
enabled: settings.data.display_repost_button,
|
||||
refetchOnMount: false,
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
@@ -120,7 +125,7 @@ export function NoteRepost({
|
||||
});
|
||||
};
|
||||
|
||||
if (!visible) return null;
|
||||
if (!settings.data.display_repost_button) return null;
|
||||
|
||||
return (
|
||||
<Tooltip.Provider>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { appSettings, cn } from "@/commons";
|
||||
import { cn } from "@/commons";
|
||||
import { ZapIcon } from "@/components";
|
||||
import { settingsQueryOptions } from "@/routes/__root";
|
||||
import { LumeWindow } from "@/system";
|
||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||
import { useSearch } from "@tanstack/react-router";
|
||||
import { useStore } from "@tanstack/react-store";
|
||||
import { useNoteContext } from "../provider";
|
||||
|
||||
export function NoteZap({
|
||||
@@ -10,10 +11,10 @@ export function NoteZap({
|
||||
smol = false,
|
||||
}: { label?: boolean; smol?: boolean }) {
|
||||
const search = useSearch({ strict: false });
|
||||
const visible = useStore(appSettings, (state) => state.display_zap_button);
|
||||
const settings = useSuspenseQuery(settingsQueryOptions);
|
||||
const event = useNoteContext();
|
||||
|
||||
if (!visible) return null;
|
||||
if (!settings.data.display_zap_button) return null;
|
||||
|
||||
return (
|
||||
<button
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { appSettings, cn } from "@/commons";
|
||||
import { useStore } from "@tanstack/react-store";
|
||||
import { cn } from "@/commons";
|
||||
import { settingsQueryOptions } from "@/routes/__root";
|
||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||
import { nanoid } from "nanoid";
|
||||
import { type ReactNode, useMemo, useState } from "react";
|
||||
import reactStringReplace from "react-string-replace";
|
||||
@@ -21,14 +22,17 @@ export function NoteContent({
|
||||
className?: string;
|
||||
}) {
|
||||
const event = useNoteContext();
|
||||
const visible = useStore(appSettings, (state) => state.display_media);
|
||||
const settings = useSuspenseQuery(settingsQueryOptions);
|
||||
|
||||
const content = useMemo(() => {
|
||||
try {
|
||||
// Get parsed meta
|
||||
const { content, hashtags, events, mentions } = event.meta;
|
||||
|
||||
// Define rich content
|
||||
let richContent: ReactNode[] | string = visible ? content : event.content;
|
||||
let richContent: ReactNode[] | string = settings.data.display_media
|
||||
? content
|
||||
: event.content;
|
||||
|
||||
for (const hashtag of hashtags) {
|
||||
const regex = new RegExp(`(|^)${hashtag}\\b`, "g");
|
||||
@@ -103,7 +107,7 @@ export function NoteContent({
|
||||
>
|
||||
{content}
|
||||
</div>
|
||||
{visible ? (
|
||||
{settings.data.display_media ? (
|
||||
event.meta?.images.length ? (
|
||||
<Images urls={event.meta.images} />
|
||||
) : null
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { replyTime } from "@/commons";
|
||||
import { Note, Spinner } from "@/components";
|
||||
import { User } from "@/components/user";
|
||||
import { replyAt } from "@/commons";
|
||||
import { Note, Spinner, User } from "@/components";
|
||||
import { LumeWindow, useEvent } from "@/system";
|
||||
import { nip19 } from "nostr-tools";
|
||||
import { type ReactNode, memo, useMemo } from "react";
|
||||
@@ -52,7 +51,7 @@ export const MentionNote = memo(function MentionNote({
|
||||
</div>
|
||||
<div className="flex-1 flex items-center justify-between">
|
||||
<span className="text-sm text-neutral-500">
|
||||
{replyTime(event.created_at)}
|
||||
{replyAt(event.created_at)}
|
||||
</span>
|
||||
<div className="invisible group-hover:visible flex items-center justify-end">
|
||||
<button
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
import { appSettings } from "@/commons";
|
||||
import { useStore } from "@tanstack/react-store";
|
||||
import { settingsQueryOptions } from "@/routes/__root";
|
||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||
import { useMemo } from "react";
|
||||
|
||||
export function ImagePreview({ url }: { url: string }) {
|
||||
const [service, visible] = useStore(appSettings, (state) => [
|
||||
state.image_resize_service,
|
||||
state.display_media,
|
||||
]);
|
||||
const settings = useSuspenseQuery(settingsQueryOptions);
|
||||
|
||||
const imageUrl = useMemo(() => {
|
||||
if (service?.length) {
|
||||
const newUrl = `${service}?url=${url}&ll&af&default=1&n=-1`;
|
||||
if (settings.data.resize_service) {
|
||||
const newUrl = `https://wsrv.nl?url=${url}&ll&af&default=1&n=-1`;
|
||||
return newUrl;
|
||||
} else {
|
||||
return url;
|
||||
}
|
||||
}, [service]);
|
||||
}, [settings.data.resize_service]);
|
||||
|
||||
if (!visible) {
|
||||
if (!settings.data.display_media) {
|
||||
return (
|
||||
<a
|
||||
href={url}
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
import { appSettings, cn } from "@/commons";
|
||||
import { cn } from "@/commons";
|
||||
import { Spinner } from "@/components";
|
||||
import { settingsQueryOptions } from "@/routes/__root";
|
||||
import { ArrowLeft, ArrowRight } from "@phosphor-icons/react";
|
||||
import { useStore } from "@tanstack/react-store";
|
||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||
import { open } from "@tauri-apps/plugin-shell";
|
||||
import useEmblaCarousel from "embla-carousel-react";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
|
||||
export function Images({ urls }: { urls: string[] }) {
|
||||
const [slidesInView, setSlidesInView] = useState([]);
|
||||
const [slidesInView, setSlidesInView] = useState<number[]>([]);
|
||||
const [emblaRef, emblaApi] = useEmblaCarousel({
|
||||
dragFree: true,
|
||||
align: "start",
|
||||
watchSlides: false,
|
||||
});
|
||||
|
||||
const service = useStore(appSettings, (state) => state.image_resize_service);
|
||||
const settings = useSuspenseQuery(settingsQueryOptions);
|
||||
|
||||
const imageUrls = useMemo(() => {
|
||||
if (service?.length) {
|
||||
if (settings.data.resize_service) {
|
||||
let newUrls: string[];
|
||||
|
||||
if (urls.length === 1) {
|
||||
@@ -28,11 +29,12 @@ export function Images({ urls }: { urls: string[] }) {
|
||||
if (url.includes("bsky.network")) {
|
||||
return url;
|
||||
}
|
||||
return `${service}?url=${url}&ll&af&default=1&n=-1`;
|
||||
return `https://wsrv.nl?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`,
|
||||
(url) =>
|
||||
`https://wsrv.nl?url=${url}&w=480&h=640&ll&af&default=1&n=-1`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -40,7 +42,7 @@ export function Images({ urls }: { urls: string[] }) {
|
||||
} else {
|
||||
return urls;
|
||||
}
|
||||
}, [service]);
|
||||
}, [settings.data.resize_service]);
|
||||
|
||||
const scrollPrev = useCallback(() => {
|
||||
if (emblaApi) emblaApi.scrollPrev();
|
||||
@@ -50,23 +52,27 @@ export function Images({ urls }: { urls: string[] }) {
|
||||
if (emblaApi) emblaApi.scrollNext();
|
||||
}, [emblaApi]);
|
||||
|
||||
const updateSlidesInView = useCallback((emblaApi) => {
|
||||
const updateSlidesInView = useCallback(() => {
|
||||
setSlidesInView((slidesInView) => {
|
||||
if (slidesInView.length === emblaApi.slideNodes().length) {
|
||||
emblaApi.off("slidesInView", updateSlidesInView);
|
||||
if (slidesInView.length === emblaApi?.slideNodes().length) {
|
||||
emblaApi?.off("slidesInView", updateSlidesInView);
|
||||
}
|
||||
|
||||
const inView = emblaApi
|
||||
.slidesInView()
|
||||
?.slidesInView()
|
||||
.filter((index) => !slidesInView.includes(index));
|
||||
|
||||
return slidesInView.concat(inView);
|
||||
if (inView) {
|
||||
return slidesInView.concat(inView);
|
||||
} else {
|
||||
return slidesInView;
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
}, [emblaApi]);
|
||||
|
||||
useEffect(() => {
|
||||
if (emblaApi && urls.length > 1) {
|
||||
updateSlidesInView(emblaApi);
|
||||
updateSlidesInView();
|
||||
|
||||
emblaApi.on("slidesInView", updateSlidesInView);
|
||||
emblaApi.on("reInit", updateSlidesInView);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { appSettings } from "@/commons";
|
||||
import { useStore } from "@tanstack/react-store";
|
||||
import { settingsQueryOptions } from "@/routes/__root";
|
||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||
|
||||
export function VideoPreview({ url }: { url: string }) {
|
||||
const visible = useStore(appSettings, (state) => state.display_media);
|
||||
const settings = useSuspenseQuery(settingsQueryOptions);
|
||||
|
||||
if (!visible) {
|
||||
if (!settings.data.display_zap_button) {
|
||||
return (
|
||||
<a
|
||||
href={url}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { cn, replyTime } from "@/commons";
|
||||
import { Note } from "@/components/note";
|
||||
import { cn, replyAt } from "@/commons";
|
||||
import { Note, User } from "@/components";
|
||||
import { type LumeEvent, LumeWindow } from "@/system";
|
||||
import { CaretDown } from "@phosphor-icons/react";
|
||||
import { Link, useSearch } from "@tanstack/react-router";
|
||||
@@ -10,7 +10,6 @@ import { type ReactNode, memo, useCallback, useMemo } from "react";
|
||||
import reactStringReplace from "react-string-replace";
|
||||
import { Hashtag } from "./note/mentions/hashtag";
|
||||
import { MentionUser } from "./note/mentions/user";
|
||||
import { User } from "./user";
|
||||
|
||||
export const ReplyNote = memo(function ReplyNote({
|
||||
event,
|
||||
@@ -66,7 +65,7 @@ export const ReplyNote = memo(function ReplyNote({
|
||||
</div>
|
||||
<div className="flex-1 flex items-center justify-between">
|
||||
<span className="text-sm text-neutral-500">
|
||||
{replyTime(event.created_at)}
|
||||
{replyAt(event.created_at)}
|
||||
</span>
|
||||
<div className="flex items-center justify-end">
|
||||
<Note.Reply smol />
|
||||
@@ -147,7 +146,7 @@ function ChildReply({ event }: { event: LumeEvent }) {
|
||||
</div>
|
||||
<div className="flex-1 flex items-center justify-between">
|
||||
<span className="text-sm text-neutral-500">
|
||||
{replyTime(event.created_at)}
|
||||
{replyAt(event.created_at)}
|
||||
</span>
|
||||
<div className="invisible group-hover:visible flex items-center justify-end">
|
||||
<Note.Reply smol />
|
||||
|
||||
@@ -1,38 +1,36 @@
|
||||
import { appSettings, cn } from "@/commons";
|
||||
import { cn } from "@/commons";
|
||||
import { settingsQueryOptions } from "@/routes/__root";
|
||||
import * as Avatar from "@radix-ui/react-avatar";
|
||||
import { useStore } from "@tanstack/react-store";
|
||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||
import { minidenticon } from "minidenticons";
|
||||
import { useMemo } from "react";
|
||||
import { useUserContext } from "./provider";
|
||||
|
||||
export function UserAvatar({ className }: { className?: string }) {
|
||||
const [service, visible] = useStore(appSettings, (state) => [
|
||||
state.image_resize_service,
|
||||
state.display_avatar,
|
||||
]);
|
||||
const settings = useSuspenseQuery(settingsQueryOptions);
|
||||
|
||||
const user = useUserContext();
|
||||
|
||||
const picture = useMemo(() => {
|
||||
if (service?.length && user.profile?.picture?.length) {
|
||||
if (settings.data.resize_service && user?.profile?.picture?.length) {
|
||||
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}`;
|
||||
return `https://wsrv.nl?url=${user.profile?.picture}&w=100&h=100&n=-1&default=${user.profile?.picture}`;
|
||||
} else {
|
||||
return user.profile?.picture;
|
||||
return user?.profile?.picture;
|
||||
}
|
||||
}, [user.profile?.picture]);
|
||||
}, [user]);
|
||||
|
||||
const fallback = useMemo(
|
||||
() =>
|
||||
`data:image/svg+xml;utf8,${encodeURIComponent(
|
||||
minidenticon(user.pubkey, 60, 50),
|
||||
minidenticon(user ? user.pubkey : "lume", 60, 50),
|
||||
)}`,
|
||||
[user.pubkey],
|
||||
[user],
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -42,18 +40,19 @@ export function UserAvatar({ className }: { className?: string }) {
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{visible ? (
|
||||
{settings.data.display_avatar ? (
|
||||
<Avatar.Image
|
||||
src={picture}
|
||||
alt={user.pubkey}
|
||||
alt={user?.pubkey}
|
||||
decoding="async"
|
||||
onContextMenu={(e) => e.preventDefault()}
|
||||
className="w-full aspect-square object-cover outline-[.5px] outline-black/5 content-visibility-auto contain-intrinsic-size-[auto]"
|
||||
/>
|
||||
) : null}
|
||||
<Avatar.Fallback>
|
||||
<img
|
||||
src={fallback}
|
||||
alt={user.pubkey}
|
||||
alt={user?.pubkey}
|
||||
className="size-full bg-black dark:bg-white outline-[.5px] outline-black/5 content-visibility-auto contain-intrinsic-size-[auto]"
|
||||
/>
|
||||
</Avatar.Fallback>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { cn, formatCreatedAt } from "@/commons";
|
||||
import { cn, createdAt } from "@/commons";
|
||||
import { useMemo } from "react";
|
||||
|
||||
export function UserTime({
|
||||
@@ -8,11 +8,11 @@ export function UserTime({
|
||||
time: number;
|
||||
className?: string;
|
||||
}) {
|
||||
const createdAt = useMemo(() => formatCreatedAt(time), [time]);
|
||||
const displayCreatedAt = useMemo(() => createdAt(time), [time]);
|
||||
|
||||
return (
|
||||
<div className={cn("text-neutral-600 dark:text-neutral-400", className)}>
|
||||
{createdAt}
|
||||
{displayCreatedAt}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user