Switch files fully to IDB, GUI fixes

This commit is contained in:
ZXMushroom63 2024-12-08 19:40:54 +08:00
parent 5fd5eefdbd
commit 4c605ec5cf
4 changed files with 220 additions and 392 deletions

View File

@ -169,7 +169,7 @@
<!-- Code assets --> <!-- Code assets -->
<script src="postinit.js"></script> <script src="postinit.js"></script>
<script src="modloader.injector.js"></script> <script src="modloader.js"></script>
<script src="modgui.js"></script> <script src="modgui.js"></script>
<script src="efserver.js"></script> <script src="efserver.js"></script>
</body> </body>

View File

@ -24,7 +24,7 @@ globalThis.modapi_guikit = "(" + (() => {
</h5> </h5>
</header> </header>
<table class="modTable"> <table id="modapi_gui_modTable">
<thead> <thead>
<tr> <tr>
<td> <td>
@ -162,15 +162,18 @@ globalThis.modapi_guikit = "(" + (() => {
font-size: 1.25rem; font-size: 1.25rem;
box-shadow: 0 -4px 6px rgba(0, 0, 0, 0.1); box-shadow: 0 -4px 6px rgba(0, 0, 0, 0.1);
} }
#modapi_gui_modTable {
min-width: 40vw;
}
</style> </style>
</div>`; </div>`;
async function fileToDataURI(file) { async function fileToText(file) {
return new Promise((res, rej) => { return new Promise((res, rej) => {
var fr = new FileReader(); var fr = new FileReader();
fr.addEventListener("error", (e) => { rej(e); }); fr.addEventListener("error", (e) => { rej(e); });
fr.addEventListener("load", (e) => { res(fr.result); }); fr.addEventListener("load", (e) => { res(fr.result); });
fr.readAsDataURL(file); fr.readAsText(file);
}); });
} }
window.modapi_displayModGui = async function (cb) { window.modapi_displayModGui = async function (cb) {
@ -191,7 +194,7 @@ globalThis.modapi_guikit = "(" + (() => {
document.querySelector("#modapi_gui_container")._cb = cb; document.querySelector("#modapi_gui_container")._cb = cb;
var modsList = await getMods(); var modsList = await getMods();
var tbody = document.querySelector("#modapi_gui_container .modTable tbody"); var tbody = document.querySelector("#modapi_gui_container #modapi_gui_modTable tbody");
tbody.innerHTML = ""; tbody.innerHTML = "";
modsList.forEach((modtxt, i) => { modsList.forEach((modtxt, i) => {
if (!modtxt) { return } if (!modtxt) { return }
@ -245,7 +248,6 @@ globalThis.modapi_guikit = "(" + (() => {
var button = document.createElement("button"); var button = document.createElement("button");
button.innerText = "Delete"; button.innerText = "Delete";
button.style.height = "3rem"; button.style.height = "3rem";
button.style.marginTop = "calc(50% - 1.5rem)";
button.addEventListener("click", async () => { button.addEventListener("click", async () => {
await removeMod(i); await removeMod(i);
window.modapi_displayModGui(); window.modapi_displayModGui();
@ -254,7 +256,7 @@ globalThis.modapi_guikit = "(" + (() => {
controls.appendChild(button); controls.appendChild(button);
tr.appendChild(mod); tr.appendChild(mod);
tr.appendChild(spacer); tr.appendChild(spacer);
tr.appendChild(button); tr.appendChild(controls);
tbody.appendChild(tr); tbody.appendChild(tr);
}); });
var once = false; var once = false;
@ -279,7 +281,7 @@ globalThis.modapi_guikit = "(" + (() => {
if (!mod || mod.length === 0) { if (!mod || mod.length === 0) {
return; return;
} }
await addMod("web@" + mod); await addMod(mod);
window.modapi_displayModGui(); window.modapi_displayModGui();
} }
window.modapi_uploadmod = async () => { window.modapi_uploadmod = async () => {
@ -292,7 +294,7 @@ globalThis.modapi_guikit = "(" + (() => {
return; return;
} }
for (let i = 0; i < f.files.length; i++) { for (let i = 0; i < f.files.length; i++) {
await addMod("web@" + (await fileToDataURI(f.files[i])).replaceAll(";base64", ";fs=" + f.files[i].name + ";base64")); await addFileMod(f.files[i].name, (await fileToText(f.files[i])));
} }
window.modapi_displayModGui(); window.modapi_displayModGui();
}); });

View File

@ -1,212 +0,0 @@
globalThis.modapi_modloader = `function promisifyIDBRequest(request) {
return new Promise((resolve, reject) => {
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
async function getDatabase() {
const dbRequest = indexedDB.open("EF_MODS");
const db = await promisifyIDBRequest(dbRequest);
if (!db.objectStoreNames.contains("filesystem")) {
db.close();
const version = db.version + 1;
const upgradeRequest = indexedDB.open("EF_MODS", version);
upgradeRequest.onupgradeneeded = (event) => {
const upgradedDb = event.target.result;
upgradedDb.createObjectStore("filesystem");
};
return promisifyIDBRequest(upgradeRequest);
}
return db;
}
async function getMods() {
const db = await getDatabase();
const transaction = db.transaction(["filesystem"], "readonly");
const objectStore = transaction.objectStore("filesystem");
const object = await promisifyIDBRequest(objectStore.get("mods.txt"));
return object ? (await object.text()).split("|") : [];
}
async function saveMods(mods) {
const db = await getDatabase();
const transaction = db.transaction(["filesystem"], "readwrite");
const objectStore = transaction.objectStore("filesystem");
const encoder = new TextEncoder();
const modsData = encoder.encode(mods.join("|"));
const modsBlob = new Blob([modsData], { type: "text/plain" });
await promisifyIDBRequest(objectStore.put(modsBlob, "mods.txt"));
}
async function addMod(mod) {
const mods = await getMods();
mods.push(mod);
await saveMods(mods);
}
async function removeMod(index) {
const mods = await getMods();
if (index >= 0 && index < mods.length) {
mods.splice(index, 1);
await saveMods(mods);
}
}
async function resetMods() {
await saveMods([]);
console.log("Mods reset");
}
window.modLoader = async function modLoader(modsArr = []) {
if (!window.eaglerMLoaderMainRun) {
var searchParams = new URLSearchParams(location.search);
searchParams.getAll("mod").forEach((modToAdd) => {
console.log(
"[EaglerML] Adding mod to loadlist from search params: " + modToAdd
);
modsArr.push(modToAdd);
});
searchParams.getAll("plugin").forEach((modToAdd) => {
console.log(
"[EaglerML] Adding mod to loadlist from search params: " + modToAdd
);
modsArr.push(modToAdd);
});
if (
!!eaglercraftXOpts &&
!!eaglercraftXOpts.Mods &&
Array.isArray(eaglercraftXOpts.Mods)
) {
eaglercraftXOpts.Mods.forEach((modToAdd) => {
console.log(
"[EaglerML] Adding mod to loadlist from eaglercraftXOpts: " +
modToAdd
);
modsArr.push(modToAdd);
});
}
console.log("[EaglerML] Searching in iDB");
try {
var idbMods = await getMods();
modsArr = modsArr.concat(idbMods
.filter(x => { return x && x.length > 0 })
.flatMap(x => { if (x.startsWith("web@")) { return x.replace("web@", "") } return x })
);
} catch (error) {
console.error(error);
}
window.eaglerMLoaderMainRun = true;
}
if (window.noLoadMods === true) {
modsArr.splice(0, modsArr.length);
}
function checkModsLoaded(totalLoaded, identifier) {
console.log(
"[EaglerML] Checking if mods are finished :: " +
totalLoaded +
"/" +
modsArr.length
);
if (totalLoaded >= modsArr.length) {
clearInterval(identifier);
window.ModGracePeriod = false;
if (
window.eaglerMLoaderMainRun &&
ModAPI &&
ModAPI.events &&
ModAPI.events.callEvent
) {
ModAPI.events.callEvent("load", {});
}
console.log(
"[EaglerML] Checking if mods are finished :: All mods loaded! Grace period off."
);
}
}
function methodB(currentMod) {
try {
console.log("[EaglerML] Loading " + currentMod + " via method B.");
var script = document.createElement("script");
script.setAttribute("data-hash", ModAPI.util.hashCode("web@"+currentMod));
script.src = currentMod;
script.setAttribute("data-isMod", "true");
script.onerror = () => {
console.log(
"[EaglerML] Failed to load " + currentMod + " via method B!"
);
script.remove();
totalLoaded++;
};
script.onload = () => {
console.log(
"[EaglerML] Successfully loaded " + currentMod + " via method B."
);
totalLoaded++;
};
document.body.appendChild(script);
} catch (error) {
console.log(
"[EaglerML] Oh no! The mod " + currentMod + " failed to load!"
);
totalLoaded++;
}
}
window.ModGracePeriod = true;
var totalLoaded = 0;
var loaderCheckInterval = null;
modsArr.forEach((c) => {
let currentMod = c;
console.log("[EaglerML] Starting " + currentMod);
try {
var req = new XMLHttpRequest();
req.open("GET", currentMod);
req.onload = function xhrLoadHandler() {
console.log("[EaglerML] Loading " + currentMod + " via method A.");
var script = document.createElement("script");
script.setAttribute("data-hash", ModAPI.util.hashCode("web@"+currentMod));
try {
script.src =
"data:text/javascript," + encodeURIComponent(req.responseText);
} catch (error) {
methodB(currentMod);
return;
}
script.setAttribute("data-isMod", "true");
script.onerror = () => {
console.log(
"[EaglerML] Failed to load " + currentMod + " via method A!"
);
script.remove();
totalLoaded++;
};
script.onload = () => {
console.log(
"[EaglerML] Successfully loaded " + currentMod + " via method A."
);
totalLoaded++;
};
document.body.appendChild(script);
};
req.onerror = function xhrErrorHandler() {
methodB(currentMod);
};
req.send();
} catch (error) {
methodB(currentMod);
}
});
loaderCheckInterval = setInterval(() => {
checkModsLoaded(totalLoaded, loaderCheckInterval);
}, 500);
console.log(
"[EaglerML] Starting to load " + modsArr.length + " mods..."
);
window.returnTotalLoadedMods = function returnTotalLoadedMods() {
return totalLoaded;
};
};`;

View File

@ -1,11 +1,12 @@
function promisifyIDBRequest(request) { globalThis.modapi_modloader = "(" + (() => {
globalThis.promisifyIDBRequest = function promisifyIDBRequest(request) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
request.onsuccess = () => resolve(request.result); request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error); request.onerror = () => reject(request.error);
}); });
} }
async function getDatabase() { globalThis.getDatabase = async function getDatabase() {
const dbRequest = indexedDB.open("EF_MODS"); const dbRequest = indexedDB.open("EF_MODS");
const db = await promisifyIDBRequest(dbRequest); const db = await promisifyIDBRequest(dbRequest);
@ -23,15 +24,27 @@ async function getDatabase() {
return db; return db;
} }
async function getMods() { globalThis.getMods = async function getMods() {
const db = await getDatabase(); const db = await getDatabase();
const transaction = db.transaction(["filesystem"], "readonly"); const transaction = db.transaction(["filesystem"], "readonly");
const objectStore = transaction.objectStore("filesystem"); const objectStore = transaction.objectStore("filesystem");
const object = await promisifyIDBRequest(objectStore.get("mods.txt")); const object = await promisifyIDBRequest(objectStore.get("mods.txt"));
return object ? (await object.text()).split("|") : []; var out = object ? (await object.text()).split("|") : [];
db.close();
return out;
} }
async function saveMods(mods) { globalThis.getMod = async function getMod(mod) {
const db = await getDatabase();
const transaction = db.transaction(["filesystem"], "readonly");
const objectStore = transaction.objectStore("filesystem");
const object = await promisifyIDBRequest(objectStore.get("mods/" + mod));
var out = object ? (await object.text()) : "";
db.close();
return out;
}
globalThis.saveMods = async function saveMods(mods) {
const db = await getDatabase(); const db = await getDatabase();
const transaction = db.transaction(["filesystem"], "readwrite"); const transaction = db.transaction(["filesystem"], "readwrite");
const objectStore = transaction.objectStore("filesystem"); const objectStore = transaction.objectStore("filesystem");
@ -39,41 +52,69 @@ async function saveMods(mods) {
const modsData = encoder.encode(mods.join("|")); const modsData = encoder.encode(mods.join("|"));
const modsBlob = new Blob([modsData], { type: "text/plain" }); const modsBlob = new Blob([modsData], { type: "text/plain" });
await promisifyIDBRequest(objectStore.put(modsBlob, "mods.txt")); await promisifyIDBRequest(objectStore.put(modsBlob, "mods.txt"));
db.close();
} }
async function addMod(mod) { globalThis.addMod = async function addMod(mod) {
const mods = await getMods();
mods.push("web@" + mod);
await saveMods(mods);
}
globalThis.addFileMod = async function addFileMod(mod, textContents) {
const mods = await getMods(); const mods = await getMods();
mods.push(mod); mods.push(mod);
await saveMods(mods); await saveMods(mods);
const db = await getDatabase();
const transaction = db.transaction(["filesystem"], "readwrite");
const objectStore = transaction.objectStore("filesystem");
const encoder = new TextEncoder();
const modsData = encoder.encode(textContents);
const modsBlob = new Blob([modsData], { type: "text/plain" });
await promisifyIDBRequest(objectStore.put(modsBlob, "mods/" + mod));
db.close();
} }
async function removeMod(index) { globalThis.removeMod = async function removeMod(index) {
const mods = await getMods(); const mods = await getMods();
if (index >= 0 && index < mods.length) { if (index >= 0 && index < mods.length) {
mods.splice(index, 1); var deleted = mods.splice(index, 1)[0];
await saveMods(mods); await saveMods(mods);
if (!deleted.startsWith("web@")) {
const db = await getDatabase();
const transaction = db.transaction(["filesystem"], "readwrite");
const objectStore = transaction.objectStore("filesystem");
await promisifyIDBRequest(objectStore.delete("mods/" + deleted));
db.close();
}
} }
} }
async function resetMods() { globalThis.resetMods = async function resetMods() {
await saveMods([]); console.log("Resetting mods...");
const db = await getDatabase();
const transaction = db.transaction(["filesystem"], "readwrite");
const objectStore = transaction.objectStore("filesystem");
await promisifyIDBRequest(objectStore.clear());
console.log("Mods reset"); console.log("Mods reset");
db.close();
} }
window.modLoader = async function modLoader(modsArr = []) { globalThis.modLoader = async function modLoader(modsArr = []) {
if (!window.eaglerMLoaderMainRun) { if (!window.eaglerMLoaderMainRun) {
var searchParams = new URLSearchParams(location.search); var searchParams = new URLSearchParams(location.search);
searchParams.getAll("mod").forEach((modToAdd) => { searchParams.getAll("mod").forEach((modToAdd) => {
console.log( console.log(
"[EaglerML] Adding mod to loadlist from search params: " + modToAdd "[EaglerML] Adding mod to loadlist from search params: " + modToAdd
); );
modsArr.push(modToAdd); modsArr.push("web@" + modToAdd);
}); });
searchParams.getAll("plugin").forEach((modToAdd) => { searchParams.getAll("plugin").forEach((modToAdd) => {
console.log( console.log(
"[EaglerML] Adding mod to loadlist from search params: " + modToAdd "[EaglerML] Adding mod to loadlist from search params: " + modToAdd
); );
modsArr.push(modToAdd); modsArr.push("web@" + modToAdd);
}); });
if ( if (
!!eaglercraftXOpts && !!eaglercraftXOpts &&
@ -94,7 +135,6 @@ window.modLoader = async function modLoader(modsArr = []) {
var idbMods = await getMods(); var idbMods = await getMods();
modsArr = modsArr.concat(idbMods modsArr = modsArr.concat(idbMods
.filter(x => { return x && x.length > 0 }) .filter(x => { return x && x.length > 0 })
.flatMap(x => { if (x.startsWith("web@")) { return x.replace("web@", "") } return x })
); );
} catch (error) { } catch (error) {
console.error(error); console.error(error);
@ -159,19 +199,21 @@ window.modLoader = async function modLoader(modsArr = []) {
window.ModGracePeriod = true; window.ModGracePeriod = true;
var totalLoaded = 0; var totalLoaded = 0;
var loaderCheckInterval = null; var loaderCheckInterval = null;
modsArr.forEach((c) => { for (let i = 0; i < modsArr.length; i++) {
let currentMod = c; let currentMod = modsArr[i];
var isIDBMod = !currentMod.startsWith("web@");
if (!isIDBMod) {
currentMod = currentMod.replace("web@", "");
}
console.log("[EaglerML] Starting " + currentMod); console.log("[EaglerML] Starting " + currentMod);
try { try {
var req = new XMLHttpRequest(); var responseText = isIDBMod ? await getMod(currentMod) : await (await fetch(currentMod)).text();
req.open("GET", currentMod);
req.onload = function xhrLoadHandler() {
console.log("[EaglerML] Loading " + currentMod + " via method A."); console.log("[EaglerML] Loading " + currentMod + " via method A.");
var script = document.createElement("script"); var script = document.createElement("script");
script.setAttribute("data-hash", ModAPI.util.hashCode("web@"+currentMod)); script.setAttribute("data-hash", ModAPI.util.hashCode((isIDBMod ? "" : "web@") + currentMod));
try { try {
script.src = script.src =
"data:text/javascript," + encodeURIComponent(req.responseText); "data:text/javascript," + encodeURIComponent(responseText);
} catch (error) { } catch (error) {
methodB(currentMod); methodB(currentMod);
return; return;
@ -191,15 +233,10 @@ window.modLoader = async function modLoader(modsArr = []) {
totalLoaded++; totalLoaded++;
}; };
document.body.appendChild(script); document.body.appendChild(script);
};
req.onerror = function xhrErrorHandler() {
methodB(currentMod);
};
req.send();
} catch (error) { } catch (error) {
methodB(currentMod); methodB(currentMod);
} }
}); }
loaderCheckInterval = setInterval(() => { loaderCheckInterval = setInterval(() => {
checkModsLoaded(totalLoaded, loaderCheckInterval); checkModsLoaded(totalLoaded, loaderCheckInterval);
}, 500); }, 500);
@ -210,3 +247,4 @@ window.modLoader = async function modLoader(modsArr = []) {
return totalLoaded; return totalLoaded;
}; };
}; };
}).toString() + ")();"