added handlers and views for uploading and removing mods and saves

This commit is contained in:
majormjr 2016-04-21 11:05:28 -04:00
parent e0ac8fdbec
commit 4ac1fc0568
12 changed files with 701 additions and 68 deletions

View File

@ -26093,6 +26093,7 @@
_this.componentDidMount = _this.componentDidMount.bind(_this);
_this.toggleMod = _this.toggleMod.bind(_this);
_this.loadInstalledModList = _this.loadInstalledModList.bind(_this);
_this.state = {
installedMods: [],
listMods: []
@ -26196,7 +26197,9 @@
_react2.default.createElement(
'section',
{ className: 'content', style: { height: "100%" } },
_react2.default.createElement(_InstalledMods2.default, this.state),
_react2.default.createElement(_InstalledMods2.default, _extends({}, this.state, {
loadInstalledModList: this.loadInstalledModList
})),
_react2.default.createElement(_ListMods2.default, _extends({}, this.state, {
toggleMod: this.toggleMod
}))
@ -26250,11 +26253,6 @@
}
_createClass(ModList, [{
key: 'componentDidMount',
value: function componentDidMount() {
console.log(this.props.listMods);
}
}, {
key: 'render',
value: function render() {
var _this2 = this;
@ -26274,6 +26272,11 @@
_react2.default.createElement(
'div',
{ className: 'box-body' },
_react2.default.createElement(
'h4',
null,
'Upload Mod'
),
_react2.default.createElement(
'div',
{ className: 'table-responsive' },
@ -26433,7 +26436,7 @@
/* 233 */
/***/ function(module, exports, __webpack_require__) {
"use strict";
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
@ -26463,30 +26466,150 @@
}
_createClass(InstalledMods, [{
key: "render",
key: 'componentDidMount',
value: function componentDidMount() {
this.uploadFile = this.uploadFile.bind(this);
this.removeMod = this.removeMod.bind(this);
}
}, {
key: 'updateInstalledMods',
value: function updateInstalledMods() {
this.props.loadInstalledModList();
}
}, {
key: 'uploadFile',
value: function uploadFile(e) {
var fd = new FormData();
fd.append('modfile', this.refs.file.files[0]);
$.ajax({
url: "/api/mods/upload",
type: "POST",
data: fd,
processData: false,
contentType: false,
success: function success(data) {
alert(data);
}
});
e.preventDefault();
this.updateInstalledMods();
}
}, {
key: 'removeMod',
value: function removeMod(i) {
$.ajax({
url: "/api/mods/rm/" + this.props.installedMods[i],
success: function success(data) {
alert(data);
}
});
this.updateInstalledMods();
}
}, {
key: 'render',
value: function render() {
var _this2 = this;
return _react2.default.createElement(
"div",
{ className: "box" },
'div',
{ className: 'box' },
_react2.default.createElement(
"div",
{ className: "box-header" },
'div',
{ className: 'box-header' },
_react2.default.createElement(
"h3",
{ className: "box-title" },
"Installed Mods"
'h3',
{ className: 'box-title' },
'Installed Mods'
)
),
_react2.default.createElement(
"div",
{ className: "box-body" },
this.props.installedMods.map(function (mod, i) {
return _react2.default.createElement(
"p",
'div',
{ className: 'box-body' },
_react2.default.createElement(
'form',
{ ref: 'uploadForm', className: 'form', encType: 'multipart/form-data' },
_react2.default.createElement(
'fieldset',
null,
mod
);
})
_react2.default.createElement(
'span',
{ className: 'btn btn-default btn-file' },
_react2.default.createElement('input', { ref: 'file', type: 'file', name: 'modfile', id: 'modfile' })
),
_react2.default.createElement('input', { type: 'button', ref: 'button', value: 'Upload', onClick: this.uploadFile })
)
),
_react2.default.createElement(
'div',
{ className: 'table-responsive' },
_react2.default.createElement(
'table',
{ className: 'table table-striped' },
_react2.default.createElement(
'thead',
null,
_react2.default.createElement(
'tr',
null,
_react2.default.createElement(
'th',
null,
'Mod Name'
),
_react2.default.createElement(
'th',
null,
'Download'
),
_react2.default.createElement(
'th',
null,
'Delete'
)
)
),
_react2.default.createElement(
'tbody',
null,
this.props.installedMods.map(function (mod, i) {
var saveLocation = "/api/mods/dl/" + mod;
return _react2.default.createElement(
'tr',
{ key: i },
_react2.default.createElement(
'td',
null,
mod
),
_react2.default.createElement(
'td',
null,
_react2.default.createElement(
'a',
{ className: 'btn btn-default', href: saveLocation },
'Download'
)
),
_react2.default.createElement(
'td',
null,
_react2.default.createElement(
'button',
{
className: 'btn btn-danger btn-small',
ref: 'modInput',
type: 'button',
onClick: _this2.removeMod.bind(_this2, i) },
_react2.default.createElement('i', { className: 'fa fa-trash' }),
'  Delete'
)
)
);
})
)
)
)
)
);
}
@ -26496,7 +26619,8 @@
}(_react2.default.Component);
InstalledMods.propTypes = {
installedMods: _react2.default.PropTypes.array.isRequired
installedMods: _react2.default.PropTypes.array.isRequired,
loadInstalledModList: _react2.default.PropTypes.func.isRequired
};
exports.default = InstalledMods;
@ -26751,6 +26875,7 @@
var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(SavesContent).call(this, props));
_this.dlSave = _this.dlSave.bind(_this);
_this.getSaves = _this.getSaves.bind(_this);
_this.state = {
saves: []
};
@ -26835,7 +26960,8 @@
'section',
{ className: 'content' },
_react2.default.createElement(_SavesList2.default, _extends({}, this.state, {
dlSave: this.dlSave
dlSave: this.dlSave,
getSaves: this.getSaves
}))
)
);
@ -26878,15 +27004,58 @@
var SavesList = function (_React$Component) {
_inherits(SavesList, _React$Component);
function SavesList() {
function SavesList(props) {
_classCallCheck(this, SavesList);
return _possibleConstructorReturn(this, Object.getPrototypeOf(SavesList).apply(this, arguments));
var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(SavesList).call(this, props));
_this.updateSavesList = _this.updateSavesList.bind(_this);
_this.uploadFile = _this.uploadFile.bind(_this);
_this.removeSave = _this.removeSave.bind(_this);
return _this;
}
_createClass(SavesList, [{
key: 'updateSavesList',
value: function updateSavesList() {
this.props.getSaves();
}
}, {
key: 'uploadFile',
value: function uploadFile(e) {
var fd = new FormData();
fd.append('savefile', this.refs.file.files[0]);
$.ajax({
url: "/api/saves/upload",
type: "POST",
data: fd,
processData: false,
contentType: false,
success: function success(data) {
alert(data);
}
});
e.preventDefault();
this.updateSavesList();
}
}, {
key: 'removeSave',
value: function removeSave(saveName, e) {
console.log(e, saveName);
$.ajax({
url: "/api/saves/rm/" + saveName,
success: function success(data) {
alert(data);
}
});
this.updateSavesList();
}
}, {
key: 'render',
value: function render() {
var _this2 = this;
return _react2.default.createElement(
'div',
{ className: 'box' },
@ -26902,6 +27071,20 @@
_react2.default.createElement(
'div',
{ className: 'box-body' },
_react2.default.createElement(
'form',
{ ref: 'uploadForm', className: 'form', encType: 'multipart/form-data' },
_react2.default.createElement(
'fieldset',
null,
_react2.default.createElement(
'span',
{ className: 'btn btn-default btn-file' },
_react2.default.createElement('input', { ref: 'file', type: 'file', name: 'modfile', id: 'modfile' })
),
_react2.default.createElement('input', { type: 'button', ref: 'button', value: 'Upload', onClick: this.uploadFile })
)
),
_react2.default.createElement(
'div',
{ className: 'table-responsive' },
@ -26942,7 +27125,10 @@
this.props.saves.map(function (save, i) {
return _react2.default.createElement(_Save2.default, {
key: i,
save: save
saves: _this2.props.saves,
index: i,
save: save,
removeSave: _this2.removeSave
});
})
)
@ -26958,7 +27144,8 @@
SavesList.propTypes = {
saves: _react2.default.PropTypes.array.isRequired,
dlSave: _react2.default.PropTypes.func.isRequired
dlSave: _react2.default.PropTypes.func.isRequired,
getSaves: _react2.default.PropTypes.func.isRequired
};
exports.default = SavesList;
@ -27032,6 +27219,20 @@
{ className: 'btn btn-default', href: saveLocation },
'Download'
)
),
_react2.default.createElement(
'td',
null,
_react2.default.createElement(
'button',
{
className: 'btn btn-danger btn-small',
ref: 'saveInput',
type: 'button',
onClick: this.props.removeSave.bind(this, this.props.saves[this.props.index].name) },
_react2.default.createElement('i', { className: 'fa fa-trash' }),
'  Delete'
)
)
);
}
@ -27041,7 +27242,10 @@
}(_react2.default.Component);
Save.propTypes = {
save: _react2.default.PropTypes.object.isRequired
save: _react2.default.PropTypes.object.isRequired,
saves: _react2.default.PropTypes.array.isRequired,
index: _react2.default.PropTypes.number.isRequired,
removeSave: _react2.default.PropTypes.func.isRequired
};
exports.default = Save;

View File

@ -3,8 +3,10 @@ package main
import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"github.com/gorilla/mux"
)
@ -17,7 +19,13 @@ func Index(w http.ResponseWriter, r *http.Request) {
func ListInstalledMods(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json;charset=UTF-8")
mods := listInstalledMods()
modDir := config.FactorioDir + "/mods"
mods, err := listInstalledMods(modDir)
if err != nil {
log.Printf("Error in ListInstalledMods handler: %s", err)
return
}
if err := json.NewEncoder(w).Encode(mods); err != nil {
log.Printf("Error in list mods: %s", err)
@ -61,20 +69,185 @@ func ListMods(w http.ResponseWriter, r *http.Request) {
if err := json.NewEncoder(w).Encode(m); err != nil {
log.Printf("Error listing mods: %s", err)
}
}
// Uploads mod to the mods directory
func UploadMod(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET":
resp := "Unsupported method"
if err := json.NewEncoder(w).Encode(resp); err != nil {
log.Printf("Error listing mods: %s", err)
}
case "POST":
log.Println("Uploading file")
r.ParseMultipartForm(32 << 20)
file, header, err := r.FormFile("modfile")
if err != nil {
json.NewEncoder(w).Encode(err.Error())
log.Printf("%+v", file)
log.Printf("%+v", header)
log.Printf("Error in formfile")
return
}
defer file.Close()
out, err := os.Create(config.FactorioModsDir + "/" + header.Filename)
if err != nil {
json.NewEncoder(w).Encode(err.Error())
log.Printf("Error in out")
return
}
defer out.Close()
_, err = io.Copy(out, file)
if err != nil {
json.NewEncoder(w).Encode(err.Error())
log.Printf("Error in io copy")
return
}
log.Printf("Uploaded mod file: %s", header.Filename)
resp := "File '" + header.Filename + "' submitted successfully"
json.NewEncoder(w).Encode(resp)
default:
w.WriteHeader(http.StatusMethodNotAllowed)
}
}
func RemoveMod(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json;charset=UTF-8")
vars := mux.Vars(r)
modName := vars["mod"]
err := rmMod(modName)
if err == nil {
// No error returned means mod was removed
resp := fmt.Sprintf("Removed mod: %s", modName)
if err := json.NewEncoder(w).Encode(resp); err != nil {
log.Printf("Error removing mod: %s", err)
}
} else {
log.Printf("Error in remove mod handler: %s", err)
resp := fmt.Sprintf("Error in remove mod handler: %s", err)
if err := json.NewEncoder(w).Encode(resp); err != nil {
log.Printf("Error removing mod: %s", err)
}
return
}
}
func DownloadMod(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json;charset=UTF-8")
vars := mux.Vars(r)
mod := vars["mod"]
modFile := config.FactorioModsDir + "/" + mod
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", mod))
log.Printf("%s downloading: %s", r.Host, modFile)
http.ServeFile(w, r, modFile)
}
// Lists all save files in the factorio/saves directory
func ListSaves(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json;charset=UTF-8")
saves := listSaves()
saveDir := config.FactorioDir + "/saves"
saves, err := listSaves(saveDir)
if err != nil {
log.Printf("Error in ListSaves handler: %s", err)
return
}
if err := json.NewEncoder(w).Encode(saves); err != nil {
log.Printf("Error listing saves: %s", err)
}
}
func DLSave(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/octet-stream")
vars := mux.Vars(r)
save := vars["save"]
saveName := config.FactorioSavesDir + "/" + save
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", save))
log.Printf("%s downloading: %s", r.Host, saveName)
http.ServeFile(w, r, saveName)
}
func UploadSave(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET":
resp := "Unsupported method"
if err := json.NewEncoder(w).Encode(resp); err != nil {
log.Printf("Error listing mods: %s", err)
}
case "POST":
log.Println("Uploading save file")
r.ParseMultipartForm(32 << 20)
file, header, err := r.FormFile("savefile")
if err != nil {
json.NewEncoder(w).Encode(err.Error())
log.Printf("%+v", file)
log.Printf("%+v", header)
log.Printf("Error in formfile")
return
}
defer file.Close()
out, err := os.Create(config.FactorioSavesDir + "/" + header.Filename)
if err != nil {
json.NewEncoder(w).Encode(err.Error())
log.Printf("Error in out: %s", err)
return
}
defer out.Close()
_, err = io.Copy(out, file)
if err != nil {
json.NewEncoder(w).Encode(err.Error())
log.Printf("Error in io copy: %s", err)
return
}
log.Printf("Uploaded save file: %s", header.Filename)
resp := "File '" + header.Filename + "' uploaded successfully"
json.NewEncoder(w).Encode(resp)
default:
w.WriteHeader(http.StatusMethodNotAllowed)
}
}
func RemoveSave(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json;charset=UTF-8")
vars := mux.Vars(r)
saveName := vars["save"]
err := rmSave(saveName)
if err == nil {
// No error returned means save was removed
resp := fmt.Sprintf("Removed save: %s", saveName)
if err := json.NewEncoder(w).Encode(resp); err != nil {
log.Printf("Error removing save %s", err)
}
} else {
log.Printf("Error in remove save handler: %s", err)
resp := fmt.Sprintf("Error in remove save handler: %s", err)
if err := json.NewEncoder(w).Encode(resp); err != nil {
log.Printf("Error removing save: %s", err)
}
return
}
}
// Returns last lines of the factorio-current.log file
func LogTail(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json;charset=UTF-8")
@ -89,16 +262,3 @@ func LogTail(w http.ResponseWriter, r *http.Request) {
log.Printf("Error tailing logfile", err)
}
}
func DLSave(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/octet-stream")
vars := mux.Vars(r)
save := vars["save"]
saveName := config.FactorioSavesDir + "/" + save
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", save))
log.Printf("%s downloading: %s", r.Host, saveName)
http.ServeFile(w, r, saveName)
}

10
main.go
View File

@ -9,9 +9,11 @@ import (
type Config struct {
FactorioDir string
FactorioSavesDir string
FactorioModsDir string
FactorioLog string
ServerIP string
ServerPort string
FactorioLog string
MaxUploadSize int64
}
var config Config
@ -19,14 +21,18 @@ var config Config
func loadFlags() {
factorioDir := flag.String("dir", "./", "Specify location of Factorio config directory.")
factorioIP := flag.String("host", "0.0.0.0", "Specify IP for webserver to listen on.")
factorioPort := flag.String("port", "8080", "Specify a port for the server")
factorioPort := flag.String("port", "8080", "Specify a port for the server.")
factorioMaxUpload := flag.Int64("max-upload", 100000, "Maximum filesize for uploaded files.")
flag.Parse()
config.FactorioDir = *factorioDir
config.FactorioSavesDir = config.FactorioDir + "/saves"
config.FactorioModsDir = config.FactorioDir + "/mods"
config.ServerIP = *factorioIP
config.ServerPort = *factorioPort
config.FactorioLog = config.FactorioDir + "/factorio-current.log"
config.MaxUploadSize = *factorioMaxUpload
log.Printf(config.FactorioSavesDir)
}

43
mods.go
View File

@ -2,8 +2,12 @@ package main
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
"os"
"strings"
)
type ModList struct {
@ -16,14 +20,13 @@ type Mod struct {
}
// List mods installed in the factorio/mods directory
func listInstalledMods() []string {
modDir := config.FactorioDir + "/mods"
func listInstalledMods(modDir string) ([]string, error) {
result := []string{}
files, err := ioutil.ReadDir(modDir)
if err != nil {
log.Printf("Error listing installed mods")
return result
return result, err
}
for _, f := range files {
if f.Name() == "mod-list.json" {
@ -32,7 +35,39 @@ func listInstalledMods() []string {
result = append(result, f.Name())
}
return result
return result, nil
}
func rmMod(modName string) error {
removed := false
if modName == "" {
return errors.New("No mod name provided.")
}
installedMods, err := listInstalledMods(config.FactorioModsDir)
if err != nil {
log.Printf("Error in remove mod list: %s", err)
return err
}
for _, mod := range installedMods {
log.Printf("Checking if %s in %s", mod, modName)
if strings.Contains(mod, modName) {
err := os.Remove(config.FactorioModsDir + "/" + mod)
if err != nil {
log.Printf("Error removing mod %s: %s", mod, err)
return err
}
log.Printf("Removed mod: %s", mod)
removed = true
}
}
if !removed {
log.Printf("Did not remove mod: %s", modName)
return errors.New(fmt.Sprintf("Did not remove mod: %s", modName))
}
return nil
}
// Parses mod-list.json file in factorio/mods

View File

@ -30,6 +30,19 @@ func NewRouter() *mux.Router {
// Serves the frontend application from the app directory
// Uses basic file server to serve index.html and Javascript application
// Routes match the ones defined in React application
r.Path("/mods").
Methods("GET").
Name("Mods").
Handler(http.StripPrefix("/mods", http.FileServer(http.Dir("./app/"))))
r.Path("/saves").
Methods("GET").
Name("Saves").
Handler(http.StripPrefix("/saves", http.FileServer(http.Dir("./app/"))))
r.Path("/logs").
Methods("GET").
Name("Logs").
Handler(http.StripPrefix("/logs", http.FileServer(http.Dir("./app/"))))
r.PathPrefix("/").
Methods("GET").
Name("Index").
@ -56,6 +69,21 @@ var apiRoutes = Routes{
"GET",
"/mods/toggle/{mod}",
ToggleMod,
}, {
"UploadMod",
"POST",
"/mods/upload",
UploadMod,
}, {
"RemoveMod",
"GET",
"/mods/rm/{mod}",
RemoveMod,
}, {
"DownloadMod",
"GET",
"/mods/dl/{mod}",
DownloadMod,
}, {
"ListSaves",
"GET",
@ -66,6 +94,16 @@ var apiRoutes = Routes{
"GET",
"/saves/dl/{save}",
DLSave,
}, {
"UploadSave",
"POST",
"/saves/upload",
UploadSave,
}, {
"RemoveSave",
"GET",
"/saves/rm/{save}",
RemoveSave,
}, {
"LogTail",
"GET",

View File

@ -1,8 +1,12 @@
package main
import (
"errors"
"fmt"
"io/ioutil"
"log"
"os"
"strings"
"time"
)
@ -12,15 +16,18 @@ type Save struct {
Size int64 `json:"size"`
}
func (s Save) String() string {
return fmt.Sprintf("%s", s.Name)
}
// Lists save files in factorio/saves
func listSaves() []Save {
saveDir := config.FactorioDir + "/saves"
func listSaves(saveDir string) ([]Save, error) {
result := []Save{}
files, err := ioutil.ReadDir(saveDir)
if err != nil {
log.Printf("Error listing save directory: %s", err)
return result
return result, err
}
for _, f := range files {
@ -28,5 +35,38 @@ func listSaves() []Save {
result = append(result, save)
}
return result
return result, nil
}
func rmSave(saveName string) error {
removed := false
if saveName == "" {
return errors.New("No save name provided")
}
saves, err := listSaves(config.FactorioSavesDir)
if err != nil {
log.Printf("Error in remove save: %s", err)
return err
}
for _, save := range saves {
log.Printf("Checking if %s in %s", save, saveName)
if strings.Contains(save.Name, saveName) {
err := os.Remove(config.FactorioSavesDir + "/" + save.Name)
if err != nil {
log.Printf("Error removing save %s: %s", saveName, err)
return err
}
log.Printf("Deleted save: %s", save)
removed = true
}
}
if !removed {
log.Printf("Did not remove save: %s", saveName)
return errors.New(fmt.Sprintf("Did not remove save: %s", saveName))
}
return nil
}

View File

@ -1,7 +1,45 @@
import React from 'react';
class InstalledMods extends React.Component {
componentDidMount() {
this.uploadFile = this.uploadFile.bind(this);
this.removeMod = this.removeMod.bind(this);
}
updateInstalledMods() {
this.props.loadInstalledModList();
}
uploadFile(e) {
var fd = new FormData();
fd.append('modfile', this.refs.file.files[0]);
$.ajax({
url: "/api/mods/upload",
type: "POST",
data: fd,
processData: false,
contentType: false,
success: (data) => {
alert(data)
}
});
e.preventDefault();
this.updateInstalledMods();
}
removeMod(i) {
$.ajax({
url: "/api/mods/rm/" + this.props.installedMods[i],
success: (data) => {
alert(data)
}
});
this.updateInstalledMods();
}
render() {
return(
<div className="box">
<div className="box-header">
@ -9,11 +47,53 @@ class InstalledMods extends React.Component {
</div>
<div className="box-body">
{this.props.installedMods.map ( (mod, i) => {
return(
<p>{mod}</p>
)
})}
<form ref="uploadForm" className="form" encType='multipart/form-data'>
<fieldset>
<span className="btn btn-default btn-file">
<input ref="file" type="file" name="modfile" id="modfile" />
</span>
<input type="button" ref="button" value="Upload" onClick={this.uploadFile} />
</fieldset>
</form>
<div className="table-responsive">
<table className="table table-striped">
<thead>
<tr>
<th>Mod Name</th>
<th>Download</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
{this.props.installedMods.map ( (mod, i) => {
var saveLocation = "/api/mods/dl/" + mod;
return(
<tr key={i}>
<td>
{mod}
</td>
<td>
<a className="btn btn-default" href={saveLocation}>Download</a>
</td>
<td>
<button
className="btn btn-danger btn-small"
ref="modInput"
type="button"
onClick={this.removeMod.bind(this, i)}>
<i className="fa fa-trash"></i>
&nbsp;
Delete
</button>
</td>
</tr>
)
})}
</tbody>
</table>
</div>
</div>
</div>
)
@ -21,7 +101,8 @@ class InstalledMods extends React.Component {
}
InstalledMods.propTypes = {
installedMods: React.PropTypes.array.isRequired
installedMods: React.PropTypes.array.isRequired,
loadInstalledModList: React.PropTypes.func.isRequired
}
export default InstalledMods

View File

@ -2,10 +2,6 @@ import React from 'react';
import Mod from './Mod.jsx'
class ModList extends React.Component {
componentDidMount() {
console.log(this.props.listMods);
}
render() {
return(
<div className="box">
@ -14,6 +10,7 @@ class ModList extends React.Component {
</div>
<div className="box-body">
<h4>Upload Mod</h4>
<div className="table-responsive">
<table className="table table-striped">
<thead>

View File

@ -7,6 +7,7 @@ class ModsContent extends React.Component {
super(props);
this.componentDidMount = this.componentDidMount.bind(this);
this.toggleMod = this.toggleMod.bind(this);
this.loadInstalledModList = this.loadInstalledModList.bind(this);
this.state = {
installedMods: [],
listMods: []
@ -75,6 +76,7 @@ class ModsContent extends React.Component {
<InstalledMods
{...this.state}
loadInstalledModList={this.loadInstalledModList}
/>
<ModList
{...this.state}

View File

@ -17,13 +17,27 @@ class Save extends React.Component {
<td>
<a className="btn btn-default" href={saveLocation}>Download</a>
</td>
</tr>
<td>
<button
className="btn btn-danger btn-small"
ref="saveInput"
type="button"
onClick={this.props.removeSave.bind(this, this.props.saves[this.props.index].name)}>
<i className="fa fa-trash"></i>
&nbsp;
Delete
</button>
</td>
</tr>
)
}
}
Save.propTypes = {
save: React.PropTypes.object.isRequired
save: React.PropTypes.object.isRequired,
saves: React.PropTypes.array.isRequired,
index: React.PropTypes.number.isRequired,
removeSave: React.PropTypes.func.isRequired
}
export default Save

View File

@ -2,6 +2,46 @@ import React from 'react';
import Save from './Save.jsx';
class SavesList extends React.Component {
constructor(props) {
super(props);
this.updateSavesList = this.updateSavesList.bind(this);
this.uploadFile = this.uploadFile.bind(this);
this.removeSave = this.removeSave.bind(this);
}
updateSavesList () {
this.props.getSaves();
}
uploadFile(e) {
var fd = new FormData();
fd.append('savefile', this.refs.file.files[0]);
$.ajax({
url: "/api/saves/upload",
type: "POST",
data: fd,
processData: false,
contentType: false,
success: (data) => {
alert(data)
}
});
e.preventDefault();
this.updateSavesList();
}
removeSave(saveName, e) {
console.log(e, saveName);
$.ajax({
url: "/api/saves/rm/" + saveName,
success: (data) => {
alert(data)
}
})
this.updateSavesList();
}
render() {
return(
<div className="box">
@ -10,6 +50,16 @@ class SavesList extends React.Component {
</div>
<div className="box-body">
<form ref="uploadForm" className="form" encType='multipart/form-data'>
<fieldset>
<span className="btn btn-default btn-file">
<input ref="file" type="file" name="modfile" id="modfile" />
</span>
<input type="button" ref="button" value="Upload" onClick={this.uploadFile} />
</fieldset>
</form>
<div className="table-responsive">
<table className="table table-striped">
<thead>
@ -25,7 +75,10 @@ class SavesList extends React.Component {
return(
<Save
key={i}
saves={this.props.saves}
index={i}
save={save}
removeSave={this.removeSave}
/>
)
})}
@ -40,7 +93,8 @@ class SavesList extends React.Component {
SavesList.propTypes = {
saves: React.PropTypes.array.isRequired,
dlSave: React.PropTypes.func.isRequired
dlSave: React.PropTypes.func.isRequired,
getSaves: React.PropTypes.func.isRequired
}
export default SavesList

View File

@ -5,6 +5,7 @@ class SavesContent extends React.Component {
constructor(props) {
super(props);
this.dlSave = this.dlSave.bind(this);
this.getSaves = this.getSaves.bind(this);
this.state = {
saves: []
}
@ -59,6 +60,7 @@ class SavesContent extends React.Component {
<SavesList
{...this.state}
dlSave={this.dlSave}
getSaves={this.getSaves}
/>