Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
31 changes: 5 additions & 26 deletions src/e3sm_quickview/app.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import asyncio
import datetime
import json
import math
import os
import time
from functools import partial
Expand Down Expand Up @@ -221,11 +220,12 @@ def _build_ui(self, **_):

with v3.VContainer(classes="h-100 pa-0", fluid=True):
with client.SizeObserver("main_size"):
# Take space to push content below the fixed overlay
html.Div(style=("`height: ${top_padding}px`",))

# Fixed overlay for toolbars
# Sticky toolbar overlay
with html.Div(style=css.TOOLBARS_FIXED_OVERLAY):
client.SizeObserver(
"toolbar_size",
style="position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;",
)
toolbars.Layout(
apply_size=self.view_manager.apply_size,
zoom=self.view_manager.zoom,
Expand Down Expand Up @@ -611,27 +611,6 @@ async def _on_projection(self, projection, **_):
await asyncio.sleep(0.1)
self.view_manager.reset_camera()

@change("active_tools", "available_animation_tracks")
def _on_toolbar_change(self, active_tools, **_):
top_padding = 0
for name in active_tools:
if name == "select-slice-time":
track_count = len(self.state.available_animation_tracks or [])
rows_needed = 1
if track_count > 3:
if track_count % 3 == 0 or (track_count + 1) % 3 == 0:
rows_needed = math.ceil(track_count / 3)
elif track_count % 2 == 0:
rows_needed = track_count / 2
else:
rows_needed = math.ceil(track_count / 3)

top_padding += 70 * rows_needed
else:
top_padding += toolbars.SIZES.get(name, 0)

self.state.top_padding = top_padding

def _on_slicing_change(self, var, ind_var, **_):
with perf.timed(f"tick.{var}={self.state[ind_var]}.total"):
with perf.timed("tick.pipeline"):
Expand Down
2 changes: 1 addition & 1 deletion src/e3sm_quickview/components/css.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
)

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


Expand Down
213 changes: 95 additions & 118 deletions src/e3sm_quickview/components/toolbars.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,35 +7,6 @@

from e3sm_quickview.utils import js

DENSITY = {
"adjust-layout": "compact",
"adjust-databounds": "default",
"select-slice-time": "default",
"animation-controls": "compact",
}

SIZES = {
"adjust-layout": 49,
"adjust-databounds": 65,
"select-slice-time": 70,
"animation-controls": 49,
}

VALUES = list(DENSITY.keys())

DEFAULT_STYLES = {
"color": "white",
"classes": "border-b-thin",
}


def to_kwargs(value):
return {
"v_show": js.is_active(value),
"density": DENSITY[value],
**DEFAULT_STYLES,
}


