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

View File

@ -32,29 +32,43 @@ export function main() {
function __loadKvmdInfo() { function __loadKvmdInfo() {
tools.httpGet("api/info", null, function(http) { tools.httpGet("api/info", null, function(http) {
if (http.status === 200) { switch (http.status) {
let ipmi_port = JSON.parse(http.responseText).result.extras.ipmi.port; case 200:
let make_item = (comment, ipmi, api) => ` __showKvmdInfo(JSON.parse(http.responseText).result);
<span class="code-comment"># ${comment}:<br>$</span> break;
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> case 401:
&nbsp;&nbsp;&nbsp;&nbsp;${window.location.protocol}//${window.location.host}/api/atx${api}<br> case 403:
`; tools.currentOpen("login");
$("ipmi-text").innerHTML = ` break;
${make_item("Power on the server if it's off", "power on", "/power?action=on")}
<br> default:
${make_item("Soft power off the server if it's on", "power soft", "/power?action=off")} setTimeout(__loadKvmdInfo, 1000);
<br> break;
${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) {
tools.currentOpen("login");
} else {
setTimeout(__loadKvmdInfo, 1000);
} }
}); });
} }
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) { if ($("atx-ask-switch").checked) {
wm.confirm(` 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. Warning! This could cause data loss on the server.
`).then(function(ok) { `).then(function(ok) {
if (ok) { if (ok) {

View File

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

View File

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

View File

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

View File

@ -220,7 +220,8 @@ export function MediaStreamer(__setActive, __setInactive, __setInfo, __orient) {
let width = frame.displayWidth; let width = frame.displayWidth;
let height = frame.displayHeight; let height = frame.displayHeight;
switch (__orient) { switch (__orient) {
case 90: case 270: case 90:
case 270:
width = frame.displayHeight; width = frame.displayHeight;
height = frame.displayWidth; 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 __id = "";
var __fps = -1; var __fps = -1;
var __state = null; var __state = null;
@ -91,7 +91,7 @@ export function MjpegStreamer(__setActive, __setInactive, __setInfo) {
var __setStreamInactive = function() { var __setStreamInactive = function() {
let old_fps = __fps; let old_fps = __fps;
__key = tools.makeId(); __key = tools.makeRandomId();
__id = ""; __id = "";
__fps = -1; __fps = -1;
__state = null; __state = null;
@ -139,7 +139,7 @@ export function MjpegStreamer(__setActive, __setInactive, __setInfo) {
__setStreamInactive(); __setStreamInactive();
__stopChecking(); __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) { if (tools.browser.is_safari || tools.browser.is_ios) {
// uStreamer fix for WebKit // uStreamer fix for WebKit
__logInfo("Using dual_final_frames=1 to fix WebKit bugs"); __logInfo("Using dual_final_frames=1 to fix WebKit bugs");

View File

@ -178,8 +178,8 @@ export function Switch() {
}; };
var __clickAddEdidButton = function() { var __clickAddEdidButton = function() {
let create_content = function(el_parent, el_ok_button) { let create_content = function(el_parent, el_ok_bt) {
tools.el.setEnabled(el_ok_button, false); tools.el.setEnabled(el_ok_bt, false);
el_parent.innerHTML = ` el_parent.innerHTML = `
<table> <table>
<tr> <tr>
@ -203,7 +203,7 @@ export function Switch() {
el_name.oninput = el_data.oninput = function() { el_name.oninput = el_data.oninput = function() {
let name = el_name.value.replace(/\s+/g, ""); let name = el_name.value.replace(/\s+/g, "");
let data = el_data.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) { if ($("switch-atx-ask-switch").checked) {
wm.confirm(` 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. Warning! This could cause data loss on the server.
`).then(function(ok) { `).then(function(ok) {
if (ok) { if (ok) {

View File

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

View File

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

View File

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