import { useState, Suspense, lazy } from "react"; import { useAuth } from "../../useAuth"; import Nav from "../../sections/Nav"; import Loading, { LoadingSpinner } from "../Loading"; import ErrorPage from "../ErrorPage"; import { BoxSeam, HouseDoor, Book, Building, Calendar3, Check2Square, Hdd, CheckSquare, Square, } from "react-bootstrap-icons"; import { useParams, NavLink, Redirect, Link } from "react-router-dom"; import { authenticatedEndpoint, ProfilePicture, useAuthenticatedRequest, } from "../../util"; import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; import CommonMembers from "./Members"; import { OverlayTrigger, Tooltip } from "react-bootstrap"; import HumanTime from "react-human-time"; type Tab = "readme" | "versions" | "members"; const Prism = lazy(() => import("react-syntax-highlighter").then((v) => ({ default: v.Prism })) ); export interface CrateInfo { name: string; readme?: string; description?: string; repository?: string; homepage?: string; documentation?: string; versions: CrateInfoVersion[]; } export interface CrateInfoVersionUploader { display_name: string; picture_url?: string; uuid: string; } export interface CrateInfoVersion { vers: string; deps: CrateInfoVersionDependency[]; features: { [key: string]: any }; size: number; uploader: CrateInfoVersionUploader; created_at: string; } export interface CrateInfoVersionDependency { name: string; req: string; registry?: string; } interface UrlParameters { organisation: string; crate: string; subview: Tab | undefined; } export default function SingleCrate() { const auth = useAuth(); const { organisation, crate, subview: currentTab, } = useParams(); if (!auth) { return ; } if (!currentTab) { return ; } const { response: crateInfo, error } = useAuthenticatedRequest( { auth, endpoint: `crates/${organisation}/${crate}`, }, [organisation, crate] ); if (error) { return ; } else if (!crateInfo) { return ; } const crateVersion = crateInfo.versions[crateInfo.versions.length - 1]; const showLinks = crateInfo.homepage || crateInfo.documentation || crateInfo.repository; return (
); } interface CratesMembersResponse { allowed_permissions: string[]; members: Member[]; } interface Member { uuid: string; display_name: string; permissions: string[]; } function Dependency({ organisation, dep, }: { organisation: string; dep: CrateInfoVersionDependency; }) { let link = <>{dep.name}; if (dep.registry === null || dep.registry === undefined) { link = ( {link} ); } else if (dep.registry === "https://github.com/rust-lang/crates.io-index") { link = ( {link} ); } else if (dep.registry.indexOf("ssh://") === 0) { const parts = dep.registry.split("/"); const org = parts[parts.length - 1]; if (org) { link = {link}; } } return (
  • {link} = "{dep.req}"
  • ); } interface MembersProps { organisation: string; crate: string; } function Members({ organisation, crate }: MembersProps) { const auth = useAuth(); if (!auth) { return <>; } const [reload, setReload] = useState(0); const { response, error } = useAuthenticatedRequest( { auth, endpoint: `crates/${organisation}/${crate}/members`, }, [reload] ); if (error) { return
    {error}
    ; } else if (!response) { return (
    Loading...
    ); } const saveMemberPermissions = async ( prospectiveMember: boolean, uuid: string, selectedPermissions: string[] ) => { let res = await fetch( authenticatedEndpoint(auth, `crates/${organisation}/${crate}/members`), { method: prospectiveMember ? "PUT" : "PATCH", headers: { Accept: "application/json", "Content-Type": "application/json", }, body: JSON.stringify({ user_uuid: uuid, permissions: selectedPermissions, }), } ); let json = await res.json(); if (json.error) { throw new Error(json.error); } setReload(reload + 1); }; const deleteMember = async (uuid: string) => { let res = await fetch( authenticatedEndpoint(auth, `crates/${organisation}/${crate}/members`), { method: "DELETE", headers: { Accept: "application/json", "Content-Type": "application/json", }, body: JSON.stringify({ user_uuid: uuid, }), } ); let json = await res.json(); if (json.error) { throw new Error(json.error); } setReload(reload + 1); }; return ( ); } function Versions(props: { crate: CrateInfo }) { const humanFileSize = (size: number) => { const i = Math.floor(Math.log(size) / Math.log(1024)); return ( Number((size / Math.pow(1024, i)).toFixed(2)) + " " + ["B", "kB", "MB", "GB", "TB"][i] ); }; if (props.crate.versions.length === 0) { return <>There hasn't yet been any versions published for this crate; } return (
    {[...props.crate.versions].reverse().map((version, index) => (
    {version.vers}
    By {version.uploader.display_name}
    {new Date(version.created_at).toLocaleString()} } > {" "}
    {humanFileSize(version.size)}
    {Object.keys(version.features).map( (feature, index) => (
    {version.features["default"].includes( feature ) ? ( ) : ( )} {feature}
    ) )}
    } > {Object.keys(version.features).length}{" "} Features
    ))}
    ); } function ReadMe(props: { crate: CrateInfo }) { if (!props.crate.readme) { return <>This crate has not added a README.; } return ( ) : ( {children} ); }, }} /> ); }