Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
46d40b8
increase timeout time
proy30 Aug 28, 2025
1b710ea
confirm number of lattice elements prior to testing lattice stats
proy30 Aug 28, 2025
4b52cd4
add screenshot capabilities
proy30 Aug 28, 2025
4ec14fd
save screenshot regardless of fail/pass
proy30 Aug 28, 2025
3196888
cleanup
proy30 Aug 28, 2025
a1954f9
use pytest.approx
proy30 Sep 9, 2025
fcbeb99
fix any race condition with adding lattice
proy30 Sep 9, 2025
6a694d0
fix any race condition with adding variable
proy30 Sep 9, 2025
5761435
increase timeout time for checking lattice parameters value
proy30 Sep 9, 2025
8a1a78a
set pytest approx values as global vars
proy30 Sep 9, 2025
eb47736
add artifacts to view dashboard screenshot
proy30 Sep 9, 2025
fc6672c
set a fixed screenshots directory
proy30 Sep 9, 2025
6f7c4c9
clean screenshot file naming
proy30 Sep 9, 2025
40ce745
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 9, 2025
7e19c8e
try a different approach to setting input with js
proy30 Sep 9, 2025
908ca12
expose lattice defaults in defaults handler tab
proy30 Aug 28, 2025
36600c2
add parameter name search bar
proy30 Aug 28, 2025
e574aaf
limit to 5 parameters per page
proy30 Aug 28, 2025
61be82e
clean up search bar
proy30 Aug 28, 2025
f67420c
improve ui of defaults_handler
proy30 Aug 28, 2025
e9aed19
improve ui
proy30 Aug 28, 2025
05ab827
add excluded list internally
proy30 Aug 28, 2025
54b1d10
move files to standardized ui.py/utils.py/components.py
proy30 Aug 28, 2025
c5c8560
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 28, 2025
9b969e0
add close/apply buttons, relocate reset button on footer and fix ui
proy30 Sep 9, 2025
bf62342
add in the functionality for the apply button
proy30 Sep 9, 2025
a703275
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 9, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,16 @@ jobs:
run: |
ctest --test-dir build --output-on-failure -E pytest.AMReX

- name: Upload dashboard screenshots
if: always()
uses: actions/upload-artifact@v4
with:
name: dashboard-screenshots-macos
path: |
build/**/pytest.ImpactX.dashboard/screenshots/**/*.png
build/**/screenshots/**/*.png
if-no-files-found: ignore

- name: run installed python module
run: |
cmake --build build --target pip_install
Expand Down
10 changes: 10 additions & 0 deletions .github/workflows/ubuntu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,16 @@ jobs:
run: |
ctest --test-dir build --output-on-failure --label-exclude slow

- name: Upload dashboard screenshots
if: always()
uses: actions/upload-artifact@v4
with:
name: dashboard-screenshots-ubuntu
path: |
build/**/pytest.ImpactX.dashboard/screenshots/**/*.png
build/**/screenshots/**/*.png
if-no-files-found: ignore

- name: run installed app
run: |
cmake --build build --target install
Expand Down
4 changes: 2 additions & 2 deletions src/python/impactx/dashboard/Input/components/navigation.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ def create_dialog_tabs(name: str, num_tabs: int, tab_names: list[str]) -> None:
card = vuetify.VCard()
with card:
with vuetify.VTabs(v_model=(f"{name}", 0)):
for tab_name in tab_names:
vuetify.VTab(tab_name)
for i, tab_name in enumerate(tab_names):
vuetify.VTab(tab_name, id=f"{name}_tab_{i}")
vuetify.VDivider()
return card

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from .ui import LatticeDefaultsHandler

