import equal from "fast-deep-equal"; import { ComponentProps, MouseEvent, useRef, useState } from "react"; import { MSModel } from "shared/models/models/model-saber.model"; import { BsmImage } from "../shared/bsm-image.component"; import { motion } from "framer-motion"; import { GlowEffect } from "../shared/glow-effect.component"; import { MODELS_TYPE_MS_PAGE_ROOT, MODEL_SABER_URL } from "shared/models/models/constants"; import { BsmLink } from "../shared/bsm-link.component"; import { useThemeColor } from "renderer/hooks/use-theme-color.hook"; import Tippy from "@tippyjs/react"; import { followCursor } from "tippy.js"; import defaultImage from "../../../../assets/images/default-version-img.jpg"; import { BsmLocalModel } from "shared/models/models/bsm-local-model.interface"; import { BsmIconType } from "../svgs/bsm-icon.component"; import { BsmButton } from "../shared/bsm-button.component"; import { BsmBasicSpinner } from "../shared/bsm-basic-spinner/bsm-basic-spinner.component"; import { isValidUrl } from "shared/helpers/url.helpers"; import { useTranslation } from "renderer/hooks/use-translation.hook"; import useDoubleClick from "use-double-click"; import { useDelayedState } from "renderer/hooks/use-delayed-state.hook"; import { ChevronTopIcon } from "../svgs/icons/chevron-top-icon.component"; import { typedMemo } from "renderer/helpers/typed-memo"; type Props = { selected?: boolean; hash: string; id?: number; isDownloading?: boolean; callbackValue?: T; hideNsFw?: boolean; onDelete?: (value: T) => void; onDownload?: (value: T) => void; onCancelDownload?: (value: T) => void; onDoubleClick?: (value: T) => void; } & Partial & Partial & Omit, "id" | "onDoubleClick">; function ModelItemElement(props: Props) { const t = useTranslation(); const color = useThemeColor("first-color"); const [hovered, setHovered] = useState(false); const [infosHovered, setInfosHovered] = useDelayedState(false); const [idContentCopied, setIdContentCopied] = useState(null); const ref = useRef(); const isNsfw = (() => { if (!props.hideNsFw) { return false; } const tags = props.tags?.map(tag => tag.toLowerCase()) ?? []; const name = props.name.toLowerCase() ?? ""; return ( tags.includes("nsfw") || tags.includes("18+") || name.includes("nsfw") || name.includes("18+") || name.includes("nude") || name.includes("naked") || name.includes("lewd") ); })(); useDoubleClick({ ref, latency: props.onDoubleClick ? 200 : 0, onSingleClick: e => props?.onClick?.(e as unknown as MouseEvent), onDoubleClick: () => props?.onDoubleClick?.(props.callbackValue), }); const modelPageUrl = (() => { if (!props.id) { return null; } const url = new URL(MODELS_TYPE_MS_PAGE_ROOT[props.type], MODEL_SABER_URL); url.searchParams.set("id", props.id.toString()); return url.toString(); })(); const authorPageUrl = (() => { if (!props.discord) { return null; } const url = new URL("Profile", MODEL_SABER_URL); url.searchParams.set("user", props.discord.toString()); return url.toString(); })(); const thumbnailUrl = (() => { if (!props.thumbnail) { return null; } if (isValidUrl(props.thumbnail)) { return props.thumbnail; } const [file, ext] = props.thumbnail.split("."); return `${props.download.split("/").slice(0, -1).join("/")}/${file}.${ext.toLowerCase()}`; })(); const modelTags = (() => { if (!props.tags) { return null; } return [...new Set(props.tags)]; })(); const actionButtons = (): { id: number; icon: BsmIconType; action: () => void; iconColor?: string }[] => { const buttons: { id: number; icon: BsmIconType; action: () => void; iconColor?: string }[] = []; if (props.onDownload && !props.onCancelDownload) { buttons.push({ id: 0, icon: "download", action: () => props.onDownload(props.callbackValue) }); } if (props.onDelete) { buttons.push({ id: 1, icon: "trash", action: () => props.onDelete(props.callbackValue) }); } if (props.onCancelDownload) { buttons.push({ id: 2, icon: "cross", action: () => props.onCancelDownload(props.callbackValue), iconColor: "red" }); } return buttons; }; const copyContent = (value: string, id?: string) => { navigator.clipboard.writeText(value); setIdContentCopied(() => id); setTimeout(() => { setIdContentCopied(() => null); }, 700); }; return ( setHovered(() => true)} onHoverEnd={() => setHovered(() => false)}>
{!props.isDownloading ? ( actionButtons().map((button, index) => ( { e.stopPropagation(); e.preventDefault(); button.action(); }} withBar={false} /> )) ) : ( )}
{ e.stopPropagation(); e.preventDefault(); }} onHoverStart={() => setInfosHovered(true, 150)} onHoverEnd={() => setInfosHovered(false, 150)} style={{ top: infosHovered ? "0" : "83%" }} >
{props.name} setInfosHovered(false, 0)} />
{props.author ?? ""}
    {modelTags?.map(tag => (
  • {tag}
  • ))}
copyContent(`${props.hash}`, "hash")}> {props.hash}
{!!props.id && ( copyContent(`${props.id}`, "id")}> {props.id} )} {props.path && ( copyContent(`${props.path}`, "path")}> {props.path} )}
); } export const ModelItem = typedMemo(ModelItemElement, equal);