diff options
author | Maxim Devaev <[email protected]> | 2022-10-08 06:09:33 +0300 |
---|---|---|
committer | Maxim Devaev <[email protected]> | 2022-10-08 06:09:33 +0300 |
commit | a39d3dffbe837c15576615656df5a924bcd44831 (patch) | |
tree | 407258ff32ac271ec2ccf6069a00e56ce1f04722 | |
parent | 16d9c3815f5bb5ba98f0923efc9f0fad8e2f48a2 (diff) |
pikvm/pikvm#803: Ability to use LEDs in GPIO title
-rw-r--r-- | kvmd/apps/__init__.py | 3 | ||||
-rw-r--r-- | kvmd/apps/kvmd/ugpio.py | 40 | ||||
-rw-r--r-- | kvmd/validators/ugpio.py | 4 | ||||
-rw-r--r-- | testenv/tests/validators/test_ugpio.py | 14 | ||||
-rw-r--r-- | web/kvm/index.html | 16 | ||||
-rw-r--r-- | web/kvm/navbar-atx.pug | 2 | ||||
-rw-r--r-- | web/kvm/navbar-gpio.pug | 2 | ||||
-rw-r--r-- | web/kvm/navbar-macro.pug | 2 | ||||
-rw-r--r-- | web/kvm/navbar-msd.pug | 2 | ||||
-rw-r--r-- | web/kvm/navbar-system.pug | 2 | ||||
-rw-r--r-- | web/kvm/navbar-text.pug | 2 | ||||
-rw-r--r-- | web/kvm/navbar.pug | 2 | ||||
-rw-r--r-- | web/share/css/navbar.css | 10 | ||||
-rw-r--r-- | web/share/js/kvm/gpio.js | 11 | ||||
-rw-r--r-- | web/share/js/wm.js | 5 |
15 files changed, 84 insertions, 33 deletions
diff --git a/kvmd/apps/__init__.py b/kvmd/apps/__init__.py index 6bac90e2..168b1b85 100644 --- a/kvmd/apps/__init__.py +++ b/kvmd/apps/__init__.py @@ -93,6 +93,7 @@ from ..validators.kvm import valid_stream_h264_gop from ..validators.ugpio import valid_ugpio_driver from ..validators.ugpio import valid_ugpio_channel from ..validators.ugpio import valid_ugpio_mode +from ..validators.ugpio import valid_ugpio_view_title from ..validators.ugpio import valid_ugpio_view_table from ..validators.hw import valid_tty_speed @@ -482,7 +483,7 @@ def _get_config_scheme() -> dict: "scheme": {}, # Dymanic content "view": { "header": { - "title": Option("GPIO"), + "title": Option("GPIO", type=valid_ugpio_view_title), }, "table": Option([], type=valid_ugpio_view_table), }, diff --git a/kvmd/apps/kvmd/ugpio.py b/kvmd/apps/kvmd/ugpio.py index 8b7b31f8..0774ff91 100644 --- a/kvmd/apps/kvmd/ugpio.py +++ b/kvmd/apps/kvmd/ugpio.py @@ -311,6 +311,27 @@ class UserGpio: # ===== def __make_view(self) -> dict: + return { + "header": {"title": self.__make_view_title()}, + "table": self.__make_view_table(), + } + + def __make_view_title(self) -> list[dict]: + raw_title = self.__view["header"]["title"] + title: list[dict] = [] + if isinstance(raw_title, list): + for item in raw_title: + if item.startswith("#") or len(item) == 0: + title.append(self.__make_item_label(item)) + else: + parts = list(map(str.strip, item.split("|", 2))) + if parts and parts[0] in self.__inputs: + title.append(self.__make_item_input(parts)) + else: + title.append(self.__make_item_label(f"#{raw_title}")) + return title + + def __make_view_table(self) -> list[list[dict] | None]: table: list[list[dict] | None] = [] for row in self.__view["table"]: if len(row) == 0: @@ -320,29 +341,24 @@ class UserGpio: items: list[dict] = [] for item in map(str.strip, row): if item.startswith("#") or len(item) == 0: - items.append(self.__make_view_label(item)) + items.append(self.__make_item_label(item)) else: parts = list(map(str.strip, item.split("|", 2))) if parts: if parts[0] in self.__inputs: - items.append(self.__make_view_input(parts)) + items.append(self.__make_item_input(parts)) elif parts[0] in self.__outputs: - items.append(self.__make_view_output(parts)) + items.append(self.__make_item_output(parts)) table.append(items) + return table - return { - "header": self.__view["header"], - "table": table, - } - - def __make_view_label(self, item: str) -> dict: - assert item.startswith("#") + def __make_item_label(self, item: str) -> dict: return { "type": "label", "text": item[1:].strip(), } - def __make_view_input(self, parts: list[str]) -> dict: + def __make_item_input(self, parts: list[str]) -> dict: assert len(parts) >= 1 color = (parts[1] if len(parts) > 1 else None) if color not in ["green", "yellow", "red"]: @@ -353,7 +369,7 @@ class UserGpio: "color": color, } - def __make_view_output(self, parts: list[str]) -> dict: + def __make_item_output(self, parts: list[str]) -> dict: assert len(parts) >= 1 confirm = False text = "Click" diff --git a/kvmd/validators/ugpio.py b/kvmd/validators/ugpio.py index c05aa75d..ba4a5c5b 100644 --- a/kvmd/validators/ugpio.py +++ b/kvmd/validators/ugpio.py @@ -46,6 +46,10 @@ def valid_ugpio_mode(arg: Any, variants: set[str]) -> str: return check_string_in_list(arg, "GPIO driver's pin mode", variants) +def valid_ugpio_view_title(arg: Any) -> (str | list[str]): + return (list(map(str, arg)) if isinstance(arg, list) else str(arg)) + + def valid_ugpio_view_table(arg: Any) -> list[list[str]]: # pylint: disable=inconsistent-return-statements try: return [list(map(str, row)) for row in list(arg)] diff --git a/testenv/tests/validators/test_ugpio.py b/testenv/tests/validators/test_ugpio.py index 9b03ab60..7207fd71 100644 --- a/testenv/tests/validators/test_ugpio.py +++ b/testenv/tests/validators/test_ugpio.py @@ -29,6 +29,7 @@ from kvmd.validators import ValidatorError from kvmd.validators.ugpio import valid_ugpio_driver from kvmd.validators.ugpio import valid_ugpio_channel from kvmd.validators.ugpio import valid_ugpio_mode +from kvmd.validators.ugpio import valid_ugpio_view_title from kvmd.validators.ugpio import valid_ugpio_view_table from kvmd.plugins.ugpio import UserGpioModes @@ -95,6 +96,19 @@ def test_fail__valid_ugpio_mode(arg: Any) -> None: # ===== @pytest.mark.parametrize("arg,retval", [ + ([], []), + ("", ""), + ("ab", "ab"), + ([""], [""]), + ([[]], ["[]"]), + (["a", None], ["a", "None"]), +]) +def test_ok__valid_ugpio_view_title(arg: Any, retval: Any) -> None: + assert valid_ugpio_view_title(arg) == retval + + +# ===== [email protected]("arg,retval", [ ([], []), ({}, []), ([[]], [[]]), diff --git a/web/kvm/index.html b/web/kvm/index.html index f9467097..b2d6789d 100644 --- a/web/kvm/index.html +++ b/web/kvm/index.html @@ -60,7 +60,7 @@ <ul id="navbar"> <li class="left"><a id="logo" href="/">← <img class="svg-gray" src="/share/svg/logo.svg" alt="&pi;-kvm"></a></li> <div class="hidden" id="hw-health-dropdown"> - <li class="left"><a class="menu-button" href="#"><img class="hidden" data-dont-hide-menu id="hw-health-undervoltage-led" src="/share/svg/led-undervoltage.svg"><img class="hidden" data-dont-hide-menu id="hw-health-overheating-led" src="/share/svg/led-overheating.svg"></a> + <li class="left"><a class="menu-button" href="#"><img class="hidden" id="hw-health-undervoltage-led" src="/share/svg/led-undervoltage.svg"><img class="hidden" id="hw-health-overheating-led" src="/share/svg/led-overheating.svg"></a> <div class="menu" data-dont-hide-menu> <div class="text"> <table> @@ -108,7 +108,7 @@ </li> </div> <div class="hidden" id="fan-health-dropdown"> - <li class="left"><a class="menu-button" href="#"><img class="hidden" data-dont-hide-menu id="fan-health-led" src="/share/svg/led-fan.svg"></a> + <li class="left"><a class="menu-button" href="#"><img class="hidden" id="fan-health-led" src="/share/svg/led-fan.svg"></a> <div class="menu" data-dont-hide-menu> <div class="text"> <table> @@ -139,7 +139,7 @@ </div> </li> </div> - <li class="right" id="system-dropdown"><a class="menu-button" href="#"><img class="led-gray" data-dont-hide-menu id="link-led" src="/share/svg/led-link.svg"><img class="led-gray" data-dont-hide-menu id="stream-led" src="/share/svg/led-stream.svg"><img class="led-gray" data-dont-hide-menu id="hid-keyboard-led" src="/share/svg/led-hid-keyboard.svg"><img class="led-gray" data-dont-hide-menu id="hid-mouse-led" src="/share/svg/led-hid-mouse.svg">System</a> + <li class="right" id="system-dropdown"><a class="menu-button" href="#"><img class="led-gray" id="link-led" src="/share/svg/led-link.svg"><img class="led-gray" id="stream-led" src="/share/svg/led-stream.svg"><img class="led-gray" id="hid-keyboard-led" src="/share/svg/led-hid-keyboard.svg"><img class="led-gray" id="hid-mouse-led" src="/share/svg/led-hid-mouse.svg"><span>System</span></a> <div class="menu" data-dont-hide-menu id="system-menu"> <table class="kv" style="width: calc(100% - 20px)"> <tr> @@ -335,7 +335,7 @@ </div> </div> </li> - <li class="right feature-disabled" id="atx-dropdown"><a class="menu-button" href="#"><img class="led-gray" data-dont-hide-menu id="atx-power-led" src="/share/svg/led-atx-power.svg"><img class="led-gray" data-dont-hide-menu id="atx-hdd-led" src="/share/svg/led-atx-hdd.svg">ATX</a> + <li class="right feature-disabled" id="atx-dropdown"><a class="menu-button" href="#"><img class="led-gray" id="atx-power-led" src="/share/svg/led-atx-power.svg"><img class="led-gray" id="atx-hdd-led" src="/share/svg/led-atx-hdd.svg"><span>ATX</span></a> <div class="menu" data-dont-hide-menu> <div class="text"><b>Control the server's power<br></b><sub>Use the short click for ACPI shutdown</sub></div> <hr> @@ -359,7 +359,7 @@ </div> </div> </li> - <li class="right feature-disabled" id="msd-dropdown"><a class="menu-button" href="#"><img class="led-gray" data-dont-hide-menu id="msd-led" src="/share/svg/led-msd.svg">Drive</a> + <li class="right feature-disabled" id="msd-dropdown"><a class="menu-button" href="#"><img class="led-gray" id="msd-led" src="/share/svg/led-msd.svg"><span>Drive</span></a> <div class="menu" data-dont-hide-menu id="msd-menu"> <div class="text"><b>Mass Storage Drive: </b><span id="msd-status"></span><br></div> <hr> @@ -570,7 +570,7 @@ </div> </div> </li> - <li class="right" id="macro-dropdown"><a class="menu-button" href="#"><img class="led-gray" data-dont-hide-menu id="hid-recorder-led" src="/share/svg/led-gear.svg">Macro</a> + <li class="right" id="macro-dropdown"><a class="menu-button" href="#"><img class="led-gray" id="hid-recorder-led" src="/share/svg/led-gear.svg"><span>Macro</span></a> <div class="menu" data-dont-hide-menu> <div class="text"><b>Record and play HID/ATX/GPIO actions<br></b><sub>For security reasons, the record will not be saved on the PiKVM</sub></div> <hr> @@ -612,7 +612,7 @@ </div> </div> </li> - <li class="right" id="text-dropdown"><a class="menu-button" href="#"><img class="feature-disabled" data-dont-hide-menu id="stream-ocr-led" src="/share/svg/led-gear.svg">Text</a> + <li class="right" id="text-dropdown"><a class="menu-button" href="#"><img class="feature-disabled" id="stream-ocr-led" src="/share/svg/led-gear.svg"><span>Text</span></a> <div class="menu" data-dont-hide-menu> <div class="text"><b>Paste text as keypress sequence<br></b><sub>Please note that PiKVM cannot switch the keyboard layout</sub></div> <hr> @@ -747,7 +747,7 @@ </table> </div> </li> - <li class="right feature-disabled" id="gpio-dropdown"><a class="menu-button" id="gpio-menu-button" href="#">GPIO</a> + <li class="right feature-disabled" id="gpio-dropdown"><a class="menu-button" id="gpio-menu-button" href="#"><span>GPIO</span></a> <div class="menu" data-dont-hide-menu id="gpio-menu"></div> </li> </ul> diff --git a/web/kvm/navbar-atx.pug b/web/kvm/navbar-atx.pug index 4122ca88..30bffaa2 100644 --- a/web/kvm/navbar-atx.pug +++ b/web/kvm/navbar-atx.pug @@ -2,7 +2,7 @@ li(id="atx-dropdown" class="right feature-disabled") a(class="menu-button" href="#") +navbar_led("atx-power-led", "led-atx-power") +navbar_led("atx-hdd-led", "led-atx-hdd") - | ATX + span ATX div(data-dont-hide-menu class="menu") div(class="text") b Control the server's power#[br] diff --git a/web/kvm/navbar-gpio.pug b/web/kvm/navbar-gpio.pug index ed5000ac..e8424ef7 100644 --- a/web/kvm/navbar-gpio.pug +++ b/web/kvm/navbar-gpio.pug @@ -1,4 +1,4 @@ li(id="gpio-dropdown" class="right feature-disabled") a(class="menu-button" id="gpio-menu-button" href="#") - | GPIO + span GPIO div(data-dont-hide-menu id="gpio-menu" class="menu") diff --git a/web/kvm/navbar-macro.pug b/web/kvm/navbar-macro.pug index 7d3ab18f..73b88c63 100644 --- a/web/kvm/navbar-macro.pug +++ b/web/kvm/navbar-macro.pug @@ -1,7 +1,7 @@ li(id="macro-dropdown" class="right") a(class="menu-button" href="#") +navbar_led("hid-recorder-led", "led-gear") - | Macro + span Macro div(data-dont-hide-menu class="menu") div(class="text") b Record and play HID/ATX/GPIO actions#[br] diff --git a/web/kvm/navbar-msd.pug b/web/kvm/navbar-msd.pug index d382c5b2..14e2d956 100644 --- a/web/kvm/navbar-msd.pug +++ b/web/kvm/navbar-msd.pug @@ -1,7 +1,7 @@ li(id="msd-dropdown" class="right feature-disabled") a(class="menu-button" href="#") +navbar_led("msd-led", "led-msd") - | Drive + span Drive div(data-dont-hide-menu id="msd-menu" class="menu") div(class="text") b Mass Storage Drive: diff --git a/web/kvm/navbar-system.pug b/web/kvm/navbar-system.pug index 499e34e1..550325ae 100644 --- a/web/kvm/navbar-system.pug +++ b/web/kvm/navbar-system.pug @@ -4,7 +4,7 @@ li(id="system-dropdown" class="right") +navbar_led("stream-led", "led-stream") +navbar_led("hid-keyboard-led", "led-hid-keyboard") +navbar_led("hid-mouse-led", "led-hid-mouse") - | System + span System div(data-dont-hide-menu id="system-menu" class="menu") table(class="kv" style="width: calc(100% - 20px)") tr diff --git a/web/kvm/navbar-text.pug b/web/kvm/navbar-text.pug index 9b50f0e1..afc7a70b 100644 --- a/web/kvm/navbar-text.pug +++ b/web/kvm/navbar-text.pug @@ -1,7 +1,7 @@ li(id="text-dropdown" class="right") a(class="menu-button" href="#") +navbar_led("stream-ocr-led", "led-gear", "feature-disabled") - | Text + span Text div(data-dont-hide-menu class="menu") div(class="text") b Paste text as keypress sequence#[br] diff --git a/web/kvm/navbar.pug b/web/kvm/navbar.pug index 9703344e..98456765 100644 --- a/web/kvm/navbar.pug +++ b/web/kvm/navbar.pug @@ -1,5 +1,5 @@ mixin navbar_led(id, icon, cls="led-gray") - img(data-dont-hide-menu id=id, class=cls src=`${svg_dir}/${icon}.svg`) + img(id=id, class=cls src=`${svg_dir}/${icon}.svg`) mixin menu_message(icon, short, classes="") div(class="text") diff --git a/web/share/css/navbar.css b/web/share/css/navbar.css index 8a339b8a..e8a73820 100644 --- a/web/share/css/navbar.css +++ b/web/share/css/navbar.css @@ -44,6 +44,7 @@ ul#navbar li.left { } ul#navbar li a#logo { + height: 50px; /* Чтобы вертикальные разделители не вылезали за пределы навбара */ line-height: 50px; outline: none; cursor: pointer; @@ -55,6 +56,7 @@ ul#navbar li a#logo { } ul#navbar li a.menu-button { + height: 50px; /* То же самое */ line-height: 50px; outline: none; cursor: pointer; @@ -83,7 +85,7 @@ ul#navbar li a.menu-button:hover:not(.active) { @media only screen and (pointer: coarse) { ul#navbar li a#logo:hover:not(.active), ul#navbar li a.menu-button:hover:not(.active) { - background-color: var(--cs-navbar-default-bg) !important; + background-color: var(--cs-navbar-default-bg); } } @@ -92,11 +94,15 @@ ul#navbar li a#logo img { height: 24px; } +ul#navbar li a.menu-button span, ul#navbar li a.menu-button img { vertical-align: middle; - margin-right: 10px; height: 20px; } +ul#navbar li a.menu-button span:not(:last-child), +ul#navbar li a.menu-button img:not(:last-child) { + margin-right: 10px; +} ul#navbar li a.menu-button-pressed { box-shadow: var(--shadow-navbar-item-pressed); diff --git a/web/share/js/kvm/gpio.js b/web/share/js/kvm/gpio.js index 15eabd79..08ec42a8 100644 --- a/web/share/js/kvm/gpio.js +++ b/web/share/js/kvm/gpio.js @@ -72,7 +72,16 @@ export function Gpio(__recorder) { self.setModel = function(model) { tools.feature.setEnabled($("gpio-dropdown"), model.view.table.length); if (model.view.table.length) { - $("gpio-menu-button").innerHTML = `${model.view.header.title}`; + let title = []; + let last_is_label = false; + for (let item of model.view.header.title) { + if (last_is_label && item.type === "label") { + title.push("<span></span>"); + } + last_is_label = (item.type === "label"); + title.push(__createItem(item)); + } + $("gpio-menu-button").innerHTML = title.join(" "); } let content = "<table class=\"kv\">"; diff --git a/web/share/js/wm.js b/web/share/js/wm.js index 51f6c01a..20ea0805 100644 --- a/web/share/js/wm.js +++ b/web/share/js/wm.js @@ -344,8 +344,9 @@ function __WindowManager() { var __globalMouseButtonHandler = function(event) { if ( - event.target.matches && !event.target.matches(".menu-button") - && event.target.closest && !event.target.closest(".modal") + event.target.closest + && !event.target.closest(".menu-button") + && !event.target.closest(".modal") ) { for (let el_item = event.target; el_item && el_item !== document; el_item = el_item.parentNode) { if (el_item.hasAttribute("data-force-hide-menu")) { |