Skip to content

Commit 5362e98

Browse files
[FR-57] Fix type errors (#14)
* fix type errors and add eslint rule * fix more type errors and add ThreadStarterMessage message type * fix types where possible * make requested changes * fix merge errors
1 parent 6dcdd1a commit 5362e98

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+728
-250
lines changed

.eslintrc.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"no-return-await": "error",
3131
"no-var": "error",
3232
"prefer-const": "error",
33-
"yoda": "error"
33+
"yoda": "error",
34+
"@typescript-eslint/consistent-type-imports": "error"
3435
}
3536
}

package.json

+7-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
"build": "vite build",
1313
"storybook": "storybook dev -p 6006",
1414
"build-storybook": "storybook build",
15-
"prepublishOnly": "pnpm build"
15+
"prepublishOnly": "pnpm build",
16+
"lint": "eslint --ext .ts,.tsx src",
17+
"typeCheck": "tsc"
1618
},
1719
"repository": {
1820
"type": "git",
@@ -29,7 +31,7 @@
2931
"@stitches/react": "^1.3.1-1",
3032
"autobind-decorator": "^2.4.0",
3133
"color": "^4.2.3",
32-
"discord-api-types": "^0.37.48",
34+
"discord-api-types": "^0.37.52",
3335
"filesize": "^10.0.6",
3436
"highlight.js": "^11.7.0",
3537
"lodash": "^4.17.21",
@@ -60,8 +62,11 @@
6062
"@storybook/react-vite": "^7.0.27",
6163
"@storybook/testing-library": "^0.2.0",
6264
"@storybook/theming": "^7.0.27",
65+
"@types/lodash": "^4.14.197",
66+
"@types/memoizee": "^0.4.8",
6367
"@types/node": "^20.3.2",
6468
"@types/react": "^18.0.26",
69+
"@types/react-lazy-load-image-component": "^1.5.3",
6570
"@types/storybook__react": "^5.2.1",
6671
"@typescript-eslint/eslint-plugin": "^5.59.9",
6772
"@typescript-eslint/parser": "^5.59.9",

pnpm-lock.yaml

+27-7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/ChatTag/index.tsx

+13-6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import Tooltip from "../Tooltip";
22
import React from "react";
33
import * as Styles from "./style";
4-
import { APIUser } from "discord-api-types/v10";
4+
import type { APIUser } from "discord-api-types/v10";
55
import { useConfig } from "../core/ConfigContext";
66

7+
// todo: support custom
78
const verified = (
89
<Tooltip placement="top" overlay="Verified Bot">
910
<Styles.VerifiedBot
@@ -21,13 +22,19 @@ const verified = (
2122
</Tooltip>
2223
);
2324

25+
function isVerifiedBot(flags?: number) {
26+
const FLAG_VERIFIED = 1 << 16;
27+
28+
return flags !== undefined && (flags & FLAG_VERIFIED) !== 0;
29+
}
30+
2431
interface TagProps {
2532
author: APIUser;
26-
crosspost: boolean;
27-
referenceGuild: string;
33+
crossPost: boolean;
34+
referenceGuild: string | undefined;
2835
}
2936

30-
function ChatTag({ author, crosspost, referenceGuild }: TagProps) {
37+
function ChatTag({ author, crossPost, referenceGuild }: TagProps) {
3138
const { chatBadge: ChatBadge } = useConfig();
3239

3340
if (ChatBadge !== undefined) {
@@ -42,9 +49,9 @@ function ChatTag({ author, crosspost, referenceGuild }: TagProps) {
4249
<Styles.Tag className="verified system">{verified} SYSTEM</Styles.Tag>
4350
);
4451

45-
if (crosspost) return <Styles.Tag className="server">SERVER</Styles.Tag>;
52+
if (crossPost) return <Styles.Tag className="server">SERVER</Styles.Tag>;
4653

47-
if (author.flags & (1 << 16))
54+
if (isVerifiedBot(author.flags))
4855
return <Styles.Tag className="verified bot">{verified} BOT</Styles.Tag>;
4956

5057
return <Styles.Tag className="bot">BOT</Styles.Tag>;

src/Content/Attachment/ImageAttachment.tsx

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
11
import useSize from "./useSize";
22
import React from "react";
33
import * as Styles from "./style";
4-
import { APIAttachment } from "discord-api-types/v10";
4+
import type { APIAttachment } from "discord-api-types/v10";
55

66
interface ImageAttachmentProps {
77
attachment: APIAttachment;
88
}
99

1010
function ImageAttachment(props: ImageAttachmentProps) {
11+
if (!props.attachment.width || !props.attachment.height) {
12+
// todo: dev mode only
13+
console.error(
14+
"ImageAttachment: attachment has no width or height",
15+
props.attachment
16+
);
17+
return null;
18+
}
19+
1120
const { width, height } = useSize(
1221
props.attachment.width,
1322
props.attachment.height

src/Content/Attachment/VideoAttachment.tsx

+60-22
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,38 @@ import useSize from "./useSize";
1010
import * as Styles from "./style";
1111
import Tooltip from "../../Tooltip";
1212
import SvgFromUrl from "../../SvgFromUrl";
13-
import { APIAttachment, APIEmbed } from "discord-api-types/v10";
13+
import type { APIAttachment, APIEmbed } from "discord-api-types/v10";
14+
15+
export type Attachment = APIAttachment & { width: number; height: number };
16+
type Embed = APIEmbed & {
17+
width: number;
18+
height: number;
19+
video: Required<APIEmbed["video"]>;
20+
};
1421

1522
interface VideoAttachmentProps {
1623
attachmentOrEmbed:
17-
| APIAttachment
18-
| APIEmbed
24+
| Attachment
25+
| Embed
1926
| {
2027
url: string;
2128
width: number;
2229
height: number;
2330
};
2431
}
2532

33+
function checkWhetherVideoEmbed(
34+
attachmentOrEmbed: VideoAttachmentProps["attachmentOrEmbed"]
35+
): attachmentOrEmbed is Embed {
36+
return "video" in attachmentOrEmbed;
37+
}
38+
39+
function isValidEmbedVideo(
40+
embedVideo: APIEmbed["video"] | null
41+
): embedVideo is Exclude<APIEmbed["video"], null | undefined> {
42+
return embedVideo !== null;
43+
}
44+
2645
function VideoAttachment(props: VideoAttachmentProps) {
2746
const [hasPlayedOnceBefore, setHasPlayedOnceBefore] = useState(false);
2847
const [paused, setPaused] = useState(true);
@@ -32,10 +51,12 @@ function VideoAttachment(props: VideoAttachmentProps) {
3251
const [isFullscreen, setIsFullscreen] = useState(false);
3352
const [isSeeking, setIsSeeking] = useState(false);
3453

35-
const videoRef = useRef(null);
36-
const attachmentRef = useRef(null);
54+
const videoRef = useRef<HTMLVideoElement>(null);
55+
const attachmentRef = useRef<HTMLDivElement>(null);
3756

3857
const durationPlayedHumanized = useMemo(() => {
58+
if (videoRef.current === null) return "0:00/0:00";
59+
3960
const minutesPassed = Math.floor(videoRef.current?.currentTime / 60);
4061
const secondsPassedRaw = Math.floor(videoRef.current?.currentTime % 60);
4162
const secondsPassed =
@@ -67,33 +88,38 @@ function VideoAttachment(props: VideoAttachmentProps) {
6788
);
6889
}, []);
6990

70-
const { width: extractedWidth, height: extractedHeight } =
71-
"video" in props.attachmentOrEmbed
72-
? props.attachmentOrEmbed.video
73-
: (props.attachmentOrEmbed as APIAttachment);
91+
if (
92+
checkWhetherVideoEmbed(props.attachmentOrEmbed) &&
93+
!isValidEmbedVideo(props.attachmentOrEmbed)
94+
) {
95+
console.error("Video embed has no video property", props.attachmentOrEmbed);
96+
return null;
97+
}
7498

75-
const { width, height } = useSize(
76-
extractedWidth,
77-
extractedHeight,
78-
isFullscreen
79-
);
99+
const { width: extractedWidth, height: extractedHeight } =
100+
checkWhetherVideoEmbed(props.attachmentOrEmbed)
101+
? (props.attachmentOrEmbed.video as Exclude<
102+
Embed["video"],
103+
null | undefined
104+
>)
105+
: props.attachmentOrEmbed;
80106

81107
function fullScreenChange() {
82108
setIsFullscreen(document.fullscreenElement !== null);
83109
}
84-
85-
function seekVideo(e, overrideSeeking?: boolean) {
110+
function seekVideo(
111+
e: React.MouseEvent<HTMLDivElement>,
112+
overrideSeeking?: boolean
113+
) {
86114
if (videoRef.current === null || (!isSeeking && !overrideSeeking)) return;
115+
const rect = e.currentTarget.getBoundingClientRect();
87116

88-
const rect = e.target.getBoundingClientRect();
89117
const x = e.clientX - rect.left;
90-
91118
const duration = videoRef.current?.duration;
92-
if (duration === undefined) return;
93119

120+
if (duration === undefined) return;
94121
videoRef.current.currentTime = (x / rect.width) * duration;
95122
}
96-
97123
useEffect(() => {
98124
document.addEventListener("fullscreenchange", fullScreenChange);
99125

@@ -102,6 +128,14 @@ function VideoAttachment(props: VideoAttachmentProps) {
102128
};
103129
}, []);
104130

131+
const { width, height } = useSize(
132+
extractedWidth,
133+
extractedHeight,
134+
isFullscreen
135+
);
136+
137+
if (width === undefined || height === undefined) return null;
138+
105139
return (
106140
<Styles.VideoAttachmentContainer
107141
style={{ width, height }}
@@ -168,7 +202,8 @@ function VideoAttachment(props: VideoAttachmentProps) {
168202
<Styles.ProgressBarFill
169203
style={{
170204
width:
171-
(videoRef.current?.currentTime / videoRef.current?.duration) *
205+
((videoRef.current?.currentTime ?? 0) /
206+
(videoRef.current?.duration ?? 1)) *
172207
100 +
173208
"%",
174209
}}
@@ -180,7 +215,10 @@ function VideoAttachment(props: VideoAttachmentProps) {
180215
height={14}
181216
svg="IconFullscreen"
182217
onClick={() => {
183-
if (document.fullscreenElement === null)
218+
if (
219+
attachmentRef.current !== null &&
220+
document.fullscreenElement === null
221+
)
184222
attachmentRef.current?.requestFullscreen();
185223
else void document.exitFullscreen();
186224
}}

src/Content/Attachment/index.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import ImageAttachment from "./ImageAttachment";
44
import GenericAttachment from "./GenericAttachment";
55
import * as Styles from "./style";
66

7-
import { APIAttachment } from "discord-api-types/v10";
7+
import type { APIAttachment } from "discord-api-types/v10";
88

99
export interface AttachmentProps {
1010
attachment: APIAttachment;
@@ -13,6 +13,7 @@ export interface AttachmentProps {
1313
function AttachmentBase(props: AttachmentProps) {
1414
if (props.attachment.width && props.attachment.height) {
1515
if (/\.(?:mp4|webm|mov)$/.test(props.attachment.filename))
16+
// @ts-expect-error TS2322 Type error is not applicable here
1617
return <VideoAttachment attachmentOrEmbed={props.attachment} />;
1718

1819
return <ImageAttachment attachment={props.attachment} />;

src/Content/Embed/EmbedVideo.tsx

+10-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as Styles from "./style";
2-
import React, { ReactNode, useState } from "react";
3-
import { APIEmbedThumbnail, APIEmbedVideo } from "discord-api-types/v10";
2+
import type { ReactNode } from "react";
3+
import React, { useState } from "react";
4+
import type { APIEmbedThumbnail, APIEmbedVideo } from "discord-api-types/v10";
45
import useSize from "../Attachment/useSize";
56
import { getSvgUrl } from "../../core/svgs";
67
import VideoAttachment from "../Attachment/VideoAttachment";
@@ -51,7 +52,8 @@ function ThumbnailWrapper({
5152
);
5253
}
5354

54-
interface EmbedVideoProps extends Pick<APIEmbedVideo, "width" | "height"> {
55+
interface EmbedVideoProps
56+
extends Required<Pick<APIEmbedVideo, "width" | "height">> {
5557
thumbnail?: APIEmbedThumbnail["url"];
5658
url: APIEmbedVideo["url"] | undefined;
5759
proxyUrl: APIEmbedVideo["proxy_url"] | undefined;
@@ -75,6 +77,11 @@ function EmbedVideo(props: EmbedVideoProps) {
7577
</ThumbnailWrapper>
7678
);
7779

80+
if (props.url === undefined) {
81+
console.error("EmbedVideo: url is undefined when proxyUrl is undefined");
82+
return null;
83+
}
84+
7885
const url = new URL(props.url);
7986
url.searchParams.set("autoplay", "1");
8087
url.searchParams.set("auto_play", "1");

0 commit comments

Comments
 (0)