Loading src/nova/trame/view/components/data_selector.py +10 −5 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ class DataSelector(datagrid.VGrid): extensions: Optional[List[str]] = None, prefix: str = "", select_strategy: str = "all", skip_init: bool = False, **kwargs: Any, ) -> None: """Constructor for DataSelector. Loading Loading @@ -64,6 +65,7 @@ class DataSelector(datagrid.VGrid): self._v_model = v_model self._v_model_name_in_state = v_model.split(".")[0] self._directory = directory self._extensions = extensions if extensions is not None else [] self._prefix = prefix self._select_strategy = select_strategy Loading @@ -79,14 +81,17 @@ class DataSelector(datagrid.VGrid): ).exec self._reset_state = client.JSEval(exec=f"{self._v_model} = []; {self._flush_state}").exec self.create_model(directory) if skip_init: return self.create_model() self.create_viewmodel() self.create_ui(**kwargs) def create_ui(self, **kwargs: Any) -> None: def create_ui(self, *args: Any, **kwargs: Any) -> None: with VBoxLayout(classes="nova-data-selector", height="100%") as self._layout: self._layout.directory_filter = None self._layout.filter = html.Div() with GridLayout(columns=2, classes="flex-1-0 h-0", valign="start"): if not self._prefix: Loading Loading @@ -162,9 +167,9 @@ class DataSelector(datagrid.VGrid): f"(+{{{{ {self._v_model}.length - 2 }}}} others)", v_if="index === 2", classes="text-caption" ) def create_model(self, directory: str) -> None: def create_model(self) -> None: state = DataSelectorState() self._model = DataSelectorModel(state, directory, self._extensions, self._prefix) self._model = DataSelectorModel(state, self._directory, self._extensions, self._prefix) def create_viewmodel(self) -> None: server = get_server(None, client_type="vue3") Loading src/nova/trame/view/components/ornl/neutron_data_selector.py +14 −110 Original line number Diff line number Diff line """View Implementation for DataSelector.""" from typing import Any, List, Optional, cast from typing import Any, List, Optional from warnings import warn from trame.app import get_server from trame.widgets import client, datagrid, html from trame.widgets import vuetify3 as vuetify from nova.mvvm.trame_binding import TrameBinding Loading @@ -13,15 +12,16 @@ from nova.trame.model.ornl.neutron_data_selector import ( NeutronDataSelectorModel, NeutronDataSelectorState, ) from nova.trame.view.layouts import GridLayout, VBoxLayout from nova.trame.view.layouts import GridLayout from nova.trame.view_model.ornl.neutron_data_selector import NeutronDataSelectorViewModel from ..data_selector import DataSelector from ..input_field import InputField vuetify.enable_lab() class NeutronDataSelector(datagrid.VGrid): class NeutronDataSelector(DataSelector): """Allows the user to select datafiles from an IPTS experiment.""" def __init__( Loading Loading @@ -65,45 +65,27 @@ class NeutronDataSelector(datagrid.VGrid): ------- None """ if "items" in kwargs: raise AttributeError("The items parameter is not allowed on DataSelector widget.") if "label" in kwargs: self._label = kwargs["label"] else: self._label = None super().__init__(v_model, "", extensions, prefix, select_strategy, skip_init=True, **kwargs) if facility and allow_custom_directories: warn("allow_custom_directories will be ignored since the facility parameter is set.", stacklevel=1) self._v_model = v_model self._v_model_name_in_state = v_model.split(".")[0] self._facility = facility self._instrument = instrument self._allow_custom_directories = allow_custom_directories self._extensions = extensions if extensions is not None else [] self._prefix = prefix self._select_strategy = select_strategy self._revogrid_id = f"nova__neutrondataselector_{self._next_id}_rv" self._state_name = f"nova__neutrondataselector_{self._next_id}_state" self._facilities_name = f"nova__neutrondataselector_{self._next_id}_facilities" self._instruments_name = f"nova__neutrondataselector_{self._next_id}_instruments" self._experiments_name = f"nova__neutrondataselector_{self._next_id}_experiments" self._directories_name = f"nova__neutrondataselector_{self._next_id}_directories" self._datafiles_name = f"nova__neutrondataselector_{self._next_id}_datafiles" self._flush_state = f"flushState('{self._v_model_name_in_state}');" self._reset_rv_grid = client.JSEval( exec=f"window.grid_manager.get('{self._revogrid_id}').updateCheckboxes()" ).exec self._reset_state = client.JSEval(exec=f"{self._v_model} = []; {self._flush_state}").exec self.create_model(facility, instrument) self.create_model() self.create_viewmodel() self.create_ui(facility, instrument, **kwargs) def create_ui(self, facility: str, instrument: str, **kwargs: Any) -> None: with VBoxLayout(classes="nova-data-selector", height="100%"): super().create_ui(**kwargs) with self._layout.filter: with GridLayout(columns=3): columns = 3 if facility == "": Loading @@ -128,91 +110,17 @@ class NeutronDataSelector(datagrid.VGrid): ) InputField(v_else=True, v_model=f"{self._state_name}.custom_directory", column_span=2) with GridLayout(columns=2, classes="flex-1-0 h-0", valign="start"): if not self._prefix: with html.Div(classes="d-flex flex-column h-100 overflow-hidden"): vuetify.VListSubheader("Available Directories", classes="flex-0-1 justify-center px-0") vuetify.VTreeview( v_if=(f"{self._directories_name}.length > 0",), activatable=True, active_strategy="single-independent", classes="flex-1-0 h-0 overflow-y-auto", fluid=True, item_value="path", items=(self._directories_name,), click_open=(self._vm.expand_directory, "[$event.path]"), update_activated=(self._vm.set_subdirectory, "$event"), ) vuetify.VListItem("No directories found", classes="flex-0-1 text-center", v_else=True) super().__init__( v_model=self._v_model, can_focus=False, columns=( "[{" " cellTemplate: (createElement, props) =>" f" window.grid_manager.get('{self._revogrid_id}').cellTemplate(createElement, props)," " columnTemplate: (createElement) =>" f" window.grid_manager.get('{self._revogrid_id}').columnTemplate(createElement)," " name: 'Available Datafiles'," " prop: 'title'," "}]", ), frame_size=10, hide_attribution=True, id=self._revogrid_id, readonly=True, stretch=True, source=(self._datafiles_name,), theme="compact", **kwargs, ) if self._label: self.label = self._label if "update_modelValue" not in kwargs: self.update_modelValue = self._flush_state # Sets up some JavaScript event handlers when the component is mounted. with self: client.ClientTriggers( mounted=( "window.grid_manager.add(" f" '{self._revogrid_id}'," f" '{self._v_model}'," f" '{self._datafiles_name}'," f" '{self._v_model_name_in_state}'" ")" ) ) with cast( vuetify.VSelect, InputField( v_model=self._v_model, classes="flex-0-1 nova-readonly", clearable=True, readonly=True, type="select", click_clear=self.reset, ), ): with vuetify.Template(raw_attrs=['v-slot:selection="{ item, index }"']): vuetify.VChip("{{ item.title.split('/').reverse()[0] }}", v_if="index < 2") html.Span( f"(+{{{{ {self._v_model}.length - 2 }}}} others)", v_if="index === 2", classes="text-caption" ) def create_model(self, facility: str, instrument: str) -> None: def create_model(self) -> None: state = NeutronDataSelectorState() self._model = NeutronDataSelectorModel( state, facility, instrument, self._extensions, self._prefix, self._allow_custom_directories self._model: NeutronDataSelectorModel = NeutronDataSelectorModel( state, self._facility, self._instrument, self._extensions, self._prefix, self._allow_custom_directories ) def create_viewmodel(self) -> None: server = get_server(None, client_type="vue3") binding = TrameBinding(server.state) self._vm = NeutronDataSelectorViewModel(self._model, binding) self._vm: NeutronDataSelectorViewModel = NeutronDataSelectorViewModel(self._model, binding) self._vm.state_bind.connect(self._state_name) self._vm.facilities_bind.connect(self._facilities_name) self._vm.instruments_bind.connect(self._instruments_name) Loading @@ -223,10 +131,6 @@ class NeutronDataSelector(datagrid.VGrid): self._vm.update_view() def reset(self, _: Any = None) -> None: self._reset_state() self._reset_rv_grid() def set_state( self, facility: Optional[str] = None, instrument: Optional[str] = None, experiment: Optional[str] = None ) -> None: Loading src/nova/trame/view_model/data_selector.py +5 −5 Original line number Diff line number Diff line Loading @@ -12,16 +12,13 @@ class DataSelectorViewModel: """Manages the view state of the DataSelector widget.""" def __init__(self, model: DataSelectorModel, binding: BindingInterface) -> None: self.model = model self.model: DataSelectorModel = model self.datafiles: List[Dict[str, Any]] = [] self.directories: List[Dict[str, Any]] = [] self.expanded: List[str] = [] self.state_bind = binding.new_bind(self.model.state) self.facilities_bind = binding.new_bind() self.instruments_bind = binding.new_bind() self.experiments_bind = binding.new_bind() self.state_bind = binding.new_bind(self.model.state, callback_after_update=self.on_state_updated) self.directories_bind = binding.new_bind() self.datafiles_bind = binding.new_bind() self.reset_bind = binding.new_bind() Loading Loading @@ -50,6 +47,9 @@ class DataSelectorViewModel: self.expanded.append(paths[-1]) self.directories_bind.update_in_view(self.directories) def on_state_updated(self, results: Dict[str, Any]) -> None: pass def set_subdirectory(self, subdirectory_path: str = "") -> None: self.model.set_subdirectory(subdirectory_path) self.update_view() Loading src/nova/trame/view_model/ornl/neutron_data_selector.py +6 −47 Original line number Diff line number Diff line """View model implementation for the DataSelector widget.""" import os from pathlib import Path from typing import Any, Dict, List, Optional from typing import Any, Dict, Optional from nova.mvvm.interface import BindingInterface from nova.trame.model.ornl.neutron_data_selector import NeutronDataSelectorModel from nova.trame.view_model.data_selector import DataSelectorViewModel class NeutronDataSelectorViewModel: class NeutronDataSelectorViewModel(DataSelectorViewModel): """Manages the view state of the DataSelector widget.""" def __init__(self, model: NeutronDataSelectorModel, binding: BindingInterface) -> None: self.model = model super().__init__(model, binding) self.model: NeutronDataSelectorModel = model self.datafiles: List[Dict[str, Any]] = [] self.directories: List[Dict[str, Any]] = [] self.expanded: List[str] = [] self.state_bind = binding.new_bind(self.model.state, callback_after_update=self.on_state_updated) self.facilities_bind = binding.new_bind() self.instruments_bind = binding.new_bind() self.experiments_bind = binding.new_bind() self.directories_bind = binding.new_bind() self.datafiles_bind = binding.new_bind() self.reset_bind = binding.new_bind() def expand_directory(self, paths: List[str]) -> None: if paths[-1] in self.expanded: return # Query for the new subdirectories to display in the view new_directories = self.model.get_directories(Path(paths[-1])) # Find the entry in the existing directories that corresponds to the directory to expand current_level: Dict[str, Any] = {} children: List[Dict[str, Any]] = self.directories for current_path in paths: if current_level: children = current_level["children"] for entry in children: if current_path == entry["path"]: current_level = entry break current_level["children"] = new_directories # Mark this directory as expanded and display the new content self.expanded.append(paths[-1]) self.directories_bind.update_in_view(self.directories) def set_subdirectory(self, subdirectory_path: str = "") -> None: self.model.set_subdirectory(subdirectory_path) self.update_view() def set_state(self, facility: Optional[str], instrument: Optional[str], experiment: Optional[str]) -> None: self.model.set_state(facility, instrument, experiment) Loading Loading @@ -80,13 +44,8 @@ class NeutronDataSelectorViewModel: self.update_view() def update_view(self) -> None: self.state_bind.update_in_view(self.model.state) self.facilities_bind.update_in_view(self.model.get_facilities()) self.instruments_bind.update_in_view(self.model.get_instruments()) self.experiments_bind.update_in_view(self.model.get_experiments()) self.directories_bind.update_in_view(self.directories) self.datafiles = [ {"path": datafile, "title": os.path.basename(datafile)} for datafile in self.model.get_datafiles() ] self.datafiles_bind.update_in_view(self.datafiles) super().update_view() Loading
src/nova/trame/view/components/data_selector.py +10 −5 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ class DataSelector(datagrid.VGrid): extensions: Optional[List[str]] = None, prefix: str = "", select_strategy: str = "all", skip_init: bool = False, **kwargs: Any, ) -> None: """Constructor for DataSelector. Loading Loading @@ -64,6 +65,7 @@ class DataSelector(datagrid.VGrid): self._v_model = v_model self._v_model_name_in_state = v_model.split(".")[0] self._directory = directory self._extensions = extensions if extensions is not None else [] self._prefix = prefix self._select_strategy = select_strategy Loading @@ -79,14 +81,17 @@ class DataSelector(datagrid.VGrid): ).exec self._reset_state = client.JSEval(exec=f"{self._v_model} = []; {self._flush_state}").exec self.create_model(directory) if skip_init: return self.create_model() self.create_viewmodel() self.create_ui(**kwargs) def create_ui(self, **kwargs: Any) -> None: def create_ui(self, *args: Any, **kwargs: Any) -> None: with VBoxLayout(classes="nova-data-selector", height="100%") as self._layout: self._layout.directory_filter = None self._layout.filter = html.Div() with GridLayout(columns=2, classes="flex-1-0 h-0", valign="start"): if not self._prefix: Loading Loading @@ -162,9 +167,9 @@ class DataSelector(datagrid.VGrid): f"(+{{{{ {self._v_model}.length - 2 }}}} others)", v_if="index === 2", classes="text-caption" ) def create_model(self, directory: str) -> None: def create_model(self) -> None: state = DataSelectorState() self._model = DataSelectorModel(state, directory, self._extensions, self._prefix) self._model = DataSelectorModel(state, self._directory, self._extensions, self._prefix) def create_viewmodel(self) -> None: server = get_server(None, client_type="vue3") Loading
src/nova/trame/view/components/ornl/neutron_data_selector.py +14 −110 Original line number Diff line number Diff line """View Implementation for DataSelector.""" from typing import Any, List, Optional, cast from typing import Any, List, Optional from warnings import warn from trame.app import get_server from trame.widgets import client, datagrid, html from trame.widgets import vuetify3 as vuetify from nova.mvvm.trame_binding import TrameBinding Loading @@ -13,15 +12,16 @@ from nova.trame.model.ornl.neutron_data_selector import ( NeutronDataSelectorModel, NeutronDataSelectorState, ) from nova.trame.view.layouts import GridLayout, VBoxLayout from nova.trame.view.layouts import GridLayout from nova.trame.view_model.ornl.neutron_data_selector import NeutronDataSelectorViewModel from ..data_selector import DataSelector from ..input_field import InputField vuetify.enable_lab() class NeutronDataSelector(datagrid.VGrid): class NeutronDataSelector(DataSelector): """Allows the user to select datafiles from an IPTS experiment.""" def __init__( Loading Loading @@ -65,45 +65,27 @@ class NeutronDataSelector(datagrid.VGrid): ------- None """ if "items" in kwargs: raise AttributeError("The items parameter is not allowed on DataSelector widget.") if "label" in kwargs: self._label = kwargs["label"] else: self._label = None super().__init__(v_model, "", extensions, prefix, select_strategy, skip_init=True, **kwargs) if facility and allow_custom_directories: warn("allow_custom_directories will be ignored since the facility parameter is set.", stacklevel=1) self._v_model = v_model self._v_model_name_in_state = v_model.split(".")[0] self._facility = facility self._instrument = instrument self._allow_custom_directories = allow_custom_directories self._extensions = extensions if extensions is not None else [] self._prefix = prefix self._select_strategy = select_strategy self._revogrid_id = f"nova__neutrondataselector_{self._next_id}_rv" self._state_name = f"nova__neutrondataselector_{self._next_id}_state" self._facilities_name = f"nova__neutrondataselector_{self._next_id}_facilities" self._instruments_name = f"nova__neutrondataselector_{self._next_id}_instruments" self._experiments_name = f"nova__neutrondataselector_{self._next_id}_experiments" self._directories_name = f"nova__neutrondataselector_{self._next_id}_directories" self._datafiles_name = f"nova__neutrondataselector_{self._next_id}_datafiles" self._flush_state = f"flushState('{self._v_model_name_in_state}');" self._reset_rv_grid = client.JSEval( exec=f"window.grid_manager.get('{self._revogrid_id}').updateCheckboxes()" ).exec self._reset_state = client.JSEval(exec=f"{self._v_model} = []; {self._flush_state}").exec self.create_model(facility, instrument) self.create_model() self.create_viewmodel() self.create_ui(facility, instrument, **kwargs) def create_ui(self, facility: str, instrument: str, **kwargs: Any) -> None: with VBoxLayout(classes="nova-data-selector", height="100%"): super().create_ui(**kwargs) with self._layout.filter: with GridLayout(columns=3): columns = 3 if facility == "": Loading @@ -128,91 +110,17 @@ class NeutronDataSelector(datagrid.VGrid): ) InputField(v_else=True, v_model=f"{self._state_name}.custom_directory", column_span=2) with GridLayout(columns=2, classes="flex-1-0 h-0", valign="start"): if not self._prefix: with html.Div(classes="d-flex flex-column h-100 overflow-hidden"): vuetify.VListSubheader("Available Directories", classes="flex-0-1 justify-center px-0") vuetify.VTreeview( v_if=(f"{self._directories_name}.length > 0",), activatable=True, active_strategy="single-independent", classes="flex-1-0 h-0 overflow-y-auto", fluid=True, item_value="path", items=(self._directories_name,), click_open=(self._vm.expand_directory, "[$event.path]"), update_activated=(self._vm.set_subdirectory, "$event"), ) vuetify.VListItem("No directories found", classes="flex-0-1 text-center", v_else=True) super().__init__( v_model=self._v_model, can_focus=False, columns=( "[{" " cellTemplate: (createElement, props) =>" f" window.grid_manager.get('{self._revogrid_id}').cellTemplate(createElement, props)," " columnTemplate: (createElement) =>" f" window.grid_manager.get('{self._revogrid_id}').columnTemplate(createElement)," " name: 'Available Datafiles'," " prop: 'title'," "}]", ), frame_size=10, hide_attribution=True, id=self._revogrid_id, readonly=True, stretch=True, source=(self._datafiles_name,), theme="compact", **kwargs, ) if self._label: self.label = self._label if "update_modelValue" not in kwargs: self.update_modelValue = self._flush_state # Sets up some JavaScript event handlers when the component is mounted. with self: client.ClientTriggers( mounted=( "window.grid_manager.add(" f" '{self._revogrid_id}'," f" '{self._v_model}'," f" '{self._datafiles_name}'," f" '{self._v_model_name_in_state}'" ")" ) ) with cast( vuetify.VSelect, InputField( v_model=self._v_model, classes="flex-0-1 nova-readonly", clearable=True, readonly=True, type="select", click_clear=self.reset, ), ): with vuetify.Template(raw_attrs=['v-slot:selection="{ item, index }"']): vuetify.VChip("{{ item.title.split('/').reverse()[0] }}", v_if="index < 2") html.Span( f"(+{{{{ {self._v_model}.length - 2 }}}} others)", v_if="index === 2", classes="text-caption" ) def create_model(self, facility: str, instrument: str) -> None: def create_model(self) -> None: state = NeutronDataSelectorState() self._model = NeutronDataSelectorModel( state, facility, instrument, self._extensions, self._prefix, self._allow_custom_directories self._model: NeutronDataSelectorModel = NeutronDataSelectorModel( state, self._facility, self._instrument, self._extensions, self._prefix, self._allow_custom_directories ) def create_viewmodel(self) -> None: server = get_server(None, client_type="vue3") binding = TrameBinding(server.state) self._vm = NeutronDataSelectorViewModel(self._model, binding) self._vm: NeutronDataSelectorViewModel = NeutronDataSelectorViewModel(self._model, binding) self._vm.state_bind.connect(self._state_name) self._vm.facilities_bind.connect(self._facilities_name) self._vm.instruments_bind.connect(self._instruments_name) Loading @@ -223,10 +131,6 @@ class NeutronDataSelector(datagrid.VGrid): self._vm.update_view() def reset(self, _: Any = None) -> None: self._reset_state() self._reset_rv_grid() def set_state( self, facility: Optional[str] = None, instrument: Optional[str] = None, experiment: Optional[str] = None ) -> None: Loading
src/nova/trame/view_model/data_selector.py +5 −5 Original line number Diff line number Diff line Loading @@ -12,16 +12,13 @@ class DataSelectorViewModel: """Manages the view state of the DataSelector widget.""" def __init__(self, model: DataSelectorModel, binding: BindingInterface) -> None: self.model = model self.model: DataSelectorModel = model self.datafiles: List[Dict[str, Any]] = [] self.directories: List[Dict[str, Any]] = [] self.expanded: List[str] = [] self.state_bind = binding.new_bind(self.model.state) self.facilities_bind = binding.new_bind() self.instruments_bind = binding.new_bind() self.experiments_bind = binding.new_bind() self.state_bind = binding.new_bind(self.model.state, callback_after_update=self.on_state_updated) self.directories_bind = binding.new_bind() self.datafiles_bind = binding.new_bind() self.reset_bind = binding.new_bind() Loading Loading @@ -50,6 +47,9 @@ class DataSelectorViewModel: self.expanded.append(paths[-1]) self.directories_bind.update_in_view(self.directories) def on_state_updated(self, results: Dict[str, Any]) -> None: pass def set_subdirectory(self, subdirectory_path: str = "") -> None: self.model.set_subdirectory(subdirectory_path) self.update_view() Loading
src/nova/trame/view_model/ornl/neutron_data_selector.py +6 −47 Original line number Diff line number Diff line """View model implementation for the DataSelector widget.""" import os from pathlib import Path from typing import Any, Dict, List, Optional from typing import Any, Dict, Optional from nova.mvvm.interface import BindingInterface from nova.trame.model.ornl.neutron_data_selector import NeutronDataSelectorModel from nova.trame.view_model.data_selector import DataSelectorViewModel class NeutronDataSelectorViewModel: class NeutronDataSelectorViewModel(DataSelectorViewModel): """Manages the view state of the DataSelector widget.""" def __init__(self, model: NeutronDataSelectorModel, binding: BindingInterface) -> None: self.model = model super().__init__(model, binding) self.model: NeutronDataSelectorModel = model self.datafiles: List[Dict[str, Any]] = [] self.directories: List[Dict[str, Any]] = [] self.expanded: List[str] = [] self.state_bind = binding.new_bind(self.model.state, callback_after_update=self.on_state_updated) self.facilities_bind = binding.new_bind() self.instruments_bind = binding.new_bind() self.experiments_bind = binding.new_bind() self.directories_bind = binding.new_bind() self.datafiles_bind = binding.new_bind() self.reset_bind = binding.new_bind() def expand_directory(self, paths: List[str]) -> None: if paths[-1] in self.expanded: return # Query for the new subdirectories to display in the view new_directories = self.model.get_directories(Path(paths[-1])) # Find the entry in the existing directories that corresponds to the directory to expand current_level: Dict[str, Any] = {} children: List[Dict[str, Any]] = self.directories for current_path in paths: if current_level: children = current_level["children"] for entry in children: if current_path == entry["path"]: current_level = entry break current_level["children"] = new_directories # Mark this directory as expanded and display the new content self.expanded.append(paths[-1]) self.directories_bind.update_in_view(self.directories) def set_subdirectory(self, subdirectory_path: str = "") -> None: self.model.set_subdirectory(subdirectory_path) self.update_view() def set_state(self, facility: Optional[str], instrument: Optional[str], experiment: Optional[str]) -> None: self.model.set_state(facility, instrument, experiment) Loading Loading @@ -80,13 +44,8 @@ class NeutronDataSelectorViewModel: self.update_view() def update_view(self) -> None: self.state_bind.update_in_view(self.model.state) self.facilities_bind.update_in_view(self.model.get_facilities()) self.instruments_bind.update_in_view(self.model.get_instruments()) self.experiments_bind.update_in_view(self.model.get_experiments()) self.directories_bind.update_in_view(self.directories) self.datafiles = [ {"path": datafile, "title": os.path.basename(datafile)} for datafile in self.model.get_datafiles() ] self.datafiles_bind.update_in_view(self.datafiles) super().update_view()