From 4ae86a0d193dcf9022596e74d0934a86309f8298 Mon Sep 17 00:00:00 2001 From: Oksamies Date: Sun, 7 Sep 2025 01:25:41 +0300 Subject: [PATCH 1/4] Refactor many pages to support proper suspensions --- apps/cyberstorm-remix/app/c/Community.css | 119 +++- apps/cyberstorm-remix/app/c/community.tsx | 412 +++++++------- .../c/tabs/PackageSearch/PackageSearch.tsx | 117 ++++ .../Collapsible/Collapsible.css | 6 + .../PackageSearch/PackageSearch.css | 27 +- .../PackageSearch/PackageSearch.tsx | 515 +++++++++++------- .../components/PackageCount/PackageCount.css | 1 + .../PageHeader/PageHeader.css | 3 +- .../app/communities/Communities.css | 8 +- .../app/communities/communities.tsx | 19 +- .../app/p/dependants/Dependants.tsx | 4 +- apps/cyberstorm-remix/app/p/packageEdit.css | 4 - apps/cyberstorm-remix/app/p/packageEdit.tsx | 4 +- .../cyberstorm-remix/app/p/packageListing.css | 68 +-- .../cyberstorm-remix/app/p/packageListing.tsx | 155 ++---- .../app/p/tabs/Changelog/Changelog.css | 5 + .../app/p/tabs/Changelog/Changelog.tsx | 111 ++-- .../app/p/tabs/Readme/Readme.css | 5 + .../app/p/tabs/Readme/Readme.tsx | 103 ++-- .../app/p/tabs/Required/Required.css | 12 +- .../app/p/tabs/Required/Required.tsx | 127 +++-- .../app/p/tabs/Versions/Versions.css | 16 +- .../app/p/tabs/Versions/Versions.tsx | 145 ++--- .../cyberstorm-remix/app/p/tabs/Wiki/Wiki.css | 40 +- .../cyberstorm-remix/app/p/tabs/Wiki/Wiki.tsx | 20 +- .../app/p/tabs/Wiki/WikiContent.tsx | 14 +- .../app/p/tabs/Wiki/WikiFirstPage.tsx | 56 +- .../app/p/tabs/Wiki/WikiNewPage.tsx | 14 +- .../app/p/tabs/Wiki/WikiPage.tsx | 91 ++-- .../app/p/tabs/Wiki/WikiPageEdit.tsx | 16 +- apps/cyberstorm-remix/app/p/team/Team.tsx | 4 +- apps/cyberstorm-remix/app/root.tsx | 144 ++++- apps/cyberstorm-remix/app/routes.ts | 40 +- .../app/settings/teams/Teams.tsx | 4 +- .../app/settings/teams/team/teamSettings.tsx | 4 +- .../app/settings/user/Settings.tsx | 4 +- apps/cyberstorm-remix/app/styles/layout.css | 6 +- .../manifest-validator/manifestValidator.tsx | 4 +- .../markdown-preview/markdownPreview.tsx | 4 +- .../package-format-docs/packageFormatDocs.tsx | 4 +- apps/cyberstorm-remix/app/upload/upload.tsx | 4 +- .../newComponents/BreadCrumbs/BreadCrumbs.css | 37 +- .../newComponents/SkeletonBox/SkeletonBox.css | 44 +- 43 files changed, 1435 insertions(+), 1105 deletions(-) create mode 100644 apps/cyberstorm-remix/app/c/tabs/PackageSearch/PackageSearch.tsx create mode 100644 apps/cyberstorm-remix/app/p/tabs/Changelog/Changelog.css create mode 100644 apps/cyberstorm-remix/app/p/tabs/Readme/Readme.css diff --git a/apps/cyberstorm-remix/app/c/Community.css b/apps/cyberstorm-remix/app/c/Community.css index 5190fb42a..1548c945b 100644 --- a/apps/cyberstorm-remix/app/c/Community.css +++ b/apps/cyberstorm-remix/app/c/Community.css @@ -8,34 +8,123 @@ gap: 1rem; align-items: flex-start; align-self: stretch; - padding: 7.5rem 3rem 2rem; + padding: 1rem 3rem 2rem; } - .community__background { - position: absolute; + .community__header { display: flex; flex-direction: column; align-items: flex-start; - height: 20rem; - border-radius: var(--section-border-radius); - overflow: hidden; + align-self: stretch; + + /* min-height: 6.25rem; */ } - .community__background-image { + .community__background { display: flex; flex-direction: column; - align-items: flex-start; - background: var(--color-body-bg-color, #101028); + align-items: center; + align-self: stretch; + justify-content: center; + height: 12.5rem; + border-radius: 0.5rem; + overflow-y: hidden; + transition: height 2s; + + > img { + border-radius: 0.5rem; + background: var(--color-ui-surface-1); + opacity: 0.8; + } + } - opacity: 0.4; - mix-blend-mode: luminosity; + .community__background--packagePage { + height: 6.25rem; + + > img { + opacity: 0.3; + mix-blend-mode: luminosity; + } } - .community__background-tint { - position: absolute; + .community__content-header { + z-index: 1; + display: flex; + gap: 1.5rem; + align-items: flex-end; + align-self: stretch; + height: max-content; + margin-top: -1rem; + padding-left: 1rem; + transition: + height ease 1s, + opacity 0.2s, + visibility 1s 0s; + } + + .community__content-header--hide { + height: 0; + visibility: collapse; + opacity: 0; + transition: + height ease 1s, + opacity 0.8s 0.2s, + visibility 1s 0s; + } + + .community__game-icon { + display: flex; + gap: 0.5rem; + align-items: center; + width: 7rem; + height: 7rem; + padding: var(--Space-12, 0.75rem); + border: 1px solid var(--Color-border-bright, rgb(70 70 149 / 0.66)); + border-radius: var(--Radius-xl, 1rem); + background: var(--Color-Surface-1, #070721); + aspect-ratio: 1/1; + } + + .community__game-icon-tinified { + display: flex; + flex: 1 0 0; + align-items: center; + justify-content: center; + height: 5.5rem; + + > img { + width: 5.5rem; + height: 5.5rem; + } + } + + .community__content-header-content { + display: flex; + flex-direction: column; + gap: 0.75rem; + align-items: flex-start; + justify-content: center; width: 100%; - height: 20rem; - background: linear-gradient(180deg, rgb(16 16 40 / 0.4) 0%, #101028 85.94%); + height: 5rem; + } + + .community__header-info { + display: flex; + flex: 1 0 0; + flex-direction: column; + gap: 0.25rem; + align-items: flex-start; + align-self: stretch; + width: 40%; + } + + .community__header-meta { + display: flex; + flex: 0 1 0; + gap: 1.5rem; + align-items: center; + width: 60%; + min-height: 16px; } .community__small-image { diff --git a/apps/cyberstorm-remix/app/c/community.tsx b/apps/cyberstorm-remix/app/c/community.tsx index a418f6f61..ade0f3bcf 100644 --- a/apps/cyberstorm-remix/app/c/community.tsx +++ b/apps/cyberstorm-remix/app/c/community.tsx @@ -1,28 +1,36 @@ -import type { LoaderFunctionArgs } from "react-router"; -import { Await, useLoaderData, useOutletContext } from "react-router"; +import type { + LoaderFunctionArgs, + ShouldRevalidateFunctionArgs, +} from "react-router"; import { - NewBreadCrumbs, - NewBreadCrumbsLink, + Await, + Outlet, + useLoaderData, + useLocation, + useOutletContext, +} from "react-router"; +import { + Heading, + NewButton, NewIcon, NewLink, + SkeletonBox, } from "@thunderstore/cyberstorm"; import "./Community.css"; -import { PackageSearch } from "~/commonComponents/PackageSearch/PackageSearch"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faBook } from "@fortawesome/free-solid-svg-icons"; +import { faBook, faDownload } from "@fortawesome/free-solid-svg-icons"; import { faDiscord } from "@fortawesome/free-brands-svg-icons"; -import { PackageOrderOptions } from "~/commonComponents/PackageSearch/components/PackageOrder"; import { faArrowUpRight } from "@fortawesome/pro-solid-svg-icons"; import { DapperTs } from "@thunderstore/dapper-ts"; import { OutletContextShape } from "../root"; -import { PageHeader } from "~/commonComponents/PageHeader/PageHeader"; import { getPublicEnvVariables, getSessionTools, } from "cyberstorm/security/publicEnvVariables"; -import { Suspense, useMemo } from "react"; +import { Suspense } from "react"; +import { classnames } from "@thunderstore/cyberstorm/src/utils/utils"; -export async function loader({ request, params }: LoaderFunctionArgs) { +export async function loader({ params }: LoaderFunctionArgs) { if (params.communityId) { const publicEnvVariables = getPublicEnvVariables(["VITE_API_URL"]); const dapper = new DapperTs(() => { @@ -31,42 +39,16 @@ export async function loader({ request, params }: LoaderFunctionArgs) { sessionId: undefined, }; }); - const searchParams = new URL(request.url).searchParams; - const ordering = - searchParams.get("ordering") ?? PackageOrderOptions.Updated; - const page = searchParams.get("page"); - const search = searchParams.get("search"); - const includedCategories = searchParams.get("includedCategories"); - const excludedCategories = searchParams.get("excludedCategories"); - const section = searchParams.get("section"); - const nsfw = searchParams.get("nsfw"); - const deprecated = searchParams.get("deprecated"); const community = dapper.getCommunity(params.communityId); - const filters = dapper.getCommunityFilters(params.communityId); return { community: community, - filters: filters, - listings: dapper.getPackageListings( - { - kind: "community", - communityId: params.communityId, - }, - ordering ?? "", - page === null ? undefined : Number(page), - search ?? "", - includedCategories?.split(",") ?? undefined, - excludedCategories?.split(",") ?? undefined, - section ? (section === "all" ? "" : section) : "", - nsfw === "true" ? true : false, - deprecated === "true" ? true : false - ), }; } throw new Response("Community not found", { status: 404 }); } -export async function clientLoader({ request, params }: LoaderFunctionArgs) { +export async function clientLoader({ params }: LoaderFunctionArgs) { if (params.communityId) { const tools = getSessionTools(); const dapper = new DapperTs(() => { @@ -75,217 +57,195 @@ export async function clientLoader({ request, params }: LoaderFunctionArgs) { sessionId: tools?.getConfig().sessionId, }; }); - const searchParams = new URL(request.url).searchParams; - const ordering = - searchParams.get("ordering") ?? PackageOrderOptions.Updated; - const page = searchParams.get("page"); - const search = searchParams.get("search"); - const includedCategories = searchParams.get("includedCategories"); - const excludedCategories = searchParams.get("excludedCategories"); - const section = searchParams.get("section"); - const nsfw = searchParams.get("nsfw"); - const deprecated = searchParams.get("deprecated"); const community = dapper.getCommunity(params.communityId); - const filters = dapper.getCommunityFilters(params.communityId); return { community: community, - filters: filters, - listings: dapper.getPackageListings( - { - kind: "community", - communityId: params.communityId, - }, - ordering ?? "", - page === null ? undefined : Number(page), - search ?? "", - includedCategories?.split(",") ?? undefined, - excludedCategories?.split(",") ?? undefined, - section ? (section === "all" ? "" : section) : "", - nsfw === "true" ? true : false, - deprecated === "true" ? true : false - ), }; } throw new Response("Community not found", { status: 404 }); } +export function shouldRevalidate(arg: ShouldRevalidateFunctionArgs) { + if ( + arg.currentUrl.pathname.split("/")[1] === arg.nextUrl.pathname.split("/")[1] + ) { + return false; + } + return arg.defaultShouldRevalidate; +} + export default function Community() { - const { community, filters, listings } = useLoaderData< - typeof loader | typeof clientLoader - >(); + const { community } = useLoaderData(); + const location = useLocation(); + const splitPath = location.pathname.split("/"); + const isSubPath = splitPath.length > 4; + const isPackageListingSubPath = + splitPath.length > 5 && splitPath[1] === "c" && splitPath[3] === "p"; const outletContext = useOutletContext() as OutletContextShape; - const listingsAndFiltersMemo = useMemo( - () => Promise.all([listings, filters]), - [] - ); - return ( <> - - - {(resolvedValue) => ( - <> - - - - - - - - - - - - )} - - - - Loading... - - } - > - - {(resolvedValue) => ( -
- {resolvedValue.hero_image_url ? ( - {resolvedValue.name} - ) : null} -
-
- )} - - -
- - - Communities - - - Loading... - - } - > - - {(resolvedValue) => ( - - {resolvedValue.name} - - )} - - - - - Loading... - - } - > + {isSubPath ? null : ( + - {(resolvedValue) => ( - - {resolvedValue.wiki_url ? ( - - - - - Modding Wiki - - - - - ) : null} - {resolvedValue.discord_url ? ( - - - - - Modding Discord - - - - - ) : null} - - } - > - {resolvedValue.name} - - )} - - - - Loading... - - } - > - {(resolvedValue) => ( <> - + + + + + + + + + )} -
+ )} + + <> +
+
+ }> + + {(resolvedValue) => + resolvedValue.hero_image_url ? ( + {resolvedValue.name} + ) : null + } + + +
+ +
+
+
+ }> + + {(resolvedValue) => + resolvedValue.community_icon_url ? ( + {resolvedValue.name} + ) : null + } + + +
+
+
+
+ }> + + {(resolvedValue) => ( + + {resolvedValue.name} + + )} + + +
+
+ }> + + {(resolvedValue) => + resolvedValue.wiki_url ? ( + + + + + Modding Wiki + + + + + ) : null + } + + + }> + + {(resolvedValue) => + resolvedValue.discord_url ? ( + + + + + Modding Discord + + + + + ) : null + } + + +
+
+ + + + + Upload package + +
+
+ + ); } diff --git a/apps/cyberstorm-remix/app/c/tabs/PackageSearch/PackageSearch.tsx b/apps/cyberstorm-remix/app/c/tabs/PackageSearch/PackageSearch.tsx new file mode 100644 index 000000000..2cf8be887 --- /dev/null +++ b/apps/cyberstorm-remix/app/c/tabs/PackageSearch/PackageSearch.tsx @@ -0,0 +1,117 @@ +import type { LoaderFunctionArgs } from "react-router"; +import { useLoaderData, useOutletContext } from "react-router"; +import { PackageSearch } from "~/commonComponents/PackageSearch/PackageSearch"; +import { PackageOrderOptions } from "~/commonComponents/PackageSearch/components/PackageOrder"; +import { DapperTs } from "@thunderstore/dapper-ts"; +import { + getPublicEnvVariables, + getSessionTools, +} from "cyberstorm/security/publicEnvVariables"; +import { OutletContextShape } from "~/root"; + +export async function loader({ request, params }: LoaderFunctionArgs) { + if (params.communityId) { + 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 ordering = + searchParams.get("ordering") ?? PackageOrderOptions.Updated; + const page = searchParams.get("page"); + const search = searchParams.get("search"); + const includedCategories = searchParams.get("includedCategories"); + const excludedCategories = searchParams.get("excludedCategories"); + const section = searchParams.get("section"); + const nsfw = searchParams.get("nsfw"); + const deprecated = searchParams.get("deprecated"); + const filters = dapper.getCommunityFilters(params.communityId); + + return { + filters: filters, + listings: dapper.getPackageListings( + { + kind: "community", + communityId: params.communityId, + }, + ordering ?? "", + page === null ? undefined : Number(page), + search ?? "", + includedCategories?.split(",") ?? undefined, + excludedCategories?.split(",") ?? undefined, + section ? (section === "all" ? "" : section) : "", + nsfw === "true" ? true : false, + deprecated === "true" ? true : false + ), + }; + } + throw new Response("Community not found", { status: 404 }); +} + +export async function clientLoader({ request, params }: LoaderFunctionArgs) { + if (params.communityId) { + const tools = getSessionTools(); + const dapper = new DapperTs(() => { + return { + apiHost: tools?.getConfig().apiHost, + sessionId: tools?.getConfig().sessionId, + }; + }); + const searchParams = new URL(request.url).searchParams; + const ordering = + searchParams.get("ordering") ?? PackageOrderOptions.Updated; + const page = searchParams.get("page"); + const search = searchParams.get("search"); + const includedCategories = searchParams.get("includedCategories"); + const excludedCategories = searchParams.get("excludedCategories"); + const section = searchParams.get("section"); + const nsfw = searchParams.get("nsfw"); + const deprecated = searchParams.get("deprecated"); + const filters = dapper.getCommunityFilters(params.communityId); + return { + filters: filters, + listings: dapper.getPackageListings( + { + kind: "community", + communityId: params.communityId, + }, + ordering ?? "", + page === null ? undefined : Number(page), + search ?? "", + includedCategories?.split(",") ?? undefined, + excludedCategories?.split(",") ?? undefined, + section ? (section === "all" ? "" : section) : "", + nsfw === "true" ? true : false, + deprecated === "true" ? true : false + ), + }; + } + throw new Response("Community not found", { status: 404 }); +} + +// function shouldRevalidate(arg: ShouldRevalidateFunctionArgs) { +// return true; // false +// } + +export default function CommunityPackageSearch() { + const { filters, listings } = useLoaderData< + typeof loader | typeof clientLoader + >(); + + const outletContext = useOutletContext() as OutletContextShape; + + return ( + <> + + + ); +} diff --git a/apps/cyberstorm-remix/app/commonComponents/Collapsible/Collapsible.css b/apps/cyberstorm-remix/app/commonComponents/Collapsible/Collapsible.css index 7ede99188..126fd96de 100644 --- a/apps/cyberstorm-remix/app/commonComponents/Collapsible/Collapsible.css +++ b/apps/cyberstorm-remix/app/commonComponents/Collapsible/Collapsible.css @@ -3,12 +3,18 @@ display: flex; flex: 1; flex-direction: column; + align-items: flex-start; + align-self: stretch; + padding: 0.5rem; + border-radius: var(--Radius-md, 0.5rem); font-size: var(--font-size-body-md); + background: var(--Color-UI-surface-1, rgb(57 57 106 / 0.15)); } .collapsible__header { display: flex; gap: var(--gap-md); + align-self: stretch; justify-content: space-between; padding: var(--space-8) var(--space-12); font-weight: var(--font-weight-bold); diff --git a/apps/cyberstorm-remix/app/commonComponents/PackageSearch/PackageSearch.css b/apps/cyberstorm-remix/app/commonComponents/PackageSearch/PackageSearch.css index 7ab1b8927..d16f40b92 100644 --- a/apps/cyberstorm-remix/app/commonComponents/PackageSearch/PackageSearch.css +++ b/apps/cyberstorm-remix/app/commonComponents/PackageSearch/PackageSearch.css @@ -11,14 +11,13 @@ top: calc(var(--header-height) + 1rem); display: flex; flex-direction: column; - gap: var(--gap-xs); + gap: var(--gap-sm); align-items: flex-start; - width: 17rem; + min-width: 15.5rem; max-height: calc(100vh - var(--header-height) - 2rem); - padding: var(--space-12); + padding-top: var(--space-16); border-radius: var(--radius-md); overflow-y: auto; - background: var(--color-surface-default); scrollbar-width: none; } @@ -29,9 +28,11 @@ .package-search__filters { display: flex; flex-direction: column; - gap: var(--gap-xs); + gap: var(--gap-sm); + gap: 0.75rem; align-items: stretch; align-self: stretch; + width: 15.5rem; } .package-search__content { @@ -40,6 +41,7 @@ flex-direction: column; gap: var(--space-24); align-items: flex-start; + padding-top: 1rem; } .package-search__pagination { @@ -78,18 +80,10 @@ align-self: stretch; } - .package-search__results { - display: flex; - flex: 1; - gap: var(--gap-xxs); - align-items: center; - } - .package-search__listing-actions { display: flex; gap: var(--gap-md); align-items: center; - justify-content: flex-end; /* > .__display { display: flex; @@ -105,6 +99,13 @@ justify-content: space-between; } + .package-search__results { + display: flex; + flex: 1; + gap: var(--gap-xxs); + align-items: center; + } + .package-search__packages { display: flex; flex: 1 1 0; diff --git a/apps/cyberstorm-remix/app/commonComponents/PackageSearch/PackageSearch.tsx b/apps/cyberstorm-remix/app/commonComponents/PackageSearch/PackageSearch.tsx index 4ed9f7db1..6ebe0e050 100644 --- a/apps/cyberstorm-remix/app/commonComponents/PackageSearch/PackageSearch.tsx +++ b/apps/cyberstorm-remix/app/commonComponents/PackageSearch/PackageSearch.tsx @@ -2,11 +2,10 @@ import { faGhost, faSearch } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { CurrentUser, - PackageCategory, PackageListings, Section, } from "@thunderstore/dapper/types"; -import { useEffect, useRef, useState } from "react"; +import { Suspense, useEffect, useRef, useState } from "react"; import { useDebounce } from "use-debounce"; import "./PackageSearch.css"; @@ -18,11 +17,7 @@ import { NewPagination, NewTextInput, } from "@thunderstore/cyberstorm"; -import { - useNavigation, - useNavigationType, - useSearchParams, -} from "react-router"; +import { Await, useNavigationType, useSearchParams } from "react-router"; import { PackageCount } from "./components/PackageCount/PackageCount"; import { isPackageOrderOptions, @@ -34,17 +29,18 @@ import { RadioGroup } from "../RadioGroup/RadioGroup"; import { CategoryTagCloud } from "./components/CategoryTagCloud/CategoryTagCloud"; import { CollapsibleMenu } from "../Collapsible/Collapsible"; import { CheckboxList } from "../CheckboxList/CheckboxList"; -import { StalenessIndicator } from "../StalenessIndicator/StalenessIndicator"; import { PackageLikeAction } from "@thunderstore/cyberstorm-forms"; -import { RequestConfig } from "@thunderstore/thunderstore-api"; +import { + CommunityFilters, + RequestConfig, +} from "@thunderstore/thunderstore-api"; import { DapperTs } from "@thunderstore/dapper-ts"; const PER_PAGE = 20; interface Props { - listings: PackageListings; - packageCategories: PackageCategory[]; - sections: Section[]; + listings: Promise; + filters: Promise; config: () => RequestConfig; currentUser?: CurrentUser; dapper: DapperTs; @@ -63,7 +59,7 @@ type SearchParamsType = { const searchParamsToBlob = ( searchParams: URLSearchParams, - sections: Section[] + sections?: Section[] ) => { const initialSearch = searchParams.getAll("search").join(" "); const initialOrder = searchParams.get("ordering"); @@ -80,7 +76,11 @@ const searchParamsToBlob = ( initialOrder && isPackageOrderOptions(initialOrder) ? (initialOrder as PackageOrderOptionsType) : undefined, - section: sections.length === 0 ? "" : initialSection ?? sections[0]?.uuid, + section: sections + ? sections.length === 0 + ? "" + : initialSection ?? sections[0]?.uuid + : initialSection ?? "", deprecated: initialDeprecated === null ? false @@ -111,10 +111,11 @@ const searchParamsToBlob = ( }; function parseCategories( - categories: CategorySelection[], includedCategories: string, - excludedCategories: string + excludedCategories: string, + categories?: CategorySelection[] ): CategorySelection[] { + if (!categories) return []; const iCArr = includedCategories.split(","); const eCArr = excludedCategories.split(","); return categories.map((c) => @@ -145,114 +146,56 @@ const compareSearchParamBlobs = ( * Component for filtering and rendering a PackageList */ export function PackageSearch(props: Props) { - const { - listings, - packageCategories: allCategories, - sections, - config, - currentUser, - dapper, - } = props; - - const sortedSections = sections.sort((a, b) => b.priority - a.priority); + const { listings, filters, config, currentUser, dapper } = props; - const navigation = useNavigation(); + // const navigation = useNavigation(); const navigationType = useNavigationType(); - const [searchParams, setSearchParams] = useSearchParams(); - - const initialParams = searchParamsToBlob(searchParams, sortedSections); - - const [searchParamsBlob, setSearchParamsBlob] = - useState(initialParams); - - const [currentPage, setCurrentPage] = useState(initialParams.page); - - // Start setters - const setSearch = (v: string) => { - setSearchParamsBlob({ ...searchParamsBlob, search: v }); - }; - - const setSection = (v: string) => { - setSearchParamsBlob({ ...searchParamsBlob, section: v }); - }; + // const listingsAndFiltersMemo = useMemo( + // () => Promise.all([listings, filters]), + // [listings, filters] + // ); - const setDeprecated = (v: boolean) => { - setSearchParamsBlob({ ...searchParamsBlob, deprecated: v }); - }; + const [sortedSections, setSortedSections] = + useState(); + const [categories, setCategories] = useState(); - const setNsfw = (v: boolean) => { - setSearchParamsBlob({ ...searchParamsBlob, nsfw: v }); - }; + const [searchParams, setSearchParams] = useSearchParams(); - const setPage = (v: number) => { - setSearchParamsBlob({ ...searchParamsBlob, page: v }); - }; + // filters.then((resolvedFilters) => { + // // Set sorted sections + // setSortedSections( + // resolvedFilters.sections.sort((a, b) => b.priority - a.priority) + // ); + // // Set current "initial" categories + // const categories: CategorySelection[] = resolvedFilters.package_categories + // .sort((a, b) => a.slug.localeCompare(b.slug)) + // .map((c) => ({ ...c, selection: "off" })); + // setCategories(categories); + // }); - const setOrder = (v: PackageOrderOptionsType) => { - setSearchParamsBlob({ ...searchParamsBlob, order: v }); - }; + const initialParams = searchParamsToBlob(searchParams); - const resetParams = (order: PackageOrderOptionsType | undefined) => { - setSearchParamsBlob({ - search: "", - order: order, - section: sortedSections.length === 0 ? "" : sortedSections[0]?.uuid, - deprecated: false, - nsfw: false, - page: 1, - includedCategories: "", - excludedCategories: "", - }); - // setOrdering(order); - // setPage(1); - // setSearchValue(""); - }; + const [searchParamsBlob, setSearchParamsBlob] = + useState(initialParams); - const clearAll = () => - setSearchParamsBlob({ - ...searchParamsBlob, - search: "", - includedCategories: "", - excludedCategories: "", - }); - // End setters + const [currentPage, setCurrentPage] = useState( + searchParams.get("page") ? Number(searchParams.get("page")) : 1 + ); // Categories start - const categories: CategorySelection[] = allCategories - .sort((a, b) => a.slug.localeCompare(b.slug)) - .map((c) => ({ ...c, selection: "off" })); - - const setCategories = (v: CategorySelection[]) => { - const newSearchParams = { ...searchParamsBlob }; - const includedCategories = v - .filter((c) => c.selection === "include") - .map((c) => c.id); - if (includedCategories.length === 0) { - newSearchParams.includedCategories = ""; - } else { - newSearchParams.includedCategories = includedCategories.join(","); - } - const excludedCategories = v - .filter((c) => c.selection === "exclude") - .map((c) => c.id); - if (excludedCategories.length === 0) { - newSearchParams.excludedCategories = ""; - } else { - newSearchParams.excludedCategories = excludedCategories.join(","); - } - setSearchParamsBlob({ ...newSearchParams }); - }; const parsedCategories = parseCategories( - categories, searchParamsBlob.includedCategories, - searchParamsBlob.excludedCategories + searchParamsBlob.excludedCategories, + categories ); const updateCatSelection = (catId: string, v: TRISTATE) => { - setCategories( + setParamsBlobCategories( + setSearchParamsBlob, + searchParamsBlob, parsedCategories.map((uc) => { if (uc.id === catId) { return { @@ -367,14 +310,18 @@ export function PackageSearch(props: Props) { // Because of the first section being a empty value, the logic check is a bit funky // If no section in search params, delete - if (sortedSections.length === 0) searchParams.delete("section"); + if (sortedSections && sortedSections.length === 0) + searchParams.delete("section"); // If new section is empty, delete (defaults to first) if (debouncedSearchParamsBlob.section === "") searchParams.delete("section"); // If new section is the first one, delete. And reset page number if section is different from last render. - if (debouncedSearchParamsBlob.section === sortedSections[0]?.uuid) { + if ( + sortedSections && + debouncedSearchParamsBlob.section === sortedSections[0]?.uuid + ) { if ( searchParamsBlobRef.current.section !== debouncedSearchParamsBlob.section @@ -386,6 +333,7 @@ export function PackageSearch(props: Props) { // If new section is different and not the first one, set it. if ( + sortedSections && searchParamsBlobRef.current.section !== debouncedSearchParamsBlob.section && debouncedSearchParamsBlob.section !== sortedSections[0]?.uuid @@ -485,8 +433,8 @@ export function PackageSearch(props: Props) { } }, [debouncedSearchParamsBlob]); + // WHOLE LIKE THING const [ratedPackages, setRatedPackages] = useState([]); - const fetchAndSetRatedPackages = async () => { setRatedPackages((await dapper.getRatedPackages()).rated_packages); }; @@ -499,31 +447,18 @@ export function PackageSearch(props: Props) { } }, [currentUser]); - // End updating page - - // Start actions const likeAction = PackageLikeAction({ isLoggedIn: Boolean(currentUser?.username), dataUpdateTrigger: fetchAndSetRatedPackages, config: config, }); - // End actions + // WHOLE LIKE THING return (
- setSearch(e.target.value)} - clearValue={() => setSearch("")} - leftIcon={} - id="searchInput" - type="search" - rootClasses="package-search__search" - />
- {sortedSections.length > 0 ? ( + {sortedSections && sortedSections.length > 0 ? ( ) : null} - {categories.length > 0 ? ( + {categories && categories.length > 0 ? ( @@ -551,7 +490,11 @@ export function PackageSearch(props: Props) { { state: searchParamsBlob.deprecated, setStateFunc: (v: boolean | TRISTATE) => - setDeprecated( + setParamsBlobValue( + setSearchParamsBlob, + searchParamsBlob, + "deprecated" + )( typeof v === "boolean" ? v : v === "include" @@ -563,7 +506,11 @@ export function PackageSearch(props: Props) { { state: searchParamsBlob.nsfw, setStateFunc: (v: boolean | TRISTATE) => - setNsfw( + setParamsBlobValue( + setSearchParamsBlob, + searchParamsBlob, + "nsfw" + )( typeof v === "boolean" ? v : v === "include" @@ -579,105 +526,189 @@ export function PackageSearch(props: Props) {
+ + setParamsBlobValue( + setSearchParamsBlob, + searchParamsBlob, + "search" + )(e.target.value) + } + clearValue={() => + setParamsBlobValue( + setSearchParamsBlob, + searchParamsBlob, + "search" + )("") + } + leftIcon={} + id="searchInput" + type="search" + rootClasses="package-search__search" + />
+ setParamsBlobCategories(setSearchParamsBlob, searchParamsBlob, v) + } rootClasses="package-search__tags" - clearAll={clearAll} + clearAll={clearAll(setSearchParamsBlob, searchParamsBlob)} />
-
- -
- {/*
*/}
+
+ + Loading... + + } + > + + {(resolvedValue) => ( + + )} + + +
- - {listings.results.length > 0 ? ( -
- {listings.results.map((p) => ( - { - if (likeAction) { - likeAction( - ratedPackages.includes(`${p.namespace}-${p.name}`), - p.namespace, - p.name, - Boolean(currentUser?.username) - ); - } - }} - /> - ))} -
- ) : (searchParamsBlob.order !== undefined && searchParams.size > 1) || - (searchParamsBlob.order === undefined && searchParams.size > 0) ? ( - - - - -
- No results found - - Make sure all keywords are spelled correctly or try different - search parameters. - -
- resetParams(searchParamsBlob.order)} - rootClasses="no-result__button" - > - Clear all filters - -
- ) : ( - - - - -
- It's empty in here - - Be the first to upload a mod! - -
-
- )} -
+
+ + Loading... + + } + > + + {(resolvedValue) => ( + <> + {resolvedValue.results.length > 0 ? ( +
+ {resolvedValue.results.map((p) => ( + { + if (likeAction) { + likeAction( + ratedPackages.includes( + `${p.namespace}-${p.name}` + ), + p.namespace, + p.name, + Boolean(currentUser?.username) + ); + } + }} + /> + ))} +
+ ) : (searchParamsBlob.order !== undefined && + searchParams.size > 1) || + (searchParamsBlob.order === undefined && + searchParams.size > 0) ? ( + + + + +
+ No results found + + Make sure all keywords are spelled correctly or try + different search parameters. + +
+ + resetParams( + setSearchParamsBlob, + searchParamsBlob.order, + sortedSections + ) + } + rootClasses="no-result__button" + > + Clear all filters + +
+ ) : ( + + + + +
+ + It's empty in here + + + Be the first to upload a mod! + +
+
+ )} + + )} +
+
+
- + + Loading... + + } + > + + {(resolvedValue) => ( + + )} + +
@@ -685,3 +716,71 @@ 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, + v: CategorySelection[] +) => { + const newSearchParams = { ...oldBlob }; + const includedCategories = v + .filter((c) => c.selection === "include") + .map((c) => c.id); + if (includedCategories.length === 0) { + newSearchParams.includedCategories = ""; + } else { + newSearchParams.includedCategories = includedCategories.join(","); + } + const excludedCategories = v + .filter((c) => c.selection === "exclude") + .map((c) => c.id); + if (excludedCategories.length === 0) { + newSearchParams.excludedCategories = ""; + } else { + newSearchParams.excludedCategories = excludedCategories.join(","); + } + setter({ ...newSearchParams }); +}; + +const resetParams = ( + setter: (v: SearchParamsType) => void, + order: PackageOrderOptionsType | undefined, + sortedSections: CommunityFilters["sections"] | undefined +) => { + setter({ + search: "", + order: order, + section: sortedSections + ? sortedSections.length === 0 + ? "" + : sortedSections[0]?.uuid + : "", + deprecated: false, + nsfw: false, + page: 1, + includedCategories: "", + excludedCategories: "", + }); + // setOrdering(order); + // setPage(1); + // setSearchValue(""); +}; + +const clearAll = + (setter: (v: SearchParamsType) => void, oldBlob: SearchParamsType) => () => + setter({ + ...oldBlob, + search: "", + includedCategories: "", + excludedCategories: "", + }); +// End setters diff --git a/apps/cyberstorm-remix/app/commonComponents/PackageSearch/components/PackageCount/PackageCount.css b/apps/cyberstorm-remix/app/commonComponents/PackageSearch/components/PackageCount/PackageCount.css index 1133e418e..66c29a67a 100644 --- a/apps/cyberstorm-remix/app/commonComponents/PackageSearch/components/PackageCount/PackageCount.css +++ b/apps/cyberstorm-remix/app/commonComponents/PackageSearch/components/PackageCount/PackageCount.css @@ -3,6 +3,7 @@ display: flex; flex: 1; flex-wrap: wrap; + justify-content: flex-end; color: var(--color-text-tertiary); font-size: var(--font-size-body-md); word-break: break-all; diff --git a/apps/cyberstorm-remix/app/commonComponents/PageHeader/PageHeader.css b/apps/cyberstorm-remix/app/commonComponents/PageHeader/PageHeader.css index 1836b0db0..2c3cddc72 100644 --- a/apps/cyberstorm-remix/app/commonComponents/PageHeader/PageHeader.css +++ b/apps/cyberstorm-remix/app/commonComponents/PageHeader/PageHeader.css @@ -1,7 +1,8 @@ @layer nimbus-components { .page-header { display: flex; - flex: 1 1 0; + + /* flex: 1 1 0; */ flex-direction: row; gap: var(--space-32); align-items: flex-start; diff --git a/apps/cyberstorm-remix/app/communities/Communities.css b/apps/cyberstorm-remix/app/communities/Communities.css index 165050c71..13224fec1 100644 --- a/apps/cyberstorm-remix/app/communities/Communities.css +++ b/apps/cyberstorm-remix/app/communities/Communities.css @@ -48,8 +48,8 @@ align-items: flex-start; align-self: stretch; aspect-ratio: 3/4; - border-radius: var(--radius-md); - background: var(--color-skeleton-bg-color); + + --skeleton-radius: var(--radius-md); } .communities__community-skeleton-content { @@ -65,8 +65,6 @@ align-items: flex-start; width: 90%; height: 1.188rem; - border-radius: var(--radius-sm); - background: var(--color-skeleton-bg-color); } .communities__community-skeleton-meta { @@ -75,8 +73,6 @@ align-self: stretch; justify-content: space-between; height: 0.937rem; - border-radius: var(--radius-sm); - background: var(--color-skeleton-bg-color); } } } diff --git a/apps/cyberstorm-remix/app/communities/communities.tsx b/apps/cyberstorm-remix/app/communities/communities.tsx index 01746f0c7..4707d2b05 100644 --- a/apps/cyberstorm-remix/app/communities/communities.tsx +++ b/apps/cyberstorm-remix/app/communities/communities.tsx @@ -4,6 +4,7 @@ import { EmptyState, NewTextInput, NewSelect, + SkeletonBox, } from "@thunderstore/cyberstorm"; import "./Communities.css"; import { useState, useEffect, useRef, memo, Suspense } from "react"; @@ -78,7 +79,7 @@ export async function loader({ request }: LoaderFunctionArgs) { }; }); return { - communities: dapper.getCommunities( + communities: await dapper.getCommunities( page, order === null ? undefined : order, search === null ? undefined : search @@ -160,7 +161,7 @@ export default function CommunitiesPage() { }, [debouncedSearchValue]); return ( -
+ <> Communities @@ -198,7 +199,7 @@ export default function CommunitiesPage() {
- + ); } @@ -240,10 +241,16 @@ const CommunitiesListSkeleton = memo(function CommunitiesListSkeleton() {
{Array.from({ length: 14 }).map((_, index) => (
-
+
+ +
-
-
+
+ +
+
+ +
))} diff --git a/apps/cyberstorm-remix/app/p/dependants/Dependants.tsx b/apps/cyberstorm-remix/app/p/dependants/Dependants.tsx index c4b39c41a..76e11222b 100644 --- a/apps/cyberstorm-remix/app/p/dependants/Dependants.tsx +++ b/apps/cyberstorm-remix/app/p/dependants/Dependants.tsx @@ -168,7 +168,7 @@ export default function Dependants() { const outletContext = useOutletContext() as OutletContextShape; return ( -
+ <> -
+ ); } diff --git a/apps/cyberstorm-remix/app/p/packageEdit.css b/apps/cyberstorm-remix/app/p/packageEdit.css index b82b0ed8a..f5c8a93e3 100644 --- a/apps/cyberstorm-remix/app/p/packageEdit.css +++ b/apps/cyberstorm-remix/app/p/packageEdit.css @@ -1,8 +1,4 @@ @layer nimbus-layout { - .package-edit { - --nimbus-layout-content-max-width: 90rem; - } - .package-edit__main { display: flex; gap: var(--gap-xxxl); diff --git a/apps/cyberstorm-remix/app/p/packageEdit.tsx b/apps/cyberstorm-remix/app/p/packageEdit.tsx index 75f66d265..1ccf2704e 100644 --- a/apps/cyberstorm-remix/app/p/packageEdit.tsx +++ b/apps/cyberstorm-remix/app/p/packageEdit.tsx @@ -236,7 +236,7 @@ export default function PackageListing() { }); return ( -
+ <>
-
+ ); } diff --git a/apps/cyberstorm-remix/app/p/packageListing.css b/apps/cyberstorm-remix/app/p/packageListing.css index 3cad99814..a5e6c41f3 100644 --- a/apps/cyberstorm-remix/app/p/packageListing.css +++ b/apps/cyberstorm-remix/app/p/packageListing.css @@ -1,37 +1,4 @@ @layer nimbus-layout { - .package-listing { - --nimbus-layout-content-max-width: 120rem; - - z-index: 1; - } - - .package-community__background { - position: absolute; - display: flex; - flex-direction: column; - align-items: flex-start; - height: 20rem; - border-radius: var(--section-border-radius); - overflow: hidden; - } - - .package-community__background-image { - display: flex; - flex-direction: column; - align-items: flex-start; - background: var(--color-body-bg-color, #101028); - - opacity: 0.4; - mix-blend-mode: luminosity; - } - - .package-community__background-tint { - position: absolute; - width: 100%; - height: 20rem; - background: linear-gradient(180deg, rgb(16 16 40 / 0.4) 0%, #101028 85.94%); - } - .package-listing__main { display: flex; gap: var(--gap-xxxl); @@ -49,12 +16,11 @@ align-items: flex-start; align-self: stretch; justify-content: flex-start; - padding: 7.5rem 3rem 2rem; } .package-listing__actions { position: absolute; - top: 2.5rem; + top: -5rem; right: 3rem; display: flex; flex-direction: column; @@ -151,12 +117,12 @@ scrollbar-width: none; } - .package-listing__content-header { - display: flex; - gap: var(--gap-xl); - align-items: flex-start; - align-self: stretch; - padding: 1rem; + .package-listing__page-header-skeleton { + height: 10rem; + } + + .package-listing__nav-skeleton { + height: 47px; } .package-listing__content { @@ -179,6 +145,16 @@ padding-top: var(--gap-md); } + .package-listing-sidebar__skeleton { + width: 20rem; + height: 21.4rem; + } + + .package-listing-sidebar__install-skeleton { + width: 20rem; + height: 3.56rem; + } + .package-listing-sidebar__install { justify-content: center; width: 100%; @@ -192,6 +168,11 @@ align-self: stretch; } + .package-listing-sidebar__actions-skeleton { + width: 20rem; + height: 2.25rem; + } + .package-listing-sidebar__actions { display: flex; gap: var(--gap-xs); @@ -267,6 +248,11 @@ word-break: break-word; } + .package-listing-sidebar__boxes-skeleton { + width: 20rem; + height: 11rem; + } + .package-listing-sidebar__categories { display: flex; flex-direction: column; diff --git a/apps/cyberstorm-remix/app/p/packageListing.tsx b/apps/cyberstorm-remix/app/p/packageListing.tsx index 2173a6910..479bf843a 100644 --- a/apps/cyberstorm-remix/app/p/packageListing.tsx +++ b/apps/cyberstorm-remix/app/p/packageListing.tsx @@ -1,4 +1,7 @@ -import type { LoaderFunctionArgs } from "react-router"; +import type { + LoaderFunctionArgs, + ShouldRevalidateFunctionArgs, +} from "react-router"; import { Await, Outlet, @@ -11,14 +14,13 @@ import { Heading, Modal, NewAlert, - NewBreadCrumbs, - NewBreadCrumbsLink, NewButton, NewIcon, NewLink, NewSelect, NewTag, NewTextInput, + SkeletonBox, Tabs, } from "@thunderstore/cyberstorm"; import "./packageListing.css"; @@ -160,6 +162,20 @@ export async function clientLoader({ params }: LoaderFunctionArgs) { clientLoader.hydrate = true; +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] + ) { + return false; + } + return arg.defaultShouldRevalidate; +} + export default function PackageListing() { const { community, listing, team, permissions } = useLoaderData< typeof loader | typeof clientLoader @@ -296,24 +312,6 @@ export default function PackageListing() { )} - - - {(resolvedValue) => - resolvedValue.hero_image_url ? ( -
- {resolvedValue.hero_image_url ? ( - {resolvedValue.name} - ) : null} -
-
- ) : null - } - -
@@ -332,74 +330,13 @@ export default function PackageListing() { } - - - Communities - - - Loading... - - } - > - - {(resolvedValue) => ( - - {resolvedValue.name} - - )} - - - - Loading... - - } - > - - {(resolvedValue) => ( - - {resolvedValue[0].namespace} - - )} - - - - Loading... - - } - > - - {(resolvedValue) => ( - - {formatToDisplayName(resolvedValue.name)} - - )} - - -
- Loading...}> + + } + > {(resolvedValue) => ( - - Loading... - - } - > + Loading...

}> {(resolvedValue) => ( <> @@ -496,13 +427,7 @@ export default function PackageListing() { )}
- - Loading... - - } - > + Loading...

}> {(resolvedValue) => ( <> @@ -516,13 +441,7 @@ export default function PackageListing() {
- - Loading... - - } - > + Loading...

}> {(resolvedValue) => ( - Loading... - + } > @@ -653,9 +570,7 @@ export default function PackageListing() {
-
+ ); } diff --git a/apps/cyberstorm-remix/app/root.tsx b/apps/cyberstorm-remix/app/root.tsx index be4bd9840..4ca25fafa 100644 --- a/apps/cyberstorm-remix/app/root.tsx +++ b/apps/cyberstorm-remix/app/root.tsx @@ -1,6 +1,7 @@ import "./styles/index.css"; import "@thunderstore/cyberstorm-theme"; import { + Await, Links, Meta, MetaFunction, @@ -11,6 +12,7 @@ import { isRouteErrorResponse, useLoaderData, useLocation, + useMatches, useRouteError, useRouteLoaderData, } from "react-router"; @@ -18,12 +20,18 @@ import { import { Provider as RadixTooltip } from "@radix-ui/react-tooltip"; import { LinkLibrary } from "cyberstorm/utils/LinkLibrary"; -import { AdContainer, LinkingProvider } from "@thunderstore/cyberstorm"; +import { + AdContainer, + isRecord, + LinkingProvider, + NewBreadCrumbs, + NewBreadCrumbsLink, +} from "@thunderstore/cyberstorm"; import { DapperTs } from "@thunderstore/dapper-ts"; import { CurrentUser } from "@thunderstore/dapper/types"; import { captureRemixErrorBoundaryError, withSentry } from "@sentry/remix"; -import { memo, ReactNode, useEffect, useRef } from "react"; +import { memo, ReactNode, Suspense, useEffect, useRef } from "react"; import { useHydrated } from "remix-utils/use-hydrated"; import Toast from "@thunderstore/cyberstorm/src/newComponents/Toast"; import { Footer } from "./commonComponents/Footer/Footer"; @@ -41,6 +49,7 @@ import { publicEnvVariablesType, } from "cyberstorm/security/publicEnvVariables"; import { StorageManager } from "@thunderstore/ts-api-react/src/storage"; +import { isPromise } from "cyberstorm/utils/typeChecks"; // REMIX TODO: https://remix.run/docs/en/main/route/links // export const links: LinksFunction = () => [{ rel: "stylesheet", href: styles }]; @@ -175,6 +184,19 @@ export function Layout({ children }: { children: React.ReactNode }) { const data = useLoaderData(); const location = useLocation(); + // const splitPath = location.pathname.split("/"); + // const isSubPath = splitPath.length > 4; + // const enableCommunitiesBreadCrumb = + // location.pathname === "/communities" || location.pathname.startsWith("/c/"); + // const isPackageListingSubPath = + // splitPath.length > 5 && splitPath[1] === "c" && splitPath[3] === "p"; + const matches = useMatches(); + + const communitiesPage = matches.find( + (m) => m.id === "communities/communities" + ); + const communityPage = matches.find((m) => m.id === "c/community"); + const packageListingPage = matches.find((m) => m.id === "p/packageListing"); const shouldShowAds = location.pathname.startsWith("/teams") ? false : location.pathname.startsWith("/settings") @@ -232,7 +254,121 @@ export function Layout({ children }: { children: React.ReactNode }) { />
- {children} +
+ {/* Breadcrumbs are build progressively */} + + {/* Communities page */} + {communitiesPage || communityPage ? ( + + Communities + + ) : null} + {/* Community page */} + {communityPage && + isRecord(communityPage.data) && + Object.prototype.hasOwnProperty.call( + communityPage.data, + "community" + ) && + isPromise(communityPage.data.community) ? ( + + Loading... + + } + > + + {(resolvedValue) => { + let label = undefined; + let icon = undefined; + if (isRecord(resolvedValue)) { + label = + Object.prototype.hasOwnProperty.call( + resolvedValue, + "name" + ) && typeof resolvedValue.name === "string" + ? resolvedValue.name + : communityPage.params.communityId; + icon = + Object.prototype.hasOwnProperty.call( + resolvedValue, + "community_icon_url" + ) && + typeof resolvedValue.community_icon_url === + "string" ? ( + + ) : undefined; + } + return matches[matches.length - 1] === + communityPage ? ( + + + {icon} + {label} + + + ) : ( + + {icon} + {label} + + ); + }} + + + ) : null} + {/* Package listing page */} + {packageListingPage && + isRecord(packageListingPage.data) && + Object.prototype.hasOwnProperty.call( + packageListingPage.data, + "listing" + ) && + isPromise(packageListingPage.data.listing) ? ( + + Loading... + + } + > + + {(resolvedValue) => { + let label = undefined; + if (isRecord(resolvedValue)) { + label = + Object.prototype.hasOwnProperty.call( + resolvedValue, + "name" + ) && typeof resolvedValue.name === "string" + ? resolvedValue.name + : packageListingPage.params.packageId; + } + return ( + + {label} + + ); + }} + + + ) : null} + + {children} +
{shouldShowAds ? (
@@ -267,7 +403,6 @@ const TooltipProvider = memo(function TooltipProvider({ }); function App() { - const location = useLocation(); const data = useRouteLoaderData("root"); const dapper = new DapperTs(() => { return { @@ -284,7 +419,6 @@ function App() { domain: data?.publicEnvVariables.VITE_API_URL, dapper: dapper, }} - key={location.key} /> ); } diff --git a/apps/cyberstorm-remix/app/routes.ts b/apps/cyberstorm-remix/app/routes.ts index 9f440a7e9..68b4cea2e 100644 --- a/apps/cyberstorm-remix/app/routes.ts +++ b/apps/cyberstorm-remix/app/routes.ts @@ -12,22 +12,31 @@ export default [ route("healthz", "./healthz.tsx"), route("/communities", "./communities/communities.tsx"), - route("/c/:communityId", "c/community.tsx"), - route("/c/:communityId/p/:namespaceId/:packageId", "p/packageListing.tsx", [ - route("", "p/tabs/Readme/Readme.tsx", { index: true }), - route("required", "p/tabs/Required/Required.tsx"), - route("changelog", "p/tabs/Changelog/Changelog.tsx"), - route("versions", "p/tabs/Versions/Versions.tsx"), - ...prefix("wiki", [ - layout("p/tabs/Wiki/Wiki.tsx", [ - index("p/tabs/Wiki/WikiFirstPage.tsx"), - route("/new", "p/tabs/Wiki/WikiNewPage.tsx"), - route("/:slug", "p/tabs/Wiki/WikiPage.tsx"), - route("/:slug/edit", "p/tabs/Wiki/WikiPageEdit.tsx"), + route("/c/:communityId", "c/community.tsx", [ + route("", "c/tabs/PackageSearch/PackageSearch.tsx", { index: true }), + ...prefix("p", [ + route(":namespaceId/:packageId", "p/packageListing.tsx", [ + route("", "p/tabs/Readme/Readme.tsx", { index: true }), + route("required", "p/tabs/Required/Required.tsx"), + route("changelog", "p/tabs/Changelog/Changelog.tsx"), + route("versions", "p/tabs/Versions/Versions.tsx"), + ...prefix("wiki", [ + layout("p/tabs/Wiki/Wiki.tsx", [ + index("p/tabs/Wiki/WikiFirstPage.tsx"), + route("/new", "p/tabs/Wiki/WikiNewPage.tsx"), + route("/:slug", "p/tabs/Wiki/WikiPage.tsx"), + route("/:slug/edit", "p/tabs/Wiki/WikiPageEdit.tsx"), + ]), + ]), + route("edit", "p/packageEdit.tsx"), ]), + route( + ":namespaceId/:packageId/dependants", + "p/dependants/Dependants.tsx" + ), + route(":namespaceId", "p/team/Team.tsx"), ]), ]), - route("/c/:communityId/p/:namespaceId/:packageId/edit", "p/packageEdit.tsx"), route( "/package/create/docs", "tools/package-format-docs/packageFormatDocs.tsx" @@ -40,11 +49,6 @@ export default [ "/tools/manifest-v1-validator", "tools/manifest-validator/manifestValidator.tsx" ), - route( - "/c/:communityId/p/:namespaceId/:packageId/dependants", - "p/dependants/Dependants.tsx" - ), - route("/c/:communityId/p/:namespaceId", "p/team/Team.tsx"), route("/package/create", "upload/upload.tsx"), // TODO: DISABLED UNTIL WE'VE GOT THE ENDPOINTS FOR THESE diff --git a/apps/cyberstorm-remix/app/settings/teams/Teams.tsx b/apps/cyberstorm-remix/app/settings/teams/Teams.tsx index d0a836547..2e4b68395 100644 --- a/apps/cyberstorm-remix/app/settings/teams/Teams.tsx +++ b/apps/cyberstorm-remix/app/settings/teams/Teams.tsx @@ -64,7 +64,7 @@ export default function Teams() { const currentUser = outletContext.currentUser; return ( -
+ <> Teams @@ -144,7 +144,7 @@ export default function Teams() {
-
+ ); } diff --git a/apps/cyberstorm-remix/app/settings/teams/team/teamSettings.tsx b/apps/cyberstorm-remix/app/settings/teams/team/teamSettings.tsx index 9852fb306..5242f9865 100644 --- a/apps/cyberstorm-remix/app/settings/teams/team/teamSettings.tsx +++ b/apps/cyberstorm-remix/app/settings/teams/team/teamSettings.tsx @@ -69,7 +69,7 @@ export default function Community() { : "profile"; return ( -
+ <>
-
+ ); } diff --git a/apps/cyberstorm-remix/app/settings/user/Settings.tsx b/apps/cyberstorm-remix/app/settings/user/Settings.tsx index 5a598bdbc..07f39997c 100644 --- a/apps/cyberstorm-remix/app/settings/user/Settings.tsx +++ b/apps/cyberstorm-remix/app/settings/user/Settings.tsx @@ -47,7 +47,7 @@ export default function Community() { : "settings"; return ( -
+ <> {outletContext.currentUser.username} @@ -85,6 +85,6 @@ export default function Community() {
-
+ ); } diff --git a/apps/cyberstorm-remix/app/styles/layout.css b/apps/cyberstorm-remix/app/styles/layout.css index 336f99f0c..3f656e6c2 100644 --- a/apps/cyberstorm-remix/app/styles/layout.css +++ b/apps/cyberstorm-remix/app/styles/layout.css @@ -3,6 +3,10 @@ /* Header dimensions */ --header-height: 3.5rem; --content-padding-top: 1.5rem; + + /* stylelint-disable */ + interpolate-size: allow-keywords; + /* stylelint-enable */ } body { @@ -20,7 +24,7 @@ gap: var(--gap-sm); align-self: flex-start; justify-content: flex-start; - max-width: var(--nimbus-layout-content-max-width, 90rem); + max-width: var(--nimbus-layout-content-max-width, 120rem); padding: var(--space-16); } diff --git a/apps/cyberstorm-remix/app/tools/manifest-validator/manifestValidator.tsx b/apps/cyberstorm-remix/app/tools/manifest-validator/manifestValidator.tsx index b80b5c174..edbc715dd 100644 --- a/apps/cyberstorm-remix/app/tools/manifest-validator/manifestValidator.tsx +++ b/apps/cyberstorm-remix/app/tools/manifest-validator/manifestValidator.tsx @@ -74,7 +74,7 @@ export default function ManifestValidator() { }, [debouncedTeamInput, debouncedManifestInput, outletContext.requestConfig]); return ( -
+ <> Manifest Validator @@ -112,7 +112,7 @@ export default function ManifestValidator() {
-
+ ); } diff --git a/apps/cyberstorm-remix/app/tools/markdown-preview/markdownPreview.tsx b/apps/cyberstorm-remix/app/tools/markdown-preview/markdownPreview.tsx index a1cf7fa6a..109a9d3be 100644 --- a/apps/cyberstorm-remix/app/tools/markdown-preview/markdownPreview.tsx +++ b/apps/cyberstorm-remix/app/tools/markdown-preview/markdownPreview.tsx @@ -53,7 +53,7 @@ export default function MarkdownPreview() { }, [debouncedMarkdownPreviewInput]); return ( -
+ <> Markdown Preview @@ -92,7 +92,7 @@ export default function MarkdownPreview() {
- + ); } diff --git a/apps/cyberstorm-remix/app/tools/package-format-docs/packageFormatDocs.tsx b/apps/cyberstorm-remix/app/tools/package-format-docs/packageFormatDocs.tsx index 9b3ba3e24..0aef7e819 100644 --- a/apps/cyberstorm-remix/app/tools/package-format-docs/packageFormatDocs.tsx +++ b/apps/cyberstorm-remix/app/tools/package-format-docs/packageFormatDocs.tsx @@ -11,7 +11,7 @@ import { PageHeader } from "~/commonComponents/PageHeader/PageHeader"; export default function PackageFormatDocs() { return ( -
+ <> Package Format Docs @@ -48,7 +48,7 @@ export default function PackageFormatDocs() {

-
+ ); } diff --git a/apps/cyberstorm-remix/app/upload/upload.tsx b/apps/cyberstorm-remix/app/upload/upload.tsx index 0b9b9255b..1fa5277b5 100644 --- a/apps/cyberstorm-remix/app/upload/upload.tsx +++ b/apps/cyberstorm-remix/app/upload/upload.tsx @@ -365,7 +365,7 @@ export default function Upload() { }, [strongForm.submitOutput]); return ( -
+ <> Upload @@ -881,7 +881,7 @@ export default function Upload() {

submitError: {JSON.stringify(strongForm.submitError)}

*/} - + ); } diff --git a/packages/cyberstorm/src/newComponents/BreadCrumbs/BreadCrumbs.css b/packages/cyberstorm/src/newComponents/BreadCrumbs/BreadCrumbs.css index df2cd9d34..7f2f87e90 100644 --- a/packages/cyberstorm/src/newComponents/BreadCrumbs/BreadCrumbs.css +++ b/packages/cyberstorm/src/newComponents/BreadCrumbs/BreadCrumbs.css @@ -9,6 +9,36 @@ font-size: var(--font-size-body-sm); line-height: var(--line-height-auto); + > span { + > span { + display: inline-flex; + gap: 0.5rem; + } + } + + > a { + > span { + display: inline-flex; + gap: 0.5rem; + } + } + + .breadcrumbs__homelink > span { + display: inline-block; + width: 0.875rem; + height: 0.875rem; + font-size: var(--font-size-body-md); + } + + img { + display: flex; + align-items: center; + justify-content: center; + width: 1rem; + height: 1rem; + aspect-ratio: 1/1; + } + > * { position: relative; display: flex; @@ -90,11 +120,4 @@ } } } - - .breadcrumbs__homelink > span { - display: inline-block; - width: 0.875rem; - height: 0.875rem; - font-size: var(--font-size-body-md); - } } diff --git a/packages/cyberstorm/src/newComponents/SkeletonBox/SkeletonBox.css b/packages/cyberstorm/src/newComponents/SkeletonBox/SkeletonBox.css index dfe4e79f0..7c86dba0c 100644 --- a/packages/cyberstorm/src/newComponents/SkeletonBox/SkeletonBox.css +++ b/packages/cyberstorm/src/newComponents/SkeletonBox/SkeletonBox.css @@ -1,24 +1,32 @@ -.skeleton { - display: flex; - border-radius: var(--radius-sm); - background: hsl(236deg 36% 36% / 0.24); - animation: pulse 2s infinite; -} - -@keyframes pulse { - 0% { - opacity: 1; +@layer components { + .skeleton { + display: flex; + width: 100%; + height: 100%; + border-radius: var(--skeleton-radius, var(--radius-md)); + background: var(--color-skeleton-bg-color); + animation: pulse 2.5s infinite ease-in-out; } - 25% { - opacity: 0.25; - } + @keyframes pulse { + 0% { + opacity: 0.2; + } - 75% { - opacity: 0.75; - } + 20% { + opacity: 0.4; + } + + 40% { + opacity: 0.2; + } + + 70% { + opacity: 0.6; + } - 100% { - opacity: 1; + 100% { + opacity: 0.2; + } } } From cb87bf3bee31d2b1a143c612bb1c8b97458bcc58 Mon Sep 17 00:00:00 2001 From: Oksamies Date: Tue, 9 Sep 2025 02:07:18 +0300 Subject: [PATCH 2/4] Add image lazy loading and improve env variable logic --- apps/cyberstorm-remix/app/root.tsx | 43 +++++++++++++++---- .../cyberstorm/security/publicEnvVariables.ts | 16 ++++++- .../src/newComponents/Image/Image.tsx | 4 +- 3 files changed, 52 insertions(+), 11 deletions(-) diff --git a/apps/cyberstorm-remix/app/root.tsx b/apps/cyberstorm-remix/app/root.tsx index 4ca25fafa..eb091d430 100644 --- a/apps/cyberstorm-remix/app/root.tsx +++ b/apps/cyberstorm-remix/app/root.tsx @@ -182,6 +182,31 @@ const adContainerIds = ["right-column-1", "right-column-2", "right-column-3"]; export function Layout({ children }: { children: React.ReactNode }) { const data = useLoaderData(); + let envVars = undefined; + let shouldUpdatePublicEnvVars = false; + if (import.meta.env.SSR) { + envVars = getPublicEnvVariables([ + "VITE_SITE_URL", + "VITE_BETA_SITE_URL", + "VITE_API_URL", + "VITE_COOKIE_DOMAIN", + "VITE_AUTH_BASE_URL", + "VITE_AUTH_RETURN_URL", + "VITE_CLIENT_SENTRY_DSN", + ]); + shouldUpdatePublicEnvVars = true; + } else { + envVars = window.NIMBUS_PUBLIC_ENV; + if ( + data && + data.publicEnvVariables && + data.publicEnvVariables !== envVars + ) { + shouldUpdatePublicEnvVars = true; + } + } + + const resolvedEnvVars = data ? data.publicEnvVariables : envVars; const location = useLocation(); // const splitPath = location.pathname.split("/"); @@ -237,19 +262,21 @@ export function Layout({ children }: { children: React.ReactNode }) { -