nova-trame 0.25.3__py3-none-any.whl → 0.25.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,21 +3,39 @@
3
3
  import os
4
4
  from functools import cmp_to_key
5
5
  from locale import strcoll
6
- from typing import Any, Union
6
+ from typing import Any, List, Union
7
+
8
+ from pydantic import BaseModel, Field
9
+
10
+
11
+ class RemoteFileInputState(BaseModel):
12
+ """Pydantic model for RemoteFileInput state."""
13
+
14
+ allow_files: bool = Field(default=False)
15
+ allow_folders: bool = Field(default=False)
16
+ base_paths: List[str] = Field(default=[])
17
+ extensions: List[str] = Field(default=[])
7
18
 
8
19
 
9
20
  class RemoteFileInputModel:
10
21
  """Manages interactions between RemoteFileInput and the file system."""
11
22
 
12
- def __init__(self, allow_files: bool, allow_folders: bool, base_paths: list[str], extensions: list[str]) -> None:
23
+ def __init__(self) -> None:
13
24
  """Creates a new RemoteFileInputModel."""
14
- self.allow_files = allow_files
15
- self.allow_folders = allow_folders
16
- self.base_paths = base_paths
17
- self.extensions = extensions
25
+ self.state = RemoteFileInputState()
26
+
27
+ def set_binding_parameters(self, **kwargs: Any) -> None:
28
+ if "allow_files" in kwargs:
29
+ self.state.allow_files = kwargs["allow_files"]
30
+ if "allow_folders" in kwargs:
31
+ self.state.allow_folders = kwargs["allow_folders"]
32
+ if "base_paths" in kwargs:
33
+ self.state.base_paths = kwargs["base_paths"]
34
+ if "extensions" in kwargs:
35
+ self.state.extensions = kwargs["extensions"]
18
36
 
19
37
  def get_base_paths(self) -> list[dict[str, Any]]:
20
- return [{"path": base_path, "directory": True} for base_path in self.base_paths]
38
+ return [{"path": base_path, "directory": True} for base_path in self.state.base_paths]
21
39
 
