view libervia/web/pages/_browser/dialog.py @ 1589:7228fc3c4744

browser (dialog): be sure to remove dialog on `OK` and `Cancel` click + avoid side effects
author Goffi <goffi@goffi.org>
date Sun, 10 Dec 2023 11:00:44 +0100
parents e47c24204449
children 0a4433a343a3
line wrap: on
line source

"""manage common dialogs"""

from browser import document, window, timer, console as log
from template import Template

log.warning = log.warn


class CancelError(Exception):
    """Dialog is cancelled"""


class Confirm:

    def __init__(self, message, ok_label="", cancel_label="", ok_color="success"):
        self._tpl = Template("dialogs/confirm.html")
        self.message = message
        self.ok_label = ok_label
        assert ok_color in ("success", "danger")
        self.ok_color = ok_color
        self.cancel_label = cancel_label
        self._notif_elt = None
        self.reset()

    def reset(self):
        """Reset values of callbacks and notif element"""
        self._ok_cb = None
        self._cancel_cb = None
        self._reject_cb = None
        if self._notif_elt is not None:
            self._notif_elt.remove()
            self._notif_elt = None

    def _default_cancel_cb(self, evt, notif_elt):
        notif_elt.remove()

    def cancel(self):
        """Cancel the dialog, without calling any callback

        will raise a CancelError
        """
        if self._notif_elt is None:
            log.warning("calling cancel on an unshown dialog")
        else:
            self._notif_elt.remove()
            self._notif_elt = None
            if self._reject_cb is not None:
                self._reject_cb(CancelError)
            else:
                log.warning("no reject callback set")
        self.reset()

    def on_ok_click(self, evt):
        evt.preventDefault()
        evt.stopPropagation()
        assert self._ok_cb is not None
        self._ok_cb(evt, self._notif_elt)
        self.reset()

    def on_cancel_click(self, evt) -> None:
        evt.preventDefault()
        evt.stopPropagation()
        assert self._cancel_cb is not None
        self._cancel_cb(evt, self._notif_elt)
        self.reset()

    def show(self, ok_cb, cancel_cb=None, reject_cb=None):
        if cancel_cb is None:
            cancel_cb = self._default_cancel_cb
        self._ok_cb = ok_cb
        self._cancel_cb = cancel_cb
        self._reject_cb = reject_cb
        notif_elt = self._tpl.get_elt({
            "message": self.message,
            "ok_label": self.ok_label,
            "ok_color": self.ok_color,
            "cancel_label": self.cancel_label,
        })
        self._notif_elt = notif_elt

        document['notifs_area'] <= notif_elt
        timer.set_timeout(lambda: notif_elt.classList.add('state_appended'), 0)
        for ok_elt in notif_elt.select(".click_to_ok"):
            ok_elt.bind("click", self.on_ok_click)
        for ok_elt in notif_elt.select(".click_to_cancel"):
            ok_elt.bind("click", self.on_cancel_click)

    def _ashow_cb(self, evt, notif_elt, resolve_cb, confirmed):
        evt.stopPropagation()
        notif_elt.remove()
        resolve_cb(confirmed)

    async def ashow(self):
        return window.Promise.new(
            lambda resolve_cb, reject_cb:
            self.show(
                lambda evt, notif_elt: self._ashow_cb(evt, notif_elt, resolve_cb, True),
                lambda evt, notif_elt: self._ashow_cb(evt, notif_elt, resolve_cb, False),
                reject_cb
            )
        )


class Notification:

    def __init__(self):
        self._tpl = Template("dialogs/notification.html")

    def close(self, notif_elt):
        notif_elt.classList.remove('state_appended')
        notif_elt.bind("transitionend", lambda __: notif_elt.remove())

    def show(
        self,
        message: str,
        level: str = "info",
        delay: int = 5
    ) -> None:
        # we log in console error messages, may be useful
        if level in ("warning", "error"):
            print(f"[{level}] {message}")
        notif_elt = self._tpl.get_elt({
            "message": message,
            "level": level,
        })
        document["notifs_area"] <= notif_elt
        timer.set_timeout(lambda: notif_elt.classList.add('state_appended'), 0)
        timer.set_timeout(lambda: self.close(notif_elt), delay * 1000)
        for elt in notif_elt.select('.click_to_close'):
            elt.bind('click', lambda __: self.close(notif_elt))


class RetryNotification:
    def __init__(self, retry_cb):
        self._tpl = Template("dialogs/retry-notification.html")
        self.retry_cb = retry_cb
        self.counter = 0
        self.timer = None

    def retry(self, notif_elt):
        if self.timer is not None:
            timer.clear_interval(self.timer)
            self.timer = None
        notif_elt.classList.remove('state_appended')
        notif_elt.bind("transitionend", lambda __: notif_elt.remove())
        self.retry_cb()

    def update_counter(self, notif_elt):
        counter = notif_elt.select_one(".retry_counter")
        counter.text = str(self.counter)
        self.counter -= 1
        if self.counter < 0:
            self.retry(notif_elt)

    def show(
        self,
        message: str,
        level: str = "warning",
        delay: int = 5
    ) -> None:
        # we log in console error messages, may be useful
        if level == "error":
            log.error(message)
        elif level == "warning":
            log.warning(message)
        self.counter = delay
        notif_elt = self._tpl.get_elt({
            "message": message,
            "level": level,
        })
        self.update_counter(notif_elt)
        document["notifs_area"] <= notif_elt
        timer.set_timeout(lambda: notif_elt.classList.add('state_appended'), 0)
        self.timer = timer.set_interval(self.update_counter, 1000, notif_elt)
        for elt in notif_elt.select('.click_to_retry'):
            elt.bind('click', lambda __: self.retry(notif_elt))




notification = Notification()