import param
from ..reactive import ReactiveHTML
from ..util import classproperty
from .datamodel import _DATA_MODELS, construct_data_model
from .resources import bundled_files, CSS_URLS
class Notification(param.Parameterized):
background = param.Color(default=None)
duration = param.Integer(default=3000, constant=True)
icon = param.String(default=None)
message = param.String(default='', constant=True)
notification_area = param.Parameter(constant=True, precedence=-1)
notification_type = param.String(default=None, constant=True, label='type')
_destroyed = param.Boolean(default=False)
def destroy(self):
from .notebook import push_on_root
self._destroyed = True
for ref in self.notification_area._models:
push_on_root(ref)
class NotificationAreaBase(ReactiveHTML):
notifications = param.List(item_type=Notification)
position = param.Selector(default='bottom-right', objects=[
'bottom-right', 'bottom-left', 'bottom-center', 'top-left',
'top-right', 'top-center', 'center-center', 'center-left',
'center-right'])
_clear = param.Integer(default=0)
_notification_type = Notification
_template = """
${notifications}
"""
__abstract = True
def __init__(self, **params):
super().__init__(**params)
self._notification_watchers = {}
def send(self, message, duration=3000, type=None, background=None, icon=None):
"""
Sends a notification to the frontend.
"""
notification = self._notification_type(
message=message, duration=duration, notification_type=type,
notification_area=self, background=background, icon=icon
)
self._notification_watchers[notification] = (
notification.param.watch(self._remove_notification, '_destroyed')
)
self.notifications.append(notification)
self.param.trigger('notifications')
return notification
def error(self, message, duration=3000):
return self.send(message, duration, type='error')
def info(self, message, duration=3000):
return self.send(message, duration, type='info')
def success(self, message, duration=3000):
return self.send(message, duration, type='success')
def warning(self, message, duration=3000):
return self.send(message, duration, type='warning')
def clear(self):
self._clear += 1
self.notifications[:] = []
def _remove_notification(self, event):
if event.obj in self.notifications:
self.notifications.remove(event.obj)
event.obj.param.unwatch(self._notification_watchers.pop(event.obj))
class NotificationArea(NotificationAreaBase):
types = param.List(default=[
{'type': 'warning',
'background': '#ffc107',
'icon': {
'className': 'fas fa-exclamation-triangle',
'tagName': 'i',
'color': 'white'
}
},
{'type': 'info',
'background': '#007bff',
'icon': {
'className': 'fas fa-info-circle',
'tagName': 'i',
'color': 'white'
}
},
])
__javascript_raw__ = ["https://cdn.jsdelivr.net/npm/notyf@3/notyf.min.js"]
@classproperty
def __javascript__(cls):
return bundled_files(cls)
@classproperty
def __js_skip__(cls):
return {'Notyf': cls.__javascript__}
__js_require__ = {
"paths": {"notyf": __javascript_raw__[0][:-3]}
}
__css_raw__ = [
"https://cdn.jsdelivr.net/npm/notyf@3/notyf.min.css",
CSS_URLS['font-awesome']
]
@classproperty
def __css__(cls):
return bundled_files(cls, 'css')
_template = ""
_scripts = {
"render": """
var [y, x] = data.position.split('-')
state.toaster = new Notyf({
dismissible: true,
position: {x: x, y: y},
types: data.types
})
""",
"notifications": """
var notification = state.current || data.notifications[data.notifications.length-1]
if (notification._destroyed) {
return
}
var config = {
duration: notification.duration,
type: notification.notification_type,
message: notification.message
}
if (notification.background != null) {
config.background = notification.background;
}
if (notification.icon != null) {
config.icon = notification.icon;
}
var toast = state.toaster.open(config);
function destroy() {
if (state.current !== notification) {
notification._destroyed = true;
}
}
toast.on('dismiss', destroy)
if (notification.duration) {
setTimeout(destroy, notification.duration)
}
if (notification.properties === undefined)
return
view.connect(notification.properties._destroyed.change, function () {
state.toaster.dismiss(toast)
})
""",
"_clear": "state.toaster.dismissAll()",
"position": """
script('_clear');
script('render');
for (notification of data.notifications) {
state.current = notification;
script('notifications');
}
state.current = undefined
"""
}
@classmethod
def demo(cls):
"""
Generates a layout which allows demoing the component.
"""
from ..layout import Column
from ..widgets import Button, ColorPicker, NumberInput, Select, TextInput
msg = TextInput(name='Message', value='This is a message')
duration = NumberInput(name='Duration', value=0, end=10000)
ntype = Select(
name='Type', options=['info', 'warning', 'error', 'success', 'custom'],
value='info'
)
background = ColorPicker(name='Color', value='#000000')
button = Button(name='Notify')
notifications = cls()
button.js_on_click(
args={
'notifications': notifications, 'msg': msg, 'duration': duration,
'ntype': ntype, 'color': background
}, code="""
var config = {
message: msg.value,
duration: duration.value,
notification_type: ntype.value,
_destroyed: false
}
if (ntype.value === 'custom') {
config.background = color.color
}
console.log(config, ntype.value)
notifications.data.notifications.push(config)
notifications.data.properties.notifications.change.emit()
"""
)
return Column(msg, duration, ntype, background, button, notifications)
# Construct a DataModel for Notification
_DATA_MODELS[Notification] = construct_data_model(Notification)