mirror of
https://github.com/drakmor/ShadowMountPlus.git
synced 2026-10-22 06:32:25 -05:00
Fix web UI compatibility with older WebKit
This commit is contained in:
1 parent
ff67cd3d61
commit
8c4d156ecf
2 files changed
+60
-33
No files matched your search
+23
-3
@@ -565,10 +565,30 @@ static void copy_sqlite_text(sqlite3_stmt *stmt, int column, char *out,
|
||||
static bool copy_json_string(struct json_object *object, const char *key,
|
||||
char *out, size_t out_size) {
|
||||
struct json_object *value = NULL;
|
||||
if (!json_object_object_get_ex(object, key, &value) ||
|
||||
!json_object_is_type(value, json_type_string)) {
|
||||
return false;
|
||||
if (!json_object_object_get_ex(object, key, &value)) {
|
||||
struct json_object *fields = NULL;
|
||||
if (!json_object_object_get_ex(object, "field_list", &fields) ||
|
||||
!json_object_is_type(fields, json_type_array))
|
||||
return false;
|
||||
|
||||
// Older app.db stores AppInfoJson values as {key, data} records.
|
||||
size_t key_length = strlen(key);
|
||||
size_t count = json_object_array_length(fields);
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
struct json_object *field = json_object_array_get_idx(fields, i);
|
||||
struct json_object *name = NULL;
|
||||
if (!json_object_is_type(field, json_type_object) ||
|
||||
!json_object_object_get_ex(field, "key", &name) ||
|
||||
!json_object_is_type(name, json_type_string) ||
|
||||
(size_t)json_object_get_string_len(name) != key_length ||
|
||||
memcmp(json_object_get_string(name), key, key_length) != 0)
|
||||
continue;
|
||||
(void)json_object_object_get_ex(field, "data", &value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!json_object_is_type(value, json_type_string))
|
||||
return false;
|
||||
const char *text = json_object_get_string(value);
|
||||
size_t length = (size_t)json_object_get_string_len(value);
|
||||
if (length == 0 || length >= out_size || memchr(text, '\0', length))
|
||||
|
||||
+37
-30
@@ -55,21 +55,19 @@
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.brand { display: flex; align-items: center; gap: 13px; }
|
||||
.brand { display: flex; align-items: center; }
|
||||
|
||||
.brand-mark {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
display: block;
|
||||
flex-shrink: 0;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border: 1px solid rgba(85, 167, 255, .45);
|
||||
border-radius: 12px;
|
||||
background: linear-gradient(145deg, #147ee8, #74baff);
|
||||
box-shadow: 0 8px 24px rgba(20, 126, 232, .25);
|
||||
font-size: 20px;
|
||||
font-weight: 800;
|
||||
margin-right: 13px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
[dir="rtl"] .brand-mark { margin-right: 0; margin-left: 13px; }
|
||||
|
||||
h1, h2, h3, p { margin: 0; }
|
||||
h1 { font-size: 20px; letter-spacing: -.02em; }
|
||||
h2 { font-size: 17px; }
|
||||
@@ -212,14 +210,14 @@
|
||||
.cover {
|
||||
position: relative;
|
||||
width: 88px;
|
||||
aspect-ratio: 1;
|
||||
height: 88px;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 12px;
|
||||
background: linear-gradient(145deg, #1d2c43, #0c111a);
|
||||
}
|
||||
|
||||
.cover img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
|
||||
.cover img { position: absolute; top: 0; right: 0; bottom: 0; left: 0; width: 100%; height: 100%; object-fit: cover; }
|
||||
.cover-fallback { display: grid; place-items: center; width: 100%; height: 100%; color: var(--blue); font-weight: 800; }
|
||||
.game-main { min-width: 0; }
|
||||
.game-heading { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: start; gap: 10px; }
|
||||
@@ -313,7 +311,10 @@
|
||||
.modal {
|
||||
position: fixed;
|
||||
z-index: 20;
|
||||
inset: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: 20px;
|
||||
@@ -327,7 +328,7 @@
|
||||
.modal-head { display: flex; align-items: start; justify-content: space-between; gap: 20px; padding: 18px; border-bottom: 1px solid var(--line); }
|
||||
.close { width: 36px; min-height: 36px; padding: 0; font-size: 22px; }
|
||||
.details { display: grid; grid-template-columns: 168px minmax(0, 1fr); align-items: start; gap: 18px; padding: 18px; }
|
||||
.details .cover { width: 168px; }
|
||||
.details .cover { width: 168px; height: 168px; }
|
||||
.detail-grid { display: grid; grid-template-columns: 130px minmax(0, 1fr); gap: 7px 12px; margin-top: 13px; font-size: 13px; }
|
||||
.detail-key { color: var(--muted); }
|
||||
.detail-value { min-width: 0; overflow-wrap: anywhere; }
|
||||
@@ -372,7 +373,7 @@
|
||||
.games { grid-template-columns: 1fr; }
|
||||
.sidebar { grid-template-columns: 1fr; }
|
||||
.details { grid-template-columns: 1fr; }
|
||||
.details .cover { width: 120px; }
|
||||
.details .cover { width: 120px; height: 120px; }
|
||||
.detail-grid { grid-template-columns: 105px minmax(0, 1fr); }
|
||||
}
|
||||
</style>
|
||||
@@ -381,7 +382,7 @@
|
||||
<main class="shell">
|
||||
<header class="topbar">
|
||||
<div class="brand">
|
||||
<div class="brand-mark" aria-hidden="true">S</div>
|
||||
<img class="brand-mark" src="/favicon.png" width="42" height="42" alt="" aria-hidden="true">
|
||||
<div>
|
||||
<h1 data-i18n="pageTitle">Shadow Mount+ Library</h1>
|
||||
<p class="subtle" id="version" data-i18n="connecting">Connecting to API…</p>
|
||||
@@ -1098,6 +1099,12 @@
|
||||
return item;
|
||||
}
|
||||
|
||||
function replaceContent(parent, child) {
|
||||
// Older WebKit can leave children intact on replaceChildren() with no arguments.
|
||||
parent.textContent = "";
|
||||
if (child) parent.appendChild(child);
|
||||
}
|
||||
|
||||
function formatBytes(value) {
|
||||
if (!Number.isFinite(value) || value < 0) return "—";
|
||||
const units = byteUnits[currentLanguage] || byteUnits[defaultLanguage];
|
||||
@@ -1181,7 +1188,7 @@
|
||||
state.activity.unshift({ message, kind, time: new Date().toLocaleTimeString(currentLanguage, { hour: "2-digit", minute: "2-digit" }) });
|
||||
state.activity = state.activity.slice(0, 8);
|
||||
const list = $("activity");
|
||||
list.replaceChildren();
|
||||
replaceContent(list);
|
||||
state.activity.forEach((entry) => {
|
||||
list.append(node("div", `activity-item ${entry.kind}`, `${entry.time} · ${entry.message}`));
|
||||
});
|
||||
@@ -1278,7 +1285,7 @@
|
||||
function renderGames() {
|
||||
const games = filteredGames();
|
||||
const list = $("games");
|
||||
list.replaceChildren();
|
||||
replaceContent(list);
|
||||
if (!games.length) {
|
||||
list.append(node("div", "empty", t(state.games.length ? "nothingFound" : "libraryEmpty")));
|
||||
} else {
|
||||
@@ -1427,7 +1434,7 @@
|
||||
return true;
|
||||
});
|
||||
if (!$("destinationModal").hidden) renderDestinationPicker();
|
||||
list.replaceChildren();
|
||||
replaceContent(list);
|
||||
if (!mounts.length) {
|
||||
list.append(node("span", "subtle", t("storageNotFound")));
|
||||
return;
|
||||
@@ -1467,7 +1474,7 @@
|
||||
} catch (error) {
|
||||
state.storageDestinations = [];
|
||||
if (!$("destinationModal").hidden) renderDestinationPicker();
|
||||
list.replaceChildren(node("span", "subtle", errorMessage(error)));
|
||||
replaceContent(list, node("span", "subtle", errorMessage(error)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1488,7 +1495,7 @@
|
||||
renderGames();
|
||||
} catch (error) {
|
||||
setConnection("offline", "disconnected");
|
||||
$("games").replaceChildren(node("div", "empty", t("apiError", { error: errorMessage(error) })));
|
||||
replaceContent($("games"), node("div", "empty", t("apiError", { error: errorMessage(error) })));
|
||||
toast(errorMessage(error), true);
|
||||
} finally {
|
||||
state.busy = false;
|
||||
@@ -1504,7 +1511,7 @@
|
||||
const list = $("manualList");
|
||||
try {
|
||||
const data = await api("/manual/list");
|
||||
list.replaceChildren();
|
||||
replaceContent(list);
|
||||
if (!data.paths.length) {
|
||||
list.append(node("span", "subtle", t("listEmpty")));
|
||||
return;
|
||||
@@ -1520,13 +1527,13 @@
|
||||
list.append(row);
|
||||
});
|
||||
} catch (error) {
|
||||
list.replaceChildren(node("span", "subtle", errorMessage(error)));
|
||||
replaceContent(list, node("span", "subtle", errorMessage(error)));
|
||||
}
|
||||
}
|
||||
|
||||
function renderScanPaths() {
|
||||
const list = $("scanPathList");
|
||||
list.replaceChildren();
|
||||
replaceContent(list);
|
||||
if (!state.scanPaths.length) {
|
||||
list.append(node("span", "subtle", t("listEmpty")));
|
||||
return;
|
||||
@@ -1563,7 +1570,7 @@
|
||||
state.scanPaths = Array.isArray(settings.scan_paths) ? settings.scan_paths.slice() : [];
|
||||
renderScanPaths();
|
||||
} catch (error) {
|
||||
$("scanPathList").replaceChildren(node("span", "subtle", errorMessage(error)));
|
||||
replaceContent($("scanPathList"), node("span", "subtle", errorMessage(error)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1771,7 +1778,7 @@
|
||||
$("modalTitle").textContent = game.title_name || game.title_id;
|
||||
$("modalSubtitle").textContent = `${game.title_id} · ${(game.platform || t("unknown")).toUpperCase()}`;
|
||||
const details = $("details");
|
||||
details.replaceChildren();
|
||||
replaceContent(details);
|
||||
details.append(coverFor(game, true));
|
||||
const content = node("div");
|
||||
const badges = node("div", "badges");
|
||||
@@ -1819,7 +1826,7 @@
|
||||
details.append(content);
|
||||
|
||||
const actions = $("modalActions");
|
||||
actions.replaceChildren();
|
||||
replaceContent(actions);
|
||||
const runtimeAction = appendRuntimeActions(actions, game);
|
||||
if (game.can_uninstall !== false) {
|
||||
actions.append(actionButton(t("uninstall"), () => uninstallGame(game), "danger"));
|
||||
@@ -1855,14 +1862,14 @@
|
||||
$("modal").hidden = false;
|
||||
$("modalTitle").textContent = t("loading");
|
||||
$("modalSubtitle").textContent = titleId;
|
||||
$("details").replaceChildren(node("div", "empty", t("loadingSource")));
|
||||
$("modalActions").replaceChildren();
|
||||
replaceContent($("details"), node("div", "empty", t("loadingSource")));
|
||||
replaceContent($("modalActions"));
|
||||
try {
|
||||
const game = await api("/games/info", { title_id: titleId });
|
||||
if (state.detailRequest === request) renderDetails(game, true);
|
||||
} catch (error) {
|
||||
if (state.detailRequest === request) {
|
||||
$("details").replaceChildren(node("div", "empty", errorMessage(error)));
|
||||
replaceContent($("details"), node("div", "empty", errorMessage(error)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1885,7 +1892,7 @@
|
||||
? "chooseCopyDestination"
|
||||
: "chooseMoveDestination");
|
||||
const choices = $("destinationChoices");
|
||||
choices.replaceChildren();
|
||||
replaceContent(choices);
|
||||
if (!state.storageDestinations.length) {
|
||||
choices.append(node("span", "subtle", t("storageNotFound")));
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user