mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-02 19:13:56 +08:00
2246 lines
102 KiB
JavaScript
2246 lines
102 KiB
JavaScript
"use strict";
|
|
"require baseclass";
|
|
"require ui";
|
|
"require dom";
|
|
// WARN: generated by rsbuild - do not edit
|
|
|
|
// The require scope
|
|
var __webpack_require__ = {};
|
|
|
|
// webpack/runtime/define_property_getters
|
|
(() => {
|
|
__webpack_require__.d = (exports, getters, values) => {
|
|
var define = (defs, kind) => {
|
|
for(var key in defs) {
|
|
if(__webpack_require__.o(defs, key) && !__webpack_require__.o(exports, key)) {
|
|
Object.defineProperty(exports, key, { enumerable: true, [kind]: defs[key] });
|
|
}
|
|
}
|
|
};
|
|
define(getters, "get");
|
|
define(values, "value");
|
|
};
|
|
})();
|
|
// webpack/runtime/has_own_property
|
|
(() => {
|
|
__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
|
|
})();
|
|
// webpack/runtime/make_namespace_object
|
|
(() => {
|
|
// define __esModule on exports
|
|
__webpack_require__.r = (exports) => {
|
|
if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
|
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
}
|
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
};
|
|
})();
|
|
var __webpack_exports__ = {};
|
|
// ESM COMPAT FLAG
|
|
__webpack_require__.r(__webpack_exports__);
|
|
|
|
// EXPORTS
|
|
__webpack_require__.d(__webpack_exports__, {
|
|
main: () => (/* binding */ main)
|
|
});
|
|
|
|
;// CONCATENATED MODULE: ../node_modules/.pnpm/@lazulikao+luci-types@https_685a81b9ead15f36368ce6e6ebcf4a27/node_modules/@lazulikao/luci-types/src/jsx/jsx-factory.ts
|
|
const Fragment = Symbol.for("jsx.fragment");
|
|
function jsx_factory_e(e, t) {
|
|
let { children: n, ...r } = null === t || "object" != typeof t || Array.isArray(t) ? {} : t, o = function e(t, n = []) {
|
|
for (let r of t)null != r && "boolean" != typeof r && (Array.isArray(r) ? e(r, n) : n.push(r instanceof Node ? r : String(r)));
|
|
return n;
|
|
}(null == n ? [] : Array.isArray(n) ? n : [
|
|
n
|
|
]);
|
|
if (e === Fragment) {
|
|
let e = document.createDocumentFragment();
|
|
return e.append(...o), e;
|
|
}
|
|
if ("function" == typeof e) {
|
|
let t = Reflect.apply(e, void 0, [
|
|
{
|
|
...r,
|
|
children: o
|
|
}
|
|
]);
|
|
if (!(t instanceof Node)) throw TypeError("JSX components must return a DOM Node");
|
|
return t;
|
|
}
|
|
if ("string" != typeof e) throw TypeError("JSX element types must be tag names or component functions");
|
|
let f = {}, l = {
|
|
...r
|
|
};
|
|
for (let [e, t] of Object.entries(l))e.startsWith("on") && "function" == typeof t ? (f[e] = t, delete l[e]) : "boolean" == typeof t && (t ? l[e] = e : delete l[e]);
|
|
let s = Object.keys(l).length > 0 ? o.length > 1 ? E(e, l, o) : E(e, l, o[0]) : o.length > 1 ? E(e, {}, o) : E(e, {}, o[0]);
|
|
for (let [e, t] of Object.entries(f)){
|
|
let n = e.slice(2).toLowerCase();
|
|
s.addEventListener(n, t);
|
|
}
|
|
return s;
|
|
}
|
|
function jsx(t, n) {
|
|
return jsx_factory_e(t, n);
|
|
}
|
|
function jsxs(t, n) {
|
|
return jsx_factory_e(t, n);
|
|
}
|
|
function jsxDEV(t, n) {
|
|
return jsx_factory_e(t, n);
|
|
}
|
|
|
|
;// CONCATENATED MODULE: ../node_modules/.pnpm/@lazulikao+luci-types@https_685a81b9ead15f36368ce6e6ebcf4a27/node_modules/@lazulikao/luci-types/src/jsx/jsx-runtime.ts
|
|
|
|
|
|
;// CONCATENATED MODULE: ./web/resources/utils/update.ts
|
|
let update_t = L.rpc;
|
|
const callGetVersion = update_t.declare({
|
|
object: "luci.fluent",
|
|
method: "get_version"
|
|
});
|
|
const callStartDownload = update_t.declare({
|
|
object: "luci.fluent",
|
|
method: "start_download",
|
|
params: [
|
|
"url",
|
|
"i18n_url",
|
|
"i18n_urls"
|
|
]
|
|
});
|
|
const callCheckDownload = update_t.declare({
|
|
object: "luci.fluent",
|
|
method: "check_download"
|
|
});
|
|
const callDoInstall = update_t.declare({
|
|
object: "luci.fluent",
|
|
method: "do_install",
|
|
params: [
|
|
"hash",
|
|
"i18n_hash",
|
|
"i18n_hashes"
|
|
]
|
|
});
|
|
class GitHubAPIError extends Error {
|
|
status;
|
|
constructor(t, e){
|
|
super(t), this.name = "GitHubAPIError", this.status = e;
|
|
}
|
|
}
|
|
function matchI18nAsset(t, e, l) {
|
|
let s = e.toLowerCase().trim();
|
|
for (let e of t){
|
|
let t = String(e.name || "").toLowerCase();
|
|
if ("apk" === l) {
|
|
if (t.startsWith(`luci-i18n-fluent-${s}-`) && t.endsWith(".apk")) return e;
|
|
} else if ((t.startsWith(`luci-i18n-fluent-${s}_`) || t.startsWith(`luci-i18n-fluent-${s}-`)) && t.endsWith(".ipk")) return e;
|
|
}
|
|
return null;
|
|
}
|
|
async function fetchLatestRelease(t, e, l = [], s) {
|
|
let a = {};
|
|
s && (a.Authorization = `token ${s}`);
|
|
let r = await fetch("nightly" === t ? "https://api.github.com/repos/LazuliKao/luci-theme-fluent/releases/tags/nightly" : "https://api.github.com/repos/LazuliKao/luci-theme-fluent/releases/latest", {
|
|
headers: a
|
|
}), n = await r.json();
|
|
if (!r.ok) {
|
|
let t = n?.message ? `: ${n.message}` : "";
|
|
throw new GitHubAPIError(`GitHub API returned status ${r.status}${t}`, r.status);
|
|
}
|
|
let i = n.assets || [], o = null, u = [];
|
|
for (let t of i){
|
|
let l = String(t.name || "");
|
|
"apk" === e && /^luci-theme-fluent-[0-9].*\.apk$/.test(l) ? o = t : "ipk" === e && /^luci-theme-fluent_[^/]+\.ipk$/.test(l) && (o = t);
|
|
}
|
|
for (let t of l){
|
|
let l = matchI18nAsset(i, t, e);
|
|
l && !u.includes(l) && u.push(l);
|
|
}
|
|
return {
|
|
tag_name: String(n.tag_name || ""),
|
|
published_at: String(n.published_at || ""),
|
|
body: String(n.body || ""),
|
|
html_url: String(n.html_url || ""),
|
|
package_asset: o,
|
|
i18n_assets: u
|
|
};
|
|
}
|
|
|
|
;// CONCATENATED MODULE: ./web/resources/view/fluent-config/tabs/about.tsx
|
|
|
|
let about_l = L.form, about_n = "https://ghfast.top/";
|
|
|
|
let about_u = about_l.DummyValue.extend({
|
|
renderWidget: (l, u, p)=>{
|
|
let h = "1.0.1", b = "ipk", f = [], g = jsxs("div", {
|
|
class: "fluent-about-logo",
|
|
children: [
|
|
jsx("img", {
|
|
src: `${L.media()}/img/fluent.svg`,
|
|
alt: "Fluent Theme Logo"
|
|
}),
|
|
jsx("h2", {
|
|
children: "Fluent Theme"
|
|
}),
|
|
jsx("p", {
|
|
class: "fluent-about-subtitle",
|
|
children: _("Fluent Design 2 theme for LuCI")
|
|
})
|
|
]
|
|
}), y = jsxs("div", {
|
|
class: "fluent-about-details",
|
|
children: [
|
|
jsxs("div", {
|
|
class: "fluent-about-detail-row",
|
|
children: [
|
|
jsxs("strong", {
|
|
children: [
|
|
_("Author"),
|
|
":"
|
|
]
|
|
}),
|
|
jsx("span", {
|
|
children: "LazuliKao"
|
|
})
|
|
]
|
|
}),
|
|
jsxs("div", {
|
|
class: "fluent-about-detail-row",
|
|
children: [
|
|
jsxs("strong", {
|
|
children: [
|
|
_("Installed version"),
|
|
":"
|
|
]
|
|
}),
|
|
jsx("span", {
|
|
class: "fluent-about-current-version",
|
|
children: "v..."
|
|
})
|
|
]
|
|
}),
|
|
jsxs("div", {
|
|
class: "fluent-about-detail-row",
|
|
children: [
|
|
jsxs("strong", {
|
|
children: [
|
|
_("Package format"),
|
|
":"
|
|
]
|
|
}),
|
|
jsx("span", {
|
|
class: "fluent-about-pkg-type",
|
|
children: "..."
|
|
})
|
|
]
|
|
}),
|
|
jsxs("div", {
|
|
class: "fluent-about-detail-row",
|
|
children: [
|
|
jsxs("strong", {
|
|
children: [
|
|
_("Source code"),
|
|
":"
|
|
]
|
|
}),
|
|
jsx("span", {
|
|
children: jsx("a", {
|
|
href: "https://github.com/LazuliKao/luci-theme-fluent",
|
|
target: "_blank",
|
|
rel: "noreferrer",
|
|
children: "GitHub Repository"
|
|
})
|
|
})
|
|
]
|
|
})
|
|
]
|
|
}), m = jsxs("select", {
|
|
class: "cbi-input-select",
|
|
id: "update-channel-select",
|
|
children: [
|
|
jsx("option", {
|
|
value: "stable",
|
|
children: _("Stable Channel")
|
|
}),
|
|
jsx("option", {
|
|
value: "nightly",
|
|
children: _("Nightly Channel (Prerelease)")
|
|
})
|
|
]
|
|
}), w = jsxs("select", {
|
|
class: "cbi-input-select",
|
|
id: "update-method-select",
|
|
children: [
|
|
jsx("option", {
|
|
value: "backend_official",
|
|
children: _("Backend (Official GitHub)")
|
|
}),
|
|
jsx("option", {
|
|
value: "backend_ghproxy",
|
|
children: _("Backend (GHProxy Acceleration)")
|
|
})
|
|
]
|
|
}), v = jsx("button", {
|
|
class: "btn cbi-button cbi-button-action",
|
|
type: "button",
|
|
children: _("Check for updates")
|
|
}), k = jsxs("div", {
|
|
class: "fluent-update-controls",
|
|
style: "display: flex; gap: 15px; align-items: center; flex-wrap: wrap;",
|
|
children: [
|
|
jsxs("div", {
|
|
class: "fluent-update-channel-selector",
|
|
children: [
|
|
jsxs("label", {
|
|
htmlFor: "update-channel-select",
|
|
children: [
|
|
_("Update channel"),
|
|
": "
|
|
]
|
|
}),
|
|
m
|
|
]
|
|
}),
|
|
v
|
|
]
|
|
}), x = jsx("div", {
|
|
class: "fluent-update-status",
|
|
style: "display: none"
|
|
}), T = jsx("div", {
|
|
class: "fluent-progress-bar__fill",
|
|
style: "width: 0%"
|
|
}), C = jsx("div", {
|
|
class: "fluent-progress-bar",
|
|
style: "display: none",
|
|
children: T
|
|
}), S = jsx("div", {
|
|
class: "fluent-progress-text",
|
|
style: "display: none"
|
|
}), $ = jsx("div", {
|
|
class: "fluent-update-card",
|
|
style: "display: none"
|
|
}), F = jsxs("div", {
|
|
class: "fluent-about-manager",
|
|
children: [
|
|
g,
|
|
y,
|
|
jsx("hr", {
|
|
class: "fluent-about-divider"
|
|
}),
|
|
jsx("h3", {
|
|
children: _("Software Update")
|
|
}),
|
|
k,
|
|
x,
|
|
$,
|
|
C,
|
|
S
|
|
]
|
|
}), P = (e, t = "info")=>{
|
|
dom.content(x, [
|
|
document.createTextNode(e)
|
|
]), x.className = `fluent-update-status status-${t}`, x.style.display = "block";
|
|
}, A = (e, t, a)=>{
|
|
C.style.display = "block", S.style.display = "block", T.style.width = `${t}%`, T.className = `fluent-progress-bar__fill fill-${e}`, dom.content(S, [
|
|
document.createTextNode(`${a} (${t}%)`)
|
|
]);
|
|
}, z = ()=>{
|
|
C.style.display = "none", S.style.display = "none";
|
|
};
|
|
(async ()=>{
|
|
try {
|
|
let e = await callGetVersion();
|
|
h = e.version, b = e.pkg_type, f = e.installed_i18n || (e.i18n_zh_cn_installed ? [
|
|
"zh-cn"
|
|
] : []);
|
|
let t = y.querySelector(".fluent-about-current-version");
|
|
t && (t.textContent = `v${h}`);
|
|
let a = y.querySelector(".fluent-about-pkg-type");
|
|
a && (a.textContent = "apk" === b ? "APK (OpenWrt 25.12+)" : "IPK (OpenWrt 24.10)");
|
|
} catch (e) {
|
|
console.error("Failed to fetch version", e), P(_("Failed to fetch current theme version."), "error");
|
|
}
|
|
})();
|
|
let N = async (l)=>{
|
|
let s = m.value;
|
|
try {
|
|
let c = await fetchLatestRelease(s, b, f, l);
|
|
console.log(c), v.disabled = !1;
|
|
let u = h.replace(/^v/, "").trim(), p = c.tag_name.replace(/^v/, "").trim(), g = L.naturalCompare(p, u) > 0, y = !g && "nightly" !== s;
|
|
if (y ? P(_("Your theme is up to date!"), "success") : P(g ? _("A new version is available!") : _("Nightly build available (reinstallation check)."), g ? "warn" : "info"), !c.package_asset) return void P(_("No matching package asset found for your system architecture in this release."), "error");
|
|
let k = c.body ? c.body : "", x = jsx("button", {
|
|
class: "btn cbi-button cbi-button-save",
|
|
type: "button",
|
|
style: "white-space: nowrap;",
|
|
children: _("Download and Install")
|
|
}), T = [
|
|
jsxs("div", {
|
|
class: "fluent-update-header",
|
|
children: [
|
|
jsx("span", {
|
|
class: "fluent-update-badge",
|
|
children: "nightly" === p ? "Nightly" : `v${p}`
|
|
}),
|
|
jsx("span", {
|
|
class: "fluent-update-date",
|
|
children: c.published_at.split("T")[0]
|
|
})
|
|
]
|
|
})
|
|
];
|
|
k && T.push(jsx("pre", {
|
|
class: "fluent-update-changelog",
|
|
children: k
|
|
})), y || T.push(jsxs("div", {
|
|
class: "fluent-update-footer",
|
|
style: "display: flex; align-items: center; justify-content: flex-end; gap: 10px; margin-top: 15px; flex-wrap: wrap;",
|
|
children: [
|
|
w,
|
|
x
|
|
]
|
|
})), dom.content($, T), $.style.display = "block", x.addEventListener("click", async ()=>{
|
|
let l = c.package_asset;
|
|
if (!l) return void P(_("No matching package asset found for your system architecture in this release."), "error");
|
|
x.disabled = !0, m.disabled = !0, w.disabled = !0, v.disabled = !0, P(_("Starting update process..."), "info");
|
|
let s = c.i18n_assets || [], d = null, u = [];
|
|
for (let e of (l.digest?.startsWith("sha256:") ? d = l.digest.replace("sha256:", "") : (console.warn("Unable to determine expected package hash from digest. Skipping verification."), d = "skip"), s))e.digest?.startsWith("sha256:") ? u.push(e.digest.replace("sha256:", "")) : u.push("skip");
|
|
let p = async ()=>{
|
|
let c = w.value.includes("ghproxy");
|
|
P(_("Starting backend download..."), "info"), A("download", 0, _("Downloading on router"));
|
|
let p = c ? about_n + l.browser_download_url : l.browser_download_url, h = s.map((e)=>c ? about_n + e.browser_download_url : e.browser_download_url).join(" "), b = await callStartDownload(p, h.split(" ")[0] || "", h);
|
|
if (0 !== b.result) throw Error(b.message || "Failed to start router download.");
|
|
for(;;){
|
|
let e = await callCheckDownload(), t = e.size || 0, a = s.reduce((e, t)=>e + (t.size || 0), 0), n = l.size + a, i = n > 0 ? Math.min(Math.round(t / n * 100), 100) : 0;
|
|
if (A("download", i, `${_("Downloading on router")} (${(t / 1024).toFixed(0)} / ${(n / 1024).toFixed(0)} KB)`), !e.running) if (0 !== e.code) throw Error("Router background download failed or file is empty.");
|
|
else break;
|
|
await new Promise((e)=>setTimeout(e, 1000));
|
|
}
|
|
if (!await new Promise((l)=>{
|
|
L.ui.showModal(_("Confirm Installation"), jsxs(Fragment, {
|
|
children: [
|
|
jsx("div", {
|
|
children: jsx("p", {
|
|
children: _("The theme package is ready. Are you sure you want to proceed with the installation?")
|
|
})
|
|
}),
|
|
jsxs("div", {
|
|
class: "right",
|
|
children: [
|
|
jsx("button", {
|
|
type: "button",
|
|
class: "btn",
|
|
onclick: ()=>{
|
|
L.ui.hideModal(), l(!1);
|
|
},
|
|
children: _("Cancel")
|
|
}),
|
|
jsx("button", {
|
|
type: "button",
|
|
class: "btn cbi-button-action",
|
|
onclick: ()=>{
|
|
L.ui.hideModal(), l(!0);
|
|
},
|
|
children: _("Continue")
|
|
})
|
|
]
|
|
})
|
|
]
|
|
}));
|
|
})) throw Error(_("Installation cancelled by user."));
|
|
P(_("Triggering installation on router..."), "info"), A("install", 100, _("Installing package"));
|
|
let f = await callDoInstall(d, u[0] || "", u.join(" "));
|
|
if (0 !== f.result) throw Error(f.message || "Router installation failed.");
|
|
let g = jsx("pre", {
|
|
style: "background: var(--fluent-code-bg, #1a1a1a); color: var(--fluent-text, #fff); padding: 10px; border-radius: 4px; font-family: monospace; font-size: 12px; white-space: pre-wrap; word-wrap: break-word; max-height: 200px; overflow-y: auto; margin-top: 15px;"
|
|
});
|
|
(async ()=>{
|
|
let e = L.env.ubuspath || "/ubus", t = L.env.sessionid;
|
|
for(;;)try {
|
|
let a = await fetch(e, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json"
|
|
},
|
|
body: JSON.stringify({
|
|
jsonrpc: "2.0",
|
|
id: 1,
|
|
method: "call",
|
|
params: [
|
|
t,
|
|
"luci.fluent",
|
|
"get_install_log",
|
|
{}
|
|
]
|
|
})
|
|
}), l = await a.json();
|
|
if (l.error) break;
|
|
l.result?.[1]?.log && (g.textContent = l.result[1].log, g.scrollTop = g.scrollHeight), await new Promise((e)=>setTimeout(e, 1000));
|
|
} catch {
|
|
break;
|
|
}
|
|
})(), P(_("Theme successfully updated! Reloading RPC service, please refresh the page in 5 seconds."), "success"), A("done", 100, _("Finished")), dom.content($, [
|
|
jsxs("div", {
|
|
class: "fluent-update-success",
|
|
children: [
|
|
jsx("h3", {
|
|
style: "color: var(--fluent-success); text-align: center;",
|
|
children: _("Upgrade Successful!")
|
|
}),
|
|
jsx("p", {
|
|
style: "text-align: center; margin: 15px 0;",
|
|
children: _("The theme is being installed on your router. The logs are displayed below. Reloading the web interface will apply the changes once RPCD restarts.")
|
|
}),
|
|
g,
|
|
jsx("div", {
|
|
style: "display: flex; justify-content: center; margin-top: 20px;",
|
|
children: jsx("button", {
|
|
class: "btn cbi-button-action",
|
|
type: "button",
|
|
style: "margin-top: 15px",
|
|
onclick: ()=>{
|
|
window.location.reload();
|
|
},
|
|
children: _("Reload Web Interface")
|
|
})
|
|
})
|
|
]
|
|
})
|
|
]);
|
|
};
|
|
try {
|
|
await p();
|
|
} catch (e) {
|
|
console.error("Update failed", e), P(`${_("Update failed")}: ${e instanceof Error ? e.message : String(e)}`, "error"), x.disabled = !1, x.removeAttribute("disabled"), m.disabled = !1, m.removeAttribute("disabled"), w.disabled = !1, w.removeAttribute("disabled"), v.disabled = !1, v.removeAttribute("disabled"), z();
|
|
}
|
|
});
|
|
} catch (l) {
|
|
if (v.disabled = !1, console.error("Failed checking updates", l), l instanceof GitHubAPIError && 403 === l.status) {
|
|
let n = jsx("input", {
|
|
type: "text",
|
|
class: "cbi-input-text",
|
|
style: "width: 100%",
|
|
placeholder: "ghp_..."
|
|
}), r = jsx("p", {
|
|
children: _("API rate limit exceeded. Please enter a GitHub Token to continue. This token is only used for frontend requests and will not be saved on the router backend.")
|
|
}), i = jsxs("p", {
|
|
style: "margin-top: 10px; font-size: 13px; color: var(--fluent-text-secondary);",
|
|
children: [
|
|
_("You can create a new token at"),
|
|
" ",
|
|
jsx("a", {
|
|
href: "https://github.com/settings/personal-access-tokens",
|
|
target: "_blank",
|
|
rel: "noopener noreferrer",
|
|
children: "https://github.com/settings/personal-access-tokens/new"
|
|
}),
|
|
".",
|
|
_("The token does NOT require any permissions/scopes to be granted (read-only public access is sufficient).")
|
|
]
|
|
}), s = jsx("pre", {
|
|
style: "margin-top: 10px; margin-bottom: 15px; font-size: 12px; white-space: pre-wrap; word-break: break-word; color: var(--fluent-error-text); background: var(--fluent-card-bg); padding: 8px; border-radius: var(--fluent-border-radius);",
|
|
children: l instanceof Error ? l.message : String(l)
|
|
});
|
|
L.ui.showModal(_("GitHub Token Required"), jsxs(Fragment, {
|
|
children: [
|
|
r,
|
|
i,
|
|
s,
|
|
n,
|
|
jsxs("div", {
|
|
class: "right",
|
|
children: [
|
|
jsx("button", {
|
|
type: "button",
|
|
class: "btn",
|
|
onclick: ()=>L.ui.hideModal(),
|
|
children: _("Cancel")
|
|
}),
|
|
jsx("button", {
|
|
type: "button",
|
|
class: "btn cbi-button-save",
|
|
onclick: ()=>{
|
|
let e = n.value.trim();
|
|
L.ui.hideModal(), e && (P(_("Retrying with token..."), "info"), v.disabled = !0, N(e));
|
|
},
|
|
children: _("Submit")
|
|
})
|
|
]
|
|
})
|
|
]
|
|
}));
|
|
} else P(`${_("Failed to check for updates")}: ${l instanceof Error ? l.message : String(l)}`, "error");
|
|
}
|
|
};
|
|
return v.addEventListener("click", ()=>{
|
|
P(_("Checking for updates..."), "info"), v.disabled = !0, $.style.display = "none", z(), N();
|
|
}), F;
|
|
}
|
|
});
|
|
const registerAboutTab = (e)=>{
|
|
e.tab("about", _("About")), e.taboption("about", about_u, "_about_mgr");
|
|
};
|
|
|
|
;// CONCATENATED MODULE: ./web/resources/fluent-defaults.ts
|
|
const FLUENT_DEFAULTS = {
|
|
mode: "auto",
|
|
primary: "#0078d4",
|
|
dark_primary: "#4da6ff",
|
|
progressbar_font: "#2e2b60",
|
|
dark_progressbar_font: "#d6d9e5",
|
|
page_bg: "#fafafa",
|
|
card_bg: "#ffffff",
|
|
sidebar_bg: "#f3f3f3",
|
|
dark_page_bg: "#1b1b1b",
|
|
dark_card_bg: "#2d2d2d",
|
|
dark_sidebar_bg: "#1f1f1f",
|
|
font_weight: "normal",
|
|
font_size: "14",
|
|
border_radius: "4",
|
|
control_height: "32",
|
|
sidebar_width: "260",
|
|
header_height: "48",
|
|
login_bg: "microsoft",
|
|
blur: "40",
|
|
transparency: "0.8",
|
|
blur_dark: "20",
|
|
transparency_dark: "0.8",
|
|
card_shadow: "small",
|
|
transition_speed: "normal",
|
|
custom_css: "",
|
|
custom_select: "1",
|
|
view_transition: "1",
|
|
tab_animation: "1",
|
|
prefers_reduced_motion: "1",
|
|
loading_bar: "1",
|
|
sticky_action_bar: "1",
|
|
uci_changes_preview: "1",
|
|
direction_mode: "auto",
|
|
progressbar_text_position: "top-start"
|
|
};
|
|
const fluentFlagDefault = (r)=>"1" === r;
|
|
|
|
;// CONCATENATED MODULE: ./web/resources/view/fluent-config/shared.ts
|
|
let shared_e = L.form;
|
|
const omitDefaultValue = (e)=>{
|
|
let t = e.parse.bind(e);
|
|
e.parse = (r)=>e.isActive(r) && e.formvalue(r) === e.default ? e.isValid(r) ? (e.remove(r), Promise.resolve()) : t(r) : t(r);
|
|
};
|
|
const transparencySteps = [
|
|
0,
|
|
0.1,
|
|
0.2,
|
|
0.3,
|
|
0.4,
|
|
0.5,
|
|
0.6,
|
|
0.7,
|
|
0.8,
|
|
0.9,
|
|
1
|
|
];
|
|
let shared_t = "fluent-live-preview", shared_r = /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i, shared_a = {
|
|
primary: {
|
|
cssVar: "--fluent-primary",
|
|
isDark: !1
|
|
},
|
|
dark_primary: {
|
|
cssVar: "--fluent-primary",
|
|
isDark: !0
|
|
},
|
|
page_bg: {
|
|
cssVar: "--fluent-bg",
|
|
isDark: !1
|
|
},
|
|
dark_page_bg: {
|
|
cssVar: "--fluent-bg",
|
|
isDark: !0
|
|
},
|
|
card_bg: {
|
|
cssVar: "--fluent-bg-card",
|
|
isDark: !1
|
|
},
|
|
dark_card_bg: {
|
|
cssVar: "--fluent-bg-card",
|
|
isDark: !0
|
|
},
|
|
sidebar_bg: {
|
|
cssVar: "--fluent-sidebar-bg",
|
|
isDark: !1
|
|
},
|
|
dark_sidebar_bg: {
|
|
cssVar: "--fluent-sidebar-bg",
|
|
isDark: !0
|
|
},
|
|
progressbar_font: {
|
|
cssVar: "--fluent-progressbar-font-color",
|
|
isDark: !1
|
|
},
|
|
dark_progressbar_font: {
|
|
cssVar: "--fluent-progressbar-font-color",
|
|
isDark: !0
|
|
}
|
|
}, shared_l = new globalThis.Map();
|
|
const configureHexColorValue = (s, o, n = !1)=>{
|
|
s.validate = (e, t)=>!e || shared_r.test(String(t)) || _("Expecting: %s").format(_("valid HEX color value")), s.render = (i, c, u)=>{
|
|
let d = shared_e.Value.prototype.render.call(s, i, c, u), p = ()=>{
|
|
let e = document.querySelector(`[id^="widget.cbid.fluent."][id$=".${o}"]`);
|
|
e && ((e, t)=>{
|
|
if ("true" === e.dataset.fluentColorPicker) return;
|
|
let a = e.parentElement;
|
|
if (!a) return;
|
|
e.dataset.fluentColorPicker = "true", e.classList.add("fluent-color-field__text");
|
|
let l = document.createElement("div");
|
|
l.className = "fluent-color-field";
|
|
let s = document.createElement("label");
|
|
s.className = "fluent-color-swatch", s.title = _("Choose color");
|
|
let o = document.createElement("input");
|
|
o.type = "color", o.className = "fluent-color-swatch__input", o.setAttribute("aria-label", _("Choose color"));
|
|
let n = document.createElement("span");
|
|
n.className = "fluent-color-swatch__preview";
|
|
let i = (e)=>{
|
|
shared_r.test(e) && (o.value = e, n.style.backgroundColor = e);
|
|
};
|
|
i(e.value), o.addEventListener("input", ()=>{
|
|
e.value = o.value, n.style.backgroundColor = o.value, shared_r.test(o.value) && t(o.value);
|
|
}), e.addEventListener("input", ()=>{
|
|
i(e.value), shared_r.test(e.value) && t(e.value);
|
|
}), s.appendChild(o), s.appendChild(n), a.insertBefore(l, e), l.appendChild(e), l.appendChild(s);
|
|
})(e, (e)=>((e, r)=>{
|
|
let s = shared_a[e];
|
|
if (!s) return;
|
|
let o = `${s.isDark ? "dark" : "light"}|${s.cssVar}`;
|
|
shared_l.set(o, {
|
|
selector: s.isDark ? ':root[data-theme="dark"]' : ":root",
|
|
cssVar: s.cssVar,
|
|
value: r
|
|
}), (()=>{
|
|
let e, r = ((e = document.getElementById(shared_t)) || ((e = document.createElement("style")).id = shared_t, document.head.appendChild(e)), e), a = new globalThis.Map();
|
|
for (let e of shared_l.values()){
|
|
let t = a.get(e.selector) ?? [];
|
|
t.push(`${e.cssVar}: ${e.value};`), a.set(e.selector, t);
|
|
}
|
|
let s = "";
|
|
for (let [e, t] of a)s += `${e} { ${t.join(" ")} }\n`;
|
|
r.textContent = s;
|
|
})();
|
|
})(o, e));
|
|
};
|
|
return n ? requestAnimationFrame(p) : setTimeout(p, 0), d;
|
|
};
|
|
};
|
|
const createModeSubtabs = (t, r, a)=>{
|
|
let l = t.taboption(r, shared_e.SectionValue, a, shared_e.TypedSection, "global").subsection;
|
|
return l.anonymous = !0, l.addremove = !1, l.tab("light", _("Light mode")), l.tab("dark", _("Dark mode")), l;
|
|
};
|
|
|
|
;// CONCATENATED MODULE: ./web/resources/view/fluent-config/tabs/advanced.ts
|
|
let advanced_e = L.form;
|
|
|
|
|
|
const registerAdvancedTab = (d)=>{
|
|
d.tab("advanced", _("Advanced"), _("Adjust layout, typography, transition timing, shadows, and inject custom CSS variables or rules when the built-in controls are not enough."));
|
|
{
|
|
let o = d.taboption("advanced", advanced_e.Value, "font_size", _("Base font size"), _("Sets the base interface font size in pixels. Most theme text scales from this value through the Fluent CSS variables. Recommended range: 12-18px."));
|
|
o.datatype = "range(12,18)", o.default = FLUENT_DEFAULTS.font_size, omitDefaultValue(o), o.placeholder = FLUENT_DEFAULTS.font_size;
|
|
}
|
|
{
|
|
let o = d.taboption("advanced", advanced_e.Value, "sidebar_width", _("Sidebar width"), _("Width of the main navigation sidebar in pixels."));
|
|
o.datatype = "range(200,420)", o.default = FLUENT_DEFAULTS.sidebar_width, omitDefaultValue(o), o.placeholder = FLUENT_DEFAULTS.sidebar_width;
|
|
}
|
|
{
|
|
let o = d.taboption("advanced", advanced_e.Value, "header_height", _("Header height"), _("Height of the top header bar in pixels."));
|
|
o.datatype = "range(40,96)", o.default = FLUENT_DEFAULTS.header_height, omitDefaultValue(o), o.placeholder = FLUENT_DEFAULTS.header_height;
|
|
}
|
|
{
|
|
let o = d.taboption("advanced", advanced_e.ListValue, "border_radius", _("Corner radius"), _("Controls the shared Fluent corner radius tokens used by cards, buttons, inputs, and related UI surfaces."));
|
|
o.value("0", _("Square (0px)")), o.value("2", _("Small (2px)")), o.value("4", _("Medium (4px)")), o.value("6", _("Rounded (6px)")), o.value("8", _("Large (8px)")), o.value("12", _("Extra large (12px)")), o.default = FLUENT_DEFAULTS.border_radius, omitDefaultValue(o);
|
|
}
|
|
{
|
|
let o = d.taboption("advanced", advanced_e.ListValue, "card_shadow", _("Card shadow"), _("Select the shadow depth applied to themed cards and panels."));
|
|
o.value("none", _("None")), o.value("small", _("Small")), o.value("medium", _("Medium")), o.value("large", _("Large")), o.default = FLUENT_DEFAULTS.card_shadow, omitDefaultValue(o);
|
|
}
|
|
{
|
|
let o = d.taboption("advanced", advanced_e.ListValue, "transition_speed", _("Theme transition speed"), _("Controls the shared Fluent transition timing used by menu, header, and other theme animations."));
|
|
o.value("fast", _("Fast")), o.value("normal", _("Normal")), o.value("slow", _("Slow")), o.value("none", _("Disabled")), o.default = FLUENT_DEFAULTS.transition_speed, omitDefaultValue(o);
|
|
}
|
|
{
|
|
let o = d.taboption("advanced", advanced_e.TextValue, "custom_css", _("Custom CSS"), _("Optional raw CSS injected into the Fluent header template. Use this for extra --fluent-* variable overrides or page-specific tweaks that are not exposed as dedicated options."));
|
|
o.default = FLUENT_DEFAULTS.custom_css, omitDefaultValue(o), o.rmempty = !0, o.rows = 12, o.wrap = "off", o.placeholder = ":root {\n --fluent-sidebar-width: 280px;\n --fluent-card-shadow: none;\n}";
|
|
}
|
|
};
|
|
|
|
;// CONCATENATED MODULE: ./web/resources/view/fluent-config/tabs/animation.ts
|
|
let animation_e = L.form;
|
|
|
|
|
|
const registerAnimationTab = (i)=>{
|
|
i.tab("animation", _("Animation"));
|
|
{
|
|
let o = i.taboption("animation", animation_e.Flag, "view_transition", _("Enable page transition animation"), _("Use the browser View Transition API to animate navigation between LuCI pages when supported."));
|
|
o.default = fluentFlagDefault(FLUENT_DEFAULTS.view_transition) ? o.enabled : o.disabled, omitDefaultValue(o);
|
|
}
|
|
{
|
|
let o = i.taboption("animation", animation_e.Flag, "tab_animation", _("Enable tab underline animation"), _("Animate the active underline when switching between native LuCI tabs and themed tab menus."));
|
|
o.default = fluentFlagDefault(FLUENT_DEFAULTS.tab_animation) ? o.enabled : o.disabled, omitDefaultValue(o);
|
|
}
|
|
{
|
|
let o = i.taboption("animation", animation_e.Flag, "prefers_reduced_motion", _("Respect reduced-motion preference"), _("When enabled, Fluent animations follow the browser or operating system reduced-motion preference."));
|
|
o.default = fluentFlagDefault(FLUENT_DEFAULTS.prefers_reduced_motion) ? o.enabled : o.disabled, omitDefaultValue(o);
|
|
}
|
|
{
|
|
let o = i.taboption("animation", animation_e.Flag, "loading_bar", _("Show top loading bar"), _("Display the themed loading indicator at the top edge during page loads and transitions."));
|
|
o.default = fluentFlagDefault(FLUENT_DEFAULTS.loading_bar) ? o.enabled : o.disabled, omitDefaultValue(o);
|
|
}
|
|
{
|
|
let o = i.taboption("animation", animation_e.Flag, "uci_changes_preview", _("Show UCI changes while applying configuration"), _("Show pending UCI changes in the top apply-status message until the configuration apply completes."));
|
|
o.default = fluentFlagDefault(FLUENT_DEFAULTS.uci_changes_preview) ? o.enabled : o.disabled, omitDefaultValue(o);
|
|
}
|
|
};
|
|
|
|
;// CONCATENATED MODULE: ./web/resources/view/fluent-config/tabs/colors.ts
|
|
let colors_r = L.form;
|
|
|
|
|
|
const registerColorsTab = (d)=>{
|
|
d.tab("colors", _("Colors"));
|
|
let l = createModeSubtabs(d, "colors", "colors_mode_tabs");
|
|
{
|
|
let o = l.taboption("light", colors_r.Value, "primary", _("Accent color"), _("HEX color used as the primary Fluent accent when the interface is rendered in light mode."));
|
|
o.default = FLUENT_DEFAULTS.primary, omitDefaultValue(o), configureHexColorValue(o, "primary");
|
|
}
|
|
{
|
|
let o = l.taboption("light", colors_r.Value, "progressbar_font", _("Progress bar text color"), _("HEX color used for progress-bar labels while the interface is rendered in light mode."));
|
|
o.default = FLUENT_DEFAULTS.progressbar_font, omitDefaultValue(o), configureHexColorValue(o, "progressbar_font");
|
|
}
|
|
{
|
|
let o = l.taboption("light", colors_r.Value, "page_bg", _("Page background"), _("HEX color used for the main page background in light mode."));
|
|
o.default = FLUENT_DEFAULTS.page_bg, omitDefaultValue(o), configureHexColorValue(o, "page_bg");
|
|
}
|
|
{
|
|
let o = l.taboption("light", colors_r.Value, "card_bg", _("Card background"), _("HEX color used for container/card elements in light mode."));
|
|
o.default = FLUENT_DEFAULTS.card_bg, omitDefaultValue(o), configureHexColorValue(o, "card_bg");
|
|
}
|
|
{
|
|
let o = l.taboption("light", colors_r.Value, "sidebar_bg", _("Sidebar background"), _("HEX color used for the navigation sidebar in light mode."));
|
|
o.default = FLUENT_DEFAULTS.sidebar_bg, omitDefaultValue(o), configureHexColorValue(o, "sidebar_bg");
|
|
}
|
|
{
|
|
let o = l.taboption("dark", colors_r.Value, "dark_primary", _("Accent color"), _("HEX color used as the primary Fluent accent when the interface is rendered in dark mode."));
|
|
o.default = FLUENT_DEFAULTS.dark_primary, omitDefaultValue(o), configureHexColorValue(o, "dark_primary", !0);
|
|
}
|
|
{
|
|
let o = l.taboption("dark", colors_r.Value, "dark_progressbar_font", _("Progress bar text color"), _("HEX color used for progress-bar labels while the interface is rendered in dark mode."));
|
|
o.default = FLUENT_DEFAULTS.dark_progressbar_font, omitDefaultValue(o), configureHexColorValue(o, "dark_progressbar_font", !0);
|
|
}
|
|
{
|
|
let o = l.taboption("dark", colors_r.Value, "dark_page_bg", _("Page background"), _("HEX color used for the main page background in dark mode."));
|
|
o.default = FLUENT_DEFAULTS.dark_page_bg, omitDefaultValue(o), configureHexColorValue(o, "dark_page_bg", !0);
|
|
}
|
|
{
|
|
let o = l.taboption("dark", colors_r.Value, "dark_card_bg", _("Card background"), _("HEX color used for container/card elements in dark mode."));
|
|
o.default = FLUENT_DEFAULTS.dark_card_bg, omitDefaultValue(o), configureHexColorValue(o, "dark_card_bg", !0);
|
|
}
|
|
{
|
|
let o = l.taboption("dark", colors_r.Value, "dark_sidebar_bg", _("Sidebar background"), _("HEX color used for the navigation sidebar in dark mode."));
|
|
o.default = FLUENT_DEFAULTS.dark_sidebar_bg, omitDefaultValue(o), configureHexColorValue(o, "dark_sidebar_bg", !0);
|
|
}
|
|
};
|
|
|
|
;// CONCATENATED MODULE: ./web/resources/view/fluent-config/tabs/general.ts
|
|
let general_e = L.form;
|
|
|
|
|
|
const registerGeneralTab = (l, r = !0)=>{
|
|
l.tab("general", _("General"));
|
|
{
|
|
let o = l.taboption("general", general_e.ListValue, "mode", _("Color mode"));
|
|
o.value("auto", _("Follow system")), o.value("light", _("Force light mode")), o.value("dark", _("Force dark mode")), o.default = FLUENT_DEFAULTS.mode, omitDefaultValue(o), o.description = _("Use the system/browser preference, or always render the Fluent theme in a fixed light or dark palette.");
|
|
}
|
|
{
|
|
let o = l.taboption("general", general_e.ListValue, "direction_mode", _("Text direction"));
|
|
o.value("auto", _("Automatic (Arabic/Persian locales only)")), o.value("rtl", _("Force RTL")), o.value("ltr", _("Force LTR")), o.default = FLUENT_DEFAULTS.direction_mode, omitDefaultValue(o), o.description = _("Choose the document direction for authenticated and login pages. Automatic mode resolves Arabic and Persian locale codes (ar, ar_*, ar-*, fa, fa_*, fa-*) to RTL and falls back to LTR for missing or unrecognized locales.");
|
|
}
|
|
{
|
|
let o = l.taboption("general", general_e.ListValue, "font_weight", _("Navigation font weight"));
|
|
o.value("normal", _("Normal")), o.value("600", _("Semibold")), o.default = FLUENT_DEFAULTS.font_weight, omitDefaultValue(o), o.description = _("Controls the font weight used by main navigation labels and related theme text accents.");
|
|
}
|
|
{
|
|
let o = l.taboption("general", general_e.ListValue, "control_height", _("Control height"));
|
|
o.value("32", _("Compact (32px)")), o.value("42", _("Comfortable (42px)")), o.default = FLUENT_DEFAULTS.control_height, omitDefaultValue(o), o.description = _("Applies to standard buttons, inputs, selects, and similar form controls across the theme.");
|
|
}
|
|
if (r) {
|
|
let r = l.taboption("general", general_e.Flag, "custom_select", _("Use Fluent custom select dropdowns"), _("Replace native select elements with the theme's custom dropdown widget."));
|
|
r.default = fluentFlagDefault(FLUENT_DEFAULTS.custom_select) ? r.enabled : r.disabled, omitDefaultValue(r);
|
|
}
|
|
{
|
|
let r = l.taboption("general", general_e.Flag, "sticky_action_bar", _("Keep action bar visible while scrolling"), _("Keep Save, Apply, and Reset actions fixed at the bottom of the viewport while scrolling."));
|
|
r.default = fluentFlagDefault(FLUENT_DEFAULTS.sticky_action_bar) ? r.enabled : r.disabled, omitDefaultValue(r);
|
|
}
|
|
{
|
|
let o = l.taboption("general", general_e.ListValue, "progressbar_text_position", _("Progress bar text position"));
|
|
o.value("top-start", _("Above bar, start")), o.value("bottom-start", _("Below bar, start")), o.value("top-end", _("Above bar, end")), o.value("bottom-end", _("Below bar, end")), o.default = FLUENT_DEFAULTS.progressbar_text_position, omitDefaultValue(o), o.description = _("Position of progress-bar labels relative to the bar. Start/end alignment follows the text direction (LTR or RTL).");
|
|
}
|
|
};
|
|
|
|
;// CONCATENATED MODULE: ./web/resources/view/fluent-config/tabs/login.tsx
|
|
|
|
let login_a = L.form, login_n = L.rpc, login_l = L.fs, login_i = L.ui, login_o = login_n.declare({
|
|
object: "luci.fluent",
|
|
method: "avail",
|
|
expect: {
|
|
avail: 0
|
|
}
|
|
}), login_r = login_n.declare({
|
|
object: "luci.fluent",
|
|
method: "remove",
|
|
params: [
|
|
"filename"
|
|
],
|
|
expect: {
|
|
result: 0
|
|
}
|
|
}), login_d = login_n.declare({
|
|
object: "luci.fluent",
|
|
method: "rename",
|
|
params: [
|
|
"newname"
|
|
],
|
|
expect: {
|
|
result: 0
|
|
}
|
|
});
|
|
|
|
|
|
let login_p = new Set([
|
|
"jpg",
|
|
"jpeg",
|
|
"png",
|
|
"gif",
|
|
"webp",
|
|
"mp4",
|
|
"webm"
|
|
]), login_b = (e)=>e.split(".").pop()?.toLowerCase() ?? "", login_m = (e)=>login_p.has(login_b(e)), login_f = (e)=>e >= 1048576 ? `${(e / 1024 / 1024).toFixed(1)} GiB` : e >= 1024 ? `${(e / 1024).toFixed(1)} MiB` : `${e} KiB`, login_h = (e, t)=>{
|
|
if (0 !== e) throw Error(`${t} failed with code ${e}.`);
|
|
}, login_v = (e, t)=>{
|
|
dom.content(e, t);
|
|
}, login_k = login_a.DummyValue.extend({
|
|
renderWidget: (a, n, c)=>{
|
|
let s = jsx("div", {
|
|
class: "cbi-value-description fluent-bg-status",
|
|
children: "Ready to upload or remove custom backgrounds."
|
|
}), u = jsx("div", {
|
|
class: "fluent-bg-hint",
|
|
children: "Supported formats: JPG, PNG, GIF, WEBP, MP4, WEBM."
|
|
}), g = jsx("button", {
|
|
class: "btn cbi-button cbi-button-action",
|
|
type: "button",
|
|
children: "Upload background"
|
|
}), p = jsx("div", {
|
|
class: "fluent-bg-actions",
|
|
children: g
|
|
}), k = jsx("div", {
|
|
class: "fluent-bg-list"
|
|
}), y = (e)=>{
|
|
login_v(s, [
|
|
document.createTextNode(e)
|
|
]);
|
|
}, $ = ()=>login_l.list("/www/luci-static/fluent/background").catch(()=>[]).then((a)=>{
|
|
let n = a.filter((e)=>"file" === e.type && login_m(String(e.name ?? ""))).sort((e, t)=>String(e.name ?? "").localeCompare(String(t.name ?? "")));
|
|
n.length ? login_v(k, n.map((a)=>{
|
|
let n, l, o, d = String(a.name ?? ""), c = jsx("button", {
|
|
class: "btn cbi-button cbi-button-remove",
|
|
type: "button",
|
|
children: "Delete"
|
|
}), s = login_i.createHandlerFn(undefined, ()=>(y(`Deleting ${d}...`), login_r(d).then((e)=>(login_h(e, `Deleting ${d}`), $().then(()=>{
|
|
y(`Deleted ${d}.`);
|
|
}))).catch((e)=>{
|
|
y(`Failed to delete ${d}: ${e instanceof Error ? e.message : String(e)}`);
|
|
})));
|
|
return s && c.addEventListener("click", s), jsxs("div", {
|
|
class: "fluent-bg-item",
|
|
children: [
|
|
(n = login_b(d), l = `/luci-static/fluent/background/${encodeURIComponent(d)}`, "mp4" === n || "webm" === n ? jsx("video", {
|
|
class: "fluent-bg-preview fluent-bg-preview-video",
|
|
muted: !0,
|
|
playsInline: !0,
|
|
preload: "metadata",
|
|
src: l
|
|
}) : jsx("div", {
|
|
class: "fluent-bg-preview",
|
|
style: `background-image:url('${l.replace(/'/g, "%27")}')`
|
|
})),
|
|
jsxs("div", {
|
|
class: "fluent-bg-meta",
|
|
children: [
|
|
jsx("strong", {
|
|
class: "fluent-bg-name",
|
|
title: d,
|
|
children: d
|
|
}),
|
|
jsx("span", {
|
|
class: "fluent-bg-size",
|
|
children: (o = Number(a.size ?? 0)) > 0 ? login_f(Math.max(1, Math.ceil(o / 1024))) : "Unknown size"
|
|
})
|
|
]
|
|
}),
|
|
jsx("div", {
|
|
class: "fluent-bg-item-actions",
|
|
children: c
|
|
})
|
|
]
|
|
});
|
|
})) : login_v(k, jsxs("div", {
|
|
class: "fluent-bg-empty",
|
|
children: [
|
|
jsx("strong", {
|
|
children: "No custom backgrounds yet"
|
|
}),
|
|
jsx("span", {
|
|
children: "Upload an image or video to use it on the login page."
|
|
})
|
|
]
|
|
}));
|
|
});
|
|
return g.addEventListener("click", ()=>{
|
|
y("Selecting background file..."), login_i.uploadFile("/tmp/fluent_background.tmp").then((e)=>{
|
|
let t;
|
|
if (!e?.name) throw Error("Upload did not return a filename.");
|
|
let a = "" !== (t = e.name.replace(/^.*[\\/]/, "").replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "")) && "." !== t && ".." !== t && login_m(t) ? t : `background-${Date.now()}.jpg`;
|
|
return y(`Saving ${a}...`), login_d(a).then((e)=>(login_h(e, `Saving ${a}`), $().then(()=>{
|
|
y(`Saved ${a}.`);
|
|
}))).catch((e)=>{
|
|
y(`Failed to save ${a}: ${e instanceof Error ? e.message : String(e)}`);
|
|
});
|
|
}).catch((e)=>{
|
|
let t = e instanceof Error ? e.message : String(e);
|
|
t && "false" !== t ? y(`Upload failed: ${t}`) : y("Upload canceled.");
|
|
});
|
|
}), login_o().then((e)=>{
|
|
y(`Ready to upload or remove custom backgrounds. Available space: ${login_f(e)}.`);
|
|
}).catch(()=>{
|
|
y("Ready to upload or remove custom backgrounds.");
|
|
}), $(), jsx("div", {
|
|
class: "fluent-bg-manager",
|
|
children: [
|
|
s,
|
|
u,
|
|
p,
|
|
k
|
|
]
|
|
});
|
|
}
|
|
});
|
|
const registerLoginTab = (e)=>{
|
|
e.tab("login", _("Login page"), _("Customize the login page background, card opacity, and blur radius for light and dark modes."));
|
|
{
|
|
let t = e.taboption("login", login_a.ListValue, "login_bg", "Background source", "Select the background image source for the login page.");
|
|
t.value("microsoft", "Microsoft dynamic canvas"), t.value("custom", "Custom background"), t.value("bing", "Bing daily wallpaper"), t.default = FLUENT_DEFAULTS.login_bg, omitDefaultValue(t);
|
|
}
|
|
e.taboption("login", login_k, "_bg_mgr", "Custom backgrounds", "Upload and manage custom background images for the login page.").depends("login_bg", "custom");
|
|
let t = createModeSubtabs(e, "login", "login_mode_tabs");
|
|
{
|
|
let e = t.taboption("light", login_a.ListValue, "transparency", _("Login card opacity"), _("Opacity of the login card in light mode. 0 is fully transparent and 1 is fully opaque."));
|
|
for (let t of transparencySteps)e.value(String(t));
|
|
e.default = FLUENT_DEFAULTS.transparency, omitDefaultValue(e);
|
|
}
|
|
{
|
|
let e = t.taboption("light", login_a.Value, "blur", _("Backdrop blur radius"), _("Blur radius in pixels behind the login card in light mode. Use 0 to disable blur."));
|
|
e.datatype = "ufloat", e.default = FLUENT_DEFAULTS.blur, omitDefaultValue(e);
|
|
}
|
|
{
|
|
let e = t.taboption("dark", login_a.ListValue, "transparency_dark", _("Login card opacity"), _("Opacity of the login card in dark mode. 0 is fully transparent and 1 is fully opaque."));
|
|
for (let t of transparencySteps)e.value(String(t));
|
|
e.default = FLUENT_DEFAULTS.transparency_dark, omitDefaultValue(e);
|
|
}
|
|
{
|
|
let e = t.taboption("dark", login_a.Value, "blur_dark", _("Backdrop blur radius"), _("Blur radius in pixels behind the login card in dark mode. Use 0 to disable blur."));
|
|
e.datatype = "ufloat", e.default = FLUENT_DEFAULTS.blur_dark, omitDefaultValue(e);
|
|
}
|
|
};
|
|
|
|
;// CONCATENATED MODULE: ./web/resources/menu-layout.ts
|
|
let menu_layout_e = "primary:", menu_layout_t = new Set([
|
|
"version",
|
|
"custom",
|
|
"titles",
|
|
"itemTitles",
|
|
"categoryMoves",
|
|
"itemMoves",
|
|
"hiddenCategories",
|
|
"hiddenItems"
|
|
]);
|
|
function menu_layout_n(e) {
|
|
return "function" == typeof _ ? _(e) : e;
|
|
}
|
|
function primaryCategoryId(t) {
|
|
return `${menu_layout_e}${t}`;
|
|
}
|
|
function menu_layout_i(t) {
|
|
return t.startsWith(menu_layout_e) ? t.slice(menu_layout_e.length) : null;
|
|
}
|
|
function menu_layout_r(e) {
|
|
let t = [], i = [];
|
|
for (let r of ui.menu.getChildren(e))if (r.satisfied) for (let e of ui.menu.getChildren(r)){
|
|
let l = e.title?.trim();
|
|
if (!e.satisfied || !l) continue;
|
|
let o = `${r.name}/${e.name}`, u = menu_layout_n(l);
|
|
for (let a of (t.push({
|
|
path: o,
|
|
pathSegments: [
|
|
r.name,
|
|
e.name
|
|
],
|
|
node: e,
|
|
title: u,
|
|
rawTitle: l
|
|
}), ui.menu.getChildren(e))){
|
|
let t = a.title?.trim();
|
|
a.satisfied && t && i.push({
|
|
path: `${o}/${a.name}`,
|
|
pathSegments: [
|
|
r.name,
|
|
e.name,
|
|
a.name
|
|
],
|
|
node: a,
|
|
title: menu_layout_n(t),
|
|
rawTitle: t,
|
|
originalPrimaryPath: o,
|
|
originalPrimaryTitle: u
|
|
});
|
|
}
|
|
}
|
|
return {
|
|
primaries: t,
|
|
items: i
|
|
};
|
|
}
|
|
function discoverMenuItems(e) {
|
|
return menu_layout_r(e).items;
|
|
}
|
|
function menu_layout_l(e) {
|
|
let t = e.primaries.map((e)=>({
|
|
id: primaryCategoryId(e.path),
|
|
title: e.title,
|
|
items: []
|
|
})), n = new Map(t.map((e)=>[
|
|
e.id,
|
|
e
|
|
]));
|
|
for (let t of e.items)n.get(primaryCategoryId(t.originalPrimaryPath))?.items.push(t.path);
|
|
return t;
|
|
}
|
|
function buildDefaultMenuCategories(e) {
|
|
return menu_layout_l(menu_layout_r(e));
|
|
}
|
|
function menu_layout_o(e) {
|
|
return e.map((e)=>({
|
|
...e,
|
|
items: [
|
|
...e.items
|
|
]
|
|
}));
|
|
}
|
|
function moveMenuItem(e, t, n, i) {
|
|
let r = menu_layout_o(e), l = r.find((e)=>e.id === n);
|
|
if (!l || i === t) return r;
|
|
for (let e of r)e.items = e.items.filter((e)=>e !== t);
|
|
let u = i ? l.items.indexOf(i) : -1;
|
|
return u >= 0 ? l.items.splice(u, 0, t) : l.items.push(t), r;
|
|
}
|
|
function moveMenuCategory(e, t, n, i = "before") {
|
|
let r = menu_layout_o(e), l = r.findIndex((e)=>e.id === t);
|
|
if (l < 0 || t === n || !r.some((e)=>e.id === n)) return r;
|
|
let [u] = r.splice(l, 1), a = r.findIndex((e)=>e.id === n);
|
|
return r.splice("after" === i ? a + 1 : a, 0, u), r;
|
|
}
|
|
function menu_layout_u(e, t) {
|
|
let n = e[t];
|
|
return new Set(e.slice(t + 1).filter((e)=>e.originalPrimaryPath === n.originalPrimaryPath).map((e)=>e.path));
|
|
}
|
|
function canRestoreMenuItem(e, t, n) {
|
|
let i = t.findIndex((e)=>e.path === n);
|
|
if (i < 0) return !1;
|
|
let r = primaryCategoryId(t[i].originalPrimaryPath), l = e.find((e)=>e.id === r);
|
|
if (!l) return !0;
|
|
let o = 0, a = -1;
|
|
for (let t of e)for(let e = 0; e < t.items.length; e += 1)t.items[e] === n && (o += 1, t.id === r && (a = e));
|
|
if (1 !== o || a < 0) return !0;
|
|
let s = menu_layout_u(t, i), f = l.items.findIndex((e)=>s.has(e));
|
|
return (f < 0 ? l.items.length - 1 : f > a ? f - 1 : f) !== a;
|
|
}
|
|
function restoreMenuItemToOriginalPosition(e, t, n) {
|
|
let i = t.findIndex((e)=>e.path === n);
|
|
if (i < 0) return menu_layout_o(e);
|
|
let r = t[i], l = primaryCategoryId(r.originalPrimaryPath), a = e.some((e)=>e.id === l) ? e : [
|
|
...e,
|
|
{
|
|
id: l,
|
|
title: r.originalPrimaryTitle,
|
|
items: []
|
|
}
|
|
], s = menu_layout_u(t, i), f = a.find((e)=>e.id === l);
|
|
return moveMenuItem(a, n, l, f?.items.find((e)=>s.has(e)));
|
|
}
|
|
function menu_layout_a(e, t) {
|
|
if ("string" != typeof e || e !== e.trim()) return !1;
|
|
let n = e.split("/");
|
|
return n.length === t && n.every(Boolean);
|
|
}
|
|
function menu_layout_s(e) {
|
|
return "number" == typeof e ? `custom:${e}` : `primary:${e}`;
|
|
}
|
|
function menu_layout_f(e, t) {
|
|
return Number.isInteger(e) && e >= 0 && e < t || menu_layout_a(e, 2) ? e : null;
|
|
}
|
|
function menu_layout_d(e, t) {
|
|
let n = e[t];
|
|
return void 0 === n ? [] : Array.isArray(n) ? n : null;
|
|
}
|
|
function parseMenuLayout(e) {
|
|
let n;
|
|
if ("string" != typeof e || "" === e.trim()) return null;
|
|
try {
|
|
n = JSON.parse(e);
|
|
} catch {
|
|
return null;
|
|
}
|
|
if (!n || "object" != typeof n || Array.isArray(n)) return null;
|
|
let i = n;
|
|
if (1 !== i.version || Object.keys(i).some((e)=>!menu_layout_t.has(e))) return null;
|
|
let r = menu_layout_d(i, "custom"), l = menu_layout_d(i, "titles"), o = menu_layout_d(i, "itemTitles"), u = menu_layout_d(i, "categoryMoves"), m = menu_layout_d(i, "itemMoves"), p = menu_layout_d(i, "hiddenCategories"), h = menu_layout_d(i, "hiddenItems");
|
|
if (!r || !l || !o || !u || !m || !p || !h) return null;
|
|
let g = [], y = new Set();
|
|
for (let e of r){
|
|
if ("string" != typeof e) return null;
|
|
let t = e.trim(), n = t.toLocaleLowerCase();
|
|
if (!t || y.has(n)) return null;
|
|
y.add(n), g.push(t);
|
|
}
|
|
let c = [], M = new Set();
|
|
for (let e of l){
|
|
if (!Array.isArray(e) || 2 !== e.length || !menu_layout_a(e[0], 2) || "string" != typeof e[1]) return null;
|
|
let t = e[1].trim();
|
|
if (!t || M.has(e[0])) return null;
|
|
M.add(e[0]), c.push([
|
|
e[0],
|
|
t
|
|
]);
|
|
}
|
|
let v = [], w = new Set();
|
|
for (let e of o){
|
|
if (!Array.isArray(e) || 2 !== e.length || !menu_layout_a(e[0], 3) || "string" != typeof e[1]) return null;
|
|
let t = e[1].trim();
|
|
if (!t || w.has(e[0])) return null;
|
|
w.add(e[0]), v.push([
|
|
e[0],
|
|
t
|
|
]);
|
|
}
|
|
let I = [], C = new Set();
|
|
for (let e of u){
|
|
if (!Array.isArray(e) || 2 !== e.length) return null;
|
|
let t = menu_layout_f(e[0], g.length), n = null === e[1] ? null : menu_layout_f(e[1], g.length);
|
|
if (null === t || null !== e[1] && null === n) return null;
|
|
let i = menu_layout_s(t);
|
|
if (C.has(i) || null !== n && i === menu_layout_s(n)) return null;
|
|
C.add(i), I.push([
|
|
t,
|
|
n
|
|
]);
|
|
}
|
|
let S = [], x = new Set();
|
|
for (let e of m){
|
|
if (!Array.isArray(e) || 3 !== e.length || !menu_layout_a(e[0], 3)) return null;
|
|
let t = menu_layout_f(e[1], g.length), n = null === e[2] ? null : menu_layout_a(e[2], 3) ? e[2] : null;
|
|
if (null === t || null !== e[2] && null === n || x.has(e[0]) || n === e[0]) return null;
|
|
x.add(e[0]), S.push([
|
|
e[0],
|
|
t,
|
|
n
|
|
]);
|
|
}
|
|
let P = [], A = new Set();
|
|
for (let e of p){
|
|
let t = menu_layout_f(e, g.length);
|
|
if (null === t || A.has(menu_layout_s(t))) return null;
|
|
A.add(menu_layout_s(t)), P.push(t);
|
|
}
|
|
let T = [], b = new Set();
|
|
for (let e of h){
|
|
if (!menu_layout_a(e, 3) || b.has(e)) return null;
|
|
b.add(e), T.push(e);
|
|
}
|
|
return {
|
|
version: 1,
|
|
custom: g,
|
|
titles: c,
|
|
itemTitles: v,
|
|
categoryMoves: I,
|
|
itemMoves: S,
|
|
hiddenCategories: P,
|
|
hiddenItems: T
|
|
};
|
|
}
|
|
function menu_layout_m() {
|
|
return {
|
|
titles: [],
|
|
itemTitles: [],
|
|
categoryMoves: [],
|
|
itemMoves: []
|
|
};
|
|
}
|
|
function menu_layout_p(e, t) {
|
|
return "number" == typeof e ? t[e] ?? null : primaryCategoryId(e);
|
|
}
|
|
function resolveMenuLayout(e, t) {
|
|
let n = menu_layout_r(e), i = menu_layout_l(n), u = new Set(), a = new Set(), s = menu_layout_m();
|
|
if (!t) return {
|
|
categories: i,
|
|
hiddenCategoryIds: u,
|
|
hiddenItemPaths: a,
|
|
itemTitles: new Map(),
|
|
pending: s,
|
|
configured: !1
|
|
};
|
|
let f = t.custom.map((e, t)=>{
|
|
let n = `custom:${t}`;
|
|
return i.push({
|
|
id: n,
|
|
title: e,
|
|
items: []
|
|
}), n;
|
|
}), d = new Set(n.items.map((e)=>e.path)), h = f.length > 0;
|
|
for (let [e, n] of t.titles){
|
|
let t = primaryCategoryId(e), r = i.find((e)=>e.id === t);
|
|
r ? (r.title = n, h = !0) : s.titles.push([
|
|
t,
|
|
n
|
|
]);
|
|
}
|
|
let g = new Map();
|
|
for (let [e, n] of t.itemTitles)d.has(e) ? (g.set(e, n), h = !0) : s.itemTitles.push([
|
|
e,
|
|
n
|
|
]);
|
|
for (let [e, n] of t.categoryMoves){
|
|
let t = menu_layout_p(e, f), r = null === n ? null : menu_layout_p(n, f), l = null !== t && i.some((e)=>e.id === t), u = null === r || i.some((e)=>e.id === r);
|
|
t && l && u ? (i = function(e, t, n) {
|
|
if (n) return moveMenuCategory(e, t, n);
|
|
let i = menu_layout_o(e), r = i.findIndex((e)=>e.id === t);
|
|
if (r < 0) return i;
|
|
let [l] = i.splice(r, 1);
|
|
return i.push(l), i;
|
|
}(i, t, r), h = !0) : t && s.categoryMoves.push([
|
|
t,
|
|
r
|
|
]);
|
|
}
|
|
for (let [e, n, r] of t.itemMoves){
|
|
let t = menu_layout_p(n, f), l = t ? i.find((e)=>e.id === t) : void 0, o = null === r || !!l?.items.includes(r);
|
|
d.has(e) && t && l && o ? (i = moveMenuItem(i, e, t, r ?? void 0), h = !0) : t && s.itemMoves.push([
|
|
e,
|
|
t,
|
|
r
|
|
]);
|
|
}
|
|
for (let e of t.hiddenCategories){
|
|
let t = menu_layout_p(e, f);
|
|
t && (u.add(t), i.some((e)=>e.id === t) && (h = !0));
|
|
}
|
|
for (let e of t.hiddenItems)a.add(e), d.has(e) && (h = !0);
|
|
return {
|
|
categories: i,
|
|
hiddenCategoryIds: u,
|
|
hiddenItemPaths: a,
|
|
itemTitles: g,
|
|
pending: s,
|
|
configured: h
|
|
};
|
|
}
|
|
function menu_layout_h(e, t) {
|
|
let n = new Map(t.map((e, t)=>[
|
|
e,
|
|
t
|
|
])), i = e.flatMap((e)=>{
|
|
let t = n.get(e);
|
|
return void 0 === t ? [] : [
|
|
{
|
|
value: e,
|
|
index: t
|
|
}
|
|
];
|
|
}), r = [], l = Array(i.length).fill(-1);
|
|
for(let e = 0; e < i.length; e += 1){
|
|
let t = 0, n = r.length;
|
|
for(; t < n;){
|
|
let l = t + n >> 1;
|
|
i[r[l]].index < i[e].index ? t = l + 1 : n = l;
|
|
}
|
|
t > 0 && (l[e] = r[t - 1]), r[t] = e;
|
|
}
|
|
let o = new Set();
|
|
for(let e = r.at(-1) ?? -1; e >= 0; e = l[e])o.add(i[e].value);
|
|
return o;
|
|
}
|
|
function menu_layout_g(e, t) {
|
|
let n = menu_layout_i(e);
|
|
return n || (t.get(e) ?? null);
|
|
}
|
|
function menu_layout_y(e, t, n) {
|
|
let i = n.get(e), r = n.get(t);
|
|
return void 0 !== i && void 0 !== r ? i - r : void 0 !== i ? -1 : void 0 !== r ? 1 : e.localeCompare(t);
|
|
}
|
|
function serializeMenuLayout(e, t, n, o, u = menu_layout_m(), a = new Map()) {
|
|
let s = menu_layout_r(e), f = menu_layout_l(s), d = new Map(f.map((e)=>[
|
|
e.id,
|
|
e
|
|
])), p = t.filter((e)=>!d.has(e.id)), c = new Map(p.map((e, t)=>[
|
|
e.id,
|
|
t
|
|
])), M = p.map((e)=>e.title.trim()), v = [], w = new Set();
|
|
for (let e of t){
|
|
let t = d.get(e.id), n = e.title.trim();
|
|
if (!t || n === t.title) continue;
|
|
let r = menu_layout_i(e.id);
|
|
r && (v.push([
|
|
r,
|
|
n
|
|
]), w.add(e.id));
|
|
}
|
|
for (let [e, t] of u.titles){
|
|
if (w.has(e)) continue;
|
|
let n = menu_layout_i(e);
|
|
n && v.push([
|
|
n,
|
|
t
|
|
]);
|
|
}
|
|
let I = new Map(s.items.map((e)=>[
|
|
e.path,
|
|
e.title
|
|
])), C = [], S = new Set();
|
|
for (let [e, t] of a){
|
|
let n = t.trim();
|
|
n && n !== I.get(e) && (C.push([
|
|
e,
|
|
n
|
|
]), S.add(e));
|
|
}
|
|
for (let [e, t] of u.itemTitles)S.has(e) || C.push([
|
|
e,
|
|
t
|
|
]);
|
|
let x = [
|
|
...f.map((e)=>e.id),
|
|
...p.map((e)=>e.id)
|
|
], P = t.map((e)=>e.id), A = [], T = new Set();
|
|
for (let [e, t] of function(e, t) {
|
|
let n = menu_layout_h(e, t), i = [];
|
|
for(let e = t.length - 1; e >= 0; e -= 1)n.has(t[e]) || i.push([
|
|
t[e],
|
|
t[e + 1] ?? null
|
|
]);
|
|
return i;
|
|
}(x, P)){
|
|
let n = menu_layout_g(e, c), i = null === t ? null : menu_layout_g(t, c);
|
|
null !== n && (null === t || null !== i) && (A.push([
|
|
n,
|
|
i
|
|
]), T.add(e));
|
|
}
|
|
for (let [e, t] of u.categoryMoves){
|
|
if (T.has(e)) continue;
|
|
let n = menu_layout_g(e, c), i = null === t ? null : menu_layout_g(t, c);
|
|
null !== n && (null === t || null !== i) && A.push([
|
|
n,
|
|
i
|
|
]);
|
|
}
|
|
let b = [], $ = new Set();
|
|
for (let e of t){
|
|
let t = menu_layout_g(e.id, c);
|
|
if (null === t) continue;
|
|
let n = menu_layout_h(d.get(e.id)?.items ?? [], e.items);
|
|
for(let i = e.items.length - 1; i >= 0; i -= 1){
|
|
let r = e.items[i];
|
|
n.has(r) || (b.push([
|
|
r,
|
|
t,
|
|
e.items[i + 1] ?? null
|
|
]), $.add(r));
|
|
}
|
|
}
|
|
for (let [e, t, n] of u.itemMoves){
|
|
if ($.has(e)) continue;
|
|
let i = menu_layout_g(t, c);
|
|
null !== i && b.push([
|
|
e,
|
|
i,
|
|
n
|
|
]);
|
|
}
|
|
let L = new Map(t.map((e, t)=>[
|
|
e.id,
|
|
t
|
|
])), O = [
|
|
...n
|
|
].sort((e, t)=>menu_layout_y(e, t, L)).flatMap((e)=>{
|
|
let t = menu_layout_g(e, c);
|
|
return null === t ? [] : [
|
|
t
|
|
];
|
|
}), j = new Map(s.items.map((e, t)=>[
|
|
e.path,
|
|
t
|
|
])), N = [
|
|
...o
|
|
].sort((e, t)=>menu_layout_y(e, t, j)), k = {
|
|
version: 1
|
|
};
|
|
return M.length > 0 && (k.custom = M), v.length > 0 && (k.titles = v), C.length > 0 && (k.itemTitles = C), A.length > 0 && (k.categoryMoves = A), b.length > 0 && (k.itemMoves = b), O.length > 0 && (k.hiddenCategories = O), N.length > 0 && (k.hiddenItems = N), 1 === Object.keys(k).length ? "" : JSON.stringify(k);
|
|
}
|
|
function buildMenuPresentation(e, t) {
|
|
let n = menu_layout_r(e), l = resolveMenuLayout(e, parseMenuLayout(t)), o = new Map(n.items.map((e)=>[
|
|
e.path,
|
|
e
|
|
]));
|
|
for (let [e, t] of l.itemTitles){
|
|
let n = o.get(e);
|
|
n && o.set(e, {
|
|
...n,
|
|
title: t
|
|
});
|
|
}
|
|
let u = new Map(n.primaries.map((e)=>[
|
|
primaryCategoryId(e.path),
|
|
e
|
|
])), a = new Set(l.hiddenItemPaths);
|
|
for (let e of l.hiddenCategoryIds){
|
|
let t = menu_layout_i(e);
|
|
t && a.add(t);
|
|
let n = l.categories.find((t)=>t.id === e);
|
|
if (n) for (let e of n.items)a.add(e);
|
|
}
|
|
return {
|
|
categories: l.categories.map((e)=>({
|
|
id: e.id,
|
|
title: e.title,
|
|
primary: u.get(e.id) ?? null,
|
|
items: e.items.flatMap((e)=>{
|
|
let t = o.get(e);
|
|
return t ? [
|
|
t
|
|
] : [];
|
|
})
|
|
})),
|
|
hiddenCategoryIds: l.hiddenCategoryIds,
|
|
hiddenPaths: a,
|
|
configured: l.configured
|
|
};
|
|
}
|
|
|
|
;// CONCATENATED MODULE: ./web/resources/view/fluent-config/tabs/menu.tsx
|
|
|
|
let menu_n = L.form;
|
|
|
|
function menu_g(e, t) {
|
|
let i = t.getBoundingClientRect();
|
|
return e.clientY < i.top + i.height / 2 ? "before" : "after";
|
|
}
|
|
let menu_f = 0;
|
|
function menu_h(e) {
|
|
return e.trim().toLocaleLowerCase();
|
|
}
|
|
function menu_y(e) {
|
|
let t = new Map(), i = new Map();
|
|
for (let n of e){
|
|
let e = n.title.trim(), l = menu_h(e);
|
|
if (!e) {
|
|
t.set(n.id, _("Primary menu titles cannot be empty."));
|
|
continue;
|
|
}
|
|
let r = i.get(l);
|
|
r ? (t.set(r, _("Primary menu titles must be unique.")), t.set(n.id, _("Primary menu titles must be unique."))) : i.set(l, n.id);
|
|
}
|
|
return t;
|
|
}
|
|
function registerMenuTab(b, v) {
|
|
var I;
|
|
let k, M, w, C;
|
|
b.tab("menu", _("Menu"));
|
|
let E = b.taboption("menu", (I = v, M = new Map((k = discoverMenuItems(I)).map((e)=>[
|
|
e.path,
|
|
e
|
|
])), w = new Map(buildDefaultMenuCategories(I).map((e)=>[
|
|
e.id,
|
|
e
|
|
])), C = new Map(), menu_n.Value.extend({
|
|
renderWidget (n, o, b) {
|
|
let v = "string" == typeof b || Array.isArray(b) ? b : null, E = "string" == typeof b ? b : "", x = function(e, t) {
|
|
let i = parseMenuLayout(t);
|
|
if (!i) return {
|
|
categories: buildDefaultMenuCategories(e),
|
|
hiddenCategoryIds: new Set(),
|
|
hiddenItemPaths: new Set(),
|
|
itemTitles: new Map(),
|
|
pending: {
|
|
titles: [],
|
|
itemTitles: [],
|
|
categoryMoves: [],
|
|
itemMoves: []
|
|
}
|
|
};
|
|
let n = resolveMenuLayout(e, i);
|
|
return {
|
|
categories: n.categories,
|
|
hiddenCategoryIds: n.hiddenCategoryIds,
|
|
hiddenItemPaths: n.hiddenItemPaths,
|
|
itemTitles: new Map(n.itemTitles),
|
|
pending: n.pending
|
|
};
|
|
}(I, v), P = new Set(), S = "" === E ? null : parseMenuLayout(E), T = "" !== E && !S, A = jsx("input", {
|
|
type: "hidden",
|
|
id: this.cbid(n),
|
|
value: E
|
|
}), R = jsx("div", {
|
|
class: "fluent-menu-editor__categories"
|
|
}), $ = new Map(), q = null, X = null, Y = null, D = null, B = null, F = jsx("div", {
|
|
class: "fluent-menu-editor__validation",
|
|
role: "alert"
|
|
}), N = jsx("div", {
|
|
class: "fluent-menu-editor__notice",
|
|
hidden: !T,
|
|
children: _('The stored menu layout is invalid. Choose "Restore defaults" or make an edit before saving.')
|
|
});
|
|
C.set(n, A);
|
|
let U = ()=>{
|
|
let e = menu_y(x.categories);
|
|
for (let [t, i] of $){
|
|
let n = e.get(t) ?? "";
|
|
i.card.classList.toggle("is-invalid", !!n), i.error && (i.error.textContent = n);
|
|
}
|
|
F.textContent = e.size > 0 ? _("Fix primary menu title errors before saving.") : "";
|
|
}, V = (e = serializeMenuLayout(I, x.categories, x.hiddenCategoryIds, x.hiddenItemPaths, x.pending, x.itemTitles))=>{
|
|
A.value = e, N.hidden = !0, U(), A.dispatchEvent(new Event("change", {
|
|
bubbles: !0
|
|
}));
|
|
}, j = ()=>"true" === document.body.dataset.reduceMotion, z = ()=>{
|
|
q?.classList.remove("is-drop-before", "is-drop-after"), q = null, X?.classList.remove("is-drag-over"), X = null;
|
|
}, W = (e, t)=>{
|
|
let i, n, l, r, o = D;
|
|
if (!o || j() || o.placeholder.parentElement === e && o.placeholder.nextSibling === t) return;
|
|
let a = (i = "category" === o.source.kind ? ".fluent-menu-editor__category" : ".fluent-menu-editor__item", Array.from(R.querySelectorAll(i)).filter((e)=>e !== D?.sourceElement && e.getClientRects().length > 0)), d = new Map(a.map((e)=>[
|
|
e,
|
|
e.getBoundingClientRect()
|
|
]));
|
|
e.insertBefore(o.placeholder, t);
|
|
let s = {
|
|
duration: Number.isFinite(r = Number.parseFloat(l = (n = getComputedStyle(document.documentElement)).getPropertyValue("--fluent-duration-fast").trim())) ? r * (l.endsWith("ms") ? 1 : 1000) : 0,
|
|
easing: n.getPropertyValue("--fluent-easing-standard").trim()
|
|
};
|
|
for (let e of a){
|
|
let t = d.get(e);
|
|
if (!t || !e.isConnected) continue;
|
|
let i = e.getBoundingClientRect(), n = t.left - i.left, l = t.top - i.top;
|
|
(0 !== n || 0 !== l) && (e.getAnimations().forEach((e)=>{
|
|
e.cancel();
|
|
}), e.animate([
|
|
{
|
|
transform: `translate(${n}px, ${l}px)`
|
|
},
|
|
{
|
|
transform: "translate(0, 0)"
|
|
}
|
|
], s));
|
|
}
|
|
}, O = (e = !0)=>{
|
|
let t, i;
|
|
z(), Y = null, e && (t = D, i = t?.sourceElement.parentElement, t && i && W(i, t.sourceElement));
|
|
}, G = (e)=>{
|
|
var t, i, n;
|
|
if (!(t = Y) || t.kind !== e.kind || t.categoryId !== e.categoryId || ("item" === t.kind && "item" === e.kind ? t.path !== e.path || t.position !== e.position : "category" === t.kind && "category" === e.kind ? t.categoryId !== e.categoryId || t.position !== e.position : "item-list" !== t.kind && ("item-category" !== t.kind || "item-category" !== e.kind || t.position !== e.position))) {
|
|
if (z(), Y = e, j()) return void ("item-list" === e.kind ? (X = e.element, e.element.classList.add("is-drag-over")) : (i = e.element, n = e.position, z(), q = i, i.classList.toggle("is-drop-before", "before" === n), i.classList.toggle("is-drop-after", "after" === n)));
|
|
"item-category" === e.kind && (X = e.element, e.element.classList.add("is-drag-over")), ((e)=>{
|
|
if (!D) return;
|
|
if ("category" === e.kind || "item" === e.kind) {
|
|
let t = "before" === e.position ? e.element : e.element.nextSibling, i = e.element.parentElement;
|
|
i && W(i, t);
|
|
return;
|
|
}
|
|
let t = "item-list" === e.kind ? e.element : e.element.querySelector(".fluent-menu-editor__items");
|
|
t && W(t, null);
|
|
})(e);
|
|
}
|
|
}, H = (e, t, i)=>{
|
|
M.has(e) && x.categories.some((e)=>e.id === t) && (x.pending.itemMoves = x.pending.itemMoves.filter(([t])=>t !== e), x.categories = moveMenuItem(x.categories, e, t, i), P.add(t), V(), eo());
|
|
}, J = ()=>{
|
|
let e = B;
|
|
if (e) {
|
|
let t;
|
|
B = null, e.sourceElement.classList.remove("is-dragging"), e.preview.remove(), O(), t = D, D = null, t && (t.placeholder.remove(), t.sourceElement.classList.remove("is-drag-source")), e.handle.hasPointerCapture(e.pointerId) && e.handle.releasePointerCapture(e.pointerId);
|
|
}
|
|
}, K = (e, t, i)=>{
|
|
let n = Array.from(R.querySelectorAll(".fluent-menu-editor__category")).filter((e)=>{
|
|
let t = e.dataset.categoryId;
|
|
return t && t !== i && !e.classList.contains("is-dragging");
|
|
}), l = null, r = Number.MAX_VALUE;
|
|
for (let i of n){
|
|
let n = i.getBoundingClientRect(), o = Math.hypot(e - (n.left + n.width / 2), t - (n.top + n.height / 2));
|
|
o < r && (r = o, l = i);
|
|
}
|
|
return l;
|
|
}, Q = (e, t)=>{
|
|
if (((e, t)=>{
|
|
let i = D?.placeholder;
|
|
if (!i?.isConnected) return !1;
|
|
let n = i.getBoundingClientRect();
|
|
return e >= n.left && e <= n.right && t >= n.top && t <= n.bottom;
|
|
})(t.clientX, t.clientY)) return;
|
|
let i = document.elementFromPoint(t.clientX, t.clientY);
|
|
if (!i) return void O();
|
|
if ("category" === e.kind) {
|
|
let n = i.closest(".fluent-menu-editor__category");
|
|
n || (n = K(t.clientX, t.clientY, e.categoryId));
|
|
let l = n?.dataset.categoryId;
|
|
return n && l && l !== e.categoryId ? void G({
|
|
kind: "category",
|
|
categoryId: l,
|
|
element: n,
|
|
position: menu_g(t, n)
|
|
}) : void O();
|
|
}
|
|
let n = i.closest(".fluent-menu-editor__item"), l = n?.dataset.itemPath, r = n?.closest(".fluent-menu-editor__items"), o = r?.dataset.categoryId;
|
|
if (n && l && r && o) return l === e.path ? void O() : void G({
|
|
kind: "item",
|
|
categoryId: o,
|
|
element: n,
|
|
path: l,
|
|
position: menu_g(t, n)
|
|
});
|
|
let a = i.closest(".fluent-menu-editor__items");
|
|
if (a) {
|
|
let e = ((e, t, i)=>{
|
|
let n = e.dataset.categoryId;
|
|
if (!n) return null;
|
|
let l = Array.from(e.querySelectorAll(":scope > .fluent-menu-editor__item")).filter((e)=>e !== D?.sourceElement && !e.classList.contains("is-dragging") && e.getClientRects().length > 0).map((e)=>({
|
|
element: e,
|
|
path: e.dataset.itemPath,
|
|
bounds: e.getBoundingClientRect()
|
|
})).filter((e)=>!!e.path);
|
|
if (0 === l.length) return {
|
|
kind: "item-list",
|
|
categoryId: n,
|
|
element: e
|
|
};
|
|
let r = [];
|
|
for (let e of l){
|
|
let t = r.at(-1);
|
|
!t || e.bounds.top >= t.bottom - 1 ? r.push({
|
|
items: [
|
|
e
|
|
],
|
|
top: e.bounds.top,
|
|
bottom: e.bounds.bottom
|
|
}) : (t.items.push(e), t.top = Math.min(t.top, e.bounds.top), t.bottom = Math.max(t.bottom, e.bounds.bottom));
|
|
}
|
|
let o = (e, t)=>({
|
|
kind: "item",
|
|
categoryId: n,
|
|
element: e.element,
|
|
path: e.path,
|
|
position: t
|
|
}), a = l[0];
|
|
if (i < r[0].top) return o(a, "before");
|
|
for(let n = 0; n < r.length; n += 1){
|
|
let l = r[n];
|
|
if (i <= l.bottom) {
|
|
if (1 === l.items.length) {
|
|
let e = l.items[0];
|
|
return o(e, i < e.bounds.top + e.bounds.height / 2 ? "before" : "after");
|
|
}
|
|
let n = l.items.reduce((e, i)=>Math.abs(t - i.bounds.left - i.bounds.width / 2) < Math.abs(t - e.bounds.left - e.bounds.width / 2) ? i : e), r = n.bounds.left + n.bounds.width / 2;
|
|
return o(n, ("rtl" === getComputedStyle(e).direction ? t > r : t < r) ? "before" : "after");
|
|
}
|
|
let a = r[n + 1];
|
|
if (a && i < a.top) return o(a.items[0], "before");
|
|
}
|
|
return {
|
|
kind: "item-list",
|
|
categoryId: n,
|
|
element: e
|
|
};
|
|
})(a, t.clientX, t.clientY);
|
|
e ? G(e) : O();
|
|
return;
|
|
}
|
|
let d = i.closest(".fluent-menu-editor__category");
|
|
d || (d = K(t.clientX, t.clientY));
|
|
let s = d?.dataset.categoryId;
|
|
d && s ? G({
|
|
kind: "item-category",
|
|
categoryId: s,
|
|
element: d,
|
|
position: "after"
|
|
}) : O();
|
|
}, Z = (e)=>{
|
|
let t = B;
|
|
t && t.pointerId === e.pointerId && (e.preventDefault(), t.preview.style.left = `${e.clientX - t.offsetX}px`, t.preview.style.top = `${e.clientY - t.offsetY}px`, Q(t.source, e));
|
|
}, ee = (e, t)=>{
|
|
let i = B;
|
|
if (i && i.pointerId === e.pointerId) if (e.preventDefault(), t) {
|
|
let t, n;
|
|
Q(i.source, e), t = B, n = Y, J(), t && n && ((e, t)=>{
|
|
if ("item" === e.kind) {
|
|
if ("item" === t.kind) {
|
|
let i = x.categories.find((e)=>e.id === t.categoryId);
|
|
if (!i) return;
|
|
let n = i.items.indexOf(t.path);
|
|
if (n < 0) return;
|
|
let l = "before" === t.position ? t.path : i.items[n + 1];
|
|
H(e.path, t.categoryId, l);
|
|
} else ("item-category" === t.kind || "item-list" === t.kind) && H(e.path, t.categoryId);
|
|
return;
|
|
}
|
|
"category" === t.kind && (x.pending.categoryMoves = x.pending.categoryMoves.filter(([t])=>t !== e.categoryId), x.categories = moveMenuCategory(x.categories, e.categoryId, t.categoryId, t.position), V(), eo());
|
|
})(t.source, n);
|
|
} else J();
|
|
}, et = (e, t, i)=>{
|
|
e.addEventListener("pointerdown", (n)=>((e, t, i, n)=>{
|
|
if (B || "mouse" !== e.pointerType && "pen" !== e.pointerType && "touch" !== e.pointerType) return;
|
|
e.preventDefault(), e.stopPropagation();
|
|
let l = i.getBoundingClientRect(), r = i.cloneNode(!0);
|
|
r.classList.add("fluent-menu-editor__drag-preview"), r.setAttribute("aria-hidden", "true"), "category" === t.kind && (r.querySelector(".fluent-menu-editor__category-error")?.remove(), r.querySelector(".fluent-menu-editor__items")?.remove()), r.style.width = `${l.width}px`, document.body.append(r);
|
|
let o = {
|
|
handle: n,
|
|
offsetX: e.clientX - l.left,
|
|
offsetY: e.clientY - l.top,
|
|
pointerId: e.pointerId,
|
|
preview: r,
|
|
source: t,
|
|
sourceElement: i
|
|
};
|
|
B = o, i.classList.add("is-dragging"), ((e, t)=>{
|
|
if (j()) return;
|
|
let i = t.getBoundingClientRect(), n = "category" === e.kind ? "category" : "item", l = document.createElement("div");
|
|
l.className = `fluent-menu-editor__drop-placeholder fluent-menu-editor__drop-placeholder--${n}`, l.style.height = `${i.height}px`, l.setAttribute("aria-hidden", "true"), t.parentElement?.insertBefore(l, t), D = {
|
|
placeholder: l,
|
|
source: e,
|
|
sourceElement: t
|
|
}, t.classList.add("is-drag-source");
|
|
})(t, i), r.style.left = `${e.clientX - o.offsetX}px`, r.style.top = `${e.clientY - o.offsetY}px`, n.setPointerCapture(e.pointerId);
|
|
})(n, t, i, e)), e.addEventListener("pointermove", Z), e.addEventListener("pointerup", (e)=>ee(e, !0)), e.addEventListener("pointercancel", (e)=>ee(e, !1)), e.addEventListener("lostpointercapture", ()=>{
|
|
B?.handle === e && J();
|
|
});
|
|
}, ei = (i, l)=>{
|
|
let o = M.get(i), a = x.categories.find((e)=>e.id === l);
|
|
if (!o || !a) return null;
|
|
let d = jsx("div", {
|
|
class: `fluent-menu-editor__item${x.hiddenItemPaths.has(i) ? " is-hidden" : ""}`,
|
|
"data-item-path": i
|
|
}), s = jsx("span", {
|
|
class: "fluent-menu-editor__drag-handle",
|
|
title: _("Drag to move second-level menu")
|
|
}), c = jsx("input", {
|
|
type: "checkbox",
|
|
checked: !x.hiddenItemPaths.has(i),
|
|
"aria-label": _("Show %s in the menu").format(o.title)
|
|
});
|
|
et(s, {
|
|
kind: "item",
|
|
path: i
|
|
}, d), c.addEventListener("change", ()=>{
|
|
c.checked ? x.hiddenItemPaths.delete(i) : x.hiddenItemPaths.add(i), d.classList.toggle("is-hidden", !c.checked), V();
|
|
});
|
|
let p = x.itemTitles.get(i) ?? o.title, g = _("Edit"), f = jsx("button", {
|
|
class: "fluent-menu-editor__category-edit",
|
|
type: "button",
|
|
"aria-label": g,
|
|
title: g
|
|
});
|
|
f.addEventListener("click", ()=>{
|
|
let l = x.itemTitles.get(i) ?? o.title, r = `${this.cbid(n)}-item-${i.replace(/\//g, "-")}-title`, a = jsx("input", {
|
|
id: r,
|
|
class: "cbi-input-text",
|
|
type: "text",
|
|
value: l,
|
|
"aria-label": _("Second-level menu title")
|
|
}), d = jsx("p", {
|
|
class: "cbi-value-description",
|
|
role: "alert",
|
|
hidden: !0
|
|
}), s = null, u = ()=>{
|
|
let e = a.value.trim() ? "" : _("Menu title cannot be empty.");
|
|
return d.textContent = e, d.hidden = !e, a.classList.toggle("cbi-input-invalid", !!e), s && (s.disabled = !!e), !e;
|
|
};
|
|
a.addEventListener("input", u), s = jsx("button", {
|
|
type: "button",
|
|
class: "btn cbi-button-save",
|
|
onclick: ()=>{
|
|
if (!u()) return;
|
|
let e = a.value.trim();
|
|
e === o.title ? x.itemTitles.delete(i) : x.itemTitles.set(i, e), V(), eo(), L.ui.hideModal();
|
|
},
|
|
children: _("Save")
|
|
});
|
|
let c = _("Restore to original title"), m = jsx("button", {
|
|
class: "fluent-menu-editor__item-reset",
|
|
type: "button",
|
|
"aria-label": c,
|
|
title: c,
|
|
onclick: ()=>{
|
|
a.value = o.title, u(), a.focus();
|
|
}
|
|
});
|
|
L.ui.showModal(_("Second-level menu title"), jsxs("div", {
|
|
class: "fluent-menu-editor__rename-dialog",
|
|
children: [
|
|
jsx("label", {
|
|
htmlFor: r,
|
|
children: _("Second-level menu title")
|
|
}),
|
|
jsxs("div", {
|
|
class: "fluent-menu-editor__rename-control",
|
|
children: [
|
|
a,
|
|
m
|
|
]
|
|
}),
|
|
d,
|
|
jsxs("div", {
|
|
class: "right",
|
|
children: [
|
|
jsx("button", {
|
|
type: "button",
|
|
class: "btn",
|
|
onclick: ()=>L.ui.hideModal(),
|
|
children: _("Cancel")
|
|
}),
|
|
s
|
|
]
|
|
})
|
|
]
|
|
})), u(), requestAnimationFrame(()=>{
|
|
a.focus(), a.select();
|
|
});
|
|
});
|
|
let h = jsxs("span", {
|
|
class: "fluent-menu-editor__item-label",
|
|
children: [
|
|
jsx("strong", {
|
|
children: p
|
|
}),
|
|
jsx("small", {
|
|
children: o.path
|
|
})
|
|
]
|
|
});
|
|
if (d.append(s, f, h), canRestoreMenuItem(x.categories, k, i)) {
|
|
let t = _("Restore %s to its original menu position").format(o.title), n = jsx("button", {
|
|
class: "fluent-menu-editor__item-reset",
|
|
type: "button",
|
|
"aria-label": t,
|
|
title: t
|
|
});
|
|
n.addEventListener("click", ()=>{
|
|
let e;
|
|
(e = M.get(i)) && P.add(primaryCategoryId(e.originalPrimaryPath)), x.pending.itemMoves = x.pending.itemMoves.filter(([e])=>e !== i), x.categories = restoreMenuItemToOriginalPosition(x.categories, k, i), V(), eo();
|
|
}), d.append(n);
|
|
}
|
|
return d.append(c), d;
|
|
}, en = (l)=>{
|
|
let r = document.createElement("details");
|
|
r.className = `fluent-menu-editor__category${x.hiddenCategoryIds.has(l.id) ? " is-hidden" : ""}`, r.dataset.categoryId = l.id, r.open = P.has(l.id);
|
|
let o = document.createElement("summary");
|
|
o.className = "fluent-menu-editor__category-header";
|
|
let a = jsx("div", {
|
|
class: "fluent-menu-editor__items",
|
|
"data-category-id": l.id
|
|
}), d = jsx("span", {
|
|
class: "fluent-menu-editor__category-count",
|
|
title: _("%d second-level menus").format(l.items.length),
|
|
children: l.items.length
|
|
}), s = jsx("div", {
|
|
class: "fluent-menu-editor__category-error",
|
|
role: "alert"
|
|
}), u = !1, c = ()=>{
|
|
if (u) return;
|
|
let t = l.items.flatMap((e)=>{
|
|
let t = ei(e, l.id);
|
|
return t ? [
|
|
t
|
|
] : [];
|
|
});
|
|
t.length > 0 ? a.append(...t) : a.appendChild(jsx("div", {
|
|
class: "fluent-menu-editor__empty",
|
|
children: _("Drop second-level menus here")
|
|
})), u = !0;
|
|
};
|
|
r.addEventListener("toggle", ()=>{
|
|
r.open ? (P.add(l.id), c()) : P.delete(l.id);
|
|
}), r.open && c();
|
|
let p = x.categories.length > 1, g = w.get(l.id), f = jsx("input", {
|
|
type: "checkbox",
|
|
checked: !x.hiddenCategoryIds.has(l.id),
|
|
"aria-label": _("Show %s in the menu").format(l.title)
|
|
}), h = jsx("span", {
|
|
class: "fluent-menu-editor__category-name",
|
|
children: l.title
|
|
}), b = _("Edit"), v = jsx("button", {
|
|
class: "fluent-menu-editor__category-edit",
|
|
type: "button",
|
|
"aria-label": b,
|
|
title: b
|
|
}), I = ()=>{
|
|
let r = `${this.cbid(n)}-${l.id}-title`, o = jsx("input", {
|
|
id: r,
|
|
class: "cbi-input-text",
|
|
type: "text",
|
|
value: l.title,
|
|
"aria-label": _("Primary menu title")
|
|
}), a = jsx("p", {
|
|
class: "cbi-value-description",
|
|
role: "alert",
|
|
hidden: !0
|
|
}), d = null, s = ()=>{
|
|
let e = o.value.trim(), t = menu_y(x.categories.map((t)=>t.id === l.id ? {
|
|
...t,
|
|
title: e
|
|
} : t)).get(l.id) ?? "";
|
|
return a.textContent = t, a.hidden = !t, o.classList.toggle("cbi-input-invalid", !!t), d && (d.disabled = !!t), !t;
|
|
};
|
|
o.addEventListener("input", s), d = jsx("button", {
|
|
type: "button",
|
|
class: "btn cbi-button-save",
|
|
onclick: ()=>{
|
|
s() && (l.title = o.value.trim(), V(), eo(), L.ui.hideModal());
|
|
},
|
|
children: _("Save")
|
|
});
|
|
let u = g ? _("Restore %s to its original menu name").format(g.title) : "", c = g ? jsx("button", {
|
|
class: "fluent-menu-editor__item-reset",
|
|
type: "button",
|
|
"aria-label": u,
|
|
title: u,
|
|
onclick: ()=>{
|
|
o.value = g.title, s(), o.focus();
|
|
}
|
|
}) : null;
|
|
L.ui.showModal(_("Primary menu title"), jsxs(Fragment, {
|
|
children: [
|
|
jsxs("div", {
|
|
class: "fluent-menu-editor__rename-dialog",
|
|
children: [
|
|
jsx("label", {
|
|
htmlFor: r,
|
|
children: _("Primary menu title")
|
|
}),
|
|
jsxs("div", {
|
|
class: "fluent-menu-editor__rename-control",
|
|
children: [
|
|
o,
|
|
c
|
|
]
|
|
}),
|
|
a
|
|
]
|
|
}),
|
|
jsxs("div", {
|
|
class: "right",
|
|
children: [
|
|
jsx("button", {
|
|
type: "button",
|
|
class: "btn",
|
|
onclick: ()=>L.ui.hideModal(),
|
|
children: _("Cancel")
|
|
}),
|
|
d
|
|
]
|
|
})
|
|
]
|
|
})), s(), requestAnimationFrame(()=>{
|
|
o.focus(), o.select();
|
|
});
|
|
};
|
|
if (v.addEventListener("click", I), f.addEventListener("change", ()=>{
|
|
f.checked ? x.hiddenCategoryIds.delete(l.id) : x.hiddenCategoryIds.add(l.id), r.classList.toggle("is-hidden", !f.checked), V();
|
|
}), p) {
|
|
let t = {
|
|
kind: "category",
|
|
categoryId: l.id
|
|
}, i = jsx("span", {
|
|
class: "fluent-menu-editor__drag-handle",
|
|
title: _("Drag to reorder primary menu")
|
|
});
|
|
et(i, t, r), o.append(i);
|
|
}
|
|
if (o.append(v, h, d), !g) {
|
|
let t = jsx("button", {
|
|
class: "fluent-menu-editor__category-delete",
|
|
type: "button",
|
|
"aria-label": _("Delete primary menu"),
|
|
title: _("Delete primary menu")
|
|
});
|
|
t.addEventListener("click", ()=>{
|
|
let e = [
|
|
...l.items
|
|
];
|
|
for (let t of (x.categories = x.categories.filter((e)=>e.id !== l.id), x.hiddenCategoryIds.delete(l.id), x.pending.categoryMoves = x.pending.categoryMoves.filter(([e, t])=>e !== l.id && t !== l.id), x.pending.itemMoves = x.pending.itemMoves.filter(([, e])=>e !== l.id), P.delete(l.id), e))x.pending.itemMoves = x.pending.itemMoves.filter(([e])=>e !== t), x.categories = restoreMenuItemToOriginalPosition(x.categories, k, t);
|
|
V(), eo();
|
|
}), o.append(t);
|
|
}
|
|
o.append(f), r.append(o), s && r.append(s), r.append(a);
|
|
let M = {
|
|
card: r
|
|
};
|
|
return s && (M.error = s), $.set(l.id, M), r;
|
|
}, el = ()=>{
|
|
let e = x.pending.titles.length + x.pending.itemTitles.length + x.pending.categoryMoves.length + x.pending.itemMoves.length;
|
|
for (let t of x.hiddenItemPaths)M.has(t) || (e += 1);
|
|
let t = new Set(x.categories.map((e)=>e.id));
|
|
for (let i of x.hiddenCategoryIds)t.has(i) || (e += 1);
|
|
return e;
|
|
}, er = jsx("button", {
|
|
class: "btn cbi-button",
|
|
type: "button"
|
|
});
|
|
function eo() {
|
|
let e, t = new Map();
|
|
for (let e of R.querySelectorAll(".fluent-menu-editor__items")){
|
|
let i = e.dataset.categoryId;
|
|
i && t.set(i, e.scrollTop);
|
|
}
|
|
let i = window.scrollX, n = window.scrollY;
|
|
$.clear(), dom.content(R, x.categories.map((e)=>en(e))), U(), er.disabled = 0 === (e = el()), e > 0 ? er.textContent = _("Clean up invalid items (%d)").format(e) : er.textContent = _("Clean up invalid items"), requestAnimationFrame(()=>{
|
|
for (let e of R.querySelectorAll(".fluent-menu-editor__items")){
|
|
let i = e.dataset.categoryId, n = i ? t.get(i) : void 0;
|
|
null != n && (e.scrollTop = n);
|
|
}
|
|
window.scrollTo(i, n);
|
|
});
|
|
}
|
|
er.addEventListener("click", ()=>{
|
|
let i = el();
|
|
0 !== i && L.ui.showModal(_("Clean up invalid items"), jsxs("div", {
|
|
class: "fluent-menu-editor__rename-dialog",
|
|
children: [
|
|
jsx("p", {
|
|
children: _("Are you sure you want to clean up %d invalid/uninstalled menu items or rules?").format(i)
|
|
}),
|
|
jsxs("div", {
|
|
class: "right",
|
|
children: [
|
|
jsx("button", {
|
|
type: "button",
|
|
class: "btn",
|
|
onclick: ()=>L.ui.hideModal(),
|
|
children: _("Cancel")
|
|
}),
|
|
jsx("button", {
|
|
type: "button",
|
|
class: "btn cbi-button-save",
|
|
onclick: ()=>{
|
|
x.pending = {
|
|
titles: [],
|
|
itemTitles: [],
|
|
categoryMoves: [],
|
|
itemMoves: []
|
|
};
|
|
let e = new Set();
|
|
for (let t of x.hiddenItemPaths)M.has(t) && e.add(t);
|
|
x.hiddenItemPaths = e;
|
|
let t = new Set(), i = new Set(x.categories.map((e)=>e.id));
|
|
for (let e of x.hiddenCategoryIds)i.has(e) && t.add(e);
|
|
x.hiddenCategoryIds = t, V(), eo(), L.ui.hideModal();
|
|
},
|
|
children: _("Clean up")
|
|
})
|
|
]
|
|
})
|
|
]
|
|
}));
|
|
});
|
|
let ea = jsx("button", {
|
|
class: "btn cbi-button cbi-button-add",
|
|
type: "button",
|
|
children: _("Add primary menu")
|
|
}), ed = jsx("button", {
|
|
class: "btn cbi-button cbi-button-reset",
|
|
type: "button",
|
|
children: _("Restore defaults")
|
|
});
|
|
return ea.addEventListener("click", ()=>{
|
|
let e = _("New primary menu"), t = new Set(x.categories.map((e)=>menu_h(e.title))), i = e, n = 2;
|
|
for(; t.has(menu_h(i));)i = _("New primary menu %d").format(n), n += 1;
|
|
let l = "u" > typeof crypto && "function" == typeof crypto.randomUUID ? crypto.randomUUID() : (menu_f += 1, `category-${Date.now()}-${menu_f}`);
|
|
x.categories.push({
|
|
id: l,
|
|
title: i,
|
|
items: []
|
|
}), P.add(l), V(), eo();
|
|
}), ed.addEventListener("click", ()=>{
|
|
x = {
|
|
categories: buildDefaultMenuCategories(I),
|
|
hiddenCategoryIds: new Set(),
|
|
hiddenItemPaths: new Set(),
|
|
itemTitles: new Map(),
|
|
pending: {
|
|
titles: [],
|
|
itemTitles: [],
|
|
categoryMoves: [],
|
|
itemMoves: []
|
|
}
|
|
}, P.clear(), V(""), eo();
|
|
}), eo(), jsxs("div", {
|
|
class: "fluent-menu-editor",
|
|
children: [
|
|
A,
|
|
N,
|
|
jsx("div", {
|
|
class: "fluent-menu-editor__actions",
|
|
children: [
|
|
er,
|
|
ea,
|
|
ed
|
|
]
|
|
}),
|
|
F,
|
|
R
|
|
]
|
|
});
|
|
},
|
|
formvalue: (e)=>C.get(e)?.value ?? "",
|
|
validate: (e, t)=>(function(e, t) {
|
|
if ("" === t || null == t) return !0;
|
|
if ("string" != typeof t) return _("The stored menu layout is invalid. Restore defaults or edit the layout before saving.");
|
|
let i = parseMenuLayout(t);
|
|
return i ? menu_y(resolveMenuLayout(e, i).categories).values().next().value ?? !0 : _("The stored menu layout is invalid. Restore defaults or edit the layout before saving.");
|
|
})(I, t)
|
|
})), "menu_layout");
|
|
E.optional = !0, E.rmempty = !0;
|
|
}
|
|
|
|
;// CONCATENATED MODULE: ./web/resources/view/fluent-config.tsx
|
|
let fluent_config_e = L.form, fluent_config_o = L.uci;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class fluent_config_m extends L.view {
|
|
load() {
|
|
return Promise.all([
|
|
fluent_config_o.load("fluent"),
|
|
L.ui.menu.load()
|
|
]);
|
|
}
|
|
render(o) {
|
|
let [, m] = o, s = new fluent_config_e.Map("fluent", _("Fluent theme settings"), _("Configure color mode, accent colors, layout sizing, animation behavior, login-page appearance, and advanced CSS overrides for luci-theme-fluent.")), u = s.section(fluent_config_e.TypedSection, "global", _("Theme settings"));
|
|
return u.addremove = !1, u.anonymous = !0, registerGeneralTab(u), registerMenuTab(u, m), registerColorsTab(u), registerAnimationTab(u), registerLoginTab(u), registerAdvancedTab(u), registerAboutTab(u), s.render();
|
|
}
|
|
}
|
|
const main = fluent_config_m;
|
|
|
|
|
|
return main; |