mirror of
https://github.com/chaosfox26/framey.git
synced 2026-10-17 15:00:27 +02:00
167 lines
7.2 KiB
JavaScript
167 lines
7.2 KiB
JavaScript
(() => {
|
|
if (window.Framey) return;
|
|
const calls = {};
|
|
const gen = Math.random().toString(36).slice(2);
|
|
let n = 0;
|
|
let busy = false;
|
|
let tc;
|
|
const el = (css, text) => {
|
|
const e = document.createElement("div");
|
|
e.dataset.fy = "";
|
|
e.style.cssText = css;
|
|
if (text !== undefined) e.textContent = text;
|
|
return e;
|
|
};
|
|
const FY = (window.Framey = {
|
|
plugins: [],
|
|
register(p) {
|
|
p.name = p.name || FY._meta.name;
|
|
p.id = p.id || (FY._meta && FY._meta.id);
|
|
p.short = p.short || FY._meta.short || p.name.split(" ")[0];
|
|
FY.plugins.push(p);
|
|
},
|
|
call(plugin, method, arg) {
|
|
return new Promise(r => {
|
|
const id = ++n;
|
|
calls[id] = r;
|
|
window.fyCall(JSON.stringify({ gen, id, plugin, method, arg }));
|
|
});
|
|
},
|
|
_reply(g, id, v) {
|
|
const c = calls[id];
|
|
if (g !== gen || !c) return;
|
|
delete calls[id];
|
|
c(v);
|
|
},
|
|
toast(text, ms) {
|
|
if (!tc || !tc.isConnected) {
|
|
tc = el("position:fixed;left:50%;bottom:40px;transform:translateX(-50%);z-index:99999;display:flex;flex-direction:column;align-items:center;gap:8px");
|
|
document.body.append(tc);
|
|
}
|
|
const t = el("background:#101018;color:#fff;border-radius:14px;padding:14px 24px;font:22px sans-serif", text);
|
|
tc.append(t);
|
|
setTimeout(() => t.remove(), ms || 2500);
|
|
},
|
|
fail(id, e) { FY.toast("Module " + id + " failed: " + e.message, 4000); },
|
|
});
|
|
const panel = el("position:fixed;top:11px;right:11px;z-index:99999;box-sizing:border-box;width:560px;max-height:650px;background:#101018;color:#fff;border-radius:16px;padding:14px;display:none;gap:12px;font:20px sans-serif;transform:scale(.9);transform-origin:top right");
|
|
const content = el("flex:1;min-width:0;overflow:auto;max-height:620px");
|
|
const rail = el("width:104px;display:flex;flex-direction:column;gap:10px");
|
|
panel.append(content, rail);
|
|
const tile = (css, text, fn) => {
|
|
const t = el("display:flex;align-items:center;border-radius:14px;background:#2a2a3a;" + css, text);
|
|
t.onclick = fn;
|
|
return t;
|
|
};
|
|
FY.el = el;
|
|
FY.button = (text, css, fn) => tile("justify-content:center;font-weight:700;" + css, text, fn);
|
|
const style = document.createElement("style");
|
|
style.dataset.fy = "core";
|
|
style.textContent =
|
|
".fy-r{-webkit-appearance:none;height:14px;border-radius:7px;background:#2a2a3a;flex:1;margin:0 10px}" +
|
|
".fy-r::-webkit-slider-thumb{-webkit-appearance:none;width:40px;height:40px;border-radius:20px;background:#5585ff}";
|
|
document.head.append(style);
|
|
const act = (method, arg, word) => {
|
|
busy = true;
|
|
return FY.call("_core", method, arg).then(r => {
|
|
busy = false;
|
|
r = r || {};
|
|
if (r.ok) {
|
|
const m = word + (typeof r.ok === "string" ? " " + r.ok : "");
|
|
sessionStorage.fyMsg = m;
|
|
FY.toast(m);
|
|
} else FY.toast(r.error || "Failed", 4000);
|
|
return r;
|
|
});
|
|
};
|
|
const LINKED = "Linked plugin: this only removes it from Framey and does not undo anything it installed on the system.";
|
|
const settings = {
|
|
id: "#settings",
|
|
name: "Settings",
|
|
short: "Settings",
|
|
render(box) {
|
|
const status = el("font-size:18px;color:#8a8aa0;margin-top:10px", "Modules run inside Steam's UI. Install only ones you trust.");
|
|
FY.call("_core", "list").then(list => {
|
|
box.replaceChildren();
|
|
if (!Array.isArray(list)) { box.textContent = (list && list.error) || "Unavailable"; return; }
|
|
list.forEach(m => {
|
|
const row = el("display:flex;align-items:center;gap:10px;margin-bottom:10px");
|
|
const del = tile("width:60px;height:56px;justify-content:center;font-size:24px", "x", () => {
|
|
if (busy) return;
|
|
if (del.textContent === "x") { del.textContent = "?"; if (m.linked) FY.toast(LINKED, 7000); return; }
|
|
act("uninstall", m.id, "Removed");
|
|
});
|
|
row.append(
|
|
el("flex:1;font-size:21px", m.name + (m.version ? " v" + m.version : "")),
|
|
tile("width:90px;height:56px;justify-content:center;font-weight:700;background:" + (m.enabled ? "#5585ff" : "#2a2a3a"), m.enabled ? "On" : "Off", () => !busy && act(m.enabled ? "disable" : "enable", m.id, m.enabled ? "Disabled" : "Enabled")),
|
|
del,
|
|
);
|
|
box.append(row);
|
|
});
|
|
const url = document.createElement("input");
|
|
url.placeholder = "https:// link to a module .zip";
|
|
url.style.cssText = "width:100%;box-sizing:border-box;height:56px;font-size:20px;border-radius:12px;border:0;padding:0 14px;margin-top:8px";
|
|
box.append(
|
|
url,
|
|
tile("height:60px;justify-content:center;font-weight:700;background:#5585ff;margin-top:10px", "Install", () => {
|
|
if (busy) return;
|
|
status.textContent = "Installing...";
|
|
act("install", url.value, "Installed").then(r => { status.textContent = r.error ? "Failed: " + r.error : ""; });
|
|
}),
|
|
tile("height:60px;justify-content:center;font-weight:700;margin-top:10px", "Reload modules", () => !busy && act("reload", undefined, "Reloaded")),
|
|
status,
|
|
);
|
|
});
|
|
},
|
|
};
|
|
const store = {
|
|
id: "#store",
|
|
name: "Store",
|
|
short: "Store",
|
|
render(box) {
|
|
FY.call("_core", "store").then(list => {
|
|
if (!Array.isArray(list)) { box.textContent = (list && list.error) || "Unavailable"; return; }
|
|
if (!list.length) box.append(el("font-size:20px;color:#8a8aa0;text-align:center;padding:80px 0", "No modules yet"));
|
|
list.forEach(m => {
|
|
const row = el("display:flex;align-items:center;gap:10px;margin-bottom:10px");
|
|
let armed = !m.installed || !m.linked;
|
|
row.append(
|
|
el("flex:1;font-size:21px", m.name + (m.version ? " v" + m.version : "")),
|
|
tile("width:130px;height:56px;justify-content:center;font-weight:700;background:" + (m.installed ? "#2a2a3a" : "#5585ff"), m.installed ? "Remove" : "Install", () => {
|
|
if (busy) return;
|
|
if (!armed) { armed = true; FY.toast(LINKED, 7000); return; }
|
|
act(m.installed ? "uninstall" : "add", m.id, m.installed ? "Removed" : "Installed");
|
|
}),
|
|
);
|
|
box.append(row);
|
|
});
|
|
});
|
|
},
|
|
};
|
|
const tabs = () => [...FY.plugins, settings, store];
|
|
const key = p => p.id || p.short;
|
|
const show = i => {
|
|
sessionStorage.fyTab = key(tabs()[i]);
|
|
rail.replaceChildren();
|
|
tabs().forEach((p, k) => rail.append(tile("height:78px;justify-content:center;text-align:center;font-weight:700;font-size:19px;padding:0 4px;background:" + (k === i ? "#5585ff" : "#2a2a3a"), p.short, () => show(k))));
|
|
content.replaceChildren();
|
|
const body = el("");
|
|
content.append(body);
|
|
try { tabs()[i].render(body); } catch (e) { body.textContent = "Module error: " + e.message; }
|
|
};
|
|
FY.toggle = () => {
|
|
const shut = panel.style.display === "none";
|
|
panel.style.display = shut ? "flex" : "none";
|
|
sessionStorage.fyOpen = shut ? "1" : "";
|
|
if (shut) show(Math.max(0, tabs().findIndex(p => key(p) === sessionStorage.fyTab)));
|
|
};
|
|
FY.restore = () => {
|
|
if (sessionStorage.fyOpen) FY.toggle();
|
|
if (sessionStorage.fyMsg) {
|
|
FY.toast(sessionStorage.fyMsg);
|
|
sessionStorage.fyMsg = "";
|
|
}
|
|
};
|
|
document.body.append(panel);
|
|
})();
|