22
40
  def scan_current_path(
23
41
  self, current_path: str, showing_all_files: bool, filter: str
@@ -72,7 +90,7 @@ class RemoteFileInputModel:
72
90
  if not showing_base_paths and file != "..":
73
91
  return os.path.join(old_path, file)
74
92
  elif not showing_base_paths:
75
- if old_path in self.base_paths:
93
+ if old_path in self.state.base_paths:
76
94
  return ""
77
95
  else:
78
96
  return os.path.dirname(old_path)
@@ -83,17 +101,21 @@ class RemoteFileInputModel:
83
101
  if entry.is_dir():
84
102
  return True
85
103
 
86
- if not self.allow_files:
104
+ if not self.state.allow_files:
87
105
  return False
88
106
 
89
- return showing_all_files or not self.extensions or any(entry.name.endswith(ext) for ext in self.extensions)
107
+ return (
108
+ showing_all_files
109
+ or not self.state.extensions
110
+ or any(entry.name.endswith(ext) for ext in self.state.extensions)
111
+ )
90
112
 
91
113
  def valid_selection(self, selection: str) -> bool:
92
114
  if self.valid_subpath(selection):
93
- if os.path.isdir(selection) and self.allow_folders:
115
+ if os.path.isdir(selection) and self.state.allow_folders:
94
116
  return True
95
117
 
96
- if os.path.isfile(selection) and self.allow_files:
118
+ if os.path.isfile(selection) and self.state.allow_files:
97
119
  return True
98
120
 
99
121
  return False
@@ -102,7 +124,7 @@ class RemoteFileInputModel:
102
124
  if subpath == "":
103
125
  return False
104
126
 
105
- for base_path in self.base_paths:
127
+ for base_path in self.state.base_paths:
106
128
  if subpath.startswith(base_path):
107
129
  return True
108
130
 
@@ -1,8 +1,12 @@
1
1
  """View implementation for FileUpload."""
2
2
 
3
- from typing import Any, List, Optional
3
+ from typing import Any, List, Tuple, Union
4
4
 
5
+ from trame.app import get_server
5
6
  from trame.widgets import vuetify3 as vuetify
7
+ from trame_server.core import State
8
+
9
+ from nova.trame._internal.utils import get_state_param
6
10
 
7
11
  from .remote_file_input import RemoteFileInput
8
12
 
@@ -12,25 +16,28 @@ class FileUpload(vuetify.VBtn):
12
16
 
13
17
  def __init__(
14
18
  self,
15
- v_model: str,
16
- base_paths: Optional[List[str]] = None,
19
+ v_model: Union[str, Tuple],
20
+ base_paths: Union[List[str], Tuple, None] = None,
21
+ extensions: Union[List[str], Tuple, None] = None,
17
22
  label: str = "",
18
- return_contents: bool = True,
23
+ return_contents: Union[bool, Tuple] = True,
19
24
  **kwargs: Any,
20
25
  ) -> None:
21
26
  """Constructor for FileUpload.
22
27
 
23
28
  Parameters
24
29
  ----------
25
- v_model : str
30
+ v_model : Union[str, Tuple]
26
31
  The state variable to set when the user uploads their file. The state variable will contain a latin1-decoded
27
32
  version of the file contents. If your file is binary or requires a different string encoding, then you can
28
33
  call `encode('latin1')` on the file contents to get the underlying bytes.
29
- base_paths: list[str], optional
34
+ base_paths: Union[List[str], Tuple], optional
30
35
  Passed to :ref:`RemoteFileInput <api_remotefileinput>`.
36
+ extensions: Union[List[str], Tuple], optional
37
+ Restricts the files shown to the user to files that end with one of the strings in the list.
31
38
  label : str, optional
32
39
  The text to display on the upload button.
33
- return_contents : bool, optional
40
+ return_contents : Union[bool, Tuple], optional
34
41
  If true, the file contents will be stored in v_model. If false, a file path will be stored in v_model.
35
42
  Defaults to true.
36
43
  **kwargs
@@ -41,20 +48,26 @@ class FileUpload(vuetify.VBtn):
41
48
  -------
42
49
  None
43
50
  """
51
+ self._server = get_server(None, client_type="vue3")
52
+
44
53
  self._v_model = v_model
45
- if base_paths:
46
- self._base_paths = base_paths
47
- else:
48
- self._base_paths = ["/"]
54
+ self._base_paths = base_paths if base_paths else ["/"]
55
+ self._extensions = extensions if extensions else []
49
56
  self._return_contents = return_contents
50
57
  self._ref_name = f"nova__fileupload_{self._next_id}"
51
58
 
52
59
  super().__init__(label, **kwargs)
53
60
  self.create_ui()
54
61
 
62
+ @property
63
+ def state(self) -> State:
64
+ return self._server.state
65
+
55
66
  def create_ui(self) -> None:
56
67
  self.local_file_input = vuetify.VFileInput(
57
- v_model=(self._v_model, None),
68
+ v_model=self._v_model,
69
+ __properties=["accept"],
70
+ accept=",".join(self._extensions) if isinstance(self._extensions, list) else self._extensions,
58
71
  classes="d-none",
59
72
  ref=self._ref_name,
60
73
  # 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):
67
80
  self.remote_file_input = RemoteFileInput(
68
81
  v_model=self._v_model,
69
82
  base_paths=self._base_paths,
83
+ extensions=self._extensions,
70
84
  input_props={"classes": "d-none"},
71
85
  return_contents=self._return_contents,
72
86
  )
@@ -79,7 +93,7 @@ class FileUpload(vuetify.VBtn):
79
93
 
80
94
  @self.server.controller.trigger(f"decode_blob_{self._id}")
81
95
  def _decode_blob(contents: bytes) -> None:
82
- self.remote_file_input.decode_file(contents, self._return_contents)
96
+ self.remote_file_input.decode_file(contents, get_state_param(self.state, self._return_contents))
83
97
 
84
98
  def select_file(self, value: str) -> None:
85
99
  """Programmatically set the RemoteFileInput path.
@@ -5,7 +5,7 @@ import os
5
5
  import re
6
6
  from enum import Enum
7
7
  from inspect import isclass
8
- from typing import Any, Dict, Optional, Union
8
+ from typing import Any, Dict, Tuple, Union
9
9
 
10
10
  from trame.app import get_server
11
11
  from trame.widgets import client
@@ -15,6 +15,7 @@ from trame_server.controller import Controller
15
15
  from trame_server.state import State
16
16
 
17
17
  from nova.mvvm.pydantic_utils import get_field_info
18
+ from nova.trame._internal.utils import set_state_param
18
19
 
19
20
  logger = logging.getLogger(__name__)
20
21
 
@@ -22,9 +23,14 @@ logger = logging.getLogger(__name__)
22
23
  class InputField:
23
24
  """Factory class for generating Vuetify input components."""
24
25
 
26
+ next_id = 0
27
+
25
28
  @staticmethod
26
29
  def create_boilerplate_properties(
27
- v_model: Optional[Union[tuple[str, Any], str]], field_type: str, debounce: int, throttle: int
30
+ v_model: Union[str, Tuple, None],
31
+ field_type: str,
32
+ debounce: Union[int, Tuple],
33
+ throttle: Union[int, Tuple],
28
34
  ) -> dict:
29
35
  if debounce == -1:
30
36
  debounce = int(os.environ.get("NOVA_TRAME_DEFAULT_DEBOUNCE", 0))
@@ -78,26 +84,43 @@ class InputField:
78
84
  ):
79
85
  args |= {"items": str([option.value for option in field_info.annotation])}
80
86
 
81
- if debounce > 0 and throttle > 0:
87
+ if debounce and throttle:
82
88
  raise ValueError("debounce and throttle cannot be used together")
83
89
 
84
- if debounce > 0:
90
+ server = get_server(None, client_type="vue3")
91
+ if debounce:
92
+ if isinstance(debounce, tuple):
93
+ debounce_field = debounce[0]
94
+ set_state_param(server.state, debounce)
95
+ else:
96
+ debounce_field = f"nova__debounce_{InputField.next_id}"
97
+ InputField.next_id += 1
98
+ set_state_param(server.state, debounce_field, debounce)
99
+
85
100
  args |= {
86
101
  "update_modelValue": (
87
102
  "window.delay_manager.debounce("
88
- f" '{v_model}',"
103
+ f" '{field}',"
89
104
  f" () => flushState('{object_name_in_state}'),"
90
- f" {debounce}"
105
+ f" {debounce_field}"
91
106
  ")"
92
107
  )
93
108
  }
94
- elif throttle > 0:
109
+ elif throttle:
110
+ if isinstance(throttle, tuple):
111
+ throttle_field = throttle[0]
112
+ set_state_param(server.state, throttle)
113
+ else:
114
+ throttle_field = f"nova__throttle_{InputField.next_id}"
115
+ InputField.next_id += 1
116
+ set_state_param(server.state, throttle_field, throttle)
117
+
95
118
  args |= {
96
119
  "update_modelValue": (
97
120
  "window.delay_manager.throttle("
98
- f" '{v_model}',"
121
+ f" '{field}',"
99
122
  f" () => flushState('{object_name_in_state}'),"
100
- f" {throttle}"
123
+ f" {throttle_field}"
101
124
  ")"
102
125
  )
103
126
  }
@@ -107,10 +130,10 @@ class InputField:
107
130
 
108
131
  def __new__(
109
132
  cls,
110
- v_model: Optional[Union[tuple[str, Any], str]] = None,
133
+ v_model: Union[str, Tuple, None] = None,
111
134
  required: bool = False,
112
- debounce: int = -1,
113
- throttle: int = -1,
135
+ debounce: Union[int, Tuple] = -1,
136
+ throttle: Union[int, Tuple] = -1,
114
137
  type: str = "text",
115
138
  **kwargs: Any,
116
139
  ) -> AbstractElement:
@@ -118,18 +141,19 @@ class InputField:
118
141
 
119
142
  Parameters
120
143
  ----------
121
- v_model : tuple[str, Any] or str, optional
144
+ v_model : Union[str, Tuple], optional
122
145
  The v-model for this component. If this references a Pydantic configuration variable, then this component
123
146
  will attempt to load a label, hint, and validation rules from the configuration for you automatically.
124
- required : bool
147
+ required : bool, optional
125
148
  If true, the input will be visually marked as required and a required rule will be added to the end of the
126
- rules list.
127
- debounce : int
149
+ rules list. This parameter will be removed in the future. Please use Pydantic to enforce validation of
150
+ required fields.
151
+ debounce : Union[int, Tuple], optional
128
152
  Number of milliseconds to wait after the last user interaction with this field before attempting to update
129
153
  the Trame state. If set to 0, then no debouncing will occur. If set to -1, then the environment variable
130
154
  `NOVA_TRAME_DEFAULT_DEBOUNCE` will be used to set this (defaults to 0). See the `Lodash Docs
131
155
  <https://lodash.com/docs/4.17.15#debounce>`__ for details.
132
- throttle : int
156
+ throttle : Union[int, Tuple], optional
133
157
  Number of milliseconds to wait between updates to the Trame state when the user is interacting with this
134
158
  field. If set to 0, then no throttling will occur. If set to -1, then the environment variable
135
159
  `NOVA_TRAME_DEFAULT_THROTTLE` will be used to set this (defaults to 0). See the `Lodash Docs
@@ -165,7 +189,9 @@ class InputField:
165
189
  - switch
166
190
  - textarea
167
191
 
168
- Any other value will produce a text field with your type used as an HTML input type attribute.
192
+ Any other value will produce a text field with your type used as an HTML input type attribute. Note that
193
+ parameter does not support binding since swapping field types dynamically produces a confusing user
194
+ experience.
169
195
  **kwargs
170
196
  All other arguments will be passed to the underlying
171
197
  `Trame Vuetify component <https://trame.readthedocs.io/en/latest/trame.widgets.vuetify3.html>`_.
@@ -184,7 +210,10 @@ class InputField:
184
210
  """
185
211
  server = get_server(None, client_type="vue3")
186
212
 
187
- kwargs = {**cls.create_boilerplate_properties(v_model, type, debounce, throttle), **kwargs}
213
+ kwargs = {
214
+ **cls.create_boilerplate_properties(v_model, type, debounce, throttle),
215
+ **kwargs,
216
+ }
188
217
 
189
218
  if "__events" not in kwargs or kwargs["__events"] is None:
190
219
  kwargs["__events"] = []
@@ -2,14 +2,17 @@
2
2
 
3
3
  from functools import partial
4
4
  from tempfile import NamedTemporaryFile
5
- from typing import Any, Optional, Union, cast
5
+ from typing import Any, List, Optional, Tuple, Union, cast
6
6
 
7
7
  from trame.app import get_server
8
8
  from trame.widgets import client, html
9
9
  from trame.widgets import vuetify3 as vuetify
10
10
  from trame_client.widgets.core import AbstractElement
11
+ from trame_server.core import State
11
12
 
13
+ from nova.mvvm._internal.utils import rgetdictvalue
12
14
  from nova.mvvm.trame_binding import TrameBinding
15
+ from nova.trame._internal.utils import get_state_name, set_state_param
13
16
  from nova.trame.model.remote_file_input import RemoteFileInputModel
14
17
  from nova.trame.view_model.remote_file_input import RemoteFileInputViewModel
15
18
 
@@ -24,57 +27,47 @@ class RemoteFileInput:
24
27
 
25
28
  def __init__(
26
29
  self,
27
- v_model: Optional[Union[tuple[str, Any], str]] = None,
28
- allow_files: bool = True,
29
- allow_folders: bool = False,
30
- allow_nonexistent_path: bool = False,
31
- base_paths: Optional[list[str]] = None,
30
+ v_model: Union[str, Tuple],
31
+ allow_files: Union[bool, Tuple] = True,
32
+ allow_folders: Union[bool, Tuple] = False,
33
+ base_paths: Union[List[str], Tuple, None] = None,
32
34
  dialog_props: Optional[dict[str, Any]] = None,
33
- extensions: Optional[list[str]] = None,
35
+ extensions: Union[List[str], Tuple, None] = None,
34
36
  input_props: Optional[dict[str, Any]] = None,
35
- return_contents: bool = False,
37
+ return_contents: Union[bool, Tuple] = False,
36
38
  ) -> None:
37
39
  """Constructor for RemoteFileInput.
38
40
 
39
41
  Parameters
40
42
  ----------
41
- v_model : tuple[str, Any] or str, optional
43
+ v_model : Union[str, Tuple]
42
44
  The v-model for this component. If this references a Pydantic configuration variable, then this component
43
45
  will attempt to load a label, hint, and validation rules from the configuration for you automatically.
44
- allow_files : bool
46
+ allow_files : Union[bool, Tuple], optional
45
47
  If true, the user can save a file selection.
46
- allow_folders : bool
48
+ allow_folders : Union[bool, Tuple], optional
47
49
  If true, the user can save a folder selection.
48
- allow_nonexistent_path : bool
49
- If false, the user will be warned when they've selected a non-existent path on the filesystem.
50
- base_paths : list[str], optional
50
+ base_paths : Union[List[str], Tuple], optional
51
51
  Only files under these paths will be shown.
52
- dialog_props : dict[str, typing.Any], optional
52
+ dialog_props : Dict[str, typing.Any], optional
53
53
  Props to be passed to VDialog.
54
- extensions : list[str], optional
54
+ extensions : Union[List[str], Tuple], optional
55
55
  Only files with these extensions will be shown by default. The user can still choose to view all files.
56
- input_props : dict[str, typing.Any], optional
56
+ input_props : Dict[str, typing.Any], optional
57
57
  Props to be passed to InputField.
58
- return_contents : bool
58
+ return_contents : Union[bool, Tuple], optional
59
59
  If true, then the v_model will contain the contents of the file. If false, then the v_model will contain the
60
- path of the file.
61
-
62
- Raises
63
- ------
64
- ValueError
65
- If v_model is None.
60
+ path of the file. Defaults to false.
66
61
 
67
62
  Returns
68
63
  -------
69
64
  None
70
65
  """
71
- if v_model is None:
72
- raise ValueError("RemoteFileInput must have a v_model attribute.")
66
+ self.server = get_server(None, client_type="vue3")
73
67
 
74
68
  self.v_model = v_model
75
69
  self.allow_files = allow_files
76
70
  self.allow_folders = allow_folders
77
- self.allow_nonexistent_path = allow_nonexistent_path
78
71
  self.base_paths = base_paths if base_paths else ["/"]
79
72
  self.dialog_props = dict(dialog_props) if dialog_props else {}
80
73
  self.extensions = extensions if extensions else []
@@ -88,10 +81,16 @@ class RemoteFileInput:
88
81
  if "width" not in self.dialog_props:
89
82
  self.dialog_props["width"] = 600
90
83
 
91
- self.create_model()
92
- self.create_viewmodel()
84
+ self._create_model()
85
+ self._create_viewmodel()
86
+ self._setup_bindings()
87
+
93
88
  self.create_ui()
94
89
 
90
+ @property
91
+ def state(self) -> State:
92
+ return self.server.state
93
+
95
94
  def create_ui(self) -> None:
96
95
  with cast(
97
96
  AbstractElement,
@@ -175,12 +174,11 @@ class RemoteFileInput:
175
174
  click=partial(self.vm.close_dialog, cancel=True),
176
175
  )
177
176
 
178
- def create_model(self) -> None:
179
- self.model = RemoteFileInputModel(self.allow_files, self.allow_folders, self.base_paths, self.extensions)
177
+ def _create_model(self) -> None:
178
+ self.model = RemoteFileInputModel()
180
179
 
181
- def create_viewmodel(self) -> None:
182
- server = get_server(None, client_type="vue3")
183
- binding = TrameBinding(server.state)
180
+ def _create_viewmodel(self) -> None:
181
+ binding = TrameBinding(self.state)
184
182
 
185
183
  if isinstance(self.v_model, tuple):
186
184
  model_name = self.v_model[0]
@@ -197,12 +195,91 @@ class RemoteFileInput:
197
195
  self.vm.file_list_bind.connect(self.vm.get_file_list_state_name())
198
196
  self.vm.filter_bind.connect(self.vm.get_filter_state_name())
199
197
  self.vm.on_close_bind.connect(client.JSEval(exec=f"{self.vm.get_dialog_state_name()} = false;").exec)
200
- if self.return_contents:
198
+ self.vm.showing_all_bind.connect(self.vm.get_showing_all_state_name())
199
+ self.vm.valid_selection_bind.connect(self.vm.get_valid_selection_state_name())
200
+
201
+ # This method sets up Trame state change listeners for each binding parameter that can be changed directly by this
202
+ # component. This allows us to communicate the changes to the developer's bindings without requiring our own. We
203
+ # don't want bindings in the internal implementation as our callbacks could compete with the developer's.
204
+ def _setup_bindings(self) -> None:
205
+ # If the bindings were given initial values, write these to the state.
206
+ self._last_allow_files = set_state_param(self.state, self.allow_files)
207
+ self._last_allow_folders = set_state_param(self.state, self.allow_folders)
208
+ self._last_base_paths = set_state_param(self.state, self.base_paths)
209
+ self._last_extensions = set_state_param(self.state, self.extensions)
210
+ self._last_return_contents = set_state_param(self.state, self.return_contents)
211
+
212
+ # Now we need to propagate the state to this component's view model.
213
+ self.vm.set_binding_parameters(
214
+ allow_files=self.allow_files,
215
+ allow_folders=self.allow_folders,
216
+ base_paths=self.base_paths,
217
+ extensions=self.extensions,
218
+ )
219
+ self._setup_update_binding(self._last_return_contents)
220
+
221
+ # Now we set up the change listeners for all bound parameters. These are responsible for updating the component
222
+ # when other portions of the application manipulate these parameters.
223
+ if isinstance(self.allow_files, tuple):
224
+
225
+ @self.state.change(get_state_name(self.allow_files[0]))
226
+ def on_allow_files_change(**kwargs: Any) -> None:
227
+ if isinstance(self.allow_files, bool):
228
+ return
229
+ allow_files = rgetdictvalue(kwargs, self.allow_files[0])
230
+ if allow_files != self._last_allow_files:
231
+ self.vm.set_binding_parameters(
232
+ allow_files=set_state_param(self.state, self.allow_files, allow_files)
233
+ )
234
+
235
+ if isinstance(self.allow_folders, tuple):
236
+
237
+ @self.state.change(get_state_name(self.allow_folders[0]))
238
+ def on_allow_folders_change(**kwargs: Any) -> None:
239
+ if isinstance(self.allow_folders, bool):
240
+ return
241
+ allow_folders = rgetdictvalue(kwargs, self.allow_folders[0])
242
+ if allow_folders != self._last_allow_folders:
243
+ self.vm.set_binding_parameters(
244
+ allow_folders=set_state_param(self.state, self.allow_folders, allow_folders)
245
+ )
246
+
247
+ if isinstance(self.base_paths, tuple):
248
+
249
+ @self.state.change(get_state_name(self.base_paths[0]))
250
+ def on_base_paths_change(**kwargs: Any) -> None:
251
+ if isinstance(self.base_paths, bool):
252
+ return
253
+ base_paths = rgetdictvalue(kwargs, self.base_paths[0])
254
+ if base_paths != self._last_base_paths:
255
+ self.vm.set_binding_parameters(base_paths=set_state_param(self.state, self.base_paths, base_paths))
256
+
257
+ if isinstance(self.extensions, tuple):
258
+
259
+ @self.state.change(get_state_name(self.extensions[0]))
260
+ def on_extensions_change(**kwargs: Any) -> None:
261
+ if isinstance(self.extensions, bool):
262
+ return
263
+ extensions = rgetdictvalue(kwargs, self.extensions[0])
264
+ if extensions != self._last_extensions:
265
+ self.vm.set_binding_parameters(extensions=set_state_param(self.state, self.extensions, extensions))
266
+
267
+ if isinstance(self.return_contents, tuple):
268
+
269
+ @self.state.change(get_state_name(self.return_contents[0]))
270
+ def on_return_contents_change(**kwargs: Any) -> None:
271
+ if isinstance(self.return_contents, bool):
272
+ return
273
+ return_contents = rgetdictvalue(kwargs, self.return_contents[0])
274
+ if return_contents != self._last_return_contents:
275
+ self._setup_update_binding(return_contents)
276
+
277
+ def _setup_update_binding(self, read_file: bool) -> None:
278
+ self.vm.reset_update_binding()
279
+ if read_file:
201
280
  self.vm.on_update_bind.connect(self.read_file)
202
281
  else:
203
282
  self.vm.on_update_bind.connect(self.set_v_model)
204
- self.vm.showing_all_bind.connect(self.vm.get_showing_all_state_name())
205
- self.vm.valid_selection_bind.connect(self.vm.get_valid_selection_state_name())
206
283
 
207
284
  def read_file(self, file_path: str) -> None:
208
285
  with open(file_path, mode="rb") as file:
@@ -5,19 +5,25 @@ class DelayManager {
5
5
  }
6
6
 
7
7
  debounce(id, func, wait, ...args) {
8
- if (!(id in this.debounces)) {
9
- this.debounces[id] = _.debounce(func, wait)
8
+ if (!(id in this.debounces) || this.debounces[id]['wait'] !== wait) {
9
+ this.debounces[id] = {
10
+ 'debounce': _.debounce(func, wait),
11
+ 'wait': wait
12
+ }
10
13
  }
11
14
 
12
- this.debounces[id](...args)
15
+ this.debounces[id]['debounce'](...args)
13
16
  }
14
17
 
15
18
  throttle(id, func, wait, ...args) {
16
- if (!(id in this.throttles)) {
17
- this.throttles[id] = _.throttle(func, wait)
19
+ if (!(id in this.throttles) || this.throttles[id]['wait'] !== wait) {
20
+ this.throttles[id] = {
21
+ 'throttle': _.throttle(func, wait),
22
+ 'wait': wait
23
+ }
18
24
  }
19
25
 
20
- this.throttles[id](...args)
26
+ this.throttles[id]['throttle'](...args)
21
27
  }
22
28
  }
23
29
 
@@ -14,6 +14,7 @@ class RemoteFileInputViewModel:
14
14
  def __init__(self, model: RemoteFileInputModel, binding: BindingInterface) -> None:
15
15
  """Creates a new RemoteFileInputViewModel."""
16
16
  self.model = model
17
+ self.binding = binding
17
18
 
18
19
  # Needed to keep state variables separated if this class is instantiated multiple times.
19
20
  self.id = RemoteFileInputViewModel.counter
@@ -23,13 +24,16 @@ class RemoteFileInputViewModel:
23
24
  self.showing_base_paths = True
24
25
  self.previous_value = ""
25
26
  self.value = ""
26
- self.dialog_bind = binding.new_bind()
27
- self.file_list_bind = binding.new_bind()
28
- self.filter_bind = binding.new_bind()
29
- self.showing_all_bind = binding.new_bind()
30
- self.valid_selection_bind = binding.new_bind()
31
- self.on_close_bind = binding.new_bind()
32
- self.on_update_bind = binding.new_bind()
27
+ self.dialog_bind = self.binding.new_bind()
28
+ self.file_list_bind = self.binding.new_bind()
29
+ self.filter_bind = self.binding.new_bind()
30
+ self.showing_all_bind = self.binding.new_bind()
31
+ self.valid_selection_bind = self.binding.new_bind()
32
+ self.on_close_bind = self.binding.new_bind()
33
+ self.on_update_bind = self.binding.new_bind()
34
+
35
+ def reset_update_binding(self) -> None:
36
+ self.on_update_bind = self.binding.new_bind()
33
37
 
34
38
  def open_dialog(self) -> None:
35
39
  self.previous_value = self.value
@@ -93,3 +97,6 @@ class RemoteFileInputViewModel:
93
97
 
94
98
  self.valid_selection_bind.update_in_view(self.model.valid_selection(new_path))
95
99
  self.populate_file_list()
100
+
101
+ def set_binding_parameters(self, **kwargs: Any) -> None:
102
+ self.model.set_binding_parameters(**kwargs)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: nova-trame
3
- Version: 0.25.3
3
+ Version: 0.25.4
4
4
  Summary: A Python Package for injecting curated themes and custom components into Trame applications
5
5
  License: MIT
6
6
  Keywords: NDIP,Python,Trame,Vuetify
@@ -3,16 +3,16 @@ nova/trame/__init__.py,sha256=gFrAg1qva5PIqR5TjvPzAxLx103IKipJLqp3XXvrQL8,59
3
3
  nova/trame/_internal/utils.py,sha256=lTTJnfqbbIe21Tg2buf5MXqKUEUop7Va5PZgpWMzRkI,1381
4
4
  nova/trame/model/data_selector.py,sha256=rDmWDtHVGgi5e2fBEYvChM2vVKNu798m67Sq8MUN2UI,4463
5
5
  nova/trame/model/ornl/neutron_data_selector.py,sha256=Nkj0DXdv3ydfXV3zeilbOGWuRCVzc_ClAOj6iNnN0uI,6276
6
- nova/trame/model/remote_file_input.py,sha256=9KAf31ZHzpsh_aXUrNcF81Q5jvUZDWCzW1QATKls-Jk,3675
6
+ nova/trame/model/remote_file_input.py,sha256=eAk7ZsFgNKcnpJ6KmOQDhiI6pPZpcrr1GMKkRLEWht8,4338
7
7
  nova/trame/view/components/__init__.py,sha256=60BeS69aOrFnkptjuD17rfPE1f4Z35iBH56TRmW5MW8,451
8
8
  nova/trame/view/components/data_selector.py,sha256=XFwIuKhIeBbkqsNEkIFDhjdDD5PD6cvbgn1jkivUhC4,14817
9
9
  nova/trame/view/components/execution_buttons.py,sha256=Br6uAmE5bY67TTYc5ZTHECNJ_RJqKmv17HAKPpQtbeg,4576
10
- nova/trame/view/components/file_upload.py,sha256=Q3t7TUJ8w6wlEqb1mnJ23yBsM1XmQPtm0awaoBrlLXo,3509
11
- nova/trame/view/components/input_field.py,sha256=Rtcl_eszvhgyC1rhTI7OMSLHjrE7DNH44eY08k7UXks,16094
10
+ nova/trame/view/components/file_upload.py,sha256=nRYwTPOzJV_TCtjk337PbeRbbIOf2PipaGKOyj0WKiA,4288
11
+ nova/trame/view/components/input_field.py,sha256=xzCmNEoB4ljGx99-gGgTV0UwriwtS8ce22zPA4QneZw,17372
12
12
  nova/trame/view/components/ornl/__init__.py,sha256=HnxzzSsxw0vQSDCVFfWsAxx1n3HnU37LMuQkfiewmSU,90
13
13
  nova/trame/view/components/ornl/neutron_data_selector.py,sha256=g633Ie3GSNu0QFEuexYl_XUxjUiGEJKjWLP7ZB5PRR0,13122
14
14
  nova/trame/view/components/progress_bar.py,sha256=zhbJwPy_HPQ8YL-ISN8sCRUQ7qY6qqo9wiV59BmvL8I,3038
15
- nova/trame/view/components/remote_file_input.py,sha256=6mUz6JZVhLO_Y4mZaQd_lpPe33KLtSpjxXS7uTNUmFI,10004
15
+ nova/trame/view/components/remote_file_input.py,sha256=cXSePj_eP3lf8bRsNEHT7dp-FO3doKkW44J3_9bgPAc,14082
16
16
  nova/trame/view/components/tool_outputs.py,sha256=IbYV4VjrkWAE354Bh5KH76SPsxGLIkOXChijS4-ce_Y,2408
17
17
  nova/trame/view/components/visualization/__init__.py,sha256=reqkkbhD5uSksHHlhVMy1qNUCwSekS5HlXk6wCREYxU,152
18
18
  nova/trame/view/components/visualization/interactive_2d_plot.py,sha256=z2s1janxAclpMEdDJk3z-CQ6r3KPNoR_SXPx9ppWnuQ,3481
@@ -25,7 +25,7 @@ nova/trame/view/layouts/vbox.py,sha256=XRV14e32MY1HWc9FTVTv1vOatWWbhLMd0lYwZP-is
25
25
  nova/trame/view/theme/__init__.py,sha256=70_marDlTigIcPEOGiJb2JTs-8b2sGM5SlY7XBPtBDM,54
26
26
  nova/trame/view/theme/assets/core_style.scss,sha256=3-3qMc5gpaDhfuVWAF_psBH5alxwiuK-hPGhVgi2cW0,4335
27
27
  nova/trame/view/theme/assets/favicon.png,sha256=Xbp1nUmhcBDeObjsebEbEAraPDZ_M163M_ZLtm5AbQc,1927
28
- nova/trame/view/theme/assets/js/delay_manager.js,sha256=mRV6KoO8-Bxq3tG5Bh9CQYy-CRVbkj3IYlqNb-Og7cI,526
28
+ nova/trame/view/theme/assets/js/delay_manager.js,sha256=BN4OL88QsyZG4XQ1sTorHpN1rwD4GnWoVKHvl5F5ydo,776
29
29
  nova/trame/view/theme/assets/js/lodash.min.js,sha256=KCyAYJ-fsqtp_HMwbjhy6IKjlA5lrVrtWt1JdMsC57k,73016
30
30
  nova/trame/view/theme/assets/js/revo_grid.js,sha256=81s0fUo8HbHmAyWag7pW0jP796Ttb1noAPOgTJlxJss,4069
31
31
  nova/trame/view/theme/assets/vuetify_config.json,sha256=a0FSgpLYWGFlRGSMhMq61MyDFBEBwvz55G4qjkM08cs,5627
@@ -36,10 +36,10 @@ nova/trame/view_model/data_selector.py,sha256=d8qdn6Q2b5fNo5lCXi1LTRdesfmy7wErIv
36
36
  nova/trame/view_model/execution_buttons.py,sha256=MfKSp95D92EqpD48C15cBo6dLO0Yld4FeRZMJNxJf7Y,3551
37
37
  nova/trame/view_model/ornl/neutron_data_selector.py,sha256=zpwvqETPuw0sEUvv0A2sU5Ha2_BKG_3xFYSaUuixLXw,1579
38
38
  nova/trame/view_model/progress_bar.py,sha256=6AUKHF3hfzbdsHqNEnmHRgDcBKY5TT8ywDx9S6ovnsc,2854
39
- nova/trame/view_model/remote_file_input.py,sha256=ojEOJ8ZPkajpbAaZi9VLj7g-uBjhb8BMrTdMmwf_J6A,3367
39
+ nova/trame/view_model/remote_file_input.py,sha256=zWOflmCDJYYR_pacHphwzricV667GSRokh-mlxpBAOo,3646
40
40
  nova/trame/view_model/tool_outputs.py,sha256=ev6LY7fJ0H2xAJn9f5ww28c8Kpom2SYc2FbvFcoN4zg,829
41
- nova_trame-0.25.3.dist-info/LICENSE,sha256=Iu5QiDbwNbREg75iYaxIJ_V-zppuv4QFuBhAW-qiAlM,1061
42
- nova_trame-0.25.3.dist-info/METADATA,sha256=fUOB8LAzrU6ZyuLaEwTISI81BTrr-JbSQC7gX1e0eZo,1727
43
- nova_trame-0.25.3.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
44
- nova_trame-0.25.3.dist-info/entry_points.txt,sha256=J2AmeSwiTYZ4ZqHHp9HO6v4MaYQTTBPbNh6WtoqOT58,42
45
- nova_trame-0.25.3.dist-info/RECORD,,
41
+ nova_trame-0.25.4.dist-info/LICENSE,sha256=Iu5QiDbwNbREg75iYaxIJ_V-zppuv4QFuBhAW-qiAlM,1061
42
+ nova_trame-0.25.4.dist-info/METADATA,sha256=WBxmym5PqxTIrvWt3kFC-LFofiAkYsdltjWN3X7iPa4,1727
43
+ nova_trame-0.25.4.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
44
+ nova_trame-0.25.4.dist-info/entry_points.txt,sha256=J2AmeSwiTYZ4ZqHHp9HO6v4MaYQTTBPbNh6WtoqOT58,42
45
+ nova_trame-0.25.4.dist-info/RECORD,,