Skip to content

Commit 214a566

Browse files
committed
refactor: slice selection toolbar with toggle groups
- Each variable is now a UI group with a toggle button showing the track name - When toggled, reveals editable textfield and slider controls - Groups wrap to new rows with flex-wrap when space is limited - Value label shows bold (t_idx) when collapsed, italic value+units always - Simplified top_padding calculation (no multi-row pre-computation needed) - Use default density for components, darker group outline, larger slider
1 parent a3cdee3 commit 214a566

4 files changed

Lines changed: 88 additions & 103 deletions

File tree

src/e3sm_quickview/app.py

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import asyncio
22
import datetime
33
import json
4-
import math
54
import os
65
import time
76
from functools import partial
@@ -221,11 +220,12 @@ def _build_ui(self, **_):
221220

222221
with v3.VContainer(classes="h-100 pa-0", fluid=True):
223222
with client.SizeObserver("main_size"):
224-
# Take space to push content below the fixed overlay
225-
html.Div(style=("`height: ${top_padding}px`",))
226-
227-
# Fixed overlay for toolbars
223+
# Sticky toolbar overlay
228224
with html.Div(style=css.TOOLBARS_FIXED_OVERLAY):
225+
client.SizeObserver(
226+
"toolbar_size",
227+
style="position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;",
228+
)
229229
toolbars.Layout(
230230
apply_size=self.view_manager.apply_size,
231231
zoom=self.view_manager.zoom,
@@ -613,22 +613,10 @@ async def _on_projection(self, projection, **_):
613613

614614
@change("active_tools", "available_animation_tracks")
615615
def _on_toolbar_change(self, active_tools, **_):
616+
# Initial estimate; client-side ResizeObserver will override with actual height
616617
top_padding = 0
617618
for name in active_tools:
618-
if name == "select-slice-time":
619-
track_count = len(self.state.available_animation_tracks or [])
620-
rows_needed = 1
621-
if track_count > 3:
622-
if track_count % 3 == 0 or (track_count + 1) % 3 == 0:
623-
rows_needed = math.ceil(track_count / 3)
624-
elif track_count % 2 == 0:
625-
rows_needed = track_count / 2
626-
else:
627-
rows_needed = math.ceil(track_count / 3)
628-
629-
top_padding += 70 * rows_needed
630-
else:
631-
top_padding += toolbars.SIZES.get(name, 0)
619+
top_padding += toolbars.SIZES.get(name, 0)
632620

633621
self.state.top_padding = top_padding
634622

src/e3sm_quickview/components/css.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
)
55

66
TOOLBARS_FIXED_OVERLAY = (
7-
"`position:fixed;top:0;width:${Math.floor(main_size?.size?.width || 0)}px;z-index:1;`",
7+
"`position:sticky;top:0;width:${Math.floor(main_size?.size?.width || 0)}px;z-index:1;background:rgb(var(--v-theme-surface));`",
88
)
99

1010

src/e3sm_quickview/components/toolbars.py

Lines changed: 79 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@
1010
DENSITY = {
1111
"adjust-layout": "compact",
1212
"adjust-databounds": "default",
13-
"select-slice-time": "default",
13+
"select-slice-time": "compact",
1414
"animation-controls": "compact",
1515
}
1616

1717
SIZES = {
1818
"adjust-layout": 49,
1919
"adjust-databounds": 65,
20-
"select-slice-time": 70,
20+
"select-slice-time": 49,
2121
"animation-controls": 49,
2222
}
2323

@@ -447,102 +447,99 @@ def __init__(self):
447447
)
448448
super().__init__(**style)
449449

450+
self.state.setdefault("expanded_slice_track", None)
451+
450452
with self:
451-
with v3.VTooltip(
452-
text=(
453-
"slice_slider_edit ? 'Toggle to text edit' : 'Toggle to slider edit'",
454-
),
455-
):
456-
with v3.Template(v_slot_activator="{ props }"):
457-
v3.VIcon(
458-
"mdi-tune-variant",
459-
v_bind="props",
460-
classes="ml-3 mr-2 opacity-50",
461-
click="slice_slider_edit = !slice_slider_edit",
462-
)
453+
v3.VIcon("mdi-tune-variant", classes="ml-3 mr-2 opacity-50")
463454

