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.
- nova/trame/model/remote_file_input.py +35 -13
- nova/trame/view/components/file_upload.py +27 -13
- nova/trame/view/components/input_field.py +48 -19
- nova/trame/view/components/remote_file_input.py +114 -37
- nova/trame/view/theme/assets/js/delay_manager.js +12 -6
- nova/trame/view_model/remote_file_input.py +14 -7
- {nova_trame-0.25.3.dist-info → nova_trame-0.25.4.dist-info}/METADATA +1 -1
- {nova_trame-0.25.3.dist-info → nova_trame-0.25.4.dist-info}/RECORD +11 -11
- {nova_trame-0.25.3.dist-info → nova_trame-0.25.4.dist-info}/LICENSE +0 -0
- {nova_trame-0.25.3.dist-info → nova_trame-0.25.4.dist-info}/WHEEL +0 -0
- {nova_trame-0.25.3.dist-info → nova_trame-0.25.4.dist-info}/entry_points.txt +0 -0
@@ -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
|
23
|
+
def __init__(self) -> None:
|
13
24
|
"""Creates a new RemoteFileInputModel."""
|
14
|
-
self.
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
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,
|
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:
|
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:
|
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
|
-
|
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=
|
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,
|
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:
|
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
|
87
|
+
if debounce and throttle:
|
82
88
|
raise ValueError("debounce and throttle cannot be used together")
|
83
89
|
|
84
|
-
|
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" '{
|
103
|
+
f" '{field}',"
|
89
104
|
f" () => flushState('{object_name_in_state}'),"
|
90
|
-
f" {
|
105
|
+
f" {debounce_field}"
|
91
106
|
")"
|
92
107
|
)
|
93
108
|
}
|
94
|
-
elif throttle
|
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" '{
|
121
|
+
f" '{field}',"
|
99
122
|
f" () => flushState('{object_name_in_state}'),"
|
100
|
-
f" {
|
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:
|
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 :
|
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
|
-
|
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 = {
|
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:
|
28
|
-
allow_files: bool = True,
|
29
|
-
allow_folders: bool = False,
|
30
|
-
|
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:
|
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 :
|
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
|
-
|
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 :
|
52
|
+
dialog_props : Dict[str, typing.Any], optional
|
53
53
|
Props to be passed to VDialog.
|
54
|
-
extensions :
|
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 :
|
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
|
-
|
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.
|
92
|
-
self.
|
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
|
179
|
-
self.model = RemoteFileInputModel(
|
177
|
+
def _create_model(self) -> None:
|
178
|
+
self.model = RemoteFileInputModel()
|
180
179
|
|
181
|
-
def
|
182
|
-
|
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
|
-
|
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] =
|
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] =
|
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)
|
@@ -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=
|
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=
|
11
|
-
nova/trame/view/components/input_field.py,sha256=
|
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=
|
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=
|
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=
|
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.
|
42
|
-
nova_trame-0.25.
|
43
|
-
nova_trame-0.25.
|
44
|
-
nova_trame-0.25.
|
45
|
-
nova_trame-0.25.
|
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,,
|
File without changes
|
File without changes
|
File without changes
|