mirror of
https://github.com/OpenFactorioServerManager/factorio-server-manager.git
synced 2024-12-29 02:57:24 +02:00
wip
This commit is contained in:
parent
7075de7d44
commit
9f0686a09e
@ -56,6 +56,7 @@
|
||||
"@fortawesome/free-solid-svg-icons": "^5.14.0",
|
||||
"@fortawesome/react-fontawesome": "^0.1.11",
|
||||
"axios": "^0.19.2",
|
||||
"fuse.js": "^6.4.1",
|
||||
"react-hook-form": "^5.7.2",
|
||||
"regenerator-runtime": "^0.13.7",
|
||||
"semver": "^6.1.1",
|
||||
|
@ -1,19 +1,19 @@
|
||||
import React from "react";
|
||||
|
||||
const Button = ({ children, type, onClick, isSubmit, className, size }) => {
|
||||
const Button = ({ children, type, onClick, isSubmit, className, size, isDisabled = false }) => {
|
||||
|
||||
let color = '';
|
||||
let padding = '';
|
||||
|
||||
switch (type) {
|
||||
case 'success':
|
||||
color = 'bg-green hover:glow-green hover:bg-green-light';
|
||||
color = `bg-green ${isDisabled ? null : "hover:glow-green hover:bg-green-light" }`;
|
||||
break;
|
||||
case 'danger':
|
||||
color = 'bg-red hover:glow-red hover:bg-red-light';
|
||||
color = `bg-red ${isDisabled ? null : "hover:glow-red hover:bg-red-light"}`;
|
||||
break;
|
||||
default:
|
||||
color = 'bg-gray-light hover:glow-orange hover:bg-orange'
|
||||
color = `bg-gray-light ${isDisabled ? null : "hover:glow-orange hover:bg-orange"}`
|
||||
}
|
||||
|
||||
switch (size) {
|
||||
@ -25,7 +25,7 @@ const Button = ({ children, type, onClick, isSubmit, className, size }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<button onClick={onClick} className={`${className ? className: null} ${padding} ${color} inline-block accentuated text-black font-bold`}
|
||||
<button onClick={onClick} disabled={isDisabled} className={`${className ? className: null} ${isDisabled ? "bg-opacity-50 cursor-not-allowed" : null} ${padding} ${color} inline-block accentuated text-black font-bold`}
|
||||
type={isSubmit ? 'submit' : 'button'}>
|
||||
{children}
|
||||
</button>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
|
||||
const Input = ({name, inputRef, placeholder = null, type="text", defaultValue=null}) => {
|
||||
const Input = ({name, inputRef, placeholder = null, type="text", defaultValue=null, hasAutoComplete=true}) => {
|
||||
return (
|
||||
<input
|
||||
className="shadow appearance-none border w-full py-2 px-3 text-black"
|
||||
@ -9,6 +9,7 @@ const Input = ({name, inputRef, placeholder = null, type="text", defaultValue=nu
|
||||
name={name}
|
||||
id={name}
|
||||
type={type}
|
||||
autoComplete={hasAutoComplete ? "on" : "off"}
|
||||
defaultValue={defaultValue}
|
||||
/>
|
||||
)
|
||||
|
@ -1,8 +1,8 @@
|
||||
import React from "react";
|
||||
|
||||
const Select = ({name, inputRef, children}) => {
|
||||
const Select = ({name, inputRef, children, className}) => {
|
||||
return (
|
||||
<div className="relative">
|
||||
<div className={`${className} relative`}>
|
||||
<select
|
||||
className="shadow appearance-none border w-full py-2 px-3 text-black"
|
||||
name={name}
|
||||
|
@ -9,12 +9,13 @@ import Tab from "../../components/Tabs/Tab";
|
||||
import AddMod from "./components/AddMod/AddMod";
|
||||
import UploadMod from "./components/UploadMod";
|
||||
import LoadMods from "./components/LoadMods";
|
||||
import Fuse from "fuse.js";
|
||||
|
||||
const Mods = () => {
|
||||
|
||||
const [installedMods, setInstalledMods] = useState([]);
|
||||
const [factorioVersion, setFactorioVersion] = useState(null);
|
||||
|
||||
const [fuse, setFuse] = useState(undefined);
|
||||
|
||||
const fetchInstalledMods = () => {
|
||||
modsResource.installed()
|
||||
@ -28,25 +29,42 @@ const Mods = () => {
|
||||
|
||||
useEffect(() => {
|
||||
server.factorioVersion()
|
||||
.then(res => {
|
||||
if (res) {
|
||||
setFactorioVersion(res.base_mod_version)
|
||||
}
|
||||
.then(data => {
|
||||
setFactorioVersion(data.base_mod_version)
|
||||
fetchInstalledMods();
|
||||
})
|
||||
|
||||
// fetch list of mods
|
||||
modsResource.portal.list()
|
||||
.then(res => {
|
||||
setFuse(new Fuse(res.results, {
|
||||
keys: [
|
||||
{
|
||||
"name": "name",
|
||||
weight: 2
|
||||
},
|
||||
{
|
||||
"name": "title",
|
||||
weight: 1
|
||||
}
|
||||
],
|
||||
minMatchCharLength: 3
|
||||
}));
|
||||
});
|
||||
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<TabControl>
|
||||
<Tab title="Install Mod">
|
||||
<AddMod refetchInstalledMods={fetchInstalledMods}/>
|
||||
<AddMod refetchInstalledMods={fetchInstalledMods} fuse={fuse}/>
|
||||
</Tab>
|
||||
<Tab title="Upload Mod">
|
||||
<UploadMod/>
|
||||
</Tab>
|
||||
<Tab title="Load Mod from Save">
|
||||
<LoadMods/>
|
||||
<LoadMods refreshMods={fetchInstalledMods}/>
|
||||
</Tab>
|
||||
</TabControl>
|
||||
|
||||
@ -82,7 +100,7 @@ const Mods = () => {
|
||||
title="Mod packs"
|
||||
className="mb-6"
|
||||
content={
|
||||
"Test"
|
||||
"Test"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
@ -5,7 +5,7 @@ import modResource from "../../../../../api/resources/mods";
|
||||
|
||||
|
||||
|
||||
const AddMod = ({refetchInstalledMods}) => {
|
||||
const AddMod = ({refetchInstalledMods, fuse}) => {
|
||||
|
||||
const [isFactorioAuthenticated, setIsFactorioAuthenticated] = useState(false);
|
||||
|
||||
@ -16,7 +16,7 @@ const AddMod = ({refetchInstalledMods}) => {
|
||||
}, []);
|
||||
|
||||
return isFactorioAuthenticated
|
||||
? <AddModForm setIsFactorioAuthenticated={setIsFactorioAuthenticated} refetchInstalledMods={refetchInstalledMods}/>
|
||||
? <AddModForm fuse={fuse} setIsFactorioAuthenticated={setIsFactorioAuthenticated} refetchInstalledMods={refetchInstalledMods}/>
|
||||
: <FactorioLogin setIsFactorioAuthenticated={setIsFactorioAuthenticated}/>
|
||||
}
|
||||
|
||||
|
@ -1,33 +1,83 @@
|
||||
import React from "react";
|
||||
import React, {useCallback, useEffect, useState} from "react";
|
||||
import modsResource from "../../../../../../api/resources/mods";
|
||||
import Button from "../../../../../components/Button";
|
||||
import Label from "../../../../../components/Label";
|
||||
import {useForm} from "react-hook-form";
|
||||
import Input from "../../../../../components/Input";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {faExternalLinkAlt} from "@fortawesome/free-solid-svg-icons/faExternalLinkAlt";
|
||||
import {faSpinner} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
const LinkModPortal = () => {
|
||||
return <a href="https://mods.factorio.com" target="_blank" className="px-2 text-blue hover:text-blue-light">Mod
|
||||
Portal <FontAwesomeIcon icon={faExternalLinkAlt}/></a>
|
||||
}
|
||||
|
||||
const AddModForm = ({setIsFactorioAuthenticated}) => {
|
||||
const AddModForm = ({setIsFactorioAuthenticated, fuse}) => {
|
||||
|
||||
const {register, handleSubmit} = useForm();
|
||||
const {register, watch, setValue} = useForm();
|
||||
const [suggestedMods, setSuggestedMods] = useState([]);
|
||||
const [selectedMod, setSelectedMod] = useState(null)
|
||||
|
||||
const [autocomplete, setAutocomplete] = useState(NaN);
|
||||
const mod = watch('mod');
|
||||
|
||||
const logout = () => {
|
||||
modsResource.portal.logout()
|
||||
.then(res => {
|
||||
setIsFactorioAuthenticated(false);
|
||||
});
|
||||
.then(() => setIsFactorioAuthenticated(false));
|
||||
}
|
||||
|
||||
const updateSuggestedMods = () => {
|
||||
if (typeof fuse != "undefined") {
|
||||
clearTimeout(autocomplete)
|
||||
setAutocomplete(setTimeout(() => setSuggestedMods(fuse.search(mod || '')), 200));
|
||||
}
|
||||
};
|
||||
|
||||
const selectMod = mod => {
|
||||
clearTimeout(autocomplete);
|
||||
setValue('mod', mod.item.title); // triggers effect for mod
|
||||
setSuggestedMods([]);
|
||||
setSelectedMod(mod);
|
||||
}
|
||||
|
||||
// get triggered if mod changed
|
||||
useEffect(() => {
|
||||
if (selectedMod === null) {
|
||||
updateSuggestedMods();
|
||||
} else if (selectedMod.item.title !== mod) {
|
||||
setSelectedMod(null);
|
||||
updateSuggestedMods();
|
||||
}
|
||||
}, [mod]);
|
||||
|
||||
const addMod = data => {
|
||||
// todo install selected mod
|
||||
console.log(data);
|
||||
// todo update list of installed mods
|
||||
}
|
||||
|
||||
return (
|
||||
<form >
|
||||
<div className="mb-4">
|
||||
<form onSubmit={addMod}>
|
||||
<div className="mb-4 relative">
|
||||
<Label text="Mod" htmlFor="mod"/>
|
||||
<Input inputRef={register({required: true})} name="mod"/>
|
||||
{ typeof fuse !== "undefined"
|
||||
? <Input inputRef={register({required: true})} hasAutoComplete={false} name="mod"/>
|
||||
: <div className="border border-gray-medium w-full py-2 px-3 text-white">
|
||||
<FontAwesomeIcon icon={faSpinner} spin={true}/> Loading List of Mods from <LinkModPortal/>
|
||||
</div>
|
||||
}
|
||||
{suggestedMods.length > 0 &&
|
||||
<ul className="bg-white text-black h-64 overflow-y-scroll absolute bottom-0 left-0 w-full -mb-64">
|
||||
{suggestedMods.map((mod, index) => <li className="px-2 py-1 cursor-pointer hover:bg-blue-light" onClick={() => selectMod(mod)} key={index}>{mod.item.title}</li>)}
|
||||
</ul>
|
||||
}
|
||||
</div>
|
||||
<Button isSubmit={true}>Install</Button>
|
||||
<Button onClick={logout} type="danger">Logout</Button>
|
||||
<Button isDisabled={selectedMod === null} isSubmit={true} className="mr-2">Install</Button>
|
||||
<Button onClick={logout} type="danger" className="mr-2">Logout</Button>
|
||||
<LinkModPortal/>
|
||||
</form>
|
||||
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
export default AddModForm;
|
@ -4,28 +4,33 @@ import Select from "../../../components/Select";
|
||||
import Label from "../../../components/Label";
|
||||
import {useForm} from "react-hook-form";
|
||||
import Button from "../../../components/Button";
|
||||
import modsResource from "../../../../api/resources/mods";
|
||||
|
||||
const LoadMods = () => {
|
||||
const LoadMods = ({refreshMods}) => {
|
||||
|
||||
const [saves, setSaves] = useState([]);
|
||||
const {register, handleSubmit} = useForm();
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const res = await savesResource.list()
|
||||
if (res.success) {
|
||||
setSaves(res.data);
|
||||
}
|
||||
setSaves(await savesResource.list());
|
||||
})();
|
||||
}, [])
|
||||
}, []);
|
||||
|
||||
const loadMods = data => {
|
||||
savesResource.mods(data.save)
|
||||
.then(({mods}) => {
|
||||
modsResource.portal.installMultiple(mods).then(refreshMods)
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<form >
|
||||
<form onSubmit={handleSubmit(loadMods)}>
|
||||
<Label text="Save" htmlFor="save"/>
|
||||
<Select name="save" inputRef={register}>
|
||||
<Select name="save" inputRef={register} className="mb-4">
|
||||
{saves?.map(save => <option value={save.name} key={save.name}>{save.name}</option>)}
|
||||
</Select>
|
||||
<Button isSubmit={true}>Save</Button>
|
||||
<Button isSubmit={true}>Load</Button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
@ -1,18 +1,24 @@
|
||||
import React, {useState} from "react";
|
||||
import Button from "../../../components/Button";
|
||||
import Label from "../../../components/Label";
|
||||
|
||||
const UploadMod = () => {
|
||||
|
||||
const [fileName, setFileName] = useState('Select File ...');
|
||||
|
||||
return (
|
||||
<div className="relative bg-white shadow text-black h-full w-full">
|
||||
<input
|
||||
className="absolute left-0 top-0 opacity-0 cursor-pointer w-full h-full"
|
||||
onChange={e => setFileName(e.currentTarget.files[0].name)}
|
||||
name="savefile"
|
||||
id="savefile" type="file"/>
|
||||
<div className="px-2 py-3">{fileName}</div>
|
||||
</div>
|
||||
<form>
|
||||
<Label text="Save" htmlFor="savefile"/>
|
||||
<div className="relative bg-white shadow text-black h-full w-full mb-4">
|
||||
<input
|
||||
className="absolute left-0 top-0 opacity-0 cursor-pointer w-full h-full"
|
||||
onChange={e => setFileName(e.currentTarget.files[0].name)}
|
||||
name="savefile"
|
||||
id="savefile" type="file"/>
|
||||
<div className="px-2 py-2">{fileName}</div>
|
||||
</div>
|
||||
<Button isSubmit={true}>Upload</Button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1,31 +1,38 @@
|
||||
import {useForm} from "react-hook-form";
|
||||
import Button from "../../../components/Button";
|
||||
import React from "react";
|
||||
import React, {useState} from "react";
|
||||
import saves from "../../../../api/resources/saves";
|
||||
import Label from "../../../components/Label";
|
||||
import Input from "../../../components/Input";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {faSpinner} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
const CreateSaveForm = ({onSuccess}) => {
|
||||
const {register, handleSubmit, errors} = useForm();
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const onSubmit = async (data, e) => {
|
||||
const res = await saves.create(data.savefile);
|
||||
if (res) {
|
||||
e.target.reset();
|
||||
onSuccess();
|
||||
}
|
||||
setIsLoading(true)
|
||||
saves.create(data.savefile)
|
||||
.then(() => {
|
||||
e.target.reset();
|
||||
onSuccess();
|
||||
})
|
||||
.finally(() => setIsLoading(false))
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="mb-6">
|
||||
<Label text="Savefile Name" htmlFor="savefile"/>
|
||||
<Input inputRef={register({required: true})} name="savefile"/>
|
||||
{errors.savefile && <span className="block text-red">Savefile Name is required</span>}
|
||||
</div>
|
||||
<Button type="success" isSubmit={true}>Create Save</Button>
|
||||
</form>
|
||||
<div className="mb-6">
|
||||
<Label text="Savefile Name" htmlFor="savefile"/>
|
||||
<Input inputRef={register({required: true})} name="savefile"/>
|
||||
{errors.savefile && <span className="block text-red">Savefile Name is required</span>}
|
||||
</div>
|
||||
{ isLoading
|
||||
? <Button type="success" isSubmit={true}>Create Save <FontAwesomeIcon icon={faSpinner} spin={true}/></Button>
|
||||
: <Button type="success" isSubmit={true}>Create Save</Button>
|
||||
}
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -36,10 +36,6 @@ const mods = {
|
||||
},
|
||||
portal: {
|
||||
login: async (username, password) => {
|
||||
// const data = new FormData();
|
||||
// data.set('username', username);
|
||||
// data.set('password', password);
|
||||
|
||||
const response = await client.post('/api/mods/portal/login', {
|
||||
username,
|
||||
password
|
||||
@ -53,6 +49,14 @@ const mods = {
|
||||
logout: async () => {
|
||||
const response = await client.get('/api/mods/portal/logout');
|
||||
return response.data
|
||||
},
|
||||
installMultiple: async mods => {
|
||||
const response = await client.post('/api/mods/portal/install/multiple', mods);
|
||||
return response.data
|
||||
},
|
||||
list: async () => {
|
||||
const response = await client.get('/api/mods/portal/list');
|
||||
return response.data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,5 +23,11 @@ export default {
|
||||
}
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
mods: async save => {
|
||||
const response = await client.post("/api/saves/mods", {
|
||||
saveFile: save
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user