Source code for deepcave.layouts.sidebar

# Copyright 2021-2024 The DeepCAVE Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

#  noqa: D400
"""
# SidebarLayout

This module defines layout for the sidebar.

Callbacks are registered and handled.

## Classes
    - SidebarLayout: Customize the Sidebar Layout.
"""

from typing import Dict, List, Tuple, Union

import dash_bootstrap_components as dbc
from dash import ALL, html
from dash.dependencies import Input, Output, State
from dash.development.base_component import Component
from dash_extensions.enrich import Trigger

from deepcave import app, queue
from deepcave.layouts import Layout
from deepcave.plugins import Plugin


[docs] class SidebarLayout(Layout): """ Customize the Sidebar Layout. Callbacks are registered and handled. Properties ---------- plugins : Dict[str, List[Plugin]] A dictionary of all categorized plugins. nav_points : Dict[str, List[Tuple[str, str, str]]] A dictionary with plugins attributes corresponding to their category. """ def __init__(self, categorized_plugins: Dict[str, List[Plugin]]) -> None: super().__init__() self.plugins = categorized_plugins nav_points: Dict[str, List[Tuple[str, str, str]]] = { category: [] for category in categorized_plugins } for category, plugins in categorized_plugins.items(): for plugin in plugins: nav_points[category].append((plugin.id, plugin.name, plugin.icon)) self.nav_points = nav_points
[docs] def register_callbacks(self) -> None: """Register the callbacks for the sidebar layout.""" # Update navigation items output = Output("navigation-items", "children") input = Input("on-page-load", "pathname") @app.callback(output, input) # type: ignore def update_navigation_items(pathname: str) -> List[Component]: """ Update the navigation items. Parameters ---------- pathname : str The pathname. Returns ------- List[Component] The navigation items. """ layouts = [] for category, points in self.nav_points.items(): layouts += [ html.H6( className="sidebar-heading d-flex justify-content-between " "align-items-center px-3 mt-4 mb-1 text-muted", children=[html.Span(category)], ) ] point_layouts = [] for id, name, icon in points: href = f"/plugins/{id}" point_layouts += [ html.Li( className="nav-item", children=[ html.A( [html.I(className=icon), name], className=f"nav-link {'active' if href in pathname else ''}", href=href, ) ], ) ] layouts += [html.Ul(className="nav flex-column", children=point_layouts)] return html.Div( className="position-sticky pt-3", children=[ html.Ul( className="nav flex-column", children=[ html.A( "General", className=f"nav-link {'active' if pathname == '/' else ''}", href="/", ), ], ), *layouts, ], ) # Callback to cancel jobs @app.callback( # Output('dropdown-container-output', 'children'), Input({"type": "cancel-job", "index": ALL}, "n_clicks"), State({"type": "cancel-job", "index": ALL}, "name"), ) def delete_job(n_clicks, job_ids): # type: ignore """Delete the job from the queue.""" for n_click, job_id in zip(n_clicks, job_ids): if n_click is not None: queue.delete_job(job_id) # Update queue information panel output = Output("queue-info", "children") @app.callback(output, Trigger("global-update", "n_intervals")) # type: ignore def update_queue_info() -> List[Component]: """Update the information of the queue.""" try: all_jobs = [ queue.get_finished_jobs(), queue.get_running_jobs(), queue.get_pending_jobs(), ] job_stati = ["[FINISHED]", "[RUNNING]", "[PENDING]"] collect = [] for jobs, status in zip(all_jobs, job_stati): for job in jobs: name = job.meta["display_name"] job_id = job.id link = job.meta["link"] collect += [(name, job_id, status, link)] items = [] for name, job_id, status, link in collect: items += [ html.Li( # className="nav-item", children=[ html.A( f"{status} {name}", href=link, style={ "width": "100%", "display": "inline-block", "vertical-align": "middle", }, ), html.Span( dbc.Button( "-", id={"type": "cancel-job", "index": name}, name=job_id, color="danger", size="sm", disabled=True if status == job_stati[0] else False, style={ "display": "inline-block", "padding": "0 0.4rem", }, className="ms-1", ), style={ "display": "table-cell", "vertical-align": "middle", }, ), ], className="nav-link", style={"display": "flex", "flex-direction": "row"}, ) ] if len(collect) > 0: return [ html.Hr(), html.H6( className="sidebar-heading d-flex justify-content-between " "align-items-center px-3 mt-4 mb-1 text-muted", children=[html.Span("Queue Information")], ), html.Ul(className="nav flex-column", children=items), ] return [] except Exception: return []
[docs] def __call__(self) -> Union[List[Component], Component]: # noqa: D102 return html.Nav( className="col-md-3 col-lg-2 d-md-block sidebar collapse", id="sidebarMenu", children=[ html.Div(id="navigation-items"), html.Div(id="queue-info"), ], )