Commit 87d59ad9 authored by Duggan, John's avatar Duggan, John
Browse files

Add parameter bindings for FileUpload

parent c63dfe22
Loading
Loading
Loading
Loading
Loading
+27 −13
Original line number Diff line number Diff line
"""View implementation for FileUpload."""

from typing import Any, List, Optional
from typing import Any, List, Tuple, Union

from trame.app import get_server
from trame.widgets import vuetify3 as vuetify
from trame_server.core import State

from nova.trame._internal.utils import get_state_param

from .remote_file_input import RemoteFileInput

@@ -12,25 +16,28 @@ class FileUpload(vuetify.VBtn):

    def __init__(
        self,
        v_model: str,
        base_paths: Optional[List[str]] = None,
        v_model: Union[str, Tuple],
        base_paths: Union[List[str], Tuple, None] = None,
        extensions: Union[List[str], Tuple, None] = None,
        label: str = "",
        return_contents: bool = True,
        return_contents: Union[bool, Tuple] = True,
        **kwargs: Any,
    ) -> None:
        """Constructor for FileUpload.

        Parameters
        ----------
        v_model : str
        v_model : Union[str, Tuple]
            The state variable to set when the user uploads their file. The state variable will contain a latin1-decoded
            version of the file contents. If your file is binary or requires a different string encoding, then you can
            call `encode('latin1')` on the file contents to get the underlying bytes.
        base_paths: list[str], optional
        base_paths: Union[List[str], Tuple], optional
            Passed to :ref:`RemoteFileInput <api_remotefileinput>`.
        extensions: Union[List[str], Tuple], optional
            Restricts the files shown to the user to files that end with one of the strings in the list.
        label : str, optional
            The text to display on the upload button.
        return_contents : bool, optional
        return_contents : Union[bool, Tuple], optional
            If true, the file contents will be stored in v_model. If false, a file path will be stored in v_model.
            Defaults to true.
        **kwargs
@@ -41,20 +48,26 @@ class FileUpload(vuetify.VBtn):
        -------
        None
        """
        self._server = get_server(None, client_type="vue3")

        self._v_model = v_model
        if base_paths:
            self._base_paths = base_paths
        else:
            self._base_paths = ["/"]
        self._base_paths = base_paths if base_paths else ["/"]
        self._extensions = extensions if extensions else []
        self._return_contents = return_contents
        self._ref_name = f"nova__fileupload_{self._next_id}"

        super().__init__(label, **kwargs)
        self.create_ui()

    @property
    def state(self) -> State:
        return self._server.state

    def create_ui(self) -> None:
        self.local_file_input = vuetify.VFileInput(
            v_model=(self._v_model, None),
            v_model=self._v_model,
            __properties=["accept"],
            accept=self._extensions,
            classes="d-none",
            ref=self._ref_name,
            # Serialize the content in a way that will work with nova-mvvm and then push it to the server.
@@ -67,6 +80,7 @@ class FileUpload(vuetify.VBtn):
        self.remote_file_input = RemoteFileInput(
            v_model=self._v_model,
            base_paths=self._base_paths,
            extensions=self._extensions,
            input_props={"classes": "d-none"},
            return_contents=self._return_contents,
        )
@@ -79,7 +93,7 @@ class FileUpload(vuetify.VBtn):

        @self.server.controller.trigger(f"decode_blob_{self._id}")
        def _decode_blob(contents: bytes) -> None:
            self.remote_file_input.decode_file(contents, self._return_contents)
            self.remote_file_input.decode_file(contents, get_state_param(self.state, self._return_contents))

    def select_file(self, value: str) -> None:
        """Programmatically set the RemoteFileInput path.
+20 −13
Original line number Diff line number Diff line
@@ -203,11 +203,11 @@ class RemoteFileInput:
    # don't want bindings in the internal implementation as our callbacks could compete with the developer's.
    def _setup_bindings(self) -> None:
        # If the bindings were given initial values, write these to the state.
        set_state_param(self.state, self.allow_files)
        set_state_param(self.state, self.allow_folders)
        set_state_param(self.state, self.base_paths)
        set_state_param(self.state, self.extensions)
        return_contents = set_state_param(self.state, self.return_contents)
        self._last_allow_files = set_state_param(self.state, self.allow_files)
        self._last_allow_folders = set_state_param(self.state, self.allow_folders)
        self._last_base_paths = set_state_param(self.state, self.base_paths)
        self._last_extensions = set_state_param(self.state, self.extensions)
        self._last_return_contents = set_state_param(self.state, self.return_contents)

        # Now we need to propagate the state to this component's view model.
        self.vm.set_binding_parameters(
@@ -216,7 +216,7 @@ class RemoteFileInput:
            base_paths=self.base_paths,
            extensions=self.extensions,
        )
        self._setup_update_binding(return_contents)
        self._setup_update_binding(self._last_return_contents)

        # Now we set up the change listeners for all bound parameters. These are responsible for updating the component
        # when other portions of the application manipulate these parameters.
@@ -227,7 +227,10 @@ class RemoteFileInput:
                if isinstance(self.allow_files, bool):
                    return
                allow_files = rgetdictvalue(kwargs, self.allow_files[0])
                self.vm.set_binding_parameters(allow_files=set_state_param(self.state, self.allow_files, allow_files))
                if allow_files != self._last_allow_files:
                    self.vm.set_binding_parameters(
                        allow_files=set_state_param(self.state, self.allow_files, allow_files)
                    )

        if isinstance(self.allow_folders, tuple):

@@ -236,6 +239,7 @@ class RemoteFileInput:
                if isinstance(self.allow_folders, bool):
                    return
                allow_folders = rgetdictvalue(kwargs, self.allow_folders[0])
                if allow_folders != self._last_allow_folders:
                    self.vm.set_binding_parameters(
                        allow_folders=set_state_param(self.state, self.allow_folders, allow_folders)
                    )
@@ -247,6 +251,7 @@ class RemoteFileInput:
                if isinstance(self.base_paths, bool):
                    return
                base_paths = rgetdictvalue(kwargs, self.base_paths[0])
                if base_paths != self._last_base_paths:
                    self.vm.set_binding_parameters(base_paths=set_state_param(self.state, self.base_paths, base_paths))

        if isinstance(self.extensions, tuple):
@@ -256,6 +261,7 @@ class RemoteFileInput:
                if isinstance(self.extensions, bool):
                    return
                extensions = rgetdictvalue(kwargs, self.extensions[0])
                if extensions != self._last_extensions:
                    self.vm.set_binding_parameters(extensions=set_state_param(self.state, self.extensions, extensions))

        if isinstance(self.return_contents, tuple):
@@ -265,6 +271,7 @@ class RemoteFileInput:
                if isinstance(self.return_contents, bool):
                    return
                return_contents = rgetdictvalue(kwargs, self.return_contents[0])
                if return_contents != self._last_return_contents:
                    self._setup_update_binding(return_contents)

    def _setup_update_binding(self, read_file: bool) -> None:
+4 −0
Original line number Diff line number Diff line
"""Model for MVVM demo of FileUpload."""

from typing import List

from pydantic import BaseModel, Field


class FileUploadState(BaseModel):
    """Model for MVVM demo of FileUpload."""

    extensions: List[str] = Field(default=[".cif", ".nxs"])
    file: str = Field(default="")
    label: str = Field(default="Upload File")
+3 −0
Original line number Diff line number Diff line
@@ -16,3 +16,6 @@ class FileUploadVM:

    def on_update(self, data: Dict[str, Any]) -> None:
        print("file size:", sys.getsizeof(self.model.file))

    def update_view(self) -> None:
        self.model_bind.update_in_view(self.model)
+6 −1
Original line number Diff line number Diff line
@@ -383,7 +383,12 @@ class App(ThemedApp):

                    vuetify.VCardTitle("Form Inputs & Controls")
                    with GridLayout(columns=3, valign="center"):
                        FileUpload(v_model="file_upload.file", base_paths=["/HFIR", "/SNS"], label="Upload File")
                        FileUpload(
                            v_model="file_upload.file",
                            base_paths=["/HFIR", "/SNS"],
                            extensions=("file_upload.extensions",),
                            label="{{ file_upload.label }}",
                        )
                        with html.Div():
                            InputField(
                                v_model="autoscroll",