From 323acd911c5c0a381078752332264b10c7864393 Mon Sep 17 00:00:00 2001 From: Patrick O'Leary Date: Tue, 14 Apr 2026 15:06:21 -0500 Subject: [PATCH 1/3] fix: add tooltips and improve toolbar UI - Add tooltips to file browser buttons - Add slider/text-input toggle to DataSelection toolbar - Style hotkey badges with rounded borders - Add primary outline to active drawer toggle buttons - Fix tooltips for color config card Resolves #44 --- src/e3sm_quickview/components/file_browser.py | 158 ++++++++++-------- src/e3sm_quickview/components/toolbars.py | 79 ++++++++- src/e3sm_quickview/components/tools.py | 5 +- src/e3sm_quickview/components/view.py | 20 ++- 4 files changed, 177 insertions(+), 85 deletions(-) diff --git a/src/e3sm_quickview/components/file_browser.py b/src/e3sm_quickview/components/file_browser.py index fac717a..a80772e 100644 --- a/src/e3sm_quickview/components/file_browser.py +++ b/src/e3sm_quickview/components/file_browser.py @@ -283,18 +283,24 @@ def ui(self): with v3.VCard(rounded="lg"): with v3.VCardTitle("File loading", classes="d-flex align-center px-3"): v3.VSpacer() - v3.VBtn( - icon="mdi-home", - variant="flat", - size="small", - click=self.goto_home, - ) - v3.VBtn( - icon="mdi-folder-upload-outline", - variant="flat", - size="small", - click=self.goto_parent, - ) + with v3.VTooltip(text="Go to launched directory"): + with v3.Template(v_slot_activator="{ props }"): + v3.VBtn( + v_bind="props", + icon="mdi-home", + variant="flat", + size="small", + click=self.goto_home, + ) + with v3.VTooltip(text="Go up a directory"): + with v3.Template(v_slot_activator="{ props }"): + v3.VBtn( + v_bind="props", + icon="mdi-folder-upload-outline", + variant="flat", + size="small", + click=self.goto_parent, + ) v3.VTextField( v_model=self.name("filter"), hide_details=True, @@ -386,59 +392,77 @@ def ui(self): v3.VDivider() with v3.VCardActions(classes="pa-3"): - v3.VBtn( - classes="text-none", - variant="tonal", - text="Simulation", - prepend_icon="mdi-database-plus", - disabled=( - f"{self.name('listing')}[{self.name('active')}]?.type !== 'file'", - ), - click=self.set_data_simulation, - ) - v3.VBtn( - classes="text-none", - text="Connectivity", - variant="tonal", - prepend_icon="mdi-vector-polyline-plus", - disabled=( - f"{self.name('listing')}[{self.name('active')}]?.type !== 'file'", - ), - click=self.set_data_connectivity, - ) - v3.VBtn( - classes="text-none", - text="Reset", - variant="tonal", - prepend_icon="mdi-close-octagon-outline", - click=f"{self.name('data_connectivity')}='';{self.name('data_simulation')}='';{self.name('error')}=false", - ) + with v3.VTooltip(text="Set selected file as simulation file"): + with v3.Template(v_slot_activator="{ props }"): + v3.VBtn( + v_bind="props", + classes="text-none", + variant="tonal", + text="Simulation", + prepend_icon="mdi-database-plus", + disabled=( + f"{self.name('listing')}[{self.name('active')}]?.type !== 'file'", + ), + click=self.set_data_simulation, + ) + with v3.VTooltip(text="Set selected file as connectivity file"): + with v3.Template(v_slot_activator="{ props }"): + v3.VBtn( + v_bind="props", + classes="text-none", + text="Connectivity", + variant="tonal", + prepend_icon="mdi-vector-polyline-plus", + disabled=( + f"{self.name('listing')}[{self.name('active')}]?.type !== 'file'", + ), + click=self.set_data_connectivity, + ) + with v3.VTooltip(text="Clear selected files"): + with v3.Template(v_slot_activator="{ props }"): + v3.VBtn( + v_bind="props", + classes="text-none", + text="Reset", + variant="tonal", + prepend_icon="mdi-close-octagon-outline", + click=f"{self.name('data_connectivity')}='';{self.name('data_simulation')}='';{self.name('error')}=false", + ) v3.VSpacer() - v3.VBtn( - border=True, - classes="text-none", - color="surface", - text="Cancel", - variant="flat", - click=self.cancel, - ) - v3.VBtn( - disabled=(f"!{self.name('is_state_file')}",), - loading=(self.name("state_loading"), False), - classes="text-none", - color="primary", - text="Import state file", - variant="flat", - click=self.import_state_file, - ) - v3.VBtn( - classes="text-none", - color=(f"{self.name('error')} ? 'error' : 'primary'",), - text="Load files", - variant="flat", - disabled=( - f"!{self.name('data_simulation')} || !{self.name('data_connectivity')} || {self.name('error')}", - ), - loading=(self.name("loading"), False), - click=self.load_data_files, - ) + with v3.VTooltip(text="Cancel file loading"): + with v3.Template(v_slot_activator="{ props }"): + v3.VBtn( + v_bind="props", + border=True, + classes="text-none", + color="surface", + text="Cancel", + variant="flat", + click=self.cancel, + ) + with v3.VTooltip(text="Import previous state file"): + with v3.Template(v_slot_activator="{ props }"): + v3.VBtn( + v_bind="props", + disabled=(f"!{self.name('is_state_file')}",), + loading=(self.name("state_loading"), False), + classes="text-none", + color="primary", + text="Import state file", + variant="flat", + click=self.import_state_file, + ) + with v3.VTooltip(text="Load simulation and connectivity files"): + with v3.Template(v_slot_activator="{ props }"): + v3.VBtn( + v_bind="props", + classes="text-none", + color=(f"{self.name('error')} ? 'error' : 'primary'",), + text="Load files", + variant="flat", + disabled=( + f"!{self.name('data_simulation')} || !{self.name('data_connectivity')} || {self.name('error')}", + ), + loading=(self.name("loading"), False), + click=self.load_data_files, + ) diff --git a/src/e3sm_quickview/components/toolbars.py b/src/e3sm_quickview/components/toolbars.py index abb6745..8e21fbc 100644 --- a/src/e3sm_quickview/components/toolbars.py +++ b/src/e3sm_quickview/components/toolbars.py @@ -191,11 +191,18 @@ def __init__(self): super().__init__(**to_kwargs("adjust-databounds")) with self: - v3.VIcon( - "mdi-web", - classes="pl-6 opacity-50", - click="crop_slider_edit = !crop_slider_edit", - ) + with v3.VTooltip( + text=( + "crop_slider_edit ? 'Toggle to text edit' : 'Toggle to slider edit'", + ), + ): + with v3.Template(v_slot_activator="{ props }"): + v3.VIcon( + "mdi-web", + v_bind="props", + classes="pl-6 opacity-50", + click="crop_slider_edit = !crop_slider_edit", + ) with v3.VRow( classes="ma-0 px-2 align-center", v_if=("crop_slider_edit", True) ): @@ -318,9 +325,24 @@ def __init__(self): super().__init__(**style) with self: - v3.VIcon("mdi-tune-variant", classes="ml-3 mr-2 opacity-50") + with v3.VTooltip( + text=( + "slice_slider_edit ? 'Toggle to text edit' : 'Toggle to slider edit'", + ), + ): + with v3.Template(v_slot_activator="{ props }"): + v3.VIcon( + "mdi-tune-variant", + v_bind="props", + classes="ml-3 mr-2 opacity-50", + click="slice_slider_edit = !slice_slider_edit", + ) - with v3.VRow(classes="ma-0 pr-2 flex-wrap flex-grow-1", dense=True): + with v3.VRow( + classes="ma-0 pr-2 flex-wrap flex-grow-1", + dense=True, + v_if=("slice_slider_edit", True), + ): # Debug: Show animation_tracks array # html.Div( # "Animation Tracks: {{ JSON.stringify(available_animation_tracks) }}", @@ -344,7 +366,7 @@ def __init__(self): ) v3.VSpacer() v3.VLabel( - "{{ parseFloat(t_values[t_idx]).toFixed(2) }} hPa (k={{ t_idx }})", + "{{ t_values ? parseFloat(t_values[t_idx]).toFixed(2) : '' }} hPa (k={{ t_idx }})", classes="text-body-2", ) v3.VSlider( @@ -360,10 +382,49 @@ def __init__(self): density="compact", hide_details=True, ) + with v3.VRow( + classes="ma-0 pl-6 pr-2 align-center ga-4", + v_if="!slice_slider_edit", + ): + with v3.VCol( + v_for="(track, idx) in available_animation_tracks", + key="idx", + ): + with client.Getter(name=("track",), value_name="t_values"): + with client.Getter( + name=("track + '_idx'",), value_name="t_idx" + ): + with v3.VRow(classes="ma-0 align-center", dense=True): + v3.VNumberInput( + model_value=("Number(t_idx)",), + update_modelValue=( + self.on_update_slider, + "[track, Number($event)]", + ), + key=("track + '_' + t_idx",), + min=0, + max=("t_values ? t_values.length - 1 : 0",), + step=[1], + hide_details=True, + density="comfortable", + variant="plain", + flat=True, + control_variant="stacked", + style="max-width: 100px;", + reverse=True, + ) + v3.VLabel( + "{{track}}", + classes="text-subtitle-2 ml-2 mt-1", + ) + v3.VLabel( + "{{ t_values ? parseFloat(t_values[Number(t_idx)]).toFixed(2) : '' }} hPa", + classes="text-body-2 text-no-wrap ml-2 mt-1", + ) def on_update_slider(self, dimension, index, *_, **__): with self.state: - self.state[f"{dimension}_idx"] = index + self.state[f"{dimension}_idx"] = int(index) class Animation(v3.VToolbar): diff --git a/src/e3sm_quickview/components/tools.py b/src/e3sm_quickview/components/tools.py index 5ed51f7..5beb2f8 100644 --- a/src/e3sm_quickview/components/tools.py +++ b/src/e3sm_quickview/components/tools.py @@ -68,7 +68,7 @@ def __init__(self, compact, title, icon, click, keybinding=None): keys=keybinding, variant="contained", inline=True, - classes="mt-n2", + classes="mt-n2 border-md border-grey-darken-1 border-opacity-100 rounded-lg", ) @@ -111,6 +111,7 @@ def __init__(self, compact, title, icon, value, disabled=None, keybinding=None): prepend_icon=icon, value=value, title=(f"{compact} ? null : '{title}'",), + active_class="border-primary border-md border-primary border-opacity-100", **add_on, ): if keybinding: @@ -119,7 +120,7 @@ def __init__(self, compact, title, icon, value, disabled=None, keybinding=None): keys=keybinding, variant="contained", inline=True, - classes="mt-n2", + classes="mt-n2 border-md border-grey-darken-1 border-opacity-100 rounded-lg", ) diff --git a/src/e3sm_quickview/components/view.py b/src/e3sm_quickview/components/view.py index 89cbfeb..1e778d5 100644 --- a/src/e3sm_quickview/components/view.py +++ b/src/e3sm_quickview/components/view.py @@ -126,31 +126,35 @@ def create_bottom_bar(config, update_color_preset): with html.Div(classes="d-flex align-center"): v3.VIconBtn( raw_attrs=[ - '''v-tooltip:bottom="config.color_blind ? 'Colorblind safe presets' : 'All color presets'"''' + '''v-tooltip:bottom="config.color_blind ? 'Toggle for all color presets' : 'Toggle for colorblind safe color presets'"''' ], icon=( "config.color_blind ? 'mdi-shield-check-outline' : 'mdi-palette'", ), click="config.color_blind = !config.color_blind", size="small", - text="Colorblind safe", + text=( + "config.color_blind ? 'Colorblind Safe' : 'All Colors'", + ), variant="text", ) v3.VIconBtn( raw_attrs=[ - '''v-tooltip:bottom="config.invert ? 'Inverted preset' : 'Normal preset'"''' + '''v-tooltip:bottom="config.invert ? 'Toggle to normal preset' : 'Toggle to inverted preset'"''' ], icon=( "config.invert ? 'mdi-invert-colors' : 'mdi-invert-colors-off'", ), click="config.invert = !config.invert", size="small", - text="Invert", + text=( + "config.invert ? 'Inverted Preset' : 'Normal Preset'", + ), variant="text", ) v3.VIconBtn( raw_attrs=[ - '''v-tooltip:bottom="config.use_log_scale ? 'Use log scale' : 'Use linear scale'"''' + '''v-tooltip:bottom="config.use_log_scale ? 'Toggle to linear scale' : 'Toggle to log scale'"''' ], icon=( "config.use_log_scale ? 'mdi-math-log' : 'mdi-stairs'", @@ -164,14 +168,16 @@ def create_bottom_bar(config, update_color_preset): ) v3.VIconBtn( raw_attrs=[ - '''v-tooltip:bottom="config.override_range ? 'Use custom range' : 'Use data range'"''' + '''v-tooltip:bottom="config.override_range ? 'Toggle to use data range' : 'Toggle to use custom range'"''' ], icon=( "config.override_range ? 'mdi-arrow-expand-horizontal' : 'mdi-pencil'", ), click="config.override_range = !config.override_range", size="small", - text="Use data range", + text=( + "config.override_range ? 'Custom Range' : 'Data Range'", + ), variant="text", ) From 25dd5b7f681fd716aa00fb56628267dcd69de827 Mon Sep 17 00:00:00 2001 From: Patrick O'Leary Date: Thu, 16 Apr 2026 05:49:13 -0500 Subject: [PATCH 2/3] fix: replace raw_attrs tooltips with v_tooltip_bottom and fix VNumberInput min/max syntax --- src/e3sm_quickview/components/toolbars.py | 4 ++-- src/e3sm_quickview/components/view.py | 24 +++++++++++------------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/e3sm_quickview/components/toolbars.py b/src/e3sm_quickview/components/toolbars.py index 8e21fbc..0b291f3 100644 --- a/src/e3sm_quickview/components/toolbars.py +++ b/src/e3sm_quickview/components/toolbars.py @@ -402,8 +402,8 @@ def __init__(self): "[track, Number($event)]", ), key=("track + '_' + t_idx",), - min=0, - max=("t_values ? t_values.length - 1 : 0",), + min=[0], + max=[("t_values ? t_values.length - 1 : 0")], step=[1], hide_details=True, density="comfortable", diff --git a/src/e3sm_quickview/components/view.py b/src/e3sm_quickview/components/view.py index 1e778d5..98fbd90 100644 --- a/src/e3sm_quickview/components/view.py +++ b/src/e3sm_quickview/components/view.py @@ -125,9 +125,9 @@ def create_bottom_bar(config, update_color_preset): with v3.VCardItem(classes="py-0 px-2"): with html.Div(classes="d-flex align-center"): v3.VIconBtn( - raw_attrs=[ - '''v-tooltip:bottom="config.color_blind ? 'Toggle for all color presets' : 'Toggle for colorblind safe color presets'"''' - ], + v_tooltip_bottom=( + "config.color_blind ? 'Toggle for all color presets' : 'Toggle for colorblind safe color presets'", + ), icon=( "config.color_blind ? 'mdi-shield-check-outline' : 'mdi-palette'", ), @@ -139,9 +139,9 @@ def create_bottom_bar(config, update_color_preset): variant="text", ) v3.VIconBtn( - raw_attrs=[ - '''v-tooltip:bottom="config.invert ? 'Toggle to normal preset' : 'Toggle to inverted preset'"''' - ], + v_tooltip_bottom=( + "config.invert ? 'Toggle to normal preset' : 'Toggle to inverted preset'", + ), icon=( "config.invert ? 'mdi-invert-colors' : 'mdi-invert-colors-off'", ), @@ -153,9 +153,9 @@ def create_bottom_bar(config, update_color_preset): variant="text", ) v3.VIconBtn( - raw_attrs=[ - '''v-tooltip:bottom="config.use_log_scale ? 'Toggle to linear scale' : 'Toggle to log scale'"''' - ], + v_tooltip_bottom=( + "config.use_log_scale ? 'Toggle to linear scale' : 'Toggle to log scale'" + ), icon=( "config.use_log_scale ? 'mdi-math-log' : 'mdi-stairs'", ), @@ -167,9 +167,9 @@ def create_bottom_bar(config, update_color_preset): variant="text", ) v3.VIconBtn( - raw_attrs=[ - '''v-tooltip:bottom="config.override_range ? 'Toggle to use data range' : 'Toggle to use custom range'"''' - ], + v_tooltip_bottom=( + "config.override_range ? 'Toggle to use data range' : 'Toggle to use custom range'", + ), icon=( "config.override_range ? 'mdi-arrow-expand-horizontal' : 'mdi-pencil'", ), From 15819a76d8c8276c85463c19f5c8b9ce32d4f6b5 Mon Sep 17 00:00:00 2001 From: Patrick O'Leary Date: Thu, 16 Apr 2026 06:53:10 -0500 Subject: [PATCH 3/3] fix: correct VNumberInput max expression syntax --- src/e3sm_quickview/components/toolbars.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/e3sm_quickview/components/toolbars.py b/src/e3sm_quickview/components/toolbars.py index 0b291f3..c5abcbf 100644 --- a/src/e3sm_quickview/components/toolbars.py +++ b/src/e3sm_quickview/components/toolbars.py @@ -403,7 +403,7 @@ def __init__(self): ), key=("track + '_' + t_idx",), min=[0], - max=[("t_values ? t_values.length - 1 : 0")], + max=["t_values ? t_values.length - 1 : 0"], step=[1], hide_details=True, density="comfortable",