Stricters Typescript

This commit is contained in:
Kalle 2022-06-10 01:02:59 +03:00
parent 7dd6efb832
commit ef2d56327d
18 changed files with 95 additions and 52 deletions

View File

@ -33,7 +33,7 @@ export function Combobox<T extends Record<string, string | null | number>>({
if (!query) return [];
const fuse = new Fuse(options, {
keys: [...Object.keys(options[0])],
keys: [...Object.keys(options[0] ?? {})],
});
return fuse
.search(query)

View File

@ -1,4 +1,5 @@
import React from "react";
import invariant from "tiny-invariant";
export function Dialog({
children,
@ -46,6 +47,7 @@ function useDOMSync(isOpen: boolean) {
if (!dialog.open && !isOpen) return;
const html = document.getElementsByTagName("html")[0];
invariant(html);
if (isOpen) {
dialog.showModal();

View File

@ -29,4 +29,6 @@ export function Label({ valueLimits, children, htmlFor }: LabelProps) {
function lengthWarning(valueLimits: NonNullable<LabelProps["valueLimits"]>) {
if (valueLimits.current >= valueLimits.max) return "error";
if (valueLimits.current / valueLimits.max >= 0.9) return "warning";
return;
}

View File

@ -28,8 +28,8 @@ export function Popover({
// @ts-expect-error Popper docs: https://popper.js.org/react-popper/v2/
ref={setPopperElement}
className="popover-content"
style={styles.popper}
{...attributes.popper}
style={styles["popper"]}
{...attributes["popper"]}
>
{children}
</HeadlessPopover.Panel>

View File

@ -1,6 +1,7 @@
import clsx from "clsx";
import randomColor from "randomcolor";
import { useState } from "react";
import { atOrError } from "~/utils/arrays";
interface HSL {
h: number;
@ -92,13 +93,19 @@ class Color {
multiply(matrix: number[]) {
const newR = this.clamp(
this.r * matrix[0] + this.g * matrix[1] + this.b * matrix[2]
this.r * atOrError(matrix, 0) +
this.g * atOrError(matrix, 1) +
this.b * atOrError(matrix, 2)
);
const newG = this.clamp(
this.r * matrix[3] + this.g * matrix[4] + this.b * matrix[5]
this.r * atOrError(matrix, 3) +
this.g * atOrError(matrix, 4) +
this.b * atOrError(matrix, 5)
);
const newB = this.clamp(
this.r * matrix[6] + this.g * matrix[7] + this.b * matrix[8]
this.r * atOrError(matrix, 6) +
this.g * atOrError(matrix, 7) +
this.b * atOrError(matrix, 8)
);
this.r = newR;
this.g = newG;
@ -240,15 +247,15 @@ class Solver {
const ck = c / Math.pow(k + 1, gamma);
for (let i = 0; i < 6; i++) {
deltas[i] = Math.random() > 0.5 ? 1 : -1;
highArgs[i] = values[i] + ck * deltas[i];
lowArgs[i] = values[i] - ck * deltas[i];
highArgs[i] = atOrError(values, i) + ck * deltas[i];
lowArgs[i] = atOrError(values, i) - ck * deltas[i];
}
const lossDiff = this.loss(highArgs) - this.loss(lowArgs);
for (let i = 0; i < 6; i++) {
const g = (lossDiff / (2 * ck)) * deltas[i];
const ak = a[i] / Math.pow(A + k + 1, alpha);
values[i] = fix(values[i] - ak * g, i);
const ak = atOrError(a, i) / Math.pow(A + k + 1, alpha);
values[i] = fix(atOrError(values, i) - ak * g, i);
}
const loss = this.loss(values);
@ -287,12 +294,12 @@ class Solver {
const color = this.reusedColor;
color.set(0, 0, 0);
color.invert(filters[0] / 100);
color.sepia(filters[1] / 100);
color.saturate(filters[2] / 100);
color.hueRotate(filters[3] * 3.6);
color.brightness(filters[4] / 100);
color.contrast(filters[5] / 100);
color.invert(atOrError(filters, 0) / 100);
color.sepia(atOrError(filters, 1) / 100);
color.saturate(atOrError(filters, 2) / 100);
color.hueRotate(atOrError(filters, 3) * 3.6);
color.brightness(atOrError(filters, 4) / 100);
color.contrast(atOrError(filters, 5) / 100);
const colorHSL = color.hsl();
return (
@ -307,7 +314,7 @@ class Solver {
css(filters: number[]) {
function fmt(idx: number, multiplier = 1) {
return Math.round(filters[idx] * multiplier);
return Math.round(atOrError(filters, idx) * multiplier);
}
return `invert(${fmt(0)}%) sepia(${fmt(1)}%) saturate(${fmt(
2
@ -328,9 +335,9 @@ function hexToRgb(hex: string): RGB {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
if (result) {
return [
parseInt(result[1], 16),
parseInt(result[2], 16),
parseInt(result[3], 16),
parseInt(atOrError(result, 1), 16),
parseInt(atOrError(result, 2), 16),
parseInt(atOrError(result, 3), 16),
];
}
@ -340,7 +347,11 @@ function hexToRgb(hex: string): RGB {
function getFilters(hex: string) {
let rgb = [255, 255, 255];
rgb = hexToRgb(hex);
const color = new Color(rgb[0], rgb[1], rgb[2]);
const color = new Color(
atOrError(rgb, 0),
atOrError(rgb, 1),
atOrError(rgb, 2)
);
const solver = new Solver(color);
const result = solver.solve();
return result.filter;

View File

@ -41,17 +41,20 @@ export class DiscordStrategy extends OAuth2Strategy<
scope: string;
constructor() {
invariant(process.env.DISCORD_CLIENT_ID);
invariant(process.env.DISCORD_CLIENT_SECRET);
invariant(process.env.BASE_URL);
invariant(process.env["DISCORD_CLIENT_ID"]);
invariant(process.env["DISCORD_CLIENT_SECRET"]);
invariant(process.env["BASE_URL"]);
super(
{
authorizationURL: "https://discord.com/api/oauth2/authorize",
tokenURL: "https://discord.com/api/oauth2/token",
clientID: process.env.DISCORD_CLIENT_ID,
clientSecret: process.env.DISCORD_CLIENT_SECRET,
callbackURL: new URL("/auth/callback", process.env.BASE_URL).toString(),
clientID: process.env["DISCORD_CLIENT_ID"],
clientSecret: process.env["DISCORD_CLIENT_SECRET"],
callbackURL: new URL(
"/auth/callback",
process.env["BASE_URL"]
).toString(),
},
async ({ accessToken }) => {
const authHeader = ["Authorization", `Bearer ${accessToken}`];

View File

@ -1,14 +1,14 @@
import { createCookieSessionStorage } from "@remix-run/node";
import invariant from "tiny-invariant";
invariant(process.env.SESSION_SECRET);
invariant(process.env["SESSION_SECRET"]);
export const sessionStorage = createCookieSessionStorage({
cookie: {
name: "_session",
sameSite: "lax",
path: "/",
httpOnly: true,
secrets: [process.env.SESSION_SECRET],
secrets: [process.env["SESSION_SECRET"]],
secure: process.env.NODE_ENV === "production",
},
});

View File

@ -1,4 +1,5 @@
import type { MonthYear } from "~/core/plus";
import { atOrError } from "~/utils/arrays";
import { databaseTimestampToDate } from "~/utils/dates";
import { sql } from "../sql";
import type { PlusSuggestion, User, UserWithPlusTier } from "../types";
@ -68,7 +69,7 @@ export interface FindVisibleForUserSuggestedUserInfo {
| "discordDiscriminator"
| "discordAvatar"
>;
suggestions: (Pick<PlusSuggestion, "id" | "text"> & {
suggestions: (Pick<PlusSuggestion, "id" | "text" | "createdAt"> & {
createdAtText: string;
author: Pick<
User,
@ -91,10 +92,12 @@ export function findVisibleForUser(
function mapFindVisibleForUserRowsToResult(rows: any[]): FindVisibleForUser {
return rows.reduce((result: FindVisibleForUser, row) => {
if (!result[row.tier]) result[row.tier] = [];
const usersOfTier = result[row.tier] ?? [];
result[row.tier] = usersOfTier;
const suggestionInfo = {
id: row.id,
createdAt: row.createdAt,
createdAtText: databaseTimestampToDate(row.createdAt).toLocaleString(
"en-US",
{
@ -113,14 +116,14 @@ function mapFindVisibleForUserRowsToResult(rows: any[]): FindVisibleForUser {
},
};
const existingSuggestion = result[row.tier].find(
const existingSuggestion = usersOfTier.find(
(suggestion) => suggestion.suggestedUser.id === row.suggestedId
);
if (existingSuggestion) {
existingSuggestion.suggestions.push(suggestionInfo);
} else {
result[row.tier].push({
usersOfTier.push({
suggestedUser: {
id: row.suggestedId,
discordId: row.suggestedDiscordId,
@ -143,7 +146,9 @@ function sortNewestPlayersToBeSuggestedFirst(
Object.entries(suggestions).map(([tier, suggestions]) => [
tier,
suggestions.sort(
(a, b) => b.suggestions[0].createdAt - a.suggestions[0].createdAt
(a, b) =>
atOrError(b.suggestions, 0).createdAt -
atOrError(a.suggestions, 0).createdAt
),
])
);

View File

@ -98,14 +98,13 @@ function groupPlusVotingResults(
> = {};
for (const row of rows) {
if (!grouped[row.tier]) {
grouped[row.tier] = {
passed: [],
failed: [],
};
}
const playersOfTier = grouped[row.tier] ?? {
passed: [],
failed: [],
};
grouped[row.tier] = playersOfTier;
grouped[row.tier][row.passedVoting ? "passed" : "failed"].push({
playersOfTier[row.passedVoting ? "passed" : "failed"].push({
id: row.id,
discordAvatar: row.discordAvatar,
discordDiscriminator: row.discordDiscriminator,

View File

@ -1,8 +1,8 @@
import Database from "better-sqlite3";
import invariant from "tiny-invariant";
invariant(process.env.DB_PATH, "DB_PATH env variable must be set");
export const sql = new Database(process.env.DB_PATH);
invariant(process.env["DB_PATH"], "DB_PATH env variable must be set");
export const sql = new Database(process.env["DB_PATH"]);
sql.pragma("journal_mode = WAL");
sql.pragma("foreign_keys = ON");

View File

@ -1,8 +1,10 @@
import { useMatches } from "@remix-run/react";
import invariant from "tiny-invariant";
import type { RootLoaderData } from "~/root";
export const useUser = () => {
const [root] = useMatches();
invariant(root);
return (root.data as RootLoaderData).user;
};

View File

@ -193,7 +193,10 @@ function hasUserSuggestedThisMonth({
}: Pick<CanSuggestNewUserFEArgs, "user" | "suggestions">) {
return Object.values(suggestions)
.flat()
.some(({ suggestions }) => suggestions[0].author.id === user?.id);
.some(
({ suggestions }) =>
suggestions[0] && suggestions[0].author.id === user?.id
);
}
export function canVoteFE() {

View File

@ -124,6 +124,7 @@ export default function PlusSuggestionsPage() {
tierVisible && data.suggestions[tierVisible]
? data.suggestions[tierVisible]
: [];
invariant(visibleSuggestions);
// xxx: looks strange when suggestedforinfo and suggestions both show https://cdn.discordapp.com/attachments/816458257714511872/984195125913731102/unknown.png
return (

View File

@ -63,8 +63,8 @@ export default function PlusCommentModalPage() {
const params = useParams();
const data = atOrError(matches, -2).data as PlusSuggestionsLoaderData;
const targetUserId = Number(params.userId);
const tierSuggestedTo = String(params.tier);
const targetUserId = Number(params["userId"]);
const tierSuggestedTo = String(params["tier"]);
const userBeingCommented = data.suggestions?.[tierSuggestedTo]?.find(
(u) => u.suggestedUser.id === targetUserId

View File

@ -32,6 +32,7 @@ import { upcomingVoting } from "~/core/plus";
import { db } from "~/db";
import type { UserWithPlusTier } from "~/db/types";
import { ErrorMessage } from "~/components/ErrorMessage";
import { atOrError } from "~/utils/arrays";
const commentActionSchema = z.object({
tier: z.preprocess(actualNumber, z.number().min(1).max(3)),
@ -80,7 +81,7 @@ export const action: ActionFunction = async ({ request }) => {
export default function PlusNewSuggestionModalPage() {
const user = useUser();
const matches = useMatches();
const data = matches.at(-2)!.data as PlusSuggestionsLoaderData;
const data = atOrError(matches, -2).data as PlusSuggestionsLoaderData;
const [selectedUser, setSelectedUser] = React.useState<{
/** User id */
value: string;
@ -93,16 +94,17 @@ export default function PlusNewSuggestionModalPage() {
return tier >= user.plusTier;
});
const [targetPlusTier, setTargetPlusTier] = React.useState<number>(
tierOptions[0]
);
const [targetPlusTier, setTargetPlusTier] = React.useState<
number | undefined
>(tierOptions[0]);
if (
!data.suggestions ||
!canSuggestNewUserFE({
user,
suggestions: data.suggestions,
})
}) ||
!targetPlusTier
) {
return <Redirect to={PLUS_SUGGESTIONS_PAGE} />;
}
@ -187,6 +189,8 @@ function getSelectedUserErrorMessage({
if (playerAlreadySuggested({ targetPlusTier, suggestions, suggested })) {
return `This user was already suggested to +${targetPlusTier}`;
}
return;
}
// TODO: better UX - allow going over but prevent submit like Twitter

View File

@ -2,6 +2,7 @@ import type { ActionFunction, LinksFunction } from "@remix-run/node";
import { Form, useMatches, useTransition } from "@remix-run/react";
import { countries } from "countries-list";
import * as React from "react";
import invariant from "tiny-invariant";
import { z } from "zod";
import { Button } from "~/components/Button";
import { Label } from "~/components/Label";
@ -47,6 +48,7 @@ export const action: ActionFunction = async ({ request }) => {
export default function UserEditPage() {
const [, parentRoute] = useMatches();
invariant(parentRoute);
const data = parentRoute.data as UserPageLoaderData;
const transition = useTransition();

View File

@ -1,5 +1,6 @@
import type { LinksFunction } from "@remix-run/node";
import { useMatches } from "@remix-run/react";
import invariant from "tiny-invariant";
import { Avatar } from "~/components/Avatar";
import { SocialLink } from "~/components/u/SocialLink";
import styles from "~/styles/u.css";
@ -11,6 +12,7 @@ export const links: LinksFunction = () => {
export default function UserInfoPage() {
const [, parentRoute] = useMatches();
invariant(parentRoute);
const data = parentRoute.data as UserPageLoaderData;
return (

View File

@ -16,6 +16,13 @@
},
"noEmit": true,
"forceConsistentCasingInFileNames": true,
"allowJs": false
"allowJs": false,
"noUncheckedIndexedAccess": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noPropertyAccessFromIndexSignature": true,
"useUnknownInCatchVariables": true
}
}