Commit 9feb0615 authored by Duggan, John's avatar Duggan, John
Browse files

Inherit the DataSelector view and vm properly

parent 9114b860
Loading
Loading
Loading
Loading
Loading
+10 −5
Original line number Diff line number Diff line
@@ -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.
@@ -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
@@ -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:
@@ -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")
+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
@@ -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__(
@@ -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 == "":
@@ -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)
@@ -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:
+5 −5
Original line number Diff line number Diff line
@@ -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()
@@ -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()
+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)
@@ -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()