added initial files

This commit is contained in:
2025-07-31 00:48:48 +02:00
parent 411cc26582
commit 2a3dc51ce9
193 changed files with 110293 additions and 0 deletions
@@ -0,0 +1,233 @@
import React, { forwardRef, memo, useContext, useEffect } from 'react';
import styled, { ThemeContext } from 'styled-components';
import { useDispatch } from 'react-redux';
import { FixedSizeList as List } from 'react-window';
import InfiniteLoader from 'react-window-infinite-loader';
import ContentLoader from 'react-content-loader';
import { transparentize } from 'polished';
import { openModal } from '../../../reducers/modals/actions';
import { CURSEFORGE } from '../../../utils/constants';
const ModpacksListWrapper = ({
// Are there more items to load?
// (This information comes from the most recent API request.)
hasNextPage,
// Are we currently loading a page of items?
// (This may be an in-flight flag in your Redux store for example.)
isNextPageLoading,
// Array of items loaded so far.
items,
height,
width,
setStep,
setVersion,
// Callback function responsible for loading the next page of items.
loadNextPage,
setModpack,
infiniteLoaderRef
}) => {
const dispatch = useDispatch();
// If there are more items to be loaded then add an extra row to hold a loading indicator.
const itemCount = hasNextPage ? items.length + 1 : items.length;
// Only load 1 page of items at a time.
// Pass an empty callback to InfiniteLoader in case it asks us to load more than once.
const loadMoreItems = isNextPageLoading ? () => {} : loadNextPage;
// Every row is loaded except for our loading indicator row.
const isItemLoaded = index => !hasNextPage || index < items.length;
// Render an item or a loading indicator.
const Item = memo(({ index, style }) => {
const modpack = items[index];
if (!modpack) {
return (
<ModpackLoader
hasNextPage={hasNextPage}
isNextPageLoading={isNextPageLoading}
loadNextPage={loadNextPage}
top={style.top + (index === 0 ? 0 : 8)}
width={width}
height={style.height - (index === 0 ? 0 : 8)}
/>
);
}
const primaryImage = modpack?.logo;
return (
<div
// eslint-disable-next-line
style={{
...style,
top: style.top + (index === 0 ? 0 : 8),
height: style.height - (index === 0 ? 0 : 8),
background: `url('${primaryImage?.thumbnailUrl}')`,
position: 'absolute',
width: width - 8,
backgroundRepeat: 'no-repeat',
backgroundSize: 'cover',
backgroundPosition: 'center',
margin: 0,
borderRadius: 4
}}
key={modpack.id}
>
<Modpack>
<div>{modpack.name}</div>
</Modpack>
<ModpackHover>
<div
onClick={() => {
setVersion({
projectID: modpack.id,
fileID: modpack.latestFiles[modpack.latestFiles.length - 1].id,
source: CURSEFORGE
});
setModpack(modpack);
setStep(1);
}}
>
Download Latest
</div>
<div
onClick={() => {
dispatch(
openModal('ModpackDescription', {
modpack,
setVersion,
setModpack,
setStep,
type: 'curseforge'
})
);
}}
>
Explore / Versions
</div>
</ModpackHover>
</div>
);
});
const innerElementType = forwardRef(({ style, ...rest }, ref) => (
<div
ref={ref}
// eslint-disable-next-line react/forbid-dom-props
style={{
...style,
paddingTop: 0
}}
// eslint-disable-next-line react/jsx-props-no-spreading
{...rest}
/>
));
return (
<InfiniteLoader
isItemLoaded={isItemLoaded}
itemCount={itemCount !== 0 ? itemCount : 40}
loadMoreItems={() => loadMoreItems()}
>
{({ onItemsRendered }) => (
<List
height={height}
width={width}
itemCount={itemCount !== 0 ? itemCount : 40}
itemSize={100}
onItemsRendered={onItemsRendered}
innerElementType={innerElementType}
ref={list => {
// Manually bind ref to reset scroll
// eslint-disable-next-line
infiniteLoaderRef.current = list;
}}
>
{Item}
</List>
)}
</InfiniteLoader>
);
};
export default memo(ModpacksListWrapper);
const Modpack = styled.div`
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 20px;
padding: 0 10px;
font-weight: 700;
background: ${props => transparentize(0.2, props.theme.palette.grey[700])};
`;
const ModpackHover = styled.div`
position: absolute;
display: flex;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: ${props => transparentize(0.4, props.theme.palette.grey[900])};
opacity: 0;
padding-left: 40%;
will-change: opacity;
transition: opacity 0.1s ease-in-out, background 0.1s ease-in-out;
div {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
background-color: transparent;
border-radius: 4px;
transition: background-color 0.1s ease-in-out;
&:hover {
background-color: ${props => props.theme.palette.primary.main};
}
}
&:hover {
opacity: 1;
}
`;
const ModpackLoader = memo(
({ height, width, top, isNextPageLoading, hasNextPage, loadNextPage }) => {
const ContextTheme = useContext(ThemeContext);
useEffect(() => {
if (hasNextPage && isNextPageLoading) {
loadNextPage();
}
}, []);
return (
<ContentLoader
speed={2}
foregroundColor={ContextTheme.palette.grey[900]}
backgroundColor={ContextTheme.palette.grey[800]}
title={false}
height={height}
style={{
width: width - 8,
height,
position: 'absolute',
margin: 0,
padding: 0,
top,
borderRadius: 4
}}
>
<rect x="0" y="0" width="100%" height={height} />
</ContentLoader>
);
}
);
@@ -0,0 +1,250 @@
/* eslint-disable no-nested-ternary */
import React, { useState, useEffect, useRef } from 'react';
import styled from 'styled-components';
import { Select, Input } from 'antd';
import { useDebouncedCallback } from 'use-debounce';
import AutoSizer from 'react-virtualized-auto-sizer';
import { useSelector } from 'react-redux';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faBomb, faExclamationCircle } from '@fortawesome/free-solid-svg-icons';
import { getSearch } from '../../../api';
import ModpacksListWrapper from './ModpacksListWrapper';
let lastRequest;
const CurseForgeModpacks = ({ setStep, setVersion, setModpack }) => {
const mcVersions = useSelector(state => state.app.vanillaManifest?.versions);
const categories = useSelector(state => state.app.curseforgeCategories);
const infiniteLoaderRef = useRef(null);
const [modpacks, setModpacks] = useState([]);
const [loading, setLoading] = useState(true);
const [minecraftVersion, setMinecraftVersion] = useState(null);
const [categoryId, setCategoryId] = useState(null);
const [sortBy, setSortBy] = useState('Featured');
const [searchText, setSearchText] = useState('');
const [hasNextPage, setHasNextPage] = useState(false);
const [error, setError] = useState(false);
const updateModpacks = useDebouncedCallback(() => {
if (infiniteLoaderRef?.current?.scrollToItem) {
infiniteLoaderRef.current.scrollToItem(0);
}
loadMoreModpacks(true);
}, 250);
const loadMoreModpacks = async (reset = false) => {
const reqObj = {};
lastRequest = reqObj;
if (!loading) {
setLoading(true);
}
if (reset && (modpacks.length !== 0 || hasNextPage)) {
setModpacks([]);
setHasNextPage(false);
}
let data = null;
try {
if (error) {
setError(false);
}
data = await getSearch(
'modpacks',
searchText,
40,
reset ? 0 : modpacks.length,
sortBy,
true,
minecraftVersion,
categoryId
);
} catch (err) {
setError(err);
return;
}
const newModpacks = reset ? data : [...modpacks, ...data];
if (lastRequest === reqObj) {
setLoading(false);
setHasNextPage(newModpacks.length % 40 === 0 && newModpacks.length !== 0);
setModpacks(newModpacks);
}
};
useEffect(() => {
updateModpacks();
}, [searchText, sortBy, minecraftVersion, categoryId]);
return (
<Container>
<HeaderContainer>
<StyledSelect
placeholder="Minecraft Version"
onChange={setMinecraftVersion}
defaultValue={null}
virtual={false}
>
<Select.Option value={null}>All Versions</Select.Option>
{(mcVersions || [])
.filter(v => v?.type === 'release')
.map(v => (
<Select.Option key={v?.id} value={v?.id}>
{v?.id}
</Select.Option>
))}
</StyledSelect>
<StyledSelect
placeholder="Minecraft Category"
onChange={setCategoryId}
defaultValue={null}
virtual={false}
>
<Select.Option key="allcategories" value={null}>
All Categories
</Select.Option>
{(categories || [])
.filter(v => v?.classId === 4471)
.sort((a, b) => a?.name.localeCompare(b?.name))
.map(v => (
<Select.Option value={v?.id} key={v?.id}>
<div
css={`
display: flex;
align-items: center;
width: 100%;
height: 100%;
`}
>
<img
src={v?.iconUrl}
css={`
height: 16px;
width: 16px;
margin-right: 10px;
`}
alt="icon"
/>
{v?.name}
</div>
</Select.Option>
))}
</StyledSelect>
<StyledSelect
placeholder="Sort by"
defaultValue="Featured"
onChange={setSortBy}
virtual={false}
>
<Select.Option key="Featured" value="Featured">
Featured
</Select.Option>
<Select.Option key="Popularity" value="Popularity">
Popularity
</Select.Option>
<Select.Option key="LastUpdated" value="LastUpdated">
Last Updated
</Select.Option>
<Select.Option key="Name" value="Name">
Name
</Select.Option>
<Select.Option key="Author" value="Author">
Author
</Select.Option>
<Select.Option key="TotalDownloads" value="TotalDownloads">
Total Downloads
</Select.Option>
</StyledSelect>
<StyledInput
placeholder="Search..."
onSearch={setSearchText}
onChange={e => setSearchText(e.target.value)}
style={{ width: 200 }}
/>
</HeaderContainer>
<ModpacksContainer>
{!error ? (
!loading && modpacks.length === 0 ? (
<div
css={`
margin-top: 120px;
display: flex;
flex-direction: column;
align-items: center;
font-size: 150px;
`}
>
<FontAwesomeIcon icon={faExclamationCircle} />
<div
css={`
font-size: 20px;
margin-top: 70px;
`}
>
No modpack has been found with the current filters.
</div>
</div>
) : (
<AutoSizer>
{({ height, width }) => (
<ModpacksListWrapper
hasNextPage={hasNextPage}
isNextPageLoading={loading}
items={modpacks}
loadNextPage={loadMoreModpacks}
width={width}
height={height}
setStep={setStep}
setVersion={setVersion}
setModpack={setModpack}
infiniteLoaderRef={infiniteLoaderRef}
/>
)}
</AutoSizer>
)
) : (
<div
css={`
margin-top: 120px;
display: flex;
flex-direction: column;
align-items: center;
font-size: 150px;
`}
>
<FontAwesomeIcon icon={faBomb} />
<div
css={`
font-size: 20px;
margin-top: 70px;
`}
>
An error occurred while loading the modpacks list...
</div>
</div>
)}
</ModpacksContainer>
</Container>
);
};
export default React.memo(CurseForgeModpacks);
const Container = styled.div`
width: 100%;
height: 100%;
`;
const StyledSelect = styled(Select)`
width: 170px;
margin-right: 20px;
`;
const StyledInput = styled(Input.Search)``;
const HeaderContainer = styled.div`
display: flex;
justify-content: center;
`;
const ModpacksContainer = styled.div`
height: calc(100% - 15px);
overflow: hidden;
padding: 10px 0;
`;