fix: prevent app crash in some cases
This commit is contained in:
@@ -4,39 +4,42 @@ import * as Tooltip from "@radix-ui/react-tooltip";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useColumnContext } from "../../column/provider";
|
import { useColumnContext } from "../../column/provider";
|
||||||
import { useNoteContext } from "../provider";
|
import { useNoteContext } from "../provider";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
export function NotePin() {
|
export function NotePin() {
|
||||||
const event = useNoteContext();
|
const { t } = useTranslation();
|
||||||
|
const { addColumn } = useColumnContext();
|
||||||
|
const event = useNoteContext();
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const pin = async () => {
|
||||||
const { addColumn } = useColumnContext();
|
if (!event) toast.error("Something is wrong!");
|
||||||
|
await addColumn({
|
||||||
|
kind: COL_TYPES.thread,
|
||||||
|
title: "Thread",
|
||||||
|
content: event?.id,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip.Provider>
|
<Tooltip.Provider>
|
||||||
<Tooltip.Root delayDuration={150}>
|
<Tooltip.Root delayDuration={150}>
|
||||||
<Tooltip.Trigger asChild>
|
<Tooltip.Trigger asChild>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={async () =>
|
onClick={() => pin()}
|
||||||
await addColumn({
|
className="inline-flex items-center justify-center gap-2 pl-2 pr-3 text-sm font-medium rounded-full h-7 w-max bg-neutral-100 hover:bg-neutral-200 dark:hover:bg-neutral-800 dark:bg-neutral-900"
|
||||||
kind: COL_TYPES.thread,
|
>
|
||||||
title: "Thread",
|
<PinIcon className="size-4" />
|
||||||
content: event.id,
|
{t("note.buttons.pin")}
|
||||||
})
|
</button>
|
||||||
}
|
</Tooltip.Trigger>
|
||||||
className="inline-flex items-center justify-center gap-2 pl-2 pr-3 text-sm font-medium rounded-full h-7 w-max bg-neutral-100 hover:bg-neutral-200 dark:hover:bg-neutral-800 dark:bg-neutral-900"
|
<Tooltip.Portal>
|
||||||
>
|
<Tooltip.Content className="inline-flex h-7 select-none text-neutral-50 dark:text-neutral-950 items-center justify-center rounded-md bg-neutral-950 dark:bg-neutral-50 px-3.5 text-sm will-change-[transform,opacity] data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade">
|
||||||
<PinIcon className="size-4" />
|
{t("note.buttons.pinTooltip")}
|
||||||
{t("note.buttons.pin")}
|
<Tooltip.Arrow className="fill-neutral-950 dark:fill-neutral-50" />
|
||||||
</button>
|
</Tooltip.Content>
|
||||||
</Tooltip.Trigger>
|
</Tooltip.Portal>
|
||||||
<Tooltip.Portal>
|
</Tooltip.Root>
|
||||||
<Tooltip.Content className="inline-flex h-7 select-none text-neutral-50 dark:text-neutral-950 items-center justify-center rounded-md bg-neutral-950 dark:bg-neutral-50 px-3.5 text-sm will-change-[transform,opacity] data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade">
|
</Tooltip.Provider>
|
||||||
{t("note.buttons.pinTooltip")}
|
);
|
||||||
<Tooltip.Arrow className="fill-neutral-950 dark:fill-neutral-50" />
|
|
||||||
</Tooltip.Content>
|
|
||||||
</Tooltip.Portal>
|
|
||||||
</Tooltip.Root>
|
|
||||||
</Tooltip.Provider>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,59 +3,60 @@ import { downloadDir } from "@tauri-apps/api/path";
|
|||||||
import { Window } from "@tauri-apps/api/window";
|
import { Window } from "@tauri-apps/api/window";
|
||||||
import { download } from "@tauri-apps/plugin-upload";
|
import { download } from "@tauri-apps/plugin-upload";
|
||||||
import { SyntheticEvent, useState } from "react";
|
import { SyntheticEvent, useState } from "react";
|
||||||
|
import { useNoteContext } from "../provider";
|
||||||
|
|
||||||
export function ImagePreview({ url }: { url: string }) {
|
export function ImagePreview({ url }: { url: string }) {
|
||||||
const [downloaded, setDownloaded] = useState(false);
|
const event = useNoteContext();
|
||||||
|
const [downloaded, setDownloaded] = useState(false);
|
||||||
|
|
||||||
const downloadImage = async (e: { stopPropagation: () => void }) => {
|
const downloadImage = async (e: { stopPropagation: () => void }) => {
|
||||||
try {
|
try {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
const downloadDirPath = await downloadDir();
|
const downloadDirPath = await downloadDir();
|
||||||
const filename = url.substring(url.lastIndexOf("/") + 1);
|
const filename = url.substring(url.lastIndexOf("/") + 1);
|
||||||
await download(url, `${downloadDirPath}/${filename}`);
|
await download(url, `${downloadDirPath}/${filename}`);
|
||||||
|
|
||||||
setDownloaded(true);
|
setDownloaded(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const open = async () => {
|
const open = async () => {
|
||||||
const name = new URL(url).pathname.split("/").pop();
|
return new Window(`image-viewer-${event.id}`, {
|
||||||
return new Window("image-viewer", {
|
url,
|
||||||
url,
|
title: "Image Viewer",
|
||||||
title: name,
|
});
|
||||||
});
|
};
|
||||||
};
|
|
||||||
|
|
||||||
const fallback = (event: SyntheticEvent<HTMLImageElement, Event>) => {
|
const fallback = (event: SyntheticEvent<HTMLImageElement, Event>) => {
|
||||||
event.currentTarget.src = "/fallback-image.jpg";
|
event.currentTarget.src = "/fallback-image.jpg";
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// biome-ignore lint/a11y/useKeyWithClickEvents: <explanation>
|
// biome-ignore lint/a11y/useKeyWithClickEvents: <explanation>
|
||||||
<div onClick={open} className="relative mt-1 mb-2.5 group">
|
<div onClick={open} className="relative mt-1 mb-2.5 group">
|
||||||
<img
|
<img
|
||||||
src={url}
|
src={url}
|
||||||
alt={url}
|
alt={url}
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
decoding="async"
|
decoding="async"
|
||||||
style={{ contentVisibility: "auto" }}
|
style={{ contentVisibility: "auto" }}
|
||||||
onError={fallback}
|
onError={fallback}
|
||||||
className="object-cover w-full h-auto border rounded-xl border-neutral-200/50 dark:border-neutral-800/50"
|
className="object-cover w-full h-auto border rounded-xl border-neutral-200/50 dark:border-neutral-800/50"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={(e) => downloadImage(e)}
|
onClick={(e) => downloadImage(e)}
|
||||||
className="absolute z-10 items-center justify-center hidden size-10 bg-white/10 text-black/70 backdrop-blur-xl rounded-lg right-2 top-2 group-hover:inline-flex hover:bg-blue-500 hover:text-white"
|
className="absolute z-10 items-center justify-center hidden size-10 bg-white/10 text-black/70 backdrop-blur-xl rounded-lg right-2 top-2 group-hover:inline-flex hover:bg-blue-500 hover:text-white"
|
||||||
>
|
>
|
||||||
{downloaded ? (
|
{downloaded ? (
|
||||||
<CheckCircleIcon className="size-5" />
|
<CheckCircleIcon className="size-5" />
|
||||||
) : (
|
) : (
|
||||||
<DownloadIcon className="size-5" />
|
<DownloadIcon className="size-5" />
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,41 +3,45 @@ import { useEvent } from "../../../hooks/useEvent";
|
|||||||
import { User } from "../../user";
|
import { User } from "../../user";
|
||||||
|
|
||||||
export function ThreadNote({ eventId }: { eventId: string }) {
|
export function ThreadNote({ eventId }: { eventId: string }) {
|
||||||
const { isLoading, data } = useEvent(eventId);
|
const { isLoading, isError, data } = useEvent(eventId);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading || !data) {
|
||||||
return <div>Loading...</div>;
|
return <div>Loading...</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
if (isError) {
|
||||||
<Note.Provider event={data}>
|
return <div>Error</div>;
|
||||||
<Note.Root className="flex flex-col rounded-xl bg-neutral-50 dark:bg-neutral-950">
|
}
|
||||||
<div className="flex items-center justify-between px-3 h-16">
|
|
||||||
<User.Provider pubkey={data.pubkey}>
|
return (
|
||||||
<User.Root className="flex h-16 items-center gap-3 flex-1">
|
<Note.Provider event={data}>
|
||||||
<User.Avatar className="size-10 shrink-0 rounded-lg object-cover ring-1 ring-neutral-200/50 dark:ring-neutral-800/50" />
|
<Note.Root className="flex flex-col rounded-xl bg-neutral-50 dark:bg-neutral-950">
|
||||||
<div className="flex flex-1 flex-col">
|
<div className="flex items-center justify-between px-3 h-16">
|
||||||
<User.Name className="font-semibold text-neutral-900 dark:text-neutral-100" />
|
<User.Provider pubkey={data.pubkey}>
|
||||||
<div className="inline-flex items-center gap-2 text-sm text-neutral-600 dark:text-neutral-400">
|
<User.Root className="flex h-16 items-center gap-3 flex-1">
|
||||||
<User.Time time={data.created_at} />
|
<User.Avatar className="size-10 shrink-0 rounded-lg object-cover ring-1 ring-neutral-200/50 dark:ring-neutral-800/50" />
|
||||||
<span>·</span>
|
<div className="flex flex-1 flex-col">
|
||||||
<User.NIP05 pubkey={data.pubkey} />
|
<User.Name className="font-semibold text-neutral-900 dark:text-neutral-100" />
|
||||||
</div>
|
<div className="inline-flex items-center gap-2 text-sm text-neutral-600 dark:text-neutral-400">
|
||||||
</div>
|
<User.Time time={data.created_at} />
|
||||||
</User.Root>
|
<span>·</span>
|
||||||
</User.Provider>
|
<User.NIP05 pubkey={data.pubkey} />
|
||||||
<Note.Menu />
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Note.Thread className="mb-2" />
|
</User.Root>
|
||||||
<Note.Content className="min-w-0 px-3" />
|
</User.Provider>
|
||||||
<div className="flex items-center justify-between px-3 h-14">
|
<Note.Menu />
|
||||||
<Note.Pin />
|
</div>
|
||||||
<div className="inline-flex items-center gap-4">
|
<Note.Thread className="mb-2" />
|
||||||
<Note.Repost />
|
<Note.Content className="min-w-0 px-3" />
|
||||||
<Note.Zap />
|
<div className="flex items-center justify-between px-3 h-14">
|
||||||
</div>
|
<Note.Pin />
|
||||||
</div>
|
<div className="inline-flex items-center gap-4">
|
||||||
</Note.Root>
|
<Note.Repost />
|
||||||
</Note.Provider>
|
<Note.Zap />
|
||||||
);
|
</div>
|
||||||
|
</div>
|
||||||
|
</Note.Root>
|
||||||
|
</Note.Provider>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user