From 8c0b3d42ad8dcfa4bc6a9f7ff6e48ac678070256 Mon Sep 17 00:00:00 2001 From: Elroi Allouch Date: Sat, 18 Nov 2023 23:35:24 +0200 Subject: [PATCH 1/8] cell: tooltip: Simplify the implementation of tooltips --- widgets/cell.py | 6 +++--- widgets/tooltip.py | 18 +++++++----------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/widgets/cell.py b/widgets/cell.py index cb91dd0..1bf5f64 100644 --- a/widgets/cell.py +++ b/widgets/cell.py @@ -74,12 +74,12 @@ async def effect(): ) if details.show_tooltip or details.show_popup: - return CellTooltip(details, hoverables=[cell]) + return html.div(cell, CellTooltip(details)) return cell @component -def CellTooltip(details: CellDetails, hoverables): +def CellTooltip(details: CellDetails): tooltip = html.div( {'style': {'width': '130px'}}, f"Device at {details.cabinet}-{details.number} has status ", @@ -88,4 +88,4 @@ def CellTooltip(details: CellDetails, hoverables): details.status, ) ) - return Tooltip(tooltip, hoverables) + return Tooltip(tooltip) diff --git a/widgets/tooltip.py b/widgets/tooltip.py index 21d7426..0af50f3 100644 --- a/widgets/tooltip.py +++ b/widgets/tooltip.py @@ -3,21 +3,17 @@ @component -def Tooltip(tooltip_content: Component, hoverables): - if not hoverables: - return html.span() - - tooltip = html.div( - {'class_name': 'vl-tooltip'}, - tooltip_content - ) - +def Tooltip(tooltip_content: Component): return html.div( { 'style': { 'position': 'relative', } }, - *hoverables, - tooltip + html.div( + { + 'class_name': 'vl-tooltip' + }, + tooltip_content + ) ) From 6163b5fa1c88f1dcc48682b186eae932745f4662 Mon Sep 17 00:00:00 2001 From: Elroi Allouch Date: Sun, 19 Nov 2023 21:06:54 +0200 Subject: [PATCH 2/8] tooltip: Clear stuck tooltips when clicking on the background --- visual_lab.py | 1 + 1 file changed, 1 insertion(+) diff --git a/visual_lab.py b/visual_lab.py index c56d118..04688dc 100644 --- a/visual_lab.py +++ b/visual_lab.py @@ -68,6 +68,7 @@ def on_click(title: str, cell_number: int): def clear_focused_cell(_): set_focused_cell(None) + set_hovered_cell(None) return html.div( { From 55acdfc494903b4f8a41eef57324c9198b40c93c Mon Sep 17 00:00:00 2001 From: Elroi Allouch Date: Sun, 19 Nov 2023 21:07:37 +0200 Subject: [PATCH 3/8] cell: popup: Seperate the implementations of tooltips and popups --- widgets/cell.py | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/widgets/cell.py b/widgets/cell.py index 1bf5f64..5a79826 100644 --- a/widgets/cell.py +++ b/widgets/cell.py @@ -72,12 +72,33 @@ async def effect(): ), StatusBar(STATUS_BAR_DELAY_OFFSET + details.delay, should_animate.current) ) + popup = None + if details.show_popup: + popup = CellPopup(details) + elif details.show_tooltip: + popup = CellTooltip(details) + if popup is not None: + return html.div(cell, popup) - if details.show_tooltip or details.show_popup: - return html.div(cell, CellTooltip(details)) return cell +@component +def CellPopup(details: CellDetails): + tooltip = html.div( + { + 'style': {'width': '130px'}, + 'onclick': event(lambda _: None, stop_propagation=True), # Clicking the popup should not make it disappear + }, + f"Device at {details.cabinet}-{details.number} has status ", + html.span( + {'style': {'color': COLORS[details.status]}}, + details.status, + ) + ) + return Tooltip(tooltip) + + @component def CellTooltip(details: CellDetails): tooltip = html.div( From fcbbbdaa2a8541bb3c76b1b22e86c677ead082fa Mon Sep 17 00:00:00 2001 From: Elroi Allouch Date: Sun, 19 Nov 2023 21:19:03 +0200 Subject: [PATCH 4/8] cell: popup: Style the popups a little --- static/dark.css | 11 +++++++++++ widgets/cell.py | 10 +++++----- widgets/tooltip.py | 6 ++++-- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/static/dark.css b/static/dark.css index ac4c554..75855c8 100755 --- a/static/dark.css +++ b/static/dark.css @@ -135,6 +135,17 @@ div#app { border-radius: 5px; background-color: #101014; box-shadow: 0px 0px 3px #0f0f11; + position: absolute; + left: -100%; + padding: 10px; + z-index: 2; +} + +.vl-popup { + border-radius: 5px; + background-color: #101014; + box-shadow: 0px 0px 5px #c5ced1; + position: absolute; left: -100%; padding: 10px; diff --git a/widgets/cell.py b/widgets/cell.py index 5a79826..2e9c385 100644 --- a/widgets/cell.py +++ b/widgets/cell.py @@ -4,7 +4,7 @@ from reactpy import html, component, use_effect, use_ref, Ref, event from css_utils import grid_position -from .tooltip import Tooltip +from . import tooltip from consts import COLORS STATUS_BAR_DELAY_OFFSET = 0.17 # trust me on this one @@ -85,7 +85,7 @@ async def effect(): @component def CellPopup(details: CellDetails): - tooltip = html.div( + contents = html.div( { 'style': {'width': '130px'}, 'onclick': event(lambda _: None, stop_propagation=True), # Clicking the popup should not make it disappear @@ -96,12 +96,12 @@ def CellPopup(details: CellDetails): details.status, ) ) - return Tooltip(tooltip) + return tooltip.Tooltip(contents, class_name=tooltip.POPUP) @component def CellTooltip(details: CellDetails): - tooltip = html.div( + contents = html.div( {'style': {'width': '130px'}}, f"Device at {details.cabinet}-{details.number} has status ", html.span( @@ -109,4 +109,4 @@ def CellTooltip(details: CellDetails): details.status, ) ) - return Tooltip(tooltip) + return tooltip.Tooltip(contents) diff --git a/widgets/tooltip.py b/widgets/tooltip.py index 0af50f3..1f7ef8e 100644 --- a/widgets/tooltip.py +++ b/widgets/tooltip.py @@ -1,9 +1,11 @@ from reactpy import html, component from reactpy.types import Component +TOOLTIP = 'vl-tooltip' +POPUP = 'vl-popup' @component -def Tooltip(tooltip_content: Component): +def Tooltip(tooltip_content: Component, class_name=TOOLTIP): return html.div( { 'style': { @@ -12,7 +14,7 @@ def Tooltip(tooltip_content: Component): }, html.div( { - 'class_name': 'vl-tooltip' + 'class_name': class_name }, tooltip_content ) From 315417bda39fb6de00a523816c91e2b65d3caff8 Mon Sep 17 00:00:00 2001 From: Elroi Allouch Date: Sun, 19 Nov 2023 21:50:05 +0200 Subject: [PATCH 5/8] popup: Add a template for popups that change by the status of the cell --- css_utils.py | 10 ++++++++++ widgets/cell.py | 21 ++++++++------------- widgets/popup.py | 43 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 13 deletions(-) create mode 100644 widgets/popup.py diff --git a/css_utils.py b/css_utils.py index 5d6c166..bf44af3 100644 --- a/css_utils.py +++ b/css_utils.py @@ -1,3 +1,6 @@ +from reactpy import html + + def grid_position(x, y, width=1, height=1): width_str = height_str = '' if width != 1: @@ -8,3 +11,10 @@ def grid_position(x, y, width=1, height=1): 'grid-row': f'{y} {height_str}', 'grid-column': f'{x} {width_str}', } + + +def colorize(text: str, color: str): + return html.span( + {'style': {'color': color}}, + text, + ) diff --git a/widgets/cell.py b/widgets/cell.py index 2e9c385..5f65ea9 100644 --- a/widgets/cell.py +++ b/widgets/cell.py @@ -3,8 +3,9 @@ from reactpy import html, component, use_effect, use_ref, Ref, event -from css_utils import grid_position +from css_utils import grid_position, colorize from . import tooltip +from .popup import generate_popup from consts import COLORS STATUS_BAR_DELAY_OFFSET = 0.17 # trust me on this one @@ -70,7 +71,8 @@ async def effect(): {'class_name': 'cell-text'}, details.number ), - StatusBar(STATUS_BAR_DELAY_OFFSET + details.delay, should_animate.current) + StatusBar(STATUS_BAR_DELAY_OFFSET + + details.delay, should_animate.current) ) popup = None if details.show_popup: @@ -87,14 +89,10 @@ async def effect(): def CellPopup(details: CellDetails): contents = html.div( { - 'style': {'width': '130px'}, - 'onclick': event(lambda _: None, stop_propagation=True), # Clicking the popup should not make it disappear + # Clicking the popup should not make it disappear + 'onclick': event(lambda _: None, stop_propagation=True), }, - f"Device at {details.cabinet}-{details.number} has status ", - html.span( - {'style': {'color': COLORS[details.status]}}, - details.status, - ) + generate_popup(details), ) return tooltip.Tooltip(contents, class_name=tooltip.POPUP) @@ -104,9 +102,6 @@ def CellTooltip(details: CellDetails): contents = html.div( {'style': {'width': '130px'}}, f"Device at {details.cabinet}-{details.number} has status ", - html.span( - {'style': {'color': COLORS[details.status]}}, - details.status, - ) + colorize(details.status, COLORS[details.status]), ) return tooltip.Tooltip(contents) diff --git a/widgets/popup.py b/widgets/popup.py new file mode 100644 index 0000000..1c4c752 --- /dev/null +++ b/widgets/popup.py @@ -0,0 +1,43 @@ +from reactpy import component, html +from reactpy.types import Component +from typing import TypeVar, Callable, cast +from . import cell +from css_utils import colorize + +POPUP_WIDTH = '200px' + +PopupMaker = TypeVar( + 'PopupMaker', + bound=Callable[['cell.CellDetails'], Component] +) +_POPUPS: dict[str, PopupMaker] = {} + + +@component +def default_handler(details: 'cell.CellDetails'): + return html.div( + { + 'style': { + 'width': POPUP_WIDTH, + } + }, + colorize('Error', '#f12323'), + f': popup for status {details.status} has not been implemented yet!' + ) + + +def get_handler(status: str) -> PopupMaker: + return _POPUPS.get(status, default_handler) + + +def generate_popup(details: 'cell.CellDetails'): + handler = get_handler(details.status) + return handler(details) + + +def popup_maker(status: str) -> Callable[[PopupMaker], PopupMaker]: + def wrapper(func: PopupMaker) -> PopupMaker: + _POPUPS[status] = func + return func + return wrapper + From f11ba6d02737c1733defdaee0ecc9bf385cc9b78 Mon Sep 17 00:00:00 2001 From: Elroi Allouch Date: Sun, 19 Nov 2023 22:04:51 +0200 Subject: [PATCH 6/8] consts: Convert magic status values to an enum like class --- consts.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/consts.py b/consts.py index bb162d0..eb001de 100644 --- a/consts.py +++ b/consts.py @@ -1,13 +1,20 @@ +class Status: + GOOD = 'good' + DOWN = 'down' + UNKNOWN = 'unknown-device' + BROKEN = 'broken' + MISCONFIGURED = 'misconfigured' + STATUSES = \ - ['good'] * 20 + \ - ['down'] * 2 + \ - ['unknown-device'] * 5 + \ - ['broken', 'misconfigured'] + [Status.GOOD] * 20 + \ + [Status.DOWN] * 2 + \ + [Status.UNKNOWN] * 5 + \ + [Status.BROKEN, Status.MISCONFIGURED] COLORS = { - 'good': '#72fa93', - 'down': '#e45f2b', - 'unknown-device': '#f6c445', - 'broken': '#e39af0', - 'misconfigured': '#9ac1f0', + Status.GOOD: '#72fa93', + Status.DOWN: '#e45f2b', + Status.UNKNOWN: '#f6c445', + Status.BROKEN: '#e39af0', + Status.MISCONFIGURED: '#9ac1f0', } From 161a55b2a532a174cf809d8815f1d479a9c29183 Mon Sep 17 00:00:00 2001 From: Elroi Allouch Date: Sun, 19 Nov 2023 22:05:13 +0200 Subject: [PATCH 7/8] popup: Add popup for 'down' cells --- widgets/cell.py | 4 ++++ widgets/popup.py | 17 ++++++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/widgets/cell.py b/widgets/cell.py index 5f65ea9..3bbe934 100644 --- a/widgets/cell.py +++ b/widgets/cell.py @@ -27,6 +27,10 @@ class CellDetails: on_click: Callable[[], None] on_hover: Callable[[bool], None] + @property + def cell_id(self) -> str: + return f'{self.cabinet}-{self.number}' + @component def StatusBar(delay: float, should_animate: bool): diff --git a/widgets/popup.py b/widgets/popup.py index 1c4c752..e568621 100644 --- a/widgets/popup.py +++ b/widgets/popup.py @@ -2,9 +2,10 @@ from reactpy.types import Component from typing import TypeVar, Callable, cast from . import cell +from consts import Status, COLORS from css_utils import colorize -POPUP_WIDTH = '200px' +POPUP_WIDTH = '250px' PopupMaker = TypeVar( 'PopupMaker', @@ -41,3 +42,17 @@ def wrapper(func: PopupMaker) -> PopupMaker: return func return wrapper + +@popup_maker(Status.DOWN) +@component +def _popup_down(details: 'cell.CellDetails'): + return html.div( + { + 'style': { + 'width': POPUP_WIDTH, + }, + }, + f'The interface in cell {details.cell_id} is ', + colorize('DOWN', COLORS[Status.DOWN]), + '. Please make sure the cable is connected!', + ) \ No newline at end of file From 0b4d22f58073d74b9c3d42d192d915c48477b1d4 Mon Sep 17 00:00:00 2001 From: Elroi Allouch Date: Sun, 19 Nov 2023 22:31:47 +0200 Subject: [PATCH 8/8] popup: Add popup for 'misconfigured' cells --- static/dark.css | 17 +++++++++++++++++ widgets/popup.py | 23 ++++++++++++++++++++++- widgets/tooltip.py | 16 ++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/static/dark.css b/static/dark.css index 75855c8..02f184c 100755 --- a/static/dark.css +++ b/static/dark.css @@ -152,6 +152,23 @@ div#app { z-index: 1; } +.vl-popup-button { + border-radius: 5px; + background-color: #1f242e; + outline-style: solid; + outline-width: 1px; + outline-color: #ffffffaa; + padding: 2px; +} + +.vl-popup-button:hover { + background-color: #161920; + outline-style: solid; + outline-width: 1px; + outline-color: #ffffff33; + padding: 2px; +} + @keyframes appear-vertical { from { height: 0px; diff --git a/widgets/popup.py b/widgets/popup.py index e568621..65cb57e 100644 --- a/widgets/popup.py +++ b/widgets/popup.py @@ -2,6 +2,7 @@ from reactpy.types import Component from typing import TypeVar, Callable, cast from . import cell +from .tooltip import PopupButton from consts import Status, COLORS from css_utils import colorize @@ -55,4 +56,24 @@ def _popup_down(details: 'cell.CellDetails'): f'The interface in cell {details.cell_id} is ', colorize('DOWN', COLORS[Status.DOWN]), '. Please make sure the cable is connected!', - ) \ No newline at end of file + ) + +@popup_maker(Status.MISCONFIGURED) +@component +def _popup_misconfigured(details: 'cell.CellDetails'): + # TODO: Improve the styling on the button in this component + def quick_fix(_): + # TODO: Reconfigure the DB + ... + + return html.div( + { + 'style': { + 'width': POPUP_WIDTH, + }, + }, + # TODO: Fix this message + f'Device #100 is in this cell, but is configured to cell A-1.\n', + f'Click the button below to move it to {details.cell_id}\n', + PopupButton('Quick Fix', quick_fix), + ) diff --git a/widgets/tooltip.py b/widgets/tooltip.py index 1f7ef8e..fe582e0 100644 --- a/widgets/tooltip.py +++ b/widgets/tooltip.py @@ -1,3 +1,4 @@ +from typing import Callable from reactpy import html, component from reactpy.types import Component @@ -19,3 +20,18 @@ def Tooltip(tooltip_content: Component, class_name=TOOLTIP): tooltip_content ) ) + + +@component +def PopupButton(text: str, onclick: Callable, override_style=None): + style = {} + if override_style: + style.update(override_style) + return html.span( + { + 'class_name': 'vl-popup-button', + 'onclick': onclick, + 'style': style, + }, + f' {text} ' + )