{!mhsfData.loading ? (
<>
{(statisticType === "playerCount"
diff --git a/apps/www/src/components/ui/sidebar.tsx b/apps/www/src/components/ui/sidebar.tsx
new file mode 100644
index 0000000..532d6e3
--- /dev/null
+++ b/apps/www/src/components/ui/sidebar.tsx
@@ -0,0 +1,726 @@
+"use client"
+
+import * as React from "react"
+import { Slot } from "@radix-ui/react-slot"
+import { type VariantProps, cva } from "class-variance-authority"
+import { PanelLeftIcon } from "lucide-react"
+
+import { useIsMobile } from "@/lib/hooks/use-mobile"
+import { cn } from "@/lib/utils"
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+import { Separator } from "@/components/ui/separator"
+import {
+ Sheet,
+ SheetContent,
+ SheetDescription,
+ SheetHeader,
+ SheetTitle,
+} from "@/components/ui/sheet"
+import { Skeleton } from "@/components/ui/skeleton"
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
+} from "@/components/ui/tooltip"
+
+const SIDEBAR_COOKIE_NAME = "sidebar_state"
+const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
+const SIDEBAR_WIDTH = "16rem"
+const SIDEBAR_WIDTH_MOBILE = "18rem"
+const SIDEBAR_WIDTH_ICON = "3rem"
+const SIDEBAR_KEYBOARD_SHORTCUT = "b"
+
+type SidebarContextProps = {
+ state: "expanded" | "collapsed"
+ open: boolean
+ setOpen: (open: boolean) => void
+ openMobile: boolean
+ setOpenMobile: (open: boolean) => void
+ isMobile: boolean
+ toggleSidebar: () => void
+}
+
+const SidebarContext = React.createContext
(null)
+
+function useSidebar() {
+ const context = React.useContext(SidebarContext)
+ if (!context) {
+ throw new Error("useSidebar must be used within a SidebarProvider.")
+ }
+
+ return context
+}
+
+function SidebarProvider({
+ defaultOpen = true,
+ open: openProp,
+ onOpenChange: setOpenProp,
+ className,
+ style,
+ children,
+ ...props
+}: React.ComponentProps<"div"> & {
+ defaultOpen?: boolean
+ open?: boolean
+ onOpenChange?: (open: boolean) => void
+}) {
+ const isMobile = useIsMobile()
+ const [openMobile, setOpenMobile] = React.useState(false)
+
+ // This is the internal state of the sidebar.
+ // We use openProp and setOpenProp for control from outside the component.
+ const [_open, _setOpen] = React.useState(defaultOpen)
+ const open = openProp ?? _open
+ const setOpen = React.useCallback(
+ (value: boolean | ((value: boolean) => boolean)) => {
+ const openState = typeof value === "function" ? value(open) : value
+ if (setOpenProp) {
+ setOpenProp(openState)
+ } else {
+ _setOpen(openState)
+ }
+
+ // This sets the cookie to keep the sidebar state.
+ document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
+ },
+ [setOpenProp, open]
+ )
+
+ // Helper to toggle the sidebar.
+ const toggleSidebar = React.useCallback(() => {
+ return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open)
+ }, [isMobile, setOpen, setOpenMobile])
+
+ // Adds a keyboard shortcut to toggle the sidebar.
+ React.useEffect(() => {
+ const handleKeyDown = (event: KeyboardEvent) => {
+ if (
+ event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
+ (event.metaKey || event.ctrlKey)
+ ) {
+ event.preventDefault()
+ toggleSidebar()
+ }
+ }
+
+ window.addEventListener("keydown", handleKeyDown)
+ return () => window.removeEventListener("keydown", handleKeyDown)
+ }, [toggleSidebar])
+
+ // We add a state so that we can do data-state="expanded" or "collapsed".
+ // This makes it easier to style the sidebar with Tailwind classes.
+ const state = open ? "expanded" : "collapsed"
+
+ const contextValue = React.useMemo(
+ () => ({
+ state,
+ open,
+ setOpen,
+ isMobile,
+ openMobile,
+ setOpenMobile,
+ toggleSidebar,
+ }),
+ [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]
+ )
+
+ return (
+
+
+
+ {children}
+
+
+
+ )
+}
+
+function Sidebar({
+ side = "left",
+ variant = "sidebar",
+ collapsible = "offcanvas",
+ className,
+ children,
+ ...props
+}: React.ComponentProps<"div"> & {
+ side?: "left" | "right"
+ variant?: "sidebar" | "floating" | "inset"
+ collapsible?: "offcanvas" | "icon" | "none"
+}) {
+ const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
+
+ if (collapsible === "none") {
+ return (
+
+ {children}
+
+ )
+ }
+
+ if (isMobile) {
+ return (
+
+
+
+ Sidebar
+ Displays the mobile sidebar.
+
+ {children}
+
+
+ )
+ }
+
+ return (
+
+ {/* This is what handles the sidebar gap on desktop */}
+
+
+
+ )
+}
+
+function SidebarTrigger({
+ className,
+ onClick,
+ ...props
+}: React.ComponentProps) {
+ const { toggleSidebar } = useSidebar()
+
+ return (
+ {
+ onClick?.(event)
+ toggleSidebar()
+ }}
+ {...props}
+ >
+
+ Toggle Sidebar
+
+ )
+}
+
+function SidebarRail({ className, ...props }: React.ComponentProps<"button">) {
+ const { toggleSidebar } = useSidebar()
+
+ return (
+
+ )
+}
+
+function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
+ return (
+
+ )
+}
+
+function SidebarInput({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function SidebarHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function SidebarFooter({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function SidebarSeparator({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function SidebarContent({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function SidebarGroup({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function SidebarGroupLabel({
+ className,
+ asChild = false,
+ ...props
+}: React.ComponentProps<"div"> & { asChild?: boolean }) {
+ const Comp = asChild ? Slot : "div"
+
+ return (
+ svg]:size-4 [&>svg]:shrink-0",
+ "group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
+ className
+ )}
+ {...props}
+ />
+ )
+}
+
+function SidebarGroupAction({
+ className,
+ asChild = false,
+ ...props
+}: React.ComponentProps<"button"> & { asChild?: boolean }) {
+ const Comp = asChild ? Slot : "button"
+
+ return (
+ svg]:size-4 [&>svg]:shrink-0",
+ // Increases the hit area of the button on mobile.
+ "after:absolute after:-inset-2 md:after:hidden",
+ "group-data-[collapsible=icon]:hidden",
+ className
+ )}
+ {...props}
+ />
+ )
+}
+
+function SidebarGroupContent({
+ className,
+ ...props
+}: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function SidebarMenu({ className, ...props }: React.ComponentProps<"ul">) {
+ return (
+
+ )
+}
+
+function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) {
+ return (
+
+ )
+}
+
+const sidebarMenuButtonVariants = cva(
+ "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
+ {
+ variants: {
+ variant: {
+ default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
+ outline:
+ "bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]",
+ },
+ size: {
+ default: "h-8 text-sm",
+ sm: "h-7 text-xs",
+ lg: "h-12 text-sm group-data-[collapsible=icon]:p-0!",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+)
+
+function SidebarMenuButton({
+ asChild = false,
+ isActive = false,
+ variant = "default",
+ size = "default",
+ tooltip,
+ className,
+ ...props
+}: React.ComponentProps<"button"> & {
+ asChild?: boolean
+ isActive?: boolean
+ tooltip?: string | React.ComponentProps
+} & VariantProps) {
+ const Comp = asChild ? Slot : "button"
+ const { isMobile, state } = useSidebar()
+
+ const button = (
+
+ )
+
+ if (!tooltip) {
+ return button
+ }
+
+ if (typeof tooltip === "string") {
+ tooltip = {
+ children: tooltip,
+ }
+ }
+
+ return (
+
+ {button}
+
+
+ )
+}
+
+function SidebarMenuAction({
+ className,
+ asChild = false,
+ showOnHover = false,
+ ...props
+}: React.ComponentProps<"button"> & {
+ asChild?: boolean
+ showOnHover?: boolean
+}) {
+ const Comp = asChild ? Slot : "button"
+
+ return (
+ svg]:size-4 [&>svg]:shrink-0",
+ // Increases the hit area of the button on mobile.
+ "after:absolute after:-inset-2 md:after:hidden",
+ "peer-data-[size=sm]/menu-button:top-1",
+ "peer-data-[size=default]/menu-button:top-1.5",
+ "peer-data-[size=lg]/menu-button:top-2.5",
+ "group-data-[collapsible=icon]:hidden",
+ showOnHover &&
+ "peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0",
+ className
+ )}
+ {...props}
+ />
+ )
+}
+
+function SidebarMenuBadge({
+ className,
+ ...props
+}: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function SidebarMenuSkeleton({
+ className,
+ showIcon = false,
+ ...props
+}: React.ComponentProps<"div"> & {
+ showIcon?: boolean
+}) {
+ // Random width between 50 to 90%.
+ const width = React.useMemo(() => {
+ return `${Math.floor(Math.random() * 40) + 50}%`
+ }, [])
+
+ return (
+
+ {showIcon && (
+
+ )}
+
+
+ )
+}
+
+function SidebarMenuSub({ className, ...props }: React.ComponentProps<"ul">) {
+ return (
+
+ )
+}
+
+function SidebarMenuSubItem({
+ className,
+ ...props
+}: React.ComponentProps<"li">) {
+ return (
+
+ )
+}
+
+function SidebarMenuSubButton({
+ asChild = false,
+ size = "md",
+ isActive = false,
+ className,
+ ...props
+}: React.ComponentProps<"a"> & {
+ asChild?: boolean
+ size?: "sm" | "md"
+ isActive?: boolean
+}) {
+ const Comp = asChild ? Slot : "a"
+
+ return (
+ svg]:text-sidebar-accent-foreground flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 outline-hidden focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
+ "data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground",
+ size === "sm" && "text-xs",
+ size === "md" && "text-sm",
+ "group-data-[collapsible=icon]:hidden",
+ className
+ )}
+ {...props}
+ />
+ )
+}
+
+export {
+ Sidebar,
+ SidebarContent,
+ SidebarFooter,
+ SidebarGroup,
+ SidebarGroupAction,
+ SidebarGroupContent,
+ SidebarGroupLabel,
+ SidebarHeader,
+ SidebarInput,
+ SidebarInset,
+ SidebarMenu,
+ SidebarMenuAction,
+ SidebarMenuBadge,
+ SidebarMenuButton,
+ SidebarMenuItem,
+ SidebarMenuSkeleton,
+ SidebarMenuSub,
+ SidebarMenuSubButton,
+ SidebarMenuSubItem,
+ SidebarProvider,
+ SidebarRail,
+ SidebarSeparator,
+ SidebarTrigger,
+ useSidebar,
+}
diff --git a/apps/www/src/lib/top-loader.tsx b/apps/www/src/components/util/top-loader.tsx
similarity index 87%
rename from apps/www/src/lib/top-loader.tsx
rename to apps/www/src/components/util/top-loader.tsx
index 3de011a..e1b6c68 100644
--- a/apps/www/src/lib/top-loader.tsx
+++ b/apps/www/src/components/util/top-loader.tsx
@@ -27,17 +27,15 @@
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
-
-// NextTopLoader.tsx
'use client';
import Loader from 'nextjs-toploader';
import { usePathname } from 'next/navigation';
-import {useEffect} from "react"
+import {ComponentProps, useEffect} from "react"
import * as NProgress from "nprogress";
import { useTheme } from 'next-themes';
-export default function NextTopLoader() {
+export default function NextTopLoader(props: ComponentProps) {
const pathname = usePathname();
const theme = useTheme()
@@ -46,6 +44,6 @@ export default function NextTopLoader() {
}, [pathname]);
return (
-
+
)
}
\ No newline at end of file
diff --git a/apps/www/src/lib/hooks/use-filters.tsx b/apps/www/src/lib/hooks/use-filters.tsx
index 1674ccf..3b47376 100644
--- a/apps/www/src/lib/hooks/use-filters.tsx
+++ b/apps/www/src/lib/hooks/use-filters.tsx
@@ -28,12 +28,21 @@
* OTHER DEALINGS IN THE SOFTWARE.
*/
-import { useEffect, useState } from "react";
+import { useEffect, useMemo, useState } from "react";
import type { OnlineServer } from "../types/mh-server";
import { useQueryState } from "nuqs";
import { toast } from "sonner";
import { tryCatch } from "../try-catch";
import { transpileTypeScript } from "@/app/(sl-modification-frame)/servers/embedded/sl-modification-frame/file/[filename]/page";
+import { useUser } from "@clerk/nextjs";
+import type { ClerkCustomActivatedModification } from "@/components/feat/server-list/modification/modification-file-creation-dialog";
+
+type EmbeddedFilter = {
+ identifier: string;
+ functionFilter: (server: OnlineServer) => boolean;
+};
+
+type SortFunction = (object1: K, object2: K) => number;
export function useFilters(data: OnlineServer[]) {
const [filteredData, setFilteredData] = useState(data);
@@ -43,9 +52,38 @@ export function useFilters(data: OnlineServer[]) {
"Haven't connected thread yet (if stuck, select the other tab, and come back)",
);
const [testModeLoading, setTestModeLoading] = useState(true);
+ const [loading, setLoading] = useState(true);
+ const [filters, setFilters] = useState([]);
+ const [sort, setSort] = useState | null>(null);
+ const { user, isSignedIn } = useUser();
+
+ const updateServers = (newFilters: EmbeddedFilter[]) => {
+ const modificationMap = data.map((v) =>
+ newFilters.map((c) => c.functionFilter(v)),
+ );
+ const resultData = data.filter(
+ (_, i) => !modificationMap[i].includes(false),
+ );
+ const sortedData = sort === null ? resultData : resultData.sort(sort);
+
+ console.table({ sortedData, modificationMap, resultData, data });
+
+
+ if (sortedData.length !== 0)
+ setFilteredData(sortedData);
+ };
+
+ // biome-ignore lint: bruh
+ useEffect(() => {
+ if (filteredData.length === 0 || data.length === 0) {
+ window.dispatchEvent(new Event("update-modification-stack"));
+ } else setLoading(false);
+ }, [data, filteredData.length]);
useEffect(() => {
- if (filteredData.length === 0) setFilteredData(data);
+ if (data.length === 0) {
+ window.dispatchEvent(new Event("update-modification-stack"));
+ } else setLoading(false);
}, [data, filteredData.length]);
const testModeInit = (type: "filter" | "sort") => {
@@ -63,7 +101,7 @@ export function useFilters(data: OnlineServer[]) {
);
if (error) {
setTestModeStatus(
- "Failed to transpile TypeScript! Error: " + error.message,
+ `Failed to transpile TypeScript! Error: ${error.message},`,
);
setTestModeLoading(false);
return;
@@ -91,18 +129,18 @@ export function useFilters(data: OnlineServer[]) {
(async () =>
type === "filter"
? new Function(
- "server",
- `${functionBody}
+ "server",
+ `${functionBody}
return filter(server)`,
- )
+ )
: new Function(
- "serverA",
- "serverB",
- `${functionBody}
+ "serverA",
+ "serverB",
+ `${functionBody}
return sort(serverA, serverB)`,
- ))(),
+ ))(),
);
if (filterErr) {
setTestModeStatus(
@@ -112,14 +150,14 @@ export function useFilters(data: OnlineServer[]) {
return;
}
if (typeof filterFunc === "function") {
- setTestModeStatus("Compiled in " + (Date.now() - startTime) + "ms");
+ setTestModeStatus(`Compiled in ${Date.now() - startTime} ms`);
toast.promise(
async () => {
- let newServers = [];
+ let newServers: OnlineServer[] = [];
if (type === "filter") {
newServers = data.filter((c) => filterFunc(c));
setTestModeStatus(
- "Server count " + data.length + " -> " + newServers.length,
+ `Server count ${data.length} -> ${newServers.length}`,
);
if (newServers.length === 0)
setTestModeStatus(
@@ -129,9 +167,12 @@ export function useFilters(data: OnlineServer[]) {
}
if (type === "sort") {
newServers = data.sort((a, b) => filterFunc(a, b));
- setTestModeStatus("Sorted " + newServers.length + " servers.");
- console.log(newServers, data.sort((a, b) => filterFunc(a, b)))
- console.log(filterFunc)
+ setTestModeStatus(`Sorted ${newServers.length} servers.`);
+ console.log(
+ newServers,
+ data.sort((a, b) => filterFunc(a, b)),
+ );
+ console.log(filterFunc);
setFilteredData(() => [...newServers]);
}
@@ -155,6 +196,7 @@ export function useFilters(data: OnlineServer[]) {
}
};
+ // biome-ignore lint: I'm gonna turn this off :sob:
useEffect(() => {
if (data.length !== 0) {
window.addEventListener("test-mode.enable.filter", () =>
@@ -166,5 +208,92 @@ export function useFilters(data: OnlineServer[]) {
}
}, [t, data]);
- return { filteredData, testModeEnabled, testModeLoading, testModeStatus };
+ // biome-ignore lint: I'm gonna turn this off :sob:
+ useEffect(() => {
+ if (!t)
+ window.addEventListener("update-modification-stack", async () => {
+ await user?.reload();
+ let newFilters: EmbeddedFilter[] = [];
+ if (isSignedIn) {
+ const activatedModifications =
+ (user.unsafeMetadata
+ .activatedModifications as ClerkCustomActivatedModification[]) ??
+ [];
+ const activeModifications = activatedModifications.filter(
+ (c) => c.active && c.testMode === "filter",
+ );
+
+ const resolvedModifications = (await Promise.all(
+ activeModifications.map(async (c) => {
+ const functionBody = c.transpiledContents
+ .replace(/export default(?!.*[;])/g, "") // Avoid replacing if followed by a semicolon
+ .replace(/export(?!.*[;])/g, ""); // Avoid replacing if followed by a semicolon
+ const { error: filterErr, data: filterFunc } = await tryCatch(
+ (async () =>
+ c.testMode === "filter"
+ ? new Function(
+ "server",
+ `${functionBody}
+
+ return filter(server)`,
+ )
+ : new Function(
+ "serverA",
+ "serverB",
+ `${functionBody}
+
+ return sort(serverA, serverB)`,
+ ))(),
+ );
+
+ if (filterErr) {
+ toast.error(
+ `Couldn't enable modification '${c.friendlyName}'. Please lint and test again.`,
+ );
+ return { identifier: `file-${c.originalFileName}.ts`, functionFilter: () => true };
+ }
+
+ if (typeof filterFunc === "function") {
+ return { identifier: `file-${c.originalFileName}.ts`, functionFilter: filterFunc };
+ }
+
+ toast.error(
+ `Couldn't enable modification '${c.friendlyName}'. Please lint and test again.`,
+ );
+ return { identifier: `file-${c.originalFileName}.ts`, functionFilter: () => true };
+ }),
+ )) as EmbeddedFilter[];
+
+ // avoid duplicates
+ resolvedModifications.forEach((item) => {
+ setFilters((c) => {
+ if (c.findIndex((i) => i.identifier === item.identifier) === -1)
+ return [
+ ...c,
+ item
+ ]
+ else return c;
+ });
+ })
+
+ newFilters = resolvedModifications.map((item) => {
+ return item;
+ });
+ }
+
+ console.log(newFilters);
+
+ updateServers(newFilters);
+ });
+ }, [data]);
+ console.log(filters);
+
+ return {
+ filteredData,
+ testModeEnabled,
+ testModeLoading,
+ testModeStatus,
+ filterCount: filters.filter((item, index, array) => array.indexOf(item) === index).length + (sort === null ? 1 : 0),
+ loading,
+ };
}
diff --git a/apps/www/src/lib/list.ts b/apps/www/src/lib/list.ts
deleted file mode 100644
index b6fe759..0000000
--- a/apps/www/src/lib/list.ts
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * MHSF, Minehut Server List
- * All external content is rather licensed under the ECA Agreement
- * located here: https://mhsf.app/docs/legal/external-content-agreement
- *
- * All code under MHSF is licensed under the MIT License
- * by open source contributors
- *
- * Copyright (c) 2025 dvelo
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to
- * deal in the Software without restriction, including without limitation the
- * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
- * sell copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
- * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
- * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
- * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
- * OTHER DEALINGS IN THE SOFTWARE.
- */
-
-import { toast } from "sonner";
-import { OnlineServer } from "./types/mh-server";
-import MiniMessage from "minimessage-js";
-
-var numberOfItemsInView = 20;
-
-export default class ServersList {
- servers: Array = [];
- currentServers: Array = [];
- private filters: Array<(server: OnlineServer) => Promise> = [];
- extraData: any = { total_players: 0, total_servers: 0 };
- private it = 0;
- hasMore = true;
-
- constructor(filters: Array<(server: OnlineServer) => Promise>) {
- this.filters = filters;
- }
- getRandomServer() {
- return this.servers[Math.floor(Math.random() * this.servers.length)];
- }
- getExtraData() {
- return this.extraData;
- }
- fetchDataAndFilter(): Promise {
- return new Promise((g, bc) => {
- fetch("https://api.minehut.com/servers")
- .then((b) => {
- if (!b.ok) {
- console.log(
- "%c[MHSF] STOP! There was an error while requesting Minehut's API! Heres the fetch object for debugging: ",
- "font-weight: bold",
- b
- );
- toast.error(`
- Error while grabbing servers from API.
- If this is happening alot, make a new issue on GitHub
- `);
- bc();
- }
- if (b.ok)
- b.json().then((serversb) => {
- var serversWithoutFilt: Array = serversb.servers;
- var serversWithFilt: Array = [];
- this.extraData.total_players = serversb.total_players;
- this.extraData.total_servers = serversb.total_servers;
- serversWithoutFilt.forEach((server: OnlineServer, im) => {
- var good = true;
- const filterEach = () => {
- return new Promise((g, b) => {
- if (arrayEmpty(this.filters)) {
- g(true);
- serversWithFilt = serversWithoutFilt;
- }
- this.filters.forEach((filter, i) => {
- // Test for if filter is compatiable with server
- filter(server).then((b) => {
- if (!b) good = false;
- if (i == this.filters.length - 1 && good) {
- serversWithFilt.push(server);
- }
- if (i == this.filters.length - 1) {
- g(true);
- }
- });
- });
- });
- };
-
- filterEach().then(() => {
- if (im == serversWithoutFilt.length - 1) {
- this.servers = serversWithFilt;
- g(true);
- }
- });
- });
- });
- })
- .catch((b) => {
- toast.error(`
- Error while grabbing servers from API.
- If this is happening alot, make a new issue on GitHub
- `);
- console.log(
- "%c[MHSF] STOP! There was an error while requesting Minehut's API! Heres the error for debugging: ",
- "font-weight: bold",
- b
- );
- bc();
- });
- });
- }
-
- moveListDown() {
- const slicedArray = this.servers.slice(
- this.it * numberOfItemsInView,
- this.it * numberOfItemsInView + numberOfItemsInView
- );
- this.currentServers = this.currentServers.concat(slicedArray);
- this.it++;
- console.log(
- "%c[MHSF] Moved list down! Updated entries: ",
- "font-weight: bold",
- slicedArray
- );
- if (slicedArray.length != numberOfItemsInView) {
- this.hasMore = false;
- }
- }
-
- editFilters(newFilters: Array<(server: OnlineServer) => Promise>) {
- this.filters = newFilters;
- this.servers = [];
- this.it = 0;
- this.currentServers = [];
- this.hasMore = true;
- }
-
- async getMOTDs(
- list: Array<{ server: string; motd: string }>
- ): Promise> {
- const result: Array<{ server: string; motd: string }> = [];
- const mm = MiniMessage.miniMessage();
- list.forEach((c) => {
- try {
- result.push({
- server: c.server,
- motd: mm.toHTML(mm.deserialize(c.motd)),
- });
- } catch (e) {
- console.log(e);
- }
- });
-
- return result;
- }
-}
-
-function arrayEmpty(a: Array) {
- return a.length == 0;
-}
diff --git a/apps/www/src/lib/motdEngine.ts b/apps/www/src/lib/motdEngine.ts
deleted file mode 100644
index b9f5168..0000000
--- a/apps/www/src/lib/motdEngine.ts
+++ /dev/null
@@ -1,242 +0,0 @@
-/*
- * MHSF, Minehut Server List
- * All external content is rather licensed under the ECA Agreement
- * located here: https://mhsf.app/docs/legal/external-content-agreement
- *
- * All code under MHSF is licensed under the MIT License
- * by open source contributors
- *
- * Copyright (c) 2025 dvelo
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to
- * deal in the Software without restriction, including without limitation the
- * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
- * sell copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
- * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
- * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
- * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
- * OTHER DEALINGS IN THE SOFTWARE.
- */
-
-// rendering engine for MOTDs (aka Minehut)
-
-const divList: any = {
- black: "black",
- dark_blue: "text-[#1D4ED8]",
- dark_green: "text-[#166634]",
- dark_red: "text-[#991b1b]",
- dark_purple: "text-[#6b21a8]",
- gold: "text-[#facc15]",
- gray: "text-[#4b5563]",
- dark_gray: "text-[#1f2937]",
- blue: "text-[#60a5fa]",
- green: "text-[#4ade80]",
- aqua: "text-[#22d3ee]",
- red: "text-[#f87171]",
- light_purple: "text-[#d8b4fe]",
- yellow: "text-[#facc15]",
- white: "text-white",
- strikethrough: "line-through",
- st: "line-through",
- u: "underline",
- underlined: "underline",
- italic: "italic",
- em: "italic",
- i: "italic",
- bold: "font-bold",
- b: "font-bold",
-};
-
-export default function parseToHTML(m: string, tw?: boolean): Promise {
- return new Promise((g, b) => {
- fetch("https://webui.advntr.dev/api/mini-to-json", {
- method: "POST",
- body: JSON.stringify({
- miniMessage: m,
- placeholders: { stringPlaceholders: {} },
- }),
- }).then((j) => {
- j.json().then((l) => {
- if (typeof l === "string") {
- g(l);
- } else {
- // This text has custom properties
- var allHTML = "";
- var root: Array = l.extra;
- if (root == undefined) {
- var curClass = "";
- var contents = "";
- if (l.color != undefined) {
- if (divList[l.color] == undefined) {
- curClass +=
- curClass == ""
- ? "text-[" + l.color + "]"
- : " text-[" + l.color + "]";
- } else {
- curClass +=
- curClass == "" ? divList[l.color] : " " + divList[l.color];
- }
- }
- if (l.strikethrough == true) {
- curClass += curClass == "" ? "line-through" : " line-through";
- }
- if (l.bold == true) {
- curClass += curClass == "" ? "font-bold" : " font-bold";
- }
- if (l.italic == true) {
- curClass += curClass == "" ? "italic" : " italic";
- }
- allHTML += createHTML("span", curClass, l.text + contents);
- } else {
- root.forEach(function (i) {
- if (typeof i === "string") {
- allHTML += i;
- } else {
- var curClass = "";
- var contents = "";
- if (i.extra != undefined) {
- i.extra.forEach(function (m) {
- contents += objToHTML(m);
- });
- }
- if (i.color != undefined) {
- if (divList[i.color] == undefined) {
- curClass +=
- curClass == ""
- ? "text-[" + i.color + "]"
- : " text-[" + i.color + "]";
- } else {
- curClass +=
- curClass == ""
- ? divList[i.color]
- : " " + divList[i.color];
- }
- }
- if (i.strikethrough == true) {
- curClass += curClass == "" ? "line-through" : " line-through";
- }
- if (i.bold == true) {
- curClass += curClass == "" ? "font-bold" : " font-bold";
- }
- if (i.italic == true) {
- curClass += curClass == "" ? "italic" : " italic";
- }
- allHTML += createHTML("span", curClass, l.text + contents);
- }
- });
- }
- g("" + allHTML + " ");
- }
- });
- if (!j.ok) {
- b("Problem while parsing MiniMessage");
- }
- });
- });
-}
-
-function objToHTML(i: Element | string): string {
- if (typeof i == "string") {
- return i;
- }
- var curClass = "";
- var contents = "";
- if (i.extra != undefined) {
- i.extra.forEach((m) => {
- contents += objToHTML(m);
- });
- }
- if (i.color != undefined) {
- if (divList[i.color] == undefined) {
- curClass +=
- curClass == "" ? "text-[" + i.color + "]" : " text-[" + i.color + "]";
- } else {
- curClass += curClass == "" ? divList[i.color] : " " + divList[i.color];
- }
- }
- if (i.strikethrough == true) {
- curClass += curClass == "" ? "line-through" : " line-through";
- }
- if (i.bold == true) {
- curClass += curClass == "" ? "font-bold" : " font-bold";
- }
- if (i.italic == true) {
- curClass += curClass == "" ? "italic" : " italic";
- }
-
- return createHTML("span", curClass, i.text + contents);
-}
-
-function createHTML(
- tag: string,
- className: string,
- contents: string,
- tw?: boolean
-) {
- if (className == undefined) className = "";
- if (contents == undefined) contents = "";
- if (contents == "\n") contents = " ";
-
- if (tw == false || tw == undefined) {
- return (
- "<" +
- tag +
- ' style="' +
- colorConvert(className) +
- '">' +
- contents +
- "" +
- tag +
- ">"
- );
- } else {
- return (
- "<" + tag + ' class="' + className + '">' + contents + "" + tag + ">"
- );
- }
-}
-type Element = {
- text: string;
- extra: Array;
- color?: string;
- bold?: boolean;
- italic?: boolean;
- strikethrough?: boolean;
-};
-
-function colorConvert(className: string) {
- const classes = className.split(" ");
- let result = "";
- classes.forEach((classUnique) => {
- if (classUnique.startsWith("text-") && classUnique != "text-white") {
- if (classUnique.startsWith("text-[")) {
- result +=
- "color: " + classUnique.substring(6, classUnique.length - 1) + "; ";
- } else {
- result +=
- "color: " + classUnique.substring(5, classUnique.length) + "; ";
- }
- }
- if (classUnique.startsWith("font-bold")) {
- result += "font-weight: 700; ";
- }
- if (classUnique.startsWith("line-through")) {
- result += "text-decoration-line: line-through; ";
- }
- if (classUnique.startsWith("italic")) {
- result += "font-style: italic; ";
- }
- });
-
- return result;
-}
diff --git a/apps/www/src/lib/single.ts b/apps/www/src/lib/single.ts
deleted file mode 100644
index 4d4d05d..0000000
--- a/apps/www/src/lib/single.ts
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * MHSF, Minehut Server List
- * All external content is rather licensed under the ECA Agreement
- * located here: https://mhsf.app/docs/legal/external-content-agreement
- *
- * All code under MHSF is licensed under the MIT License
- * by open source contributors
- *
- * Copyright (c) 2025 dvelo
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to
- * deal in the Software without restriction, including without limitation the
- * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
- * sell copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
- * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
- * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
- * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
- * OTHER DEALINGS IN THE SOFTWARE.
- */
-
-import { serverOwned } from "./api";
-import { OnlineServer, ServerResponse } from "./types/mh-server";
-import { toast } from "sonner";
-
-export default class ServerSingle {
- private name = "";
- private onlineObj: OnlineServer | undefined = undefined;
- private offlineObj: ServerResponse | undefined = undefined;
- online = false;
-
- constructor(name: string) {
- this.name = name;
- }
- setName(newName: string) {
- this.name = newName;
- }
-
- isCustomized(): Promise {
- return serverOwned(this.name);
- }
-
- init(skipOnline?: boolean): Promise {
- return new Promise((g, bc) => {
- fetch("https://api.minehut.com/server/" + this.name + "?byName=true")
- .then((d) => {
- if (d.ok) {
- d.json().then((m) => {
- this.online = m.server.online;
- this.offlineObj = m.server;
- if (this.online == true && skipOnline != true) {
- fetch("https://api.minehut.com/servers").then((l) =>
- l.json().then((o) => {
- if (o.servers.find((j: OnlineServer) => j.name == this.name) == undefined) {
- g(true);
- }
- o.servers.forEach((j: OnlineServer) => {
- if (j.name == this.name) {
- this.onlineObj = j;
- g(true);
- }
- });
- })
- );
- } else g(true);
- });
- } else {
- console.log(
- "%c[MHSF] STOP! There was an error while requesting Minehut's API! Heres the fetch object for debugging: ",
- "font-weight: bold",
- d
- );
- toast.error(`
- Error while grabbing servers from API.
- If this is happening alot, make a new issue on GitHub
- `);
- bc();
- }
- })
- .catch((b) => {
- toast.error(`
- Error while grabbing servers from API.
- If this is happening alot, make a new issue on GitHub
- `);
- console.log(
- "%c[MHSF] STOP! There was an error while requesting Minehut's API! Heres the error for debugging: ",
- "font-weight: bold",
- b
- );
- bc();
- });
- });
- }
-
- getAuthor(): string | undefined {
- if (this.onlineObj == undefined || this.onlineObj.author == undefined) {
- return undefined;
- } else {
- return this.onlineObj.author;
- }
- }
-
- grabOnline(): OnlineServer | undefined {
- return this.onlineObj;
- }
- grabOffline(): ServerResponse | undefined {
- if (this.offlineObj != undefined) {
- this.offlineObj.__unix =
- "Time in this file is defined in Unix time. Convert it in something like https://www.epochconverter.com/ (in milliseconds)";
- }
- return this.offlineObj;
- }
-}
diff --git a/apps/www/src/pages/api/v1/get-status.ts b/apps/www/src/pages/api/v1/get-status.ts
index 7b31c54..f7cbf07 100644
--- a/apps/www/src/pages/api/v1/get-status.ts
+++ b/apps/www/src/pages/api/v1/get-status.ts
@@ -45,6 +45,8 @@ export default async function handler(
const result = await betterStackResult.json();
const url = await betterStackURL.json();
+ console.log(result)
+
const filtered = result.data.filter(
(c: any) =>
c.attributes.ends_at === null &&
diff --git a/apps/www/src/pages/api/v1/motd.ts b/apps/www/src/pages/api/v1/motd.ts
deleted file mode 100644
index 2dfa011..0000000
--- a/apps/www/src/pages/api/v1/motd.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * MHSF, Minehut Server List
- * All external content is rather licensed under the ECA Agreement
- * located here: https://mhsf.app/docs/legal/external-content-agreement
- *
- * All code under MHSF is licensed under the MIT License
- * by open source contributors
- *
- * Copyright (c) 2025 dvelo
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to
- * deal in the Software without restriction, including without limitation the
- * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
- * sell copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
- * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
- * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
- * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
- * OTHER DEALINGS IN THE SOFTWARE.
- */
-
-import { NextApiRequest, NextApiResponse } from "next";
-
-export default async function handler(
- req: NextApiRequest,
- res: NextApiResponse
-) {
- // Deprecated - moved exclusively to the client
- // const initalList: Array<{ server: string; motd: string }> = req.body.motd;
- // const resultedList: Array<{ server: string; motd: string }> = [];
- // var interval = 0;
- // if (initalList != undefined && initalList.forEach != undefined) {
- // initalList.forEach((c, i) => {
- // parseToHTML(c.motd)
- // .then((m) => {
- // interval++;
- // resultedList.push({ motd: m, server: c.server });
- // if (interval == initalList.length) {
- // res.send({ result: resultedList });
- // }
- // })
- // .catch(() => {
- // resultedList.push({ motd: "Error to grab MOTD", server: c.server });
- // if (i == initalList.length - 1) {
- // res.send({ result: resultedList });
- // }
- // });
- // });
- // } else {
- // res.status(400).send({
- // message: "Wrong structure.. you might be using the legacy MOTD.",
- // });
- // }
-}
diff --git a/apps/www/src/pages/api/v1/server/get/[server]/index.ts b/apps/www/src/pages/api/v1/server/get/[server]/index.ts
index 64eabd8..9a0cd3b 100644
--- a/apps/www/src/pages/api/v1/server/get/[server]/index.ts
+++ b/apps/www/src/pages/api/v1/server/get/[server]/index.ts
@@ -77,28 +77,29 @@ export default async function handler(
const serverData = await findServerData(server as string);
if (!serverData.exists) return res.status(404).send({ server: null });
- const mongo = new MongoClient(process.env.MONGO_DB as string);
+ const mongo = new MongoClient(process.env.MONGO_DB as string);
try {
await mongo.connect();
const db = mongo.db(process.env.CUSTOM_MONGO_DB ?? "mhsf");
+ const stats = mongo.db("mhsf")
const userId = req.cookies.userId;
// Run queries in parallel
const [favoriteData, customizationData, playerData, achievements] =
await Promise.all([
- findFavoriteData(serverData.name, userId, db, {
+ findFavoriteData(serverData.name, userId, stats, {
maxFavoriteEntries,
favoriteTimespanStart,
favoriteTimespanEnd,
}),
findCustomizationData(serverData.name, userId, db),
- findPlayerData(serverData.name, db, {
+ findPlayerData(serverData.name, stats, {
maxPlayerEntries,
playerTimespanStart,
playerTimespanEnd,
}),
- findAchievements(serverData.name, db, {
+ findAchievements(serverData.name, stats, {
maxAchievementEntries,
achievementTimespanStart,
achievementTimespanEnd,
diff --git a/package.json b/package.json
index c611414..c11bf82 100644
--- a/package.json
+++ b/package.json
@@ -16,5 +16,11 @@
"node": ">=18"
},
"packageManager": "yarn@1.22.22",
- "workspaces": ["apps/*", "packages/*"]
+ "workspaces": [
+ "apps/*",
+ "packages/*"
+ ],
+ "dependencies": {
+ "cloc": "^2.4.0-cloc"
+ }
}
diff --git a/yarn.lock b/yarn.lock
index 4c9bad4..2918e1e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2139,6 +2139,11 @@
resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz#6f766faa975f8738269ebb8a23bad4f5a8d2faec"
integrity sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==
+"@radix-ui/react-compose-refs@1.1.2":
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz#a2c4c47af6337048ee78ff6dc0d090b390d2bb30"
+ integrity sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==
+
"@radix-ui/react-context-menu@^2.1.5", "@radix-ui/react-context-menu@^2.2.6":
version "2.2.6"
resolved "https://registry.yarnpkg.com/@radix-ui/react-context-menu/-/react-context-menu-2.2.6.tgz#752fd1d91f92bba287ef2b558770f4ca7d74523e"
@@ -2748,7 +2753,7 @@
aria-hidden "^1.2.4"
react-remove-scroll "^2.6.3"
-"@radix-ui/react-separator@^1.1.0":
+"@radix-ui/react-separator@^1.1.0", "@radix-ui/react-separator@^1.1.2":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@radix-ui/react-separator/-/react-separator-1.1.2.tgz#24c5450db20f341f2b743ed4b07b907e18579216"
integrity sha512-oZfHcaAp2Y6KFBX6I5P1u7CQoy4lheCGiYj+pGFrHy8E/VNRb5E39TkTr3JrV520csPBTZjkuKFdEsjS5EUNKQ==
@@ -2777,13 +2782,20 @@
dependencies:
"@radix-ui/react-compose-refs" "1.1.1"
-"@radix-ui/react-slot@1.1.2", "@radix-ui/react-slot@^1.1.0", "@radix-ui/react-slot@^1.1.2":
+"@radix-ui/react-slot@1.1.2", "@radix-ui/react-slot@^1.1.0":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.1.2.tgz#daffff7b2bfe99ade63b5168407680b93c00e1c6"
integrity sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==
dependencies:
"@radix-ui/react-compose-refs" "1.1.1"
+"@radix-ui/react-slot@^1.2.0":
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.2.0.tgz#57727fc186ddb40724ccfbe294e1a351d92462ba"
+ integrity sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w==
+ dependencies:
+ "@radix-ui/react-compose-refs" "1.1.2"
+
"@radix-ui/react-switch@1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-switch/-/react-switch-1.1.0.tgz#fcf8e778500f1d60d4b2bec2fc3fad77a7c118e3"
@@ -4598,7 +4610,7 @@ citty@^0.1.6:
dependencies:
consola "^3.2.3"
-class-variance-authority@0.7.1, class-variance-authority@^0.7.0, class-variance-authority@^0.7.1:
+class-variance-authority@0.7.1, class-variance-authority@^0.7.1:
version "0.7.1"
resolved "https://registry.yarnpkg.com/class-variance-authority/-/class-variance-authority-0.7.1.tgz#4008a798a0e4553a781a57ac5177c9fb5d043787"
integrity sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==
@@ -4650,6 +4662,11 @@ cliui@^8.0.1:
strip-ansi "^6.0.1"
wrap-ansi "^7.0.0"
+cloc@^2.4.0-cloc:
+ version "2.4.0-cloc"
+ resolved "https://registry.yarnpkg.com/cloc/-/cloc-2.4.0-cloc.tgz#d73e598c87c8cca57c391c79ec9e5268b7d9c225"
+ integrity sha512-7SH5scbWgvVkg0LLUsGpCBnUnB9hCvryGFq9uZsMsuOm/JcxDs+6XB/EBuu446MzfCV0K7ydfTxB0bkEEbHHvw==
+
clone@^1.0.2:
version "1.0.4"
resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
@@ -6448,6 +6465,15 @@ framer-motion@^11.3.8:
motion-utils "^11.18.1"
tslib "^2.4.0"
+framer-motion@^12.7.4:
+ version "12.7.4"
+ resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-12.7.4.tgz#50aeb8b5b5a672dea931bdb74956d7b526bf0b4b"
+ integrity sha512-jX0bPsTmU0oPZTYz/dVyD0dmOyEOEJvdn0TaZBE5I8g2GvVnnQnW9f65cJnoVfUkY3WZWNXGXnPbVA9YnaIfVA==
+ dependencies:
+ motion-dom "^12.7.4"
+ motion-utils "^12.7.2"
+ tslib "^2.4.0"
+
fresh@0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
@@ -8169,10 +8195,10 @@ lucide-react@^0.474.0:
resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.474.0.tgz#9fcaa96250fa2de0b3e2803d4ad744eaea572247"
integrity sha512-CmghgHkh0OJNmxGKWc0qfPJCYHASPMVSyGY8fj3xgk4v84ItqDg64JNKFZn5hC6E0vHi6gxnbCgwhyVB09wQtA==
-lucide-react@^0.479.0:
- version "0.479.0"
- resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.479.0.tgz#7321f979a389ec5dd86747b2deb6444cf0922f8d"
- integrity sha512-aBhNnveRhorBOK7uA4gDjgaf+YlHMdMhQ/3cupk6exM10hWlEU+2QtWYOfhXhjAsmdb6LeKR+NZnow4UxRRiTQ==
+lucide-react@^0.487.0:
+ version "0.487.0"
+ resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.487.0.tgz#c18a463404e8ef106d46a7c9cceddf9fc8b9ff6b"
+ integrity sha512-aKqhOQ+YmFnwq8dWgGjOuLc8V1R9/c/yOd+zDY4+ohsR2Jo05lSGc3WsstYPIzcTpeosN7LoCkLReUUITvaIvw==
luxon@~3.5.0:
version "3.5.0"
@@ -9476,11 +9502,31 @@ motion-dom@^11.18.1:
dependencies:
motion-utils "^11.18.1"
+motion-dom@^12.7.4:
+ version "12.7.4"
+ resolved "https://registry.yarnpkg.com/motion-dom/-/motion-dom-12.7.4.tgz#80f5f8d706e94bc29f6f4f4afa300ff9e1f976a2"
+ integrity sha512-1ZUHAoSUMMxP6jPqyxlk9XUfb6NxMsnWPnH2YGhrOhTURLcXWbETi6eemoKb60Pe32NVJYduL4B62VQSO5Jq8Q==
+ dependencies:
+ motion-utils "^12.7.2"
+
motion-utils@^11.18.1:
version "11.18.1"
resolved "https://registry.yarnpkg.com/motion-utils/-/motion-utils-11.18.1.tgz#671227669833e991c55813cf337899f41327db5b"
integrity sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA==
+motion-utils@^12.7.2:
+ version "12.7.2"
+ resolved "https://registry.yarnpkg.com/motion-utils/-/motion-utils-12.7.2.tgz#99b673d8851583b325bd0c8b0f04c5bf42b9b818"
+ integrity sha512-XhZwqctxyJs89oX00zn3OGCuIIpVevbTa+u82usWBC6pSHUd2AoNWiYa7Du8tJxJy9TFbZ82pcn5t7NOm1PHAw==
+
+motion@^12.7.4:
+ version "12.7.4"
+ resolved "https://registry.yarnpkg.com/motion/-/motion-12.7.4.tgz#d0a6032ccc6323e996caf0bc2ba6d0262fcc6214"
+ integrity sha512-MBGrMbYageHw4iZJn+pGTr7abq5n53jCxYkhFC1It3vYukQPRWg5zij46MnwYGpLR8KG465MLHSASXot9edYOw==
+ dependencies:
+ framer-motion "^12.7.4"
+ tslib "^2.4.0"
+
mri@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b"