aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMitch Riedstra <mitch@riedstra.us>2021-03-19 21:19:42 -0400
committerMitch Riedstra <mitch@riedstra.us>2021-03-19 21:19:42 -0400
commit0e62a3b46b25e7c101b14ed44235f3c276982fc0 (patch)
treefe37304d6e36dcb5ebe1e921a20795c38d476165
parent1e435e039d95ad8834dc3f3cd244ff87d5624c73 (diff)
downloadsteam-export-0e62a3b46b25e7c101b14ed44235f3c276982fc0.tar.gz
steam-export-0e62a3b46b25e7c101b14ed44235f3c276982fc0.tar.xz
Sweeping changes to switch to bootstrap and make the UI overall a bit more user friendly
-rw-r--r--cmd/web/app.go4
-rw-r--r--cmd/web/handlers.go11
-rw-r--r--cmd/web/main.go16
-rw-r--r--cmd/web/static/main.js14
-rw-r--r--cmd/web/static/style.css81
-rw-r--r--cmd/web/templates/index.html466
-rw-r--r--readme.md9
-rw-r--r--steam/steam.go8
8 files changed, 429 insertions, 180 deletions
diff --git a/cmd/web/app.go b/cmd/web/app.go
index 8f6df89..b0749b5 100644
--- a/cmd/web/app.go
+++ b/cmd/web/app.go
@@ -1,8 +1,8 @@
package main
import (
- "sync"
"html/template"
+ "sync"
"time"
"riedstra.dev/mitch/steam-export/steam"
@@ -23,7 +23,6 @@ type statusInfo struct {
Start *time.Time
}
-
type App struct {
Library *steamLib
Status *statusInfo
@@ -72,4 +71,3 @@ func (a *App) LibraryReload() {
a.LibrarySet(cur)
return
}
-
diff --git a/cmd/web/handlers.go b/cmd/web/handlers.go
index b8fa705..b9b863a 100644
--- a/cmd/web/handlers.go
+++ b/cmd/web/handlers.go
@@ -1,13 +1,13 @@
package main
import (
- "fmt"
- "strings"
- "net/url"
- "os"
"encoding/json"
+ "fmt"
"io"
"net/http"
+ "net/url"
+ "os"
+ "strings"
"time"
"github.com/gorilla/mux"
@@ -197,11 +197,10 @@ func (a *App) HandleSetLib(w http.ResponseWriter, r *http.Request) {
func HandleQuit(w http.ResponseWriter, r *http.Request) {
Logger.Println("Quit was called, exiting")
w.Header().Add("Content-type", "text/plain")
- w.Write([]byte("Shutting down..."))
+ w.Write([]byte("Shutting down... feel free to close this"))
go func() {
time.Sleep(time.Second * 2)
os.Exit(0)
}()
return
}
-
diff --git a/cmd/web/main.go b/cmd/web/main.go
index 45b64ae..da0bebd 100644
--- a/cmd/web/main.go
+++ b/cmd/web/main.go
@@ -5,10 +5,10 @@ import (
"flag"
"fmt"
"log"
- "math/rand"
"net/http"
"os"
- "time"
+ "strconv"
+ "strings"
"github.com/gorilla/mux"
)
@@ -66,8 +66,16 @@ func main() {
err = s.ListenAndServe()
if err != nil {
Logger.Printf("Encountered: %s", err)
- rand.Seed(time.Now().UnixNano())
- Listen = fmt.Sprintf(":%d", rand.Intn(9000)+1024)
+
+ parts := strings.Split(Listen, ":")
+ port, err := strconv.Atoi(parts[1])
+ if err != nil {
+ panic(err)
+ }
+ port += 1
+
+ Listen = fmt.Sprintf("%s:%d", parts[0], port)
+
Logger.Printf("Trying: %s", Listen)
s.Addr = Listen
}
diff --git a/cmd/web/static/main.js b/cmd/web/static/main.js
index 55edf0c..b793be4 100644
--- a/cmd/web/static/main.js
+++ b/cmd/web/static/main.js
@@ -22,7 +22,8 @@ function formatDuration(dur) {
}
function setStatus(stat) {
- var elem = document.getElementById("installBar");
+ var barContainer = document.getElementById("installBarContainer");
+ var elem = document.getElementById("installBar");
var msg = document.getElementById("message");
// console.log(stat)
@@ -42,7 +43,9 @@ function setStatus(stat) {
elem.style.width = percent + "%";
elem.innerHTML = percent + "%";
- elem.style.display = "";
+ barContainer.style.display = "";
+
+ elem.ariaValueNow = percent;
// in seconds
var eta = Math.round(((stat.Size - trans)/1024/1024) / rate);
@@ -56,8 +59,11 @@ function setStatus(stat) {
if(!stat.Running && stat.Transferred >= 50 && stat.Error == null) {
elem.style.width = 100 + "%";
elem.innerHTML = 100 + "%";
+ elem.ariaValueNow = 100;
+
msg.innerHTML = "Completed install for: " + stat.Url;
- elem.style.display = "";
+
+ barContainer.style.display = "";
msg.style.display = "";
}
@@ -65,7 +71,7 @@ function setStatus(stat) {
msg.innerHTML = "Errors encountered while installing from: \n\n"
+ stat.Url + "\n\n" +
JSON.stringify(stat.Error, undefined, 2);
- elem.style.display = "hidden";
+ barContainer.style.display = "hidden";
msg.style.display = "";
}
}
diff --git a/cmd/web/static/style.css b/cmd/web/static/style.css
deleted file mode 100644
index ba648e3..0000000
--- a/cmd/web/static/style.css
+++ /dev/null
@@ -1,81 +0,0 @@
-nav {
- border-bottom: 3px solid #000;
- padding-bottom: 10px;
-}
-a {
- text-decoration: none;
- color: #268bd2;
-}
-a:visited {
- color: #d22653;
-}
-a:hover {
- text-decoration: underline;
-}
-pre code {
- line-height: 1.1;
- overflow: auto;
-}
-
-code {
- color: #9672d5;
- font-size: .8em;
- font-family: "Roboto Mono", "Monaco", "Lucida Console", "DejaVu Sans Mono",
-"monospace";
-}
-
-pre code {
- color: #000;
- background-color: #FFFFEA;
- display: block;
- padding: 10px;
- border: 1px solid;
-}
-
-blockquote {
- border-left: 4px solid #aaa;
- padding-left: 1em;
-}
-
-body {
- margin: 40px auto;
- max-width: 80%;
- line-height: 1.6;
- font-size: 1em;
- color: #444;
- padding: 0 10px;
- /* Added because some browsers don't default to white */
- background-color: #fff;
-}
-
-img {
- width: 100%;
- height: auto;
-}
-
-h1,h2,h3 {
- line-height: 1.2
-}
-
-@media screen and (min-width: 1200px) {
- body {
- max-width: 960px;
- }
-}
-
-
-#status {
- width: 100%;
- background-color: #ddd;
-}
-
-.installBar {
- width: 0%;
- height: 30px;
- /* background-color: #4CAF50; */
- background-color: #268bd2;
- text-align: center;
- line-height: 30px;
- color: white;
-}
-
diff --git a/cmd/web/templates/index.html b/cmd/web/templates/index.html
index 74f2298..eaaf722 100644
--- a/cmd/web/templates/index.html
+++ b/cmd/web/templates/index.html
@@ -2,115 +2,417 @@
<html lang="en">
<head>
<meta charset="UTF-8">
- <link id="maincss" rel="stylesheet" href="/static/style.css" defer>
+ <link href="/static/css/bootstrap.min.css" rel="stylesheet" defer>
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Steam Game index</title>
</head>
<body>
-<nav>
- <a href="/">Home</a>
- {{ if .Local }}
- <div style="display: block; float: right;">
- <a href="/quit">Shutdown Server / Quit</a>
- </div>
- {{ end }}
+<nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top">
+ <div class="container-fluid">
+ <a class="navbar-brand" href="/">Steam Exporter</a>
+ <button class="navbar-toggler"
+ type="button"
+ data-bs-toggle="collapse"
+ data-bs-target="#navbarsExampleDefault"
+ aria-controls="navbarsExampleDefault"
+ aria-expanded="false"
+ aria-label="Toggle navigation">
+ <span class="navbar-toggler-icon"></span>
+ </button>
+
+ <div class="collapse navbar-collapse" id="navbarsExampleDefault">
+ <ul class="navbar-nav me-auto mb-2 mb-md-0">
+ <li class="nav-item">
+ <a class="nav-link" aria-current="page"
+ href="/steam-export-web.exe">
+ Download
+ </a>
+ </li>
+ <li class="nav-item">
+ <a href="#"
+ id="shareLink"
+ data-clipboard-text="http://{{$.HostIP}}:{{$.Port}}/"
+ class="nav-link">
+ Copy Share link
+ </a>
+ </li>
+ {{ if .Local }}
+ <li class="nav-item">
+ <a class="nav-link" aria-current="page"
+ data-bs-target="#installModal"
+ data-bs-toggle="modal"
+ href="#">
+ Install Game
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" aria-current="page"
+ data-bs-target="#libraryModal"
+ data-bs-toggle="modal"
+ href="#">
+ Change Library Path
+ </a>
+ </li>
+ {{ end }}
+ </ul>
+
+ <ul class="navbar-nav ">
+ {{ if .Local }}
+ <li class="nav-item">
+ <a class="nav-link" href="#"
+ data-bs-target="#versionModal"
+ data-bs-toggle="modal"
+ >
+ Version
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="/quit">Shutdown Server/Quit</a>
+ </li>
+ {{ end }}
+ </ul>
+ </div>
+ </div>
</nav>
-{{ if .Local }}
-<script src="/static/main.js"></script>
-<h2>Library: {{.Lib.Folder}}</h2>
+<div class="container" style="padding-top: 6rem;">
-<div id="status">
- <div id="installBar" class="installBar" style="display: none;">0%</div>
+<div class="position-fixed bottom-0 end-0 p-3" >
+ <div id="notificationCopied" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
+ <div class="toast-body" id="notificationCopiedText"></div>
+ </div>
</div>
-<pre><code id="message" style="display: none;">
-</code></pre>
-<br />
-
-<h3>About</h3>
-<p>
-The steam exporter is designed to let you export your steam games, either to
-another local hard drive or another computer on the network.
-</p>
-<p>
-It also allows you to import games from across the network as well if you
-provide an HTTP url from which to download the game file as exported
-from this application.
-</p>
-<p>
-<a href="/steam-export-web.exe">
- You can download this application from this UI as well here.
-</a>
-</p>
-
-<p>
-You can give people this link to view the library remotely and download
-games from your computer:
-<br /><br />
-<a href="http://{{.HostIP}}:{{.Port}}/">http://{{.HostIP}}:{{.Port}}/</a>
-</p>
+{{ if .Local }}
+ <script src="/static/main.js"></script>
+ <h2>Library: {{.Lib.Folder}}</h2>
+
+ <div class="row">
+ <div id="installBarContainer" class="progress" style="display: none;">
+ <div id="installBar" class="progress-bar progress-bar-striped progress-bar-animated"
+ role="progressbar"
+ aria-valuenow="0"
+ aria-valuemin="0"
+ aria-valuemax="100"
+ style="width: 0%;">
+ </div>
+ </div>
+ </div>
+
+ <div class="row" style="padding-top: 10px;">
+ <pre><code id="message" style="display: none;"></code></pre>
+ </div>
+
+ <h3>About</h3>
+ <p>
+ The steam exporter is designed to let you export your steam games, either to
+ another local hard drive or another computer on the network.
+ </p>
+ <p>
+ It also allows you to import games from across the network as well if you
+ provide an HTTP url from which to download the game file as exported
+ from this application.
+ </p>
+ <p>
+ <a href="/steam-export-web.exe">
+ You can download this application from this UI as well here.
+ </a>
+ </p>
+
+ <p>
+ You can give people this link to view the library remotely and download
+ games from your computer:
+ <br /><br />
+ <a href="http://{{.HostIP}}:{{.Port}}/">http://{{.HostIP}}:{{.Port}}/</a>
+ </p>
{{ else }}
-<h2>Remote Steam library access</h2>
+
+ <h2>Remote Steam library access</h2>
+
+ <p>
+ <a href="/steam-export-web.exe">
+ If you need this program to install the games click here.
+ </a>
+ </p>
+
+{{ end }}
-<a href="/steam-export-web.exe">
- If you need this program to install the games click here.
-</a>
+<h3>Installed games:</h3>
-<p>
-Right click and copy the link address to paste into your local machine
-if you do not wish to store the archive or have enough space for it on
-your drive.
-</p>
-{{ end }}
+<div class="container">
+ <div class="row">
+ <div class="col-3">
+ <input class="form-control" type="text" id="gameTableSearch" onkeyup="gameTableFilter()" placeholder="Filter by name">
+ </div>
+ <div class="col-3">
+ <button class="btn btn-secondary" onClick="clearGameTableSearch()">Clear</button>
+ </div>
+ </div>
+
+ <table id="gameTable" class="table dark table-sm table-striped text-center">
+ <thead>
+ <tr>
+ <th scope="col">Name</th>
+ <th scope="col">Size</th>
+ <th scope="col" data-sort-method="none">Actions</th>
+ </tr>
+ </thead>
+ <tbody>
+ {{ range $key, $val := .Lib.Games}}
+ <tr>
-<p>
-Installed games:
+ <td>{{$key}}</td>
+ <td>{{$val.GetSize}}</td>
+ <td>
+ <a href="/download/{{$key}}" class="btn btn-secondary">Download</a>
+ <button data-clipboard-text="http://{{$.HostIP}}:{{$.Port}}/download/{{$key}}" class="btn btn-primary">Copy link</button>
+ <button data-bs-target="#delete{{$val.Slug}}Modal" data-bs-toggle="modal" class="btn btn-danger">Delete</button>
+ </td>
+ </tr>
+ {{ end }}
+ </tbody>
+ </table>
+</div>
+
+{{ if .Local }}
-Tip: You can right click and save link as to specify a save location, e.g.
-an external hard drive
-</p>
-<ul>
{{ range $key, $val := .Lib.Games }}
-<li>
-<a href="/download/{{$key}}">{{$key}} ({{$val.GetSize}})</a>
-</li>
+<div class="modal fade" id="delete{{$val.Slug}}Modal" tabindex="-1" aria-labelledby="delete{{$val.Slug}}ModalLabel" aria-hidden="true">
+
+ <div class="modal-dialog modal-dialog-centered modal-dialog-scrollable">
+ <div class="modal-content">
+ <div class="modal-header">
+ <h5 class="modal-title" id="delete{{$val.Slug}}ModalLabel">
+ Delete {{$key}}
+ </h5>
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
+ </div>
+ <div class="modal-body">
+
+
+ <form class="row row-cols-lg-auto g-3 align-items-center" action="/delete" method="POST">
+ <div class="col-12">
+ <div class="input-group">
+ <div class="input-group-text">
+ Are you sure you wish to delete '{{$key}}'?
+ </div>
+ <input type="hidden" name="name" value= "{{$key}}" />
+ </div>
+ </div>
+
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
+ Close
+ </button>
+ <input type="submit" class="btn btn-danger" value="Delete" />
+ </div>
+ </form>
+ </div>
+ </div>
+</div>
+
{{ end }}
-</ul>
-{{ if .Local }}
-Delete a game: ( Type out exact name, case sensitive )
-<form action="/delete" method="POST">
- <input type="text" name="name" />
- <input type="submit" value="Delete">
-</form>
+<div class="modal fade" id="installModal" tabindex="-1" aria-labelledby="installModalLabel" aria-hidden="true">
+
+ <div class="modal-dialog modal-dialog-centered modal-dialog-scrollable">
+ <div class="modal-content">
+ <div class="modal-header">
+ <h5 class="modal-title" id="installModalLabel">
+ Install Game
+ </h5>
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
+ </div>
+ <div class="modal-body">
+
-Install a game from a URL or local file path:
+ <form class="row g-3 align-items-center" action="/install" method="GET">
+ <div class="col-12">
+ <label class="visually-hidden" for="inlineFormInputInstallModal">
+ C:\Program Files (x86)\Steam\Steamapps
+ </label>
+ <div class="input-group">
+ <div class="input-group-text">URI:</div>
+ <input name="uri"
+ type="text"
+ class="form-control"
+ id="inlineFormInputInstallModal"
+ placeholder="https://10.0.0.123:8899/download/Some Game">
+ </div>
+ </div>
-<form action="/install" method="GET">
- <input type="text" name="uri" />
- <input type="submit" value="Install">
-</form>
+ <p>
+ Enter a URI above, typically this will be another machine on your local network.
+ </p>
-<p>
-Note that You can also give someone a URL to install a game if they're running
-this program, e.g. <br />
-http://127.0.0.1:8899/install?uri=http://my-server-ip-or-hostname/download/My Game
-</p>
+ <p>
+ The URI should return a stream with the Game's data, typically this
+ is provided by running another copy of this program on the computer that has
+ the game already installed.
+ </p>
+ <p>
+ Simply click "copy link" over there and paste it in here to install.
+ </p>
+
+ <p>
+ This will force a reload of the entire steam library when complete.
+ </p>
+
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
+ Close
+ </button>
+ <input type="submit" class="btn btn-primary" value="Submit" />
+ </div>
+ </form>
+ </div>
+ </div>
+</div>
+
+<!-- Modal -->
+<div class="modal fade" id="libraryModal" tabindex="-1" aria-labelledby="libraryModalLabel" aria-hidden="true">
+
+ <div class="modal-dialog modal-dialog-centered modal-dialog-scrollable">
+ <div class="modal-content">
+ <div class="modal-header">
+ <h5 class="modal-title" id="libraryModalLabel">
+ Change Library Path
+ </h5>
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
+ </div>
+ <div class="modal-body">
+
+
+ <form class="row g-3 align-items-center" action="/setLib" method="GET">
+ <div class="col-12">
+ <label class="visually-hidden" for="inlineFormInputlibraryModal">
+ C:\Program Files (x86)\Steam\Steamapps\common
+ </label>
+ <div class="input-group">
+ <div class="input-group-text">Path:</div>
+ <input name="path"
+ type="text"
+ class="form-control"
+ id="inlineFormInputlibraryModal"
+ placeholder="C:\Program Files (x86)\Steam\Steamapps\common">
+ </div>
+ </div>
+
+ <p>
+ Typically you will not need to change this, however if you have
+ installed steam games on multiple hard drives you may need to.
+ </p>
+
+ <p>
+ This will force a reload of the entire steam library.
+ </p>
+
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
+ Close
+ </button>
+ <input type="submit" class="btn btn-primary" value="Submit" />
+ </div>
+ </form>
+ </div>
+ </div>
+</div>
-Change library path
-<form action="/setLib" method="GET">
- <input type="text" name="path" />
- <input type="submit" value="Update">
-</form>
{{ end }}
-<h3>Version information</h3>
-<pre><code>{{.Version}}</pre></code>
+<div class="modal fade" id="versionModal" tabindex="-1"
+ aria-labelledby="versionModalLabel" aria-hidden="true">
+
+ <div class="modal-dialog modal-dialog-centered modal-dialog-scrollable">
+ <div class="modal-content">
+ <div class="modal-header">
+ <h5 class="modal-title" id="versionModalLabel">
+ Version Information
+ </h5>
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
+ </div>
+ <div class="modal-body">
+
+ <pre><code>{{.Version}}</pre></code>
+
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
+ Close
+ </button>
+ </div>
+ </div>
+ </div>
+</div>
+
+
+</div> <!-- /container -->
+
+<script src="static/js/bootstrap.min.js"></script>
+<script src="static/js/clipboard.min.js"></script>
+<script src="static/js/tablesort/tablesort.min.js"></script>
+<script src="static/js/tablesort/sorts/tablesort.filesize.min.js"></script>
+<script>
+function clipboardNotify(e) {
+/*
+console.log("Action:", e.action)
+console.log("Text:", e.text)
+console.log("Trigger:", e.trigger)
+*/
+
+var elem = document.getElementById("notificationCopied")
+var toast = new bootstrap.Toast(elem)
+
+var content = document.getElementById("notificationCopiedText")
+content.innerHTML = "Copied: " + e.text
+
+toast.show()
+}
+</script>
+<script>
+ clip = new ClipboardJS('.btn');
+ clip1 = new ClipboardJS('#shareLink');
+
+ clip.on('success', clipboardNotify);
+ clip1.on('success', clipboardNotify);
+
+ new Tablesort(document.getElementById("gameTable"), {
+ })
+</script>
+<script>
+</script>
+<script>
+ function clearGameTableSearch() {
+ document.getElementById("gameTableSearch").value=""
+ gameTableFilter()
+ }
+ function gameTableFilter() {
+ var input, filter, table, tr, td, i, txtValue;
+ input = document.getElementById("gameTableSearch");
+ filter = input.value.toUpperCase();
+ table = document.getElementById("gameTable");
+ tr = table.getElementsByTagName("tr");
+ for (i = 0; i < tr.length; i++) {
+ td = tr[i].getElementsByTagName("td")[0];
+ if (td) {
+ txtValue = td.textContent || td.innerText;
+ if (txtValue.toUpperCase().indexOf(filter) > -1) {
+ tr[i].style.display = "";
+ } else {
+ tr[i].style.display = "none";
+ }
+ }
+ }
+ }
+</script>
</body>
diff --git a/readme.md b/readme.md
index 2c9ebbb..7d972c0 100644
--- a/readme.md
+++ b/readme.md
@@ -38,3 +38,12 @@ Screenshots:
## Building
On Windows I'm using https://www.msys2.org/ for my development environment.
+
+
+### External Tools
+
+Mostly for the frontend:
+
+ * [Bootstrap](https://getbootstrap.com/)
+ * [Clipboard.js](https://clipboardjs.com/)
+ * [Tablesort](http://tristen.ca/tablesort/demo/)
diff --git a/steam/steam.go b/steam/steam.go
index 24e4c54..aca20df 100644
--- a/steam/steam.go
+++ b/steam/steam.go
@@ -9,6 +9,7 @@ import (
"io/ioutil"
"os"
"path/filepath"
+ "regexp"
"strings"
)
@@ -23,6 +24,13 @@ type Game struct {
Size int64
}
+var slugregexp = regexp.MustCompile(`[^-0-9A-Za-z_:.]`)
+
+func (g Game) Slug() string {
+ // return strings.ReplaceAll(g.Name, " ", "-")
+ return slugregexp.ReplaceAllString(g.Name, "-")
+}
+
func ProcessMultipleLibraries(r []string) ([]*Library, error) {
var libs []*Library
for _, i := range r {