From ad6a66ac8fcf7726540f750a6d83e5f80fb0cd71 Mon Sep 17 00:00:00 2001 From: Devaev Maxim Date: Sat, 1 Dec 2018 13:08:35 +0300 Subject: ui -> wm --- testenv/eslintrc.yaml | 4 +- web/js/kvm/atx.js | 12 +- web/js/kvm/hid.js | 18 +-- web/js/kvm/main.js | 12 +- web/js/kvm/msd.js | 22 +-- web/js/kvm/stream.js | 24 ++-- web/js/ui.js | 391 -------------------------------------------------- web/js/wm.js | 391 ++++++++++++++++++++++++++++++++++++++++++++++++++ web/kvm/index.html | 2 +- 9 files changed, 438 insertions(+), 438 deletions(-) delete mode 100644 web/js/ui.js create mode 100644 web/js/wm.js diff --git a/testenv/eslintrc.yaml b/testenv/eslintrc.yaml index 2ef7c7fc..44e1be8a 100644 --- a/testenv/eslintrc.yaml +++ b/testenv/eslintrc.yaml @@ -6,8 +6,8 @@ globals: Msd: true Session: true Streamer: true - Ui: true - ui: true + WindowManager: true + wm: true tools: true checkBrowser: true "$": true diff --git a/web/js/kvm/atx.js b/web/js/kvm/atx.js index 9ef0c063..6a641ac9 100644 --- a/web/js/kvm/atx.js +++ b/web/js/kvm/atx.js @@ -18,9 +18,9 @@ function Atx() { $("atx-power-led").className = (state.leds.power ? "led-green" : "led-gray"); $("atx-hdd-led").className = (state.leds.hdd ? "led-red" : "led-gray"); - ui.switchDisabled($("atx-power-button"), state.busy); - ui.switchDisabled($("atx-power-button-long"), state.busy); - ui.switchDisabled($("atx-reset-button"), state.busy); + wm.switchDisabled($("atx-power-button"), state.busy); + wm.switchDisabled($("atx-power-button-long"), state.busy); + wm.switchDisabled($("atx-reset-button"), state.busy); }; self.clearState = function() { @@ -29,14 +29,14 @@ function Atx() { }; var __clickButton = function(button, confirm_msg) { - ui.confirm(confirm_msg).then(function(ok) { + wm.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.
Please try again later"); + wm.error("Performing another ATX operation for other client.
Please try again later"); } else if (http.status !== 200) { - ui.error("Click error:
", http.responseText); + wm.error("Click error:
", http.responseText); } } }); diff --git a/web/js/kvm/hid.js b/web/js/kvm/hid.js index c837b809..49d659ab 100644 --- a/web/js/kvm/hid.js +++ b/web/js/kvm/hid.js @@ -54,9 +54,9 @@ function Hid() { /********************************************************************************/ self.setSocket = function(ws) { - ui.switchDisabled($("hid-pak-text"), !ws); - ui.switchDisabled($("hid-pak-button"), !ws); - ui.switchDisabled($("hid-reset-button"), !ws); + wm.switchDisabled($("hid-pak-text"), !ws); + wm.switchDisabled($("hid-pak-button"), !ws); + wm.switchDisabled($("hid-reset-button"), !ws); __ws = ws; __keyboard.setSocket(ws); __mouse.setSocket(ws); @@ -152,10 +152,10 @@ function Hid() { Are you sure you want to continue? `; - ui.confirm(confirm_msg).then(function(ok) { + wm.confirm(confirm_msg).then(function(ok) { if (ok) { - ui.switchDisabled($("hid-pak-text"), true); - ui.switchDisabled($("hid-pak-button"), true); + wm.switchDisabled($("hid-pak-text"), true); + wm.switchDisabled($("hid-pak-button"), true); $("hid-pak-led").className = "led-yellow-rotating-fast"; $("hid-pak-led").title = "Autotyping..."; @@ -169,8 +169,8 @@ function Hid() { iterate(); } else { $("hid-pak-text").value = ""; - ui.switchDisabled($("hid-pak-text"), false); - ui.switchDisabled($("hid-pak-button"), false); + wm.switchDisabled($("hid-pak-text"), false); + wm.switchDisabled($("hid-pak-button"), false); $("hid-pak-led").className = "led-gray"; $("hid-pak-led").title = ""; } @@ -188,7 +188,7 @@ function Hid() { var http = tools.makeRequest("POST", "/kvmd/hid/reset", function() { if (http.readyState === 4) { if (http.status !== 200) { - ui.error("HID reset error:
", http.responseText); + wm.error("HID reset error:
", http.responseText); } } }); diff --git a/web/js/kvm/main.js b/web/js/kvm/main.js index fe6b595c..5c6d775a 100644 --- a/web/js/kvm/main.js +++ b/web/js/kvm/main.js @@ -1,15 +1,15 @@ -var ui; +var wm; function main() { if (checkBrowser()) { - ui = new Ui(); + wm = new WindowManager(); - tools.setOnClick($("show-about-button"), () => ui.showWindow($("about-window"))); - tools.setOnClick($("show-keyboard-button"), () => ui.showWindow($("keyboard-window"))); - tools.setOnClick($("show-stream-button"), () => ui.showWindow($("stream-window"))); + tools.setOnClick($("show-about-button"), () => wm.showWindow($("about-window"))); + tools.setOnClick($("show-keyboard-button"), () => wm.showWindow($("keyboard-window"))); + tools.setOnClick($("show-stream-button"), () => wm.showWindow($("stream-window"))); tools.setOnClick($("open-log-button"), () => window.open("/kvmd/log?seek=3600&follow=1", "_blank")); - ui.showWindow($("stream-window")); + wm.showWindow($("stream-window")); new Session(); } diff --git a/web/js/kvm/msd.js b/web/js/kvm/msd.js index 4ce5d464..39578cdd 100644 --- a/web/js/kvm/msd.js +++ b/web/js/kvm/msd.js @@ -55,20 +55,20 @@ function Msd() { var http = tools.makeRequest("POST", "/kvmd/msd/connect?to=" + to, function() { if (http.readyState === 4) { if (http.status !== 200) { - ui.error("Switch error:
", http.responseText); + wm.error("Switch error:
", http.responseText); } } __applyState(); }); __applyState(); - ui.switchDisabled($(`msd-switch-to-${to}-button`), true); + wm.switchDisabled($(`msd-switch-to-${to}-button`), 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.
Maximum:", __formatSize(__state.info.size)); + wm.error("New image is too big for your Mass Storage Device.
Maximum:", __formatSize(__state.info.size)); el_input.value = ""; image_file = null; } @@ -80,7 +80,7 @@ function Msd() { var http = tools.makeRequest("POST", "/kvmd/msd/reset", function() { if (http.readyState === 4) { if (http.status !== 200) { - ui.error("MSD reset error:
", http.responseText); + wm.error("MSD reset error:
", http.responseText); } } __applyState(); @@ -120,12 +120,12 @@ function Msd() { $("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"); - ui.switchDisabled($("msd-switch-to-kvm-button"), (!__state.in_operate || __state.connected_to === "kvm" || __state.busy)); - ui.switchDisabled($("msd-switch-to-server-button"), (!__state.in_operate || __state.connected_to === "server" || __state.busy)); - ui.switchDisabled($("msd-select-new-image-button"), (!__state.in_operate || __state.connected_to !== "kvm" || __state.busy || __upload_http)); - ui.switchDisabled($("msd-upload-new-image-button"), (!__state.in_operate || __state.connected_to !== "kvm" || __state.busy || !__image_file)); - ui.switchDisabled($("msd-abort-uploading-button"), (!__state.in_operate || !__upload_http)); - ui.switchDisabled($("msd-reset-button"), (!__state.in_operate || __upload_http)); + wm.switchDisabled($("msd-switch-to-kvm-button"), (!__state.in_operate || __state.connected_to === "kvm" || __state.busy)); + wm.switchDisabled($("msd-switch-to-server-button"), (!__state.in_operate || __state.connected_to === "server" || __state.busy)); + wm.switchDisabled($("msd-select-new-image-button"), (!__state.in_operate || __state.connected_to !== "kvm" || __state.busy || __upload_http)); + wm.switchDisabled($("msd-upload-new-image-button"), (!__state.in_operate || __state.connected_to !== "kvm" || __state.busy || !__image_file)); + wm.switchDisabled($("msd-abort-uploading-button"), (!__state.in_operate || !__upload_http)); + wm.switchDisabled($("msd-reset-button"), (!__state.in_operate || __upload_http)); $("msd-new-image").style.display = (__image_file ? "block" : "none"); $("msd-progress").setAttribute("data-label", "Waiting for upload ..."); @@ -146,7 +146,7 @@ function Msd() { var __uploadStateChange = function() { if (__upload_http.readyState === 4) { if (__upload_http.status !== 200) { - ui.error("Can't upload image to the Mass Storage Device:
", __upload_http.responseText); + wm.error("Can't upload image to the Mass Storage Device:
", __upload_http.responseText); } $("msd-select-new-image-file").value = ""; __image_file = null; diff --git a/web/js/kvm/stream.js b/web/js/kvm/stream.js index 52e01fc4..2133349a 100644 --- a/web/js/kvm/stream.js +++ b/web/js/kvm/stream.js @@ -49,7 +49,7 @@ function Streamer() { } if (!$("stream-quality-slider").activated) { - ui.switchDisabled($("stream-quality-slider"), false); + wm.switchDisabled($("stream-quality-slider"), false); if ($("stream-quality-slider").value !== source.quality) { $("stream-quality-slider").value = source.quality; __updateQualityValue(source.quality); @@ -57,7 +57,7 @@ function Streamer() { } if (!$("stream-desired-fps-slider").activated) { - ui.switchDisabled($("stream-desired-fps-slider"), false); + wm.switchDisabled($("stream-desired-fps-slider"), false); if ($("stream-desired-fps-slider").value !== source.desired_fps) { $("stream-desired-fps-slider").value = source.desired_fps; __updateDesiredFpsValue(source.desired_fps); @@ -101,8 +101,8 @@ function Streamer() { $("stream-box").classList.remove("stream-box-inactive"); $("stream-led").className = "led-green"; $("stream-led").title = "Stream is active"; - ui.switchDisabled($("stream-screenshot-button"), false); - ui.switchDisabled($("stream-reset-button"), false); + wm.switchDisabled($("stream-screenshot-button"), false); + wm.switchDisabled($("stream-reset-button"), false); tools.info("Stream: acquired"); __prev = true; } @@ -121,10 +121,10 @@ function Streamer() { $("stream-box").classList.add("stream-box-inactive"); $("stream-led").className = "led-gray"; $("stream-led").title = "Stream inactive"; - ui.switchDisabled($("stream-screenshot-button"), true); - ui.switchDisabled($("stream-reset-button"), true); - ui.switchDisabled($("stream-quality-slider"), true); - ui.switchDisabled($("stream-desired-fps-slider"), true); + wm.switchDisabled($("stream-screenshot-button"), true); + wm.switchDisabled($("stream-reset-button"), true); + wm.switchDisabled($("stream-quality-slider"), true); + wm.switchDisabled($("stream-desired-fps-slider"), true); __client_key = tools.makeId(); __client_id = ""; @@ -165,7 +165,7 @@ function Streamer() { var http = tools.makeRequest("POST", "/kvmd/streamer/reset", function() { if (http.readyState === 4) { if (http.status !== 200) { - ui.error("Can't reset stream:
", http.responseText); + wm.error("Can't reset stream:
", http.responseText); } } }); @@ -175,7 +175,7 @@ function Streamer() { var http = tools.makeRequest("POST", `/kvmd/streamer/set_params?${name}=${value}`, function() { if (http.readyState === 4) { if (http.status !== 200) { - ui.error("Can't configure stream:
", http.responseText); + wm.error("Can't configure stream:
", http.responseText); } } }); @@ -191,7 +191,7 @@ function Streamer() { var __adjustSizeFactor = function() { var el_window = $("stream-window"); var el_slider = $("stream-size-slider"); - var view = ui.getViewGeometry(); + var view = wm.getViewGeometry(); for (var size = 100; size >= el_slider.min; size -= el_slider.step) { tools.info("Stream: adjusting size:", size); @@ -214,7 +214,7 @@ function Streamer() { var el_stream_image = $("stream-image"); el_stream_image.style.width = __resolution.width * __size_factor + "px"; el_stream_image.style.height = __resolution.height * __size_factor + "px"; - ui.showWindow($("stream-window"), false, center); + wm.showWindow($("stream-window"), false, center); }; __init__(); diff --git a/web/js/ui.js b/web/js/ui.js deleted file mode 100644 index efbb1a6c..00000000 --- a/web/js/ui.js +++ /dev/null @@ -1,391 +0,0 @@ -function Ui() { - var self = this; - - /********************************************************************************/ - - var __top_z_index = 0; - var __windows = []; - var __menu_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($$("menu-item"), function(el_item) { - el_item.parentElement.querySelector(".menu-item-content").setAttribute("tabindex", "-1"); - tools.setOnClick(el_item, () => __toggleMenu(el_item)); - __menu_items.push(el_item); - }); - - Array.prototype.forEach.call($$("window"), function(el_window) { - el_window.setAttribute("tabindex", "-1"); - __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"; - __activateLastWindow(el_window); - }); - } - }); - - if ($("menu-logo")) { - tools.setOnClick($("menu-logo"), () => window.history.back()); - } - - window.onmouseup = __globalMouseButtonHandler; - window.ontouchend = __globalMouseButtonHandler; - - window.addEventListener("focusin", __focusIn); - window.addEventListener("focusout", __focusOut); - - window.addEventListener("resize", () => __organizeWindowsOnResize(false)); - window.addEventListener("orientationchange", () => __organizeWindowsOnResize(true)); - }; - - /********************************************************************************/ - - 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_window.style.visibility = "hidden"; - el_modal.outerHTML = ""; - var index = __windows.indexOf(el_modal); - if (index !== -1) { - __windows.splice(index, 1); - } - __activateLastWindow(el_modal); - 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); - __activateWindow(el_modal); - - return promise; - }; - - self.switchDisabled = function(el, disabled) { - if (disabled && document.activeElement === el) { - var el_to_focus = ( - el.closest(".modal-window") - || el.closest(".window") - || el.closest(".menu-item-content") - ); - if (el_to_focus) { - el_to_focus.focus(); - } - } - el.disabled = disabled; - }; - - self.showWindow = function(el_window, activate=true, center=false) { - if (!__isWindowOnPage(el_window) || el_window.hasAttribute("data-centered") || center) { - var view = self.getViewGeometry(); - var rect = el_window.getBoundingClientRect(); - - el_window.style.top = Math.max($("menu").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 (activate) { - __activateWindow(el_window); - } - }; - - self.getViewGeometry = function() { - return { - top: $("menu").clientHeight, - bottom: Math.max(document.documentElement.clientHeight, window.innerHeight || 0), - left: 0, - right: Math.max(document.documentElement.clientWidth, window.innerWidth || 0), - }; - }; - - var __isWindowOnPage = function(el_window) { - var view = self.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 __toggleMenu = function(el_a) { - var all_hidden = true; - - __menu_items.forEach(function(el_item) { - var el_menu = el_item.parentElement.querySelector(".menu-item-content"); - if (el_item === el_a && window.getComputedStyle(el_menu, null).visibility === "hidden") { - el_item.classList.add("menu-item-selected"); - el_menu.style.visibility = "visible"; - el_menu.focus(); - all_hidden &= false; - } else { - el_item.classList.remove("menu-item-selected"); - el_menu.style.visibility = "hidden"; - } - }); - - if (all_hidden) { - document.onkeyup = null; - __activateLastWindow(); - } else { - document.onkeyup = function(event) { - if (event.code === "Escape") { - event.preventDefault(); - __closeAllMenues(); - __activateLastWindow(); - } - }; - } - }; - - var __closeAllMenues = function() { - document.onkeyup = null; - __menu_items.forEach(function(el_item) { - var el_menu = el_item.parentElement.querySelector(".menu-item-content"); - el_item.classList.remove("menu-item-selected"); - el_menu.style.visibility = "hidden"; - }); - }; - - var __focusIn = function(event) { - var el_parent; - if ((el_parent = event.target.closest(".modal-window")) !== null) { - el_parent.classList.add("window-active"); - } else if ((el_parent = event.target.closest(".window")) !== null) { - el_parent.classList.add("window-active"); - } else if ((el_parent = event.target.closest(".menu-item-content")) !== null) { - el_parent.classList.add("menu-item-content-active"); - } - tools.debug("Focus in:", el_parent); - }; - - var __focusOut = function(event) { - var el_parent; - if ((el_parent = event.target.closest(".modal-window")) !== null) { - el_parent.classList.remove("window-active"); - } else if ((el_parent = event.target.closest(".window")) !== null) { - el_parent.classList.remove("window-active"); - } else if ((el_parent = event.target.closest(".menu-item-content")) !== null) { - el_parent.classList.remove("menu-item-content-active"); - } - tools.debug("Focus out:", el_parent); - }; - - var __globalMouseButtonHandler = function(event) { - if (!event.target.matches(".menu-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(); - __activateLastWindow(); - } - }; - - var __organizeWindowsOnResize = function(orientation) { - var view = self.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($("menu").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 __activateLastWindow = function(el_except_window=null) { - var el_last_window = null; - - if (document.activeElement) { - el_last_window = (document.activeElement.closest(".modal-window") || document.activeElement.closest(".window")); - } - - if (!el_last_window || el_last_window === el_except_window) { - var max_z_index = 0; - - __windows.forEach(function(el_window) { - var z_index = parseInt(window.getComputedStyle(el_window, null).zIndex) || 0; - var visibility = window.getComputedStyle(el_window, null).visibility; - - if (max_z_index < z_index && visibility !== "hidden" && el_window !== el_except_window) { - el_last_window = el_window; - max_z_index = z_index; - } - }); - } - - if (el_last_window) { - tools.debug("Activating last window:", el_last_window); - __activateWindow(el_last_window); - } - }; - - var __activateWindow = function(el_window) { - if (window.getComputedStyle(el_window, null).visibility !== "hidden") { - var el_to_focus; - var el_window_contains_focus; - - if (el_window.className === "modal") { - el_to_focus = el_window.querySelector(".modal-window"); - el_window_contains_focus = (document.activeElement && document.activeElement.closest(".modal-window")); - } else { // .window - el_to_focus = el_window; - el_window_contains_focus = (document.activeElement && document.activeElement.closest(".window")); - } - - if (el_window.className !== "modal" && parseInt(el_window.style.zIndex) !== __top_z_index) { - __top_z_index += 1; - el_window.style.zIndex = __top_z_index; - tools.debug("UI: activated window:", el_window); - } - - if (el_window !== el_window_contains_focus) { - el_to_focus.focus(); - tools.debug("UI: focused window:", el_window); - } - } - }; - - 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(); - __activateWindow(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", ""); - el_window.onclick = el_window.ontouchend = () => __activateWindow(el_window); - - el_grab.onmousedown = startMoving; - el_grab.ontouchstart = startMoving; - }; - - __init__(); -} diff --git a/web/js/wm.js b/web/js/wm.js new file mode 100644 index 00000000..002009c2 --- /dev/null +++ b/web/js/wm.js @@ -0,0 +1,391 @@ +function WindowManager() { + var self = this; + + /********************************************************************************/ + + var __top_z_index = 0; + var __windows = []; + var __menu_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($$("menu-item"), function(el_item) { + el_item.parentElement.querySelector(".menu-item-content").setAttribute("tabindex", "-1"); + tools.setOnClick(el_item, () => __toggleMenu(el_item)); + __menu_items.push(el_item); + }); + + Array.prototype.forEach.call($$("window"), function(el_window) { + el_window.setAttribute("tabindex", "-1"); + __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"; + __activateLastWindow(el_window); + }); + } + }); + + if ($("menu-logo")) { + tools.setOnClick($("menu-logo"), () => window.history.back()); + } + + window.onmouseup = __globalMouseButtonHandler; + window.ontouchend = __globalMouseButtonHandler; + + window.addEventListener("focusin", __focusIn); + window.addEventListener("focusout", __focusOut); + + window.addEventListener("resize", () => __organizeWindowsOnResize(false)); + window.addEventListener("orientationchange", () => __organizeWindowsOnResize(true)); + }; + + /********************************************************************************/ + + 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_window.style.visibility = "hidden"; + el_modal.outerHTML = ""; + var index = __windows.indexOf(el_modal); + if (index !== -1) { + __windows.splice(index, 1); + } + __activateLastWindow(el_modal); + 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); + __activateWindow(el_modal); + + return promise; + }; + + self.switchDisabled = function(el, disabled) { + if (disabled && document.activeElement === el) { + var el_to_focus = ( + el.closest(".modal-window") + || el.closest(".window") + || el.closest(".menu-item-content") + ); + if (el_to_focus) { + el_to_focus.focus(); + } + } + el.disabled = disabled; + }; + + self.showWindow = function(el_window, activate=true, center=false) { + if (!__isWindowOnPage(el_window) || el_window.hasAttribute("data-centered") || center) { + var view = self.getViewGeometry(); + var rect = el_window.getBoundingClientRect(); + + el_window.style.top = Math.max($("menu").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 (activate) { + __activateWindow(el_window); + } + }; + + self.getViewGeometry = function() { + return { + top: $("menu").clientHeight, + bottom: Math.max(document.documentElement.clientHeight, window.innerHeight || 0), + left: 0, + right: Math.max(document.documentElement.clientWidth, window.innerWidth || 0), + }; + }; + + var __isWindowOnPage = function(el_window) { + var view = self.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 __toggleMenu = function(el_a) { + var all_hidden = true; + + __menu_items.forEach(function(el_item) { + var el_menu = el_item.parentElement.querySelector(".menu-item-content"); + if (el_item === el_a && window.getComputedStyle(el_menu, null).visibility === "hidden") { + el_item.classList.add("menu-item-selected"); + el_menu.style.visibility = "visible"; + el_menu.focus(); + all_hidden &= false; + } else { + el_item.classList.remove("menu-item-selected"); + el_menu.style.visibility = "hidden"; + } + }); + + if (all_hidden) { + document.onkeyup = null; + __activateLastWindow(); + } else { + document.onkeyup = function(event) { + if (event.code === "Escape") { + event.preventDefault(); + __closeAllMenues(); + __activateLastWindow(); + } + }; + } + }; + + var __closeAllMenues = function() { + document.onkeyup = null; + __menu_items.forEach(function(el_item) { + var el_menu = el_item.parentElement.querySelector(".menu-item-content"); + el_item.classList.remove("menu-item-selected"); + el_menu.style.visibility = "hidden"; + }); + }; + + var __focusIn = function(event) { + var el_parent; + if ((el_parent = event.target.closest(".modal-window")) !== null) { + el_parent.classList.add("window-active"); + } else if ((el_parent = event.target.closest(".window")) !== null) { + el_parent.classList.add("window-active"); + } else if ((el_parent = event.target.closest(".menu-item-content")) !== null) { + el_parent.classList.add("menu-item-content-active"); + } + tools.debug("Focus in:", el_parent); + }; + + var __focusOut = function(event) { + var el_parent; + if ((el_parent = event.target.closest(".modal-window")) !== null) { + el_parent.classList.remove("window-active"); + } else if ((el_parent = event.target.closest(".window")) !== null) { + el_parent.classList.remove("window-active"); + } else if ((el_parent = event.target.closest(".menu-item-content")) !== null) { + el_parent.classList.remove("menu-item-content-active"); + } + tools.debug("Focus out:", el_parent); + }; + + var __globalMouseButtonHandler = function(event) { + if (!event.target.matches(".menu-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(); + __activateLastWindow(); + } + }; + + var __organizeWindowsOnResize = function(orientation) { + var view = self.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($("menu").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 __activateLastWindow = function(el_except_window=null) { + var el_last_window = null; + + if (document.activeElement) { + el_last_window = (document.activeElement.closest(".modal-window") || document.activeElement.closest(".window")); + } + + if (!el_last_window || el_last_window === el_except_window) { + var max_z_index = 0; + + __windows.forEach(function(el_window) { + var z_index = parseInt(window.getComputedStyle(el_window, null).zIndex) || 0; + var visibility = window.getComputedStyle(el_window, null).visibility; + + if (max_z_index < z_index && visibility !== "hidden" && el_window !== el_except_window) { + el_last_window = el_window; + max_z_index = z_index; + } + }); + } + + if (el_last_window) { + tools.debug("Activating last window:", el_last_window); + __activateWindow(el_last_window); + } + }; + + var __activateWindow = function(el_window) { + if (window.getComputedStyle(el_window, null).visibility !== "hidden") { + var el_to_focus; + var el_window_contains_focus; + + if (el_window.className === "modal") { + el_to_focus = el_window.querySelector(".modal-window"); + el_window_contains_focus = (document.activeElement && document.activeElement.closest(".modal-window")); + } else { // .window + el_to_focus = el_window; + el_window_contains_focus = (document.activeElement && document.activeElement.closest(".window")); + } + + if (el_window.className !== "modal" && parseInt(el_window.style.zIndex) !== __top_z_index) { + __top_z_index += 1; + el_window.style.zIndex = __top_z_index; + tools.debug("UI: activated window:", el_window); + } + + if (el_window !== el_window_contains_focus) { + el_to_focus.focus(); + tools.debug("UI: focused window:", el_window); + } + } + }; + + 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(); + __activateWindow(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", ""); + el_window.onclick = el_window.ontouchend = () => __activateWindow(el_window); + + el_grab.onmousedown = startMoving; + el_grab.ontouchstart = startMoving; + }; + + __init__(); +} diff --git a/web/kvm/index.html b/web/kvm/index.html index 6b67cfff..d8807e03 100644 --- a/web/kvm/index.html +++ b/web/kvm/index.html @@ -29,7 +29,7 @@ - + -- cgit v1.2.3