summaryrefslogtreecommitdiff
path: root/web/share
diff options
context:
space:
mode:
authorMaxim Devaev <[email protected]>2021-04-12 15:26:32 +0300
committerGitHub <[email protected]>2021-04-12 15:26:32 +0300
commit3af4a8b852019993ee31becd5651734377da6ec9 (patch)
tree499b77bfaa0c6f939175ea167d5ccb3956be95f0 /web/share
parent8158941833bd8a50278b61967e00d23522e633d7 (diff)
Rework of #39 (#40)
* Rework of #39 patch from @arykov (thanks) * styled resize corner * fixed squashing of resizaable window on moving * keep window size and opsition after full screen * not necessary * attempt to fix desktop overflow * improved * ios fixes * refactoring
Diffstat (limited to 'web/share')
-rw-r--r--web/share/css/kvm/stream.css18
-rw-r--r--web/share/css/vars.css2
-rw-r--r--web/share/css/window.css51
-rw-r--r--web/share/js/keypad.js10
-rw-r--r--web/share/js/kvm/mouse.js31
-rw-r--r--web/share/js/kvm/session.js2
-rw-r--r--web/share/js/kvm/stream.js64
-rw-r--r--web/share/js/tools.js2
-rw-r--r--web/share/js/wm.js124
9 files changed, 224 insertions, 80 deletions
diff --git a/web/share/css/kvm/stream.css b/web/share/css/kvm/stream.css
index 189867a7..cd202eea 100644
--- a/web/share/css/kvm/stream.css
+++ b/web/share/css/kvm/stream.css
@@ -20,11 +20,19 @@
*****************************************************************************/
+div#stream-window {
+ min-width: 400px;
+ min-height: 200px;
+}
+
div#stream-info {
display: none;
}
div#stream-box {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
position: relative;
display: inline-block;
border: var(--border-window-thin);
@@ -45,8 +53,9 @@ div.stream-box-mouse-enabled {
}
img#stream-image {
- width: 640px;
- height: 480px;
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
display: block;
background-color: black;
}
@@ -69,9 +78,12 @@ div#stream-mouse-buttons {
div#stream-window {
padding-top: 3px !important;
border-top: 0 !important;
- border-radius: 0 0 8px 8px !important;
+ border-left: 0 !important;
+ border-right: 0 !important;
+ border-radius: 0 !important;
top: 50px !important;
left: 50% !important;
+ width: 100% !important;
-webkit-transform: translateX(-50%) !important;
transform: translateX(-50%) !important;
}
diff --git a/web/share/css/vars.css b/web/share/css/vars.css
index bec1f10e..f49f40cc 100644
--- a/web/share/css/vars.css
+++ b/web/share/css/vars.css
@@ -66,6 +66,8 @@
--cs-key-pressed-fg: #6c7481;
--cs-key-holded-bg: #436a8a;
+ --cs-corner-bg: #5b90bb;
+
--shadow-micro: 1px 2px 4px 0 rgba(0, 0, 0, 0.4);
--shadow-small: 0 2px 4px 0 rgba(0, 0, 0, 0.2);
--shadow-big: 0 8px 16px 0 rgba(0, 0, 0, 0.4);
diff --git a/web/share/css/window.css b/web/share/css/window.css
index 3af2eb0c..e50db8ba 100644
--- a/web/share/css/window.css
+++ b/web/share/css/window.css
@@ -34,9 +34,31 @@ div.window {
background-color: var(--cs-window-default-bg);
padding: 30px 9px 9px 9px;
}
+div.window-resizable {
+ resize: both;
+}
div.window-active {
border: var(--border-intensive-2px) !important;
}
+div.window-resizable.window-active::-webkit-resizer {
+ width: 0;
+ height: 0;
+ border-style: solid;
+ border-width: 0 0 20px 20px;
+ border-color: transparent transparent var(--cs-corner-bg) transparent;
+ /* border-bottom-right-radius: 8px; */
+ /* Size does not work */
+ display:block;
+ width: 20px !important;
+ height: 20px !important;
+}
+div.window-full-screen {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+}
div.window div.window-header {
overflow: hidden;
@@ -68,17 +90,43 @@ div.window div.window-header-grabbed {
border-bottom: var(--border-intensive-thin);
}
+div.window div.window-header button.window-button-full-screen,
+div.window div.window-header button.window-button-maximize,
div.window div.window-header button.window-button-close {
border: none;
position: absolute;
top: -2px;
- right: -6px;
width: 44px;
height: 24px;
padding-left: 0;
+ padding-right: 0;
color: var(--cs-window-closer-default-fg);
display: inline-block;
}
+div.window div.window-header button.window-button-full-screen {
+ right: 90px;
+}
+div.window div.window-header button.window-button-close {
+ right: 0px;
+}
+div.window div.window-header button.window-button-maximize {
+ right: 45px;
+}
+
+div.window div.window-lock-alert {
+ white-space: normal;
+ text-align: justify;
+ border: var(--border-window-thin);
+ border-radius: 8px;
+ box-sizing: border-box;
+ padding: 5px 5px 5px 5px;
+ background-color: var(--cs-window-default-bg);
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ z-index: 10000;
+}
@media only screen and (min-device-width: 768px) and (max-device-width: 1024px) {
/* iPad */
@@ -91,6 +139,7 @@ div.window div.window-header button.window-button-close {
div.window div.window-header div.window-grab {
height: 35px !important;
}
+ div.window div.window-header button.window-button-full-screen,
div.window div.window-header button.window-button-close {
height: 40px !important;
}
diff --git a/web/share/js/keypad.js b/web/share/js/keypad.js
index 554e43cf..23ed0620 100644
--- a/web/share/js/keypad.js
+++ b/web/share/js/keypad.js
@@ -138,7 +138,7 @@ export function Keypad(keys_parent, key_callback) {
var __isPressed = function(el_key) {
let is_pressed = false;
let el_keys = __resolveKeys(el_key);
- for (let el_key of el_keys) {
+ for (el_key of el_keys) {
is_pressed = (is_pressed || el_key.classList.contains("pressed"));
}
return is_pressed;
@@ -147,7 +147,7 @@ export function Keypad(keys_parent, key_callback) {
var __isHolded = function(el_key) {
let is_holded = false;
let el_keys = __resolveKeys(el_key);
- for (let el_key of el_keys) {
+ for (el_key of el_keys) {
is_holded = (is_holded || el_key.classList.contains("holded"));
}
return is_holded;
@@ -156,7 +156,7 @@ export function Keypad(keys_parent, key_callback) {
var __isActive = function(el_key) {
let is_active = false;
let el_keys = __resolveKeys(el_key);
- for (let el_key of el_keys) {
+ for (el_key of el_keys) {
is_active = (is_active || el_key.classList.contains("pressed") || el_key.classList.contains("holded"));
}
return is_active;
@@ -164,14 +164,14 @@ export function Keypad(keys_parent, key_callback) {
var __activate = function(el_key, cls) {
let el_keys = __resolveKeys(el_key);
- for (let el_key of el_keys) {
+ for (el_key of el_keys) {
el_key.classList.add(cls);
}
};
var __deactivate = function(el_key) {
let el_keys = __resolveKeys(el_key);
- for (let el_key of el_keys) {
+ for (el_key of el_keys) {
el_key.classList.remove("pressed");
el_key.classList.remove("holded");
}
diff --git a/web/share/js/kvm/mouse.js b/web/share/js/kvm/mouse.js
index 3ac7935e..2893a5ae 100644
--- a/web/share/js/kvm/mouse.js
+++ b/web/share/js/kvm/mouse.js
@@ -40,8 +40,8 @@ export function Mouse(record_callback) {
var __keypad = null;
- var __current_pos = {x: 0, y:0};
- var __sent_pos = {x: 0, y:0};
+ var __current_pos = {x: 0, y: 0};
+ var __sent_pos = {x: 0, y: 0};
var __wheel_delta = {x: 0, y: 0};
var __relative_deltas = [];
@@ -210,15 +210,16 @@ export function Mouse(record_callback) {
if (__absolute) {
let pos = __current_pos;
if (pos.x !== __sent_pos.x || pos.y !== __sent_pos.y) {
- let el_stream_image = $("stream-image");
- let 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: moved:", to);
- __sendEvent("mouse_move", {"to": to});
- __sent_pos = pos;
+ let geometry = $("stream-box").stream_geometry;
+ if (geometry) {
+ let to = {
+ x: __translate(pos.x, geometry.x, geometry.width, -32768, 32767),
+ y: __translate(pos.y, geometry.y, geometry.height, -32768, 32767),
+ };
+ tools.debug("Mouse: moved:", to);
+ __sendEvent("mouse_move", {"to": to});
+ __sent_pos = pos;
+ }
}
} else if (__relative_deltas.length) {
tools.debug("Mouse: relative:", __relative_deltas);
@@ -228,7 +229,13 @@ export function Mouse(record_callback) {
};
var __translate = function(x, a, b, c, d) {
- return Math.round((x - a) / (b - a) * (d - c) + c);
+ let translated = Math.round((x - a) / b * (d - c) + c);
+ if (translated < c) {
+ return c;
+ } else if (translated > d) {
+ return d;
+ }
+ return translated;
};
var __streamWheelHandler = function(event) {
diff --git a/web/share/js/kvm/session.js b/web/share/js/kvm/session.js
index 2e4c4f6d..5aa0a6a0 100644
--- a/web/share/js/kvm/session.js
+++ b/web/share/js/kvm/session.js
@@ -47,7 +47,7 @@ export function Session() {
var __hid = new Hid();
var __atx = new Atx();
var __msd = new Msd();
- var __streamer = new Streamer();
+ var __streamer = new Streamer(__hid);
var __wol = new WakeOnLan();
var __gpio = new Gpio();
diff --git a/web/share/js/kvm/stream.js b/web/share/js/kvm/stream.js
index 83e3c4d4..2db37608 100644
--- a/web/share/js/kvm/stream.js
+++ b/web/share/js/kvm/stream.js
@@ -34,8 +34,6 @@ export function Streamer() {
var __resolution = {width: 640, height: 480};
- var __size_factor = 1;
-
var __mjpeg_key = tools.makeId();
var __mjpeg_id = "";
var __mjpeg_fps = -1;
@@ -53,10 +51,6 @@ export function Streamer() {
$("stream-resolution-selector").onchange = (() => __sendParam("resolution", $("stream-resolution-selector").value));
- tools.sliderSetParams($("stream-size-slider"), 20, 200, 5, 100);
- $("stream-size-slider").oninput = () => __resize();
- $("stream-size-slider").onchange = () => __resize();
-
tools.setOnClick($("stream-screenshot-button"), __clickScreenshotButton);
tools.setOnClick($("stream-reset-button"), __clickResetButton);
@@ -65,6 +59,7 @@ export function Streamer() {
self.setState(__state_for_invisible);
}
};
+ $("stream-window").resize_hook = __resizeHook;
};
/************************************************************************/
@@ -88,6 +83,11 @@ export function Streamer() {
}
if (state && state.streamer) {
+ if (!window.ResizeObserver) {
+ // Browsers that don't support this API(on lower versions of iOS for example)
+ __resizeHook();
+ }
+
if (!$("stream-quality-slider").activated) {
wm.setElementEnabled($("stream-quality-slider"), true);
if ($("stream-quality-slider").value !== state.streamer.encoder.quality) {
@@ -107,13 +107,8 @@ export function Streamer() {
}
let resolution_str = __makeStringResolution(state.streamer.source.resolution);
- if (__makeStringResolution(__resolution) != resolution_str) {
+ if (__makeStringResolution(__resolution) !== resolution_str) {
__resolution = state.streamer.source.resolution;
- if ($("stream-auto-resize-switch").checked) {
- __adjustSizeFactor();
- } else {
- __applySizeFactor();
- }
}
if (state.features.resolution) {
@@ -260,40 +255,17 @@ export function Streamer() {
});
};
- var __resize = function() {
- let size = $("stream-size-slider").value;
- $("stream-size-value").innerHTML = `${size}%`;
- __size_factor = size / 100;
- __applySizeFactor();
- };
-
- var __adjustSizeFactor = function() {
- let el_window = $("stream-window");
- let el_slider = $("stream-size-slider");
- let view = wm.getViewGeometry();
-
- for (let size = 100; size >= el_slider.min; size -= el_slider.step) {
- tools.info("Stream: adjusting size:", size);
- $("stream-size-slider").value = size;
- __resize();
-
- let rect = el_window.getBoundingClientRect();
- if (
- rect.bottom <= view.bottom
- && rect.top >= view.top
- && rect.left >= view.left
- && rect.right <= view.right
- ) {
- break;
- }
- }
- };
-
- var __applySizeFactor = function() {
- let el = $("stream-image");
- el.style.width = __resolution.width * __size_factor + "px";
- el.style.height = __resolution.height * __size_factor + "px";
- wm.showWindow($("stream-window"), false);
+ var __resizeHook = function() {
+ let rect = $("stream-image").getBoundingClientRect();
+ let width = $("stream-image").naturalWidth;
+ let height = $("stream-image").naturalHeight;
+ let ratio = Math.min(rect.width / width, rect.height / height);
+ $("stream-box").stream_geometry = {
+ "x": Math.round((rect.width - ratio * width) / 2),
+ "y": Math.round((rect.height - ratio * height) / 2),
+ "width": Math.round(ratio * width),
+ "height": Math.round(ratio * height),
+ };
};
var __makeStringResolution = function(resolution) {
diff --git a/web/share/js/tools.js b/web/share/js/tools.js
index d6965803..521cb1a0 100644
--- a/web/share/js/tools.js
+++ b/web/share/js/tools.js
@@ -243,5 +243,5 @@ export var tools = new function() {
};
export var $ = (id) => document.getElementById(id);
-export var $$ = (cls) => document.getElementsByClassName(cls);
+export var $$ = (cls) => [].slice.call(document.getElementsByClassName(cls));
export var $$$ = (selector) => document.querySelectorAll(selector);
diff --git a/web/share/js/wm.js b/web/share/js/wm.js
index f879e3e8..49e79ecb 100644
--- a/web/share/js/wm.js
+++ b/web/share/js/wm.js
@@ -54,14 +54,37 @@ function __WindowManager() {
__menu_buttons.push(el_button);
}
+ if (!window.ResizeObserver) {
+ tools.error("ResizeObserver not supported");
+ }
+
for (let el_window of $$("window")) {
el_window.setAttribute("tabindex", "-1");
__makeWindowMovable(el_window);
__windows.push(el_window);
- let el_button = el_window.querySelector(".window-header .window-button-close");
- if (el_button) {
- tools.setOnClick(el_button, function() {
+ if (el_window.classList.contains("window-resizable") && window.ResizeObserver) {
+ new ResizeObserver(function() {
+ // При переполнении рабочей области сократить размер окна по высоте.
+ // По ширине оно настраивается само в CSS.
+ let view = self.getViewGeometry();
+ let rect = el_window.getBoundingClientRect();
+ if ((rect.bottom - rect.top) > (view.bottom - view.top)) {
+ el_window.style.height = view.bottom - view.top + "px";
+ }
+
+ if (el_window.hasAttribute("data-centered")) {
+ __centerWindow(el_window);
+ }
+ if (el_window.resize_hook) {
+ el_window.resize_hook();
+ }
+ }).observe(el_window);
+ }
+
+ let el_close_button = el_window.querySelector(".window-header .window-button-close");
+ if (el_close_button) {
+ tools.setOnClick(el_close_button, function() {
let close_window = function() {
__closeWindow(el_window);
__activateLastWindow(el_window);
@@ -78,6 +101,22 @@ function __WindowManager() {
}
});
}
+
+ let el_maximize_button = el_window.querySelector(".window-header .window-button-maximize");
+ if (el_maximize_button) {
+ tools.setOnClick(el_maximize_button, function() {
+ __maximizeWindow(el_window);
+ __activateLastWindow(el_window);
+ });
+ }
+
+ let el_full_screen_button = el_window.querySelector(".window-header .window-button-full-screen");
+ if (el_full_screen_button) {
+ tools.setOnClick(el_full_screen_button, function() {
+ __fullScreenWindow(el_window);
+ __activateLastWindow(el_window);
+ });
+ }
}
window.onmouseup = __globalMouseButtonHandler;
@@ -86,8 +125,10 @@ function __WindowManager() {
window.addEventListener("focusin", __focusIn);
window.addEventListener("focusout", __focusOut);
- window.addEventListener("resize", __organizeWindowsOnResize);
- window.addEventListener("orientationchange", __organizeWindowsOnResize);
+ window.addEventListener("resize", __organizeWindowsOnBrowserResize);
+ window.addEventListener("orientationchange", __organizeWindowsOnBrowserResize);
+
+ document.onfullscreenchange = __onFullScreenChange;
};
/************************************************************************/
@@ -205,7 +246,7 @@ function __WindowManager() {
if (activate) {
__activateWindow(el_window);
}
- if (Object.prototype.hasOwnProperty.call(el_window, "show_hook")) {
+ if (el_window.show_hook) {
if (showed) {
el_window.show_hook();
}
@@ -239,7 +280,7 @@ function __WindowManager() {
let el_menu = el_button.parentElement.querySelector(".menu");
if (el_button === el_a && window.getComputedStyle(el_menu, null).visibility === "hidden") {
let rect = el_menu.getBoundingClientRect();
- let offset = self.getViewGeometry().right - (rect.x + 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) {
el_menu.style.right = "0px";
} else {
@@ -319,7 +360,7 @@ function __WindowManager() {
}
};
- var __organizeWindowsOnResize = function() {
+ var __organizeWindowsOnBrowserResize = function() {
for (let el_window of $$("window")) {
if (el_window.style.visibility === "visible") {
__organizeWindow(el_window);
@@ -331,10 +372,19 @@ function __WindowManager() {
let view = self.getViewGeometry();
let rect = el_window.getBoundingClientRect();
+ if (el_window.classList.contains("window-resizable")) {
+ // При переполнении рабочей области сократить размер окна
+ if ((rect.bottom - rect.top) > (view.bottom - view.top)) {
+ el_window.style.height = view.bottom - view.top + "px";
+ }
+ if ((rect.right - rect.left) > (view.right - view.left)) {
+ el_window.style.width = view.right - view.left + "px";
+ }
+ rect = el_window.getBoundingClientRect();
+ }
+
if (el_window.hasAttribute("data-centered") || center) {
- el_window.style.top = Math.max(view.top, Math.round((view.bottom - rect.height) / 2)) + "px";
- el_window.style.left = Math.round((view.right - rect.width) / 2) + "px";
- el_window.setAttribute("data-centered", "");
+ __centerWindow(el_window);
} else {
if (rect.top <= view.top) {
el_window.style.top = view.top + "px";
@@ -350,6 +400,14 @@ function __WindowManager() {
}
};
+ var __centerWindow = function(el_window) {
+ let view = self.getViewGeometry();
+ let rect = el_window.getBoundingClientRect();
+ el_window.style.top = Math.max(view.top, Math.round((view.bottom - rect.height) / 2)) + "px";
+ el_window.style.left = Math.round((view.right - rect.width) / 2) + "px";
+ el_window.setAttribute("data-centered", "");
+ };
+
var __activateLastWindow = function(el_except_window=null) {
let el_last_window = null;
@@ -415,6 +473,10 @@ function __WindowManager() {
let prev_pos = {x: 0, y: 0};
function startMoving(event) {
+ // При перетаскивании resizable-окна за правый кран экрана оно ужимается.
+ // Этот костыль фиксит это.
+ el_window.style.width = el_window.offsetWidth + "px";
+
__closeAllMenues();
__activateWindow(el_window);
event = (event || window.event);
@@ -474,5 +536,45 @@ function __WindowManager() {
el_grab.ontouchstart = startMoving;
};
+ var __onFullScreenChange = function(event) {
+ let el_window = event.target;
+ if (!document.fullscreenElement) {
+ el_window.style.padding = "";
+ let rect = el_window.before_full_screen;
+ if (rect) {
+ el_window.style.width = rect.width + "px";
+ el_window.style.height = rect.height + "px";
+ el_window.style.top = rect.top + "px";
+ el_window.style.left = rect.left + "px";
+ }
+ } else {
+ el_window.style.padding = "0px 0px 0px 0px";
+ }
+ };
+
+ var __fullScreenWindow = function(el_window) {
+ el_window.before_full_screen = el_window.getBoundingClientRect();
+ el_window.requestFullscreen();
+ if ("keyboard" in navigator && "lock" in navigator.keyboard) {
+ navigator.keyboard.lock();
+ } else {
+ let el_lock_alert = el_window.querySelector(".window-lock-alert");
+ if (el_lock_alert) {
+ tools.hiddenSetVisible(el_lock_alert, true);
+ setTimeout(function() {
+ tools.hiddenSetVisible(el_lock_alert, false);
+ }, 7000);
+ }
+ }
+ };
+
+ var __maximizeWindow = function(el_window) {
+ let vertical_offset = $("navbar").offsetHeight;
+ el_window.style.left = "0px";
+ el_window.style.top = vertical_offset + "px";
+ el_window.style.width = window.innerWidth + "px";
+ el_window.style.height = window.innerHeight - vertical_offset + "px";
+ };
+
__init__();
}