Mod versions and version creation pages
This commit is contained in:
parent
45900e9033
commit
f8bf646565
|
@ -12,7 +12,9 @@ import ComingSoonPage from './pages/coming_soon';
|
|||
|
||||
import ModsPage from './pages/mods';
|
||||
import ModPage from './pages/mod_page'
|
||||
import ModVersionsPage from './pages/mod_versions'
|
||||
import ModCreationPage from './pages/mod_creation'
|
||||
import ModVersionCreationPage from './pages/create_mod_version'
|
||||
|
||||
import ModpacksPage from './pages/modpacks';
|
||||
|
||||
|
@ -50,6 +52,8 @@ export function App() {
|
|||
|
||||
<ModPage path="/mods/:name"></ModPage>
|
||||
<ModCreationPage path="/create/mod" ></ModCreationPage>
|
||||
<ModVersionsPage path="/mods/:name/versions"></ModVersionsPage>
|
||||
<ModVersionCreationPage path="/mods/:name/versions/create"></ModVersionCreationPage>
|
||||
|
||||
<LoginPage path='/login'></LoginPage>
|
||||
<RegisterPage path='/register'></RegisterPage>
|
||||
|
|
1
frontend/src/assets/game.svg
Normal file
1
frontend/src/assets/game.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="M189-180q-53.08 0-89.81-36.85Q62.46-253.69 62-307.32q0-7.91.62-15.37.61-7.46 2.61-15.69l84-336q12.26-47.14 49.93-76.38Q236.82-780 285-780h390q48.18 0 85.84 29.24 37.67 29.24 49.93 76.38l84 336q2 8.23 3.11 16.19 1.12 7.96 1.12 15.87 0 53.63-37.35 89.97Q824.31-180 771.05-180q-36.67 0-67.28-19.31-30.62-19.31-47.08-51.92L627.92-310q-7.3-15-21.92-22.5-14.62-7.5-31-7.5H385q-16.19 0-30.9 7.31-14.72 7.31-22.02 22.69l-28.77 58.77q-15.69 33.38-46.84 52.31Q225.33-180 189-180Zm3.21-40q24.17 0 44.67-13.08 20.5-13.07 30.81-34.69l28-57.77q12.63-25.58 37.05-40.02Q357.15-380 385-380h190q28.31 0 52.46 15.31 24.16 15.31 37.85 39.92l28 57q10.31 21.62 30.81 34.69Q744.62-220 769.33-220q36.41 0 62.43-24.65Q857.77-269.31 858-305q0-2.08-2.77-23.62l-84-335q-8.54-33.15-35.31-54.76Q709.15-740 675-740H285q-34.98 0-61.83 21.62-26.86 21.61-34.4 54.76l-84 335q-1.23 4.47-2.77 22.62 0 36.48 26.65 61.24Q155.31-220 192.21-220ZM540-529.23q12.38 0 21.58-9.19 9.19-9.2 9.19-21.58 0-12.38-9.19-21.58-9.2-9.19-21.58-9.19-12.38 0-21.58 9.19-9.19 9.2-9.19 21.58 0 12.38 9.19 21.58 9.2 9.19 21.58 9.19Zm80-80q12.38 0 21.58-9.19 9.19-9.2 9.19-21.58 0-12.38-9.19-21.58-9.2-9.19-21.58-9.19-12.38 0-21.58 9.19-9.19 9.2-9.19 21.58 0 12.38 9.19 21.58 9.2 9.19 21.58 9.19Zm0 160q12.38 0 21.58-9.19 9.19-9.2 9.19-21.58 0-12.38-9.19-21.58-9.2-9.19-21.58-9.19-12.38 0-21.58 9.19-9.19 9.2-9.19 21.58 0 12.38 9.19 21.58 9.2 9.19 21.58 9.19Zm80-80q12.38 0 21.58-9.19 9.19-9.2 9.19-21.58 0-12.38-9.19-21.58-9.2-9.19-21.58-9.19-12.38 0-21.58 9.19-9.19 9.2-9.19 21.58 0 12.38 9.19 21.58 9.2 9.19 21.58 9.19Zm-360.04 56.92q7.66 0 12.69-5.01 5.04-5.01 5.04-12.68v-52.31H410q7.67 0 12.68-5t5.01-12.65q0-7.66-5.01-12.69-5.01-5.04-12.68-5.04h-52.31V-630q0-7.67-5-12.68t-12.65-5.01q-7.66 0-12.69 5.01-5.04 5.01-5.04 12.68v52.31H270q-7.67 0-12.68 5t-5.01 12.65q0 7.66 5.01 12.69 5.01 5.04 12.68 5.04h52.31V-490q0 7.67 5 12.68t12.65 5.01ZM480-480Z"/></svg>
|
After Width: | Height: | Size: 2 KiB |
1
frontend/src/assets/hardware_platform.svg
Normal file
1
frontend/src/assets/hardware_platform.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="M360-160v-40h80v-80H184.62q-27.62 0-46.12-18.5Q120-317 120-344.62v-390.76q0-27.62 18.5-46.12Q157-800 184.62-800h590.76q27.62 0 46.12 18.5Q840-763 840-735.38v390.76q0 27.62-18.5 46.12Q803-280 775.38-280H520v80h80v40H360ZM184.62-320h590.76q9.24 0 16.93-7.69 7.69-7.69 7.69-16.93v-390.76q0-9.24-7.69-16.93-7.69-7.69-16.93-7.69H184.62q-9.24 0-16.93 7.69-7.69 7.69-7.69 16.93v390.76q0 9.24 7.69 16.93 7.69 7.69 16.93 7.69ZM160-320v-440 440Z"/></svg>
|
After Width: | Height: | Size: 560 B |
1
frontend/src/assets/loader.svg
Normal file
1
frontend/src/assets/loader.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="M358.15-160H200q-16.85 0-28.42-11.58Q160-183.15 160-200v-158.15q34.15-10 57.08-37.81Q240-423.77 240-460q0-36.23-22.92-64.04-22.93-27.81-57.08-37.81V-720q0-16.85 11.58-28.42Q183.15-760 200-760h160q10.77-34.31 37.85-54.85 27.07-20.54 62.15-20.54t62.15 20.54Q549.23-794.31 560-760h160q16.85 0 28.42 11.58Q760-736.85 760-720v160q34.31 10.77 54.85 37.85 20.54 27.07 20.54 62.15t-20.54 62.15Q794.31-370.77 760-360v160q0 16.85-11.58 28.42Q736.85-160 720-160H561.85q-10.77-36.15-38.81-58.08Q495-240 460-240t-63.04 21.92q-28.04 21.93-38.81 58.08ZM200-200h131.92q17.08-39.85 52.77-59.92Q420.38-280 460-280q39.62 0 75.31 20.08Q571-239.85 588.08-200H720v-195.38h18.46q28.77-4.62 42.85-23.7 14.07-19.07 14.07-40.92t-14.07-40.92q-14.08-19.08-42.85-23.7H720V-720H524.62v-18.46q-4.62-28.77-23.7-42.85-19.07-14.07-40.92-14.07t-40.92 14.07q-19.08 14.08-23.7 42.85V-720H200v131.08q37.08 17.69 58.54 52.77Q280-501.08 280-460q0 40.85-21.46 75.92-21.46 35.08-58.54 53V-200Zm260-260Z"/></svg>
|
After Width: | Height: | Size: 1.1 KiB |
269
frontend/src/pages/create_mod_version.jsx
Normal file
269
frontend/src/pages/create_mod_version.jsx
Normal file
|
@ -0,0 +1,269 @@
|
|||
// Preact
|
||||
import { h } from 'preact';
|
||||
import { useState, useEffect } from 'preact/hooks';
|
||||
import Cookies from 'js-cookie'
|
||||
import { jwtDecode } from 'jwt-decode'
|
||||
|
||||
// Functions
|
||||
import { createModVersion } from '../services/mods';
|
||||
|
||||
// Components
|
||||
import InputField from '../components/Fields/input_field'
|
||||
import TextArea from '../components/Fields/text_area'
|
||||
|
||||
// Images
|
||||
import logo from '../assets/logo.png'
|
||||
import profile from '../assets/profile.svg'
|
||||
|
||||
// Styles
|
||||
import styles from '../styles/version_creation.module.css'
|
||||
import Button from '../components/Buttons/button';
|
||||
|
||||
function ModVersionCreationPage({name}) {
|
||||
//TODO add missing fields
|
||||
|
||||
const null_fields = {
|
||||
version_number: null,
|
||||
channel: null,
|
||||
changelog: null,
|
||||
game_version: null,
|
||||
platform: null,
|
||||
environment: null,
|
||||
url: null
|
||||
}
|
||||
|
||||
//TODO use a service
|
||||
let user;
|
||||
const token = Cookies.get('authToken');
|
||||
if (token) {
|
||||
const decoded_token = jwtDecode(token);
|
||||
if (decoded_token) {
|
||||
user = decoded_token.username;
|
||||
} else {
|
||||
location.replace('/login')
|
||||
}
|
||||
} else {
|
||||
location.replace('/login')
|
||||
}
|
||||
|
||||
const [version_number, setVersionNumber] = useState('');
|
||||
const [channel, setChannel] = useState('');
|
||||
const [game_version, setGameVersion] = useState('');
|
||||
const [platform, setPlatform] = useState('');
|
||||
const [environment, setEnvironment] = useState('');
|
||||
const [changelog, setChangelog] = useState('');
|
||||
const [version_url, setVersionUrl] = useState('');
|
||||
|
||||
const [publish_status, setPublishStatus] = useState(null);
|
||||
const [field_errors, setFieldErrors] = useState(null_fields);
|
||||
|
||||
|
||||
// Handle fields changes
|
||||
|
||||
const handleVersionNumberChange = (event) => {
|
||||
setVersionNumber(event.target.value);
|
||||
// Reset error
|
||||
setFieldErrors(prevErrors => ({ ...prevErrors, version_number: null }));
|
||||
};
|
||||
const handleChannelChange = (event) => {
|
||||
setChannel(event.target.value);
|
||||
setFieldErrors(prevErrors => ({ ...prevErrors, channel: null }));
|
||||
};
|
||||
const handleGameVersionChange = (event) => {
|
||||
setGameVersion(event.target.value);
|
||||
setFieldErrors(prevErrors => ({ ...prevErrors, game_version: null }));
|
||||
};
|
||||
const handlePlatformChange = (event) => {
|
||||
setPlatform(event.target.value);
|
||||
console.debug(event.target.value);
|
||||
setFieldErrors(prevErrors => ({ ...prevErrors, platform: null }));
|
||||
};
|
||||
const handleEnvironmentChange = (event) => {
|
||||
setEnvironment(event.target.value);
|
||||
console.debug(event.target.value);
|
||||
setFieldErrors(prevErrors => ({ ...prevErrors, environment: null }));
|
||||
};
|
||||
const handleChangelogChange = (event) => {
|
||||
setChangelog(event.target.value);
|
||||
console.debug(event.target.value);
|
||||
setFieldErrors(prevErrors => ({ ...prevErrors, changelog: null }));
|
||||
};
|
||||
const handleVersionUrlChange = (event) => {
|
||||
setVersionUrl(event.target.value);
|
||||
console.debug(event.target.value);
|
||||
setFieldErrors(prevErrors => ({ ...prevErrors, version_url: null }));
|
||||
};
|
||||
|
||||
// Submission
|
||||
|
||||
const handleSubmit = async (event) => {
|
||||
event.preventDefault();
|
||||
setFieldErrors(null_fields);
|
||||
|
||||
setPublishStatus('publishing');
|
||||
|
||||
try {
|
||||
// Gather data
|
||||
const version_data = {
|
||||
version_number: version_number,
|
||||
channel: channel,
|
||||
changelog: changelog,
|
||||
game_version: changelog,
|
||||
platform: changelog,
|
||||
environment: changelog,
|
||||
url: version_url
|
||||
}
|
||||
|
||||
// Query
|
||||
const response = await createModVersion(name, version_data);
|
||||
|
||||
// On success
|
||||
console.debug('Published successfully:', response);
|
||||
setPublishStatus('success');
|
||||
window.location.replace("/mods/" + name + "/versions"); //TODO no page reloading
|
||||
|
||||
} catch (error) {
|
||||
|
||||
console.error('Creation failed:', error);
|
||||
setPublishStatus('error');
|
||||
|
||||
//TODO handle different codes differently
|
||||
setFieldErrors({
|
||||
version_number: ' ',
|
||||
channel: ' ',
|
||||
changelog: ' ',
|
||||
game_version: ' ',
|
||||
platform: ' ',
|
||||
environment: ' ',
|
||||
version_url: ' '
|
||||
});
|
||||
}
|
||||
finally {
|
||||
setPublishStatus(null);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<a href="/">
|
||||
<img src={logo} class="logo img" alt="WF" />
|
||||
<p class="logo text"> creator </p>
|
||||
</a>
|
||||
|
||||
<div className={styles.container}>
|
||||
<form className={styles.form} onSubmit={handleSubmit}>
|
||||
|
||||
<div className={styles.topFields}>
|
||||
{/* Version number */}
|
||||
<InputField
|
||||
id="version_number"
|
||||
name="version_number"
|
||||
value={version_number}
|
||||
onChange={handleVersionNumberChange}
|
||||
error={field_errors.version_number}
|
||||
placeholder="version_number"
|
||||
required
|
||||
className={styles.smallField}
|
||||
>
|
||||
</InputField>
|
||||
|
||||
{/* Channel */}
|
||||
<InputField
|
||||
id="channel"
|
||||
name="channel"
|
||||
value={channel}
|
||||
onChange={handleChannelChange}
|
||||
error={field_errors.channel}
|
||||
placeholder="channel"
|
||||
className={styles.smallField}
|
||||
>
|
||||
</InputField>
|
||||
|
||||
</div>
|
||||
<div className={styles.middleFields}>
|
||||
{/* Game version */}
|
||||
<InputField
|
||||
id="game_version"
|
||||
name="game_version"
|
||||
value={game_version}
|
||||
onChange={handleGameVersionChange}
|
||||
error={field_errors.game_version}
|
||||
placeholder="game_version"
|
||||
className={styles.smallField}
|
||||
>
|
||||
</InputField>
|
||||
|
||||
{/* Platform */}
|
||||
<InputField
|
||||
id="platform"
|
||||
name="platform"
|
||||
value={platform}
|
||||
onChange={handlePlatformChange}
|
||||
error={field_errors.platform}
|
||||
placeholder="platform"
|
||||
className={styles.smallField}
|
||||
>
|
||||
</InputField>
|
||||
|
||||
{/* Environment */}
|
||||
<InputField
|
||||
id="environment"
|
||||
name="environment"
|
||||
value={environment}
|
||||
onChange={handleEnvironmentChange}
|
||||
error={field_errors.environment}
|
||||
placeholder="environment"
|
||||
className={styles.smallField}
|
||||
>
|
||||
</InputField>
|
||||
|
||||
</div>
|
||||
|
||||
<div className={styles.bottomFields}>
|
||||
{/* Full description */}
|
||||
<TextArea
|
||||
id="changelog"
|
||||
name="changelog"
|
||||
value={changelog}
|
||||
onChange={handleChangelogChange}
|
||||
error={field_errors.changelog}
|
||||
placeholder="changelog"
|
||||
containerClass={styles.changelogField}
|
||||
>
|
||||
</TextArea>
|
||||
</div>
|
||||
|
||||
<div className={styles.bottomBottomFields}>
|
||||
|
||||
{/* Version url */}
|
||||
<InputField
|
||||
id="version_url"
|
||||
name="version_url"
|
||||
value={version_url}
|
||||
onChange={handleVersionUrlChange}
|
||||
error={field_errors.version_url}
|
||||
placeholder="version_url"
|
||||
className={styles.smallField}
|
||||
>
|
||||
</InputField>
|
||||
|
||||
</div>
|
||||
|
||||
<Button className={styles.createButton}>
|
||||
Publish
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
|
||||
<div className={styles.infosPanel}>
|
||||
|
||||
<img src={profile} className={styles.profilePicture}></img>
|
||||
<p className={styles.author}>{user}</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</>)
|
||||
}
|
||||
|
||||
export default ModVersionCreationPage
|
|
@ -5,7 +5,7 @@ import Cookies from 'js-cookie'
|
|||
import { jwtDecode } from 'jwt-decode'
|
||||
|
||||
// Functions
|
||||
import { getMod, deleteMod } from '../services/mods';
|
||||
import { getMod, deleteMod, getModVersions } from '../services/mods';
|
||||
|
||||
// Components
|
||||
import Button from '../components/Buttons/button';
|
||||
|
@ -26,6 +26,7 @@ function ModPage({name}) {
|
|||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState(null)
|
||||
const [owner, setOwner] = useState(false);
|
||||
const [versions, setVersions] = useState(null);
|
||||
|
||||
// UseEffect
|
||||
useEffect(() => {
|
||||
|
@ -34,8 +35,11 @@ function ModPage({name}) {
|
|||
setLoading(true);
|
||||
setError(false);
|
||||
try {
|
||||
const fetched_mod = await getMod(name);
|
||||
setMod(fetched_mod);
|
||||
const fetched_mod = getMod(name);
|
||||
const fetched_versions = getModVersions(name, {});
|
||||
setMod( await fetched_mod);
|
||||
setVersions( await fetched_versions);
|
||||
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
} finally {
|
||||
|
@ -53,18 +57,19 @@ function ModPage({name}) {
|
|||
location.replace('/dashboard');
|
||||
};
|
||||
|
||||
// Load user informations
|
||||
//TODO use a service
|
||||
//? for some reason, doesn't work inside useEffect
|
||||
const token = Cookies.get('authToken');
|
||||
if (token) {
|
||||
const decoded_token = jwtDecode(token);
|
||||
if (decoded_token) {
|
||||
if (decoded_token.username === mod.author) {
|
||||
setOwner(true);
|
||||
}
|
||||
|
||||
// Load user informations
|
||||
//TODO use a service
|
||||
//? for some reason, doesn't work inside useEffect
|
||||
const token = Cookies.get('authToken');
|
||||
if (token) {
|
||||
const decoded_token = jwtDecode(token);
|
||||
if (decoded_token) {
|
||||
if (decoded_token.username === mod.author) {
|
||||
setOwner(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const base_page = (
|
||||
<>
|
||||
|
@ -139,35 +144,35 @@ function ModPage({name}) {
|
|||
|
||||
<p className={styles.panelTitle}>Versions</p>
|
||||
<div className={styles.timeline}>
|
||||
<a href={"/notfound"}>
|
||||
<svg className={styles.versionDot}>
|
||||
<circle cx="11" cy="11" r="10" stroke="#3a3a3a" stroke-width="1" fill="#1a1a1a" />
|
||||
</svg>
|
||||
<div className={styles.version}>
|
||||
v3.0 (latest)
|
||||
</div>
|
||||
</a>
|
||||
<a href={"/notfound"}>
|
||||
<svg className={styles.versionDot}>
|
||||
<circle cx="11" cy="11" r="10" stroke="#3a3a3a" stroke-width="1" fill="#1a1a1a" />
|
||||
</svg>
|
||||
<div className={styles.version}>
|
||||
v2.0
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href={"/notfound"}>
|
||||
<svg className={styles.versionDot}>
|
||||
<circle cx="11" cy="11" r="10" stroke="#3a3a3a" stroke-width="1" fill="#1a1a1a" />
|
||||
</svg>
|
||||
<div className={styles.version}>
|
||||
v1.0
|
||||
</div>
|
||||
</a>
|
||||
|
||||
{versions.map( (version) => {
|
||||
const url = `/mods/${name}/versions`;
|
||||
return (
|
||||
<a href={url}>
|
||||
<svg className={styles.versionDot}>
|
||||
<circle
|
||||
cx="11" cy="11" r="10"
|
||||
stroke="#3a3a3a" stroke-width="1"
|
||||
fill="#1a1a1a"
|
||||
href={url}
|
||||
/>
|
||||
</svg>
|
||||
<div className={styles.version} href={url}>
|
||||
{`${version.channel} ${version.version_number}`}
|
||||
</div>
|
||||
</a>)
|
||||
})}
|
||||
</div>
|
||||
|
||||
{ owner ? (
|
||||
<div className={styles.panelActions}>
|
||||
<Button
|
||||
// variant='secondary'
|
||||
className={styles.deleteButton}
|
||||
href={`/mods/${name}/versions/create`}
|
||||
>
|
||||
Add version
|
||||
</Button>
|
||||
<Button
|
||||
variant='delete'
|
||||
className={styles.deleteButton}
|
||||
|
|
220
frontend/src/pages/mod_versions.jsx
Normal file
220
frontend/src/pages/mod_versions.jsx
Normal file
|
@ -0,0 +1,220 @@
|
|||
// Preact
|
||||
import { h } from 'preact';
|
||||
import { useState, useEffect } from 'preact/hooks';
|
||||
import Cookies from 'js-cookie'
|
||||
import { jwtDecode } from 'jwt-decode'
|
||||
|
||||
// Functions
|
||||
import { getModVersions, getMod } from '../services/mods';
|
||||
|
||||
// Components
|
||||
import Button from '../components/Buttons/button';
|
||||
|
||||
// Images
|
||||
import logo from '../assets/logo.png'
|
||||
import download_icon from '../assets/download_alt.svg'
|
||||
import GameIcon from '../assets/game.svg'
|
||||
import LoaderIcon from '../assets/loader.svg'
|
||||
import PlatformIcon from '../assets/hardware_platform.svg'
|
||||
|
||||
// Styles
|
||||
import styles from '../styles/content_page.module.css'
|
||||
|
||||
function ModVersionsPage({name}) {
|
||||
|
||||
// UseState
|
||||
const [mod, setMod] = useState({})
|
||||
const [versions, setVersions] = useState([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState(null)
|
||||
const [owner, setOwner] = useState(false);
|
||||
const [currentVersion, setCurrentVersion] = useState(null);
|
||||
|
||||
// UseEffect
|
||||
useEffect(() => {
|
||||
// Load mod informations
|
||||
async function loadItems() {
|
||||
setLoading(true);
|
||||
setError(false);
|
||||
try {
|
||||
const fetched_mod = await getMod(name);
|
||||
setMod(fetched_mod);
|
||||
const fetched_versions = await getModVersions(name, {});
|
||||
setVersions(fetched_versions);
|
||||
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
loadItems();
|
||||
|
||||
}, []); // <-- Tells useEffect to run once after render
|
||||
|
||||
// Handles
|
||||
const handleDeleteVersion = async () => {
|
||||
//TODO
|
||||
console.warn("Cannot delete version: Not implemented");
|
||||
// await deleteMod(mod.name);
|
||||
// location.replace('/dashboard');
|
||||
};
|
||||
|
||||
const handleSelectVersion = (version) => {
|
||||
|
||||
console.debug("Selected ", version);
|
||||
setCurrentVersion(version)
|
||||
}
|
||||
|
||||
if (currentVersion == null) {
|
||||
//TODO make it work
|
||||
if (versions.length != 0) {
|
||||
setCurrentVersion(versions[0]);
|
||||
console.debug(currentVersion);
|
||||
}
|
||||
}
|
||||
|
||||
// Load user informations
|
||||
//TODO use a service
|
||||
//? for some reason, doesn't work inside useEffect
|
||||
const token = Cookies.get('authToken');
|
||||
if (token) {
|
||||
const decoded_token = jwtDecode(token);
|
||||
if (decoded_token) {
|
||||
if (decoded_token.username === mod.author) {
|
||||
setOwner(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const base_page = (
|
||||
<>
|
||||
<a href="/">
|
||||
<img src={logo} class="logo img" alt="WF" />
|
||||
<p class="logo text"> mods </p>
|
||||
</a>
|
||||
</>
|
||||
);
|
||||
|
||||
if (loading) {
|
||||
// TODO replace by loading screen
|
||||
return (
|
||||
<>
|
||||
{base_page}
|
||||
<div className={styles.container}>
|
||||
<div className={styles.content}>
|
||||
<p className={styles.title}>Loading...</p>
|
||||
</div>
|
||||
<div className={styles.infosPanel}> </div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
if (error) {
|
||||
// TODO replace by popup
|
||||
return (
|
||||
<>
|
||||
{base_page}
|
||||
<div className={styles.container}>
|
||||
<div className={styles.content}>
|
||||
<p className={styles.title}>Couldn't load mod versions</p>
|
||||
<p className={styles.fullDescription}>{error}</p>
|
||||
</div>
|
||||
<div className={styles.infosPanel}> </div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{base_page}
|
||||
<div className={styles.container}>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.backgroundImage}></div>
|
||||
<p className={styles.title}>Versions</p>
|
||||
<div className={styles.timeline}>
|
||||
|
||||
{versions.map( (version) => {
|
||||
return (
|
||||
<a href={versions.url}>
|
||||
<svg className={styles.versionDot}>
|
||||
<circle
|
||||
cx="11" cy="11" r="10"
|
||||
stroke="#3a3a3a" stroke-width="1"
|
||||
fill="#1a1a1a"
|
||||
onClick={() => {handleSelectVersion(version)}}/>
|
||||
</svg>
|
||||
<div className={styles.version} onClick={() => {handleSelectVersion(version)}}>
|
||||
{`${version.channel} ${version.version_number}`}
|
||||
</div>
|
||||
</a>)
|
||||
})}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.infosPanel}>
|
||||
|
||||
{currentVersion ? (
|
||||
<>
|
||||
<div className={styles.bigPanelTitle}>
|
||||
{`${currentVersion.version_number} ${currentVersion.channel}`}
|
||||
</div>
|
||||
|
||||
<div className={styles.compatContainer}>
|
||||
<div className={styles.count}>
|
||||
<img src={GameIcon} className={styles.countIcon} alt='Game version'></img>
|
||||
{ currentVersion.game_version || '?'}
|
||||
</div>
|
||||
<div className={styles.count}>
|
||||
<img src={LoaderIcon} className={styles.countIcon} alt='Mod loader'></img>
|
||||
{ currentVersion.platform || '?'}
|
||||
</div>
|
||||
<div className={styles.count}>
|
||||
<img src={PlatformIcon} className={styles.countIcon} alt='Environment'></img>
|
||||
{ currentVersion.environment || '?'}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div className={styles.countsContainer}>
|
||||
<div className={styles.count}>
|
||||
<img src={download_icon} className={styles.countIcon} alt='Downloads count'></img>
|
||||
{mod.mod_infos.downloads_count || 0}
|
||||
</div>
|
||||
<Button className={styles.countButton} href={currentVersion.url} >Download</Button>
|
||||
</div>
|
||||
|
||||
<div className={styles.fullChangelog}>
|
||||
{currentVersion.changelog || "No changelog"}
|
||||
</div>
|
||||
|
||||
{ owner ? (
|
||||
<div className={styles.panelActions}>
|
||||
<Button
|
||||
variant='delete'
|
||||
className={styles.deleteButton}
|
||||
onClick={handleDeleteVersion}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<> </>
|
||||
)
|
||||
}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</>)
|
||||
}
|
||||
|
||||
export default ModVersionsPage
|
|
@ -101,4 +101,66 @@ export async function deleteMod(mod_name) {
|
|||
console.error('Failed to delete mod:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export async function getModVersions(mod_name, filters) {
|
||||
try {
|
||||
let url_parameters = "";
|
||||
|
||||
// Parse filters
|
||||
for (const [key, value] of Object.entries(filters)) {
|
||||
if (url_parameters === "") {
|
||||
url_parameters += "?"
|
||||
} else {
|
||||
url_parameters += "&"
|
||||
}
|
||||
|
||||
url_parameters += `${key}=${value}`;
|
||||
}
|
||||
|
||||
const response = await fetch(`${API_BASE_URL}/mods/${mod_name}/versions${url_parameters}`, {
|
||||
method: 'GET',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error(response.body); //TODO integrate body to error
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to delete mod:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export async function createModVersion(mod_name, version_data) {
|
||||
try {
|
||||
const auth_token = Cookies.get('authToken');
|
||||
|
||||
const response = await fetch(`${API_BASE_URL}/mods/${mod_name}/versions`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': auth_token,
|
||||
},
|
||||
body: JSON.stringify({...version_data})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error(response.body); //TODO integrate body to error
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to create mod version:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
|
@ -132,6 +132,18 @@
|
|||
background-color: #2a2a2a;
|
||||
border: #3a3a3a .1rem solid;
|
||||
border-radius: .5rem;
|
||||
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.countButton {
|
||||
|
||||
/* padding: .5em; */
|
||||
margin: 0 .5rem;
|
||||
min-width: 3em;
|
||||
|
||||
height: 100%;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.countIcon {
|
||||
|
@ -171,6 +183,8 @@
|
|||
font-size: 1.3em;
|
||||
border-radius: .5em;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
transition: 200ms;
|
||||
}
|
||||
|
||||
|
@ -183,6 +197,8 @@
|
|||
width: 1.5em;
|
||||
|
||||
z-index: 2;
|
||||
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.versionDot > circle {
|
||||
|
@ -206,6 +222,10 @@
|
|||
|
||||
width: 100%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
@ -215,4 +235,23 @@
|
|||
width: 100%;
|
||||
min-width: 5em;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.bigPanelTitle {
|
||||
|
||||
font-size: 2rem;
|
||||
margin-bottom: 2.5rem;
|
||||
}
|
||||
|
||||
.fullChangelog {
|
||||
|
||||
margin-top: 3rem;
|
||||
color: #8a8a8a
|
||||
}
|
||||
|
||||
.compatContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
margin-bottom: 3érem;
|
||||
}
|
138
frontend/src/styles/version_creation.module.css
Normal file
138
frontend/src/styles/version_creation.module.css
Normal file
|
@ -0,0 +1,138 @@
|
|||
.container {
|
||||
position: absolute;
|
||||
top: 11rem;
|
||||
right: 4rem;
|
||||
left: 22rem;
|
||||
bottom: 4rem;
|
||||
display: inline;
|
||||
--gap: 1rem;
|
||||
}
|
||||
|
||||
.form {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
|
||||
width: calc(70% - var(--gap));
|
||||
min-height: 20rem;
|
||||
|
||||
padding: 3rem;
|
||||
|
||||
background-color: #1a1a1a;
|
||||
color: #eaeaea;
|
||||
|
||||
border: #3a3a3a solid;
|
||||
border-width: .1em;
|
||||
border-radius: .5rem;
|
||||
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
.infosPanel {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: calc(70% + var(--gap) + 7rem);
|
||||
right: 0;
|
||||
|
||||
padding: 2rem;
|
||||
min-height: 20rem;
|
||||
align-items: center;
|
||||
|
||||
background-color: #1a1a1a;
|
||||
color: #eaeaea;
|
||||
|
||||
border: #3a3a3a solid;
|
||||
border-width: .1em;
|
||||
border-radius: .5rem;
|
||||
display: inline;
|
||||
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
.profilePicture {
|
||||
/*TODO keep it center when overlapping*/
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
|
||||
width: 7em;
|
||||
}
|
||||
|
||||
.author {
|
||||
margin: 0;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
color: #c5c5c5;
|
||||
font-weight: 600;
|
||||
font-size: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.panelTitle {
|
||||
margin-top: 2rem;
|
||||
|
||||
color: #eaeaea;
|
||||
font-weight: 600;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
|
||||
.smallField {
|
||||
width: 20em;
|
||||
}
|
||||
|
||||
/* .ChangelogField {
|
||||
|
||||
margin-top: -.01rem;
|
||||
margin-left: 1rem;
|
||||
|
||||
height: 7em;
|
||||
min-width: 20em;
|
||||
display: inline-flex;
|
||||
flex-grow: 1;
|
||||
} */
|
||||
|
||||
.changelogField {
|
||||
min-width: 20em;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.topFields {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.middleFields {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.bottomFields {
|
||||
/* display: flex;
|
||||
justify-content: right; */
|
||||
height: 25vh;
|
||||
min-height: 12em;
|
||||
}
|
||||
|
||||
.bottomBottomFields {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
gap: 1rem;
|
||||
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
|
||||
.createButton {
|
||||
position: absolute;
|
||||
bottom: 1em;
|
||||
right: 1em;
|
||||
|
||||
font-size: 1.4rem;
|
||||
}
|
Loading…
Reference in a new issue