Skip to content

Commit 53e62ce

Browse files
committed
fix: prevent app crash in some cases
1 parent 4c28b48 commit 53e62ce

File tree

3 files changed

+121
-113
lines changed

3 files changed

+121
-113
lines changed

packages/ark/src/components/note/buttons/pin.tsx

Lines changed: 34 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,39 +4,42 @@ import * as Tooltip from "@radix-ui/react-tooltip";
44
import { useTranslation } from "react-i18next";
55
import { useColumnContext } from "../../column/provider";
66
import { useNoteContext } from "../provider";
7+
import { toast } from "sonner";
78

89
export function NotePin() {
9-
const event = useNoteContext();
10+
const { t } = useTranslation();
11+
const { addColumn } = useColumnContext();
12+
const event = useNoteContext();
1013

11-
const { t } = useTranslation();
12-
const { addColumn } = useColumnContext();
14+
const pin = async () => {
15+
if (!event) toast.error("Something is wrong!");
16+
await addColumn({
17+
kind: COL_TYPES.thread,
18+
title: "Thread",
19+
content: event?.id,
20+
});
21+
};
1322

14-
return (
15-
<Tooltip.Provider>
16-
<Tooltip.Root delayDuration={150}>
17-
<Tooltip.Trigger asChild>
18-
<button
19-
type="button"
20-
onClick={async () =>
21-
await addColumn({
22-
kind: COL_TYPES.thread,
23-
title: "Thread",
24-
content: event.id,
25-
})
26-
}
27-
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"
28-
>
29-
<PinIcon className="size-4" />
30-
{t("note.buttons.pin")}
31-
</button>
32-
</Tooltip.Trigger>
33-
<Tooltip.Portal>
34-
<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">
35-
{t("note.buttons.pinTooltip")}
36-
<Tooltip.Arrow className="fill-neutral-950 dark:fill-neutral-50" />
37-
</Tooltip.Content>
38-
</Tooltip.Portal>
39-
</Tooltip.Root>
40-
</Tooltip.Provider>
41-
);
23+
return (
24+
<Tooltip.Provider>
25+
<Tooltip.Root delayDuration={150}>
26+
<Tooltip.Trigger asChild>
27+
<button
28+
type="button"
29+
onClick={() => pin()}
30+
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"
31+
>
32+
<PinIcon className="size-4" />
33+
{t("note.buttons.pin")}
34+
</button>
35+
</Tooltip.Trigger>
36+
<Tooltip.Portal>
37+
<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">
38+
{t("note.buttons.pinTooltip")}
39+
<Tooltip.Arrow className="fill-neutral-950 dark:fill-neutral-50" />
40+
</Tooltip.Content>
41+
</Tooltip.Portal>
42+
</Tooltip.Root>
43+
</Tooltip.Provider>
44+
);
4245
}

packages/ark/src/components/note/preview/image.tsx

Lines changed: 48 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -3,59 +3,60 @@ import { downloadDir } from "@tauri-apps/api/path";
33
import { Window } from "@tauri-apps/api/window";
44
import { download } from "@tauri-apps/plugin-upload";
55
import { SyntheticEvent, useState } from "react";
6+
import { useNoteContext } from "../provider";
67

78
export function ImagePreview({ url }: { url: string }) {
8-
const [downloaded, setDownloaded] = useState(false);
9+
const event = useNoteContext();
10+
const [downloaded, setDownloaded] = useState(false);
911

10-
const downloadImage = async (e: { stopPropagation: () => void }) => {
11-
try {
12-
e.stopPropagation();
12+
const downloadImage = async (e: { stopPropagation: () => void }) => {
13+
try {
14+
e.stopPropagation();
1315

14-
const downloadDirPath = await downloadDir();
15-
const filename = url.substring(url.lastIndexOf("/") + 1);
16-
await download(url, `${downloadDirPath}/${filename}`);
16+
const downloadDirPath = await downloadDir();
17+
const filename = url.substring(url.lastIndexOf("/") + 1);
18+
await download(url, `${downloadDirPath}/${filename}`);
1719

18-
setDownloaded(true);
19-
} catch (e) {
20-
console.error(e);
21-
}
22-
};
20+
setDownloaded(true);
21+
} catch (e) {
22+
console.error(e);
23+
}
24+
};
2325

24-
const open = async () => {
25-
const name = new URL(url).pathname.split("/").pop();
26-
return new Window("image-viewer", {
27-
url,
28-
title: name,
29-
});
30-
};
26+
const open = async () => {
27+
return new Window(`image-viewer-${event.id}`, {
28+
url,
29+
title: "Image Viewer",
30+
});
31+
};
3132

32-
const fallback = (event: SyntheticEvent<HTMLImageElement, Event>) => {
33-
event.currentTarget.src = "/fallback-image.jpg";
34-
};
33+
const fallback = (event: SyntheticEvent<HTMLImageElement, Event>) => {
34+
event.currentTarget.src = "/fallback-image.jpg";
35+
};
3536

36-
return (
37-
// biome-ignore lint/a11y/useKeyWithClickEvents: <explanation>
38-
<div onClick={open} className="relative mt-1 mb-2.5 group">
39-
<img
40-
src={url}
41-
alt={url}
42-
loading="lazy"
43-
decoding="async"
44-
style={{ contentVisibility: "auto" }}
45-
onError={fallback}
46-
className="object-cover w-full h-auto border rounded-xl border-neutral-200/50 dark:border-neutral-800/50"
47-
/>
48-
<button
49-
type="button"
50-
onClick={(e) => downloadImage(e)}
51-
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"
52-
>
53-
{downloaded ? (
54-
<CheckCircleIcon className="size-5" />
55-
) : (
56-
<DownloadIcon className="size-5" />
57-
)}
58-
</button>
59-
</div>
60-
);
37+
return (
38+
// biome-ignore lint/a11y/useKeyWithClickEvents: <explanation>
39+
<div onClick={open} className="relative mt-1 mb-2.5 group">
40+
<img
41+
src={url}
42+
alt={url}
43+
loading="lazy"
44+
decoding="async"
45+
style={{ contentVisibility: "auto" }}
46+
onError={fallback}
47+
className="object-cover w-full h-auto border rounded-xl border-neutral-200/50 dark:border-neutral-800/50"
48+
/>
49+
<button
50+
type="button"
51+
onClick={(e) => downloadImage(e)}
52+
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"
53+
>
54+
{downloaded ? (
55+
<CheckCircleIcon className="size-5" />
56+
) : (
57+
<DownloadIcon className="size-5" />
58+
)}
59+
</button>
60+
</div>
61+
);
6162
}