__all__ = [
"LatticeDefaultsHandler",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
"""
This file is part of ImpactX

Copyright 2025 ImpactX contributors
Authors: Parthib Roy, Axel Huebl
License: BSD-3-Clause-LBNL
"""

from .... import ctrl, html, vuetify


def header_summary():
"""
Renders a small legend for match count below the search bar.
"""
vuetify.VIcon(
"mdi-checkbox-blank-circle",
size="x-small",
color="primary",
classes="mr-2",
)
html.Span(
"Matches: {{ lattice_defaults_filtered.length }} / {{ lattice_defaults.length }}",
id="lattice_defaults_search_summary",
classes="text-caption text-grey-darken-1",
aria_live="polite",
__properties=["aria-live"],
)


def text_field(**kwargs):
"""
Shared text field with common defaults for this view.
Distinct props can be passed via kwargs and will override defaults.
"""
defaults = dict(
variant="outlined",
density="compact",
hide_details=True,
style="min-width: 0;",
)
defaults.update(kwargs)
return vuetify.VTextField(**defaults)


def search_bar():
"""
Search input with match summary on a separate row for visual clarity.
"""
# Row 1: Search field (full width)
with vuetify.VRow(classes="align-start"):
with vuetify.VCol(cols=12, classes="pb-0"):
text_field(
v_model=("lattice_defaults_filter", ""),
label="Search parameters",
placeholder="e.g., nslice",
prepend_inner_icon="mdi-magnify",
clearable=True,
classes="text-body-2",
id="lattice_defaults_search",
aria_label="Search parameters",
aria_describedby="lattice_defaults_search_summary",
__properties=["aria-label", "aria-describedby"],
)

# Row 2: Match summary (subtle, full width)
with vuetify.VRow(classes="mt-n2 mb-1"):
with vuetify.VCol(cols=12, classes="d-flex align-center"):
header_summary()


def pagination():
"""
Pagination control bound to lattice defaults filter list.
"""
return vuetify.VPagination(
v_model=("lattice_defaults_page", 1),
length=("Math.max(1, Math.ceil(lattice_defaults_filtered.length / 5))",),
total_visible=7,
__properties=["length", "total_visible"],
density="comfortable",
)


def reset_button():
"""
Reset defaults button.
"""
return vuetify.VBtn(
"Reset Defaults",
id="reset_lattice_defaults",
color="primary",
click=ctrl.reset_lattice_defaults,
block=True,
)
220 changes: 220 additions & 0 deletions src/python/impactx/dashboard/Input/lattice/defaults_handler/ui.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
"""
This file is part of ImpactX

Copyright 2025 ImpactX contributors
Authors: Parthib Roy, Axel Huebl
License: BSD-3-Clause-LBNL
"""

from copy import deepcopy

from .... import ctrl, html, state, vuetify
from . import components
from . import utils as _utils

# Initialize applied and staged defaults
state.lattice_defaults_applied = _utils.build_initial_defaults_list()
state.lattice_defaults = deepcopy(state.lattice_defaults_applied)
state.is_only_default = len(state.lattice_defaults) == 1
state.lattice_defaults_filter = ""
state.lattice_defaults_page = 1
state.lattice_defaults_filtered = []
state.lattice_defaults_no_results = False
state.lattice_defaults_has_changes = False


def _sync_has_changes_flag() -> None:
"""
Set has_changes when staged edits differ from applied values.
"""
state.lattice_defaults_has_changes = (
state.lattice_defaults != state.lattice_defaults_applied
)
state.dirty("lattice_defaults_has_changes")


# -----------------------------------------------------------------------------
# State listeners and controllers
# -----------------------------------------------------------------------------


@state.change("lattice_defaults")
def _on_defaults_change(*_args, **_kwargs):
# Only update UI-related state; do not apply globally until "Apply" is clicked
_utils.update_delete_availability()
_utils.sync_filtered_defaults()


@state.change("lattice_defaults_filter")
def _on_filter_change(*_args, **_kwargs):
"""
Reset to the first page when filter changes and update filtered list
"""
state.lattice_defaults_page = 1
state.dirty("lattice_defaults_page")
_utils.sync_filtered_defaults()


@ctrl.add("update_lattice_default")
def _on_update_default(field_name: str, identifier, new_value) -> None:
"""
Update a field of a lattice default entry.
field_name: which field to update, e.g. "value"
identifier: either the row index (int) or the parameter name (str)
new_value: the value to assign
"""
# Resolve the index: use it directly if it's int, otherwise look up by name
if isinstance(identifier, int):
row_index = identifier
else:
row_index = next(
(
i
for i, row in enumerate(state.lattice_defaults)
if row.get("name") == identifier
),
None,
)
if row_index is None:
return

entry = state.lattice_defaults[row_index]
if field_name == "value":
entry["value"] = new_value

state.dirty("lattice_defaults")
_sync_has_changes_flag()


@ctrl.add("reset_lattice_defaults")
def _on_reset_defaults() -> None:
state.lattice_defaults = _utils.build_initial_defaults_list()
state.lattice_defaults_filter = ""
state.dirty("lattice_defaults")
_sync_has_changes_flag()


@ctrl.add("apply_lattice_defaults")
def _on_apply_defaults() -> None:
"""
Mark current overrides as applied. Recompute parameter map to ensure
downstream consumers see the latest values, then clear the dirty flag.
"""
# Persist staged edits as applied and recompute parameter map
state.lattice_defaults_applied = deepcopy(state.lattice_defaults)
_utils.apply_overrides_to_parameter_map()
state.lattice_defaults_has_changes = False
state.dirty("lattice_defaults_has_changes")


@state.change("lattice_configuration_dialog_settings")
def _on_dialog_visibility_change(lattice_configuration_dialog_settings, **_):
# On close/cancel, revert staged edits to last applied and clear dirty flag
if not lattice_configuration_dialog_settings:
state.lattice_defaults = deepcopy(state.lattice_defaults_applied)
_utils.sync_filtered_defaults()
state.lattice_defaults_has_changes = False
state.dirty("lattice_defaults", "lattice_defaults_has_changes")


class LatticeDefaultsHandler:
"""
UI entry point for editing lattice parameter default overrides.
"""

@staticmethod
def _build_initial_defaults_list():
return _utils.build_initial_defaults_list()

@staticmethod
def defaults_handler():
"""
Renders the Defaults tab using a Variables-like UI.
"""
if (
isinstance(state.lattice_defaults, list)
and len(state.lattice_defaults) == 1
and not (
state.lattice_defaults[0].get("name")
or state.lattice_defaults[0].get("value")
)
):
state.lattice_defaults = _utils.build_initial_defaults_list()
state.dirty("lattice_defaults")
_utils.sync_filtered_defaults()
elif not state.lattice_defaults_filtered:
_utils.sync_filtered_defaults()
with vuetify.VCardText(classes="py-1 pb-0"):
components.search_bar()
with vuetify.VCardText(classes="pt-1"):
with vuetify.VContainer(fluid=True):
with vuetify.VRow(
v_for=(
"(item, index) in lattice_defaults_filtered.slice((lattice_defaults_page - 1) * 5, (lattice_defaults_page) * 5)",
),
classes="align-center justify-center py-0",
):
with vuetify.VCol(cols=5, classes="pr-0"):
components.text_field(
placeholder="Parameter Name",
v_model=("item.name",),
id=("'default_name_' + (item.name || '')",),
background_color="grey lighten-4",
readonly=True,
clearable=False,
)
with vuetify.VCol(cols=1, classes="px-0 text-center"):
html.Span("=", classes="mx-0")
with vuetify.VCol(cols=4, classes="pl-0"):
components.text_field(
placeholder="Default Value",
v_model=("item.value",),
id=("'default_value_' + (item.name || '')",),
type="text",
background_color="grey lighten-4",
update_modelValue=(
ctrl.update_lattice_default,
"['value', item.name, $event]",
),
clearable=True,
)
vuetify.VAlert(
"No matches found",
type="info",
variant="tonal",
density="compact",
border=True,
classes="ma-2",
v_show=("lattice_defaults_no_results",),
)
with vuetify.VCardText(classes="pt-0 pb-2"):
# Pagination centered above the footer actions
with vuetify.VRow(classes="justify-center"):
with vuetify.VCol(cols="auto"):
components.pagination()

vuetify.VDivider(classes="my-2")

# Footer actions: Reset (left) and Close/Apply (right)
with vuetify.VRow(classes="align-center"):
with vuetify.VCol(cols=6):
vuetify.VBtn(
"Reset",
color="primary",
variant="tonal",
click=ctrl.reset_lattice_defaults,
)
with vuetify.VCol(cols=6, classes="d-flex justify-end"):
vuetify.VBtn(
"Close",
variant="text",
color="#00313C",
click="lattice_configuration_dialog_settings = false",
classes="mr-2",
)
vuetify.VBtn(
"Apply",
color="primary",
disabled=("!lattice_defaults_has_changes",),
click=ctrl.apply_lattice_defaults,
)
Loading
Loading