aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/vgstash/web/vgstash-favicon.pngbin0 -> 324 bytes
-rw-r--r--src/vgstash/web/vgstash-web.html46
-rw-r--r--src/vgstash/web/vgstash.css333
-rw-r--r--src/vgstash/web/vgstash.js278
4 files changed, 657 insertions, 0 deletions
diff --git a/src/vgstash/web/vgstash-favicon.png b/src/vgstash/web/vgstash-favicon.png
new file mode 100644
index 0000000..5456f3b
--- /dev/null
+++ b/src/vgstash/web/vgstash-favicon.png
Binary files differ
diff --git a/src/vgstash/web/vgstash-web.html b/src/vgstash/web/vgstash-web.html
new file mode 100644
index 0000000..d5b6bdc
--- /dev/null
+++ b/src/vgstash/web/vgstash-web.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <title>vgstash for {{USERNAME}}</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf8">
+ <link rel="stylesheet" href="./vgstash.css" type="text/css">
+ <link rel="icon" href="./vgstash-favicon.png" type="image/png">
+ <script src="./vgstash.js"></script>
+ </head>
+ <body>
+ <header id="top">
+ <h1><em>vgstash</em> for {{USERNAME}}</h1>
+ <p id="help" class="help" tabindex="0" title="Help">?</p>
+ </header>
+ <div class="notice">
+ <p>The <strong>vgstash</strong> webview is currently in development, and is slated for the </code>v0.4</code> release. As such, consider this page a <em>work in progress</em>. Thanks. <a href="#" onclick="show_contact();">Issues?</a>
+ </div>
+ <nav>
+ <ul id="filter_list">
+ <li>All</li>
+ <li title="Games marked New or Playing">Backlog</li>
+ <li title="Track the age of games in your backlog">Backlog Age</li>
+ <li title="Games marked Beaten">Beaten</li>
+ <li title="Games marked Complete">Complete</li>
+ <li title="Games I own digitally">Digital</li>
+ <li title="Games marked Beaten or Complete">Done</li>
+ <li title="Games belonging to a collection">Members</li>
+ <li title="Games with notes">Notes</li>
+ <li title="Games that I own somehow">Owned</li>
+ <li title="Games that I own physically">Physical</li>
+ <li title="Games marked Playing">Playlog</li>
+ </ul>
+ </nav>
+ <div id="vgs_view">
+ <!-- script insertion point -->
+ </div>
+ <!-- modal box -->
+ <div id="modal" class="backdrop">
+ <section id="modal_box" class="modal_box">
+ </section>
+ </div>
+ <footer>
+ <a href="https://pypi.org/project/vgstash">VGStash</a> &copy; 2016-2025 <a href="https://zlg.space/">zlg</a>. Licensed under <a href="https://www.gnu.org/licenses/agpl-3.0.html">AGPL 3.0</a>.
+ </footer>
+ </body>
+</html>
diff --git a/src/vgstash/web/vgstash.css b/src/vgstash/web/vgstash.css
new file mode 100644
index 0000000..8d45958
--- /dev/null
+++ b/src/vgstash/web/vgstash.css
@@ -0,0 +1,333 @@
+:root {
+ --main-bg-color: #152c0e;
+ --main-fg-color: #fff;
+ --header-color: #fff;
+ --header-highlight-color: #fff;
+ --notice-box-bg-color: #eceb13;
+ --notice-box-fg-color: var(--main-bg-color);
+ --button-bg-color: #1a5806;
+ --button-fg-color: #fff;
+ --hover-button-bg-color: #acd;
+ --hover-button-fg-color: #125;
+ --selected-button-bg-color: #000;
+ --selected-button-fg-color: #ffff6c;
+ --modal-bg-color: #dfdfdf;
+ --modal-fg-color: var(--main-bg-color);
+ --table-text-color: var(--main-bg-color);
+ --table-spacing-color: #fff;
+ --table-head-bg-color: #eceb13;
+ --table-head-fg-color: var(--table-text-color);
+ --table-even-bg-color: #dfdfdf;
+ --table-even-fg-color: var(--table-text-color);
+ --table-odd-bg-color: #b4b4b4;
+ --table-odd-fg-color: var(--table-text-color);
+ --note-link-color: var(--table-text-color);
+ --row-hover-bg-color: #f198f0;
+ --row-hover-fg-color: #882250;
+ --row-hover-link-color: var(--row-hover-fg-color);
+ --footer-fg-color: var(--main-fg-color);
+ --footer-link-color: #7cf;
+}
+
+/* MODIFY VARIABLES ABOVE THIS LINE */
+
+html {
+ font-size: 16px;
+ font-family: "Fira Sans", "Liberation Sans", "Arial", sans-serif;
+ margin: 0;
+ padding: 0;
+ background: var(--main-bg-color);
+ color: #fff;
+}
+
+body {
+ font-size: 62.5%;
+ margin: 1.6em auto;
+ padding: 0;
+ width: 90%;
+}
+
+#top {
+ margin-bottom: 1.5em;
+ min-height: 2.8em;
+}
+
+#top h1 {
+ color: var(--header-color);
+ margin: 0;
+ padding: 0;
+ line-height: 1.3;
+ font-size: 2.2em;
+ float: left;
+}
+
+#top h1 em {
+ background: var(--header-highlight-color);
+ color: var(--main-bg-color);
+ padding: .15em .2em;
+ border-radius: 0.3em;
+ font-style: normal;
+}
+
+#top p {
+ float: right;
+}
+
+.help {
+ display: inline-block;
+ border: 1px solid #6ce;
+ color: #fff;
+ cursor: pointer;
+ background: #39B;
+ padding: .1em .2em;
+ margin: 0;
+ border-radius: 1em;
+ width: .9em;
+ height: 1.1em;
+ text-align: center;
+ text-shadow: 1px 1px 1px #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000;
+ text-decoration: none;
+ font-size: 1.8em;
+ font-weight: bold;
+}
+
+.notice {
+ border-radius: .5em;
+ background: var(--notice-box-bg-color);
+ color: var(--notice-box-fg-color);
+ padding: .2em .5em;
+ clear: both;
+}
+
+.notice p {
+ margin: .5em;
+ padding: 0;
+ font-size: 1.5em;
+}
+
+nav {
+ margin: 1em 0;
+ padding: 0;
+ /* background: #000; */
+ clear: both;
+}
+
+nav ul {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ display: block;
+}
+
+nav ul li {
+ display: inline-block;
+ background: var(--button-bg-color);
+ box-shadow: 0 0 .1em rgba(5,0,10,0.7);
+ color: var(--button-fg-color);
+ font-size: 1.8em;
+ font-weight: bold;
+ text-decoration: none;
+ border-radius: .4em;
+ margin: .1em .2em;
+ padding: 0.1em 0.4em;
+}
+
+nav ul li:hover,
+nav ul li.selected:hover {
+ cursor: pointer;
+ background: var(--hover-button-bg-color);
+ color: var(--hover-button-fg-color);
+}
+
+nav ul li.selected {
+ background: var(--selected-button-bg-color);
+ color: var(--selected-button-fg-color);
+}
+
+table {
+ font-size: 1em;
+}
+
+#modal {
+ display: none;
+}
+
+.backdrop {
+ position: fixed;
+ left: 0;
+ right; 0;
+ top: 0;
+ left: 0;
+ background: rgba(30,30,30,0.8);
+ margin: 0;
+ padding: 0;
+ width: 100%;
+ height: 100%;
+ z-index: 1;
+ overflow: auto;
+}
+
+#modal_box {
+ background: var(--modal-bg-color);
+ color: var(--modal-fg-color);
+ border-radius: 1.3em;
+ width: 85%;
+ margin: 3em auto;
+ padding: 1em 0;
+ font-size: 1.6em;
+}
+
+#modal_box > div {
+ padding: 0 1em;
+ margin: 0;
+ /* overflow: auto; */
+}
+
+#modal_close {
+ background: #d30;
+ border-radius: 0.7em;
+ border: 1px solid #e76;
+ box-shadow: 0 0.1em 0.2em #000;
+ color: #fff;
+ cursor: pointer;
+ display: block;
+ float: right;
+ font-size: 2.4em;
+ line-height: 0.8;
+ margin: 0;
+ padding: 0;
+ position: relative;
+ right: -0.4em;
+ text-align: center;
+ text-decoration: none;
+ text-shadow: 1px 1px 1px #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000;
+ top: -0.7em;
+ width: 0.8em;
+}
+
+
+#modal_box h2 {
+ margin: 0;
+ font-size: 1.7em;
+ font-weight: bold;
+ text-align: center;
+ margin-bottom: 0.3em;
+ padding-bottom: 0.3em;
+}
+
+#modal_box pre.notes {
+ white-space: pre-wrap;
+}
+
+#modal_box table {
+ color: var(--main-bg-color);
+ background: var(--table-spacing-color);
+ box-shadow: 0 0 0.8em rgba(17,14,16,0.7);
+ border-radius: 0.3em;
+ line-height: 1.4;
+}
+
+#modal_box table td {
+ padding: 0.3em;
+}
+
+#modal_box thead tr th {
+ background: var(--table-head-bg-color);
+ color: var(--table-head-fg-color);
+ font-size: 1.2em;
+ border-bottom: 1px solid;
+}
+
+#modal_box tbody tr:nth-of-type(2n+1) {
+ background: var(--table-odd-bg-color);
+}
+
+#modal_box tbody tr:nth-of-type(2n) {
+ background: var(--table-even-bg-color);
+}
+
+#modal_box p:last-child {
+ margin: 1em 0 0 0;
+}
+
+#modal_box span.notes {
+ cursor: inherit;
+}
+
+/* VGS VIEW BELOW */
+
+#vgs_view {
+ clear: both;
+}
+
+#vgs_view p:first-child {
+ font-size: 1.4em;
+ text-align: center;
+}
+
+#vgs_view table {
+ color: var(--table-text-color);
+ background: var(--table-spacing-color);
+ box-shadow: 0 0 0.8em rgba(17,14,16,0.7);
+ border-radius: 0.3em;
+ line-height: 1.4;
+ font-size: 1.5em;
+ width: 100%;
+}
+
+#vgs_view table thead {
+ position: sticky;
+ top: 0;
+}
+
+#vgs_view table td {
+ padding: 0.3em;
+}
+
+#vgs_view thead tr th {
+ background: var(--table-head-bg-color);
+ font-size: 1.2em;
+ border-bottom: 1px solid;
+}
+
+#vgs_view tbody tr:hover td {
+ background: var(--row-hover-bg-color);
+ color: var(--row-hover-fg-color);
+}
+
+#vgs_view tbody tr:hover td span.notes {
+ color: var(--row-hover-link-color);
+}
+
+#vgs_view tr:nth-of-type(2n+1) {
+ background: var(--table-odd-bg-color);
+}
+
+#vgs_view tr:nth-of-type(2n) {
+ background: var(--table-even-bg-color);
+}
+
+tr[id|="n"]:target {
+ /* display: table-row !important; */
+ background: red;
+}
+
+span.notes {
+ text-decoration: underline;
+ cursor: pointer;
+ font-weight: bold;
+ color: var(--note-link-color);
+}
+
+footer {
+ margin: 2em auto;
+ font-size: 1.5em;
+ text-align: center;
+ color: var(--footer-fg-color);
+}
+
+footer a:link,
+footer a:visited {
+ /* TODO: color is arbitrary, revisit */
+ color: var(--footer-link-color);
+}
diff --git a/src/vgstash/web/vgstash.js b/src/vgstash/web/vgstash.js
new file mode 100644
index 0000000..1b4046e
--- /dev/null
+++ b/src/vgstash/web/vgstash.js
@@ -0,0 +1,278 @@
+function loadJSON() {
+ var xobj = new XMLHttpRequest();
+ xobj.overrideMimeType("application/json");
+ xobj.open('GET', './vgstash.json', true);
+ xobj.setRequestHeader("Accept", "application/json");
+ xobj.onreadystatechange = function () {
+ if (xobj.readyState == 4 && xobj.status == "200") {
+ // Required use of an anonymous callback as .open will NOT return a value but simply returns undefined in asynchronous mode
+ data_loaded(xobj);
+ }
+ };
+ xobj.send(null);
+}
+
+/* compare two games based on their purchase date, sort ascending */
+function compare_p_date(a, b) {
+ var result;
+ if (a.p_date > b.p_date) {
+ // 1 = a comes AFTER b
+ result = 1;
+ } else if (a.p_date == b.p_date) {
+ // 0 = a and b are EQUAL
+ result = 0;
+ } else if (a.p_date < b.p_date) {
+ // -1 = b comes AFTER a
+ result = -1;
+ }
+ return result;
+}
+
+var current_filter = "";
+var gamecol;
+var anchor;
+var own_map = {
+ 0: "&ndash;",
+ 1: "Physical",
+ 2: "Digital",
+ 3: "Both",
+ 4: "Member"
+}
+var prog_map = {
+ 0: "&ndash;",
+ 1: "New",
+ 2: "Playing",
+ 3: "Beaten",
+ 4: "Complete"
+}
+
+window.addEventListener("load", loadJSON);
+
+function data_loaded(evt) {
+ console.log("VGStash JSON data loaded!");
+ // set some globals
+ gamecol = JSON.parse(evt.responseText);
+ anchor = document.getElementById("vgs_view");
+ // setup filter button actions
+ flist = document.getElementById("filter_list");
+ // console.log(flist.children);
+ let i = 0;
+ // TODO: why doesn't 'for (f in flist)' work correctly here?
+ for (i = 0; i < flist.childElementCount; i++) {
+ flist.children[i].addEventListener("click", update);
+ }
+ document.getElementById("help").addEventListener('click', show_help);
+ // write to the page
+ render(anchor, filter_vgs(gamecol, "all"));
+}
+
+function filter_vgs(vgscol, filter_name) {
+ var new_col = new Array();
+ for (game in vgscol) {
+ vgscol[game].rowid = game;
+ switch (filter_name) {
+ case 'backlog':
+ if (vgscol[game].progress < 3 && vgscol[game].progress > 0) {
+ new_col.push(vgscol[game]);
+ }
+ break;
+ case 'backlog_age':
+ if (vgscol[game].progress < 3 && vgscol[game].progress > 0
+ && vgscol[game].p_date != null) {
+ tgame = vgscol[game];
+ tgame.years = Math.floor(((Date.now() / 1000) - tgame.p_date) / (60 * 60 * 24 * 365));
+ tgame.days = Math.floor(((Date.now() / 1000) - tgame.p_date) / (60 * 60 * 24) % 365);
+ console.log(tgame);
+ console.log(vgscol[game]);
+ new_col.push(tgame);
+ }
+ break;
+ case 'beaten':
+ if (vgscol[game].progress == 3) {
+ new_col.push(vgscol[game]);
+ }
+ break;
+ case 'complete':
+ if (vgscol[game].progress > 3) {
+ new_col.push(vgscol[game]);
+ }
+ break;
+ case 'digital':
+ if (vgscol[game].ownership > 1 && vgscol[game].ownership < 4) {
+ new_col.push(vgscol[game]);
+ }
+ break;
+ case 'done':
+ if (vgscol[game].progress >= 3) {
+ new_col.push(vgscol[game]);
+ }
+ break;
+ case 'members':
+ if (vgscol[game].ownership == 4) {
+ new_col.push(vgscol[game]);
+ }
+ break;
+ case 'notes':
+ if (vgscol[game].notes.length > 0) {
+ new_col.push(vgscol[game]);
+ }
+ break;
+ case 'owned':
+ if (vgscol[game].ownership > 0 && vgscol[game].ownership < 4) {
+ new_col.push(vgscol[game]);
+ }
+ break;
+ case 'physical':
+ if (vgscol[game].ownership % 2 == 1) {
+ new_col.push(vgscol[game]);
+ }
+ break;
+ case 'playlog':
+ if (vgscol[game].progress == 2) {
+ new_col.push(vgscol[game]);
+ }
+ break;
+ default:
+ /* filter 'all' */
+ new_col.push(vgscol[game]);
+ }
+ }
+ if (filter_name == "backlog_age") {
+ // sort
+ new_col.sort(compare_p_date);
+ }
+ current_filter = filter_name;
+ return new_col;
+}
+
+function clear_button_styles() {
+ flist = document.getElementById("filter_list").children;
+ for (f in flist) {
+ flist[f].className = "";
+ }
+}
+
+function update() {
+ clear_button_styles();
+ this.className = "selected";
+ let fname = this.innerText.toLowerCase().replaceAll(" ", "_");
+ console.log(fname);
+ render(anchor, filter_vgs(gamecol, fname));
+}
+
+function render(target, vgscol) {
+ var output = "";
+ var note_count = 0;
+ var game_count = 0;
+ var system_set = new Array();
+ var note_set = new Array();
+
+ if (current_filter == "backlog_age") {
+ output = "<table>\n<colgroup>\n<col id=\"title_col\"></col>\n<col></col>\n<col></col>\n<col></col>\n<col></col>\n<col></col>\n</colgroup><thead>\n<th>Title</th>\n<th>System</th>\n<th>Ownership</th>\n<th>Progress</th>\n<th>Years</th>\n<th>Days</th>\n</thead>\n<tbody>\n";
+ } else {
+ output = "<table>\n<colgroup>\n<col id=\"title_col\"></col>\n<col></col>\n<col></col>\n<col></col>\n</colgroup><thead>\n<th>Title</th>\n<th>System</th>\n<th>Ownership</th>\n<th>Progress</th>\n</thead>\n<tbody>\n";
+ }
+ for (var game in vgscol) {
+ var has_notes = false;
+ if (vgscol[game].notes.length > 0) {
+ has_notes = true;
+ note_count += 1;
+ note_set.push(vgscol[game].notes);
+ }
+ game_count += 1;
+ if (!system_set.includes(vgscol[game].system)) {
+ system_set.push(vgscol[game].system);
+ }
+ if (has_notes) {
+ var ntitle = '<span class="notes" onclick="show_notes(gamecol[' + vgscol[game].rowid + ']);">' + vgscol[game].title + '</span>';
+ } else {
+ var ntitle = vgscol[game].title
+ }
+ if (current_filter == "backlog_age") {
+ output += "<tr><td>" + ntitle + "</td><td>" + vgscol[game].system + "</td><td>" + own_map[vgscol[game].ownership] + "</td><td>" + prog_map[vgscol[game].progress] + "</td><td>" + vgscol[game].years.toString() + "</td><td>" + vgscol[game].days.toString() + "</td></tr>\n";
+ } else {
+ output += "<tr><td>" + ntitle + "</td><td>" + vgscol[game].system + "</td><td>" + own_map[vgscol[game].ownership] + "</td><td>" + prog_map[vgscol[game].progress] + "</td></tr>\n";
+ }
+ }
+ output += "\n</tbody>\n</table>\n";
+ message = "<p>Showing " + game_count + " games across " + system_set.length + " systems.</p>";
+ target.innerHTML = message + output;
+}
+
+function show_modal(heading, content) {
+ var backdrop = document.getElementById("modal");
+ var modal_box = document.getElementById("modal_box");
+ var text = '';
+ text = "<span id=\"modal_close\">&times;</span>\n" +
+ "<h2>" + heading + "</h2>\n" +
+ "<div>" + content + "</div>";
+ modal_box.innerHTML = text;
+ window.addEventListener('click',
+ function(evt) {
+ if (evt.target.id == 'modal_close' ||
+ evt.target == backdrop) {
+ backdrop.style.display = 'none';
+ }
+ }
+ );
+ backdrop.style.display = 'block';
+}
+
+function show_contact() {
+ let mtext = '<p>If the VGStash webview doesn\'t work for you (i.e. the list of games isn\'t appearing), and you are using a browser that supports <code>XmlHttpRequest</code>, please <a href="mailto:zlg+vgsweb@zlg.space">e-mail me</a> and describe the issue. </p>' +
+ '<p>When reporting an error, <strong>please include your browser name, its version, and the error output from the browser Console.</strong> In Firefox, the Console can be accessed directly with <kbd>Ctrl+Shift+K</kbd>. Chrome users can open the Dev Tools with <kbd>Ctrl+Shift+I</kbd> and click on the "Console" tab. Next, just copy the text you see and send it in your error report.' +
+ '<p>Thanks! --zlg</p>';
+ show_modal("Contacting ZLG with Issues", mtext);
+}
+
+function show_help() {
+ let mtext = '<table>' +
+ '<thead>' +
+ '<tr>' +
+ '<th>Text</th>' +
+ '<th>Meaning</th>' +
+ '</tr>' +
+ '</thead>' +
+ '<tbody>' +
+ '<tr>' +
+ '<td>&ndash; (dash)</td>' +
+ '<td>Unbeatable, or Unowned</td>' +
+ '</tr>' +
+ '<tr>' +
+ '<td>Physical</td>' +
+ '<td>A tangible copy of the game, e.g. cartridge, disc, etc.</td>' +
+ '</tr>' +
+ '<tr>' +
+ '<td>Digital</td>' +
+ "<td>A virtual copy of the game, i.e. an executable file on your device's storage.</td>" +
+ '</tr>' +
+ '<tr>' +
+ '<td>Member</td>' +
+ "<td>A game that you own as part of a collection.</td>" +
+ '</tr>' +
+ '<tr>' +
+ '<td>New</td>' +
+ '<td>The game has not been started yet.</td>' +
+ '</tr>' +
+ '<tr>' +
+ '<td>Playing</td>' +
+ '<td>The game is in the "now playing" list.</td>' +
+ '</tr>' +
+ '<tr>' +
+ '<td>Beaten</td>' +
+ "<td>The game's main story has been completed.</td>" +
+ '</tr>' +
+ '<tr>' +
+ '<td>Complete</td>' +
+ '<td>Every objective has been completed, including any achievements or side missions. Exceptions are made for achievements that are unobtainable due to network dependency.</td>' +
+ '</tr>' +
+ '</tbody>' +
+ '</table>' +
+ '<p><span class="notes">Underlined</span> game titles have notes you can read if you click on them.</p>';
+ show_modal("Legend", mtext);
+}
+
+function show_notes(game) {
+ console.log(game);
+ show_modal(game.title + ' [' + game.system + '] Notes', '<pre class="notes">' + game.notes + '</pre>');
+}