mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-01-31 18:11:54 +08:00
moved kvmd to the root
This commit is contained in:
63
web/js/atx.js
Normal file
63
web/js/atx.js
Normal file
@@ -0,0 +1,63 @@
|
||||
function Atx() {
|
||||
var self = this;
|
||||
|
||||
/********************************************************************************/
|
||||
|
||||
var __init__ = function() {
|
||||
$("atx-power-led").title = "Power Led";
|
||||
$("atx-hdd-led").title = "Disk Activity Led";
|
||||
|
||||
tools.setOnClick($("atx-power-button"), () => __clickButton("power", null, "Are you sure to click the power button?"));
|
||||
tools.setOnClick($("atx-power-button-long"), () => __clickButton("power_long", 15000, "Are you sure to perform the long press of the power button?"));
|
||||
tools.setOnClick($("atx-reset-button"), () => __clickButton("reset", null, "Are you sure to reboot the server?"));
|
||||
};
|
||||
|
||||
/********************************************************************************/
|
||||
|
||||
self.loadInitialState = function() {
|
||||
var http = tools.makeRequest("GET", "/kvmd/atx", function() {
|
||||
if (http.readyState === 4) {
|
||||
if (http.status === 200) {
|
||||
__setButtonsBusy(JSON.parse(http.responseText).result.busy);
|
||||
} else {
|
||||
setTimeout(self.loadInitialState, 1000);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
self.setState = function(state) {
|
||||
__setButtonsBusy(state.busy);
|
||||
$("atx-power-led").className = (state.leds.power ? "led-on" : "led-off");
|
||||
$("atx-hdd-led").className = (state.leds.hdd ? "led-hdd-busy" : "led-off");
|
||||
};
|
||||
|
||||
self.clearState = function() {
|
||||
$("atx-power-led").className = "led-off";
|
||||
$("atx-hdd-led").className = "led-off";
|
||||
};
|
||||
|
||||
var __clickButton = function(button, timeout, confirm_msg) {
|
||||
ui.confirm(confirm_msg).then(function(ok) {
|
||||
if (ok) {
|
||||
var http = tools.makeRequest("POST", "/kvmd/atx/click?button=" + button, function() {
|
||||
if (http.readyState === 4) {
|
||||
if (http.status === 409) {
|
||||
ui.error("Performing another ATX operation for other client.<br>Please try again later");
|
||||
} else if (http.status !== 200) {
|
||||
ui.error("Click error:<br>", http.responseText);
|
||||
}
|
||||
}
|
||||
}, timeout);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var __setButtonsBusy = function(busy) {
|
||||
$("atx-power-button").disabled = busy;
|
||||
$("atx-power-button-long").disabled = busy;
|
||||
$("atx-reset-button").disabled = busy;
|
||||
};
|
||||
|
||||
__init__();
|
||||
}
|
||||
186
web/js/hid.js
Normal file
186
web/js/hid.js
Normal file
@@ -0,0 +1,186 @@
|
||||
function Hid() {
|
||||
var self = this;
|
||||
|
||||
/********************************************************************************/
|
||||
|
||||
var __ws = null;
|
||||
|
||||
var __chars_to_codes = {};
|
||||
var __codes_delay = 50;
|
||||
|
||||
var __keyboard = new Keyboard();
|
||||
var __mouse = new Mouse();
|
||||
|
||||
var __init__ = function() {
|
||||
var __hidden_attr = null;
|
||||
var __visibility_change_attr = null;
|
||||
|
||||
if (typeof document.hidden !== "undefined") {
|
||||
__hidden_attr = "hidden";
|
||||
__visibility_change_attr = "visibilitychange";
|
||||
} else if (typeof document.webkitHidden !== "undefined") {
|
||||
__hidden_attr = "webkitHidden";
|
||||
__visibility_change_attr = "webkitvisibilitychange";
|
||||
} else if (typeof document.mozHidden !== "undefined") {
|
||||
__hidden_attr = "mozHidden";
|
||||
__visibility_change_attr = "mozvisibilitychange";
|
||||
}
|
||||
|
||||
if (__visibility_change_attr) {
|
||||
document.addEventListener(
|
||||
__visibility_change_attr,
|
||||
function() {
|
||||
if (document[__hidden_attr]) {
|
||||
__releaseAll();
|
||||
}
|
||||
},
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
window.onpagehide = __releaseAll;
|
||||
window.onblur = __releaseAll;
|
||||
|
||||
if (window.navigator.clipboard && window.navigator.clipboard.readText) {
|
||||
__chars_to_codes = __buildCharsToCodes();
|
||||
tools.setOnClick($("pak-button"), __pasteAsKeysFromClipboard);
|
||||
} else {
|
||||
$("pak-button").title = $("pak-led").title = "Your browser does not support the Clipboard API.\nUse Google Chrome or Chromium.";
|
||||
}
|
||||
|
||||
Array.prototype.forEach.call(document.querySelectorAll("[data-shortcut]"), function(el_shortcut) {
|
||||
tools.setOnClick(el_shortcut, () => __emitShortcut(el_shortcut.getAttribute("data-shortcut").split(" ")));
|
||||
});
|
||||
};
|
||||
|
||||
/********************************************************************************/
|
||||
|
||||
self.setSocket = function(ws) {
|
||||
__ws = ws;
|
||||
__keyboard.setSocket(ws);
|
||||
__mouse.setSocket(ws);
|
||||
$("pak-button").disabled = !(window.navigator.clipboard && window.navigator.clipboard.readText && ws);
|
||||
};
|
||||
|
||||
var __releaseAll = function() {
|
||||
__keyboard.releaseAll();
|
||||
};
|
||||
|
||||
var __emitShortcut = function(codes) {
|
||||
return new Promise(function(resolve) {
|
||||
tools.debug("Emitting keys:", codes);
|
||||
|
||||
var raw_events = [];
|
||||
[[codes, true], [codes.slice().reverse(), false]].forEach(function(op) {
|
||||
var [op_codes, state] = op;
|
||||
op_codes.forEach(function(code) {
|
||||
raw_events.push({code: code, state: state});
|
||||
});
|
||||
});
|
||||
|
||||
var index = 0;
|
||||
var iterate = () => setTimeout(function() {
|
||||
__keyboard.fireEvent(raw_events[index].code, raw_events[index].state);
|
||||
++index;
|
||||
if (index < raw_events.length) {
|
||||
iterate();
|
||||
} else {
|
||||
resolve(null);
|
||||
}
|
||||
}, __codes_delay);
|
||||
iterate();
|
||||
});
|
||||
};
|
||||
|
||||
var __buildCharsToCodes = function() {
|
||||
var chars_to_codes = {
|
||||
"\n": ["Enter"],
|
||||
"\t": ["Tab"],
|
||||
" ": ["Space"],
|
||||
"`": ["Backquote"], "~": ["ShiftLeft", "Backquote"],
|
||||
"\\": ["Backslash"], "|": ["ShiftLeft", "Backslash"],
|
||||
"[": ["BracketLeft"], "{": ["ShiftLeft", "BracketLeft"],
|
||||
"]": ["BracketLeft"], "}": ["ShiftLeft", "BracketRight"],
|
||||
",": ["Comma"], "<": ["ShiftLeft", "Comma"],
|
||||
".": ["Period"], ">": ["ShiftLeft", "Period"],
|
||||
"1": ["Digit1"], "!": ["ShiftLeft", "Digit1"],
|
||||
"2": ["Digit2"], "@": ["ShiftLeft", "Digit2"],
|
||||
"3": ["Digit3"], "#": ["ShiftLeft", "Digit3"],
|
||||
"4": ["Digit4"], "$": ["ShiftLeft", "Digit4"],
|
||||
"5": ["Digit5"], "%": ["ShiftLeft", "Digit5"],
|
||||
"6": ["Digit6"], "^": ["ShiftLeft", "Digit6"],
|
||||
"7": ["Digit7"], "&": ["ShiftLeft", "Digit7"],
|
||||
"8": ["Digit8"], "*": ["ShiftLeft", "Digit8"],
|
||||
"9": ["Digit9"], "(": ["ShiftLeft", "Digit9"],
|
||||
"0": ["Digit0"], ")": ["ShiftLeft", "Digit0"],
|
||||
"-": ["Minus"], "_": ["ShiftLeft", "Minus"],
|
||||
"'": ["Quote"], "\"": ["ShiftLeft", "Quote"],
|
||||
";": ["Semicolon"], ":": ["ShiftLeft", "Semicolon"],
|
||||
"/": ["Slash"], "?": ["ShiftLeft", "Slash"],
|
||||
"=": ["Equal"], "+": ["ShiftLeft", "Equal"],
|
||||
};
|
||||
|
||||
for (var ch = "a".charCodeAt(0); ch <= "z".charCodeAt(0); ++ch) {
|
||||
var low = String.fromCharCode(ch);
|
||||
var up = low.toUpperCase();
|
||||
var code = "Key" + up;
|
||||
chars_to_codes[low] = [code];
|
||||
chars_to_codes[up] = ["ShiftLeft", code];
|
||||
}
|
||||
|
||||
return chars_to_codes;
|
||||
};
|
||||
|
||||
var __pasteAsKeysFromClipboard = function() {
|
||||
window.navigator.clipboard.readText().then(__pasteAsKeys);
|
||||
};
|
||||
|
||||
var __pasteAsKeys = function(text) {
|
||||
text = text.replace(/[^\x00-\x7F]/g, ""); // eslint-disable-line no-control-regex
|
||||
if (text) {
|
||||
var clipboard_codes = [];
|
||||
var codes_count = 0;
|
||||
[...text].forEach(function(ch) {
|
||||
var codes = __chars_to_codes[ch];
|
||||
if (codes) {
|
||||
codes_count += codes.length;
|
||||
clipboard_codes.push(codes);
|
||||
}
|
||||
});
|
||||
|
||||
var confirm_msg = (
|
||||
"You are going to automatically type " + codes_count
|
||||
+ " characters from the system clipboard."
|
||||
+ " It will take " + (__codes_delay * codes_count * 2 / 1000) + " seconds.<br>"
|
||||
+ "<br>Are you sure you want to continue?<br>"
|
||||
);
|
||||
|
||||
ui.confirm(confirm_msg).then(function(ok) {
|
||||
if (ok) {
|
||||
$("pak-button").disabled = true;
|
||||
$("pak-led").className = "led-pak-typing";
|
||||
$("pak-led").title = "Autotyping...";
|
||||
|
||||
tools.debug("Paste-as-keys:", text);
|
||||
|
||||
var index = 0;
|
||||
var iterate = function() {
|
||||
__emitShortcut(clipboard_codes[index]).then(function() {
|
||||
++index;
|
||||
if (index < clipboard_codes.length && __ws) {
|
||||
iterate();
|
||||
} else {
|
||||
$("pak-button").disabled = false;
|
||||
$("pak-led").className = "led-off";
|
||||
$("pak-led").title = "";
|
||||
}
|
||||
});
|
||||
};
|
||||
iterate();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
__init__();
|
||||
}
|
||||
187
web/js/keyboard.js
Normal file
187
web/js/keyboard.js
Normal file
@@ -0,0 +1,187 @@
|
||||
function Keyboard() {
|
||||
var self = this;
|
||||
|
||||
/********************************************************************************/
|
||||
|
||||
var __ws = null;
|
||||
|
||||
var __keys = [].slice.call(document.querySelectorAll("div#keyboard-desktop div.keyboard-block div.keyboard-row div.key"));
|
||||
var __modifiers = [].slice.call(document.querySelectorAll("div#keyboard-desktop div.keyboard-block div.keyboard-row div.modifier"));
|
||||
|
||||
var __mac_cmd_hook = ((
|
||||
window.navigator.oscpu
|
||||
|| window.navigator.platform
|
||||
|| window.navigator.appVersion
|
||||
|| "Unknown"
|
||||
).indexOf("Mac") !== -1);
|
||||
|
||||
var __init__ = function() {
|
||||
$("hid-keyboard-led").title = "Keyboard free";
|
||||
|
||||
$("keyboard-window").onkeydown = (event) => __keyboardHandler(event, true);
|
||||
$("keyboard-window").onkeyup = (event) => __keyboardHandler(event, false);
|
||||
$("keyboard-window").onfocus = __updateLeds;
|
||||
$("keyboard-window").onblur = __updateLeds;
|
||||
|
||||
$("stream-window").onkeydown = (event) => __keyboardHandler(event, true);
|
||||
$("stream-window").onkeyup = (event) => __keyboardHandler(event, false);
|
||||
$("stream-window").onfocus = __updateLeds;
|
||||
$("stream-window").onblur = __updateLeds;
|
||||
|
||||
Array.prototype.forEach.call($$("key"), function(el_key) {
|
||||
tools.setOnDown(el_key, () => __clickHandler(el_key, true));
|
||||
tools.setOnUp(el_key, () => __clickHandler(el_key, false));
|
||||
el_key.onmouseout = function() {
|
||||
if (__isPressed(el_key)) {
|
||||
__clickHandler(el_key, false);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
Array.prototype.forEach.call($$("modifier"), function(el_key) {
|
||||
tools.setOnDown(el_key, () => __toggleModifierHandler(el_key));
|
||||
});
|
||||
|
||||
if (__mac_cmd_hook) {
|
||||
tools.info("Keyboard: enabled Mac-CMD-Hook");
|
||||
}
|
||||
};
|
||||
|
||||
/********************************************************************************/
|
||||
|
||||
self.setSocket = function(ws) {
|
||||
if (ws !== __ws) {
|
||||
self.releaseAll();
|
||||
__ws = ws;
|
||||
}
|
||||
__updateLeds();
|
||||
};
|
||||
|
||||
self.releaseAll = function() {
|
||||
__keys.concat(__modifiers).forEach(function(el_key) {
|
||||
if (__isActive(el_key)) {
|
||||
self.fireEvent(el_key.getAttribute("data-key"), false);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
self.fireEvent = function(code, state) {
|
||||
__keyboardHandler({code: code}, state);
|
||||
};
|
||||
|
||||
var __updateLeds = function() {
|
||||
if (__ws && (document.activeElement === $("stream-window") || document.activeElement === $("keyboard-window"))) {
|
||||
$("hid-keyboard-led").className = "led-on";
|
||||
$("hid-keyboard-led").title = "Keyboard captured";
|
||||
} else {
|
||||
$("hid-keyboard-led").className = "led-off";
|
||||
$("hid-keyboard-led").title = "Keyboard free";
|
||||
}
|
||||
};
|
||||
|
||||
var __keyboardHandler = function(event, state) {
|
||||
if (event.preventDefault) {
|
||||
event.preventDefault();
|
||||
}
|
||||
var el_key = document.querySelector("[data-key='" + event.code + "']");
|
||||
if (el_key && !event.repeat) {
|
||||
__commonHandler(el_key, state, "pressed");
|
||||
if (__mac_cmd_hook) {
|
||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=28089
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1299553
|
||||
if ((event.code === "MetaLeft" || event.code === "MetaRight") && !state) {
|
||||
__keys.forEach(function(el_key) {
|
||||
if (__isActive(el_key)) {
|
||||
self.fireEvent(el_key.getAttribute("data-key"), false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
__unholdModifiers();
|
||||
}
|
||||
};
|
||||
|
||||
var __clickHandler = function(el_key, state) {
|
||||
__commonHandler(el_key, state, "pressed");
|
||||
__unholdModifiers();
|
||||
};
|
||||
|
||||
var __toggleModifierHandler = function(el_key) {
|
||||
__commonHandler(el_key, !__isActive(el_key), "holded");
|
||||
};
|
||||
|
||||
var __unholdModifiers = function() {
|
||||
__modifiers.forEach(function(el_key) {
|
||||
if (__isHolded(el_key)) {
|
||||
__deactivate(el_key);
|
||||
__sendKey(el_key, false);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var __commonHandler = function(el_key, state, cls) {
|
||||
if (state && !__isActive(el_key)) {
|
||||
__deactivate(el_key);
|
||||
__activate(el_key, cls);
|
||||
__sendKey(el_key, true);
|
||||
} else {
|
||||
__deactivate(el_key);
|
||||
__sendKey(el_key, false);
|
||||
}
|
||||
};
|
||||
|
||||
var __isPressed = function(el_key) {
|
||||
var is_pressed = false;
|
||||
Array.prototype.forEach.call(__resolveKeys(el_key), function(el_key) {
|
||||
is_pressed = (is_pressed || el_key.classList.contains("pressed"));
|
||||
});
|
||||
return is_pressed;
|
||||
};
|
||||
|
||||
var __isHolded = function(el_key) {
|
||||
var is_holded = false;
|
||||
Array.prototype.forEach.call(__resolveKeys(el_key), function(el_key) {
|
||||
is_holded = (is_holded || el_key.classList.contains("holded"));
|
||||
});
|
||||
return is_holded;
|
||||
};
|
||||
|
||||
var __isActive = function(el_key) {
|
||||
var is_active = false;
|
||||
Array.prototype.forEach.call(__resolveKeys(el_key), function(el_key) {
|
||||
is_active = (is_active || el_key.classList.contains("pressed") || el_key.classList.contains("holded"));
|
||||
});
|
||||
return is_active;
|
||||
};
|
||||
|
||||
var __activate = function(el_key, cls) {
|
||||
Array.prototype.forEach.call(__resolveKeys(el_key), function(el_key) {
|
||||
el_key.classList.add(cls);
|
||||
});
|
||||
};
|
||||
|
||||
var __deactivate = function(el_key) {
|
||||
Array.prototype.forEach.call(__resolveKeys(el_key), function(el_key) {
|
||||
el_key.classList.remove("pressed");
|
||||
el_key.classList.remove("holded");
|
||||
});
|
||||
};
|
||||
|
||||
var __resolveKeys = function(el_key) {
|
||||
return document.querySelectorAll("[data-key='" + el_key.getAttribute("data-key") + "']");
|
||||
};
|
||||
|
||||
var __sendKey = function(el_key, state) {
|
||||
var code = el_key.getAttribute("data-key");
|
||||
tools.debug("Key", (state ? "pressed:" : "released:"), code);
|
||||
if (__ws) {
|
||||
__ws.send(JSON.stringify({
|
||||
event_type: "key",
|
||||
key: code,
|
||||
state: state,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
__init__();
|
||||
}
|
||||
16
web/js/main.js
Normal file
16
web/js/main.js
Normal file
@@ -0,0 +1,16 @@
|
||||
var ui;
|
||||
|
||||
function main() {
|
||||
if (
|
||||
!window.navigator
|
||||
|| window.navigator.userAgent.indexOf("MSIE ") > 0
|
||||
|| window.navigator.userAgent.indexOf("Trident/") > 0
|
||||
|| window.navigator.userAgent.indexOf("Edge/") > 0
|
||||
) {
|
||||
$("bad-browser-modal").style.visibility = "visible";
|
||||
} else {
|
||||
ui = new Ui();
|
||||
new Session(new Atx(), new Hid(), new Msd());
|
||||
new Stream();
|
||||
}
|
||||
}
|
||||
146
web/js/mouse.js
Normal file
146
web/js/mouse.js
Normal file
@@ -0,0 +1,146 @@
|
||||
function Mouse() {
|
||||
var self = this;
|
||||
|
||||
/********************************************************************************/
|
||||
|
||||
var __ws = null;
|
||||
|
||||
var __current_pos = {x: 0, y:0};
|
||||
var __sent_pos = {x: 0, y:0};
|
||||
|
||||
var __stream_hovered = false;
|
||||
|
||||
var __init__ = function() {
|
||||
$("hid-mouse-led").title = "Mouse free";
|
||||
|
||||
$("stream-box").onmouseenter = __hoverStream;
|
||||
$("stream-box").onmouseleave = __leaveStream;
|
||||
$("stream-box").onmousedown = (event) => __buttonHandler(event, true);
|
||||
$("stream-box").onmouseup = (event) => __buttonHandler(event, false);
|
||||
$("stream-box").oncontextmenu = (event) => event.preventDefault();
|
||||
$("stream-box").onmousemove = __moveHandler;
|
||||
$("stream-box").onwheel = __wheelHandler;
|
||||
|
||||
$("stream-box").ontouchstart = (event) => __touchMoveHandler(event);
|
||||
Array.prototype.forEach.call(document.querySelectorAll("[data-mouse-button]"), function(el_button) {
|
||||
var button = el_button.getAttribute("data-mouse-button");
|
||||
tools.setOnDown(el_button, () => __sendButton(button, true));
|
||||
tools.setOnUp(el_button, () => __sendButton(button, false));
|
||||
});
|
||||
|
||||
setInterval(__sendMove, 100);
|
||||
};
|
||||
|
||||
/********************************************************************************/
|
||||
|
||||
self.setSocket = function(ws) {
|
||||
__ws = ws;
|
||||
if (ws) {
|
||||
$("stream-box").classList.add("stream-box-mouse-enabled");
|
||||
} else {
|
||||
$("stream-box").classList.remove("stream-box-mouse-enabled");
|
||||
}
|
||||
};
|
||||
|
||||
var __hoverStream = function() {
|
||||
__stream_hovered = true;
|
||||
__updateLeds();
|
||||
};
|
||||
|
||||
var __leaveStream = function() {
|
||||
__stream_hovered = false;
|
||||
__updateLeds();
|
||||
};
|
||||
|
||||
var __updateLeds = function() {
|
||||
if (__ws && __stream_hovered) {
|
||||
$("hid-mouse-led").className = "led-on";
|
||||
$("hid-mouse-led").title = "Mouse tracked";
|
||||
} else {
|
||||
$("hid-mouse-led").className = "led-off";
|
||||
$("hid-mouse-led").title = "Mouse free";
|
||||
}
|
||||
};
|
||||
|
||||
var __buttonHandler = function(event, state) {
|
||||
// https://www.w3schools.com/jsref/event_button.asp
|
||||
event.preventDefault();
|
||||
switch (event.button) {
|
||||
case 0: __sendButton("left", state); break;
|
||||
case 2: __sendButton("right", state); break;
|
||||
}
|
||||
};
|
||||
|
||||
var __touchMoveHandler = function(event) {
|
||||
event.preventDefault();
|
||||
if (event.touches[0].target && event.touches[0].target.getBoundingClientRect) {
|
||||
var rect = event.touches[0].target.getBoundingClientRect();
|
||||
__current_pos = {
|
||||
x: Math.round(event.touches[0].clientX - rect.left),
|
||||
y: Math.round(event.touches[0].clientY - rect.top),
|
||||
};
|
||||
__sendMove();
|
||||
}
|
||||
};
|
||||
|
||||
var __moveHandler = function(event) {
|
||||
var rect = event.target.getBoundingClientRect();
|
||||
__current_pos = {
|
||||
x: Math.round(event.clientX - rect.left),
|
||||
y: Math.round(event.clientY - rect.top),
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
var __sendButton = function(button, state) {
|
||||
tools.debug("Mouse button", (state ? "pressed:" : "released:"), button);
|
||||
__sendMove();
|
||||
if (__ws) {
|
||||
__ws.send(JSON.stringify({
|
||||
event_type: "mouse_button",
|
||||
button: button,
|
||||
state: state,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
var __sendMove = function() {
|
||||
var pos = __current_pos;
|
||||
if (pos.x !== __sent_pos.x || pos.y !== __sent_pos.y) {
|
||||
var el_stream_image = $("stream-image");
|
||||
var to = {
|
||||
x: __translate(pos.x, 0, el_stream_image.clientWidth, -32768, 32767),
|
||||
y: __translate(pos.y, 0, el_stream_image.clientHeight, -32768, 32767),
|
||||
};
|
||||
tools.debug("Mouse move:", to);
|
||||
if (__ws) {
|
||||
__ws.send(JSON.stringify({
|
||||
event_type: "mouse_move",
|
||||
to: to,
|
||||
}));
|
||||
}
|
||||
__sent_pos = pos;
|
||||
}
|
||||
};
|
||||
|
||||
var __translate = function(x, a, b, c, d) {
|
||||
return Math.round((x - a) / (b - a) * (d - c) + c);
|
||||
};
|
||||
|
||||
var __wheelHandler = function(event) {
|
||||
// https://learn.javascript.ru/mousewheel
|
||||
if (event.preventDefault) {
|
||||
event.preventDefault();
|
||||
}
|
||||
var delta = {x: event.deltaX, y: event.deltaY};
|
||||
tools.debug("Mouse wheel:", delta);
|
||||
if (__ws) {
|
||||
__ws.send(JSON.stringify({
|
||||
event_type: "mouse_wheel",
|
||||
delta: delta,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
__init__();
|
||||
}
|
||||
164
web/js/msd.js
Normal file
164
web/js/msd.js
Normal file
@@ -0,0 +1,164 @@
|
||||
function Msd() {
|
||||
var self = this;
|
||||
|
||||
/********************************************************************************/
|
||||
|
||||
var __state = null;
|
||||
var __upload_http = null;
|
||||
var __image_file = null;
|
||||
|
||||
var __init__ = function() {
|
||||
$("msd-led").title = "Unknown state";
|
||||
|
||||
$("msd-select-new-image-file").onchange = __selectNewImageFile;
|
||||
tools.setOnClick($("msd-select-new-image-button"), () => $("msd-select-new-image-file").click());
|
||||
|
||||
tools.setOnClick($("msd-upload-new-image-button"), __clickUploadNewImageButton);
|
||||
tools.setOnClick($("msd-abort-uploading-button"), __clickAbortUploadingButton);
|
||||
|
||||
tools.setOnClick($("msd-switch-to-kvm-button"), () => __clickSwitchButton("kvm"));
|
||||
tools.setOnClick($("msd-switch-to-server-button"), () => __clickSwitchButton("server"));
|
||||
};
|
||||
|
||||
/********************************************************************************/
|
||||
|
||||
self.loadInitialState = function() {
|
||||
var http = tools.makeRequest("GET", "/kvmd/msd", function() {
|
||||
if (http.readyState === 4) {
|
||||
if (http.status === 200) {
|
||||
self.setState(JSON.parse(http.responseText).result);
|
||||
} else {
|
||||
setTimeout(self.loadInitialState, 1000);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
self.setState = function(state) {
|
||||
__state = state;
|
||||
__applyState();
|
||||
};
|
||||
|
||||
var __clickUploadNewImageButton = function() {
|
||||
var form_data = new FormData();
|
||||
form_data.append("image_name", __image_file.name);
|
||||
form_data.append("image_data", __image_file);
|
||||
|
||||
__upload_http = new XMLHttpRequest();
|
||||
__upload_http.open("POST", "/kvmd/msd/write", true);
|
||||
__upload_http.upload.timeout = 5000;
|
||||
__upload_http.onreadystatechange = __uploadStateChange;
|
||||
__upload_http.upload.onprogress = __uploadProgress;
|
||||
__upload_http.send(form_data);
|
||||
};
|
||||
|
||||
var __clickAbortUploadingButton = function() {
|
||||
__upload_http.onreadystatechange = null;
|
||||
__upload_http.upload.onprogress = null;
|
||||
__upload_http.abort();
|
||||
__upload_http = null;
|
||||
$("msd-progress").setAttribute("data-label", "Aborted");
|
||||
$("msd-progress-value").style.width = "0%";
|
||||
};
|
||||
|
||||
var __clickSwitchButton = function(to) {
|
||||
var http = tools.makeRequest("POST", "/kvmd/msd/connect?to=" + to, function() {
|
||||
if (http.readyState === 4) {
|
||||
if (http.status !== 200) {
|
||||
ui.error("Switch error:<br>", http.responseText);
|
||||
}
|
||||
}
|
||||
__applyState();
|
||||
});
|
||||
__applyState();
|
||||
$("msd-switch-to-" + to + "-button").disabled = true;
|
||||
};
|
||||
|
||||
var __selectNewImageFile = function() {
|
||||
var el_input = $("msd-select-new-image-file");
|
||||
var image_file = (el_input.files.length ? el_input.files[0] : null);
|
||||
if (image_file && image_file.size > __state.info.size) {
|
||||
ui.error("New image is too big for your Mass Storage Device.<br>Maximum:", __formatSize(__state.info.size));
|
||||
el_input.value = "";
|
||||
image_file = null;
|
||||
}
|
||||
__image_file = image_file;
|
||||
__applyState();
|
||||
};
|
||||
|
||||
var __applyState = function() {
|
||||
if (__state.connected_to === "server") {
|
||||
$("msd-another-another-user-uploads").style.display = "none";
|
||||
$("msd-led").className = "led-on";
|
||||
$("msd-status").innerHTML = $("msd-led").title = "Connected to Server";
|
||||
$("msd-another-another-user-uploads").style.display = "none";
|
||||
} else if (__state.busy) {
|
||||
if (!__upload_http) {
|
||||
$("msd-another-another-user-uploads").style.display = "block";
|
||||
}
|
||||
$("msd-led").className = "led-msd-writing";
|
||||
$("msd-status").innerHTML = $("msd-led").title = "Uploading new image";
|
||||
} else {
|
||||
$("msd-another-another-user-uploads").style.display = "none";
|
||||
$("msd-led").className = "led-off";
|
||||
if (__state.in_operate) {
|
||||
$("msd-status").innerHTML = $("msd-led").title = "Connected to KVM";
|
||||
} else {
|
||||
$("msd-status").innerHTML = $("msd-led").title = "Unavailable";
|
||||
}
|
||||
}
|
||||
|
||||
$("msd-not-in-operate").style.display = (__state.in_operate ? "none" : "block");
|
||||
$("msd-current-image-broken").style.display = (
|
||||
__state.in_operate && __state.info.image &&
|
||||
!__state.info.image.complete && !__state.busy ? "block" : "none"
|
||||
);
|
||||
|
||||
$("msd-current-image-name").innerHTML = (__state.in_operate && __state.info.image ? __state.info.image.name : "None");
|
||||
$("msd-current-image-size").innerHTML = (__state.in_operate && __state.info.image ? __formatSize(__state.info.image.size) : "None");
|
||||
$("msd-storage-size").innerHTML = (__state.in_operate ? __formatSize(__state.info.size) : "Unavailable");
|
||||
|
||||
$("msd-switch-to-kvm-button").disabled = (!__state.in_operate || __state.connected_to === "kvm" || __state.busy);
|
||||
$("msd-switch-to-server-button").disabled = (!__state.in_operate || __state.connected_to === "server" || __state.busy);
|
||||
$("msd-select-new-image-button").disabled = (!__state.in_operate || __state.connected_to !== "kvm" || __state.busy || __upload_http);
|
||||
$("msd-upload-new-image-button").disabled = (!__state.in_operate || __state.connected_to !== "kvm" || __state.busy || !__image_file);
|
||||
$("msd-abort-uploading-button").disabled = (!__state.in_operate || !__upload_http);
|
||||
|
||||
$("msd-new-image").style.display = (__image_file ? "block" : "none");
|
||||
$("msd-progress").setAttribute("data-label", "Waiting for upload ...");
|
||||
$("msd-progress-value").style.width = "0%";
|
||||
$("msd-new-image-name").innerHTML = (__image_file ? __image_file.name : "");
|
||||
$("msd-new-image-size").innerHTML = (__image_file ? __formatSize(__image_file.size) : "");
|
||||
};
|
||||
|
||||
var __formatSize = function(size) {
|
||||
if (size > 0) {
|
||||
var index = Math.floor( Math.log(size) / Math.log(1024) );
|
||||
return (size / Math.pow(1024, index)).toFixed(2) * 1 + " " + ["B", "kB", "MB", "GB", "TB"][index];
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
var __uploadStateChange = function() {
|
||||
if (__upload_http.readyState === 4) {
|
||||
if (__upload_http.status !== 200) {
|
||||
ui.error("Can't upload image to the Mass Storage Device:<br>", __upload_http.responseText);
|
||||
}
|
||||
$("msd-select-new-image-file").value = "";
|
||||
__image_file = null;
|
||||
__upload_http = null;
|
||||
__applyState();
|
||||
}
|
||||
};
|
||||
|
||||
var __uploadProgress = function(event) {
|
||||
if(event.lengthComputable) {
|
||||
var percent = Math.round((event.loaded * 100) / event.total);
|
||||
$("msd-progress").setAttribute("data-label", percent + "%");
|
||||
$("msd-progress-value").style.width = percent + "%";
|
||||
}
|
||||
};
|
||||
|
||||
__init__();
|
||||
}
|
||||
121
web/js/session.js
Normal file
121
web/js/session.js
Normal file
@@ -0,0 +1,121 @@
|
||||
function Session(atx, hid, msd) {
|
||||
// var self = this;
|
||||
|
||||
/********************************************************************************/
|
||||
|
||||
var __ws = null;
|
||||
|
||||
var __ping_timer = null;
|
||||
var __missed_heartbeats = 0;
|
||||
|
||||
var __init__ = function() {
|
||||
$("link-led").title = "Not connected yet...";
|
||||
__loadKvmdVersion();
|
||||
__startPoller();
|
||||
};
|
||||
|
||||
/********************************************************************************/
|
||||
|
||||
var __loadKvmdVersion = function() {
|
||||
var http = tools.makeRequest("GET", "/kvmd/info", function() {
|
||||
if (http.readyState === 4) {
|
||||
if (http.status === 200) {
|
||||
var version = JSON.parse(http.responseText).result.version;
|
||||
$("kvmd-version").innerHTML = "kvmd " + version.kvmd;
|
||||
$("about-version-kvmd").innerHTML = version.kvmd;
|
||||
$("about-version-python").innerHTML = version.python;
|
||||
$("about-version-platform").innerHTML = version.platform;
|
||||
} else {
|
||||
setTimeout(__loadKvmdVersion, 1000);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var __startPoller = function() {
|
||||
$("link-led").className = "led-link-connecting";
|
||||
$("link-led").title = "Connecting...";
|
||||
var http = tools.makeRequest("GET", "/wsauth", function() {
|
||||
if (http.readyState === 4) {
|
||||
if (http.status === 200) {
|
||||
__ws = new WebSocket((location.protocol == "https:" ? "wss" : "ws") + "://" + location.host + "/kvmd/ws");
|
||||
__ws.onopen = __wsOpenHandler;
|
||||
__ws.onmessage = __wsMessageHandler;
|
||||
__ws.onerror = __wsErrorHandler;
|
||||
__ws.onclose = __wsCloseHandler;
|
||||
} else {
|
||||
__wsCloseHandler(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var __wsOpenHandler = function(event) {
|
||||
$("link-led").className = "led-on";
|
||||
$("link-led").title = "Connected";
|
||||
tools.debug("WebSocket opened:", event);
|
||||
atx.loadInitialState();
|
||||
msd.loadInitialState();
|
||||
hid.setSocket(__ws);
|
||||
__missed_heartbeats = 0;
|
||||
__ping_timer = setInterval(__pingServer, 1000);
|
||||
};
|
||||
|
||||
var __wsMessageHandler = function(event) {
|
||||
// tools.debug("WebSocket: received data:", event.data);
|
||||
event = JSON.parse(event.data);
|
||||
if (event.msg_type === "pong") {
|
||||
__missed_heartbeats = 0;
|
||||
} else if (event.msg_type === "event") {
|
||||
if (event.msg.event === "atx_state") {
|
||||
atx.setState(event.msg.event_attrs);
|
||||
} else if (event.msg.event === "msd_state") {
|
||||
msd.setState(event.msg.event_attrs);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var __wsErrorHandler = function(event) {
|
||||
tools.error("WebSocket error:", event);
|
||||
if (__ws) {
|
||||
__ws.onclose = null;
|
||||
__ws.close();
|
||||
__wsCloseHandler(null);
|
||||
}
|
||||
};
|
||||
|
||||
var __wsCloseHandler = function(event) {
|
||||
$("link-led").className = "led-off";
|
||||
tools.debug("WebSocket closed:", event);
|
||||
if (__ping_timer) {
|
||||
clearInterval(__ping_timer);
|
||||
__ping_timer = null;
|
||||
}
|
||||
hid.setSocket(null);
|
||||
atx.clearState();
|
||||
__ws = null;
|
||||
setTimeout(function() {
|
||||
$("link-led").className = "led-link-connecting";
|
||||
setTimeout(__startPoller, 500);
|
||||
}, 500);
|
||||
};
|
||||
|
||||
var __pingServer = function() {
|
||||
try {
|
||||
__missed_heartbeats += 1;
|
||||
if (__missed_heartbeats >= 5) {
|
||||
throw new Error("Too many missed heartbeats");
|
||||
}
|
||||
__ws.send(JSON.stringify({"event_type": "ping"}));
|
||||
} catch (err) {
|
||||
tools.error("Ping error:", err.message);
|
||||
if (__ws) {
|
||||
__ws.onclose = null;
|
||||
__ws.close();
|
||||
__wsCloseHandler(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
__init__();
|
||||
}
|
||||
119
web/js/stream.js
Normal file
119
web/js/stream.js
Normal file
@@ -0,0 +1,119 @@
|
||||
function Stream() {
|
||||
// var self = this;
|
||||
|
||||
/********************************************************************************/
|
||||
|
||||
var __prev_state = false;
|
||||
|
||||
var __quality = 80;
|
||||
|
||||
var __normal_size = {width: 640, height: 480};
|
||||
var __size_factor = 1;
|
||||
|
||||
var __init__ = function() {
|
||||
$("stream-led").title = "Stream inactive";
|
||||
|
||||
var quality = 10;
|
||||
for (; quality <= 100; quality += 10) {
|
||||
$("stream-quality-select").innerHTML += "<option value=\"" + quality + "\">" + quality + "%</option>";
|
||||
}
|
||||
|
||||
tools.setOnClick($("stream-reset-button"), __clickResetButton);
|
||||
$("stream-quality-select").onchange = __changeQuality;
|
||||
$("stream-size-slider").oninput = __resize;
|
||||
$("stream-size-slider").onchange = __resize;
|
||||
|
||||
__startPoller();
|
||||
};
|
||||
|
||||
/********************************************************************************/
|
||||
|
||||
// XXX: In current implementation we don't need this event because Stream() has own state poller
|
||||
|
||||
var __startPoller = function() {
|
||||
var http = tools.makeRequest("GET", "/streamer/ping", function() {
|
||||
if (http.readyState === 4) {
|
||||
var response = (http.status === 200 ? JSON.parse(http.responseText) : null);
|
||||
if (http.status !== 200 || !response.stream.online) {
|
||||
tools.info("Refreshing stream ...");
|
||||
__prev_state = false;
|
||||
$("stream-image").className = "stream-image-inactive";
|
||||
$("stream-box").classList.add("stream-box-inactive");
|
||||
$("stream-led").className = "led-off";
|
||||
$("stream-led").title = "Stream inactive";
|
||||
$("stream-reset-button").disabled = true;
|
||||
$("stream-quality-select").disabled = true;
|
||||
} else if (http.status === 200 && !__prev_state) {
|
||||
__normal_size = response.stream.resolution;
|
||||
__refreshImage();
|
||||
__prev_state = true;
|
||||
$("stream-image").className = "stream-image-active";
|
||||
$("stream-box").classList.remove("stream-box-inactive");
|
||||
$("stream-led").className = "led-on";
|
||||
$("stream-led").title = "Stream is active";
|
||||
$("stream-reset-button").disabled = false;
|
||||
$("stream-quality-select").disabled = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
setTimeout(__startPoller, 1500);
|
||||
};
|
||||
|
||||
var __clickResetButton = function() {
|
||||
$("stream-reset-button").disabled = true;
|
||||
var http = tools.makeRequest("POST", "/kvmd/streamer/reset", function() {
|
||||
if (http.readyState === 4) {
|
||||
if (http.status !== 200) {
|
||||
ui.error("Can't reset stream:<br>", http.responseText);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var __changeQuality = function() {
|
||||
var quality = parseInt($("stream-quality-select").value);
|
||||
if (__quality != quality) {
|
||||
$("stream-quality-select").disabled = true;
|
||||
var http = tools.makeRequest("POST", "/kvmd/streamer/set_params?quality=" + quality, function() {
|
||||
if (http.readyState === 4) {
|
||||
if (http.status !== 200) {
|
||||
ui.error("Can't configure stream:<br>", http.responseText);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var __resize = function() {
|
||||
var percent = $("stream-size-slider").value;
|
||||
$("stream-size-value").innerHTML = percent + "%";
|
||||
__size_factor = percent / 100;
|
||||
__applySizeFactor();
|
||||
};
|
||||
|
||||
var __applySizeFactor = function() {
|
||||
var el_stream_image = $("stream-image");
|
||||
el_stream_image.style.width = __normal_size.width * __size_factor + "px";
|
||||
el_stream_image.style.height = __normal_size.height * __size_factor + "px";
|
||||
ui.showWindow($("stream-window"), false);
|
||||
};
|
||||
|
||||
var __refreshImage = function() {
|
||||
var http = tools.makeRequest("GET", "/kvmd/streamer", function() {
|
||||
if (http.readyState === 4 && http.status === 200) {
|
||||
var result = JSON.parse(http.responseText).result;
|
||||
|
||||
if (__quality != result.quality) {
|
||||
tools.info("Quality changed:", result.quality);
|
||||
document.querySelector("#stream-quality-select [value=\"" + result.quality + "\"]").selected = true;
|
||||
__quality = result.quality;
|
||||
}
|
||||
|
||||
__applySizeFactor();
|
||||
$("stream-image").src = "/streamer/stream?t=" + new Date().getTime();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
__init__();
|
||||
}
|
||||
43
web/js/tools.js
Normal file
43
web/js/tools.js
Normal file
@@ -0,0 +1,43 @@
|
||||
var tools = new function() {
|
||||
var __debug = (new URL(window.location.href)).searchParams.get("debug");
|
||||
|
||||
this.makeRequest = function(method, url, callback, timeout=null) {
|
||||
var http = new XMLHttpRequest();
|
||||
http.open(method, url, true);
|
||||
http.onreadystatechange = callback;
|
||||
http.timeout = (timeout ? timeout : 5000);
|
||||
http.send();
|
||||
return http;
|
||||
};
|
||||
|
||||
this.setOnClick = function(el, callback) {
|
||||
el.onclick = el.ontouchend = function(event) {
|
||||
event.preventDefault();
|
||||
callback();
|
||||
};
|
||||
};
|
||||
this.setOnDown = function(el, callback) {
|
||||
el.onmousedown = el.ontouchstart = function(event) {
|
||||
event.preventDefault();
|
||||
callback();
|
||||
};
|
||||
};
|
||||
this.setOnUp = function(el, callback) {
|
||||
el.onmouseup = el.ontouchend = function(event) {
|
||||
event.preventDefault();
|
||||
callback();
|
||||
};
|
||||
};
|
||||
|
||||
this.debug = function(...args) {
|
||||
if (__debug) {
|
||||
console.log("LOG/DEBUG", ...args); // eslint-disable-line no-console
|
||||
}
|
||||
};
|
||||
|
||||
this.info = (...args) => console.log("LOG/INFO", ...args); // eslint-disable-line no-console
|
||||
this.error = (...args) => console.error("LOG/ERROR", ...args); // eslint-disable-line no-console
|
||||
};
|
||||
|
||||
var $ = (id) => document.getElementById(id);
|
||||
var $$ = (cls) => document.getElementsByClassName(cls);
|
||||
319
web/js/ui.js
Normal file
319
web/js/ui.js
Normal file
@@ -0,0 +1,319 @@
|
||||
function Ui() {
|
||||
var self = this;
|
||||
|
||||
/********************************************************************************/
|
||||
|
||||
var __top_z_index = 0;
|
||||
var __windows = [];
|
||||
var __ctl_items = [];
|
||||
|
||||
var __init__ = function() {
|
||||
Array.prototype.forEach.call(document.querySelectorAll("button"), function(el_button) {
|
||||
// XXX: Workaround for iOS Safari:
|
||||
// https://stackoverflow.com/questions/3885018/active-pseudo-class-doesnt-work-in-mobile-safari
|
||||
el_button.ontouchstart = function() {};
|
||||
});
|
||||
|
||||
Array.prototype.forEach.call($$("ctl-item"), function(el_item) {
|
||||
tools.setOnClick(el_item, () => __toggleMenu(el_item));
|
||||
__ctl_items.push(el_item);
|
||||
});
|
||||
|
||||
Array.prototype.forEach.call($$("window"), function(el_window) {
|
||||
__makeWindowMovable(el_window);
|
||||
__windows.push(el_window);
|
||||
|
||||
var el_button = el_window.querySelector(".window-header .window-button-close");
|
||||
if (el_button) {
|
||||
tools.setOnClick(el_button, function() {
|
||||
el_window.style.visibility = "hidden";
|
||||
__raiseLastWindow();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
window.onmouseup = __globalMouseButtonHandler;
|
||||
window.ontouchend = __globalMouseButtonHandler;
|
||||
|
||||
window.addEventListener("resize", () => __organizeWindowsOnResize(false));
|
||||
window.addEventListener("orientationchange", () => __organizeWindowsOnResize(true));
|
||||
|
||||
tools.setOnClick($("show-about-button"), () => self.showWindow($("about-window")));
|
||||
tools.setOnClick($("show-keyboard-button"), () => self.showWindow($("keyboard-window")));
|
||||
tools.setOnClick($("show-stream-button"), () => self.showWindow($("stream-window")));
|
||||
|
||||
self.showWindow($("stream-window"));
|
||||
};
|
||||
|
||||
/********************************************************************************/
|
||||
|
||||
self.error = (...args) => __modalDialog("Error", args.join(" "), true, false);
|
||||
self.confirm = (...args) => __modalDialog("Question", args.join(" "), true, true);
|
||||
|
||||
var __modalDialog = function(header, text, ok, cancel) {
|
||||
var el_modal = document.createElement("div");
|
||||
el_modal.className = "modal";
|
||||
el_modal.style.visibility = "visible";
|
||||
|
||||
var el_window = document.createElement("div");
|
||||
el_window.className = "modal-window";
|
||||
el_window.setAttribute("tabindex", "-1");
|
||||
el_modal.appendChild(el_window);
|
||||
|
||||
var el_header = document.createElement("div");
|
||||
el_header.className = "modal-header";
|
||||
el_header.innerHTML = header;
|
||||
el_window.appendChild(el_header);
|
||||
|
||||
var el_content = document.createElement("div");
|
||||
el_content.className = "modal-content";
|
||||
el_content.innerHTML = text;
|
||||
el_window.appendChild(el_content);
|
||||
|
||||
var promise = null;
|
||||
if (ok || cancel) {
|
||||
promise = new Promise(function(resolve) {
|
||||
var el_buttons = document.createElement("div");
|
||||
el_buttons.className = "modal-buttons";
|
||||
el_window.appendChild(el_buttons);
|
||||
|
||||
function close (retval) {
|
||||
el_modal.outerHTML = "";
|
||||
var index = __windows.indexOf(el_modal);
|
||||
if (index !== -1) {
|
||||
__windows.splice(index, 1);
|
||||
}
|
||||
__raiseLastWindow();
|
||||
resolve(retval);
|
||||
}
|
||||
|
||||
if (cancel) {
|
||||
var el_cancel_button = document.createElement("button");
|
||||
el_cancel_button.innerHTML = "Cancel";
|
||||
tools.setOnClick(el_cancel_button, () => close(false));
|
||||
el_buttons.appendChild(el_cancel_button);
|
||||
}
|
||||
if (ok) {
|
||||
var el_ok_button = document.createElement("button");
|
||||
el_ok_button.innerHTML = "OK";
|
||||
tools.setOnClick(el_ok_button, () => close(true));
|
||||
el_buttons.appendChild(el_ok_button);
|
||||
}
|
||||
if (ok && cancel) {
|
||||
el_ok_button.className = "row50";
|
||||
el_cancel_button.className = "row50";
|
||||
}
|
||||
|
||||
el_window.onkeyup = function(event) {
|
||||
event.preventDefault();
|
||||
if (ok && event.code === "Enter") {
|
||||
el_ok_button.click();
|
||||
} else if (cancel && event.code === "Escape") {
|
||||
el_cancel_button.click();
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
__windows.push(el_modal);
|
||||
document.body.appendChild(el_modal);
|
||||
__raiseWindow(el_modal);
|
||||
|
||||
return promise;
|
||||
};
|
||||
|
||||
self.showWindow = function(el_window, raise=true) {
|
||||
if (!__isWindowOnPage(el_window) || el_window.hasAttribute("data-centered")) {
|
||||
var view = __getViewGeometry();
|
||||
var rect = el_window.getBoundingClientRect();
|
||||
el_window.style.top = Math.max($("ctl").clientHeight, 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", "");
|
||||
}
|
||||
el_window.style.visibility = "visible";
|
||||
if (raise) {
|
||||
__raiseWindow(el_window);
|
||||
}
|
||||
};
|
||||
|
||||
var __isWindowOnPage = function(el_window) {
|
||||
var view = __getViewGeometry();
|
||||
var rect = el_window.getBoundingClientRect();
|
||||
|
||||
return (
|
||||
(rect.bottom - el_window.clientHeight / 1.5) <= view.bottom
|
||||
&& rect.top >= view.top
|
||||
&& (rect.left + el_window.clientWidth / 1.5) >= view.left
|
||||
&& (rect.right - el_window.clientWidth / 1.5) <= view.right
|
||||
);
|
||||
};
|
||||
|
||||
var __getViewGeometry = function() {
|
||||
return {
|
||||
top: $("ctl").clientHeight,
|
||||
bottom: Math.max(document.documentElement.clientHeight, window.innerHeight || 0),
|
||||
left: 0,
|
||||
right: Math.max(document.documentElement.clientWidth, window.innerWidth || 0),
|
||||
};
|
||||
};
|
||||
|
||||
var __toggleMenu = function(el_a) {
|
||||
var all_hidden = true;
|
||||
|
||||
__ctl_items.forEach(function(el_item) {
|
||||
var el_menu = el_item.parentElement.querySelector(".ctl-dropdown-content");
|
||||
if (el_item === el_a && window.getComputedStyle(el_menu, null).visibility === "hidden") {
|
||||
el_item.focus();
|
||||
el_item.classList.add("ctl-item-selected");
|
||||
el_menu.style.visibility = "visible";
|
||||
all_hidden &= false;
|
||||
} else {
|
||||
el_item.classList.remove("ctl-item-selected");
|
||||
el_menu.style.visibility = "hidden";
|
||||
}
|
||||
});
|
||||
|
||||
if (all_hidden) {
|
||||
document.onkeyup = null;
|
||||
__raiseLastWindow();
|
||||
} else {
|
||||
document.onkeyup = function(event) {
|
||||
if (event.code === "Escape") {
|
||||
event.preventDefault();
|
||||
__closeAllMenues();
|
||||
__raiseLastWindow();
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
var __closeAllMenues = function() {
|
||||
document.onkeyup = null;
|
||||
__ctl_items.forEach(function(el_item) {
|
||||
var el_menu = el_item.parentElement.querySelector(".ctl-dropdown-content");
|
||||
el_item.classList.remove("ctl-item-selected");
|
||||
el_menu.style.visibility = "hidden";
|
||||
});
|
||||
};
|
||||
|
||||
var __globalMouseButtonHandler = function(event) {
|
||||
if (!event.target.matches(".ctl-item")) {
|
||||
for (var el_item = event.target; el_item && el_item !== document; el_item = el_item.parentNode) {
|
||||
if (el_item.hasAttribute("data-force-hide-menu")) {
|
||||
break;
|
||||
} else if (el_item.hasAttribute("data-dont-hide-menu")) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
__closeAllMenues();
|
||||
__raiseLastWindow();
|
||||
}
|
||||
};
|
||||
|
||||
var __organizeWindowsOnResize = function(orientation) {
|
||||
var view = __getViewGeometry();
|
||||
Array.prototype.forEach.call($$("window"), function(el_window) {
|
||||
if (el_window.style.visibility === "visible" && (orientation || el_window.hasAttribute("data-centered"))) {
|
||||
var rect = el_window.getBoundingClientRect();
|
||||
el_window.style.top = Math.max($("ctl").clientHeight, 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", "");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var __makeWindowMovable = function(el_window) {
|
||||
var el_header = el_window.querySelector(".window-header");
|
||||
var el_grab = el_window.querySelector(".window-header .window-grab");
|
||||
|
||||
var prev_pos = {x: 0, y: 0};
|
||||
|
||||
function startMoving(event) {
|
||||
__closeAllMenues();
|
||||
__raiseWindow(el_window);
|
||||
event = (event || window.event);
|
||||
event.preventDefault();
|
||||
|
||||
if (!event.touches || event.touches.length === 1) {
|
||||
el_header.classList.add("window-header-grabbed");
|
||||
|
||||
prev_pos = getEventPosition(event);
|
||||
|
||||
document.onmousemove = doMoving;
|
||||
document.onmouseup = stopMoving;
|
||||
|
||||
document.ontouchmove = doMoving;
|
||||
document.ontouchend = stopMoving;
|
||||
}
|
||||
}
|
||||
|
||||
function doMoving(event) {
|
||||
el_window.removeAttribute("data-centered");
|
||||
|
||||
event = (event || window.event);
|
||||
event.preventDefault();
|
||||
|
||||
var event_pos = getEventPosition(event);
|
||||
var x = prev_pos.x - event_pos.x;
|
||||
var 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";
|
||||
|
||||
prev_pos = event_pos;
|
||||
}
|
||||
|
||||
function stopMoving() {
|
||||
el_header.classList.remove("window-header-grabbed");
|
||||
|
||||
document.onmousemove = null;
|
||||
document.onmouseup = null;
|
||||
|
||||
document.ontouchmove = null;
|
||||
document.ontouchend = null;
|
||||
}
|
||||
|
||||
function getEventPosition(event) {
|
||||
if (event.touches) {
|
||||
return {x: event.touches[0].clientX, y: event.touches[0].clientY};
|
||||
} else {
|
||||
return {x: event.clientX, y: event.clientY};
|
||||
}
|
||||
}
|
||||
|
||||
el_window.setAttribute("data-centered", "");
|
||||
tools.setOnClick(el_window, () => __raiseWindow(el_window));
|
||||
|
||||
el_grab.onmousedown = startMoving;
|
||||
el_grab.ontouchstart = startMoving;
|
||||
};
|
||||
|
||||
var __raiseLastWindow = function() {
|
||||
var last_el_window = null;
|
||||
var max_z_index = 0;
|
||||
__windows.forEach(function(el_window) {
|
||||
var z_index = parseInt(window.getComputedStyle(el_window, null).zIndex) || 0;
|
||||
if (max_z_index < z_index && window.getComputedStyle(el_window, null).visibility !== "hidden") {
|
||||
last_el_window = el_window;
|
||||
max_z_index = z_index;
|
||||
}
|
||||
});
|
||||
__raiseWindow(last_el_window);
|
||||
};
|
||||
|
||||
var __raiseWindow = function(el_window) {
|
||||
var el_to_focus = (el_window.className === "modal" ? el_window.querySelector(".modal-window") : el_window);
|
||||
if (document.activeElement !== el_to_focus) {
|
||||
el_to_focus.focus();
|
||||
tools.debug("Focused window:", el_window);
|
||||
if (el_window.className !== "modal" && parseInt(el_window.style.zIndex) !== __top_z_index) {
|
||||
var z_index = __top_z_index + 1;
|
||||
el_window.style.zIndex = z_index;
|
||||
__top_z_index = z_index;
|
||||
tools.debug("Raised window:", el_window);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
__init__();
|
||||
}
|
||||
Reference in New Issue
Block a user