diff --git a/src/python/impactx/dashboard/Input/__init__.py b/src/python/impactx/dashboard/Input/__init__.py index 67d1ec383..e480f4b16 100644 --- a/src/python/impactx/dashboard/Input/__init__.py +++ b/src/python/impactx/dashboard/Input/__init__.py @@ -1,11 +1,12 @@ -from .components import CardComponents, InputComponents, NavigationComponents +from .components import CardBase, CardComponents, InputComponents, NavigationComponents from .defaults import DashboardDefaults from .generalFunctions import generalFunctions __all__ = [ - "InputComponents", "CardComponents", "DashboardDefaults", - "NavigationComponents", "generalFunctions", + "InputComponents", + "NavigationComponents", + "CardBase", ] diff --git a/src/python/impactx/dashboard/Input/components.py b/src/python/impactx/dashboard/Input/components.py index e447a4690..9944348f1 100644 --- a/src/python/impactx/dashboard/Input/components.py +++ b/src/python/impactx/dashboard/Input/components.py @@ -9,7 +9,7 @@ from typing import Optional from .. import html, setup_server, vuetify -from .defaults import TooltipDefaults +from .defaults import TooltipDefaults, UIDefaults from .generalFunctions import generalFunctions server, state, ctrl = setup_server() @@ -18,6 +18,45 @@ state.documentation_url = "" +def clean_name(section_name): + return section_name.lower().replace(" ", "_") + + +class CardBase(UIDefaults): + HEADER_NAME = "Base Section" + + def __init__(self): + self.header = self.HEADER_NAME.lower().replace(" ", "_") + self.collapsable = (f"collapse_{self.header}_height",) + + def card(self): + """ + Creates UI content for a section. + """ + + self.init_dialog(self.HEADER_NAME, self.card_content) + self.card_content() + + def card_content(self): + raise NotImplementedError("Card must contain card_content.") + + @staticmethod + def init_dialog(section_name: str, content_callback) -> None: + """ + Renders the expansion dialog UI for the input sections card. + Only runs once, when the section's card is built. + """ + + section_name_cleaned = clean_name(section_name) + expand_state_name = f"expand_{section_name_cleaned}" + + setattr(state, expand_state_name, False) + + with vuetify.VDialog(v_model=(expand_state_name,), width="fit-content"): + with vuetify.VCard(): + content_callback() + + class CardComponents: """ Class contains staticmethods to build @@ -32,42 +71,97 @@ def input_header(section_name: str, additional_components=None) -> None: :param section_name: The name for the input section. """ - documentation_name = section_name.lower().replace(" ", "_") + section_name_cleaned = clean_name(section_name) + + def render_components(position: str): + if additional_components and position in additional_components: + additional_components[position]() + with vuetify.VCardTitle(section_name): vuetify.VSpacer() - if additional_components: - additional_components() - CardComponents.refresh_icon(documentation_name) - CardComponents.documentation_icon(documentation_name) + render_components("start") + CardComponents.refresh_icon(section_name_cleaned) + CardComponents.documentation_icon(section_name_cleaned) + CardComponents.collapse_button(section_name_cleaned) + CardComponents.expand_button(section_name_cleaned) + render_components("end") vuetify.VDivider() @staticmethod - def documentation_icon(section_name: str) -> vuetify.VIcon: + def documentation_icon(section_name: str) -> vuetify.VBtn: """ Takes user to input section's documentation. :param section_name: The name for the input section. """ - return vuetify.VIcon( - "mdi-information", + with vuetify.VBtn( style="color: #00313C;", click=lambda: generalFunctions.open_documentation(section_name), - ) + icon=True, + small=True, + ): + vuetify.VIcon( + "mdi-information", + ) @staticmethod - def refresh_icon(section_name: str) -> vuetify.VIcon: + def refresh_icon(section_name: str) -> vuetify.VBtn: """ Resets input values to default. :param section_name: The name for the input section. """ - return vuetify.VIcon( - "mdi-refresh", + with vuetify.VBtn( style="color: #00313C;", click=lambda: generalFunctions.reset_inputs(section_name), - ) + icon=True, + small=True, + ): + vuetify.VIcon("mdi-refresh") + + @staticmethod + def expand_button(section_name: str) -> vuetify.VBtn: + """ + A button which expands/closes the given card configuration. + + :param section_name: The name for the input section. + """ + + with vuetify.VBtn( + color="primary", + click=f"expand_{section_name} = !expand_{section_name}", + icon=True, + small=True, + ): + vuetify.VIcon( + v_text=(f"expand_{section_name} ? 'mdi-close' : 'mdi-arrow-expand'",) + ) + + @staticmethod + def collapse_button(section_name: str) -> vuetify.VBtn: + """ + A button which collapses the given cards inputs. + + :param section_name: The name for the input section. + """ + section_name_cleaned = clean_name(section_name) + collapsed_state_name = f"collapse_{section_name_cleaned}" + + setattr(state, collapsed_state_name, False) + + with vuetify.VBtn( + color="primary", + click=f"collapse_{section_name_cleaned} = !collapse_{section_name_cleaned}", + icon=True, + small=True, + ): + vuetify.VIcon( + v_text=( + f"collapse_{section_name_cleaned} ? 'mdi-chevron-down' : 'mdi-chevron-up'", + ) + ) class InputComponents: diff --git a/src/python/impactx/dashboard/Input/csrConfiguration/csrMain.py b/src/python/impactx/dashboard/Input/csrConfiguration/csrMain.py index 9eafafa9f..0adf8f230 100644 --- a/src/python/impactx/dashboard/Input/csrConfiguration/csrMain.py +++ b/src/python/impactx/dashboard/Input/csrConfiguration/csrMain.py @@ -7,28 +7,28 @@ """ from ... import setup_server, vuetify -from .. import CardComponents, InputComponents +from .. import CardBase, CardComponents, InputComponents server, state, ctrl = setup_server() -class csrConfiguration: - @staticmethod - def card(): - """ - Creates UI content for CSR. - """ +class csrConfiguration(CardBase): + HEADER_NAME = "CSR" - with vuetify.VCard(v_show="csr", style="width: 170px;"): - CardComponents.input_header("CSR") - with vuetify.VCardText(): - with vuetify.VRow(classes="my-0"): - with vuetify.VCol(classes="py-0"): + def __init__(self): + super().__init__() + + def card_content(self): + with vuetify.VCard(style=self.collapsable): + CardComponents.input_header(self.HEADER_NAME) + with vuetify.VCardText(**self.CARD_TEXT_OVERFLOW): + with vuetify.VRow(**self.ROW_STYLE): + with vuetify.VCol(): InputComponents.select( label="Particle Shape", ) - with vuetify.VRow(classes="my-0"): - with vuetify.VCol(classes="py-0"): + with vuetify.VRow(**self.ROW_STYLE): + with vuetify.VCol(): InputComponents.text_field( label="CSR Bins", ) diff --git a/src/python/impactx/dashboard/Input/defaults.py b/src/python/impactx/dashboard/Input/defaults.py index 5fcce5219..3bdb3f29b 100644 --- a/src/python/impactx/dashboard/Input/defaults.py +++ b/src/python/impactx/dashboard/Input/defaults.py @@ -8,14 +8,24 @@ from impactx.impactx_pybind import ImpactX, RefPart +from .. import setup_server from .defaults_helper import InputDefaultsHelper +server, state, ctrl = setup_server() + class DashboardDefaults: """ Defaults for input parameters in the ImpactX dashboard. """ + COLLAPSABLE_SECTIONS = [ + "collapse_input_parameters", + "collapse_csr", + "collapse_distribution_parameters", + "collapse_space_charge", + "collapse_lattice_configuration", + ] # ------------------------------------------------------------------------- # Inputs by section # ------------------------------------------------------------------------- @@ -149,3 +159,37 @@ class TooltipDefaults: TOOLTIP = InputDefaultsHelper.get_docstrings( [RefPart, ImpactX], DashboardDefaults.DEFAULT_VALUES ) + + +class ToolbarDefaults: + """ + Default styling and states for the toolbar + section in the ImpactX dashboard. + """ + + TOOLBAR_SIZE = 64 + FOOTER_SIZE = 8 + + +class UIDefaults: + """ + Default UI which the input cards reply on in the ImpactX dashboard. + """ + + ROW_STYLE = { + "dense": True, + } + + CARD_TEXT_OVERFLOW = { + "classes": "custom-scrollbar", + "style": { + "flex": "1", + "overflow-y": "auto", + "overflow-x": "auto", + }, + } + + CARD_STYLE = { + "display": "flex", + "flex-direction": "column", + } diff --git a/src/python/impactx/dashboard/Input/distributionParameters/distributionMain.py b/src/python/impactx/dashboard/Input/distributionParameters/distributionMain.py index ce2f1d9db..4913d83ce 100644 --- a/src/python/impactx/dashboard/Input/distributionParameters/distributionMain.py +++ b/src/python/impactx/dashboard/Input/distributionParameters/distributionMain.py @@ -13,7 +13,13 @@ from impactx import distribution from ... import setup_server, vuetify -from .. import CardComponents, DashboardDefaults, InputComponents, generalFunctions +from .. import ( + CardBase, + CardComponents, + DashboardDefaults, + InputComponents, + generalFunctions, +) from . import DistributionFunctions server, state, ctrl = setup_server() @@ -161,21 +167,24 @@ def on_distribution_parameter_change(parameter_name, parameter_value, parameter_ # ----------------------------------------------------------------------------- -class DistributionParameters: +class DistributionParameters(CardBase): """ User-Input section for beam distribution. """ - @staticmethod - def card(): + HEADER_NAME = "Distribution Parameters" + + def __init__(self): + super().__init__() + + def card_content(self): """ Creates UI content for beam distribution. """ - - with vuetify.VCard(style="width: 340px; height: 300px"): - CardComponents.input_header("Distribution Parameters") - with vuetify.VCardText(): - with vuetify.VRow(): + with vuetify.VCard(style=self.collapsable): + CardComponents.input_header(self.HEADER_NAME) + with vuetify.VCardText(**self.CARD_TEXT_OVERFLOW): + with vuetify.VRow(**self.ROW_STYLE): with vuetify.VCol(cols=6): InputComponents.select( label="Select Distribution", @@ -188,14 +197,15 @@ def card(): v_model_name="distribution_type", disabled=("distribution_type_disable",), ) - with vuetify.VRow(classes="my-2"): + with vuetify.VRow(**self.ROW_STYLE): for i in range(3): - with vuetify.VCol(cols=4, classes="py-0"): + with vuetify.VCol(cols=4): with vuetify.VRow( v_for="(parameter, index) in selected_distribution_parameters", v_if=f"index % 3 == {i}", + **self.ROW_STYLE, ): - with vuetify.VCol(classes="py-1"): + with vuetify.VCol(): vuetify.VTextField( label=("parameter.parameter_name",), v_model=("parameter.parameter_default_value",), diff --git a/src/python/impactx/dashboard/Input/inputParameters/inputMain.py b/src/python/impactx/dashboard/Input/inputParameters/inputMain.py index b6944b824..2bd23e080 100644 --- a/src/python/impactx/dashboard/Input/inputParameters/inputMain.py +++ b/src/python/impactx/dashboard/Input/inputParameters/inputMain.py @@ -7,74 +7,74 @@ """ from ... import setup_server, vuetify -from .. import CardComponents, InputComponents +from .. import CardBase, CardComponents, InputComponents from . import InputFunctions server, state, ctrl = setup_server() -class InputParameters: +class InputParameters(CardBase): """ User-Input section for beam properties. """ + HEADER_NAME = "Input Parameters" + + def __init__(self): + super().__init__() + @state.change("kin_energy_unit") def on_kin_energy_unit_change(**kwargs) -> None: if state.kin_energy_on_ui != 0: InputFunctions.update_kin_energy_sim_value() - def card(self): - """ - Creates UI content for beam properties. - """ - - with vuetify.VCard(style="width: 340px; height: 350px"): - CardComponents.input_header("Input Parameters") - with vuetify.VCardText(): - with vuetify.VRow(classes="py-2"): - with vuetify.VCol(cols=6, classes="py-0"): + def card_content(self): + with vuetify.VCard(style=self.collapsable): + CardComponents.input_header(self.HEADER_NAME) + with vuetify.VCardText(**self.CARD_TEXT_OVERFLOW): + with vuetify.VRow(**self.ROW_STYLE): + with vuetify.VCol(cols="auto"): vuetify.VCheckbox( label="Space Charge", v_model=("space_charge", False), dense=True, ) - with vuetify.VCol(cols=6, classes="py-0"): + with vuetify.VCol(cols="auto"): vuetify.VCheckbox( label="CSR", v_model=("csr", False), dense=True, ) - with vuetify.VRow(classes="my-2"): - with vuetify.VCol(cols=6, classes="py-0"): + with vuetify.VRow(**self.ROW_STYLE): + with vuetify.VCol(cols=6): InputComponents.text_field( label="Ref. Particle Charge", v_model_name="charge_qe", ) - with vuetify.VCol(cols=6, classes="py-0"): + with vuetify.VCol(cols=6): InputComponents.text_field( label="Ref. Particle Mass", v_model_name="mass_MeV", ) - with vuetify.VRow(classes="my-0"): - with vuetify.VCol(cols=12, classes="py-0"): + with vuetify.VRow(**self.ROW_STYLE): + with vuetify.VCol(cols=12): InputComponents.text_field( label="Number of Particles", v_model_name="npart", ) - with vuetify.VRow(classes="my-2"): - with vuetify.VCol(cols=8, classes="py-0"): + with vuetify.VRow(**self.ROW_STYLE): + with vuetify.VCol(cols=8): InputComponents.text_field( label="Kinetic Energy", v_model_name="kin_energy_on_ui", - classes="mr-2", ) - with vuetify.VCol(cols=4, classes="py-0"): + with vuetify.VCol(cols=4): InputComponents.select( label="Unit", v_model_name="kin_energy_unit", ) - with vuetify.VRow(classes="my-2"): - with vuetify.VCol(cols=12, classes="py-0"): + with vuetify.VRow(**self.ROW_STYLE): + with vuetify.VCol(cols=12): InputComponents.text_field( label="Bunch Charge", v_model_name="bunch_charge_C", diff --git a/src/python/impactx/dashboard/Input/latticeConfiguration/__init__.py b/src/python/impactx/dashboard/Input/latticeConfiguration/__init__.py index e69de29bb..bf3e0937e 100644 --- a/src/python/impactx/dashboard/Input/latticeConfiguration/__init__.py +++ b/src/python/impactx/dashboard/Input/latticeConfiguration/__init__.py @@ -0,0 +1,5 @@ +from .helper import LatticeConfigurationHelper + +__all__ = [ + "LatticeConfigurationHelper", +] diff --git a/src/python/impactx/dashboard/Input/latticeConfiguration/helper.py b/src/python/impactx/dashboard/Input/latticeConfiguration/helper.py new file mode 100644 index 000000000..249d974a1 --- /dev/null +++ b/src/python/impactx/dashboard/Input/latticeConfiguration/helper.py @@ -0,0 +1,86 @@ +from ... import setup_server, vuetify + +server, state, ctrl = setup_server() + + +class LatticeConfigurationHelper: + """ + Helper class to build the Lattice Configuration section of the dashboard + """ + + @staticmethod + def expand_configuration() -> vuetify.VBtn: + """ + A button which expands/closes the lattice configuration. + """ + + with vuetify.VBtn( + color="primary", + click="expand_configuration = !expand_configuration", + icon=True, + small=True, + ): + vuetify.VIcon( + v_text=("expand_configuration ? 'mdi-close' : 'mdi-arrow-expand'",), + ) + + @staticmethod + def settings() -> vuetify.VBtn: + """ + A button which opens the lattice configuration settings. + """ + + with vuetify.VBtn( + click="lattice_configuration_dialog_settings = true", + icon=True, + small=True, + ): + vuetify.VIcon("mdi-cog") + + @staticmethod + def move_element_up() -> vuetify.VBtn: + """ + A button which allows the dashboard user to + move a lattice element's index upward. + """ + + with vuetify.VBtn( + click=(ctrl.move_latticeElementIndex_up, "[index]"), + icon=True, + small=True, + ): + vuetify.VIcon( + "mdi-menu-up", + ) + + @staticmethod + def move_element_down() -> vuetify.VBtn: + """ + A button which allows the dashboard user to + move a lattice element's index downward. + """ + + with vuetify.VBtn( + click=(ctrl.move_latticeElementIndex_down, "[index]"), + icon=True, + small=True, + ): + vuetify.VIcon( + "mdi-menu-down", + ) + + @staticmethod + def delete_element() -> vuetify.VBtn: + """ + A button which allows the dashboard user to + move a lattice element's index downward. + """ + + with vuetify.VBtn( + click=(ctrl.deleteLatticeElement, "[index]"), + icon=True, + small=True, + ): + vuetify.VIcon( + "mdi-delete", + ) diff --git a/src/python/impactx/dashboard/Input/latticeConfiguration/latticeMain.py b/src/python/impactx/dashboard/Input/latticeConfiguration/latticeMain.py index 350ae6589..932c03232 100644 --- a/src/python/impactx/dashboard/Input/latticeConfiguration/latticeMain.py +++ b/src/python/impactx/dashboard/Input/latticeConfiguration/latticeMain.py @@ -9,7 +9,14 @@ from impactx import elements from ... import setup_server, vuetify -from .. import CardComponents, InputComponents, NavigationComponents, generalFunctions +from .. import ( + CardBase, + CardComponents, + InputComponents, + NavigationComponents, + generalFunctions, +) +from . import LatticeConfigurationHelper server, state, ctrl = setup_server() @@ -218,87 +225,82 @@ def update_default_value(parameter_name, new_value): # ----------------------------------------------------------------------------- -class LatticeConfiguration: - """ - User-Input section for configuring lattice elements. - """ - - @staticmethod - def card(): - """ - Creates UI content for lattice configuration. - """ +class LatticeConfiguration(CardBase): + HEADER_NAME = "Lattice Configuration" - with vuetify.VDialog(v_model=("showDialog", False)): - LatticeConfiguration.dialog_configuration_list() + def __init__(self): + super().__init__() + def init_settings_dialog(self): with vuetify.VDialog( v_model=("lattice_configuration_dialog_settings", False), width="500px" ): LatticeConfiguration.dialog_settings() - with vuetify.VCard(style="width: 696px;"): - CardComponents.input_header("Lattice Configuration") - with vuetify.VCardText(): - with vuetify.VRow(align="center", no_gutters=True): - with vuetify.VCol(cols=10): + def card_content(self): + self.init_settings_dialog() + with vuetify.VCard(style=self.collapsable): + CardComponents.input_header( + self.HEADER_NAME, + additional_components={ + "start": LatticeConfigurationHelper.settings, + }, + ) + with vuetify.VCardText(**self.CARD_TEXT_OVERFLOW): + with vuetify.VRow(**self.ROW_STYLE): + with vuetify.VCol(cols=True): vuetify.VCombobox( label="Select Accelerator Lattice", v_model=("selected_lattice", None), items=("listOfLatticeElements",), error_messages=("isSelectedLatticeListEmpty",), dense=True, - classes="mr-2 pt-6", ) with vuetify.VCol(cols="auto"): vuetify.VBtn( "ADD", color="primary", dense=True, - classes="mr-2", click=ctrl.add_latticeElement, ) + with vuetify.VRow( + **self.ROW_STYLE, + v_for="(latticeElement, index) in selected_lattice_list", + align="center", + style="flex-wrap: nowrap;", + ): with vuetify.VCol(cols="auto"): - vuetify.VIcon( - "mdi-cog", - click="lattice_configuration_dialog_settings = true", + LatticeConfigurationHelper.move_element_up() + LatticeConfigurationHelper.move_element_down() + LatticeConfigurationHelper.delete_element() + with vuetify.VCol(cols="auto"): + vuetify.VChip( + v_text=("latticeElement.name",), + dense=True, + classes="mr-2", + style="justify-content: center", + ) + with vuetify.VCol( + v_for="(parameter, parameterIndex) in latticeElement.parameters", + cols="auto", + classes="pa-2", + ): + vuetify.VTextField( + label=("parameter.parameter_name",), + v_model=("parameter.parameter_default_value",), + change=( + ctrl.updateLatticeElementParameters, + "[index, parameter.parameter_name, $event, parameter.parameter_type]", + ), + error_messages=("parameter.parameter_error_message",), + dense=True, + style="width: 100px;", ) - with vuetify.VRow(): - with vuetify.VCol(): - with vuetify.VCard( - style="height: 300px; width: 700px; overflow-y: auto;" - ): - with vuetify.VCardTitle( - "Elements", classes="text-subtitle-2 pa-3" - ): - vuetify.VSpacer() - vuetify.VIcon( - "mdi-arrow-expand", - color="primary", - click="showDialog = true", - ) - vuetify.VDivider() - LatticeConfiguration.configuration_list() # ----------------------------------------------------------------------------- # Dialogs # ----------------------------------------------------------------------------- - @staticmethod - def dialog_configuration_list(): - """ - Displays the configuration for lattice elements - shown in a dialog box. - """ - - with vuetify.VCard(): - with vuetify.VCardTitle("Elements", classes="headline d-flex align-center"): - vuetify.VSpacer() - with vuetify.VBtn(icon=True, click="showDialog = false"): - vuetify.VIcon("mdi-close") - vuetify.VDivider() - LatticeConfiguration.configuration_list() - @staticmethod def dialog_settings(): """ @@ -321,55 +323,3 @@ def dialog_settings(): "['nslice', $event]", ), ) - - # ----------------------------------------------------------------------------- - # lattice_configuration_lsit - # ----------------------------------------------------------------------------- - - @staticmethod - def configuration_list(): - """ - Displays the configuration for lattice elements. - """ - with vuetify.VContainer(fluid=True): - with vuetify.VRow( - v_for="(latticeElement, index) in selected_lattice_list", - align="center", - no_gutters=True, - style="min-width: 1500px;", - ): - with vuetify.VCol(cols="auto", classes="pa-2"): - vuetify.VIcon( - "mdi-menu-up", - click=(ctrl.move_latticeElementIndex_up, "[index]"), - ) - vuetify.VIcon( - "mdi-menu-down", - click=(ctrl.move_latticeElementIndex_down, "[index]"), - ) - vuetify.VIcon( - "mdi-delete", - click=(ctrl.deleteLatticeElement, "[index]"), - ) - vuetify.VChip( - v_text=("latticeElement.name",), - dense=True, - classes="mr-2", - style="justify-content: center", - ) - with vuetify.VCol( - v_for="(parameter, parameterIndex) in latticeElement.parameters", - cols="auto", - classes="pa-2", - ): - vuetify.VTextField( - label=("parameter.parameter_name",), - v_model=("parameter.parameter_default_value",), - change=( - ctrl.updateLatticeElementParameters, - "[index, parameter.parameter_name, $event, parameter.parameter_type]", - ), - error_messages=("parameter.parameter_error_message",), - dense=True, - style="width: 100px;", - ) diff --git a/src/python/impactx/dashboard/Input/shared.py b/src/python/impactx/dashboard/Input/shared.py index 18671e9e4..9a16886ee 100644 --- a/src/python/impactx/dashboard/Input/shared.py +++ b/src/python/impactx/dashboard/Input/shared.py @@ -47,3 +47,30 @@ def on_input_state_change(**_): setattr(state, state_name, converted_value) if state_name == "kin_energy_on_ui": InputParameters.on_kin_energy_unit_change() + + @ctrl.add("collapse_all_sections") + def on_collapse_all_sections_click(): + state.expand_all_sections = not state.expand_all_sections + for collapsable_section in DashboardDefaults.COLLAPSABLE_SECTIONS: + setattr(state, collapsable_section, state.expand_all_sections) + + @state.change(*DashboardDefaults.COLLAPSABLE_SECTIONS) + def on_collapsable_section_change(**kwargs): + max_height = "1000px" + min_height = "64px" + + state_changes = state.modified_keys & set( + DashboardDefaults.COLLAPSABLE_SECTIONS + ) + for state_name in state_changes: + new_height = min_height if getattr(state, state_name) else max_height + + setattr( + state, + f"{state_name}_height", + { + "max-height": new_height, + "overflow": "hidden", + "transition": "max-height 0.5s", + }, + ) diff --git a/src/python/impactx/dashboard/Input/space_charge_configuration/spaceChargeMain.py b/src/python/impactx/dashboard/Input/space_charge_configuration/spaceChargeMain.py index 4daa7545c..b6319751e 100644 --- a/src/python/impactx/dashboard/Input/space_charge_configuration/spaceChargeMain.py +++ b/src/python/impactx/dashboard/Input/space_charge_configuration/spaceChargeMain.py @@ -7,7 +7,13 @@ """ from ... import setup_server, vuetify -from .. import CardComponents, InputComponents, NavigationComponents, generalFunctions +from .. import ( + CardBase, + CardComponents, + InputComponents, + NavigationComponents, + generalFunctions, +) from . import SpaceChargeFunctions server, state, ctrl = setup_server() @@ -173,86 +179,67 @@ def multigrid_settings(): ) -class SpaceChargeConfiguration: - @staticmethod - def card(): - """ - Creates UI content for space charge configuration - """ +class SpaceChargeConfiguration(CardBase): + HEADER_NAME = "Space Charge" + + def __init__(self): + super().__init__() + def card_content(self): with vuetify.VDialog( v_model=("space_charge_dialog_settings", False), width="500px" ): SpaceChargeConfiguration.dialog_settings() - with vuetify.VCard(v_show="space_charge", style="width: 340px;"): + with vuetify.VCard(style=self.collapsable): CardComponents.input_header( - "Space Charge", additional_components=multigrid_settings + self.HEADER_NAME, additional_components={"start": multigrid_settings} ) - with vuetify.VCardText(): - with vuetify.VRow(classes="my-0"): - with vuetify.VCol(cols=5, classes="py-0"): + with vuetify.VCardText(**self.CARD_TEXT_OVERFLOW): + with vuetify.VRow(**self.ROW_STYLE): + with vuetify.VCol(cols=4): InputComponents.select( label="Poisson Solver", - hide_details=True, ) - with vuetify.VCol(cols=4, classes="py-0"): + with vuetify.VCol(cols=4): InputComponents.select( label="Particle Shape", ) - with vuetify.VCol(cols=3, classes="py-0"): + with vuetify.VCol(cols=4): InputComponents.select( label="Max Level", ) - with vuetify.VCol(classes="pa-0"): - vuetify.VListItemSubtitle( - "nCell", - classes="font-weight-bold black--text", - ) - with vuetify.VRow(classes="my-0"): - for direction in ["x", "y", "z"]: - with vuetify.VCol(cols=4, classes="py-0"): - InputComponents.text_field( - label="", - v_model_name=f"n_cell_{direction}", - prefix=f"{direction}:", - style="margin-top: -5px", - ) - with vuetify.VCol(classes="pa-0"): - vuetify.VListItemSubtitle( - "Blocking Factor", - classes="font-weight-bold black--text mt-2", - ) - with vuetify.VRow(classes="my-0"): - for direction in ["x", "y", "z"]: - with vuetify.VCol(cols=4, classes="py-0"): - InputComponents.text_field( - label="", - prefix=f"{direction}:", - v_model_name=f"blocking_factor_{direction}", - style="margin-top: -5px", - ) - with vuetify.VCol(classes="pa-0"): - vuetify.VListItemSubtitle( - "prob_relative", - classes="font-weight-bold black--text mt-2", - ) - with vuetify.VRow(classes="my-0"): - with vuetify.VCol( - v_for=("(field, index) in prob_relative_fields",), - classes="py-0", - ): - vuetify.VTextField( - placeholder=("val."), - v_model=("field.value",), - input=(ctrl.update_prob_relative, "[index, $event]"), - error_messages=("field.error_message",), - type="number", - step=("field.step",), - __properties=["step"], - dense=True, - style="margin-top: -5px", - ) + for field in ["n_cell", "blocking_factor"]: + with vuetify.VRow(**self.ROW_STYLE): + for direction in ["x", "y", "z"]: + with vuetify.VCol(cols=4): + InputComponents.text_field( + label=field if direction == "x" else "", + v_model_name=f"{field}_{direction}", + prefix=f"{direction}:", + ) + with vuetify.VRow(**self.ROW_STYLE): + for i in range(3): + with vuetify.VCol(cols=4): + with vuetify.VRow( + v_for="(field, index) in prob_relative_fields", + v_if=f"index % 3 == {i}", + **self.ROW_STYLE, + ): + with vuetify.VCol(): + vuetify.VTextField( + label=("index === 0 ? 'prob_relative' : ''",), + v_model=("field.value",), + input=( + ctrl.update_prob_relative, + "[index, $event]", + ), + error_messages=("field.error_message",), + type="number", + step=("field.step",), + __properties=["step"], + dense=True, + ) @staticmethod def dialog_settings(): diff --git a/src/python/impactx/dashboard/Input/style.css b/src/python/impactx/dashboard/Input/style.css new file mode 100644 index 000000000..8a987aeb1 --- /dev/null +++ b/src/python/impactx/dashboard/Input/style.css @@ -0,0 +1,4 @@ +.custom-scrollbar { + scrollbar-width: thin; + scrollbar-color: #807f7f #f0f0f0; +} diff --git a/src/python/impactx/dashboard/Toolbar/controls.py b/src/python/impactx/dashboard/Toolbar/controls.py index 7a00f9659..b21e2ae6b 100644 --- a/src/python/impactx/dashboard/Toolbar/controls.py +++ b/src/python/impactx/dashboard/Toolbar/controls.py @@ -23,6 +23,8 @@ state.import_file_error = False state.importing_file = False +state.expand_all_sections = False + class ToolbarImport: @state.change("import_file") @@ -81,6 +83,18 @@ def export_button() -> vuetify.VBtn: vuetify.VIcon("mdi-download", left=True, small=True) html.Span("Export") + @staticmethod + def collapse_all_sections_button(): + with vuetify.VBtn( + click=ctrl.collapse_all_sections, + color="primary", + icon=True, + small=True, + ): + vuetify.VIcon( + v_text=("expand_all_sections ? 'mdi-collapse-all' : 'mdi-expand-all'",) + ) + @staticmethod def import_button() -> None: """ @@ -141,6 +155,7 @@ def reset_inputs_button() -> vuetify.VBtn: click=ctrl.reset_all, outlined=True, small=True, + classes="mr-4", ): vuetify.VIcon("mdi-refresh", left=True) html.Span("Reset") @@ -218,6 +233,8 @@ def dashboard_toolbar(toolbar_name: str) -> None: InputToolbar.import_button() InputToolbar.export_button() InputToolbar.reset_inputs_button() + vuetify.VDivider(vertical=True, classes="mr-2") + InputToolbar.collapse_all_sections_button() elif toolbar_name == "run": (GeneralToolbar.dashboard_info(),) (vuetify.VSpacer(),) @@ -243,5 +260,6 @@ def dashboard_info() -> vuetify.VAlert: dense=True, dismissible=True, v_model=("show_dashboard_alert", True), - classes="mt-4", + classes="text-body-2 hidden-md-and-down", + style="width: 50vw; overflow: hidden; margin: auto;", ) diff --git a/src/python/impactx/dashboard/__main__.py b/src/python/impactx/dashboard/__main__.py index b66624ed7..67949c49a 100644 --- a/src/python/impactx/dashboard/__main__.py +++ b/src/python/impactx/dashboard/__main__.py @@ -28,29 +28,47 @@ server, state, ctrl = setup_server() +from pathlib import Path + +from trame.widgets import client + +CSS_FILE = Path(__file__).with_name("Input").joinpath("style.css") + from .Input.shared import SharedUtilities shared_utilities = SharedUtilities() inputParameters = InputParameters() +distribution = DistributionParameters() +lattice_config = LatticeConfiguration() +space_charge = SpaceChargeConfiguration() +csr = csrConfiguration() + +card_column_padding = {"classes": "pa-2"} +card_row_padding = {"classes": "ma-2"} +card_breakpoints = {"cols": 12, "lg": 6, "md": 12, "sm": 6} with RouterViewLayout(server, "/Input"): with vuetify.VContainer(fluid=True): with vuetify.VRow(): - with vuetify.VCol(cols="auto", classes="pa-2"): - with vuetify.VRow(no_gutters=True): - with vuetify.VCol(cols="auto", classes="pa-2"): + with vuetify.VCol(cols=12, md=6): + with vuetify.VRow(**card_row_padding): + with vuetify.VCol(**{**card_breakpoints, **card_column_padding}): inputParameters.card() - with vuetify.VCol(cols="auto", classes="pa-2"): - SpaceChargeConfiguration.card() - with vuetify.VCol(cols="auto", classes="pa-2"): - csrConfiguration.card() - with vuetify.VRow(no_gutters=True): - with vuetify.VCol(cols="auto", classes="pa-2"): - DistributionParameters.card() - with vuetify.VRow(no_gutters=True): - with vuetify.VCol(cols="auto", classes="pa-2"): - LatticeConfiguration.card() + with vuetify.VCol( + **{**card_breakpoints, **card_column_padding}, + v_show="space_charge", + ): + space_charge.card() + with vuetify.VCol(**{**card_breakpoints, **card_column_padding}): + distribution.card() + with vuetify.VCol( + **{**card_breakpoints, **card_column_padding}, v_show="csr" + ): + csr.card() + with vuetify.VRow(**card_row_padding): + with vuetify.VCol(cols=12, **card_column_padding): + lattice_config.card() with RouterViewLayout(server, "/Analyze"): with vuetify.VContainer(fluid=True): @@ -73,6 +91,9 @@ def application(): init_terminal() with SinglePageWithDrawerLayout(server) as layout: layout.title.hide() + with layout: + client.Style(CSS_FILE.read_text()) + with layout.toolbar: with vuetify.Template(v_if="$route.path == '/Analyze'"): GeneralToolbar.dashboard_toolbar("analyze")