js cleanup

This commit is contained in:
Maxim Devaev 2025-02-06 14:09:58 +02:00
parent beb5d541b0
commit 94fe2226f1
13 changed files with 445 additions and 357 deletions

View File

@ -39,23 +39,39 @@ export function main() {
}
function __setAppText() {
let e_href = tools.escape(window.location.href);
$("app-text").innerHTML = `
<span class="code-comment"># On Linux using Chromium/Chrome via any terminal:<br>
$</span> \`which chromium 2>/dev/null || which chrome 2>/dev/null || which google-chrome\` --app="${window.location.href}"<br>
$</span> \`which chromium 2>/dev/null || which chrome 2>/dev/null || which google-chrome\` --app="${e_href}"<br>
<br>
<span class="code-comment"># On MacOS using Terminal application:<br>
$</span> /Applications/Google&bsol; Chrome.app/Contents/MacOS/Google&bsol; Chrome --app="${window.location.href}"<br>
$</span> /Applications/Google&bsol; Chrome.app/Contents/MacOS/Google&bsol; Chrome --app="${e_href}"<br>
<br>
<span class="code-comment"># On Windows via cmd.exe:<br>
C:&bsol;&gt;</span> start chrome --app="${window.location.href}"
C:&bsol;&gt;</span> start chrome --app="${e_href}"
`;
}
function __loadKvmdInfo() {
tools.httpGet("api/info", {"fields": "auth,meta,extras"}, function(http) {
if (http.status === 200) {
let info = JSON.parse(http.responseText).result;
switch (http.status) {
case 200:
__showKvmdInfo(JSON.parse(http.responseText).result);
break;
case 401:
case 403:
tools.currentOpen("login");
break;
default:
setTimeout(__loadKvmdInfo, 1000);
break;
}
});
}
function __showKvmdInfo(info) {
let apps = [];
if (info.extras === null) {
wm.error("Not all applications in the menu can be displayed due an error.<br>See KVMD logs for details.");
@ -71,7 +87,7 @@ function __loadKvmdInfo() {
});
}
$("apps-box").innerHTML = "<ul id=\"apps\"></ul>";
let html = "";
// Don't use this option, it may be removed in any time
let hide_kvm_button = (
@ -79,17 +95,22 @@ function __loadKvmdInfo() {
|| tools.config.getBool("index--hide-kvm-button", false)
);
if (!hide_kvm_button) {
$("apps").innerHTML += __makeApp(null, "kvm", "share/svg/kvm.svg", "KVM");
html += __makeApp(null, "kvm", "share/svg/kvm.svg", "KVM");
}
for (let app of apps) {
if (app.place >= 0 && (app.enabled || app.started)) {
$("apps").innerHTML += __makeApp(null, app.path, app.icon, app.name);
html += __makeApp(null, app.path, app.icon, app.name);
}
}
if (info.auth.enabled) {
$("apps").innerHTML += __makeApp("logout-button", "#", "share/svg/logout.svg", "Logout");
html += __makeApp("logout-button", "#", "share/svg/logout.svg", "Logout");
}
$("apps-box").innerHTML = `<ul id="apps">${html}</ul>`;
if (info.auth.enabled) {
tools.el.setOnClick($("logout-button"), __logout);
}
@ -100,23 +121,18 @@ function __loadKvmdInfo() {
$("kvmd-meta-server-host").innerHTML = "";
document.title = "PiKVM Index";
}
} else if (http.status === 401 || http.status === 403) {
tools.currentOpen("login");
} else {
setTimeout(__loadKvmdInfo, 1000);
}
});
}
function __makeApp(id, path, icon, name) {
// Tailing slash in href is added to avoid Nginx 301 redirect
// when the location doesn't have tailing slash: "foo -> foo/".
// Reverse proxy over PiKVM can be misconfigured to handle this.
let e_add_id = (id ? `id="${tools.escape(id)}"` : "");
return `<li>
<div ${id ? "id=\"" + id + "\"" : ""} class="app">
<a href="${ROOT_PREFIX}${path}/">
<div ${e_add_id} class="app">
<a href="${tools.escape(ROOT_PREFIX + path)}/">
<div>
<img class="svg-gray" src="${ROOT_PREFIX}${icon}">
<img class="svg-gray" src="${tools.escape(ROOT_PREFIX + icon)}">
${tools.escape(name)}
</div>
</a>
@ -126,10 +142,16 @@ function __makeApp(id, path, icon, name) {
function __logout() {
tools.httpPost("api/auth/logout", null, function(http) {
if (http.status === 200 || http.status === 401 || http.status === 403) {
switch (http.status) {
case 200:
case 401:
case 403:
tools.currentOpen("login");
} else {
break;
default:
wm.error("Logout error", http.responseText);
break;
}
});
}

View File

@ -32,29 +32,43 @@ export function main() {
function __loadKvmdInfo() {
tools.httpGet("api/info", null, function(http) {
if (http.status === 200) {
let ipmi_port = JSON.parse(http.responseText).result.extras.ipmi.port;
let make_item = (comment, ipmi, api) => `
<span class="code-comment"># ${comment}:<br>$</span>
ipmitool -I lanplus -U admin -P admin -H ${window.location.hostname} -p ${ipmi_port} ${ipmi}<br>
<span class="code-comment">$</span> curl -XPOST -HX-KVMD-User:admin -HX-KVMD-Passwd:admin -k \\<br>
&nbsp;&nbsp;&nbsp;&nbsp;${window.location.protocol}//${window.location.host}/api/atx${api}<br>
`;
$("ipmi-text").innerHTML = `
${make_item("Power on the server if it's off", "power on", "/power?action=on")}
<br>
${make_item("Soft power off the server if it's on", "power soft", "/power?action=off")}
<br>
${make_item("Hard power off the server if it's on", "power off", "/power?action=off_hard")}
<br>
${make_item("Hard reset the server if it's on", "power reset", "/power?action=reset_hard")}
<br>
${make_item("Check the power status", "power status", "")}
`;
} else if (http.status === 401 || http.status === 403) {
switch (http.status) {
case 200:
__showKvmdInfo(JSON.parse(http.responseText).result);
break;
case 401:
case 403:
tools.currentOpen("login");
} else {
break;
default:
setTimeout(__loadKvmdInfo, 1000);
break;
}
});
}
function __showKvmdInfo(info) {
let make_item = function (comment, cmd, api) {
return `
<span class="code-comment">
# ${tools.escape(comment)}:<br>$
</span>
ipmitool -I lanplus -U admin -P admin
-H ${tools.escape(window.location.hostname)}
-p ${tools.escape(info.extras.ipmi.port)} ${tools.escape(cmd)}
<br>
<span class="code-comment">$</span>
curl -XPOST -HX-KVMD-User:admin -HX-KVMD-Passwd:admin -k \\<br>&nbsp;&nbsp;&nbsp;&nbsp;
${tools.escape(window.location.protocol + "//" + window.location.host + "/api/atx" + api)}
`;
};
$("ipmi-text").innerHTML = [
make_item("Power on the server if it's off", "power on", "/power?action=on"),
make_item("Soft power off the server if it's on", "power soft", "/power?action=off"),
make_item("Hard power off the server if it's on", "power off", "/power?action=off_hard"),
make_item("Hard reset the server if it's on", "power reset", "/power?action=reset_hard"),
make_item("Check the power status", "power status", ""),
].join("<br><br>");
}

View File

@ -106,7 +106,7 @@ export function Atx(__recorder) {
if ($("atx-ask-switch").checked) {
wm.confirm(`
Are you sure you want to press the <b>${button}</b> button?<br>
Are you sure you want to press the <b>${tools.escape(button)}</b> button?<br>
Warning! This could cause data loss on the server.
`).then(function(ok) {
if (ok) {

View File

@ -135,30 +135,36 @@ export function Gpio(__recorder) {
var __createItem = function(item) {
if (item.type === "label") {
return item.text;
} else if (item.type === "input") {
let e_ch_class = tools.escape(`__gpio-led-${item.channel}`);
let e_icon = tools.escape(`${ROOT_PREFIX}share/svg/led-circle.svg`);
return `
<img
class="__gpio-led __gpio-led-${item.channel} inline-lamp-big led-gray"
src="${ROOT_PREFIX}share/svg/led-circle.svg"
data-color="${item.color}"
class="__gpio-led ${e_ch_class} inline-lamp-big led-gray"
src="${e_icon}"
data-color="${tools.escape(item.color)}"
/>
`;
} else if (item.type === "output") {
let controls = [];
let confirm = (item.confirm ? "Are you sure you want to perform this action?" : "");
let e_ch = tools.escape(item.channel);
let e_confirm = (item.confirm ? tools.escape("Are you sure you want to perform this action?") : "");
if (item.scheme["switch"]) {
let id = tools.makeId();
let e_id = tools.escape(`__gpio-switch-${tools.makeRandomId()}`);
let e_ch_class = tools.escape(`__gpio-switch-${item.channel}`);
controls.push(`
<td><div class="switch-box">
<input
disabled
type="checkbox"
id="__gpio-switch-${id}"
class="__gpio-switch __gpio-switch-${item.channel}"
data-channel="${item.channel}"
data-confirm="${confirm}"
id="${e_id}"
class="__gpio-switch ${e_ch_class}"
data-channel="${e_ch}"
data-confirm="${e_confirm}"
/>
<label for="__gpio-switch-${id}">
<label for="${e_id}">
<span class="switch-inner"></span>
<span class="switch"></span>
</label>
@ -166,22 +172,23 @@ export function Gpio(__recorder) {
`);
}
if (item.scheme.pulse.delay) {
let e_ch_class = tools.escape(`__gpio-button-${item.channel}`);
controls.push(`
<td><button
disabled
class="__gpio-button __gpio-button-${item.channel}"
class="__gpio-button ${e_ch_class}"
${item.hide ? "data-force-hide-menu" : ""}
data-channel="${item.channel}"
data-confirm="${confirm}"
data-channel="${e_ch}"
data-confirm="${e_confirm}"
>
${(item.hide ? "&bull; " : "") + item.text}
${(item.hide ? "&bull; " : "") + tools.escape(item.text)}
</button></td>
`);
}
return `<table><tr>${controls.join("<td>&nbsp;&nbsp;&nbsp;</td>")}</tr></table>`;
} else {
return "";
}
return "";
};
var __setLedState = function(el, on) {

View File

@ -112,7 +112,7 @@ export function Mouse(__getGeometry, __recordWsEvent) {
};
var __updateRate = function(value) {
$("hid-mouse-rate-value").innerHTML = value + " ms";
$("hid-mouse-rate-value").innerText = value + " ms";
tools.storage.set("hid.mouse.rate", value);
if (__timer) {
clearInterval(__timer);
@ -121,13 +121,13 @@ export function Mouse(__getGeometry, __recordWsEvent) {
};
var __updateScrollRate = function(value) {
$("hid-mouse-scroll-value").innerHTML = value;
$("hid-mouse-scroll-value").innerText = value;
tools.storage.set("hid.mouse.scroll_rate", value);
__scroll_rate = value;
};
var __updateRelativeSens = function(value) {
$("hid-mouse-sens-value").innerHTML = value.toFixed(1);
$("hid-mouse-sens-value").innerText = value.toFixed(1);
tools.storage.set("hid.mouse.sens", value);
__relative_sens = value;
};

View File

@ -208,7 +208,7 @@ export function Msd() {
if (el.__names_json !== names_json) {
el.innerHTML = names.map(name => `
<div class="text">
<div id="__msd-storage-${tools.makeIdByText(name)}-progress" class="progress">
<div id="__msd-storage-${tools.makeTextId(name)}-progress" class="progress">
<span class="progress-value"></span>
</div>
</div>
@ -223,7 +223,7 @@ export function Msd() {
? `${names.length === 1 ? "Storage: %s" : "Internal storage: %s"}` // eslint-disable-line
: `Storage [${name}${part.writable ? "]" : ", read-only]"}: %s` // eslint-disable-line
);
let id = `__msd-storage-${tools.makeIdByText(name)}-progress`;
let id = `__msd-storage-${tools.makeTextId(name)}-progress`;
tools.progress.setSizeOf($(id), title, part.size, part.free);
}
};
@ -270,8 +270,8 @@ export function Msd() {
};
var __clickDownloadButton = function() {
let image = encodeURIComponent($("msd-image-selector").value);
tools.windowOpen(`api/msd/read?image=${image}`);
let e_image = encodeURIComponent($("msd-image-selector").value);
tools.windowOpen(`api/msd/read?image=${e_image}`);
};
var __clickRemoveButton = function() {
@ -299,13 +299,13 @@ export function Msd() {
var __clickUploadNewButton = function() {
let file = tools.input.getFile($("msd-new-file"));
__http = new XMLHttpRequest();
let prefix = encodeURIComponent($("msd-new-part-selector").value);
let e_prefix = encodeURIComponent($("msd-new-part-selector").value);
if (file) {
let image = encodeURIComponent(file.name);
__http.open("POST", `${ROOT_PREFIX}api/msd/write?prefix=${prefix}&image=${image}&remove_incomplete=1`, true);
let e_image = encodeURIComponent(file.name);
__http.open("POST", `${ROOT_PREFIX}api/msd/write?prefix=${e_prefix}&image=${e_image}&remove_incomplete=1`, true);
} else {
let url = encodeURIComponent($("msd-new-url").value);
__http.open("POST", `${ROOT_PREFIX}api/msd/write_remote?prefix=${prefix}&url=${url}&remove_incomplete=1`, true);
let e_url = encodeURIComponent($("msd-new-url").value);
__http.open("POST", `${ROOT_PREFIX}api/msd/write_remote?prefix=${e_prefix}&url=${e_url}&remove_incomplete=1`, true);
}
__http.upload.timeout = 7 * 24 * 3600;
__http.onreadystatechange = __uploadStateChange;
@ -402,7 +402,8 @@ export function Msd() {
if (__state && __state.storage && __state.storage.parts) {
let part = __state.storage.parts[$("msd-new-part-selector").value];
if (part && (file.size > part.size)) {
wm.error(`The new image is too big for the Mass Storage partition.<br>Maximum: ${tools.formatSize(part.size)}`);
let e_size = tools.escape(tools.formatSize(part.size));
wm.error(`The new image is too big for the Mass Storage partition.<br>Maximum: ${e_size}`);
el.value = "";
}
}

View File

@ -220,7 +220,8 @@ export function MediaStreamer(__setActive, __setInactive, __setInfo, __orient) {
let width = frame.displayWidth;
let height = frame.displayHeight;
switch (__orient) {
case 90: case 270:
case 90:
case 270:
width = frame.displayHeight;
height = frame.displayWidth;
}

View File

@ -32,7 +32,7 @@ export function MjpegStreamer(__setActive, __setInactive, __setInfo) {
/************************************************************************/
var __key = tools.makeId();
var __key = tools.makeRandomId();
var __id = "";
var __fps = -1;
var __state = null;
@ -91,7 +91,7 @@ export function MjpegStreamer(__setActive, __setInactive, __setInfo) {
var __setStreamInactive = function() {
let old_fps = __fps;
__key = tools.makeId();
__key = tools.makeRandomId();
__id = "";
__fps = -1;
__state = null;
@ -139,7 +139,7 @@ export function MjpegStreamer(__setActive, __setInactive, __setInfo) {
__setStreamInactive();
__stopChecking();
let path = `${ROOT_PREFIX}streamer/stream?key=${__key}`;
let path = `${ROOT_PREFIX}streamer/stream?key=${encodeURIComponent(__key)}`;
if (tools.browser.is_safari || tools.browser.is_ios) {
// uStreamer fix for WebKit
__logInfo("Using dual_final_frames=1 to fix WebKit bugs");

View File

@ -178,8 +178,8 @@ export function Switch() {
};
var __clickAddEdidButton = function() {
let create_content = function(el_parent, el_ok_button) {
tools.el.setEnabled(el_ok_button, false);
let create_content = function(el_parent, el_ok_bt) {
tools.el.setEnabled(el_ok_bt, false);
el_parent.innerHTML = `
<table>
<tr>
@ -203,7 +203,7 @@ export function Switch() {
el_name.oninput = el_data.oninput = function() {
let name = el_name.value.replace(/\s+/g, "");
let data = el_data.value.replace(/\s+/g, "");
tools.el.setEnabled(el_ok_button, ((name.length > 0) && /[0-9a-fA-F]{512}/.test(data)));
tools.el.setEnabled(el_ok_bt, ((name.length > 0) && /[0-9a-fA-F]{512}/.test(data)));
};
};
@ -584,7 +584,7 @@ export function Switch() {
};
if ($("switch-atx-ask-switch").checked) {
wm.confirm(`
Are you sure you want to press the <b>${button}</b> button?<br>
Are you sure you want to press the <b>${tools.escape(button)}</b> button?<br>
Warning! This could cause data loss on the server.
`).then(function(ok) {
if (ok) {

View File

@ -52,20 +52,28 @@ function __login() {
let passwd = $("passwd-input").value + $("code-input").value;
let body = `user=${encodeURIComponent(user)}&passwd=${encodeURIComponent(passwd)}`;
tools.httpPost("api/auth/login", null, function(http) {
if (http.status === 200) {
switch (http.status) {
case 200:
tools.currentOpen("");
} else if (http.status === 403) {
break;
case 403:
wm.error("Invalid credentials").then(__tryAgain);
} else {
break;
default: {
let error = "";
if (http.status === 400) {
try { error = JSON.parse(http.responseText)["result"]["error"]; } catch { /* Nah */ }
try {
error = JSON.parse(http.responseText)["result"]["error"];
} catch { /* Nah */ }
}
if (error === "ValidatorError") {
wm.error("Invalid characters in credentials").then(__tryAgain);
} else {
wm.error("Login error", http.responseText).then(__tryAgain);
}
} break;
}
}, body, "application/x-www-form-urlencoded");
__setEnabled(false);

View File

@ -86,8 +86,11 @@ export var tools = new function() {
/************************************************************************/
self.escape = function(text) {
if (typeof text !== "string") {
text = "" + text;
}
return text.replace(
/[^0-9A-Za-z ]/g,
/[^-_0-9A-Za-z ]/g,
ch => "&#" + ch.charCodeAt(0) + ";"
);
};
@ -100,7 +103,7 @@ export var tools = new function() {
return text[0].toUpperCase() + text.slice(1);
};
self.makeId = function() {
self.makeRandomId = function() {
let chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let id = "";
for (let count = 0; count < 16; ++count) {
@ -109,16 +112,10 @@ export var tools = new function() {
return id;
};
self.makeIdByText = function(text) {
self.makeTextId = function(text) {
return btoa(text).replace("=", "_");
};
self.getRandomInt = function(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
};
self.formatSize = function(size) {
if (size > 0) {
let index = Math.floor( Math.log(size) / Math.log(1024) );
@ -149,6 +146,12 @@ export var tools = new function() {
return remapped;
};
self.getRandomInt = function(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
};
/************************************************************************/
self.el = new function() {
@ -270,26 +273,34 @@ export var tools = new function() {
self.radio = new function() {
return {
"makeItem": function(name, title, value) {
let e_id = self.escape(name) + self.makeTextId(value);
return `
<input type="radio" id="${name}-${value}" name="${name}" value="${value}" />
<label for="${name}-${value}">${title}</label>
<input
type="radio"
id="${e_id}"
name="${tools.escape(name)}"
value="${tools.escape(value)}"
/>
<label for="${e_id}">
${tools.escape(title)}
</label>
`;
},
"setOnClick": function(name, callback, prevent_default=true) {
for (let el of $$$(`input[type="radio"][name="${name}"]`)) {
for (let el of $$$(`input[type="radio"][name="${CSS.escape(name)}"]`)) {
self.el.setOnClick(el, callback, prevent_default);
}
},
"getValue": function(name) {
return document.querySelector(`input[type="radio"][name="${name}"]:checked`).value;
return document.querySelector(`input[type="radio"][name="${CSS.escape(name)}"]:checked`).value;
},
"setValue": function(name, value) {
for (let el of $$$(`input[type="radio"][name="${name}"]`)) {
for (let el of $$$(`input[type="radio"][name="${CSS.escape(name)}"]`)) {
el.checked = (el.value === value);
}
},
"clickValue": function(name, value) {
for (let el of $$$(`input[type="radio"][name="${name}"]`)) {
for (let el of $$$(`input[type="radio"][name="${CSS.escape(name)}"]`)) {
if (el.value === value) {
el.click();
return;
@ -297,7 +308,7 @@ export var tools = new function() {
}
},
"setEnabled": function(name, enabled) {
for (let el of $$$(`input[type="radio"][name="${name}"]`)) {
for (let el of $$$(`input[type="radio"][name="${CSS.escape(name)}"]`)) {
self.el.setEnabled(el, enabled);
}
},

View File

@ -32,16 +32,26 @@ export function main() {
function __loadKvmdInfo() {
tools.httpGet("api/info", null, function(http) {
if (http.status === 200) {
let vnc_port = JSON.parse(http.responseText).result.extras.vnc.port;
$("vnc-text").innerHTML = `
<span class="code-comment"># How to connect using the Linux terminal:<br>
$</span> vncviewer ${window.location.hostname}::${vnc_port}
`;
} else if (http.status === 401 || http.status === 403) {
switch (http.status) {
case 200:
__showKvmdInfo(JSON.parse(http.responseText).result);
break;
case 401:
case 403:
tools.currentOpen("login");
} else {
break;
default:
setTimeout(__loadKvmdInfo, 1000);
break;
}
});
}
function __showKvmdInfo(info) {
$("vnc-text").innerHTML = `
<span class="code-comment"># How to connect using the Linux terminal:<br>
$</span> vncviewer ${tools.escape(window.location.hostname + "::" + info.extras.vnc.port)}
`;
}

View File

@ -42,96 +42,105 @@ function __WindowManager() {
var __menu_buttons = [];
var __init__ = function() {
for (let el_button of $$$("button")) {
for (let el of $$$("button")) {
// XXX: Workaround for iOS Safari:
// https://stackoverflow.com/questions/3885018/active-pseudo-class-doesnt-work-in-mobile-safari
el_button.ontouchstart = function() {};
el.ontouchstart = function() {};
}
for (let el_button of $$("menu-button")) {
el_button.parentElement.querySelector(".menu").setAttribute("tabindex", "-1");
tools.el.setOnDown(el_button, () => __toggleMenu(el_button));
__menu_buttons.push(el_button);
for (let el of $$("menu-button")) {
el.parentElement.querySelector(".menu").setAttribute("tabindex", "-1");
tools.el.setOnDown(el, () => __toggleMenu(el));
__menu_buttons.push(el);
}
if (!window.ResizeObserver) {
tools.error("ResizeObserver not supported");
}
for (let el_window of $$("window")) {
el_window.setAttribute("tabindex", "-1");
__makeWindowMovable(el_window);
__windows.push(el_window);
for (let el_win of $$("window")) {
el_win.setAttribute("tabindex", "-1");
__makeWindowMovable(el_win);
__windows.push(el_win);
if (el_window.classList.contains("window-resizable") && window.ResizeObserver) {
if (el_win.classList.contains("window-resizable") && window.ResizeObserver) {
new ResizeObserver(function() {
// При переполнении рабочей области сократить размер окна по высоте.
// По ширине оно настраивается само в CSS.
let view = self.getViewGeometry();
let rect = el_window.getBoundingClientRect();
let rect = el_win.getBoundingClientRect();
if ((rect.bottom - rect.top) > (view.bottom - view.top)) {
let ratio = (rect.bottom - rect.top) / (view.bottom - view.top);
el_window.style.height = view.bottom - view.top + "px";
el_window.style.width = Math.round((rect.right - rect.left) / ratio) + "px";
el_win.style.height = view.bottom - view.top + "px";
el_win.style.width = Math.round((rect.right - rect.left) / ratio) + "px";
}
if (el_window.hasAttribute("data-centered")) {
__centerWindow(el_window);
if (el_win.hasAttribute("data-centered")) {
__centerWindow(el_win);
}
}).observe(el_window);
}).observe(el_win);
}
let el_close_button = el_window.querySelector(".window-header .window-button-close");
if (el_close_button) {
el_close_button.title = "Close window";
tools.el.setOnClick(el_close_button, () => self.closeWindow(el_window));
{
let el = el_win.querySelector(".window-header .window-button-close");
if (el) {
el.title = "Close window";
tools.el.setOnClick(el, () => self.closeWindow(el_win));
}
}
let el_maximize_button = el_window.querySelector(".window-header .window-button-maximize");
if (el_maximize_button) {
el_maximize_button.title = "Maximize window";
tools.el.setOnClick(el_maximize_button, function() {
__maximizeWindow(el_window);
__activateLastWindow(el_window);
});
}
let el_orig_button = el_window.querySelector(".window-header .window-button-original");
if (el_orig_button) {
el_orig_button.title = "Reduce window to its original size and center it";
tools.el.setOnClick(el_orig_button, function() {
el_window.style.width = "";
el_window.style.height = "";
__centerWindow(el_window);
__activateLastWindow(el_window);
});
}
let el_enter_full_tab_button = el_window.querySelector(".window-header .window-button-enter-full-tab");
let el_exit_full_tab_button = el_window.querySelector(".window-button-exit-full-tab");
if (el_enter_full_tab_button && el_exit_full_tab_button) {
el_enter_full_tab_button.title = "Stretch to the entire tab";
tools.el.setOnClick(el_enter_full_tab_button, () => self.setFullTabWindow(el_window, true));
tools.el.setOnClick(el_exit_full_tab_button, () => self.setFullTabWindow(el_window, false));
}
let el_full_screen_button = el_window.querySelector(".window-header .window-button-full-screen");
if (el_full_screen_button && __getFullScreenFunction(el_window)) {
el_full_screen_button.title = "Go to full-screen mode";
tools.el.setOnClick(el_full_screen_button, function() {
__fullScreenWindow(el_window);
el_window.focus(el_window); // Почему-то теряется фокус
__activateLastWindow(el_window);
{
let el = el_win.querySelector(".window-header .window-button-maximize");
if (el) {
el.title = "Maximize window";
tools.el.setOnClick(el, function() {
__maximizeWindow(el_win);
__activateLastWindow(el_win);
});
}
}
for (let el_button of $$$("button[data-show-window]")) {
tools.el.setOnClick(el_button, () => self.showWindow($(el_button.getAttribute("data-show-window"))));
{
let el = el_win.querySelector(".window-header .window-button-original");
if (el) {
el.title = "Reduce window to its original size and center it";
tools.el.setOnClick(el, function() {
el_win.style.width = "";
el_win.style.height = "";
__centerWindow(el_win);
__activateLastWindow(el_win);
});
}
}
window.onmouseup = __globalMouseButtonHandler;
window.ontouchend = __globalMouseButtonHandler;
{
let el_enter = el_win.querySelector(".window-header .window-button-enter-full-tab");
let el_exit = el_win.querySelector(".window-button-exit-full-tab");
if (el_enter && el_exit) {
el_enter.title = "Stretch to the entire tab";
tools.el.setOnClick(el_enter, () => self.setFullTabWindow(el_win, true));
tools.el.setOnClick(el_exit, () => self.setFullTabWindow(el_win, false));
}
}
{
let el = el_win.querySelector(".window-header .window-button-full-screen");
if (el && __getFullScreenFunction(el_win)) {
el.title = "Go to full-screen mode";
tools.el.setOnClick(el, function() {
__fullScreenWindow(el_win);
el_win.focus(el_win); // Почему-то теряется фокус
__activateLastWindow(el_win);
});
}
}
}
for (let el of $$$("button[data-show-window]")) {
tools.el.setOnClick(el, () => self.showWindow($(el.getAttribute("data-show-window"))));
}
window.onmouseup = window.ontouchend = __globalMouseButtonHandler;
window.addEventListener("focusin", (event) => __focusInOut(event, true));
window.addEventListener("focusout", (event) => __focusInOut(event, false));
@ -196,7 +205,12 @@ function __WindowManager() {
var __modalCodeDialog = function(header, html, code, ok, cancel) {
let create_content = function(el_content) {
if (code) {
html += `<br><br><div class="code"><pre style="margin:0px">${tools.escape(code)}</pre></div>`;
html += `
<br><br>
<div class="code">
<pre style="margin:0px">${tools.escape(code)}</pre>
</div>
`;
}
el_content.innerHTML = html;
};
@ -210,49 +224,49 @@ function __WindowManager() {
el_modal.className = "modal";
el_modal.style.visibility = "visible";
let el_window = document.createElement("div");
el_window.className = "modal-window";
el_window.setAttribute("tabindex", "-1");
el_modal.appendChild(el_window);
let el_win = document.createElement("div");
el_win.className = "modal-window";
el_win.setAttribute("tabindex", "-1");
el_modal.appendChild(el_win);
let el_header = document.createElement("div");
el_header.className = "modal-header";
el_header.innerText = header;
el_window.appendChild(el_header);
el_win.appendChild(el_header);
let el_content = document.createElement("div");
el_content.className = "modal-content";
el_window.appendChild(el_content);
el_win.appendChild(el_content);
let el_buttons = document.createElement("div");
el_buttons.classList.add("modal-buttons", "buttons-row");
el_window.appendChild(el_buttons);
el_win.appendChild(el_buttons);
let el_cancel_button = null;
let el_ok_button = null;
let el_cancel_bt = null;
let el_ok_bt = null;
if (cancel) {
el_cancel_button = document.createElement("button");
el_cancel_button.className = "row100";
el_cancel_button.innerText = "Cancel";
el_buttons.appendChild(el_cancel_button);
el_cancel_bt = document.createElement("button");
el_cancel_bt.className = "row100";
el_cancel_bt.innerText = "Cancel";
el_buttons.appendChild(el_cancel_bt);
}
if (ok) {
el_ok_button = document.createElement("button");
el_ok_button.className = "row100";
el_ok_button.innerText = "OK";
el_buttons.appendChild(el_ok_button);
el_ok_bt = document.createElement("button");
el_ok_bt.className = "row100";
el_ok_bt.innerText = "OK";
el_buttons.appendChild(el_ok_bt);
}
if (ok && cancel) {
el_ok_button.className = "row50";
el_cancel_button.className = "row50";
el_ok_bt.className = "row50";
el_cancel_bt.className = "row50";
}
el_window.onkeyup = function(event) {
el_win.onkeyup = function(event) {
event.preventDefault();
if (ok && event.code === "Enter") {
el_ok_button.click();
el_ok_bt.click();
} else if (cancel && event.code === "Escape") {
el_cancel_button.click();
el_cancel_bt.click();
}
};
@ -260,7 +274,7 @@ function __WindowManager() {
if (ok || cancel) {
promise = new Promise(function(resolve) {
function close(retval) {
__closeWindow(el_window);
__closeWindow(el_win);
let index = __windows.indexOf(el_modal);
if (index !== -1) {
__windows.splice(index, 1);
@ -276,10 +290,10 @@ function __WindowManager() {
}
if (cancel) {
tools.el.setOnClick(el_cancel_button, () => close(false));
tools.el.setOnClick(el_cancel_bt, () => close(false));
}
if (ok) {
tools.el.setOnClick(el_ok_button, () => close(true));
tools.el.setOnClick(el_ok_bt, () => close(true));
}
});
}
@ -288,7 +302,7 @@ function __WindowManager() {
(parent || document.fullscreenElement || document.body).appendChild(el_modal);
if (typeof html === "function") {
// Это должно быть здесь, потому что элемент должен иметь родителя чтобы существовать
html(el_content, el_ok_button);
html(el_content, el_ok_bt);
} else {
el_content.innerHTML = html;
}
@ -297,26 +311,26 @@ function __WindowManager() {
return promise;
};
self.showWindow = function(el_window, activate=true, center=false) {
self.showWindow = function(el_win, activate=true, center=false) {
let showed = false;
if (!self.isWindowVisible(el_window)) {
if (!self.isWindowVisible(el_win)) {
center = true;
showed = true;
}
__organizeWindow(el_window, center);
el_window.style.visibility = "visible";
__organizeWindow(el_win, center);
el_win.style.visibility = "visible";
if (activate) {
__activateWindow(el_window);
__activateWindow(el_win);
}
if (el_window.show_hook) {
if (el_win.show_hook) {
if (showed) {
el_window.show_hook();
el_win.show_hook();
}
}
};
self.isWindowVisible = function(el_window) {
return (window.getComputedStyle(el_window, null).visibility !== "hidden");
self.isWindowVisible = function(el_win) {
return (window.getComputedStyle(el_win, null).visibility !== "hidden");
};
self.getViewGeometry = function() {
@ -329,35 +343,35 @@ function __WindowManager() {
};
};
self.closeWindow = function(el_window) {
__closeWindow(el_window);
__activateLastWindow(el_window);
self.closeWindow = function(el_win) {
__closeWindow(el_win);
__activateLastWindow(el_win);
};
self.setFullTabWindow = function(el_window, enabled) {
el_window.classList.toggle("window-full-tab", enabled);
__activateLastWindow(el_window);
self.setFullTabWindow = function(el_win, enabled) {
el_win.classList.toggle("window-full-tab", enabled);
__activateLastWindow(el_win);
let el_navbar = $("navbar");
if (el_navbar) {
tools.hidden.setVisible(el_navbar, !enabled);
}
};
var __closeWindow = function(el_window) {
el_window.focus();
el_window.blur();
el_window.style.visibility = "hidden";
if (el_window.close_hook) {
el_window.close_hook();
var __closeWindow = function(el_win) {
el_win.focus();
el_win.blur();
el_win.style.visibility = "hidden";
if (el_win.close_hook) {
el_win.close_hook();
}
};
var __toggleMenu = function(el_a) {
let all_hidden = true;
for (let el_button of __menu_buttons) {
let el_menu = el_button.parentElement.querySelector(".menu");
if (el_button === el_a && window.getComputedStyle(el_menu, null).visibility === "hidden") {
for (let el_bt of __menu_buttons) {
let el_menu = el_bt.parentElement.querySelector(".menu");
if (el_bt === el_a && window.getComputedStyle(el_menu, null).visibility === "hidden") {
let rect = el_menu.getBoundingClientRect();
let offset = self.getViewGeometry().right - (rect.left + el_menu.clientWidth + 2); // + 2 is ugly hack
if (offset < 0) {
@ -366,13 +380,13 @@ function __WindowManager() {
el_menu.style.removeProperty("right");
}
el_button.classList.add("menu-button-pressed");
el_bt.classList.add("menu-button-pressed");
el_menu.style.visibility = "visible";
let el_focus = el_menu.querySelector("[data-focus]");
(el_focus !== null ? el_focus : el_menu).focus();
all_hidden &= false;
} else {
el_button.classList.remove("menu-button-pressed");
el_bt.classList.remove("menu-button-pressed");
el_menu.style.visibility = "hidden";
el_menu.style.removeProperty("right");
}
@ -394,9 +408,9 @@ function __WindowManager() {
var __closeAllMenues = function() {
document.onkeyup = null;
for (let el_button of __menu_buttons) {
let el_menu = el_button.parentElement.querySelector(".menu");
el_button.classList.remove("menu-button-pressed");
for (let el_bt of __menu_buttons) {
let el_menu = el_bt.parentElement.querySelector(".menu");
el_bt.classList.remove("menu-button-pressed");
el_menu.style.visibility = "hidden";
el_menu.style.removeProperty("right");
}
@ -420,10 +434,10 @@ function __WindowManager() {
&& !event.target.closest(".menu-button")
&& !event.target.closest(".modal")
) {
for (let el_item = event.target; el_item && el_item !== document; el_item = el_item.parentNode) {
if (el_item.classList.contains("menu")) {
for (let el = event.target; el && el !== document; el = el.parentNode) {
if (el.classList.contains("menu")) {
return;
} else if (el_item.hasAttribute("data-force-hide-menu")) {
} else if (el.hasAttribute("data-force-hide-menu")) {
break;
}
}
@ -433,122 +447,122 @@ function __WindowManager() {
};
var __organizeWindowsOnBrowserResize = function() {
for (let el_window of $$("window")) {
if (el_window.style.visibility === "visible") {
if (tools.browser.is_mobile && el_window.classList.contains("window-resizable")) {
for (let el_win of $$("window")) {
if (el_win.style.visibility === "visible") {
if (tools.browser.is_mobile && el_win.classList.contains("window-resizable")) {
// FIXME: При смене ориентации на мобильном браузере надо сбрасывать
// настройки окна стрима, поэтому тут стоит вот этот костыль
el_window.style.width = "";
el_window.style.height = "";
el_win.style.width = "";
el_win.style.height = "";
}
__organizeWindow(el_window);
__organizeWindow(el_win);
}
}
};
var __organizeWindow = function(el_window, center=false) {
var __organizeWindow = function(el_win, center=false) {
let view = self.getViewGeometry();
let rect = el_window.getBoundingClientRect();
let rect = el_win.getBoundingClientRect();
if (el_window.classList.contains("window-resizable")) {
if (el_win.classList.contains("window-resizable")) {
// При переполнении рабочей области сократить размер окна
if ((rect.bottom - rect.top) > (view.bottom - view.top)) {
let ratio = (rect.bottom - rect.top) / (view.bottom - view.top);
el_window.style.height = view.bottom - view.top + "px";
el_window.style.width = Math.round((rect.right - rect.left) / ratio) + "px";
el_win.style.height = view.bottom - view.top + "px";
el_win.style.width = Math.round((rect.right - rect.left) / ratio) + "px";
}
if ((rect.right - rect.left) > (view.right - view.left)) {
el_window.style.width = view.right - view.left + "px";
el_win.style.width = view.right - view.left + "px";
}
rect = el_window.getBoundingClientRect();
rect = el_win.getBoundingClientRect();
}
if (el_window.hasAttribute("data-centered") || center) {
__centerWindow(el_window);
if (el_win.hasAttribute("data-centered") || center) {
__centerWindow(el_win);
} else {
if (rect.top <= view.top) {
el_window.style.top = view.top + "px";
el_win.style.top = view.top + "px";
} else if (rect.bottom > view.bottom) {
el_window.style.top = view.bottom - rect.height + "px";
el_win.style.top = view.bottom - rect.height + "px";
}
if (rect.left <= view.left) {
el_window.style.left = view.left + "px";
el_win.style.left = view.left + "px";
} else if (rect.right > view.right) {
el_window.style.left = view.right - rect.width + "px";
el_win.style.left = view.right - rect.width + "px";
}
}
};
var __centerWindow = function(el_window) {
var __centerWindow = function(el_win) {
let view = self.getViewGeometry();
let rect = el_window.getBoundingClientRect();
el_window.style.top = Math.max(view.top, Math.round((view.bottom - rect.height) / 2)) + "px";
el_window.style.left = Math.round((view.right - rect.width) / 2) + "px";
el_window.setAttribute("data-centered", "");
let rect = el_win.getBoundingClientRect();
el_win.style.top = Math.max(view.top, Math.round((view.bottom - rect.height) / 2)) + "px";
el_win.style.left = Math.round((view.right - rect.width) / 2) + "px";
el_win.setAttribute("data-centered", "");
};
var __activateLastWindow = function(el_except_window=null) {
let el_last_window = null;
var __activateLastWindow = function(el_except_win=null) {
let el_last_win = null;
if (document.activeElement) {
el_last_window = (document.activeElement.closest(".modal-window") || document.activeElement.closest(".window"));
if (el_last_window && window.getComputedStyle(el_last_window, null).visibility === "hidden") {
el_last_window = null;
el_last_win = (document.activeElement.closest(".modal-window") || document.activeElement.closest(".window"));
if (el_last_win && window.getComputedStyle(el_last_win, null).visibility === "hidden") {
el_last_win = null;
}
}
if (!el_last_window || el_last_window === el_except_window) {
if (!el_last_win || el_last_win === el_except_win) {
let max_z_index = 0;
for (let el_window of __windows) {
let z_index = parseInt(window.getComputedStyle(el_window, null).zIndex) || 0;
let visibility = window.getComputedStyle(el_window, null).visibility;
for (let el_win of __windows) {
let z_index = parseInt(window.getComputedStyle(el_win, null).zIndex) || 0;
let visibility = window.getComputedStyle(el_win, null).visibility;
if (max_z_index < z_index && visibility !== "hidden" && el_window !== el_except_window) {
el_last_window = el_window;
if (max_z_index < z_index && visibility !== "hidden" && el_win !== el_except_win) {
el_last_win = el_win;
max_z_index = z_index;
}
}
}
if (el_last_window) {
tools.debug("UI: Activating last window:", el_last_window);
__activateWindow(el_last_window);
if (el_last_win) {
tools.debug("UI: Activating last window:", el_last_win);
__activateWindow(el_last_win);
} else {
tools.debug("UI: No last window to activation");
}
};
var __activateWindow = function(el_window) {
if (window.getComputedStyle(el_window, null).visibility !== "hidden") {
var __activateWindow = function(el_win) {
if (window.getComputedStyle(el_win, null).visibility !== "hidden") {
let el_to_focus;
let el_window_contains_focus;
let el_focused; // A window which contains a focus
if (el_window.className === "modal") {
el_to_focus = el_window.querySelector(".modal-window");
el_window_contains_focus = (document.activeElement && document.activeElement.closest(".modal-window"));
if (el_win.className === "modal") {
el_to_focus = el_win.querySelector(".modal-window");
el_focused = (document.activeElement && document.activeElement.closest(".modal-window"));
} else { // .window
el_to_focus = el_window;
el_window_contains_focus = (document.activeElement && document.activeElement.closest(".window"));
el_to_focus = el_win;
el_focused = (document.activeElement && document.activeElement.closest(".window"));
}
if (el_window.className !== "modal" && parseInt(el_window.style.zIndex) !== __top_z_index) {
if (el_win.className !== "modal" && parseInt(el_win.style.zIndex) !== __top_z_index) {
__top_z_index += 1;
el_window.style.zIndex = __top_z_index;
tools.debug("UI: Activated window:", el_window);
el_win.style.zIndex = __top_z_index;
tools.debug("UI: Activated window:", el_win);
}
if (el_window !== el_window_contains_focus) {
if (el_win !== el_focused) {
el_to_focus.focus();
tools.debug("UI: Focused window:", el_window);
tools.debug("UI: Focused window:", el_win);
}
}
};
var __makeWindowMovable = function(el_window) {
let el_header = el_window.querySelector(".window-header");
let el_grab = el_window.querySelector(".window-header .window-grab");
var __makeWindowMovable = function(el_win) {
let el_header = el_win.querySelector(".window-header");
let el_grab = el_win.querySelector(".window-header .window-grab");
if (el_header === null || el_grab === null) {
// Для псевдоокна OCR
return;
@ -559,10 +573,10 @@ function __WindowManager() {
function startMoving(event) {
// При перетаскивании resizable-окна за правый кран экрана оно ужимается.
// Этот костыль фиксит это.
el_window.style.width = el_window.offsetWidth + "px";
el_win.style.width = el_win.offsetWidth + "px";
__closeAllMenues();
__activateWindow(el_window);
__activateWindow(el_win);
event = (event || window.event);
event.preventDefault();
@ -580,7 +594,7 @@ function __WindowManager() {
}
function doMoving(event) {
el_window.removeAttribute("data-centered");
el_win.removeAttribute("data-centered");
event = (event || window.event);
event.preventDefault();
@ -589,8 +603,8 @@ function __WindowManager() {
let x = prev_pos.x - event_pos.x;
let y = prev_pos.y - event_pos.y;
el_window.style.top = (el_window.offsetTop - y) + "px";
el_window.style.left = (el_window.offsetLeft - x) + "px";
el_win.style.top = (el_win.offsetTop - y) + "px";
el_win.style.left = (el_win.offsetLeft - x) + "px";
prev_pos = event_pos;
}
@ -613,29 +627,29 @@ function __WindowManager() {
}
}
el_window.setAttribute("data-centered", "");
el_window.onmousedown = el_window.ontouchstart = () => __activateWindow(el_window);
el_win.setAttribute("data-centered", "");
el_win.onmousedown = el_win.ontouchstart = () => __activateWindow(el_win);
el_grab.onmousedown = startMoving;
el_grab.ontouchstart = startMoving;
};
var __onFullScreenChange = function(event) {
let el_window = event.target;
let el_win = event.target;
if (!document.fullscreenElement) {
let rect = el_window.before_full_screen;
let rect = el_win.before_full_screen;
if (rect) {
el_window.style.width = rect.width + "px";
el_window.style.height = rect.height + "px";
el_window.style.top = rect.top + "px";
el_window.style.left = rect.left + "px";
el_win.style.width = rect.width + "px";
el_win.style.height = rect.height + "px";
el_win.style.top = rect.top + "px";
el_win.style.left = rect.left + "px";
}
}
};
var __fullScreenWindow = function(el_window) {
el_window.before_full_screen = el_window.getBoundingClientRect();
__getFullScreenFunction(el_window).call(el_window);
var __fullScreenWindow = function(el_win) {
el_win.before_full_screen = el_win.getBoundingClientRect();
__getFullScreenFunction(el_win).call(el_win);
if (navigator.keyboard && navigator.keyboard.lock) {
navigator.keyboard.lock();
} else {
@ -647,26 +661,26 @@ function __WindowManager() {
+ "In Chrome use HTTPS and enable <i>system-keyboard-lock</i><br>"
+ "by putting at URL <i>chrome://flags/#system-keyboard-lock</i>"
);
__modalDialog("Keyboard lock is unsupported", msg, true, false, el_window);
__modalDialog("Keyboard lock is unsupported", msg, true, false, el_win);
}
};
var __maximizeWindow = function(el_window) {
var __maximizeWindow = function(el_win) {
let el_navbar = $("navbar");
let vertical_offset = (el_navbar ? el_navbar.offsetHeight : 0);
el_window.style.left = "0px";
el_window.style.top = vertical_offset + "px";
el_window.style.width = window.innerWidth + "px";
el_window.style.height = window.innerHeight - vertical_offset + "px";
el_win.style.left = "0px";
el_win.style.top = vertical_offset + "px";
el_win.style.width = window.innerWidth + "px";
el_win.style.height = window.innerHeight - vertical_offset + "px";
};
var __getFullScreenFunction = function(el_window) {
if (el_window.requestFullscreen) {
return el_window.requestFullscreen;
} else if (el_window.webkitRequestFullscreen) {
return el_window.webkitRequestFullscreen;
} else if (el_window.mozRequestFullscreen) {
return el_window.mozRequestFullscreen;
var __getFullScreenFunction = function(el_win) {
if (el_win.requestFullscreen) {
return el_win.requestFullscreen;
} else if (el_win.webkitRequestFullscreen) {
return el_win.webkitRequestFullscreen;
} else if (el_win.mozRequestFullscreen) {
return el_win.mozRequestFullscreen;
}
return null;
};