class Layout(v3.VToolbar):
def __init__(
Expand All @@ -45,7 +16,12 @@ def __init__(
pan=None,
reset_camera=None,
):
super().__init__(**to_kwargs("adjust-layout"))
super().__init__(
v_show=js.is_active("adjust-layout"),
density="compact",
color="white",
classes="border-b-thin",
)

self.state.setdefault("show_zoom_controls", False)
self.state.setdefault("show_pan_controls", False)
Expand Down Expand Up @@ -311,7 +287,12 @@ def __init__(

class Cropping(v3.VToolbar):
def __init__(self):
super().__init__(**to_kwargs("adjust-databounds"))
super().__init__(
v_show=js.is_active("adjust-databounds"),
density="default",
color="white",
classes="border-b-thin",
)

with self:
with v3.VTooltip(
Expand Down Expand Up @@ -439,110 +420,101 @@ def _on_crop_lat(self, crop_latitude_min, crop_latitude_max, **_):

class DataSelection(html.Div):
def __init__(self):
style = to_kwargs("select-slice-time")
# Use style instead of d-flex class to avoid !important override of v-show
# Add background color to match VToolbar appearance
style["style"] = (
"display: flex; align-items: center; background: rgb(var(--v-theme-surface));"
super().__init__(
v_show=js.is_active("select-slice-time"),
classes="border-b-thin",
style="display: flex; align-items: center; background: rgb(var(--v-theme-surface));",
)
super().__init__(**style)

self.state.setdefault("expanded_slice_track", None)

with self:
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",
)
v3.VIcon("mdi-tune-variant", classes="ml-3 mr-2 opacity-50")

with v3.VRow(
classes="ma-0 pr-2 flex-wrap flex-grow-1",
dense=True,
v_if=("slice_slider_edit", True),
with html.Div(
classes="d-flex align-center flex-wrap flex-grow-1 ga-1 py-1 pr-2"
):
# Debug: Show animation_tracks array
# html.Div(
# "Animation Tracks: {{ JSON.stringify(available_animation_tracks) }}",
# classes="col-12",
# )
# Each track gets a column (3 per row)
with v3.VCol(
cols=("utils.quickview.cols(available_animation_tracks.length)",),
with html.Template(
v_for="(track, idx) in available_animation_tracks",
key="idx",
classes="pa-2",
):
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.VLabel(
"{{track}}",
classes="text-subtitle-2",
)
v3.VSpacer()
v3.VLabel(
"{{ dim_units[track] ? parseFloat(t_values[t_idx]).toFixed(2) + ' ' + dim_units[track] : 'Index value: ' + t_idx }} (k={{ t_idx }})",
classes="text-body-2",
)
v3.VSlider(
model_value=("t_idx",),
update_modelValue=(
self.on_update_slider,
"[track, $event]",
# --- Per-variable group ---
with v3.VSheet(
classes="d-flex align-center rounded px-1 ga-1",
color=(
"expanded_slice_track === track ? 'grey-lighten-3' : 'grey-lighten-4'",
),
min=0,
# max=100,#("get(track.value).length - 1",),
max=("t_values.length - 1",),
step=1,
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",
):
# Toggle button with track name
v3.VBtn(
"{{ track }}",
v_tooltip_bottom="'Toggle ' + track + ' controls'",
flat=True,
control_variant="stacked",
style="max-width: 100px;",
reverse=True,
variant=(
"expanded_slice_track === track ? 'flat' : 'outlined'",
),
rounded=True,
click="expanded_slice_track = expanded_slice_track === track ? null : track",
color=(
"expanded_slice_track === track ? 'primary' : ''",
),
style=(
"'text-transform: none;' + (expanded_slice_track === track ? '' : ' background-color: white;')",
),
)
# Expanded controls
with html.Div(
v_if="expanded_slice_track === track",
classes="d-flex align-center ga-1",
style="height: 36px; overflow: visible;",
):
v3.VDivider(vertical=True, classes="mx-1")
# Text input
html.Input(
type="number",
value=("t_idx",),
min=[0],
max=["t_values ? t_values.length - 1 : 0"],
step=[1],
change=(
self.on_update_slider,
"[track, Number($event.target.value)]",
),
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;",
)
# Slider
v3.VSlider(
v_tooltip_bottom=(
"dim_units[track] ? parseFloat(t_values[t_idx]).toFixed(2) + ' ' + dim_units[track] : 'Index: ' + t_idx",
),
model_value=("t_idx",),
update_modelValue=(
self.on_update_slider,
"[track, $event]",
),
min=0,
max=("t_values.length - 1",),
step=1,
show_ticks="always",
hide_details=True,
density="compact",
style="min-width: 200px; max-width: 400px;",
)
# Index label (shown when collapsed)
v3.VLabel(
"{{track}}",
classes="text-subtitle-2 ml-2 mt-1",
v_if="expanded_slice_track !== track",
v_text="`(${t_idx})`",
classes="font-weight-bold",
)
# Value + units label
v3.VLabel(
"{{ dim_units[track] ? parseFloat(t_values[Number(t_idx)]).toFixed(2) + ' ' + dim_units[track] : 'Index value: ' + t_idx }}",
classes="text-body-2 text-no-wrap ml-2 mt-1",
v_if="dim_units[track] && isNaN(Number(dim_units[track]))",
v_text="`${parseFloat(t_values[t_idx]).toFixed(2)} ${dim_units[track]}`",
classes="font-italic text-medium-emphasis",
)

def on_update_slider(self, dimension, index, *_, **__):
Expand All @@ -552,7 +524,12 @@ def on_update_slider(self, dimension, index, *_, **__):

class Animation(v3.VToolbar):
def __init__(self):
super().__init__(**to_kwargs("animation-controls"))
super().__init__(
v_show=js.is_active("animation-controls"),
density="compact",
color="white",
classes="border-b-thin",
)
with self:
v3.VIcon(
"mdi-video",
Expand Down
2 changes: 1 addition & 1 deletion src/e3sm_quickview/view_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -894,7 +894,7 @@ def _build_ui(self):
with v3.VCard(
variant="tonal",
style=(
"active_layout !== 'auto_layout' ? `height: calc(100% - ${top_padding}px;` : 'overflow-hidden'",
"active_layout !== 'auto_layout' ? `height: calc(100% - ${toolbar_size?.size?.height || 0}px)` : 'overflow-hidden'",
),
tile=("active_layout !== 'auto_layout'",),
raw_attrs=[f'data-field-name="{self.variable_name}"'],
Expand Down
Loading