Rework the web UI: tile-based progress, view nav, responsive

Replace the single-page layout with a view-based one (Games / Settings /
Logs via the left nav) and fold all download UI into the game tile — the
separate download queue is gone.

Per tile while downloading: an in-tile progress bar with auto-scaled size
(B/KB/MB/GB/TB), live transfer speed and ETA from the poll deltas, and a
green "Downloading" marker. One fixed-width action button that no longer
reflows with its label: a blue Update/Download/Install that morphs into an
amber Cancel while the download runs, plus a ghost Delete for a finished
package.

Settings and Logs moved to their own pages. Mobile-first responsive layout
(rail collapses to a top bar, icon-only nav, single-column tiles, >=44px
touch targets, 16px inputs). Real free space (statvfs) is shown auto-scaled
in the rail and status strip.

New "Home-screen shortcut" toggle (default on, persisted as home_shortcut
in config.json). The actual PS5 tile install is not wired yet: it needs a
prebuilt deeplinkUri stub PKG installed via sceAppInstUtil.

Fixes from an adversarial review pass:
- Reconcile in-flight downloads from /api/downloads onto the cards, so
  progress + Cancel appear after a reload or a download started elsewhere,
  and stop the per-poll full-grid rebuild.
- Clear the downloaded flag on install and once the server reports the
  title up to date, so a patched title no longer shows Install/Delete
  forever.
- Zero a stale speed/ETA if the byte counter goes backwards.
- a11y: nav buttons keep an accessible name when the label is hidden on
  small screens; visible focus ring on the search box; filter group is
  role=group with aria-pressed; drop the noisy grid-level aria-live; fold
  the transient "checking" state into a visible filter bucket.
This commit is contained in:
Knutwurst committed 2026-06-23 21:48:54 +02:00
1 parent 5f0d1be078
commit 3f40dc1d7c
4 files changed
+734 -1119

No files matched your search

+14 -5
View File
@@ -38,7 +38,8 @@ static struct {
int install_after_download;
int delete_pkg_after_install;
int verify_downloads; /* SHA-256 each manifest piece (default off) */
} g_cfg = { "deny", 0, 1, 0 };
int home_shortcut; /* user wants a home-screen browser tile */
} g_cfg = { "deny", 0, 1, 0, 1 };
static struct {
int active;
@@ -438,11 +439,13 @@ build_config_json(void) {
"{\"default_policy\":\"%s\","
"\"install_after_download\":%s,"
"\"delete_pkg_after_install\":%s,"
"\"verify_downloads\":%s,",
"\"verify_downloads\":%s,"
"\"home_shortcut\":%s,",
g_cfg.default_policy[0] ? g_cfg.default_policy : "deny",
g_cfg.install_after_download ? "true" : "false",
g_cfg.delete_pkg_after_install ? "true" : "false",
g_cfg.verify_downloads ? "true" : "false");
g_cfg.verify_downloads ? "true" : "false",
g_cfg.home_shortcut ? "true" : "false");
pthread_mutex_unlock(&g_mutex);
char *out = malloc(strlen(head) + sizeof(config_tail_json));
@@ -467,11 +470,13 @@ save_config(void) {
"{\n\"default_policy\":\"%s\",\n"
"\"install_after_download\":%s,\n"
"\"delete_pkg_after_install\":%s,\n"
"\"verify_downloads\":%s,\n\"titles\":{",
"\"verify_downloads\":%s,\n"
"\"home_shortcut\":%s,\n\"titles\":{",
g_cfg.default_policy[0] ? g_cfg.default_policy : "deny",
g_cfg.install_after_download ? "true" : "false",
g_cfg.delete_pkg_after_install ? "true" : "false",
g_cfg.verify_downloads ? "true" : "false");
g_cfg.verify_downloads ? "true" : "false",
g_cfg.home_shortcut ? "true" : "false");
for (size_t i = 0; i < g_title_count; i++)
fprintf(f, "%s\"%s\":%s", i ? "," : "",
g_titles[i].title_id, g_titles[i].enabled ? "true" : "false");
@@ -507,6 +512,8 @@ load_config(void) {
json_get_bool(buf, "delete_pkg_after_install", g_cfg.delete_pkg_after_install);
g_cfg.verify_downloads =
json_get_bool(buf, "verify_downloads", g_cfg.verify_downloads);
g_cfg.home_shortcut =
json_get_bool(buf, "home_shortcut", g_cfg.home_shortcut);
/* per-title overrides live under "titles": { "<id>": true|false, ... } */
const char *titles = strstr(buf, "\"titles\"");
@@ -1093,6 +1100,8 @@ handle_config_post(struct MHD_Connection *conn, const char *body) {
json_get_bool(body, "delete_pkg_after_install", g_cfg.delete_pkg_after_install);
g_cfg.verify_downloads =
json_get_bool(body, "verify_downloads", g_cfg.verify_downloads);
g_cfg.home_shortcut =
json_get_bool(body, "home_shortcut", g_cfg.home_shortcut);
pthread_mutex_unlock(&g_mutex);
save_config();