diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/vgstash/web/vgstash-favicon.png | bin | 0 -> 324 bytes | |||
-rw-r--r-- | src/vgstash/web/vgstash-web.html | 46 | ||||
-rw-r--r-- | src/vgstash/web/vgstash.css | 333 | ||||
-rw-r--r-- | src/vgstash/web/vgstash.js | 278 |
4 files changed, 657 insertions, 0 deletions
diff --git a/src/vgstash/web/vgstash-favicon.png b/src/vgstash/web/vgstash-favicon.png Binary files differnew file mode 100644 index 0000000..5456f3b --- /dev/null +++ b/src/vgstash/web/vgstash-favicon.png 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> © 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: "–", + 1: "Physical", + 2: "Digital", + 3: "Both", + 4: "Member" +} +var prog_map = { + 0: "–", + 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\">×</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>– (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>'); +} |