From 1acf6450391feba50c51f8922342ceab20340397 Mon Sep 17 00:00:00 2001 From: Min-Hsueh Chiu Date: Wed, 18 Mar 2026 17:05:37 -0700 Subject: [PATCH 1/2] add ErrorMessage --- crystal_toolkit/components/__init__.py | 1 + crystal_toolkit/components/error_msg.py | 286 ++++++++++++++++++++++++ crystal_toolkit/components/pourbaix.py | 60 +++-- crystal_toolkit/core/plugin.py | 1 + 4 files changed, 333 insertions(+), 15 deletions(-) create mode 100644 crystal_toolkit/components/error_msg.py diff --git a/crystal_toolkit/components/__init__.py b/crystal_toolkit/components/__init__.py index 1b2cc6a6..6c5b35dc 100644 --- a/crystal_toolkit/components/__init__.py +++ b/crystal_toolkit/components/__init__.py @@ -6,6 +6,7 @@ ) from crystal_toolkit.components.diffraction import XRayDiffractionComponent from crystal_toolkit.components.diffraction_tem import TEMDiffractionComponent +from crystal_toolkit.components.error_msg import ErrorMessage from crystal_toolkit.components.fermi_surface import FermiSurfaceComponent from crystal_toolkit.components.localenv import LocalEnvironmentPanel from crystal_toolkit.components.phase_diagram import ( diff --git a/crystal_toolkit/components/error_msg.py b/crystal_toolkit/components/error_msg.py new file mode 100644 index 00000000..56337bb3 --- /dev/null +++ b/crystal_toolkit/components/error_msg.py @@ -0,0 +1,286 @@ +""" +Author: Sheng Pang + +Message Snake - A reusable Dash notification snackbar component. + +Provides fixed-position toast notifications callable from any page. +Supports fade-in/fade-out animations, auto-dismiss, and manual close. + +Usage: + from mp_web.layouts.message_snake.message_snake import message_snake, register_message_snake_callbacks + + # 1. Include in layout + notification = message_snake( + message="Operation completed successfully!", + msg_type="success", + position="bottom-right", + snake_id="my-toast", + ) + + # 2. Register callbacks (in generate_callbacks or app setup) + register_message_snake_callbacks(app, "my-toast") +""" + +from __future__ import annotations + +from dash import Input, Output, dcc, html + +from crystal_toolkit.core.mpcomponent import MPComponent + +# Bulma-inspired color scheme for notification types +_TYPE_COLORS = { + "info": { + "background": "hsl(217, 71%, 53%)", # Bulma $info / $link (#3273dc) + "color": "#ffffff", + }, + "warning": { + "background": "hsl(44, 100%, 47%)", # Bulma-inspired darker yellow/orange + "color": "#ffffff", + }, + "error": { + "background": "hsl(348, 86%, 61%)", # Bulma $danger (#f14668) + "color": "#ffffff", + }, + "success": { + "background": "hsl(153, 53%, 53%)", # Bulma $success (#48c78e) + "color": "#ffffff", + }, +} + +# Position presets: each maps to CSS properties +_POSITION_STYLES = { + "top": { + "top": "20px", + "left": "50%", + "transform": "translateX(-50%)", + }, + "bottom": { + "bottom": "20px", + "left": "50%", + "transform": "translateX(-50%)", + }, + "center": { + "top": "50%", + "left": "50%", + "transform": "translate(-50%, -50%)", + }, + "top-right": { + "top": "20px", + "right": "20px", + }, + "top-left": { + "top": "20px", + "left": "20px", + }, + "bottom-right": { + "bottom": "20px", + "right": "20px", + }, + "bottom-left": { + "bottom": "20px", + "left": "20px", + }, +} + +# Icons per message type (Font Awesome classes) +_TYPE_ICONS = { + "info": "fas fa-info-circle", + "warning": "fas fa-exclamation-triangle", + "error": "fas fa-times-circle", + "success": "fas fa-check-circle", +} + + +class ErrorMessage(MPComponent): + def __init__( + self, + message, + id, + msg_type, + position="bottom-right", + style=None, + show_icon=False, + min_width="280px", + max_width="420px", + z_index=9999, + auto_dismiss_ms=50000, + ): + """Create a fixed-position notification snackbar (message snake). + + Returns a Dash html.Div with fade-in animation, auto-dismiss timer, + and optional close button. Must call register_message_snake_callbacks() + to enable auto-dismiss and close functionality. + + Args: + message (str): The notification message to display. + id (str): Unique HTML id for the notification container. + msg_type (str): Notification type - 'info', 'warning', 'error', 'success'. + position (str): Fixed position on screen. One of: + 'top', 'bottom', 'center', 'top-right', 'top-left', + 'bottom-right', 'bottom-left'. + style (dict, optional): Additional CSS style overrides. + show_icon (bool): Whether to show a type-specific icon. + min_width (str): Minimum width of the notification. + max_width (str): Maximum width of the notification. + z_index (int): CSS z-index for layering. + auto_dismiss_ms (int): Auto-dismiss delay in milliseconds. Defaults to 50000 (50s). + + Returns: + html.Div: A wrapper containing the notification, CSS animation, and auto-dismiss timer. + """ + # super().__init__(id=id) + self.snake_id = id + print("aaaaa") + print(self.snake_id) + self.show_icon = show_icon + self.msg_type = msg_type + self.message = message + self.auto_dismiss_ms = auto_dismiss_ms + + # Resolve type colors + type_style = _TYPE_COLORS.get(msg_type, _TYPE_COLORS["info"]) + + # Resolve position + pos_style = _POSITION_STYLES.get(position, _POSITION_STYLES["bottom-right"]) + + # Build the notification style + self.notification_style = { + "position": "fixed", + "zIndex": z_index, + "minWidth": min_width, + "maxWidth": max_width, + "padding": "14px 20px", + "borderRadius": "6px", + "boxShadow": "0 4px 14px rgba(0, 0, 0, 0.2)", + "display": "flex", + "alignItems": "center", + "gap": "12px", + "fontFamily": "'Inter', 'Segoe UI', Roboto, Helvetica, Arial, sans-serif", + "fontSize": "18px", + "fontWeight": "500", + "lineHeight": "1.4", + # Visible on mount; fade-out handled by callback via transition + "opacity": "1", + "transition": "opacity 0.4s ease", + **type_style, + **pos_style, + } + + # Apply user style overrides + if style: + self.notification_style.update(style) + + @property + def _sub_layouts(self): + # Build inner content + children = [] + + # Icon + if self.show_icon: + icon_class = _TYPE_ICONS.get(self.msg_type, _TYPE_ICONS["info"]) + children.append( + html.I( + className=icon_class, + style={"fontSize": "18px", "flexShrink": "0"}, + ) + ) + + # Message text + children.append( + html.Span(self.message, id=f"{self.snake_id}-message", style={"flex": "1"}) + ) + + # Close button + children.append( + html.Button( + html.I(className="fas fa-times"), + className="delete", + style={ + "background": "transparent", + "border": "none", + "color": "inherit", + "cursor": "pointer", + "fontSize": "18px", + "padding": "0", + "marginLeft": "8px", + "opacity": "0.8", + "flexShrink": "0", + }, + id=f"{self.snake_id}-close", + n_clicks=0, + ) + ) + + # Notification div + notification_div = html.Div( + children=children, + id=f"{self.snake_id}-div", + style=self.notification_style, + ) + + # Auto-dismiss interval timer (fires once after auto_dismiss_ms) + interval = dcc.Interval( + id=f"{self.snake_id}-timer", + interval=self.auto_dismiss_ms, + n_intervals=0, + max_intervals=1, + ) + + return {"notification_div": notification_div, "interval": interval} + + def layout(self) -> html.Div: + sub_layouts = self._sub_layouts + return html.Div( + [ + sub_layouts["notification_div"], + sub_layouts["interval"], + ], + id=self.snake_id, + style={"display": "none"}, + ) + + def generate_callbacks(self, app, cache) -> None: + """Register auto-dismiss and close button callbacks for a message snake. + + Must be called once per snake_id during app setup (e.g., in generate_callbacks). + + Args: + app: The Dash app instance. + snake_id (str): The snake_id used when creating the message_snake. + """ + + @app.callback( + Output(self.snake_id, "style"), + Input(f"{self.snake_id}-close", "n_clicks"), + prevent_initial_call=True, + ) + def close_message(n_clicks): + print("what?") + return {"display": "none"} + + """ + @app.callback( + Output(self.id(self.snake_id), "style"), + Input(self.id(f"{self.snake_id}-timer"), "n_intervals"), + Input(self.id(f"{self.snake_id}-close"), "n_clicks"), + State(self.id(self.snake_id), "style"), + prevent_initial_call=True, + ) + def _dismiss_message_snake(n_intervals, n_clicks, current_style): + # Fade out and hide the message snake on timer or close click. + if not current_style: + raise PreventUpdate + + ctx = callback_context + if not ctx.triggered: + raise PreventUpdate + + # Apply fade-out: transition opacity to 0, then hide + new_style = {**current_style} + new_style["transition"] = "opacity 0.4s ease" + new_style["opacity"] = "0" + new_style["pointerEvents"] = "none" + # Override the fade-in animation so it does not reset + new_style["animation"] = "none" + return new_style + """ diff --git a/crystal_toolkit/components/pourbaix.py b/crystal_toolkit/components/pourbaix.py index b5b5429e..3f89a633 100644 --- a/crystal_toolkit/components/pourbaix.py +++ b/crystal_toolkit/components/pourbaix.py @@ -17,6 +17,7 @@ from shapely.geometry import Polygon import crystal_toolkit.helpers.layouts as ctl +from crystal_toolkit.components.error_msg import ErrorMessage from crystal_toolkit.core.mpcomponent import MPComponent try: @@ -443,6 +444,17 @@ def get_figure_div(self, figure=None): @property def _sub_layouts(self) -> dict[str, Component]: + invalid_comp_error = ErrorMessage( + "Invalid composition input!", + id=self.id("invalid-comp-alarm"), + msg_type="error", + ) + invalid_conc_error = ErrorMessage( + "Invalid concentration input!", + id=self.id("invalid-conc-alarm"), + msg_type="error", + ) + options = html.Div( [ self.get_bool_input( @@ -460,10 +472,11 @@ def _sub_layouts(self) -> dict[str, Component]: [ html.Div( [ - self.get_alarm_window( - self.id("invalid-comp-alarm"), - message="Illegal composition entry!", - ), + invalid_comp_error.layout(), + # self.get_alarm_window( + # self.id("invalid-comp-alarm"), + # message="Illegal composition entry!", + # ), html.Div( [ html.H5( @@ -521,10 +534,11 @@ def _sub_layouts(self) -> dict[str, Component]: ), html.Div( [ - self.get_alarm_window( - id=self.id("invalid-conc-alarm"), - message=f"Illegal concentration entry! Must be between {MIN_CONCENTRATION} and {MAX_CONCENTRATION} M", - ), + invalid_conc_error.layout(), + # self.get_alarm_window( + # id=self.id("invalid-conc-alarm"), + # message=f"Illegal concentration entry! Must be between {MIN_CONCENTRATION} and {MAX_CONCENTRATION} M", + # ), ], id=self.id("conc-panel"), style={"display": "none"}, @@ -794,8 +808,8 @@ def get_pourbaix_diagram(pourbaix_entries, **kwargs): @app.callback( Output(self.id("graph-panel"), "children"), - Output(self.id("invalid-comp-alarm"), "displayed"), - Output(self.id("invalid-conc-alarm"), "displayed"), + Output(self.id("invalid-comp-alarm"), "style"), + Output(self.id("invalid-conc-alarm"), "style"), Output(self.id("display-composition"), "children"), Input(self.id(), "data"), Input(self.id("display-composition"), "children"), @@ -832,7 +846,12 @@ def make_figure( if len(raw_comp_list) != len(elements): logger.error("Invalid composition input!") - return (self.get_figure_div(), True, False, "") + return ( + self.get_figure_div(), + {"display": "block"}, + {"display": "none"}, + "", + ) try: # avoid direct type casting because string inputs may raise errors comp_list = [float(t) for t in raw_comp_list] @@ -844,7 +863,12 @@ def make_figure( except Exception: logger.error("Invalid composition input!") - return (self.get_figure_div(), True, False, "") + return ( + self.get_figure_div(), + {"display": "block"}, + {"display": "none"}, + "", + ) kwargs = self.reconstruct_kwargs_from_state() @@ -873,8 +897,14 @@ def make_figure( for key, val in kwargs.items(): if "conc" in key: # keys are encoded like "conc-Ag" if val is None: + print("oooooooo") # if the input is out of pre-defined range, Input will get None - return (self.get_figure_div(), False, True, "") + return ( + self.get_figure_div(), + {"display": "none"}, + {"display": "block"}, + "", + ) el = key.split("-")[1] conc_dict[el] = val @@ -901,7 +931,7 @@ def make_figure( return ( self.get_figure_div(figure=figure), - False, - False, + {"display": "none"}, + {"display": "none"}, html.Small(f"Pourbaix composition set to {unicodeify(formula)}."), ) diff --git a/crystal_toolkit/core/plugin.py b/crystal_toolkit/core/plugin.py index 8aa34d2a..4a955a6f 100644 --- a/crystal_toolkit/core/plugin.py +++ b/crystal_toolkit/core/plugin.py @@ -100,6 +100,7 @@ def crystal_toolkit_layout(self, layout) -> html.Div: layout.children += stores_to_add for component in mpcomp_module.MPComponent._callbacks_to_generate: + print(component.__module__) component.generate_callbacks(self.app, self.cache) return layout From 35d49ca1b3fa1750b3f25271367ce7c5d5398b02 Mon Sep 17 00:00:00 2001 From: Min-Hsueh Chiu Date: Thu, 19 Mar 2026 17:50:05 -0700 Subject: [PATCH 2/2] ErrorMessageAIO update --- crystal_toolkit/components/__init__.py | 2 +- crystal_toolkit/components/error_msg.py | 165 ++++++++++++++---------- crystal_toolkit/components/pourbaix.py | 58 ++++----- crystal_toolkit/core/plugin.py | 1 - 4 files changed, 124 insertions(+), 102 deletions(-) diff --git a/crystal_toolkit/components/__init__.py b/crystal_toolkit/components/__init__.py index 6c5b35dc..e4137033 100644 --- a/crystal_toolkit/components/__init__.py +++ b/crystal_toolkit/components/__init__.py @@ -6,7 +6,7 @@ ) from crystal_toolkit.components.diffraction import XRayDiffractionComponent from crystal_toolkit.components.diffraction_tem import TEMDiffractionComponent -from crystal_toolkit.components.error_msg import ErrorMessage +from crystal_toolkit.components.error_msg import ErrorMessageAIO from crystal_toolkit.components.fermi_surface import FermiSurfaceComponent from crystal_toolkit.components.localenv import LocalEnvironmentPanel from crystal_toolkit.components.phase_diagram import ( diff --git a/crystal_toolkit/components/error_msg.py b/crystal_toolkit/components/error_msg.py index 56337bb3..7ecea6d2 100644 --- a/crystal_toolkit/components/error_msg.py +++ b/crystal_toolkit/components/error_msg.py @@ -1,5 +1,6 @@ """ Author: Sheng Pang +Modifier: Min-Hsueh Chiu Message Snake - A reusable Dash notification snackbar component. @@ -7,23 +8,25 @@ Supports fade-in/fade-out animations, auto-dismiss, and manual close. Usage: - from mp_web.layouts.message_snake.message_snake import message_snake, register_message_snake_callbacks + from crystal_toolkit.components.error_msg import ErrorMessageAIO # 1. Include in layout - notification = message_snake( - message="Operation completed successfully!", - msg_type="success", - position="bottom-right", - snake_id="my-toast", - ) + ErrorMessageAIO( + "Invalid composition input!", + aio_id=self.id("invalid-comp-alarm"), + msg_type="error", + ).layout(), - # 2. Register callbacks (in generate_callbacks or app setup) - register_message_snake_callbacks(app, "my-toast") + # 2. Add to callback: + Output(ErrorMessage.ids.visible(self.id("invalid-comp-alarm")), "data"), + # Return True to display the message, and False to hide it. + + Note: Do not need to register callbacks as using All-in-one pattern """ from __future__ import annotations -from dash import Input, Output, dcc, html +from dash import MATCH, Input, Output, callback, ctx, dcc, html from crystal_toolkit.core.mpcomponent import MPComponent @@ -91,11 +94,47 @@ } -class ErrorMessage(MPComponent): +class ErrorMessageAIO(MPComponent): + class ids: + wrapper = lambda aio_id: { + "component": "ErrorMessageAIO", + "subcomponents": "wrapper", + "aio_id": aio_id, + } + close_button = lambda aio_id: { + "component": "ErrorMessageAIO", + "subcomponents": "close_button", + "aio_id": aio_id, + } + message = lambda aio_id: { + "component": "ErrorMessageAIO", + "subcomponents": "message", + "aio_id": aio_id, + } + div = lambda aio_id: { + "component": "ErrorMessageAIO", + "subcomponents": "div", + "aio_id": aio_id, + } + timer = lambda aio_id: { + "component": "ErrorMessageAIO", + "subcomponents": "timer", + "aio_id": aio_id, + } + visible = lambda aio_id: { + "component": "ErrorMessageAIO", + "subcomponents": "visible", + "aio_id": aio_id, + } + + ids = ids + + # _callbacks_registered = False # no instance registry + def __init__( self, message, - id, + aio_id, msg_type, position="bottom-right", style=None, @@ -125,13 +164,9 @@ def __init__( z_index (int): CSS z-index for layering. auto_dismiss_ms (int): Auto-dismiss delay in milliseconds. Defaults to 50000 (50s). - Returns: - html.Div: A wrapper containing the notification, CSS animation, and auto-dismiss timer. """ - # super().__init__(id=id) - self.snake_id = id - print("aaaaa") - print(self.snake_id) + + self.snake_id = aio_id self.show_icon = show_icon self.msg_type = msg_type self.message = message @@ -187,7 +222,9 @@ def _sub_layouts(self): # Message text children.append( - html.Span(self.message, id=f"{self.snake_id}-message", style={"flex": "1"}) + html.Span( + self.message, id=self.ids.message(self.snake_id), style={"flex": "1"} + ) ) # Close button @@ -206,7 +243,7 @@ def _sub_layouts(self): "opacity": "0.8", "flexShrink": "0", }, - id=f"{self.snake_id}-close", + id=self.ids.close_button(self.snake_id), n_clicks=0, ) ) @@ -214,13 +251,13 @@ def _sub_layouts(self): # Notification div notification_div = html.Div( children=children, - id=f"{self.snake_id}-div", + id=self.ids.div(self.snake_id), style=self.notification_style, ) # Auto-dismiss interval timer (fires once after auto_dismiss_ms) interval = dcc.Interval( - id=f"{self.snake_id}-timer", + id=self.ids.timer(self.snake_id), interval=self.auto_dismiss_ms, n_intervals=0, max_intervals=1, @@ -232,55 +269,53 @@ def layout(self) -> html.Div: sub_layouts = self._sub_layouts return html.Div( [ + dcc.Store(id=self.ids.visible(self.snake_id), data=False), sub_layouts["notification_div"], sub_layouts["interval"], ], - id=self.snake_id, + id=self.ids.wrapper(self.snake_id), style={"display": "none"}, ) - def generate_callbacks(self, app, cache) -> None: - """Register auto-dismiss and close button callbacks for a message snake. - - Must be called once per snake_id during app setup (e.g., in generate_callbacks). - - Args: - app: The Dash app instance. - snake_id (str): The snake_id used when creating the message_snake. - """ + @callback( + Output(ids.wrapper(MATCH), "style"), + Input(ids.visible(MATCH), "data"), + Input(ids.close_button(MATCH), "n_clicks"), + prevent_initial_call=True, + ) + def sync_message(command_visible, close_clicks): + triggered = ctx.triggered_id - @app.callback( - Output(self.snake_id, "style"), - Input(f"{self.snake_id}-close", "n_clicks"), - prevent_initial_call=True, - ) - def close_message(n_clicks): - print("what?") + if ( + isinstance(triggered, dict) + and triggered.get("subcomponents") == "close_button" + ): return {"display": "none"} - """ - @app.callback( - Output(self.id(self.snake_id), "style"), - Input(self.id(f"{self.snake_id}-timer"), "n_intervals"), - Input(self.id(f"{self.snake_id}-close"), "n_clicks"), - State(self.id(self.snake_id), "style"), - prevent_initial_call=True, - ) - def _dismiss_message_snake(n_intervals, n_clicks, current_style): - # Fade out and hide the message snake on timer or close click. - if not current_style: - raise PreventUpdate - - ctx = callback_context - if not ctx.triggered: - raise PreventUpdate - - # Apply fade-out: transition opacity to 0, then hide - new_style = {**current_style} - new_style["transition"] = "opacity 0.4s ease" - new_style["opacity"] = "0" - new_style["pointerEvents"] = "none" - # Override the fade-in animation so it does not reset - new_style["animation"] = "none" - return new_style - """ + return {"display": "block"} if command_visible else {"display": "none"} + + """ + @callback( + Output(ids.wrapper(MATCH), "style"), + Input(ids.timer(MATCH), "n_intervals"), + Input(ids.close_button(MATCH), "n_clicks"), + State(ids.wrapper(MATCH), "style"), + prevent_initial_call=True, + ) + def _dismiss_message_snake(n_intervals, n_clicks, current_style): + # Fade out and hide the message snake on timer or close click. + if not current_style: + raise PreventUpdate + + if not ctx.triggered: + raise PreventUpdate + + # Apply fade-out: transition opacity to 0, then hide + new_style = {**current_style} + new_style["transition"] = "opacity 0.4s ease" + new_style["opacity"] = "0" + new_style["pointerEvents"] = "none" + # Override the fade-in animation so it does not reset + new_style["animation"] = "none" + return new_style + """ diff --git a/crystal_toolkit/components/pourbaix.py b/crystal_toolkit/components/pourbaix.py index 3f89a633..83601b71 100644 --- a/crystal_toolkit/components/pourbaix.py +++ b/crystal_toolkit/components/pourbaix.py @@ -17,7 +17,7 @@ from shapely.geometry import Polygon import crystal_toolkit.helpers.layouts as ctl -from crystal_toolkit.components.error_msg import ErrorMessage +from crystal_toolkit.components.error_msg import ErrorMessageAIO from crystal_toolkit.core.mpcomponent import MPComponent try: @@ -444,17 +444,6 @@ def get_figure_div(self, figure=None): @property def _sub_layouts(self) -> dict[str, Component]: - invalid_comp_error = ErrorMessage( - "Invalid composition input!", - id=self.id("invalid-comp-alarm"), - msg_type="error", - ) - invalid_conc_error = ErrorMessage( - "Invalid concentration input!", - id=self.id("invalid-conc-alarm"), - msg_type="error", - ) - options = html.Div( [ self.get_bool_input( @@ -470,13 +459,18 @@ def _sub_layouts(self) -> dict[str, Component]: ), html.Div( [ + ErrorMessageAIO( + "Invalid composition input!", + aio_id=self.id("invalid-comp-alarm"), + msg_type="error", + ).layout(), + ErrorMessageAIO( + "Invalid concentration input!", + aio_id=self.id("invalid-conc-alarm"), + msg_type="error", + ).layout(), html.Div( [ - invalid_comp_error.layout(), - # self.get_alarm_window( - # self.id("invalid-comp-alarm"), - # message="Illegal composition entry!", - # ), html.Div( [ html.H5( @@ -533,13 +527,7 @@ def _sub_layouts(self) -> dict[str, Component]: style={"display": "none"}, ), html.Div( - [ - invalid_conc_error.layout(), - # self.get_alarm_window( - # id=self.id("invalid-conc-alarm"), - # message=f"Illegal concentration entry! Must be between {MIN_CONCENTRATION} and {MAX_CONCENTRATION} M", - # ), - ], + [], id=self.id("conc-panel"), style={"display": "none"}, ), @@ -808,8 +796,8 @@ def get_pourbaix_diagram(pourbaix_entries, **kwargs): @app.callback( Output(self.id("graph-panel"), "children"), - Output(self.id("invalid-comp-alarm"), "style"), - Output(self.id("invalid-conc-alarm"), "style"), + Output(ErrorMessageAIO.ids.visible(self.id("invalid-comp-alarm")), "data"), + Output(ErrorMessageAIO.ids.visible(self.id("invalid-conc-alarm")), "data"), Output(self.id("display-composition"), "children"), Input(self.id(), "data"), Input(self.id("display-composition"), "children"), @@ -848,8 +836,8 @@ def make_figure( logger.error("Invalid composition input!") return ( self.get_figure_div(), - {"display": "block"}, - {"display": "none"}, + True, + False, "", ) try: @@ -865,8 +853,8 @@ def make_figure( logger.error("Invalid composition input!") return ( self.get_figure_div(), - {"display": "block"}, - {"display": "none"}, + True, + False, "", ) @@ -897,12 +885,12 @@ def make_figure( for key, val in kwargs.items(): if "conc" in key: # keys are encoded like "conc-Ag" if val is None: - print("oooooooo") + logger.error("Invalid concentration input!") # if the input is out of pre-defined range, Input will get None return ( self.get_figure_div(), - {"display": "none"}, - {"display": "block"}, + False, + True, "", ) @@ -931,7 +919,7 @@ def make_figure( return ( self.get_figure_div(figure=figure), - {"display": "none"}, - {"display": "none"}, + False, + False, html.Small(f"Pourbaix composition set to {unicodeify(formula)}."), ) diff --git a/crystal_toolkit/core/plugin.py b/crystal_toolkit/core/plugin.py index 4a955a6f..8aa34d2a 100644 --- a/crystal_toolkit/core/plugin.py +++ b/crystal_toolkit/core/plugin.py @@ -100,7 +100,6 @@ def crystal_toolkit_layout(self, layout) -> html.Div: layout.children += stores_to_add for component in mpcomp_module.MPComponent._callbacks_to_generate: - print(component.__module__) component.generate_callbacks(self.app, self.cache) return layout