From 157ae8db3cf97f20f4014d3935cbe33e42123c34 Mon Sep 17 00:00:00 2001 From: Oksamies Date: Fri, 10 Oct 2025 16:54:43 +0300 Subject: [PATCH 1/9] Add proxy configuration for /p pages in ts-dev-proxy --- tools/ts-dev-proxy/nginx.conf | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tools/ts-dev-proxy/nginx.conf b/tools/ts-dev-proxy/nginx.conf index 7ba839c68..83b4aaefb 100644 --- a/tools/ts-dev-proxy/nginx.conf +++ b/tools/ts-dev-proxy/nginx.conf @@ -65,6 +65,11 @@ http { proxy_set_header Host new.thunderstore.temp; } + location /p { + proxy_pass http://host.docker.internal:3000/p; + proxy_set_header Host new.thunderstore.temp; + } + location /settings { proxy_pass http://host.docker.internal:3000/settings; proxy_set_header Host new.thunderstore.temp; From 4f08f16b3ad1547147789ce706a144c48d7f97f2 Mon Sep 17 00:00:00 2001 From: Oksamies Date: Fri, 10 Oct 2025 16:55:39 +0300 Subject: [PATCH 2/9] Add fetchPackageVersionDetails function and related schemas to thunderstore-api --- .../src/get/packageVersionDetails.ts | 24 +++++++++++++++++++ packages/thunderstore-api/src/index.ts | 1 + .../src/schemas/objectSchemas.ts | 2 +- .../src/schemas/requestSchemas.ts | 11 +++++++++ .../src/schemas/responseSchemas.ts | 23 ++++++++++++++++++ 5 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 packages/thunderstore-api/src/get/packageVersionDetails.ts diff --git a/packages/thunderstore-api/src/get/packageVersionDetails.ts b/packages/thunderstore-api/src/get/packageVersionDetails.ts new file mode 100644 index 000000000..44e57b238 --- /dev/null +++ b/packages/thunderstore-api/src/get/packageVersionDetails.ts @@ -0,0 +1,24 @@ +import { ApiEndpointProps } from "../index"; +import { apiFetch } from "../apiFetch"; +import { PackageVersionDetailsRequestParams } from "../schemas/requestSchemas"; +import { + PackageVersionDetailsResponseData, + packageVersionDetailsResponseDataSchema, +} from "../schemas/responseSchemas"; + +export async function fetchPackageVersionDetails( + props: ApiEndpointProps +): Promise { + const { config, params } = props; + const path = `api/cyberstorm/package/${params.namespace_id}/${params.package_name}/v/${params.package_version}/`; + + return await apiFetch({ + args: { + config: config, + path: path, + }, + requestSchema: undefined, + queryParamsSchema: undefined, + responseSchema: packageVersionDetailsResponseDataSchema, + }); +} diff --git a/packages/thunderstore-api/src/index.ts b/packages/thunderstore-api/src/index.ts index ae0f6934f..9e4b0ffae 100644 --- a/packages/thunderstore-api/src/index.ts +++ b/packages/thunderstore-api/src/index.ts @@ -35,6 +35,7 @@ export * from "./get/packageReadme"; export * from "./get/packageSubmission"; export * from "./get/packageVersionDependencies"; export * from "./get/packageVersions"; +export * from "./get/packageVersionDetails"; export * from "./get/packageWiki"; export * from "./get/ratedPackages"; export * from "./get/packageSource"; diff --git a/packages/thunderstore-api/src/schemas/objectSchemas.ts b/packages/thunderstore-api/src/schemas/objectSchemas.ts index 82dedd6b5..aa2abecea 100644 --- a/packages/thunderstore-api/src/schemas/objectSchemas.ts +++ b/packages/thunderstore-api/src/schemas/objectSchemas.ts @@ -135,7 +135,7 @@ export const packageListingSchema = z.object({ export type PackageListing = z.infer; -const packageTeamSchema = z.object({ +export const packageTeamSchema = z.object({ name: z.string().min(1), members: teamMemberSchema.array(), }); diff --git a/packages/thunderstore-api/src/schemas/requestSchemas.ts b/packages/thunderstore-api/src/schemas/requestSchemas.ts index 5c8a90792..76dc83fff 100644 --- a/packages/thunderstore-api/src/schemas/requestSchemas.ts +++ b/packages/thunderstore-api/src/schemas/requestSchemas.ts @@ -167,6 +167,17 @@ export type PackageListingDetailsRequestParams = z.infer< typeof packageListingDetailsRequestParamsSchema >; +// PackageVersionDetailsRequest +export const packageVersionDetailsRequestParamsSchema = z.object({ + namespace_id: z.string(), + package_name: z.string(), + package_version: z.string(), +}); + +export type PackageVersionDetailsRequestParams = z.infer< + typeof packageVersionDetailsRequestParamsSchema +>; + // PackageReadmeRequest export const packageReadmeRequestParamsSchema = z.object({ namespace_id: z.string(), diff --git a/packages/thunderstore-api/src/schemas/responseSchemas.ts b/packages/thunderstore-api/src/schemas/responseSchemas.ts index 730625991..435c94483 100644 --- a/packages/thunderstore-api/src/schemas/responseSchemas.ts +++ b/packages/thunderstore-api/src/schemas/responseSchemas.ts @@ -19,6 +19,7 @@ import { packagePermissionsSchema, packageSourceSchema, packageVersionDependencySchema, + packageTeamSchema, } from "../schemas/objectSchemas"; import { paginatedResults } from "../schemas/objectSchemas"; @@ -110,6 +111,28 @@ export type PackageListingDetailsResponseData = z.infer< typeof packageListingDetailsResponseDataSchema >; +// PackageVersionDetailsResponse +export const packageVersionDetailsResponseDataSchema = z.object({ + description: z.string(), + download_count: z.number().int(), + icon_url: z.string().nullable(), + install_url: z.string().nullable(), + name: z.string().min(1), + version_number: z.string().min(1), + namespace: z.string().min(1), + size: z.number().int(), + datetime_created: z.string().datetime(), + dependency_count: z.number().int(), + download_url: z.string(), + full_version_name: z.string().min(1), + team: packageTeamSchema, + website_url: z.string().nullable(), +}); + +export type PackageVersionDetailsResponseData = z.infer< + typeof packageVersionDetailsResponseDataSchema +>; + // PackagePermissionsResponse export const packagePermissionsResponseDataSchema = packagePermissionsSchema; From ec9cde045c91f0eac3582d54186b30d3e23758eb Mon Sep 17 00:00:00 2001 From: Oksamies Date: Fri, 10 Oct 2025 16:56:07 +0300 Subject: [PATCH 3/9] Add getFakePackageVersionDetails function and integrate into DapperFake and DapperTs --- packages/dapper-fake/src/fakers/package.ts | 66 +++++++++++++++++++ packages/dapper-fake/src/index.ts | 2 + packages/dapper-ts/src/index.ts | 3 + .../dapper-ts/src/methods/packageVersion.ts | 23 +++++++ 4 files changed, 94 insertions(+) create mode 100644 packages/dapper-ts/src/methods/packageVersion.ts diff --git a/packages/dapper-fake/src/fakers/package.ts b/packages/dapper-fake/src/fakers/package.ts index b388d094a..9ed6585fb 100644 --- a/packages/dapper-fake/src/fakers/package.ts +++ b/packages/dapper-fake/src/fakers/package.ts @@ -157,6 +157,72 @@ export const getFakePackageListingDetails = async ( }; }; +// Content used to render Package's detail view. +export const getFakePackageVersionDetails = async ( + namespace: string, + name: string, + version: string +) => { + const seed = `${namespace}-${name}-${version}`; + setSeed(seed); + + // Generate a base icon (nullable 10% of time) + const iconUrl = + faker.helpers.maybe(() => getFakeImg(), { + probability: 0.9, + }) ?? null; + + // Fake dependencies (reuse logic but need extra flags) + const dependencyCount = faker.number.int({ min: 0, max: 6 }); + const dependencies = await Promise.all( + range(dependencyCount).map(async () => { + const depSeed = `${seed}-dep-${faker.string.alpha(8)}`; + setSeed(depSeed); + const isActive = faker.datatype.boolean(0.8); + const removed = faker.datatype.boolean(0.1); + const unavailable = !removed && faker.datatype.boolean(0.05); + return { + description: isActive + ? faker.company.buzzPhrase() + : "This package has been removed.", + icon_url: isActive + ? faker.helpers.maybe(() => getFakeImg(256, 256), { + probability: 0.85, + }) ?? null + : null, + is_active: isActive, + name: faker.word.words(3).split(" ").join("_"), + namespace: faker.word.sample(), + version_number: getVersionNumber(), + is_removed: removed, + is_unavailable: unavailable, + }; + }) + ); + + return { + description: faker.company.buzzPhrase(), + download_count: faker.number.int({ min: 0, max: 5_000_000 }), + icon_url: iconUrl, + name, + namespace, + size: faker.number.int({ min: 20_000, max: 4_000_000_000 }), + datetime_created: faker.date.past({ years: 2 }).toISOString(), + dependencies, + dependency_count: dependencies.length, + download_url: `https://thunderstore.io/package/download/${namespace}/${name}/${version}/`, + full_version_name: `${namespace}-${name}-${version}`, + team: { + name: faker.word.words(3), + members: await getFakeTeamMembers(seed), + }, + website_url: + faker.helpers.maybe(() => faker.internet.url(), { + probability: 0.9, + }) ?? null, + }; +}; + // Shown on a tab on Package's detail view. export const getFakePackageVersions = async ( namespace: string, diff --git a/packages/dapper-fake/src/index.ts b/packages/dapper-fake/src/index.ts index 2daebf748..9d127ee39 100644 --- a/packages/dapper-fake/src/index.ts +++ b/packages/dapper-fake/src/index.ts @@ -13,6 +13,7 @@ import { getFakePackageVersionDependencies, getFakePackageVersions, getFakePackageSource, + getFakePackageVersionDetails, } from "./fakers/package"; import { getFakeServiceAccounts } from "./fakers/serviceAccount"; import { @@ -34,6 +35,7 @@ export class DapperFake implements DapperInterface { public getPackageListingDetails = getFakePackageListingDetails; public getPackageListings = getFakePackageListings; public getPackageReadme = getFakeReadme; + public getPackageVersionDetails = getFakePackageVersionDetails; public getPackageVersions = getFakePackageVersions; public getPackageSource = getFakePackageSource; public getPackageVersionDependencies = getFakePackageVersionDependencies; diff --git a/packages/dapper-ts/src/index.ts b/packages/dapper-ts/src/index.ts index d66951b62..53edf2260 100644 --- a/packages/dapper-ts/src/index.ts +++ b/packages/dapper-ts/src/index.ts @@ -28,6 +28,7 @@ import { getTeamServiceAccounts, postTeamCreate, } from "./methods/team"; +import { getPackageVersionDetails } from "./methods/packageVersion"; export interface DapperTsInterface extends DapperInterface { config: () => RequestConfig; @@ -51,6 +52,7 @@ export class DapperTs implements DapperTsInterface { this.getPackageListings = this.getPackageListings.bind(this); this.getPackageListingDetails = this.getPackageListingDetails.bind(this); this.getPackageReadme = this.getPackageReadme.bind(this); + this.getPackageVersionDetails = this.getPackageVersionDetails.bind(this); this.getPackageVersions = this.getPackageVersions.bind(this); this.getPackageVersionDependencies = this.getPackageVersionDependencies.bind(this); @@ -80,6 +82,7 @@ export class DapperTs implements DapperTsInterface { public getPackageReadme = getPackageReadme; public getPackageVersions = getPackageVersions; public getPackageVersionDependencies = getPackageVersionDependencies; + public getPackageVersionDetails = getPackageVersionDetails; public getPackageWiki = getPackageWiki; public getPackageWikiPage = getPackageWikiPage; public getPackagePermissions = getPackagePermissions; diff --git a/packages/dapper-ts/src/methods/packageVersion.ts b/packages/dapper-ts/src/methods/packageVersion.ts new file mode 100644 index 000000000..dc7ba7cf4 --- /dev/null +++ b/packages/dapper-ts/src/methods/packageVersion.ts @@ -0,0 +1,23 @@ +import { fetchPackageVersionDetails } from "@thunderstore/thunderstore-api"; + +import { DapperTsInterface } from "../index"; + +export async function getPackageVersionDetails( + this: DapperTsInterface, + namespaceId: string, + packageName: string, + packageVersion: string +) { + const data = await fetchPackageVersionDetails({ + config: this.config, + params: { + namespace_id: namespaceId, + package_name: packageName, + package_version: packageVersion, + }, + data: {}, + queryParams: {}, + }); + + return data; +} From ab7f7070f1ba6fa6769b48b2f0ec54f15a811d09 Mon Sep 17 00:00:00 2001 From: Oksamies Date: Fri, 10 Oct 2025 16:56:33 +0300 Subject: [PATCH 4/9] Fix formatToDisplayName function to replace hyphens with spaces --- packages/cyberstorm/src/utils/utils.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/cyberstorm/src/utils/utils.ts b/packages/cyberstorm/src/utils/utils.ts index efa6ba16a..c0833facb 100644 --- a/packages/cyberstorm/src/utils/utils.ts +++ b/packages/cyberstorm/src/utils/utils.ts @@ -58,4 +58,5 @@ export const componentClasses = ( return listOfClasses; }; -export const formatToDisplayName = (name: string) => name.replaceAll("_", " "); +export const formatToDisplayName = (name: string) => + name.replaceAll("-", " ").replaceAll("_", " "); From 5cc6c293762cedf61180a02418299246ae1afd5d Mon Sep 17 00:00:00 2001 From: Oksamies Date: Fri, 10 Oct 2025 16:58:51 +0300 Subject: [PATCH 5/9] Add setParamsBlobValue utility function for updating search parameters --- apps/cyberstorm-remix/cyberstorm/utils/searchParamsUtils.ts | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 apps/cyberstorm-remix/cyberstorm/utils/searchParamsUtils.ts diff --git a/apps/cyberstorm-remix/cyberstorm/utils/searchParamsUtils.ts b/apps/cyberstorm-remix/cyberstorm/utils/searchParamsUtils.ts new file mode 100644 index 000000000..546e277c5 --- /dev/null +++ b/apps/cyberstorm-remix/cyberstorm/utils/searchParamsUtils.ts @@ -0,0 +1,6 @@ +export function setParamsBlobValue< + SearchParamsType, + K extends keyof SearchParamsType, +>(setter: (v: SearchParamsType) => void, oldBlob: SearchParamsType, key: K) { + return (v: SearchParamsType[K]) => setter({ ...oldBlob, [key]: v }); +} From 4c672b192b8b30c7375582dc418c1247f089fbfc Mon Sep 17 00:00:00 2001 From: Oksamies Date: Fri, 10 Oct 2025 17:03:19 +0300 Subject: [PATCH 6/9] Add rowSemverCompare function for comparing semantic versioning in table rows --- .../cyberstorm/utils/semverCompare.ts | 19 +++++++++++++++++++ .../cyberstorm/utils/typeChecks.ts | 8 ++++++++ 2 files changed, 27 insertions(+) create mode 100644 apps/cyberstorm-remix/cyberstorm/utils/semverCompare.ts diff --git a/apps/cyberstorm-remix/cyberstorm/utils/semverCompare.ts b/apps/cyberstorm-remix/cyberstorm/utils/semverCompare.ts new file mode 100644 index 000000000..6b1139522 --- /dev/null +++ b/apps/cyberstorm-remix/cyberstorm/utils/semverCompare.ts @@ -0,0 +1,19 @@ +import { type TableCompareColumnMeta } from "@thunderstore/cyberstorm"; +import { type TableRow } from "@thunderstore/cyberstorm/src/newComponents/Table/Table"; +import { isSemver } from "./typeChecks"; +import semverCompare from "semver/functions/compare"; + +export function rowSemverCompare( + a: TableRow, + b: TableRow, + columnMeta: TableCompareColumnMeta +) { + const av = String(a[0].sortValue); + const bv = String(b[0].sortValue); + + if (isSemver(av) && isSemver(bv)) { + return semverCompare(av, bv) * columnMeta.direction; + } + + return 0; +} diff --git a/apps/cyberstorm-remix/cyberstorm/utils/typeChecks.ts b/apps/cyberstorm-remix/cyberstorm/utils/typeChecks.ts index a6bbb0b83..91e574d84 100644 --- a/apps/cyberstorm-remix/cyberstorm/utils/typeChecks.ts +++ b/apps/cyberstorm-remix/cyberstorm/utils/typeChecks.ts @@ -1,3 +1,5 @@ +import semverValid from "semver/functions/valid"; + export const isRecord = (obj: unknown): obj is Record => obj instanceof Object; @@ -5,3 +7,9 @@ export const isRecord = (obj: unknown): obj is Record => export function isPromise(value: any): value is Promise { return typeof value?.then === "function"; } + +type ConfirmedSemverStringType = string; + +export const isSemver = (s: string): s is ConfirmedSemverStringType => { + return Boolean(semverValid(s)); +}; From 5721cb36e266563d998e10296859b20c13f15e38 Mon Sep 17 00:00:00 2001 From: Oksamies Date: Fri, 10 Oct 2025 17:04:06 +0300 Subject: [PATCH 7/9] Refactor setParamsBlobValue function to import from searchParamsUtils and remove redundant definition --- .../app/commonComponents/PackageSearch/PackageSearch.tsx | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/apps/cyberstorm-remix/app/commonComponents/PackageSearch/PackageSearch.tsx b/apps/cyberstorm-remix/app/commonComponents/PackageSearch/PackageSearch.tsx index ac1a6617e..f641bf0dc 100644 --- a/apps/cyberstorm-remix/app/commonComponents/PackageSearch/PackageSearch.tsx +++ b/apps/cyberstorm-remix/app/commonComponents/PackageSearch/PackageSearch.tsx @@ -37,6 +37,7 @@ import { } from "@thunderstore/thunderstore-api"; import { DapperTs } from "@thunderstore/dapper-ts"; import { isPromise } from "cyberstorm/utils/typeChecks"; +import { setParamsBlobValue } from "cyberstorm/utils/searchParamsUtils"; const PER_PAGE = 20; @@ -721,14 +722,6 @@ export function PackageSearch(props: Props) { PackageSearch.displayName = "PackageSearch"; // Start setters -function setParamsBlobValue( - setter: (v: SearchParamsType) => void, - oldBlob: SearchParamsType, - key: K -) { - return (v: SearchParamsType[K]) => setter({ ...oldBlob, [key]: v }); -} - const setParamsBlobCategories = ( setter: (v: SearchParamsType) => void, oldBlob: SearchParamsType, From 06c638930b84c3854ae5d3f7ab7485c6671bbb16 Mon Sep 17 00:00:00 2001 From: Oksamies Date: Fri, 10 Oct 2025 17:05:58 +0300 Subject: [PATCH 8/9] Fix shouldRevalidate function to correctly compare path segments for revalidation logic --- apps/cyberstorm-remix/app/p/packageListing.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/cyberstorm-remix/app/p/packageListing.tsx b/apps/cyberstorm-remix/app/p/packageListing.tsx index 8bc8483dc..a93c36daa 100644 --- a/apps/cyberstorm-remix/app/p/packageListing.tsx +++ b/apps/cyberstorm-remix/app/p/packageListing.tsx @@ -166,7 +166,7 @@ export function shouldRevalidate(arg: ShouldRevalidateFunctionArgs) { // If we're staying on the same package page, don't revalidate if ( oldPath[2] === newPath[2] && - oldPath[4] === newPath[4] && + oldPath[3] === newPath[3] && oldPath[5] === newPath[5] ) { return false; From c6891dd59a2348cb93d41894ec6a4448fee39bfe Mon Sep 17 00:00:00 2001 From: Oksamies Date: Fri, 10 Oct 2025 17:07:16 +0300 Subject: [PATCH 9/9] feat: Add package version detail views with readme, required dependencies, and versions - Implemented `PackageVersionReadme` and `PackageVersionWithoutCommunityReadme` components for displaying package readme content. - Created `PackageVersionRequired` and `PackageVersionWithoutCommunityRequired` components to show required dependencies for specific package versions. - Developed `PackageVersionVersions` and `PackageVersionWithoutCommunityVersions` components to list available versions of a package. - Updated routing to support new package version detail pages, including community and non-community versions. - Enhanced link library to include new links for package version details, required dependencies, and versions. - Refactored existing components to improve code organization and maintainability. --- .../ListingDependency/ListingDependency.tsx | 90 ++- .../PaginatedDependencies.css} | 18 +- .../PaginatedDependencies.tsx | 175 ++++++ .../cyberstorm-remix/app/p/packageVersion.tsx | 545 ++++++++++++++++++ .../app/p/packageVersionWithoutCommunity.tsx | 492 ++++++++++++++++ .../p/tabs/Readme/PackageVersionReadme.tsx | 85 +++ .../PackageVersionWithoutCommunityReadme.tsx | 85 +++ .../tabs/Required/PackageVersionRequired.tsx | 76 +++ ...PackageVersionWithoutCommunityRequired.tsx | 76 +++ .../app/p/tabs/Required/Required.tsx | 101 ++-- .../tabs/Versions/PackageVersionVersions.tsx | 134 +++++ ...PackageVersionWithoutCommunityVersions.tsx | 132 +++++ .../app/p/tabs/Versions/Versions.tsx | 124 ++-- .../app/p/tabs/Versions/common.tsx | 55 ++ apps/cyberstorm-remix/app/root.tsx | 59 +- apps/cyberstorm-remix/app/routes.ts | 30 + .../cyberstorm/utils/LinkLibrary.tsx | 35 ++ apps/storybook/LinkLibrary.tsx | 35 ++ .../src/components/Links/LinkingProvider.tsx | 21 + .../cyberstorm/src/components/Links/Links.tsx | 5 + 20 files changed, 2184 insertions(+), 189 deletions(-) rename apps/cyberstorm-remix/app/{p/tabs/Required/Required.css => commonComponents/PaginatedDependencies/PaginatedDependencies.css} (74%) create mode 100644 apps/cyberstorm-remix/app/commonComponents/PaginatedDependencies/PaginatedDependencies.tsx create mode 100644 apps/cyberstorm-remix/app/p/packageVersion.tsx create mode 100644 apps/cyberstorm-remix/app/p/packageVersionWithoutCommunity.tsx create mode 100644 apps/cyberstorm-remix/app/p/tabs/Readme/PackageVersionReadme.tsx create mode 100644 apps/cyberstorm-remix/app/p/tabs/Readme/PackageVersionWithoutCommunityReadme.tsx create mode 100644 apps/cyberstorm-remix/app/p/tabs/Required/PackageVersionRequired.tsx create mode 100644 apps/cyberstorm-remix/app/p/tabs/Required/PackageVersionWithoutCommunityRequired.tsx create mode 100644 apps/cyberstorm-remix/app/p/tabs/Versions/PackageVersionVersions.tsx create mode 100644 apps/cyberstorm-remix/app/p/tabs/Versions/PackageVersionWithoutCommunityVersions.tsx create mode 100644 apps/cyberstorm-remix/app/p/tabs/Versions/common.tsx diff --git a/apps/cyberstorm-remix/app/commonComponents/ListingDependency/ListingDependency.tsx b/apps/cyberstorm-remix/app/commonComponents/ListingDependency/ListingDependency.tsx index 376e4fefd..ad3f335e5 100644 --- a/apps/cyberstorm-remix/app/commonComponents/ListingDependency/ListingDependency.tsx +++ b/apps/cyberstorm-remix/app/commonComponents/ListingDependency/ListingDependency.tsx @@ -1,15 +1,14 @@ -import type { packageListingDependencySchema } from "@thunderstore/thunderstore-api"; +import { type PackageVersionDependency } from "@thunderstore/thunderstore-api"; import "./ListingDependency.css"; import { formatToDisplayName, Image, NewLink } from "@thunderstore/cyberstorm"; export interface ListingDependencyProps { - dependency: typeof packageListingDependencySchema._type; - // TODO: Remove when package versiond detail is available - domain: string; + dependency: PackageVersionDependency; + communityId?: string; } export function ListingDependency(props: ListingDependencyProps) { - const { dependency, domain } = props; + const { dependency, communityId } = props; return (
@@ -23,16 +22,30 @@ export function ListingDependency(props: ListingDependencyProps) { />
- - {formatToDisplayName(dependency.name)} - + {communityId ? ( + + {formatToDisplayName(dependency.name)} + + ) : ( + + {formatToDisplayName(dependency.name)} + + )} by
Version: - - {dependency.version_number} - + {communityId ? ( + + {dependency.version_number} + + ) : ( + + {dependency.version_number} + + )}
diff --git a/apps/cyberstorm-remix/app/p/tabs/Required/Required.css b/apps/cyberstorm-remix/app/commonComponents/PaginatedDependencies/PaginatedDependencies.css similarity index 74% rename from apps/cyberstorm-remix/app/p/tabs/Required/Required.css rename to apps/cyberstorm-remix/app/commonComponents/PaginatedDependencies/PaginatedDependencies.css index c446dac8a..bf5440b72 100644 --- a/apps/cyberstorm-remix/app/p/tabs/Required/Required.css +++ b/apps/cyberstorm-remix/app/commonComponents/PaginatedDependencies/PaginatedDependencies.css @@ -1,9 +1,5 @@ -@layer nimbus-layout { - .package-required__skeleton { - height: 500px; - } - - .package-required { +@layer nimbus-components { + .paginated-dependencies { display: flex; flex-direction: column; gap: var(--gap-md); @@ -11,7 +7,11 @@ overflow-x: auto; } - .package-required__title { + .paginated-dependencies__skeleton { + height: 500px; + } + + .paginated-dependencies__title { display: flex; flex-direction: column; gap: var(--gap-md); @@ -20,13 +20,13 @@ padding-bottom: var(--space-24); } - .package-required__description { + .paginated-dependencies__description { color: var(--color-text-secondary); font-weight: var(--font-weight-regular); line-height: var(--line-height-md); } - .package-required__body { + .paginated-dependencies__body { display: flex; flex-direction: column; gap: var(--gap-3xs); diff --git a/apps/cyberstorm-remix/app/commonComponents/PaginatedDependencies/PaginatedDependencies.tsx b/apps/cyberstorm-remix/app/commonComponents/PaginatedDependencies/PaginatedDependencies.tsx new file mode 100644 index 000000000..bf2c2008e --- /dev/null +++ b/apps/cyberstorm-remix/app/commonComponents/PaginatedDependencies/PaginatedDependencies.tsx @@ -0,0 +1,175 @@ +import { memo, Suspense, useEffect, useMemo, useRef, useState } from "react"; +import "./PaginatedDependencies.css"; +import { Heading, NewPagination, SkeletonBox } from "@thunderstore/cyberstorm"; +import { ListingDependency } from "../ListingDependency/ListingDependency"; +import { Await, useNavigationType, useSearchParams } from "react-router"; +import { useDebounce } from "use-debounce"; +import type { getPackageVersionDetails } from "@thunderstore/dapper-ts/src/methods/packageVersion"; +import type { getPackageVersionDependencies } from "@thunderstore/dapper-ts/src/methods/package"; +import { setParamsBlobValue } from "cyberstorm/utils/searchParamsUtils"; + +interface Props { + version: + | Awaited> + | ReturnType; + dependencies: + | Awaited> + | ReturnType; + pageSize?: number; + siblingCount?: number; +} + +export const PaginatedDependencies = memo(function PaginatedDependencies( + props: Props +) { + const navigationType = useNavigationType(); + + const [searchParams, setSearchParams] = useSearchParams(); + + const initialParams = searchParamsToBlob(searchParams); + + const [searchParamsBlob, setSearchParamsBlob] = + useState(initialParams); + + const [currentPage, setCurrentPage] = useState( + searchParams.get("page") ? Number(searchParams.get("page")) : 1 + ); + + const [debouncedSearchParamsBlob] = useDebounce(searchParamsBlob, 300, { + maxWait: 300, + }); + + const searchParamsBlobRef = useRef(debouncedSearchParamsBlob); + + const searchParamsRef = useRef(searchParams); + useEffect(() => { + if (navigationType === "POP") { + if (searchParamsRef.current !== searchParams) { + const spb = searchParamsToBlob(searchParams); + setSearchParamsBlob(spb); + setCurrentPage(spb.page); + searchParamsRef.current = searchParams; + } + searchParamsBlobRef.current = searchParamsToBlob(searchParams); + } + }, [searchParams]); + + useEffect(() => { + if ( + navigationType !== "POP" || + (navigationType === "POP" && + searchParamsBlobRef.current !== debouncedSearchParamsBlob) + ) { + if (searchParamsBlobRef.current !== debouncedSearchParamsBlob) { + const oldPage = searchParams.get("page") + ? Number(searchParams.get("page")) + : 1; + // Page number + if (oldPage !== debouncedSearchParamsBlob.page) { + if (debouncedSearchParamsBlob.page === 1) { + searchParams.delete("page"); + setCurrentPage(1); + } else { + searchParams.set("page", String(debouncedSearchParamsBlob.page)); + setCurrentPage(debouncedSearchParamsBlob.page); + } + } + const uncommittedSearchParams = searchParamsToBlob(searchParams); + + if ( + navigationType !== "POP" || + (navigationType === "POP" && + !compareSearchParamBlobs( + uncommittedSearchParams, + searchParamsBlobRef.current + ) && + compareSearchParamBlobs( + uncommittedSearchParams, + debouncedSearchParamsBlob + )) + ) { + setSearchParams(searchParams, { preventScrollReset: true }); + } + searchParamsBlobRef.current = debouncedSearchParamsBlob; + } + } + }, [debouncedSearchParamsBlob]); + + const versionAndDependencies = useMemo( + () => Promise.all([props.version, props.dependencies]), + [currentPage] + ); + + return ( +
+ } + > + Error occurred while loading required dependencies
+ } + > + {(resolvedValue) => { + return ( + <> +
+ + Required mods ({resolvedValue[0].dependency_count}) + + + This package requires the following packages to work. + +
+
+ {resolvedValue[1].results.map((dep, key) => { + return ; + })} +
+ + + ); + }} + + +
+ ); +}); + +PaginatedDependencies.displayName = "PaginatedDependencies"; + +export type SearchParamsType = { + page: number; +}; + +export const compareSearchParamBlobs = ( + b1: SearchParamsType, + b2: SearchParamsType +) => { + if (b1.page !== b2.page) return false; + return true; +}; + +export const searchParamsToBlob = (searchParams: URLSearchParams) => { + const initialPage = searchParams.get("page"); + + return { + page: + initialPage && + !Number.isNaN(Number.parseInt(initialPage)) && + Number.isSafeInteger(Number.parseInt(initialPage)) + ? Number.parseInt(initialPage) + : 1, + }; +}; diff --git a/apps/cyberstorm-remix/app/p/packageVersion.tsx b/apps/cyberstorm-remix/app/p/packageVersion.tsx new file mode 100644 index 000000000..2502d75cd --- /dev/null +++ b/apps/cyberstorm-remix/app/p/packageVersion.tsx @@ -0,0 +1,545 @@ +import type { + LoaderFunctionArgs, + ShouldRevalidateFunctionArgs, +} from "react-router"; +import { + Await, + Outlet, + useLoaderData, + useLocation, + useOutletContext, +} from "react-router"; +import { + Drawer, + Heading, + NewAlert, + NewButton, + NewIcon, + NewLink, + SkeletonBox, + Tabs, +} from "@thunderstore/cyberstorm"; +import "./packageListing.css"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { ThunderstoreLogo } from "@thunderstore/cyberstorm/src/svg/svg"; +import { + faUsers, + faHandHoldingHeart, + faDownload, + faCaretRight, +} from "@fortawesome/free-solid-svg-icons"; +import { + memo, + type ReactElement, + Suspense, + useEffect, + useMemo, + useRef, + useState, +} from "react"; +import { useHydrated } from "remix-utils/use-hydrated"; +import { PageHeader } from "~/commonComponents/PageHeader/PageHeader"; +import { faArrowUpRight } from "@fortawesome/pro-solid-svg-icons"; +import { RelativeTime } from "@thunderstore/cyberstorm/src/components/RelativeTime/RelativeTime"; +import { + formatFileSize, + formatInteger, + formatToDisplayName, +} from "@thunderstore/cyberstorm/src/utils/utils"; +import { DapperTs } from "@thunderstore/dapper-ts"; +import { type OutletContextShape } from "~/root"; +import { CopyButton } from "~/commonComponents/CopyButton/CopyButton"; +import { + getPublicEnvVariables, + getSessionTools, +} from "cyberstorm/security/publicEnvVariables"; +import { getTeamDetails } from "@thunderstore/dapper-ts/src/methods/team"; +import { isPromise } from "cyberstorm/utils/typeChecks"; +import { getPackageVersionDetails } from "@thunderstore/dapper-ts/src/methods/packageVersion"; + +export async function loader({ params }: LoaderFunctionArgs) { + if ( + params.communityId && + params.namespaceId && + params.packageId && + params.packageVersion + ) { + const publicEnvVariables = getPublicEnvVariables(["VITE_API_URL"]); + const dapper = new DapperTs(() => { + return { + apiHost: publicEnvVariables.VITE_API_URL, + sessionId: undefined, + }; + }); + + return { + communityId: params.communityId, + community: await dapper.getCommunity(params.communityId), + version: await dapper.getPackageVersionDetails( + params.namespaceId, + params.packageId, + params.packageVersion + ), + team: await dapper.getTeamDetails(params.namespaceId), + }; + } + throw new Response("Package not found", { status: 404 }); +} + +export async function clientLoader({ params }: LoaderFunctionArgs) { + if ( + params.communityId && + params.namespaceId && + params.packageId && + params.packageVersion + ) { + const tools = getSessionTools(); + const dapper = new DapperTs(() => { + return { + apiHost: tools?.getConfig().apiHost, + sessionId: tools?.getConfig().sessionId, + }; + }); + + return { + communityId: params.communityId, + community: dapper.getCommunity(params.communityId), + version: dapper.getPackageVersionDetails( + params.namespaceId, + params.packageId, + params.packageVersion + ), + team: dapper.getTeamDetails(params.namespaceId), + }; + } + throw new Response("Package not found", { status: 404 }); +} + +export function shouldRevalidate(arg: ShouldRevalidateFunctionArgs) { + const oldPath = arg.currentUrl.pathname.split("/"); + const newPath = arg.nextUrl.pathname.split("/"); + // If we're staying on the same package page, don't revalidate + if ( + oldPath[2] === newPath[2] && + oldPath[4] === newPath[4] && + oldPath[5] === newPath[5] && + oldPath[7] === newPath[7] + ) { + return false; + } + return arg.defaultShouldRevalidate; +} + +export default function PackageVersion() { + const { communityId, community, version, team } = useLoaderData< + typeof loader | typeof clientLoader + >(); + + const location = useLocation(); + + const outletContext = useOutletContext() as OutletContextShape; + + const isHydrated = useHydrated(); + const startsHydrated = useRef(isHydrated); + + // START: For sidebar meta dates + const [firstUploaded, setFirstUploaded] = useState< + ReactElement | undefined + >(); + + // This will be loaded 2 times in development because of: + // https://react.dev/reference/react/StrictMode + // If strict mode is removed from the entry.client.tsx, this should only run once + useEffect(() => { + if (!startsHydrated.current && isHydrated) return; + if (isPromise(version)) { + version.then((versionData) => { + setFirstUploaded( + + ); + }); + } else { + setFirstUploaded( + + ); + } + }, []); + // END: For sidebar meta dates + + const currentTab = location.pathname.split("/")[8] || "details"; + + const versionAndCommunityPromise = useMemo( + () => Promise.all([version, community]), + [] + ); + + const versionAndTeamPromise = useMemo(() => Promise.all([version, team]), []); + + return ( + <> + + + {(resolvedValue) => ( + <> + + + + + + + + + + + + )} + + +
+
+
+
+ + You are viewing a potentially older version of this package. + + } + > + + {(resolvedValue) => ( + + You are viewing a potentially older version of this + package.{" "} + + View Latest Version + + + )} + + + + } + > + + {(resolvedValue) => ( + + + + + + {resolvedValue.namespace} + + {resolvedValue.website_url ? ( + + {resolvedValue.website_url} + + + + + ) : null} + + } + > + {formatToDisplayName(resolvedValue.name)} + + )} + + + +
+ + + Details + + } + rootClasses="package-listing__drawer" + > + Loading...

}> + + {(resolvedValue) => ( + <>{packageMeta(firstUploaded, resolvedValue)} + )} + +
+
+ Loading...

}> + + {(resolvedValue) => ( + + )} + +
+
+ + } + > + + {(resolvedValue) => ( + <> + + + Details + + + Required ({resolvedValue.dependency_count}) + + + Versions + + + + )} + + +
+ +
+
+ +
+
+
+ + ); +} + +const Actions = memo(function Actions(props: { + team: Awaited>; + version: Awaited>; +}) { + const { team, version } = props; + return ( +
+ + + + + Download + + {team.donation_link ? ( + + + + + + ) : null} +
+ ); +}); + +function packageMeta( + firstUploaded: ReactElement | undefined, + version: Awaited> +) { + return ( +
+
+
Date Uploaded
+
{firstUploaded}
+
+
+
Downloads
+
+ {formatInteger(version.download_count)} +
+
+
+
Size
+
+ {formatFileSize(version.size)} +
+
+
+
Dependency string
+
+
+ + {version.full_version_name} + + +
+
+
+
+ ); +} diff --git a/apps/cyberstorm-remix/app/p/packageVersionWithoutCommunity.tsx b/apps/cyberstorm-remix/app/p/packageVersionWithoutCommunity.tsx new file mode 100644 index 000000000..233a7a973 --- /dev/null +++ b/apps/cyberstorm-remix/app/p/packageVersionWithoutCommunity.tsx @@ -0,0 +1,492 @@ +import type { + LoaderFunctionArgs, + ShouldRevalidateFunctionArgs, +} from "react-router"; +import { + Await, + Outlet, + useLoaderData, + useLocation, + useOutletContext, +} from "react-router"; +import { + Drawer, + Heading, + NewAlert, + NewButton, + NewIcon, + NewLink, + SkeletonBox, + Tabs, +} from "@thunderstore/cyberstorm"; +import "./packageListing.css"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { ThunderstoreLogo } from "@thunderstore/cyberstorm/src/svg/svg"; +import { + faUsers, + faHandHoldingHeart, + faDownload, + faCaretRight, +} from "@fortawesome/free-solid-svg-icons"; +import { + memo, + type ReactElement, + Suspense, + useEffect, + useMemo, + useRef, + useState, +} from "react"; +import { useHydrated } from "remix-utils/use-hydrated"; +import { PageHeader } from "~/commonComponents/PageHeader/PageHeader"; +import { faArrowUpRight } from "@fortawesome/pro-solid-svg-icons"; +import { RelativeTime } from "@thunderstore/cyberstorm/src/components/RelativeTime/RelativeTime"; +import { + formatFileSize, + formatInteger, + formatToDisplayName, +} from "@thunderstore/cyberstorm/src/utils/utils"; +import { DapperTs } from "@thunderstore/dapper-ts"; +import { type OutletContextShape } from "~/root"; +import { CopyButton } from "~/commonComponents/CopyButton/CopyButton"; +import { + getPublicEnvVariables, + getSessionTools, +} from "cyberstorm/security/publicEnvVariables"; +import { getTeamDetails } from "@thunderstore/dapper-ts/src/methods/team"; +import { isPromise } from "cyberstorm/utils/typeChecks"; +import { getPackageVersionDetails } from "@thunderstore/dapper-ts/src/methods/packageVersion"; + +export async function loader({ params }: LoaderFunctionArgs) { + if (params.namespaceId && params.packageId && params.packageVersion) { + const publicEnvVariables = getPublicEnvVariables(["VITE_API_URL"]); + const dapper = new DapperTs(() => { + return { + apiHost: publicEnvVariables.VITE_API_URL, + sessionId: undefined, + }; + }); + + return { + version: await dapper.getPackageVersionDetails( + params.namespaceId, + params.packageId, + params.packageVersion + ), + team: await dapper.getTeamDetails(params.namespaceId), + }; + } + throw new Response("Package not found", { status: 404 }); +} + +export async function clientLoader({ params }: LoaderFunctionArgs) { + if (params.namespaceId && params.packageId && params.packageVersion) { + const tools = getSessionTools(); + const dapper = new DapperTs(() => { + return { + apiHost: tools?.getConfig().apiHost, + sessionId: tools?.getConfig().sessionId, + }; + }); + + return { + version: dapper.getPackageVersionDetails( + params.namespaceId, + params.packageId, + params.packageVersion + ), + team: dapper.getTeamDetails(params.namespaceId), + }; + } + throw new Response("Package not found", { status: 404 }); +} + +export function shouldRevalidate(arg: ShouldRevalidateFunctionArgs) { + const oldPath = arg.currentUrl.pathname.split("/"); + const newPath = arg.nextUrl.pathname.split("/"); + // If we're staying on the same package page, don't revalidate + if ( + oldPath[2] === newPath[2] && + oldPath[3] === newPath[3] && + oldPath[5] === newPath[5] + ) { + return false; + } + return arg.defaultShouldRevalidate; +} + +export default function PackageVersion() { + const { version, team } = useLoaderData< + typeof loader | typeof clientLoader + >(); + + const location = useLocation(); + + const outletContext = useOutletContext() as OutletContextShape; + + const isHydrated = useHydrated(); + const startsHydrated = useRef(isHydrated); + + // START: For sidebar meta dates + const [firstUploaded, setFirstUploaded] = useState< + ReactElement | undefined + >(); + + // This will be loaded 2 times in development because of: + // https://react.dev/reference/react/StrictMode + // If strict mode is removed from the entry.client.tsx, this should only run once + useEffect(() => { + if (!startsHydrated.current && isHydrated) return; + if (isPromise(version)) { + version.then((versionData) => { + setFirstUploaded( + + ); + }); + } else { + setFirstUploaded( + + ); + } + }, []); + // END: For sidebar meta dates + + const currentTab = location.pathname.split("/")[6] || "details"; + + const versionAndTeamPromise = useMemo(() => Promise.all([version, team]), []); + + return ( + <> + + + {(resolvedValue) => ( + <> + + + + + + + + + + + + )} + + +
+
+
+
+ + You are viewing a potentially older version of this package. + + + } + > + + {(resolvedValue) => ( + + + + + + {resolvedValue.namespace} + + {resolvedValue.website_url ? ( + + {resolvedValue.website_url} + + + + + ) : null} + + } + > + {formatToDisplayName(resolvedValue.name)} + + )} + + + +
+ + + Details + + } + rootClasses="package-listing__drawer" + > + Loading...

}> + + {(resolvedValue) => ( + <>{packageMeta(firstUploaded, resolvedValue)} + )} + +
+
+ Loading...

}> + + {(resolvedValue) => ( + + )} + +
+
+ + } + > + + {(resolvedValue) => ( + <> + + + Details + + + Required ({resolvedValue.dependency_count}) + + + Versions + + + + )} + + +
+ +
+
+ +
+
+
+ + ); +} + +const Actions = memo(function Actions(props: { + team: Awaited>; + version: Awaited>; +}) { + const { team, version } = props; + return ( +
+ + + + + Download + + {team.donation_link ? ( + + + + + + ) : null} +
+ ); +}); + +function packageMeta( + firstUploaded: ReactElement | undefined, + version: Awaited> +) { + return ( +
+
+
Date Uploaded
+
{firstUploaded}
+
+
+
Downloads
+
+ {formatInteger(version.download_count)} +
+
+
+
Size
+
+ {formatFileSize(version.size)} +
+
+
+
Dependency string
+
+
+ + {version.full_version_name} + + +
+
+
+
+ ); +} diff --git a/apps/cyberstorm-remix/app/p/tabs/Readme/PackageVersionReadme.tsx b/apps/cyberstorm-remix/app/p/tabs/Readme/PackageVersionReadme.tsx new file mode 100644 index 000000000..321be74c1 --- /dev/null +++ b/apps/cyberstorm-remix/app/p/tabs/Readme/PackageVersionReadme.tsx @@ -0,0 +1,85 @@ +import { Await, type LoaderFunctionArgs } from "react-router"; +import { useLoaderData } from "react-router"; +import { DapperTs } from "@thunderstore/dapper-ts"; +import { + getPublicEnvVariables, + getSessionTools, +} from "cyberstorm/security/publicEnvVariables"; +import { Suspense } from "react"; +import { SkeletonBox } from "@thunderstore/cyberstorm"; +import "./Readme.css"; + +export async function loader({ params }: LoaderFunctionArgs) { + if (params.namespaceId && params.packageId && params.packageVersion) { + const publicEnvVariables = getPublicEnvVariables(["VITE_API_URL"]); + const dapper = new DapperTs(() => { + return { + apiHost: publicEnvVariables.VITE_API_URL, + sessionId: undefined, + }; + }); + return { + readme: await dapper.getPackageReadme( + params.namespaceId, + params.packageId, + params.packageVersion + ), + }; + } + return { + status: "error", + message: "Failed to load readme", + readme: { html: "" }, + }; +} + +export async function clientLoader({ params }: LoaderFunctionArgs) { + if (params.namespaceId && params.packageId && params.packageVersion) { + const tools = getSessionTools(); + const dapper = new DapperTs(() => { + return { + apiHost: tools?.getConfig().apiHost, + sessionId: tools?.getConfig().sessionId, + }; + }); + return { + readme: dapper.getPackageReadme( + params.namespaceId, + params.packageId, + params.packageVersion + ), + }; + } + return { + status: "error", + message: "Failed to load readme", + readme: { html: "" }, + }; +} + +export default function PackageVersionReadme() { + const { status, message, readme } = useLoaderData< + typeof loader | typeof clientLoader + >(); + + if (status === "error") return
{message}
; + return ( + }> + Error occurred while loading description} + > + {(resolvedValue) => ( + <> +
+
+
+ + )} + + + ); +} diff --git a/apps/cyberstorm-remix/app/p/tabs/Readme/PackageVersionWithoutCommunityReadme.tsx b/apps/cyberstorm-remix/app/p/tabs/Readme/PackageVersionWithoutCommunityReadme.tsx new file mode 100644 index 000000000..321be74c1 --- /dev/null +++ b/apps/cyberstorm-remix/app/p/tabs/Readme/PackageVersionWithoutCommunityReadme.tsx @@ -0,0 +1,85 @@ +import { Await, type LoaderFunctionArgs } from "react-router"; +import { useLoaderData } from "react-router"; +import { DapperTs } from "@thunderstore/dapper-ts"; +import { + getPublicEnvVariables, + getSessionTools, +} from "cyberstorm/security/publicEnvVariables"; +import { Suspense } from "react"; +import { SkeletonBox } from "@thunderstore/cyberstorm"; +import "./Readme.css"; + +export async function loader({ params }: LoaderFunctionArgs) { + if (params.namespaceId && params.packageId && params.packageVersion) { + const publicEnvVariables = getPublicEnvVariables(["VITE_API_URL"]); + const dapper = new DapperTs(() => { + return { + apiHost: publicEnvVariables.VITE_API_URL, + sessionId: undefined, + }; + }); + return { + readme: await dapper.getPackageReadme( + params.namespaceId, + params.packageId, + params.packageVersion + ), + }; + } + return { + status: "error", + message: "Failed to load readme", + readme: { html: "" }, + }; +} + +export async function clientLoader({ params }: LoaderFunctionArgs) { + if (params.namespaceId && params.packageId && params.packageVersion) { + const tools = getSessionTools(); + const dapper = new DapperTs(() => { + return { + apiHost: tools?.getConfig().apiHost, + sessionId: tools?.getConfig().sessionId, + }; + }); + return { + readme: dapper.getPackageReadme( + params.namespaceId, + params.packageId, + params.packageVersion + ), + }; + } + return { + status: "error", + message: "Failed to load readme", + readme: { html: "" }, + }; +} + +export default function PackageVersionReadme() { + const { status, message, readme } = useLoaderData< + typeof loader | typeof clientLoader + >(); + + if (status === "error") return
{message}
; + return ( + }> + Error occurred while loading description
} + > + {(resolvedValue) => ( + <> +
+
+
+ + )} + + + ); +} diff --git a/apps/cyberstorm-remix/app/p/tabs/Required/PackageVersionRequired.tsx b/apps/cyberstorm-remix/app/p/tabs/Required/PackageVersionRequired.tsx new file mode 100644 index 000000000..8cf870a3a --- /dev/null +++ b/apps/cyberstorm-remix/app/p/tabs/Required/PackageVersionRequired.tsx @@ -0,0 +1,76 @@ +import { type LoaderFunctionArgs } from "react-router"; +import { useLoaderData } from "react-router"; +import { DapperTs } from "@thunderstore/dapper-ts"; +import { + getPublicEnvVariables, + getSessionTools, +} from "cyberstorm/security/publicEnvVariables"; +import { PaginatedDependencies } from "~/commonComponents/PaginatedDependencies/PaginatedDependencies"; + +export async function loader({ params, request }: LoaderFunctionArgs) { + if (params.namespaceId && params.packageId && params.packageVersion) { + const publicEnvVariables = getPublicEnvVariables(["VITE_API_URL"]); + const dapper = new DapperTs(() => { + return { + apiHost: publicEnvVariables.VITE_API_URL, + sessionId: undefined, + }; + }); + const searchParams = new URL(request.url).searchParams; + const page = searchParams.get("page"); + + return { + version: await dapper.getPackageVersionDetails( + params.namespaceId, + params.packageId, + params.packageVersion + ), + dependencies: await dapper.getPackageVersionDependencies( + params.namespaceId, + params.packageId, + params.packageVersion, + page === null ? undefined : Number(page) + ), + }; + } + throw new Response("Package version dependencies not found", { status: 404 }); +} + +export async function clientLoader({ params, request }: LoaderFunctionArgs) { + if (params.namespaceId && params.packageId && params.packageVersion) { + const tools = getSessionTools(); + const dapper = new DapperTs(() => { + return { + apiHost: tools?.getConfig().apiHost, + sessionId: tools?.getConfig().sessionId, + }; + }); + const searchParams = new URL(request.url).searchParams; + const page = searchParams.get("page"); + + return { + version: dapper.getPackageVersionDetails( + params.namespaceId, + params.packageId, + params.packageVersion + ), + dependencies: dapper.getPackageVersionDependencies( + params.namespaceId, + params.packageId, + params.packageVersion, + page === null ? undefined : Number(page) + ), + }; + } + throw new Response("Package version dependencies not found", { status: 404 }); +} + +export default function PackageVersionRequired() { + const { version, dependencies } = useLoaderData< + typeof loader | typeof clientLoader + >(); + + return ( + + ); +} diff --git a/apps/cyberstorm-remix/app/p/tabs/Required/PackageVersionWithoutCommunityRequired.tsx b/apps/cyberstorm-remix/app/p/tabs/Required/PackageVersionWithoutCommunityRequired.tsx new file mode 100644 index 000000000..592621ac2 --- /dev/null +++ b/apps/cyberstorm-remix/app/p/tabs/Required/PackageVersionWithoutCommunityRequired.tsx @@ -0,0 +1,76 @@ +import { type LoaderFunctionArgs } from "react-router"; +import { useLoaderData } from "react-router"; +import { DapperTs } from "@thunderstore/dapper-ts"; +import { + getPublicEnvVariables, + getSessionTools, +} from "cyberstorm/security/publicEnvVariables"; +import { PaginatedDependencies } from "~/commonComponents/PaginatedDependencies/PaginatedDependencies"; + +export async function loader({ params, request }: LoaderFunctionArgs) { + if (params.namespaceId && params.packageId && params.packageVersion) { + const publicEnvVariables = getPublicEnvVariables(["VITE_API_URL"]); + const dapper = new DapperTs(() => { + return { + apiHost: publicEnvVariables.VITE_API_URL, + sessionId: undefined, + }; + }); + const searchParams = new URL(request.url).searchParams; + const page = searchParams.get("page"); + + return { + version: await dapper.getPackageVersionDetails( + params.namespaceId, + params.packageId, + params.packageVersion + ), + dependencies: await dapper.getPackageVersionDependencies( + params.namespaceId, + params.packageId, + params.packageVersion, + page === null ? undefined : Number(page) + ), + }; + } + throw new Response("Package version dependencies not found", { status: 404 }); +} + +export async function clientLoader({ params, request }: LoaderFunctionArgs) { + if (params.namespaceId && params.packageId && params.packageVersion) { + const tools = getSessionTools(); + const dapper = new DapperTs(() => { + return { + apiHost: tools?.getConfig().apiHost, + sessionId: tools?.getConfig().sessionId, + }; + }); + const searchParams = new URL(request.url).searchParams; + const page = searchParams.get("page"); + + return { + version: dapper.getPackageVersionDetails( + params.namespaceId, + params.packageId, + params.packageVersion + ), + dependencies: dapper.getPackageVersionDependencies( + params.namespaceId, + params.packageId, + params.packageVersion, + page === null ? undefined : Number(page) + ), + }; + } + throw new Response("Package version dependencies not found", { status: 404 }); +} + +export default function PackageVersionWithoutCommunityRequired() { + const { version, dependencies } = useLoaderData< + typeof loader | typeof clientLoader + >(); + + return ( + + ); +} diff --git a/apps/cyberstorm-remix/app/p/tabs/Required/Required.tsx b/apps/cyberstorm-remix/app/p/tabs/Required/Required.tsx index a4ace16ba..be62cd526 100644 --- a/apps/cyberstorm-remix/app/p/tabs/Required/Required.tsx +++ b/apps/cyberstorm-remix/app/p/tabs/Required/Required.tsx @@ -1,17 +1,13 @@ -import "./Required.css"; -import { Heading, SkeletonBox } from "@thunderstore/cyberstorm"; -import { Await, type LoaderFunctionArgs } from "react-router"; -import { useLoaderData, useOutletContext } from "react-router"; -import { ListingDependency } from "~/commonComponents/ListingDependency/ListingDependency"; +import { type LoaderFunctionArgs } from "react-router"; +import { useLoaderData } from "react-router"; import { DapperTs } from "@thunderstore/dapper-ts"; -import { type OutletContextShape } from "~/root"; import { getPublicEnvVariables, getSessionTools, } from "cyberstorm/security/publicEnvVariables"; -import { Suspense } from "react"; +import { PaginatedDependencies } from "~/commonComponents/PaginatedDependencies/PaginatedDependencies"; -export async function loader({ params }: LoaderFunctionArgs) { +export async function loader({ params, request }: LoaderFunctionArgs) { if (params.communityId && params.namespaceId && params.packageId) { const publicEnvVariables = getPublicEnvVariables(["VITE_API_URL"]); const dapper = new DapperTs(() => { @@ -20,18 +16,32 @@ export async function loader({ params }: LoaderFunctionArgs) { sessionId: undefined, }; }); + const searchParams = new URL(request.url).searchParams; + const page = searchParams.get("page"); + const listing = await dapper.getPackageListingDetails( + params.communityId, + params.namespaceId, + params.packageId + ); + return { - listing: dapper.getPackageListingDetails( - params.communityId, + version: await dapper.getPackageVersionDetails( + params.namespaceId, + params.packageId, + listing.latest_version_number + ), + dependencies: await dapper.getPackageVersionDependencies( params.namespaceId, - params.packageId + params.packageId, + listing.latest_version_number, + page === null ? undefined : Number(page) ), }; } - throw new Response("Listing dependencies not found", { status: 404 }); + throw new Response("Package version dependencies not found", { status: 404 }); } -export async function clientLoader({ params }: LoaderFunctionArgs) { +export async function clientLoader({ params, request }: LoaderFunctionArgs) { if (params.communityId && params.namespaceId && params.packageId) { const tools = getSessionTools(); const dapper = new DapperTs(() => { @@ -40,56 +50,37 @@ export async function clientLoader({ params }: LoaderFunctionArgs) { sessionId: tools?.getConfig().sessionId, }; }); + const searchParams = new URL(request.url).searchParams; + const page = searchParams.get("page"); + const listing = await dapper.getPackageListingDetails( + params.communityId, + params.namespaceId, + params.packageId + ); + return { - listing: dapper.getPackageListingDetails( - params.communityId, + version: dapper.getPackageVersionDetails( + params.namespaceId, + params.packageId, + listing.latest_version_number + ), + dependencies: dapper.getPackageVersionDependencies( params.namespaceId, - params.packageId + params.packageId, + listing.latest_version_number, + page === null ? undefined : Number(page) ), }; } - throw new Response("Listing dependencies not found", { status: 404 }); + throw new Response("Package version dependencies not found", { status: 404 }); } -export default function Required() { - const { listing } = useLoaderData(); - const outletContext = useOutletContext() as OutletContextShape; +export default function PackageVersionRequired() { + const { version, dependencies } = useLoaderData< + typeof loader | typeof clientLoader + >(); return ( - }> - Error occurred while loading required dependencies
- } - > - {(resolvedValue) => ( - <> -
-
- - Required mods ({resolvedValue.dependencies.length}) - - - This package requires the following packages to work. - -
-
- {resolvedValue.dependencies.map((dep, key) => { - return ( - - ); - })} -
-
- - )} -
-
+ ); } diff --git a/apps/cyberstorm-remix/app/p/tabs/Versions/PackageVersionVersions.tsx b/apps/cyberstorm-remix/app/p/tabs/Versions/PackageVersionVersions.tsx new file mode 100644 index 000000000..829d9e9e2 --- /dev/null +++ b/apps/cyberstorm-remix/app/p/tabs/Versions/PackageVersionVersions.tsx @@ -0,0 +1,134 @@ +import "./Versions.css"; +import { + NewTableSort, + NewTable, + Heading, + SkeletonBox, + NewLink, +} from "@thunderstore/cyberstorm"; +import { Await, type LoaderFunctionArgs } from "react-router"; +import { useLoaderData } from "react-router"; +import { DapperTs } from "@thunderstore/dapper-ts"; +import { + getPublicEnvVariables, + getSessionTools, +} from "cyberstorm/security/publicEnvVariables"; +import { Suspense } from "react"; +import { DownloadLink, InstallLink, ModManagerBanner } from "./common"; +import { rowSemverCompare } from "cyberstorm/utils/semverCompare"; +import { columns } from "./Versions"; + +export async function loader({ params }: LoaderFunctionArgs) { + if (params.communityId && params.namespaceId && params.packageId) { + const publicEnvVariables = getPublicEnvVariables(["VITE_API_URL"]); + const dapper = new DapperTs(() => { + return { + apiHost: publicEnvVariables.VITE_API_URL, + sessionId: undefined, + }; + }); + return { + communityId: params.communityId, + namespaceId: params.namespaceId, + packageId: params.packageId, + versions: dapper.getPackageVersions(params.namespaceId, params.packageId), + }; + } + return { + status: "error", + message: "Failed to load versions", + versions: [], + }; +} + +export async function clientLoader({ params }: LoaderFunctionArgs) { + if (params.communityId && params.namespaceId && params.packageId) { + const tools = getSessionTools(); + const dapper = new DapperTs(() => { + return { + apiHost: tools?.getConfig().apiHost, + sessionId: tools?.getConfig().sessionId, + }; + }); + return { + communityId: params.communityId, + namespaceId: params.namespaceId, + packageId: params.packageId, + versions: dapper.getPackageVersions(params.namespaceId, params.packageId), + }; + } + return { + status: "error", + message: "Failed to load versions", + versions: [], + }; +} + +export default function Versions() { + const { communityId, namespaceId, packageId, status, message, versions } = + useLoaderData(); + + if (status === "error") { + return
{message}
; + } + + return ( + }> + + {(resolvedValue) => ( +
+ +
+ + Versions + + } + headers={columns} + rows={resolvedValue.map((v) => [ + { + value: ( + + {v.version_number} + + ), + sortValue: v.version_number, + }, + { + value: new Date(v.datetime_created).toUTCString(), + sortValue: v.datetime_created, + }, + { + value: v.download_count.toLocaleString(), + sortValue: v.download_count, + }, + { + value: ( +
+ + +
+ ), + sortValue: 0, + }, + ])} + sortDirection={NewTableSort.DESC} + csModifiers={["alignLastColumnRight"]} + customSortCompare={{ 0: rowSemverCompare }} + /> +
+
+ )} +
+
+ ); +} diff --git a/apps/cyberstorm-remix/app/p/tabs/Versions/PackageVersionWithoutCommunityVersions.tsx b/apps/cyberstorm-remix/app/p/tabs/Versions/PackageVersionWithoutCommunityVersions.tsx new file mode 100644 index 000000000..81b931dc9 --- /dev/null +++ b/apps/cyberstorm-remix/app/p/tabs/Versions/PackageVersionWithoutCommunityVersions.tsx @@ -0,0 +1,132 @@ +import "./Versions.css"; +import { + NewTableSort, + NewTable, + Heading, + SkeletonBox, + NewLink, +} from "@thunderstore/cyberstorm"; +import { Await, type LoaderFunctionArgs } from "react-router"; +import { useLoaderData } from "react-router"; +import { DapperTs } from "@thunderstore/dapper-ts"; +import { + getPublicEnvVariables, + getSessionTools, +} from "cyberstorm/security/publicEnvVariables"; +import { Suspense } from "react"; +import { DownloadLink, InstallLink, ModManagerBanner } from "./common"; +import { rowSemverCompare } from "cyberstorm/utils/semverCompare"; +import { columns } from "./Versions"; + +export async function loader({ params }: LoaderFunctionArgs) { + if (params.namespaceId && params.packageId) { + const publicEnvVariables = getPublicEnvVariables(["VITE_API_URL"]); + const dapper = new DapperTs(() => { + return { + apiHost: publicEnvVariables.VITE_API_URL, + sessionId: undefined, + }; + }); + return { + namespaceId: params.namespaceId, + packageId: params.packageId, + versions: dapper.getPackageVersions(params.namespaceId, params.packageId), + }; + } + return { + status: "error", + message: "Failed to load versions", + versions: [], + }; +} + +export async function clientLoader({ params }: LoaderFunctionArgs) { + if (params.namespaceId && params.packageId) { + const tools = getSessionTools(); + const dapper = new DapperTs(() => { + return { + apiHost: tools?.getConfig().apiHost, + sessionId: tools?.getConfig().sessionId, + }; + }); + return { + namespaceId: params.namespaceId, + packageId: params.packageId, + versions: dapper.getPackageVersions(params.namespaceId, params.packageId), + }; + } + return { + status: "error", + message: "Failed to load versions", + versions: [], + }; +} + +export default function Versions() { + const { namespaceId, packageId, status, message, versions } = useLoaderData< + typeof loader | typeof clientLoader + >(); + + if (status === "error") { + return
{message}
; + } + + return ( + }> + + {(resolvedValue) => ( +
+ +
+ + Versions + + } + headers={columns} + rows={resolvedValue.map((v) => [ + { + value: ( + + {v.version_number} + + ), + sortValue: v.version_number, + }, + { + value: new Date(v.datetime_created).toUTCString(), + sortValue: v.datetime_created, + }, + { + value: v.download_count.toLocaleString(), + sortValue: v.download_count, + }, + { + value: ( +
+ + +
+ ), + sortValue: 0, + }, + ])} + sortDirection={NewTableSort.DESC} + csModifiers={["alignLastColumnRight"]} + customSortCompare={{ 0: rowSemverCompare }} + /> +
+
+ )} +
+
+ ); +} diff --git a/apps/cyberstorm-remix/app/p/tabs/Versions/Versions.tsx b/apps/cyberstorm-remix/app/p/tabs/Versions/Versions.tsx index f81ac1a12..12d9be3fc 100644 --- a/apps/cyberstorm-remix/app/p/tabs/Versions/Versions.tsx +++ b/apps/cyberstorm-remix/app/p/tabs/Versions/Versions.tsx @@ -1,37 +1,25 @@ -import { faDownload } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; - import "./Versions.css"; import { NewTableSort, - NewButton, - NewIcon, NewTable, type NewTableLabels, Heading, - NewAlert, SkeletonBox, + NewLink, } from "@thunderstore/cyberstorm"; import { Await, type LoaderFunctionArgs } from "react-router"; import { useLoaderData } from "react-router"; -import { versionsSchema } from "@thunderstore/dapper-ts/src/methods/package"; import { DapperTs } from "@thunderstore/dapper-ts"; -import semverGt from "semver/functions/gt"; -import semverLt from "semver/functions/lt"; -import semverValid from "semver/functions/valid"; -import { - type TableCompareColumnMeta, - type TableRow, -} from "@thunderstore/cyberstorm/src/newComponents/Table/Table"; -import { ThunderstoreLogo } from "@thunderstore/cyberstorm/src/svg/svg"; import { getPublicEnvVariables, getSessionTools, } from "cyberstorm/security/publicEnvVariables"; import { Suspense } from "react"; +import { DownloadLink, InstallLink, ModManagerBanner } from "./common"; +import { rowSemverCompare } from "cyberstorm/utils/semverCompare"; export async function loader({ params }: LoaderFunctionArgs) { - if (params.namespaceId && params.packageId) { + if (params.communityId && params.namespaceId && params.packageId) { const publicEnvVariables = getPublicEnvVariables(["VITE_API_URL"]); const dapper = new DapperTs(() => { return { @@ -40,18 +28,21 @@ export async function loader({ params }: LoaderFunctionArgs) { }; }); return { + communityId: params.communityId, + namespaceId: params.namespaceId, + packageId: params.packageId, versions: dapper.getPackageVersions(params.namespaceId, params.packageId), }; } return { status: "error", message: "Failed to load versions", - versions: versionsSchema.parse({}), + versions: [], }; } export async function clientLoader({ params }: LoaderFunctionArgs) { - if (params.namespaceId && params.packageId) { + if (params.communityId && params.namespaceId && params.packageId) { const tools = getSessionTools(); const dapper = new DapperTs(() => { return { @@ -60,46 +51,22 @@ export async function clientLoader({ params }: LoaderFunctionArgs) { }; }); return { + communityId: params.communityId, + namespaceId: params.namespaceId, + packageId: params.packageId, versions: dapper.getPackageVersions(params.namespaceId, params.packageId), }; } return { status: "error", message: "Failed to load versions", - versions: versionsSchema.parse({}), + versions: [], }; } -type ConfirmedSemverStringType = string; - -export const isSemver = (s: string): s is ConfirmedSemverStringType => { - if (semverValid(s)) { - return true; - } else { - return false; - } -}; - -function rowSemverCompare( - a: TableRow, - b: TableRow, - columnMeta: TableCompareColumnMeta -) { - if (isSemver(String(a[0].sortValue)) && isSemver(String(b[0].sortValue))) { - if (semverLt(String(a[0].sortValue), String(b[0].sortValue))) { - return columnMeta.direction; - } - if (semverGt(String(a[0].sortValue), String(b[0].sortValue))) { - return -columnMeta.direction; - } - } - return 0; -} - export default function Versions() { - const { status, message, versions } = useLoaderData< - typeof loader | typeof clientLoader - >(); + const { communityId, namespaceId, packageId, status, message, versions } = + useLoaderData(); if (status === "error") { return
{message}
; @@ -120,7 +87,22 @@ export default function Versions() { } headers={columns} rows={resolvedValue.map((v) => [ - { value: v.version_number, sortValue: v.version_number }, + { + value: ( + + {v.version_number} + + ), + sortValue: v.version_number, + }, { value: new Date(v.datetime_created).toUTCString(), sortValue: v.datetime_created, @@ -139,7 +121,7 @@ export default function Versions() { sortValue: 0, }, ])} - sortDirection={NewTableSort.ASC} + sortDirection={NewTableSort.DESC} csModifiers={["alignLastColumnRight"]} customSortCompare={{ 0: rowSemverCompare }} /> @@ -151,7 +133,7 @@ export default function Versions() { ); } -const columns: NewTableLabels = [ +export const columns: NewTableLabels = [ { value: "Version", disableSort: false, @@ -169,43 +151,3 @@ const columns: NewTableLabels = [ }, { value: "Actions", disableSort: true }, ]; - -const ModManagerBanner = () => ( - - Please note that the install buttons only work if you have compatible client - software installed, such as the{" "} - - Thunderstore Mod Manager. - {" "} - Otherwise use the zip download links instead. - -); - -const DownloadLink = (props: { download_url: string }) => ( - - - - - Download - -); - -const InstallLink = (props: { install_url: string }) => ( - - - - - Install - -); diff --git a/apps/cyberstorm-remix/app/p/tabs/Versions/common.tsx b/apps/cyberstorm-remix/app/p/tabs/Versions/common.tsx new file mode 100644 index 000000000..965e754ae --- /dev/null +++ b/apps/cyberstorm-remix/app/p/tabs/Versions/common.tsx @@ -0,0 +1,55 @@ +import { faDownload } from "@fortawesome/pro-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { NewAlert, NewButton, NewIcon } from "@thunderstore/cyberstorm"; +import { ThunderstoreLogo } from "@thunderstore/cyberstorm/src/svg/svg"; +import { memo } from "react"; + +export const ModManagerBanner = memo(function ModManagerBanner() { + return ( + + Please note that the install buttons only work if you have compatible + client software installed, such as the{" "} + + Thunderstore Mod Manager. + {" "} + Otherwise use the zip download links instead. + + ); +}); + +export const DownloadLink = memo(function DownloadLink(props: { + download_url: string; +}) { + return ( + + + + + Download + + ); +}); + +export const InstallLink = memo(function InstallLink(props: { + install_url: string; +}) { + return ( + + + + + Install + + ); +}); diff --git a/apps/cyberstorm-remix/app/root.tsx b/apps/cyberstorm-remix/app/root.tsx index e67743a26..d554ebf38 100644 --- a/apps/cyberstorm-remix/app/root.tsx +++ b/apps/cyberstorm-remix/app/root.tsx @@ -271,6 +271,10 @@ export function Layout({ children }: { children: React.ReactNode }) { const uploadPage = matches.find((m) => m.id === "upload/upload"); const communityPage = matches.find((m) => m.id === "c/community"); const packageListingPage = matches.find((m) => m.id === "p/packageListing"); + const packageVersionPage = matches.find((m) => m.id === "p/packageVersion"); + const packageVersionWithoutCommunityPage = matches.find( + (m) => m.id === "p/packageVersionWithoutCommunity" + ); const packageEditPage = matches.find((m) => m.id === "p/packageEdit"); const packageDependantsPage = matches.find( (m) => m.id === "p/dependants/Dependants" @@ -447,10 +451,12 @@ export function Layout({ children }: { children: React.ReactNode }) { communityPage || packageListingPage || packageDependantsPage || - packageTeamPage, + packageTeamPage || + packageVersionPage, Boolean(packageListingPage) || Boolean(packageDependantsPage) || - Boolean(packageTeamPage) + Boolean(packageTeamPage) || + Boolean(packageVersionPage) )} {/* Package listing page */} {getPackageListingBreadcrumb( @@ -458,6 +464,55 @@ export function Layout({ children }: { children: React.ReactNode }) { packageEditPage, packageDependantsPage )} + {/* Package Version Page */} + {packageVersionPage ? ( + <> + + {packageVersionPage.params.packageId} + + + + {packageVersionPage.params.packageVersion} + + + + ) : null} + {/* Package version without community Page */} + {packageVersionWithoutCommunityPage ? ( + <> + + + { + packageVersionWithoutCommunityPage.params + .namespaceId + } + + + + + { + packageVersionWithoutCommunityPage.params + .packageId + } + + + + + { + packageVersionWithoutCommunityPage.params + .packageVersion + } + + + + ) : null} {packageEditPage ? ( Edit package diff --git a/apps/cyberstorm-remix/app/routes.ts b/apps/cyberstorm-remix/app/routes.ts index bcb572314..4723f9599 100644 --- a/apps/cyberstorm-remix/app/routes.ts +++ b/apps/cyberstorm-remix/app/routes.ts @@ -33,9 +33,39 @@ export default [ ]), ]), ]), + route( + ":namespaceId/:packageId/v/:packageVersion", + "p/packageVersion.tsx", + [ + route( + "/c/:communityId/p/:namespaceId/:packageId/v/:packageVersion/", + "p/tabs/Readme/PackageVersionReadme.tsx" + ), + route("required", "p/tabs/Required/PackageVersionRequired.tsx"), + route("versions", "p/tabs/Versions/PackageVersionVersions.tsx"), + ] + ), route(":namespaceId/:packageId/edit", "p/packageEdit.tsx"), ]), ]), + route( + "/p/:namespaceId/:packageId/v/:packageVersion", + "p/packageVersionWithoutCommunity.tsx", + [ + route( + "/p/:namespaceId/:packageId/v/:packageVersion/", + "p/tabs/Readme/PackageVersionWithoutCommunityReadme.tsx" + ), + route( + "required", + "p/tabs/Required/PackageVersionWithoutCommunityRequired.tsx" + ), + route( + "versions", + "p/tabs/Versions/PackageVersionWithoutCommunityVersions.tsx" + ), + ] + ), route( "/c/:communityId/p/:namespaceId/:packageId/dependants", "p/dependants/Dependants.tsx" diff --git a/apps/cyberstorm-remix/cyberstorm/utils/LinkLibrary.tsx b/apps/cyberstorm-remix/cyberstorm/utils/LinkLibrary.tsx index c74681f1b..dd4270c56 100644 --- a/apps/cyberstorm-remix/cyberstorm/utils/LinkLibrary.tsx +++ b/apps/cyberstorm-remix/cyberstorm/utils/LinkLibrary.tsx @@ -162,6 +162,41 @@ const library: LinkLibrary = { ref={p.customRef} /> ), + PackageVersionRequired: (p) => ( + + ), + PackageVersionVersions: (p) => ( + + ), + PackageVersionWithoutCommunity: (p) => ( + + ), + PackageVersionWithoutCommunityRequired: (p) => ( + + ), + PackageVersionWithoutCommunityVersions: (p) => ( + + ), PackageFormatDocs: (p) => ( ), diff --git a/apps/storybook/LinkLibrary.tsx b/apps/storybook/LinkLibrary.tsx index bfb535e62..bd8bdcc11 100644 --- a/apps/storybook/LinkLibrary.tsx +++ b/apps/storybook/LinkLibrary.tsx @@ -164,6 +164,41 @@ const library: LinkLibrary = { ref={p.customRef} /> ), + PackageVersionRequired: (p) => ( + + ), + PackageVersionVersions: (p) => ( + + ), + PackageVersionWithoutCommunity: (p) => ( + + ), + PackageVersionWithoutCommunityRequired: (p) => ( + + ), + PackageVersionWithoutCommunityVersions: (p) => ( + + ), PackageFormatDocs: (p) => ( ), diff --git a/packages/cyberstorm/src/components/Links/LinkingProvider.tsx b/packages/cyberstorm/src/components/Links/LinkingProvider.tsx index 4d9f6ded7..b1bf1067f 100644 --- a/packages/cyberstorm/src/components/Links/LinkingProvider.tsx +++ b/packages/cyberstorm/src/components/Links/LinkingProvider.tsx @@ -110,6 +110,22 @@ export interface LinkLibrary { PackageFormatDocs: NoRequiredProps; /** PackageVersion's detail view */ PackageVersion: (props: AnyProps & PackageVersionProps) => RE | null; + /** PackageVersion's required view */ + PackageVersionRequired: (props: AnyProps & PackageVersionProps) => RE | null; + /** PackageVersion's versions view */ + PackageVersionVersions: (props: AnyProps & PackageVersionProps) => RE | null; + /** PackageVersionWithoutCommunity's detail view */ + PackageVersionWithoutCommunity: ( + props: AnyProps & Omit + ) => RE | null; + /** PackageVersionWithoutCommunity's required view */ + PackageVersionWithoutCommunityRequired: ( + props: AnyProps & Omit + ) => RE | null; + /** PackageVersionWithoutCommunity's versions view */ + PackageVersionWithoutCommunityVersions: ( + props: AnyProps & Omit + ) => RE | null; /** View for submitting new packages or versions */ PackageUpload: NoRequiredProps; /** Privacy policy */ @@ -163,6 +179,11 @@ const library: LinkLibrary = { PackageDependants: noop, PackageFormatDocs: noop, PackageVersion: noop, + PackageVersionRequired: noop, + PackageVersionVersions: noop, + PackageVersionWithoutCommunity: noop, + PackageVersionWithoutCommunityRequired: noop, + PackageVersionWithoutCommunityVersions: noop, PackageUpload: noop, PrivacyPolicy: noop, User: noop, diff --git a/packages/cyberstorm/src/components/Links/Links.tsx b/packages/cyberstorm/src/components/Links/Links.tsx index b5a6dff9b..df27c815d 100644 --- a/packages/cyberstorm/src/components/Links/Links.tsx +++ b/packages/cyberstorm/src/components/Links/Links.tsx @@ -40,6 +40,11 @@ export type CyberstormLinkIds = | "PackageDependants" | "PackageFormatDocs" | "PackageVersion" + | "PackageVersionRequired" + | "PackageVersionVersions" + | "PackageVersionWithoutCommunity" + | "PackageVersionWithoutCommunityRequired" + | "PackageVersionWithoutCommunityVersions" | "PackageUpload" | "PrivacyPolicy" | "Settings"