464-
with v3.VRow(
465-
classes="ma-0 pr-2 flex-wrap flex-grow-1",
466-
dense=True,
467-
v_if=("slice_slider_edit", True),
455+
with html.Div(
456+
classes="d-flex align-center flex-wrap flex-grow-1 ga-1 py-1 pr-2"
468457
):
469-
# Debug: Show animation_tracks array
470-
# html.Div(
471-
# "Animation Tracks: {{ JSON.stringify(available_animation_tracks) }}",
472-
# classes="col-12",
473-
# )
474-
# Each track gets a column (3 per row)
475-
with v3.VCol(
476-
cols=("utils.quickview.cols(available_animation_tracks.length)",),
458+
with html.Template(
477459
v_for="(track, idx) in available_animation_tracks",
478460
key="idx",
479-
classes="pa-2",
480461
):
481462
with client.Getter(name=("track",), value_name="t_values"):
482463
with client.Getter(
483464
name=("track + '_idx'",), value_name="t_idx"
484465
):
485-
with v3.VRow(classes="ma-0 align-center", dense=True):
486-
v3.VLabel(
487-
"{{track}}",
488-
classes="text-subtitle-2",
489-
)
490-
v3.VSpacer()
491-
v3.VLabel(
492-
"{{ dim_units[track] ? parseFloat(t_values[t_idx]).toFixed(2) + ' ' + dim_units[track] : 'Index value: ' + t_idx }} (k={{ t_idx }})",
493-
classes="text-body-2",
494-
)
495-
v3.VSlider(
496-
model_value=("t_idx",),
497-
update_modelValue=(
498-
self.on_update_slider,
499-
"[track, $event]",
466+
# --- Per-variable group ---
467+
with v3.VSheet(
468+
classes="d-flex align-center rounded px-1 ga-1",
469+
color=(
470+
"expanded_slice_track === track ? 'grey-lighten-3' : 'grey-lighten-4'",
500471
),
501-
min=0,
502-
# max=100,#("get(track.value).length - 1",),
503-
max=("t_values.length - 1",),
504-
step=1,
505-
density="compact",
506-
hide_details=True,
507-
)
508-
with v3.VRow(
509-
classes="ma-0 pl-6 pr-2 align-center ga-4",
510-
v_if="!slice_slider_edit",
511-
):
512-
with v3.VCol(
513-
v_for="(track, idx) in available_animation_tracks",
514-
key="idx",
515-
):
516-
with client.Getter(name=("track",), value_name="t_values"):
517-
with client.Getter(
518-
name=("track + '_idx'",), value_name="t_idx"
519-
):
520-
with v3.VRow(classes="ma-0 align-center", dense=True):
521-
v3.VNumberInput(
522-
model_value=("Number(t_idx)",),
523-
update_modelValue=(
524-
self.on_update_slider,
525-
"[track, Number($event)]",
526-
),
527-
key=("track + '_' + t_idx",),
528-
min=[0],
529-
max=["t_values ? t_values.length - 1 : 0"],
530-
step=[1],
531-
hide_details=True,
532-
density="comfortable",
533-
variant="plain",
472+
):
473+
# Toggle button with track name
474+
v3.VBtn(
475+
"{{ track }}",
476+
v_tooltip_bottom="'Toggle ' + track + ' controls'",
534477
flat=True,
535-
control_variant="stacked",
536-
style="max-width: 100px;",
537-
reverse=True,
478+
variant=(
479+
"expanded_slice_track === track ? 'flat' : 'outlined'",
480+
),
481+
rounded=True,
482+
click="expanded_slice_track = expanded_slice_track === track ? null : track",
483+
color=(
484+
"expanded_slice_track === track ? 'primary' : ''",
485+
),
486+
style=(
487+
"'text-transform: none;' + (expanded_slice_track === track ? '' : ' background-color: white;')",
488+
),
538489
)
490+
# Expanded controls
491+
with html.Div(
492+
v_if="expanded_slice_track === track",
493+
classes="d-flex align-center ga-1",
494+
style="height: 36px; overflow: visible;",
495+
):
496+
v3.VDivider(vertical=True, classes="mx-1")
497+
# Text input
498+
html.Input(
499+
type="number",
500+
value=("t_idx",),
501+
min=[0],
502+
max=["t_values ? t_values.length - 1 : 0"],
503+
step=[1],
504+
change=(
505+
self.on_update_slider,
506+
"[track, Number($event.target.value)]",
507+
),
508+
style="width: 60px; height: 28px; padding: 16px 4px; border: 1px solid #ccc; border-radius: 4px; font-size: 14px; box-sizing: border-box; text-align: right;",
509+
)
510+
# Slider
511+
v3.VSlider(
512+
v_tooltip_bottom=(
513+
"dim_units[track] ? parseFloat(t_values[t_idx]).toFixed(2) + ' ' + dim_units[track] : 'Index: ' + t_idx",
514+
),
515+
model_value=("t_idx",),
516+
update_modelValue=(
517+
self.on_update_slider,
518+
"[track, $event]",
519+
),
520+
min=0,
521+
max=("t_values.length - 1",),
522+
step=1,
523+
show_ticks="always",
524+
hide_details=True,
525+
density="compact",
526+
style="min-width: 200px; max-width: 400px;",
527+
)
528+
# Index label (shown when collapsed)
539529
v3.VLabel(
540-
"{{track}}",
541-
classes="text-subtitle-2 ml-2 mt-1",
530+
v_if="expanded_slice_track !== track",
531+
v_html="'<b>(' + t_idx + ')</b>'",
532+
style="opacity: 1; color: rgba(0,0,0,0.87);",
542533
)
534+
# Value + units label
543535
v3.VLabel(
544-
"{{ dim_units[track] ? parseFloat(t_values[Number(t_idx)]).toFixed(2) + ' ' + dim_units[track] : 'Index value: ' + t_idx }}",
545-
classes="text-body-2 text-no-wrap ml-2 mt-1",
536+
v_if=(
537+
"dim_units[track] && isNaN(Number(dim_units[track]))",
538+
),
539+
v_html=(
540+
"'<i style=\\x27opacity:0.5\\x27>' + parseFloat(t_values[t_idx]).toFixed(2) + ' ' + dim_units[track] + '</i>'",
541+
),
542+
style="opacity: 1; color: rgba(0,0,0,0.87);",
546543
)
547544

548545
def on_update_slider(self, dimension, index, *_, **__):

src/e3sm_quickview/view_manager.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -894,7 +894,7 @@ def _build_ui(self):
894894
with v3.VCard(
895895
variant="tonal",
896896
style=(
897-
"active_layout !== 'auto_layout' ? `height: calc(100% - ${top_padding}px;` : 'overflow-hidden'",
897+
"active_layout !== 'auto_layout' ? `height: calc(100% - ${toolbar_size?.size?.height || 0}px)` : 'overflow-hidden'",
898898
),
899899
tile=("active_layout !== 'auto_layout'",),
900900
raw_attrs=[f'data-field-name="{self.variable_name}"'],

0 commit comments

Comments
 (0)