packages/ark/src/components/note/primitives/thread.tsx

Lines changed: 39 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,41 +3,45 @@ import { useEvent } from "../../../hooks/useEvent";
33
import { User } from "../../user";
44

55
export function ThreadNote({ eventId }: { eventId: string }) {
6-
const { isLoading, data } = useEvent(eventId);
6+
const { isLoading, isError, data } = useEvent(eventId);
77

8-
if (isLoading) {
9-
return <div>Loading...</div>;
10-
}
8+
if (isLoading || !data) {
9+
return <div>Loading...</div>;
10+
}
1111

12-
return (
13-
<Note.Provider event={data}>
14-
<Note.Root className="flex flex-col rounded-xl bg-neutral-50 dark:bg-neutral-950">
15-
<div className="flex items-center justify-between px-3 h-16">
16-
<User.Provider pubkey={data.pubkey}>
17-
<User.Root className="flex h-16 items-center gap-3 flex-1">
18-
<User.Avatar className="size-10 shrink-0 rounded-lg object-cover ring-1 ring-neutral-200/50 dark:ring-neutral-800/50" />
19-
<div className="flex flex-1 flex-col">
20-
<User.Name className="font-semibold text-neutral-900 dark:text-neutral-100" />
21-
<div className="inline-flex items-center gap-2 text-sm text-neutral-600 dark:text-neutral-400">
22-
<User.Time time={data.created_at} />
23-
<span>·</span>
24-
<User.NIP05 pubkey={data.pubkey} />
25-
</div>
26-
</div>
27-
</User.Root>
28-
</User.Provider>
29-
<Note.Menu />
30-
</div>
31-
<Note.Thread className="mb-2" />
32-
<Note.Content className="min-w-0 px-3" />
33-
<div className="flex items-center justify-between px-3 h-14">
34-
<Note.Pin />
35-
<div className="inline-flex items-center gap-4">
36-
<Note.Repost />
37-
<Note.Zap />
38-
</div>
39-
</div>
40-
</Note.Root>
41-
</Note.Provider>
42-
);
12+
if (isError) {
13+
return <div>Error</div>;
14+
}
15+
16+
return (
17+
<Note.Provider event={data}>
18+
<Note.Root className="flex flex-col rounded-xl bg-neutral-50 dark:bg-neutral-950">
19+
<div className="flex items-center justify-between px-3 h-16">
20+
<User.Provider pubkey={data.pubkey}>
21+
<User.Root className="flex h-16 items-center gap-3 flex-1">
22+
<User.Avatar className="size-10 shrink-0 rounded-lg object-cover ring-1 ring-neutral-200/50 dark:ring-neutral-800/50" />
23+
<div className="flex flex-1 flex-col">
24+
<User.Name className="font-semibold text-neutral-900 dark:text-neutral-100" />
25+
<div className="inline-flex items-center gap-2 text-sm text-neutral-600 dark:text-neutral-400">
26+
<User.Time time={data.created_at} />
27+
<span>·</span>
28+
<User.NIP05 pubkey={data.pubkey} />
29+
</div>
30+
</div>
31+
</User.Root>
32+
</User.Provider>
33+
<Note.Menu />
34+
</div>
35+
<Note.Thread className="mb-2" />
36+
<Note.Content className="min-w-0 px-3" />
37+
<div className="flex items-center justify-between px-3 h-14">
38+
<Note.Pin />
39+
<div className="inline-flex items-center gap-4">
40+
<Note.Repost />
41+
<Note.Zap />
42+
</div>
43+
</div>
44+
</Note.Root>
45+
</Note.Provider>
46+
);
4347
}

0 commit comments

Comments
 (0)