import grouping and sorting with Prettier plugin

This commit is contained in:
Timothy Armes
2023-09-11 09:09:34 +02:00
parent 4984639f23
commit 7111f000e5
43 changed files with 589 additions and 210 deletions
+5 -1
View File
@@ -1,5 +1,9 @@
{
"trailingComma": "all",
"bracketSpacing": true,
"plugins": ["prettier-plugin-tailwindcss"]
"plugins": ["@trivago/prettier-plugin-sort-imports", "prettier-plugin-tailwindcss"],
"importOrder": ["^react$", "^@?\\w", "^@\\\\", "<THIRD_PARTY_MODULES>", "^\\.\\.", "^\\."],
"importOrderSeparation": true,
"importOrderSortSpecifiers": true,
"tailwindFunctions": ["twMerge"]
}
+3
View File
@@ -23,9 +23,12 @@
"spiderfied",
"Spiderfy",
"spiderified",
"superjson",
"tailwindcss",
"timothyarmes",
"tournois",
"trivago",
"TRPC",
"unspiderfied",
"unspiderfy"
],
+12
View File
@@ -23,6 +23,18 @@ and is deployed on [Vercel](https://vercel.com/)
- [Leaflet](https://leafletjs.com/) - a Javascript library for interactive maps
- [Nodemailer](https://nodemailer.com/) - a module for Node.js applications to allow easy as cake email sending
## Required VSCode plugins
- ESLint
- Prettier - Code formatter
## Recommended VSCode plugins
- Tailwind CSS IntelliSense
- Tailwind Docs
- Code Spell Checker (to avoid English spelling mistakes in the code)
- Console Ninja (for easier debugging)
## Contributions
Contributions are encouraged. Please open an issue to discuss your ideas.
+8 -8
View File
@@ -1,18 +1,18 @@
"use client";
import { MapProps } from "@/types";
import { useMemo, useRef, ChangeEvent } from "react";
import { useTranslations } from "next-intl";
import { useSetAtom } from "jotai";
import { mapBoundsAtom } from "@/app/atoms";
import { ChangeEvent, useMemo, useRef } from "react";
import { useSetAtom } from "jotai";
import L from "leaflet";
import { MapContainer, TileLayer, Marker } from "react-leaflet";
import "leaflet/dist/leaflet.css";
import "leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.css";
import "leaflet-defaulticon-compatibility";
import "leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.css";
import "leaflet/dist/leaflet.css";
import { useTranslations } from "next-intl";
import { MapContainer, Marker, TileLayer } from "react-leaflet";
import MapEvents from "@/app/[lang]/components/MapEvents";
import { mapBoundsAtom } from "@/app/atoms";
import { MapProps } from "@/types";
const Map = ({ position, setPosition, center }: MapProps) => {
const t = useTranslations("Map");
@@ -1,17 +1,19 @@
"use client";
import dynamic from "next/dynamic";
import { useState } from "react";
import { useTranslations } from "next-intl";
import { useAtomValue } from "jotai";
import { franceCenterAtom } from "@/app/atoms";
import useTournamentForm from "@/hooks/useTournamentForm";
import { useTranslations } from "next-intl";
import dynamic from "next/dynamic";
import InfoMessage from "@/app/[lang]/components/InfoMessage";
import {
handleTournamentSubmit,
handleClearTournamentForm,
} from "@/handlers/formHandlers";
import LoadingMap from "@/app/[lang]/components/LoadingMap";
import { franceCenterAtom } from "@/app/atoms";
import {
handleClearTournamentForm,
handleTournamentSubmit,
} from "@/handlers/formHandlers";
import useTournamentForm from "@/hooks/useTournamentForm";
const Map = dynamic(() => import("./Map"), {
ssr: false,
+2 -1
View File
@@ -1,6 +1,7 @@
import { useTranslations } from "next-intl";
import Link from "next-intl/link";
import { FaGithub, FaRegEnvelope } from "react-icons/fa";
import { useTranslations } from "next-intl";
import ThemeSwitcher from "@/app/[lang]/components/ThemeSwitcher";
export default function Footer() {
+1
View File
@@ -1,6 +1,7 @@
"use client";
import { useRef, useState } from "react";
import { twMerge } from "tailwind-merge";
import HamburgerMenu from "./HamburgerMenu";
+8 -7
View File
@@ -1,16 +1,17 @@
import { Dispatch, RefObject, SetStateAction, useRef, useState } from "react";
import { useTranslations } from "next-intl";
import Link from "next-intl/link";
import { twMerge } from "tailwind-merge";
import useHamburgerClose from "@/hooks/useHamburgerClose";
interface HamburgerMenuState {
menuVisible: boolean;
setMenuVisible: Dispatch<SetStateAction<boolean>>;
hamburgerButtonRef: RefObject<HTMLDivElement>;
}
import { Dispatch, RefObject, SetStateAction, useRef, useState } from "react";
import { twMerge } from "tailwind-merge";
import Link from "next-intl/link";
import { useTranslations } from "next-intl";
import useHamburgerClose from "@/hooks/useHamburgerClose";
const HamburgerMenu = ({
menuVisible,
setMenuVisible,
+2 -1
View File
@@ -1,6 +1,7 @@
import { ResponseMessage } from "@/types";
import { Dispatch, SetStateAction } from "react";
import { ResponseMessage } from "@/types";
const InfoMessage = ({
responseMessage,
}: {
+3 -1
View File
@@ -1,9 +1,11 @@
import { useEffect } from "react";
import { useSetAtom } from "jotai";
import { mapBoundsAtom } from "@/app/atoms";
import L from "leaflet";
import { useMapEvent } from "react-leaflet";
import { mapBoundsAtom } from "@/app/atoms";
const MapEvents = () => {
const setMapBounds = useSetAtom(mapBoundsAtom);
+1 -1
View File
@@ -1,5 +1,5 @@
import Link from "next-intl/link";
import { useTranslations } from "next-intl";
import Link from "next-intl/link";
import Hamburger from "./Hamburger";
+3 -2
View File
@@ -1,10 +1,11 @@
import { NextIntlClientProvider, AbstractIntlMessages } from "next-intl";
import { ReactNode } from "react";
import { AbstractIntlMessages, NextIntlClientProvider } from "next-intl";
import "@/css/globals.css";
import Navbar from "./Navbar";
import Footer from "./Footer";
import Navbar from "./Navbar";
const TestableLayout = ({
children,
+1
View File
@@ -1,6 +1,7 @@
"use client";
import { useEffect, useState } from "react";
import useDarkMode from "@/hooks/useDarkMode";
const ThemeSwitcher = () => {
+3 -3
View File
@@ -1,12 +1,12 @@
"use client";
import { useState } from "react";
import { useTranslations } from "next-intl";
import useContactForm from "@/hooks/useContactForm";
import { handleEmailSubmit } from "@/handlers/formHandlers";
import InfoMessage from "@/app/[lang]/components/InfoMessage";
import { handleEmailSubmit } from "@/handlers/formHandlers";
import useContactForm from "@/hooks/useContactForm";
const ContactForm = () => {
const t = useTranslations("Contact");
+8 -7
View File
@@ -1,15 +1,16 @@
import Script from "next/script";
import { Inter, Julius_Sans_One } from "next/font/google";
import { useLocale, NextIntlClientProvider } from "next-intl";
import { getTranslator } from "next-intl/server";
import { notFound } from "next/navigation";
import { ReactNode } from "react";
import { NextIntlClientProvider, useLocale } from "next-intl";
import { getTranslator } from "next-intl/server";
import { Inter, Julius_Sans_One } from "next/font/google";
import { notFound } from "next/navigation";
import Script from "next/script";
import "@/css/globals.css";
import Providers from "./providers";
import Navbar from "./components/Navbar";
import Footer from "./components/Footer";
import Navbar from "./components/Navbar";
import Providers from "./providers";
const inter = Inter({ subsets: ["latin"], variable: "--font-inter" });
const title = Julius_Sans_One({
+2 -2
View File
@@ -1,6 +1,6 @@
import Link from "next/link";
import Image from "next/image";
import { useTranslations } from "next-intl";
import Image from "next/image";
import Link from "next/link";
import bgImage from "@/public/images/map-bg.jpg";
+1 -1
View File
@@ -1,5 +1,5 @@
import Link from "next-intl/link";
import { useTranslations } from "next-intl";
import Link from "next-intl/link";
export default function About() {
const t = useTranslations("About");
+6 -5
View File
@@ -1,12 +1,13 @@
import { useMap } from "react-leaflet";
import { useEffect, useMemo } from "react";
import { useTranslations } from "next-intl";
import L from "leaflet";
import { useAtomValue } from "jotai";
import { TimeControl } from "@/types";
import { useAtomValue } from "jotai";
import L from "leaflet";
import { useTranslations } from "next-intl";
import { useMap } from "react-leaflet";
import { filteredTournamentsByTimeControlAtom } from "@/app/atoms";
import { TimeControlColours } from "@/app/constants";
import { TimeControl } from "@/types";
const Legend = () => {
const t = useTranslations("Tournaments");
+3 -2
View File
@@ -1,11 +1,12 @@
"use client";
import { ScrollableElement } from "@/types";
import { useEffect, useRef, useState } from "react";
import { FaArrowUp } from "react-icons/fa";
import { handleScrollToTop } from "@/handlers/scrollHandlers";
import { useEffect, useRef, useState } from "react";
import { useBreakpoint } from "@/hooks/tailwind";
import { ScrollableElement } from "@/types";
const ScrollToTopButton = () => {
const scrollToTopElementRef = useRef<ScrollableElement | null>(null);
+2 -1
View File
@@ -1,6 +1,7 @@
import { useTranslations } from "next-intl";
import { useAtom } from "jotai/index";
import { useTranslations } from "next-intl";
import { IoCloseOutline } from "react-icons/io5";
import { searchStringAtom } from "@/app/atoms";
const SearchBar = () => {
+3 -3
View File
@@ -1,13 +1,13 @@
import { useAtom, useAtomValue } from "jotai";
import { useTranslations } from "next-intl";
import {
classicAtom,
rapidAtom,
blitzAtom,
classicAtom,
otherAtom,
rapidAtom,
tournamentsAtom,
} from "@/app/atoms";
import { useTranslations } from "next-intl";
import { TimeControl } from "@/types";
const TimeControlFilters = () => {
+21 -23
View File
@@ -1,34 +1,32 @@
"use client";
import { TimeControl } from "@/types";
import { useCallback, useEffect, useMemo, useRef } from "react";
import { useMemo, useRef, useCallback, useEffect } from "react";
import L, { LatLngLiteral, Marker, DomUtil } from "leaflet";
import { MapContainer, TileLayer, LayerGroup } from "react-leaflet";
import MarkerClusterGroup from "react-leaflet-cluster";
import { FaAngleDoubleDown } from "react-icons/fa";
import { useAtomValue, useSetAtom } from "jotai";
import L, { DomUtil, LatLngLiteral, Marker } from "leaflet";
import "leaflet-defaulticon-compatibility";
import "leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.css";
import "leaflet.smooth_marker_bouncing";
import "leaflet/dist/leaflet.css";
import { countBy, groupBy } from "lodash";
import {
mapBoundsAtom,
debouncedHoveredListTournamentIdAtom,
filteredTournamentsByTimeControlAtom,
normsOnlyAtom,
} from "@/app/atoms";
import { generatePieSVG } from "@/lib/pie";
import { TimeControlColours } from "@/app/constants";
import Legend from "./Legend";
import { TournamentMarker, TournamentMarkerRef } from "./TournamentMarker";
import TimeControlFilters from "./TimeControlFilters";
import { FaAngleDoubleDown } from "react-icons/fa";
import { LayerGroup, MapContainer, TileLayer } from "react-leaflet";
import MarkerClusterGroup from "react-leaflet-cluster";
import MapEvents from "@/app/[lang]/components/MapEvents";
import {
debouncedHoveredListTournamentIdAtom,
filteredTournamentsByTimeControlAtom,
mapBoundsAtom,
normsOnlyAtom,
} from "@/app/atoms";
import { TimeControlColours } from "@/app/constants";
import { generatePieSVG } from "@/lib/pie";
import { TimeControl } from "@/types";
import "leaflet/dist/leaflet.css";
import "leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.css";
import "leaflet-defaulticon-compatibility";
import "leaflet.smooth_marker_bouncing";
import Legend from "./Legend";
import TimeControlFilters from "./TimeControlFilters";
import { TournamentMarker, TournamentMarkerRef } from "./TournamentMarker";
// Declare a class type that adds in methods etc. defined by leaflet.smooth_marker_bouncing
// to keep Typescript happy
+10 -9
View File
@@ -1,17 +1,18 @@
"use client";
import { forwardRef, useMemo, useImperativeHandle, useRef } from "react";
import { Tournament } from "@/types";
import L from "leaflet";
import { Marker, Popup, MarkerProps } from "react-leaflet";
import { useTranslations } from "next-intl";
import { useSetAtom } from "jotai";
import { FaTrophy } from "react-icons/fa";
import { last } from "lodash";
import { forwardRef, useImperativeHandle, useMemo, useRef } from "react";
import { useSetAtom } from "jotai";
import L from "leaflet";
import { last } from "lodash";
import { useTranslations } from "next-intl";
import { FaTrophy } from "react-icons/fa";
import { Marker, MarkerProps, Popup } from "react-leaflet";
import type { BouncingMarker } from "@/leafletTypes";
import { debouncedHoveredMapTournamentGroupIdAtom } from "@/app/atoms";
import { TimeControlColours } from "@/app/constants";
import type { BouncingMarker } from "@/leafletTypes";
import { Tournament } from "@/types";
export type TournamentMarkerRef = {
getMarker: () => L.Marker<any>;
+9 -8
View File
@@ -1,26 +1,27 @@
"use client";
import { useEffect } from "react";
import { useAtom, useAtomValue, useSetAtom } from "jotai";
import { useTranslations } from "next-intl";
import { useAtomValue, useSetAtom, useAtom } from "jotai";
import { twMerge } from "tailwind-merge";
import { FaExternalLinkAlt } from "react-icons/fa";
import { FaTrophy } from "react-icons/fa";
import { Tooltip } from "react-tooltip";
import { twMerge } from "tailwind-merge";
import {
filteredTournamentsListAtom,
syncVisibleAtom,
normsOnlyAtom,
hoveredMapTournamentGroupIdAtom,
debouncedHoveredMapTournamentGroupIdAtom,
debouncedHoveredListTournamentIdAtom,
debouncedHoveredMapTournamentGroupIdAtom,
filteredTournamentsListAtom,
hoveredMapTournamentGroupIdAtom,
normsOnlyAtom,
syncVisibleAtom,
} from "@/app/atoms";
import { useBreakpoint } from "@/hooks/tailwind";
import ScrollToTopButton from "./ScrollToTopButton";
import SearchBar from "./SearchBar";
import TimeControlFilters from "./TimeControlFilters";
import ScrollToTopButton from "./ScrollToTopButton";
export default function TournamentTable() {
const t = useTranslations("Tournaments");
+9 -9
View File
@@ -1,18 +1,18 @@
"use client";
import { useHydrateAtoms } from "jotai/utils";
import dynamic from "next/dynamic";
import LoadingMap from "@/app/[lang]/components/LoadingMap";
import { tournamentsAtom } from "@/app/atoms";
import { Tournament } from "@/types";
import TournamentTable from "./TournamentTable";
type TournamentsDisplayProps = {
tournaments: Tournament[];
};
import { Tournament } from "@/types";
import dynamic from "next/dynamic";
import { useHydrateAtoms } from "jotai/utils";
import { tournamentsAtom } from "@/app/atoms";
import TournamentTable from "./TournamentTable";
import LoadingMap from "@/app/[lang]/components/LoadingMap";
/**
* Imports the tournament map component, ensuring CSR only.
* @remarks SSR is not supported by react-leaflet
+6 -6
View File
@@ -1,12 +1,12 @@
import clientPromise from "@/lib/mongodb";
import { errorLog } from "@/utils/logger";
import { Tournament, TimeControl } from "@/types";
import { differenceInDays, isSameDay, parse } from "date-fns";
import { groupBy } from "lodash";
import { parse, differenceInDays, isSameDay } from "date-fns";
import { ObjectId } from "mongodb";
import clientPromise from "@/lib/mongodb";
import { TimeControl, Tournament } from "@/types";
import { errorLog } from "@/utils/logger";
import TournamentsDisplay from "./TournamentsDisplay";
import { ObjectId } from "mongodb";
export const revalidate = 3600; // Revalidate cache every 6 hours
+2 -1
View File
@@ -1,5 +1,6 @@
import clientPromise from "@/lib/mongodb";
import { NextResponse } from "next/server";
import clientPromise from "@/lib/mongodb";
import { errorLog } from "@/utils/logger";
export async function POST(req: Request) {
+3 -2
View File
@@ -1,6 +1,7 @@
import NodeMailer from "nodemailer";
import { NextResponse } from "next/server";
import { infoLog, errorLog } from "@/utils/logger";
import NodeMailer from "nodemailer";
import { errorLog, infoLog } from "@/utils/logger";
export async function POST(req: Request) {
const { email, subject, message } = await req.json();
+1 -1
View File
@@ -1,7 +1,7 @@
import { TimeControl, Tournament } from "@/types";
import { atom } from "jotai";
import { LatLngBounds, LatLngLiteral } from "leaflet";
import { TimeControl, Tournament } from "@/types";
import atomWithDebounce from "@/utils/atomWithDebounce";
import { normalizedContains } from "@/utils/string";
+2 -2
View File
@@ -1,8 +1,8 @@
import { TimeControl } from "@/types"
import { TimeControl } from "@/types";
export const TimeControlColours = {
[TimeControl.Classic]: "#00ac39",
[TimeControl.Rapid]: "#0086c7",
[TimeControl.Blitz]: "#ddce20",
[TimeControl.Other]: "#ea5f17",
}
};
+6 -4
View File
@@ -1,12 +1,14 @@
import { ResponseMessage, TournamentFormProps } from "@/types";
import { LatLngLiteral } from "leaflet";
import { Dispatch, FormEvent, SetStateAction } from "react";
import { LatLngLiteral } from "leaflet";
import { useTranslations } from "next-intl";
import { clearMessage } from "@/app/[lang]/components/InfoMessage";
import { errorLog } from "@/utils/logger";
import discordWebhook from "@/lib/discordWebhook";
import sendMail from "@/lib/sendMail";
import tournamentFormToDB from "@/lib/tournamentFormToDB";
import discordWebhook from "@/lib/discordWebhook";
import { ResponseMessage, TournamentFormProps } from "@/types";
import { errorLog } from "@/utils/logger";
export const handleClearTournamentForm = (
formRefs: TournamentFormProps,
+24 -7
View File
@@ -1,11 +1,15 @@
import { useLayoutEffect, useEffect, useState, useRef, useMemo } from "react";
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
import resolveConfig from "tailwindcss/resolveConfig";
import tailwindConfig from "@/tailwind.config.js";
const config = resolveConfig(tailwindConfig);
const isSSR =
typeof window === "undefined" || !window.navigator || /ServerSideRendering|^Deno\//.test(window.navigator.userAgent);
typeof window === "undefined" ||
!window.navigator ||
/ServerSideRendering|^Deno\//.test(window.navigator.userAgent);
const isBrowser = !isSSR;
const useIsomorphicEffect = isBrowser ? useLayoutEffect : useEffect;
@@ -18,7 +22,9 @@ export type CreatorReturnType = {
function create(screens: object | undefined) {
if (!screens) {
throw new Error("Failed to create breakpoint hooks, given `screens` value is invalid.");
throw new Error(
"Failed to create breakpoint hooks, given `screens` value is invalid.",
);
}
function useBreakpoint(breakpoint: string, defaultValue: boolean = false) {
@@ -46,15 +52,25 @@ function create(screens: object | undefined) {
return match;
}
function useBreakpointEffect<Breakpoint extends string>(breakpoint: Breakpoint, effect: (match: boolean) => void) {
function useBreakpointEffect<Breakpoint extends string>(
breakpoint: Breakpoint,
effect: (match: boolean) => void,
) {
const match = useBreakpoint(breakpoint);
useEffect(() => effect(match));
return null;
}
function useBreakpointValue<Breakpoint extends string, T, U>(breakpoint: Breakpoint, valid: T, invalid: U) {
function useBreakpointValue<Breakpoint extends string, T, U>(
breakpoint: Breakpoint,
valid: T,
invalid: U,
) {
const match = useBreakpoint(breakpoint);
const value = useMemo(() => (match ? valid : invalid), [invalid, match, valid]);
const value = useMemo(
() => (match ? valid : invalid),
[invalid, match, valid],
);
return value;
}
@@ -65,4 +81,5 @@ function create(screens: object | undefined) {
} as CreatorReturnType;
}
export const { useBreakpoint, useBreakpointEffect, useBreakpointValue } = create(config.theme!.screens);
export const { useBreakpoint, useBreakpointEffect, useBreakpointValue } =
create(config.theme!.screens);
+21 -16
View File
@@ -1,31 +1,36 @@
export const useDynamicViewportUnits = () => {
const setVh = function () {
var svh = document.documentElement.clientHeight * 0.01;
document.documentElement.style.setProperty('--1svh', (svh + "px"));
document.documentElement.style.setProperty("--1svh", svh + "px");
var dvh = window.innerHeight * 0.01;
document.documentElement.style.setProperty('--1dvh', (dvh + "px"));
document.documentElement.style.setProperty("--1dvh", dvh + "px");
};
const isMobile = function() {
if(/Android|iPhone|iPad|iPod/i.test(navigator.userAgent) || (navigator.userAgent.match(/Mac/) && navigator.maxTouchPoints && navigator.maxTouchPoints > 2)) {
const isMobile = function () {
if (
/Android|iPhone|iPad|iPod/i.test(navigator.userAgent) ||
(navigator.userAgent.match(/Mac/) &&
navigator.maxTouchPoints &&
navigator.maxTouchPoints > 2)
) {
return true;
}
return false;
}
};
// SSR support
if (typeof window === 'undefined') {
if (typeof window === "undefined") {
return;
}
// Don't run polyfill if browser supports the units natively
if ('CSS' in window &&
'supports' in window.CSS &&
window.CSS.supports('height: 100svh') &&
window.CSS.supports('height: 100dvh') &&
window.CSS.supports('height: 100lvh')
if (
"CSS" in window &&
"supports" in window.CSS &&
window.CSS.supports("height: 100svh") &&
window.CSS.supports("height: 100dvh") &&
window.CSS.supports("height: 100lvh")
) {
return;
}
@@ -35,10 +40,10 @@ export const useDynamicViewportUnits = () => {
return;
}
window.addEventListener('resize', setVh);
window.addEventListener("resize", setVh);
setVh();
return () => {
window.removeEventListener('resize', setVh);
}
}
window.removeEventListener("resize", setVh);
};
};
+2 -2
View File
@@ -1,3 +1,5 @@
import { Dispatch, RefObject, SetStateAction, useEffect } from "react";
interface HamburgerClose {
menuVisible: boolean;
setMenuVisible: Dispatch<SetStateAction<boolean>>;
@@ -9,8 +11,6 @@ interface HamburgerClose {
menuTimeout: () => void;
}
import { Dispatch, RefObject, SetStateAction, useEffect } from "react";
const useHamburgerClose = ({
menuVisible,
setMenuVisible,
+4 -2
View File
@@ -1,8 +1,10 @@
import { TournamentFormProps } from "@/types";
import { useRef, useState } from "react";
import { LatLngLiteral } from "leaflet";
import { useAtomValue } from "jotai";
import { LatLngLiteral } from "leaflet";
import { franceCenterAtom } from "@/app/atoms";
import { TournamentFormProps } from "@/types";
export const useTournamentForm = (): TournamentFormProps => {
const center = useAtomValue(franceCenterAtom);
+28 -12
View File
@@ -3,11 +3,11 @@ function round(n: number) {
}
function polarToCartesian(radius: number, angleInDegrees: number) {
var radians = (angleInDegrees - 90) * Math.PI / 180;
var radians = ((angleInDegrees - 90) * Math.PI) / 180;
return {
x: round(radius + (radius * Math.cos(radians))),
y: round(radius + (radius * Math.sin(radians)))
x: round(radius + radius * Math.cos(radians)),
y: round(radius + radius * Math.sin(radians)),
};
}
@@ -21,8 +21,17 @@ function getDAttribute(radius: number, startAngle: number, endAngle: number) {
const largeArcFlag = endAngle - startAngle <= 180 ? 0 : 1;
const d = [
"M", start.x, start.y,
"A", radius, radius, 0, largeArcFlag, 1, end.x, end.y
"M",
start.x,
start.y,
"A",
radius,
radius,
0,
largeArcFlag,
1,
end.x,
end.y,
];
if (isCircle) {
@@ -37,32 +46,39 @@ function path(d: string, colour: string) {
return `<path d='${d}' style="fill: ${colour}" />`;
}
function svg(className: string, width: number, content: string ) {
function svg(className: string, width: number, content: string) {
return `<svg class="${className}" viewBox="0 0 ${width} ${width}"><g class='pie'>${content}</g></svg>`;
}
type PieSlice = {
value: number;
colour: string;
}
};
export function generatePieSVG(className: string, radius: number, values: PieSlice[]) {
export function generatePieSVG(
className: string,
radius: number,
values: PieSlice[],
) {
type Sector = {
colour: string;
degrees: number;
from?: number;
to?: number;
}
};
const total = values.reduce((a, b) => a + b.value, 0);
const data: Sector[] = values.map(({ value, colour }) => ({ colour, degrees: value / total * 360 }));
const data: Sector[] = values.map(({ value, colour }) => ({
colour,
degrees: (value / total) * 360,
}));
const paths = data.reduce<[number, string][]>((prev, value, i) => {
const from = i === 0 ? 0 : prev[i - 1][0];
const to = from + value.degrees;
return [...prev, [to, path(getDAttribute(radius, from, to ), value.colour)]];
return [...prev, [to, path(getDAttribute(radius, from, to), value.colour)]];
}, []);
return svg(className, radius * 2, paths.map(([to, path]) => path) .join(''));
return svg(className, radius * 2, paths.map(([to, path]) => path).join(""));
}
+1
View File
@@ -1,4 +1,5 @@
import { LatLngLiteral } from "leaflet";
import { TournamentFormProps } from "@/types";
import formatDate from "@/utils/formatDate";
+312 -26
View File
@@ -10,6 +10,7 @@
"dependencies": {
"@kodingdotninja/use-tailwind-breakpoint": "^0.0.5",
"@next/bundle-analyzer": "^13.4.12",
"@trivago/prettier-plugin-sort-imports": "^4.2.0",
"@types/node": "20.5.1",
"@types/react": "18.2.21",
"@types/react-dom": "18.2.7",
@@ -82,34 +83,198 @@
}
},
"node_modules/@babel/code-frame": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz",
"integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==",
"dev": true,
"version": "7.22.13",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
"integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
"dependencies": {
"@babel/highlight": "^7.22.5"
"@babel/highlight": "^7.22.13",
"chalk": "^2.4.2"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/code-frame/node_modules/ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dependencies": {
"color-convert": "^1.9.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/@babel/code-frame/node_modules/chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dependencies": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/@babel/code-frame/node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dependencies": {
"color-name": "1.1.3"
}
},
"node_modules/@babel/code-frame/node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
},
"node_modules/@babel/code-frame/node_modules/escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/@babel/code-frame/node_modules/has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
"engines": {
"node": ">=4"
}
},
"node_modules/@babel/code-frame/node_modules/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dependencies": {
"has-flag": "^3.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/@babel/generator": {
"version": "7.17.7",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.7.tgz",
"integrity": "sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w==",
"dependencies": {
"@babel/types": "^7.17.0",
"jsesc": "^2.5.1",
"source-map": "^0.5.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-environment-visitor": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz",
"integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-function-name": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz",
"integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==",
"dependencies": {
"@babel/template": "^7.22.5",
"@babel/types": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-function-name/node_modules/@babel/types": {
"version": "7.22.17",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.17.tgz",
"integrity": "sha512-YSQPHLFtQNE5xN9tHuZnzu8vPr61wVTBZdfv1meex1NBosa4iT05k/Jw06ddJugi4bk7The/oSwQGFcksmEJQg==",
"dependencies": {
"@babel/helper-string-parser": "^7.22.5",
"@babel/helper-validator-identifier": "^7.22.15",
"to-fast-properties": "^2.0.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-hoist-variables": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz",
"integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==",
"dependencies": {
"@babel/types": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-hoist-variables/node_modules/@babel/types": {
"version": "7.22.17",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.17.tgz",
"integrity": "sha512-YSQPHLFtQNE5xN9tHuZnzu8vPr61wVTBZdfv1meex1NBosa4iT05k/Jw06ddJugi4bk7The/oSwQGFcksmEJQg==",
"dependencies": {
"@babel/helper-string-parser": "^7.22.5",
"@babel/helper-validator-identifier": "^7.22.15",
"to-fast-properties": "^2.0.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-split-export-declaration": {
"version": "7.22.6",
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz",
"integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==",
"dependencies": {
"@babel/types": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-split-export-declaration/node_modules/@babel/types": {
"version": "7.22.17",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.17.tgz",
"integrity": "sha512-YSQPHLFtQNE5xN9tHuZnzu8vPr61wVTBZdfv1meex1NBosa4iT05k/Jw06ddJugi4bk7The/oSwQGFcksmEJQg==",
"dependencies": {
"@babel/helper-string-parser": "^7.22.5",
"@babel/helper-validator-identifier": "^7.22.15",
"to-fast-properties": "^2.0.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-string-parser": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz",
"integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz",
"integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==",
"dev": true,
"version": "7.22.15",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.15.tgz",
"integrity": "sha512-4E/F9IIEi8WR94324mbDUMo074YTheJmd7eZF5vITTeYchqAi6sYXRLHUVsmkdmY4QjfKTcB2jB7dVP3NaBElQ==",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/highlight": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz",
"integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==",
"dev": true,
"version": "7.22.13",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.13.tgz",
"integrity": "sha512-C/BaXcnnvBCmHTpz/VGZ8jgtE2aYlW4hxDhseJAWZb7gqGM/qtCK6iZUb0TyKFf7BOUsBH7Q7fkRsDRhg1XklQ==",
"dependencies": {
"@babel/helper-validator-identifier": "^7.22.5",
"chalk": "^2.0.0",
"chalk": "^2.4.2",
"js-tokens": "^4.0.0"
},
"engines": {
@@ -120,7 +285,6 @@
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"dependencies": {
"color-convert": "^1.9.0"
},
@@ -132,7 +296,6 @@
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"dependencies": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
@@ -146,7 +309,6 @@
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dev": true,
"dependencies": {
"color-name": "1.1.3"
}
@@ -154,14 +316,12 @@
"node_modules/@babel/highlight/node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"dev": true
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
},
"node_modules/@babel/highlight/node_modules/escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
"dev": true,
"engines": {
"node": ">=0.8.0"
}
@@ -170,7 +330,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
"dev": true,
"engines": {
"node": ">=4"
}
@@ -179,7 +338,6 @@
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"dependencies": {
"has-flag": "^3.0.0"
},
@@ -187,6 +345,17 @@
"node": ">=4"
}
},
"node_modules/@babel/parser": {
"version": "7.22.16",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.16.tgz",
"integrity": "sha512-+gPfKv8UWeKKeJTUxe59+OobVcrYHETCsORl61EmSkmgymguYk/X5bp7GuUIXaFsc6y++v8ZxPsLSSuujqDphA==",
"bin": {
"parser": "bin/babel-parser.js"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@babel/runtime": {
"version": "7.22.6",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.6.tgz",
@@ -198,6 +367,72 @@
"node": ">=6.9.0"
}
},
"node_modules/@babel/template": {
"version": "7.22.15",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
"integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==",
"dependencies": {
"@babel/code-frame": "^7.22.13",
"@babel/parser": "^7.22.15",
"@babel/types": "^7.22.15"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/template/node_modules/@babel/types": {
"version": "7.22.17",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.17.tgz",
"integrity": "sha512-YSQPHLFtQNE5xN9tHuZnzu8vPr61wVTBZdfv1meex1NBosa4iT05k/Jw06ddJugi4bk7The/oSwQGFcksmEJQg==",
"dependencies": {
"@babel/helper-string-parser": "^7.22.5",
"@babel/helper-validator-identifier": "^7.22.15",
"to-fast-properties": "^2.0.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/traverse": {
"version": "7.17.3",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.3.tgz",
"integrity": "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==",
"dependencies": {
"@babel/code-frame": "^7.16.7",
"@babel/generator": "^7.17.3",
"@babel/helper-environment-visitor": "^7.16.7",
"@babel/helper-function-name": "^7.16.7",
"@babel/helper-hoist-variables": "^7.16.7",
"@babel/helper-split-export-declaration": "^7.16.7",
"@babel/parser": "^7.17.3",
"@babel/types": "^7.17.0",
"debug": "^4.1.0",
"globals": "^11.1.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/traverse/node_modules/globals": {
"version": "11.12.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
"engines": {
"node": ">=4"
}
},
"node_modules/@babel/types": {
"version": "7.17.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz",
"integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==",
"dependencies": {
"@babel/helper-validator-identifier": "^7.16.7",
"to-fast-properties": "^2.0.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@eslint-community/eslint-utils": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@@ -815,6 +1050,28 @@
"react-dom": "^18.0.0"
}
},
"node_modules/@trivago/prettier-plugin-sort-imports": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-4.2.0.tgz",
"integrity": "sha512-YBepjbt+ZNBVmN3ev1amQH3lWCmHyt5qTbLCp/syXJRu/Kw2koXh44qayB1gMRxcL/gV8egmjN5xWSrYyfUtyw==",
"dependencies": {
"@babel/generator": "7.17.7",
"@babel/parser": "^7.20.5",
"@babel/traverse": "7.17.3",
"@babel/types": "7.17.0",
"javascript-natural-sort": "0.7.1",
"lodash": "^4.17.21"
},
"peerDependencies": {
"@vue/compiler-sfc": "3.x",
"prettier": "2.x - 3.x"
},
"peerDependenciesMeta": {
"@vue/compiler-sfc": {
"optional": true
}
}
},
"node_modules/@types/aria-query": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.1.tgz",
@@ -1725,7 +1982,6 @@
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dev": true,
"dependencies": {
"ms": "2.1.2"
},
@@ -3584,6 +3840,11 @@
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true
},
"node_modules/javascript-natural-sort": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz",
"integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw=="
},
"node_modules/jiti": {
"version": "1.19.1",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.19.1.tgz",
@@ -3625,6 +3886,17 @@
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/jsesc": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
"bin": {
"jsesc": "bin/jsesc"
},
"engines": {
"node": ">=4"
}
},
"node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
@@ -3942,8 +4214,7 @@
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/mz": {
"version": "2.7.0",
@@ -4663,7 +4934,6 @@
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz",
"integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==",
"dev": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
@@ -5416,6 +5686,14 @@
"npm": ">= 3.0.0"
}
},
"node_modules/source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
"integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
@@ -5802,6 +6080,14 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/to-fast-properties": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
"integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
"engines": {
"node": ">=4"
}
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+4 -1
View File
@@ -6,11 +6,14 @@
"dev": "next dev",
"build": "ANALYZE=true next build",
"start": "next start",
"lint": "next lint"
"lint": "next lint",
"format": "prettier --check \"**/*.{js,jsx,ts,tsx}\"",
"format:fix": "prettier --write \"**/*.{js,jsx,ts,tsx}\""
},
"dependencies": {
"@kodingdotninja/use-tailwind-breakpoint": "^0.0.5",
"@next/bundle-analyzer": "^13.4.12",
"@trivago/prettier-plugin-sort-imports": "^4.2.0",
"@types/node": "20.5.1",
"@types/react": "18.2.21",
"@types/react-dom": "18.2.7",
+24 -18
View File
@@ -9,35 +9,41 @@ module.exports = {
theme: {
extend: {
fontFamily: {
sans: ['var(--font-inter)'],
title: ['var(--font-title)'],
sans: ["var(--font-inter)"],
title: ["var(--font-title)"],
},
colors: {
'primary': {
DEFAULT: '#0086C7',
50: '#A8E3FF',
100: '#94DCFF',
200: '#6BCFFF',
300: '#42C1FF',
400: '#1AB4FF',
500: '#00A1F0',
600: '#0086C7',
700: '#00608F',
800: '#003A57',
900: '#00151F',
950: '#000203'
primary: {
DEFAULT: "#0086C7",
50: "#A8E3FF",
100: "#94DCFF",
200: "#6BCFFF",
300: "#42C1FF",
400: "#1AB4FF",
500: "#00A1F0",
600: "#0086C7",
700: "#00608F",
800: "#003A57",
900: "#00151F",
950: "#000203",
},
},
minHeight: {
// We use 100svh, falling back to the the --1svh var that we add as a polyfill for older browsers.
// The 9rem is for the header and footer.
// In the event that the polyfill fails, we fall back to 0.9vh as a guess of how much 1svh is.
content: ["calc(var(--1svh, 0.9vh) * 100 - 9rem)", "calc(100svh - 9rem)"],
content: [
"calc(var(--1svh, 0.9vh) * 100 - 9rem)",
"calc(100svh - 9rem)",
],
},
height: {
content: ["calc(var(--1svh, 0.9vh) * 100 - 9rem)", "calc(100svh - 9rem)"],
content: [
"calc(var(--1svh, 0.9vh) * 100 - 9rem)",
"calc(100svh - 9rem)",
],
},
},
},
plugins: [require('@tailwindcss/forms')],
plugins: [require("@tailwindcss/forms")],
};
+2 -1
View File
@@ -1,6 +1,7 @@
import { LatLngLiteral } from "leaflet";
import { Dispatch, MutableRefObject, SetStateAction } from "react";
import { LatLngLiteral } from "leaflet";
export enum TimeControl {
Classic = "Classic",
Rapid = "Rapid",
+8 -3
View File
@@ -1,4 +1,4 @@
import { atom, SetStateAction } from "jotai";
import { SetStateAction, atom } from "jotai";
export default function atomWithDebounce<T>(
initialValue: T,
@@ -37,9 +37,14 @@ export default function atomWithDebounce<T>(
onDebounceStart();
const nextTimeoutId = setTimeout(() => {
const nextTimeoutId = setTimeout(
() => {
onDebounceEnd();
}, nextValue === initialValue ? delayOnResetMilliseconds : delayMilliseconds);
},
nextValue === initialValue
? delayOnResetMilliseconds
: delayMilliseconds,
);
// set previous timeout atom in case it needs to get cleared
set(prevTimeoutAtom, nextTimeoutId);