nova-trame 0.22.1__py3-none-any.whl → 0.23.1__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/data_selector.py +29 -162
- nova/trame/model/ornl/neutron_data_selector.py +193 -0
- nova/trame/view/components/data_selector.py +53 -79
- nova/trame/view/components/ornl/__init__.py +3 -0
- nova/trame/view/components/ornl/neutron_data_selector.py +155 -0
- nova/trame/view/theme/assets/core_style.scss +8 -2
- nova/trame/view_model/data_selector.py +17 -35
- nova/trame/view_model/ornl/neutron_data_selector.py +51 -0
- {nova_trame-0.22.1.dist-info → nova_trame-0.23.1.dist-info}/METADATA +2 -2
- {nova_trame-0.22.1.dist-info → nova_trame-0.23.1.dist-info}/RECORD +13 -9
- {nova_trame-0.22.1.dist-info → nova_trame-0.23.1.dist-info}/LICENSE +0 -0
- {nova_trame-0.22.1.dist-info → nova_trame-0.23.1.dist-info}/WHEEL +0 -0
- {nova_trame-0.22.1.dist-info → nova_trame-0.23.1.dist-info}/entry_points.txt +0 -0
@@ -3,142 +3,29 @@
|
|
3
3
|
import os
|
4
4
|
from pathlib import Path
|
5
5
|
from typing import Any, Dict, List, Optional
|
6
|
-
from warnings import warn
|
7
6
|
|
8
7
|
from natsort import natsorted
|
9
|
-
from pydantic import BaseModel, Field
|
10
|
-
from typing_extensions import Self
|
11
|
-
|
12
|
-
CUSTOM_DIRECTORIES_LABEL = "Custom Directory"
|
13
|
-
|
14
|
-
INSTRUMENTS = {
|
15
|
-
"HFIR": {
|
16
|
-
"CG-1A": "CG1A",
|
17
|
-
"CG-1B": "CG1B",
|
18
|
-
"CG-1D": "CG1D",
|
19
|
-
"CG-2": "CG2",
|
20
|
-
"CG-3": "CG3",
|
21
|
-
"CG-4B": "CG4B",
|
22
|
-
"CG-4C": "CG4C",
|
23
|
-
"CG-4D": "CG4D",
|
24
|
-
"HB-1": "HB1",
|
25
|
-
"HB-1A": "HB1A",
|
26
|
-
"HB-2A": "HB2A",
|
27
|
-
"HB-2B": "HB2B",
|
28
|
-
"HB-2C": "HB2C",
|
29
|
-
"HB-3": "HB3",
|
30
|
-
"HB-3A": "HB3A",
|
31
|
-
"NOW-G": "NOWG",
|
32
|
-
"NOW-V": "NOWV",
|
33
|
-
},
|
34
|
-
"SNS": {
|
35
|
-
"BL-18": "ARCS",
|
36
|
-
"BL-0": "BL0",
|
37
|
-
"BL-2": "BSS",
|
38
|
-
"BL-5": "CNCS",
|
39
|
-
"BL-9": "CORELLI",
|
40
|
-
"BL-6": "EQSANS",
|
41
|
-
"BL-14B": "HYS",
|
42
|
-
"BL-11B": "MANDI",
|
43
|
-
"BL-1B": "NOM",
|
44
|
-
"NOW-G": "NOWG",
|
45
|
-
"BL-15": "NSE",
|
46
|
-
"BL-11A": "PG3",
|
47
|
-
"BL-4B": "REF_L",
|
48
|
-
"BL-4A": "REF_M",
|
49
|
-
"BL-17": "SEQ",
|
50
|
-
"BL-3": "SNAP",
|
51
|
-
"BL-12": "TOPAZ",
|
52
|
-
"BL-1A": "USANS",
|
53
|
-
"BL-10": "VENUS",
|
54
|
-
"BL-16B": "VIS",
|
55
|
-
"BL-7": "VULCAN",
|
56
|
-
},
|
57
|
-
}
|
8
|
+
from pydantic import BaseModel, Field
|
58
9
|
|
59
10
|
|
60
11
|
class DataSelectorState(BaseModel, validate_assignment=True):
|
61
12
|
"""Selection state for identifying datafiles."""
|
62
13
|
|
63
|
-
allow_custom_directories: bool = Field(default=False)
|
64
|
-
facility: str = Field(default="", title="Facility")
|
65
|
-
instrument: str = Field(default="", title="Instrument")
|
66
|
-
experiment: str = Field(default="", title="Experiment")
|
67
|
-
custom_directory: str = Field(default="", title="Custom Directory")
|
68
14
|
directory: str = Field(default="")
|
15
|
+
subdirectory: str = Field(default="")
|
69
16
|
extensions: List[str] = Field(default=[])
|
70
17
|
prefix: str = Field(default="")
|
71
18
|
|
72
|
-
@field_validator("experiment", mode="after")
|
73
|
-
@classmethod
|
74
|
-
def validate_experiment(cls, experiment: str) -> str:
|
75
|
-
if experiment and not experiment.startswith("IPTS-"):
|
76
|
-
raise ValueError("experiment must begin with IPTS-")
|
77
|
-
return experiment
|
78
|
-
|
79
|
-
@model_validator(mode="after")
|
80
|
-
def validate_state(self) -> Self:
|
81
|
-
valid_facilities = self.get_facilities()
|
82
|
-
if self.facility and self.facility not in valid_facilities:
|
83
|
-
warn(f"Facility '{self.facility}' could not be found. Valid options: {valid_facilities}", stacklevel=1)
|
84
|
-
|
85
|
-
valid_instruments = self.get_instruments()
|
86
|
-
if self.instrument and self.facility != CUSTOM_DIRECTORIES_LABEL and self.instrument not in valid_instruments:
|
87
|
-
warn(
|
88
|
-
(
|
89
|
-
f"Instrument '{self.instrument}' could not be found in '{self.facility}'. "
|
90
|
-
f"Valid options: {valid_instruments}"
|
91
|
-
),
|
92
|
-
stacklevel=1,
|
93
|
-
)
|
94
|
-
# Validating the experiment is expensive and will fail in our CI due to the filesystem not being mounted there.
|
95
|
-
|
96
|
-
return self
|
97
|
-
|
98
|
-
def get_facilities(self) -> List[str]:
|
99
|
-
facilities = list(INSTRUMENTS.keys())
|
100
|
-
if self.allow_custom_directories:
|
101
|
-
facilities.append(CUSTOM_DIRECTORIES_LABEL)
|
102
|
-
return facilities
|
103
|
-
|
104
|
-
def get_instruments(self) -> List[str]:
|
105
|
-
return list(INSTRUMENTS.get(self.facility, {}).keys())
|
106
|
-
|
107
19
|
|
108
20
|
class DataSelectorModel:
|
109
21
|
"""Manages file system interactions for the DataSelector widget."""
|
110
22
|
|
111
|
-
def __init__(
|
112
|
-
self
|
113
|
-
|
114
|
-
self.state =
|
115
|
-
self.state.facility = facility
|
116
|
-
self.state.instrument = instrument
|
23
|
+
def __init__(self, state: DataSelectorState, directory: str, extensions: List[str], prefix: str) -> None:
|
24
|
+
self.state: DataSelectorState = state
|
25
|
+
|
26
|
+
self.state.directory = directory
|
117
27
|
self.state.extensions = extensions
|
118
28
|
self.state.prefix = prefix
|
119
|
-
self.state.allow_custom_directories = allow_custom_directories
|
120
|
-
|
121
|
-
def get_facilities(self) -> List[str]:
|
122
|
-
return natsorted(self.state.get_facilities())
|
123
|
-
|
124
|
-
def get_instrument_dir(self) -> str:
|
125
|
-
return INSTRUMENTS.get(self.state.facility, {}).get(self.state.instrument, "")
|
126
|
-
|
127
|
-
def get_instruments(self) -> List[str]:
|
128
|
-
return natsorted(self.state.get_instruments())
|
129
|
-
|
130
|
-
def get_experiments(self) -> List[str]:
|
131
|
-
experiments = []
|
132
|
-
|
133
|
-
instrument_path = Path("/") / self.state.facility / self.get_instrument_dir()
|
134
|
-
try:
|
135
|
-
for dirname in os.listdir(instrument_path):
|
136
|
-
if dirname.startswith("IPTS-") and os.access(instrument_path / dirname, mode=os.R_OK):
|
137
|
-
experiments.append(dirname)
|
138
|
-
except OSError:
|
139
|
-
pass
|
140
|
-
|
141
|
-
return natsorted(experiments)
|
142
29
|
|
143
30
|
def sort_directories(self, directories: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
144
31
|
# Sort the current level of dictionaries
|
@@ -151,31 +38,7 @@ class DataSelectorModel:
|
|
151
38
|
|
152
39
|
return sorted_dirs
|
153
40
|
|
154
|
-
def
|
155
|
-
if not self.state.experiment:
|
156
|
-
return None
|
157
|
-
|
158
|
-
return Path("/") / self.state.facility / self.get_instrument_dir() / self.state.experiment
|
159
|
-
|
160
|
-
def get_custom_directory_path(self) -> Optional[Path]:
|
161
|
-
# Don't expose the full file system
|
162
|
-
if not self.state.custom_directory:
|
163
|
-
return None
|
164
|
-
|
165
|
-
return Path(self.state.custom_directory)
|
166
|
-
|
167
|
-
def get_directories(self, base_path: Optional[Path] = None) -> List[Dict[str, Any]]:
|
168
|
-
using_custom_directory = self.state.facility == CUSTOM_DIRECTORIES_LABEL
|
169
|
-
if base_path:
|
170
|
-
pass
|
171
|
-
elif using_custom_directory:
|
172
|
-
base_path = self.get_custom_directory_path()
|
173
|
-
else:
|
174
|
-
base_path = self.get_experiment_directory_path()
|
175
|
-
|
176
|
-
if not base_path:
|
177
|
-
return []
|
178
|
-
|
41
|
+
def get_directories_from_path(self, base_path: Path) -> List[Dict[str, Any]]:
|
179
42
|
directories = []
|
180
43
|
try:
|
181
44
|
for dirpath, dirs, _ in os.walk(base_path):
|
@@ -184,6 +47,10 @@ class DataSelectorModel:
|
|
184
47
|
|
185
48
|
if len(path_parts) > 1:
|
186
49
|
dirs.clear()
|
50
|
+
elif path_parts != ["."]:
|
51
|
+
# Subdirectories are fully queried upon being opened, so we only need to query one item to determine
|
52
|
+
# if the target directory has any children.
|
53
|
+
dirs[:] = dirs[:1]
|
187
54
|
|
188
55
|
# Only create a new entry for top-level directories
|
189
56
|
if len(path_parts) == 1 and path_parts[0] != ".": # This indicates a top-level directory
|
@@ -208,21 +75,24 @@ class DataSelectorModel:
|
|
208
75
|
|
209
76
|
return self.sort_directories(directories)
|
210
77
|
|
211
|
-
def
|
212
|
-
|
213
|
-
|
214
|
-
if self.state.experiment:
|
215
|
-
base_path = Path("/") / self.state.facility / self.get_instrument_dir() / self.state.experiment
|
216
|
-
elif self.state.custom_directory:
|
217
|
-
base_path = Path(self.state.custom_directory)
|
78
|
+
def get_directories(self, base_path: Optional[Path] = None) -> List[Dict[str, Any]]:
|
79
|
+
if base_path:
|
80
|
+
pass
|
218
81
|
else:
|
82
|
+
base_path = Path(self.state.directory)
|
83
|
+
|
84
|
+
if not base_path:
|
219
85
|
return []
|
220
86
|
|
87
|
+
return self.get_directories_from_path(base_path)
|
88
|
+
|
89
|
+
def get_datafiles_from_path(self, base_path: Path) -> List[str]:
|
90
|
+
datafiles = []
|
221
91
|
try:
|
222
92
|
if self.state.prefix:
|
223
|
-
datafile_path =
|
93
|
+
datafile_path = base_path / self.state.prefix
|
224
94
|
else:
|
225
|
-
datafile_path =
|
95
|
+
datafile_path = base_path / self.state.subdirectory
|
226
96
|
|
227
97
|
for entry in os.scandir(datafile_path):
|
228
98
|
if entry.is_file():
|
@@ -237,13 +107,10 @@ class DataSelectorModel:
|
|
237
107
|
|
238
108
|
return natsorted(datafiles)
|
239
109
|
|
240
|
-
def
|
241
|
-
self.state.directory
|
110
|
+
def get_datafiles(self) -> List[str]:
|
111
|
+
base_path = Path(self.state.directory)
|
112
|
+
|
113
|
+
return self.get_datafiles_from_path(base_path)
|
242
114
|
|
243
|
-
def
|
244
|
-
|
245
|
-
self.state.facility = facility
|
246
|
-
if instrument is not None:
|
247
|
-
self.state.instrument = instrument
|
248
|
-
if experiment is not None:
|
249
|
-
self.state.experiment = experiment
|
115
|
+
def set_subdirectory(self, subdirectory_path: str) -> None:
|
116
|
+
self.state.subdirectory = subdirectory_path
|
@@ -0,0 +1,193 @@
|
|
1
|
+
"""Model implementation for DataSelector."""
|
2
|
+
|
3
|
+
import os
|
4
|
+
from pathlib import Path
|
5
|
+
from typing import Any, Dict, List, Optional
|
6
|
+
from warnings import warn
|
7
|
+
|
8
|
+
from natsort import natsorted
|
9
|
+
from pydantic import Field, field_validator, model_validator
|
10
|
+
from typing_extensions import Self
|
11
|
+
|
12
|
+
from ..data_selector import DataSelectorModel, DataSelectorState
|
13
|
+
|
14
|
+
CUSTOM_DIRECTORIES_LABEL = "Custom Directory"
|
15
|
+
|
16
|
+
INSTRUMENTS = {
|
17
|
+
"HFIR": {
|
18
|
+
"CG-1A": "CG1A",
|
19
|
+
"CG-1B": "CG1B",
|
20
|
+
"CG-1D": "CG1D",
|
21
|
+
"CG-2": "CG2",
|
22
|
+
"CG-3": "CG3",
|
23
|
+
"CG-4B": "CG4B",
|
24
|
+
"CG-4C": "CG4C",
|
25
|
+
"CG-4D": "CG4D",
|
26
|
+
"HB-1": "HB1",
|
27
|
+
"HB-1A": "HB1A",
|
28
|
+
"HB-2A": "HB2A",
|
29
|
+
"HB-2B": "HB2B",
|
30
|
+
"HB-2C": "HB2C",
|
31
|
+
"HB-3": "HB3",
|
32
|
+
"HB-3A": "HB3A",
|
33
|
+
"NOW-G": "NOWG",
|
34
|
+
"NOW-V": "NOWV",
|
35
|
+
},
|
36
|
+
"SNS": {
|
37
|
+
"BL-18": "ARCS",
|
38
|
+
"BL-0": "BL0",
|
39
|
+
"BL-2": "BSS",
|
40
|
+
"BL-5": "CNCS",
|
41
|
+
"BL-9": "CORELLI",
|
42
|
+
"BL-6": "EQSANS",
|
43
|
+
"BL-14B": "HYS",
|
44
|
+
"BL-11B": "MANDI",
|
45
|
+
"BL-1B": "NOM",
|
46
|
+
"NOW-G": "NOWG",
|
47
|
+
"BL-15": "NSE",
|
48
|
+
"BL-11A": "PG3",
|
49
|
+
"BL-4B": "REF_L",
|
50
|
+
"BL-4A": "REF_M",
|
51
|
+
"BL-17": "SEQ",
|
52
|
+
"BL-3": "SNAP",
|
53
|
+
"BL-12": "TOPAZ",
|
54
|
+
"BL-1A": "USANS",
|
55
|
+
"BL-10": "VENUS",
|
56
|
+
"BL-16B": "VIS",
|
57
|
+
"BL-7": "VULCAN",
|
58
|
+
},
|
59
|
+
}
|
60
|
+
|
61
|
+
|
62
|
+
class NeutronDataSelectorState(DataSelectorState):
|
63
|
+
"""Selection state for identifying datafiles."""
|
64
|
+
|
65
|
+
allow_custom_directories: bool = Field(default=False)
|
66
|
+
facility: str = Field(default="", title="Facility")
|
67
|
+
instrument: str = Field(default="", title="Instrument")
|
68
|
+
experiment: str = Field(default="", title="Experiment")
|
69
|
+
custom_directory: str = Field(default="", title="Custom Directory")
|
70
|
+
|
71
|
+
@field_validator("experiment", mode="after")
|
72
|
+
@classmethod
|
73
|
+
def validate_experiment(cls, experiment: str) -> str:
|
74
|
+
if experiment and not experiment.startswith("IPTS-"):
|
75
|
+
raise ValueError("experiment must begin with IPTS-")
|
76
|
+
return experiment
|
77
|
+
|
78
|
+
@model_validator(mode="after")
|
79
|
+
def validate_state(self) -> Self:
|
80
|
+
valid_facilities = self.get_facilities()
|
81
|
+
if self.facility and self.facility not in valid_facilities:
|
82
|
+
warn(f"Facility '{self.facility}' could not be found. Valid options: {valid_facilities}", stacklevel=1)
|
83
|
+
|
84
|
+
valid_instruments = self.get_instruments()
|
85
|
+
if self.instrument and self.facility != CUSTOM_DIRECTORIES_LABEL and self.instrument not in valid_instruments:
|
86
|
+
warn(
|
87
|
+
(
|
88
|
+
f"Instrument '{self.instrument}' could not be found in '{self.facility}'. "
|
89
|
+
f"Valid options: {valid_instruments}"
|
90
|
+
),
|
91
|
+
stacklevel=1,
|
92
|
+
)
|
93
|
+
# Validating the experiment is expensive and will fail in our CI due to the filesystem not being mounted there.
|
94
|
+
|
95
|
+
return self
|
96
|
+
|
97
|
+
def get_facilities(self) -> List[str]:
|
98
|
+
facilities = list(INSTRUMENTS.keys())
|
99
|
+
if self.allow_custom_directories:
|
100
|
+
facilities.append(CUSTOM_DIRECTORIES_LABEL)
|
101
|
+
return facilities
|
102
|
+
|
103
|
+
def get_instruments(self) -> List[str]:
|
104
|
+
return list(INSTRUMENTS.get(self.facility, {}).keys())
|
105
|
+
|
106
|
+
|
107
|
+
class NeutronDataSelectorModel(DataSelectorModel):
|
108
|
+
"""Manages file system interactions for the DataSelector widget."""
|
109
|
+
|
110
|
+
def __init__(
|
111
|
+
self,
|
112
|
+
state: NeutronDataSelectorState,
|
113
|
+
facility: str,
|
114
|
+
instrument: str,
|
115
|
+
extensions: List[str],
|
116
|
+
prefix: str,
|
117
|
+
allow_custom_directories: bool,
|
118
|
+
) -> None:
|
119
|
+
super().__init__(state, "", extensions, prefix)
|
120
|
+
self.state: NeutronDataSelectorState = state
|
121
|
+
|
122
|
+
self.state.facility = facility
|
123
|
+
self.state.instrument = instrument
|
124
|
+
self.state.extensions = extensions
|
125
|
+
self.state.prefix = prefix
|
126
|
+
self.state.allow_custom_directories = allow_custom_directories
|
127
|
+
|
128
|
+
def get_facilities(self) -> List[str]:
|
129
|
+
return natsorted(self.state.get_facilities())
|
130
|
+
|
131
|
+
def get_instrument_dir(self) -> str:
|
132
|
+
return INSTRUMENTS.get(self.state.facility, {}).get(self.state.instrument, "")
|
133
|
+
|
134
|
+
def get_instruments(self) -> List[str]:
|
135
|
+
return natsorted(self.state.get_instruments())
|
136
|
+
|
137
|
+
def get_experiments(self) -> List[str]:
|
138
|
+
experiments = []
|
139
|
+
|
140
|
+
instrument_path = Path("/") / self.state.facility / self.get_instrument_dir()
|
141
|
+
try:
|
142
|
+
for dirname in os.listdir(instrument_path):
|
143
|
+
if dirname.startswith("IPTS-") and os.access(instrument_path / dirname, mode=os.R_OK):
|
144
|
+
experiments.append(dirname)
|
145
|
+
except OSError:
|
146
|
+
pass
|
147
|
+
|
148
|
+
return natsorted(experiments)
|
149
|
+
|
150
|
+
def get_experiment_directory_path(self) -> Optional[Path]:
|
151
|
+
if not self.state.experiment:
|
152
|
+
return None
|
153
|
+
|
154
|
+
return Path("/") / self.state.facility / self.get_instrument_dir() / self.state.experiment
|
155
|
+
|
156
|
+
def get_custom_directory_path(self) -> Optional[Path]:
|
157
|
+
# Don't expose the full file system
|
158
|
+
if not self.state.custom_directory:
|
159
|
+
return None
|
160
|
+
|
161
|
+
return Path(self.state.custom_directory)
|
162
|
+
|
163
|
+
def get_directories(self, base_path: Optional[Path] = None) -> List[Dict[str, Any]]:
|
164
|
+
using_custom_directory = self.state.facility == CUSTOM_DIRECTORIES_LABEL
|
165
|
+
if base_path:
|
166
|
+
pass
|
167
|
+
elif using_custom_directory:
|
168
|
+
base_path = self.get_custom_directory_path()
|
169
|
+
else:
|
170
|
+
base_path = self.get_experiment_directory_path()
|
171
|
+
|
172
|
+
if not base_path:
|
173
|
+
return []
|
174
|
+
|
175
|
+
return self.get_directories_from_path(base_path)
|
176
|
+
|
177
|
+
def get_datafiles(self) -> List[str]:
|
178
|
+
if self.state.experiment:
|
179
|
+
base_path = Path("/") / self.state.facility / self.get_instrument_dir() / self.state.experiment
|
180
|
+
elif self.state.custom_directory:
|
181
|
+
base_path = Path(self.state.custom_directory)
|
182
|
+
else:
|
183
|
+
return []
|
184
|
+
|
185
|
+
return self.get_datafiles_from_path(base_path)
|
186
|
+
|
187
|
+
def set_state(self, facility: Optional[str], instrument: Optional[str], experiment: Optional[str]) -> None:
|
188
|
+
if facility is not None:
|
189
|
+
self.state.facility = facility
|
190
|
+
if instrument is not None:
|
191
|
+
self.state.instrument = instrument
|
192
|
+
if experiment is not None:
|
193
|
+
self.state.experiment = experiment
|
@@ -1,15 +1,15 @@
|
|
1
1
|
"""View Implementation for DataSelector."""
|
2
2
|
|
3
|
+
from asyncio import ensure_future, sleep
|
3
4
|
from typing import Any, List, Optional, cast
|
4
|
-
from warnings import warn
|
5
5
|
|
6
6
|
from trame.app import get_server
|
7
7
|
from trame.widgets import client, datagrid, html
|
8
8
|
from trame.widgets import vuetify3 as vuetify
|
9
9
|
|
10
10
|
from nova.mvvm.trame_binding import TrameBinding
|
11
|
-
from nova.trame.model.data_selector import
|
12
|
-
from nova.trame.view.layouts import GridLayout, VBoxLayout
|
11
|
+
from nova.trame.model.data_selector import DataSelectorModel, DataSelectorState
|
12
|
+
from nova.trame.view.layouts import GridLayout, HBoxLayout, VBoxLayout
|
13
13
|
from nova.trame.view_model.data_selector import DataSelectorViewModel
|
14
14
|
|
15
15
|
from .input_field import InputField
|
@@ -18,16 +18,15 @@ vuetify.enable_lab()
|
|
18
18
|
|
19
19
|
|
20
20
|
class DataSelector(datagrid.VGrid):
|
21
|
-
"""Allows the user to select datafiles from
|
21
|
+
"""Allows the user to select datafiles from the server."""
|
22
22
|
|
23
23
|
def __init__(
|
24
24
|
self,
|
25
25
|
v_model: str,
|
26
|
-
|
27
|
-
facility: str = "",
|
28
|
-
instrument: str = "",
|
26
|
+
directory: str,
|
29
27
|
extensions: Optional[List[str]] = None,
|
30
28
|
prefix: str = "",
|
29
|
+
refresh_rate: int = 30,
|
31
30
|
select_strategy: str = "all",
|
32
31
|
**kwargs: Any,
|
33
32
|
) -> None:
|
@@ -38,18 +37,17 @@ class DataSelector(datagrid.VGrid):
|
|
38
37
|
v_model : str
|
39
38
|
The name of the state variable to bind to this widget. The state variable will contain a list of the files
|
40
39
|
selected by the user.
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
facility : str, optional
|
45
|
-
The facility to restrict data selection to. Options: HFIR, SNS
|
46
|
-
instrument : str, optional
|
47
|
-
The instrument to restrict data selection to. Please use the instrument acronym (e.g. CG-2).
|
40
|
+
directory : str
|
41
|
+
The top-level folder to expose to users. Only contents of this directory and its children will be exposed to
|
42
|
+
users.
|
48
43
|
extensions : List[str], optional
|
49
44
|
A list of file extensions to restrict selection to. If unset, then all files will be shown.
|
50
45
|
prefix : str, optional
|
51
|
-
A subdirectory within the
|
52
|
-
folder browser and will be able to see all files in the
|
46
|
+
A subdirectory within the selected top-level folder to show files. If not specified, the user will be shown
|
47
|
+
a folder browser and will be able to see all files in the selected top-level folder.
|
48
|
+
refresh_rate : int, optional
|
49
|
+
The number of seconds between attempts to automatically refresh the file list. Set to zero to disable this
|
50
|
+
feature. Defaults to 30 seconds.
|
53
51
|
select_strategy : str, optional
|
54
52
|
The selection strategy to pass to the `VDataTable component <https://trame.readthedocs.io/en/latest/trame.widgets.vuetify3.html#trame.widgets.vuetify3.VDataTable>`__.
|
55
53
|
If unset, the `all` strategy will be used.
|
@@ -61,6 +59,12 @@ class DataSelector(datagrid.VGrid):
|
|
61
59
|
-------
|
62
60
|
None
|
63
61
|
"""
|
62
|
+
if "allow_custom_directory" in kwargs or "facility" in kwargs or "instrument" in kwargs:
|
63
|
+
raise TypeError(
|
64
|
+
"The old DataSelector component has been renamed to NeutronDataSelector. Please import it from "
|
65
|
+
"`nova.trame.view.components.ornl`."
|
66
|
+
)
|
67
|
+
|
64
68
|
if "items" in kwargs:
|
65
69
|
raise AttributeError("The items parameter is not allowed on DataSelector widget.")
|
66
70
|
|
@@ -69,21 +73,16 @@ class DataSelector(datagrid.VGrid):
|
|
69
73
|
else:
|
70
74
|
self._label = None
|
71
75
|
|
72
|
-
if facility and allow_custom_directories:
|
73
|
-
warn("allow_custom_directories will be ignored since the facility parameter is set.", stacklevel=1)
|
74
|
-
|
75
76
|
self._v_model = v_model
|
76
77
|
self._v_model_name_in_state = v_model.split(".")[0]
|
77
|
-
self.
|
78
|
+
self._directory = directory
|
78
79
|
self._extensions = extensions if extensions is not None else []
|
79
80
|
self._prefix = prefix
|
81
|
+
self._refresh_rate = refresh_rate
|
80
82
|
self._select_strategy = select_strategy
|
81
83
|
|
82
84
|
self._revogrid_id = f"nova__dataselector_{self._next_id}_rv"
|
83
85
|
self._state_name = f"nova__dataselector_{self._next_id}_state"
|
84
|
-
self._facilities_name = f"nova__dataselector_{self._next_id}_facilities"
|
85
|
-
self._instruments_name = f"nova__dataselector_{self._next_id}_instruments"
|
86
|
-
self._experiments_name = f"nova__dataselector_{self._next_id}_experiments"
|
87
86
|
self._directories_name = f"nova__dataselector_{self._next_id}_directories"
|
88
87
|
self._datafiles_name = f"nova__dataselector_{self._next_id}_datafiles"
|
89
88
|
|
@@ -93,36 +92,22 @@ class DataSelector(datagrid.VGrid):
|
|
93
92
|
).exec
|
94
93
|
self._reset_state = client.JSEval(exec=f"{self._v_model} = []; {self._flush_state}").exec
|
95
94
|
|
96
|
-
self.create_model(
|
95
|
+
self.create_model()
|
97
96
|
self.create_viewmodel()
|
98
97
|
|
99
|
-
self.create_ui(
|
98
|
+
self.create_ui(**kwargs)
|
100
99
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
InputField(
|
113
|
-
v_if=f"{self._state_name}.facility !== '{CUSTOM_DIRECTORIES_LABEL}'",
|
114
|
-
v_model=f"{self._state_name}.instrument",
|
115
|
-
items=(self._instruments_name,),
|
116
|
-
type="autocomplete",
|
117
|
-
)
|
118
|
-
InputField(
|
119
|
-
v_if=f"{self._state_name}.facility !== '{CUSTOM_DIRECTORIES_LABEL}'",
|
120
|
-
v_model=f"{self._state_name}.experiment",
|
121
|
-
column_span=columns,
|
122
|
-
items=(self._experiments_name,),
|
123
|
-
type="autocomplete",
|
124
|
-
)
|
125
|
-
InputField(v_else=True, v_model=f"{self._state_name}.custom_directory", column_span=2)
|
100
|
+
ensure_future(self._refresh_loop())
|
101
|
+
|
102
|
+
def create_ui(self, *args: Any, **kwargs: Any) -> None:
|
103
|
+
with VBoxLayout(classes="nova-data-selector", height="100%") as self._layout:
|
104
|
+
with HBoxLayout(valign="center"):
|
105
|
+
self._layout.filter = html.Div(classes="flex-1-1")
|
106
|
+
with vuetify.VBtn(
|
107
|
+
classes="mx-1", density="compact", icon=True, variant="text", click=self.refresh_contents
|
108
|
+
):
|
109
|
+
vuetify.VIcon("mdi-refresh")
|
110
|
+
vuetify.VTooltip("Refresh Contents", activator="parent")
|
126
111
|
|
127
112
|
with GridLayout(columns=2, classes="flex-1-0 h-0", valign="start"):
|
128
113
|
if not self._prefix:
|
@@ -137,7 +122,7 @@ class DataSelector(datagrid.VGrid):
|
|
137
122
|
item_value="path",
|
138
123
|
items=(self._directories_name,),
|
139
124
|
click_open=(self._vm.expand_directory, "[$event.path]"),
|
140
|
-
update_activated=(self._vm.
|
125
|
+
update_activated=(self._vm.set_subdirectory, "$event"),
|
141
126
|
)
|
142
127
|
vuetify.VListItem("No directories found", classes="flex-0-1 text-center", v_else=True)
|
143
128
|
|
@@ -198,10 +183,9 @@ class DataSelector(datagrid.VGrid):
|
|
198
183
|
f"(+{{{{ {self._v_model}.length - 2 }}}} others)", v_if="index === 2", classes="text-caption"
|
199
184
|
)
|
200
185
|
|
201
|
-
def create_model(self
|
202
|
-
|
203
|
-
|
204
|
-
)
|
186
|
+
def create_model(self) -> None:
|
187
|
+
state = DataSelectorState()
|
188
|
+
self._model = DataSelectorModel(state, self._directory, self._extensions, self._prefix)
|
205
189
|
|
206
190
|
def create_viewmodel(self) -> None:
|
207
191
|
server = get_server(None, client_type="vue3")
|
@@ -209,39 +193,29 @@ class DataSelector(datagrid.VGrid):
|
|
209
193
|
|
210
194
|
self._vm = DataSelectorViewModel(self._model, binding)
|
211
195
|
self._vm.state_bind.connect(self._state_name)
|
212
|
-
self._vm.facilities_bind.connect(self._facilities_name)
|
213
|
-
self._vm.instruments_bind.connect(self._instruments_name)
|
214
|
-
self._vm.experiments_bind.connect(self._experiments_name)
|
215
196
|
self._vm.directories_bind.connect(self._directories_name)
|
216
197
|
self._vm.datafiles_bind.connect(self._datafiles_name)
|
217
198
|
self._vm.reset_bind.connect(self.reset)
|
218
199
|
|
219
200
|
self._vm.update_view()
|
220
201
|
|
202
|
+
def refresh_contents(self) -> None:
|
203
|
+
self._vm.update_view(refresh_directories=True)
|
204
|
+
|
221
205
|
def reset(self, _: Any = None) -> None:
|
222
206
|
self._reset_state()
|
223
207
|
self._reset_rv_grid()
|
224
208
|
|
225
|
-
def set_state(
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
If a parameter is None, then it will not be updated.
|
209
|
+
def set_state(self, *args: Any, **kwargs: Any) -> None:
|
210
|
+
raise TypeError(
|
211
|
+
"The old DataSelector component has been renamed to NeutronDataSelector. Please import it from "
|
212
|
+
"`nova.trame.view.components.ornl`."
|
213
|
+
)
|
231
214
|
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
instrument : str, optional
|
237
|
-
The instrument to restrict data selection to. Must be at the selected facility.
|
238
|
-
experiment : str, optional
|
239
|
-
The experiment to restrict data selection to. Must begin with "IPTS-". It is your responsibility to validate
|
240
|
-
that the provided experiment exists within the instrument directory. If it doesn't then no datafiles will be
|
241
|
-
shown to the user.
|
215
|
+
async def _refresh_loop(self) -> None:
|
216
|
+
if self._refresh_rate > 0:
|
217
|
+
while True:
|
218
|
+
await sleep(self._refresh_rate)
|
242
219
|
|
243
|
-
|
244
|
-
|
245
|
-
None
|
246
|
-
"""
|
247
|
-
self._vm.set_state(facility, instrument, experiment)
|
220
|
+
self.refresh_contents()
|
221
|
+
self.state.dirty(self._datafiles_name)
|
@@ -0,0 +1,155 @@
|
|
1
|
+
"""View Implementation for DataSelector."""
|
2
|
+
|
3
|
+
from typing import Any, List, Optional
|
4
|
+
from warnings import warn
|
5
|
+
|
6
|
+
from trame.app import get_server
|
7
|
+
from trame.widgets import vuetify3 as vuetify
|
8
|
+
|
9
|
+
from nova.mvvm.trame_binding import TrameBinding
|
10
|
+
from nova.trame.model.ornl.neutron_data_selector import (
|
11
|
+
CUSTOM_DIRECTORIES_LABEL,
|
12
|
+
NeutronDataSelectorModel,
|
13
|
+
NeutronDataSelectorState,
|
14
|
+
)
|
15
|
+
from nova.trame.view.layouts import GridLayout
|
16
|
+
from nova.trame.view_model.ornl.neutron_data_selector import NeutronDataSelectorViewModel
|
17
|
+
|
18
|
+
from ..data_selector import DataSelector
|
19
|
+
from ..input_field import InputField
|
20
|
+
|
21
|
+
vuetify.enable_lab()
|
22
|
+
|
23
|
+
|
24
|
+
class NeutronDataSelector(DataSelector):
|
25
|
+
"""Allows the user to select datafiles from an IPTS experiment."""
|
26
|
+
|
27
|
+
def __init__(
|
28
|
+
self,
|
29
|
+
v_model: str,
|
30
|
+
allow_custom_directories: bool = False,
|
31
|
+
facility: str = "",
|
32
|
+
instrument: str = "",
|
33
|
+
extensions: Optional[List[str]] = None,
|
34
|
+
prefix: str = "",
|
35
|
+
refresh_rate: int = 30,
|
36
|
+
select_strategy: str = "all",
|
37
|
+
**kwargs: Any,
|
38
|
+
) -> None:
|
39
|
+
"""Constructor for DataSelector.
|
40
|
+
|
41
|
+
Parameters
|
42
|
+
----------
|
43
|
+
v_model : str
|
44
|
+
The name of the state variable to bind to this widget. The state variable will contain a list of the files
|
45
|
+
selected by the user.
|
46
|
+
allow_custom_directories : bool, optional
|
47
|
+
Whether or not to allow users to provide their own directories to search for datafiles in. Ignored if the
|
48
|
+
facility parameter is set.
|
49
|
+
facility : str, optional
|
50
|
+
The facility to restrict data selection to. Options: HFIR, SNS
|
51
|
+
instrument : str, optional
|
52
|
+
The instrument to restrict data selection to. Please use the instrument acronym (e.g. CG-2).
|
53
|
+
extensions : List[str], optional
|
54
|
+
A list of file extensions to restrict selection to. If unset, then all files will be shown.
|
55
|
+
prefix : str, optional
|
56
|
+
A subdirectory within the user's chosen experiment to show files. If not specified, the user will be shown a
|
57
|
+
folder browser and will be able to see all files in the experiment that they have access to.
|
58
|
+
refresh_rate : int, optional
|
59
|
+
The number of seconds between attempts to automatically refresh the file list. Set to zero to disable this
|
60
|
+
feature. Defaults to 30 seconds.
|
61
|
+
select_strategy : str, optional
|
62
|
+
The selection strategy to pass to the `VDataTable component <https://trame.readthedocs.io/en/latest/trame.widgets.vuetify3.html#trame.widgets.vuetify3.VDataTable>`__.
|
63
|
+
If unset, the `all` strategy will be used.
|
64
|
+
**kwargs
|
65
|
+
All other arguments will be passed to the underlying
|
66
|
+
`VDataTable component <https://trame.readthedocs.io/en/latest/trame.widgets.vuetify3.html#trame.widgets.vuetify3.VDataTable>`_.
|
67
|
+
|
68
|
+
Returns
|
69
|
+
-------
|
70
|
+
None
|
71
|
+
"""
|
72
|
+
if facility and allow_custom_directories:
|
73
|
+
warn("allow_custom_directories will be ignored since the facility parameter is set.", stacklevel=1)
|
74
|
+
|
75
|
+
self._facility = facility
|
76
|
+
self._instrument = instrument
|
77
|
+
self._allow_custom_directories = allow_custom_directories
|
78
|
+
|
79
|
+
self._facilities_name = f"nova__neutrondataselector_{self._next_id}_facilities"
|
80
|
+
self._instruments_name = f"nova__neutrondataselector_{self._next_id}_instruments"
|
81
|
+
self._experiments_name = f"nova__neutrondataselector_{self._next_id}_experiments"
|
82
|
+
|
83
|
+
super().__init__(v_model, "", extensions, prefix, refresh_rate, select_strategy, **kwargs)
|
84
|
+
|
85
|
+
def create_ui(self, **kwargs: Any) -> None:
|
86
|
+
super().create_ui(**kwargs)
|
87
|
+
with self._layout.filter:
|
88
|
+
with GridLayout(columns=3):
|
89
|
+
columns = 3
|
90
|
+
if self._facility == "":
|
91
|
+
columns -= 1
|
92
|
+
InputField(
|
93
|
+
v_model=f"{self._state_name}.facility", items=(self._facilities_name,), type="autocomplete"
|
94
|
+
)
|
95
|
+
if self._instrument == "":
|
96
|
+
columns -= 1
|
97
|
+
InputField(
|
98
|
+
v_if=f"{self._state_name}.facility !== '{CUSTOM_DIRECTORIES_LABEL}'",
|
99
|
+
v_model=f"{self._state_name}.instrument",
|
100
|
+
items=(self._instruments_name,),
|
101
|
+
type="autocomplete",
|
102
|
+
)
|
103
|
+
InputField(
|
104
|
+
v_if=f"{self._state_name}.facility !== '{CUSTOM_DIRECTORIES_LABEL}'",
|
105
|
+
v_model=f"{self._state_name}.experiment",
|
106
|
+
column_span=columns,
|
107
|
+
items=(self._experiments_name,),
|
108
|
+
type="autocomplete",
|
109
|
+
)
|
110
|
+
InputField(v_else=True, v_model=f"{self._state_name}.custom_directory", column_span=2)
|
111
|
+
|
112
|
+
def create_model(self) -> None:
|
113
|
+
state = NeutronDataSelectorState()
|
114
|
+
self._model: NeutronDataSelectorModel = NeutronDataSelectorModel(
|
115
|
+
state, self._facility, self._instrument, self._extensions, self._prefix, self._allow_custom_directories
|
116
|
+
)
|
117
|
+
|
118
|
+
def create_viewmodel(self) -> None:
|
119
|
+
server = get_server(None, client_type="vue3")
|
120
|
+
binding = TrameBinding(server.state)
|
121
|
+
|
122
|
+
self._vm: NeutronDataSelectorViewModel = NeutronDataSelectorViewModel(self._model, binding)
|
123
|
+
self._vm.state_bind.connect(self._state_name)
|
124
|
+
self._vm.facilities_bind.connect(self._facilities_name)
|
125
|
+
self._vm.instruments_bind.connect(self._instruments_name)
|
126
|
+
self._vm.experiments_bind.connect(self._experiments_name)
|
127
|
+
self._vm.directories_bind.connect(self._directories_name)
|
128
|
+
self._vm.datafiles_bind.connect(self._datafiles_name)
|
129
|
+
self._vm.reset_bind.connect(self.reset)
|
130
|
+
|
131
|
+
self._vm.update_view()
|
132
|
+
|
133
|
+
def set_state(
|
134
|
+
self, facility: Optional[str] = None, instrument: Optional[str] = None, experiment: Optional[str] = None
|
135
|
+
) -> None:
|
136
|
+
"""Programmatically set the facility, instrument, and/or experiment to restrict data selection to.
|
137
|
+
|
138
|
+
If a parameter is None, then it will not be updated.
|
139
|
+
|
140
|
+
Parameters
|
141
|
+
----------
|
142
|
+
facility : str, optional
|
143
|
+
The facility to restrict data selection to. Options: HFIR, SNS
|
144
|
+
instrument : str, optional
|
145
|
+
The instrument to restrict data selection to. Must be at the selected facility.
|
146
|
+
experiment : str, optional
|
147
|
+
The experiment to restrict data selection to. Must begin with "IPTS-". It is your responsibility to validate
|
148
|
+
that the provided experiment exists within the instrument directory. If it doesn't then no datafiles will be
|
149
|
+
shown to the user.
|
150
|
+
|
151
|
+
Returns
|
152
|
+
-------
|
153
|
+
None
|
154
|
+
"""
|
155
|
+
self._vm.set_state(facility, instrument, experiment)
|
@@ -22,8 +22,14 @@ html {
|
|
22
22
|
|
23
23
|
.nova-data-selector {
|
24
24
|
.v-list-group {
|
25
|
-
|
26
|
-
|
25
|
+
--prepend-width: 12px !important;
|
26
|
+
}
|
27
|
+
|
28
|
+
.v-treeview.v-list {
|
29
|
+
--indent-padding: 0px !important;
|
30
|
+
|
31
|
+
& > .v-list-group > .v-list-item {
|
32
|
+
padding-left: 0 !important;
|
27
33
|
}
|
28
34
|
}
|
29
35
|
}
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
import os
|
4
4
|
from pathlib import Path
|
5
|
-
from typing import Any, Dict, List
|
5
|
+
from typing import Any, Dict, List
|
6
6
|
|
7
7
|
from nova.mvvm.interface import BindingInterface
|
8
8
|
from nova.trame.model.data_selector import DataSelectorModel
|
@@ -12,16 +12,13 @@ class DataSelectorViewModel:
|
|
12
12
|
"""Manages the view state of the DataSelector widget."""
|
13
13
|
|
14
14
|
def __init__(self, model: DataSelectorModel, binding: BindingInterface) -> None:
|
15
|
-
self.model = model
|
15
|
+
self.model: DataSelectorModel = model
|
16
16
|
|
17
17
|
self.datafiles: List[Dict[str, Any]] = []
|
18
18
|
self.directories: List[Dict[str, Any]] = []
|
19
|
-
self.expanded: List[str] =
|
19
|
+
self.expanded: Dict[str, List[str]] = {}
|
20
20
|
|
21
21
|
self.state_bind = binding.new_bind(self.model.state, callback_after_update=self.on_state_updated)
|
22
|
-
self.facilities_bind = binding.new_bind()
|
23
|
-
self.instruments_bind = binding.new_bind()
|
24
|
-
self.experiments_bind = binding.new_bind()
|
25
22
|
self.directories_bind = binding.new_bind()
|
26
23
|
self.datafiles_bind = binding.new_bind()
|
27
24
|
self.reset_bind = binding.new_bind()
|
@@ -47,43 +44,28 @@ class DataSelectorViewModel:
|
|
47
44
|
current_level["children"] = new_directories
|
48
45
|
|
49
46
|
# Mark this directory as expanded and display the new content
|
50
|
-
self.expanded
|
47
|
+
self.expanded[paths[-1]] = paths
|
51
48
|
self.directories_bind.update_in_view(self.directories)
|
52
49
|
|
53
|
-
def
|
54
|
-
self.
|
55
|
-
self.
|
56
|
-
|
57
|
-
def set_state(self, facility: Optional[str], instrument: Optional[str], experiment: Optional[str]) -> None:
|
58
|
-
self.model.set_state(facility, instrument, experiment)
|
59
|
-
self.update_view()
|
50
|
+
def reexpand_directories(self) -> None:
|
51
|
+
paths_to_expand = self.expanded.values()
|
52
|
+
self.expanded = {}
|
60
53
|
|
61
|
-
|
62
|
-
|
63
|
-
self.directories = self.model.get_directories()
|
64
|
-
self.expanded = []
|
65
|
-
self.reset_bind.update_in_view(None)
|
54
|
+
for paths in paths_to_expand:
|
55
|
+
self.expand_directory(paths)
|
66
56
|
|
67
57
|
def on_state_updated(self, results: Dict[str, Any]) -> None:
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
self.reset()
|
73
|
-
case "instrument":
|
74
|
-
self.model.set_state(facility=None, instrument=None, experiment="")
|
75
|
-
self.reset()
|
76
|
-
case "experiment":
|
77
|
-
self.reset()
|
78
|
-
case "custom_directory":
|
79
|
-
self.reset()
|
58
|
+
pass
|
59
|
+
|
60
|
+
def set_subdirectory(self, subdirectory_path: str = "") -> None:
|
61
|
+
self.model.set_subdirectory(subdirectory_path)
|
80
62
|
self.update_view()
|
81
63
|
|
82
|
-
def update_view(self) -> None:
|
64
|
+
def update_view(self, refresh_directories: bool = False) -> None:
|
83
65
|
self.state_bind.update_in_view(self.model.state)
|
84
|
-
self.
|
85
|
-
|
86
|
-
|
66
|
+
if not self.directories or refresh_directories:
|
67
|
+
self.directories = self.model.get_directories()
|
68
|
+
self.reexpand_directories()
|
87
69
|
self.directories_bind.update_in_view(self.directories)
|
88
70
|
|
89
71
|
self.datafiles = [
|
@@ -0,0 +1,51 @@
|
|
1
|
+
"""View model implementation for the DataSelector widget."""
|
2
|
+
|
3
|
+
from typing import Any, Dict, Optional
|
4
|
+
|
5
|
+
from nova.mvvm.interface import BindingInterface
|
6
|
+
from nova.trame.model.ornl.neutron_data_selector import NeutronDataSelectorModel
|
7
|
+
from nova.trame.view_model.data_selector import DataSelectorViewModel
|
8
|
+
|
9
|
+
|
10
|
+
class NeutronDataSelectorViewModel(DataSelectorViewModel):
|
11
|
+
"""Manages the view state of the DataSelector widget."""
|
12
|
+
|
13
|
+
def __init__(self, model: NeutronDataSelectorModel, binding: BindingInterface) -> None:
|
14
|
+
super().__init__(model, binding)
|
15
|
+
self.model: NeutronDataSelectorModel = model
|
16
|
+
|
17
|
+
self.facilities_bind = binding.new_bind()
|
18
|
+
self.instruments_bind = binding.new_bind()
|
19
|
+
self.experiments_bind = binding.new_bind()
|
20
|
+
|
21
|
+
def set_state(self, facility: Optional[str], instrument: Optional[str], experiment: Optional[str]) -> None:
|
22
|
+
self.model.set_state(facility, instrument, experiment)
|
23
|
+
self.update_view()
|
24
|
+
|
25
|
+
def reset(self) -> None:
|
26
|
+
self.model.set_subdirectory("")
|
27
|
+
self.directories = self.model.get_directories()
|
28
|
+
self.expanded = {}
|
29
|
+
self.reset_bind.update_in_view(None)
|
30
|
+
|
31
|
+
def on_state_updated(self, results: Dict[str, Any]) -> None:
|
32
|
+
for update in results.get("updated", []):
|
33
|
+
match update:
|
34
|
+
case "facility":
|
35
|
+
self.model.set_state(facility=None, instrument="", experiment="")
|
36
|
+
self.reset()
|
37
|
+
case "instrument":
|
38
|
+
self.model.set_state(facility=None, instrument=None, experiment="")
|
39
|
+
self.reset()
|
40
|
+
case "experiment":
|
41
|
+
self.reset()
|
42
|
+
case "custom_directory":
|
43
|
+
self.reset()
|
44
|
+
self.update_view()
|
45
|
+
|
46
|
+
def update_view(self, refresh_directories: bool = False) -> None:
|
47
|
+
self.facilities_bind.update_in_view(self.model.get_facilities())
|
48
|
+
self.instruments_bind.update_in_view(self.model.get_instruments())
|
49
|
+
self.experiments_bind.update_in_view(self.model.get_experiments())
|
50
|
+
|
51
|
+
super().update_view(refresh_directories)
|
@@ -1,10 +1,10 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: nova-trame
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.23.1
|
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
|
7
|
-
Author: Duggan
|
7
|
+
Author: John Duggan
|
8
8
|
Author-email: dugganjw@ornl.gov
|
9
9
|
Requires-Python: >=3.10,<4.0
|
10
10
|
Classifier: License :: OSI Approved :: MIT License
|
@@ -1,12 +1,15 @@
|
|
1
1
|
nova/__init__.py,sha256=ED6jHcYiuYpr_0vjGz0zx2lrrmJT9sDJCzIljoDfmlM,65
|
2
2
|
nova/trame/__init__.py,sha256=gFrAg1qva5PIqR5TjvPzAxLx103IKipJLqp3XXvrQL8,59
|
3
|
-
nova/trame/model/data_selector.py,sha256=
|
3
|
+
nova/trame/model/data_selector.py,sha256=8eaIVGak6XOAQMxdUotOsiXisW14sSRd1V0oViglJVQ,4448
|
4
|
+
nova/trame/model/ornl/neutron_data_selector.py,sha256=nfjXwT93JcPjJwyDynoP_stDVAfjUs7neeVZTk_04gc,6424
|
4
5
|
nova/trame/model/remote_file_input.py,sha256=9KAf31ZHzpsh_aXUrNcF81Q5jvUZDWCzW1QATKls-Jk,3675
|
5
6
|
nova/trame/view/components/__init__.py,sha256=60BeS69aOrFnkptjuD17rfPE1f4Z35iBH56TRmW5MW8,451
|
6
|
-
nova/trame/view/components/data_selector.py,sha256=
|
7
|
+
nova/trame/view/components/data_selector.py,sha256=d5UDF7ENBNifmLyIecx8VyINRY4vSxlQHyJ2a5G7IOY,9606
|
7
8
|
nova/trame/view/components/execution_buttons.py,sha256=fIkrWKI3jFZqk3GHhtmYh3nK2c-HOXpD3D3zd_TUpi0,4049
|
8
9
|
nova/trame/view/components/file_upload.py,sha256=7VcpfA6zmiqMDLkwVPlb35Tf0IUTBN1xsHpoUFnSr1w,3111
|
9
10
|
nova/trame/view/components/input_field.py,sha256=q6WQ_N-BOlimUL9zgazDlsDfK28FrrKjH4he8e_HzRA,16088
|
11
|
+
nova/trame/view/components/ornl/__init__.py,sha256=HnxzzSsxw0vQSDCVFfWsAxx1n3HnU37LMuQkfiewmSU,90
|
12
|
+
nova/trame/view/components/ornl/neutron_data_selector.py,sha256=8B0BVXzVtMApnbMGD6ms6G4gdv5l-dIWM6YlHcBgnoc,6878
|
10
13
|
nova/trame/view/components/progress_bar.py,sha256=fCfPw4MPAvORaeFOXugreok4GLpDVZGMkqvnv-AhMxg,2967
|
11
14
|
nova/trame/view/components/remote_file_input.py,sha256=ByrBFj8svyWezcardCWrS_4Ag3fgTYNg_11lDW1FIA8,9669
|
12
15
|
nova/trame/view/components/tool_outputs.py,sha256=-6pDURd2l_FK_8EWa9BI3KhU_KJXJ6uyJ_rW4nQVc08,2337
|
@@ -19,7 +22,7 @@ nova/trame/view/layouts/hbox.py,sha256=qlOMp_iOropIkC9Jxa6D89b7OPv0pNvJ73tUEzddy
|
|
19
22
|
nova/trame/view/layouts/utils.py,sha256=Hg34VQWTG3yHBsgNvmfatR4J-uL3cko7UxSJpT-h3JI,376
|
20
23
|
nova/trame/view/layouts/vbox.py,sha256=hzhzPu99R2fAclMe-FwHZseJWk7iailZ31bKdGhi1hk,3514
|
21
24
|
nova/trame/view/theme/__init__.py,sha256=70_marDlTigIcPEOGiJb2JTs-8b2sGM5SlY7XBPtBDM,54
|
22
|
-
nova/trame/view/theme/assets/core_style.scss,sha256=
|
25
|
+
nova/trame/view/theme/assets/core_style.scss,sha256=AJ-2hyQRVkyOGWJB0lGMzlQeshbCJP5BGNYCt9e9AOI,4208
|
23
26
|
nova/trame/view/theme/assets/favicon.png,sha256=Xbp1nUmhcBDeObjsebEbEAraPDZ_M163M_ZLtm5AbQc,1927
|
24
27
|
nova/trame/view/theme/assets/js/delay_manager.js,sha256=mRV6KoO8-Bxq3tG5Bh9CQYy-CRVbkj3IYlqNb-Og7cI,526
|
25
28
|
nova/trame/view/theme/assets/js/lodash.min.js,sha256=KCyAYJ-fsqtp_HMwbjhy6IKjlA5lrVrtWt1JdMsC57k,73016
|
@@ -28,13 +31,14 @@ nova/trame/view/theme/assets/vuetify_config.json,sha256=a0FSgpLYWGFlRGSMhMq61MyD
|
|
28
31
|
nova/trame/view/theme/exit_button.py,sha256=Kqv1GVJZGrSsj6_JFjGU3vm3iNuMolLC2T1x2IsdmV0,3094
|
29
32
|
nova/trame/view/theme/theme.py,sha256=8JqSrEbhxK1SccXE1_jUdel9Wtc2QNObVEwtbVWG_QY,13146
|
30
33
|
nova/trame/view/utilities/local_storage.py,sha256=vD8f2VZIpxhIKjZwEaD7siiPCTZO4cw9AfhwdawwYLY,3218
|
31
|
-
nova/trame/view_model/data_selector.py,sha256=
|
34
|
+
nova/trame/view_model/data_selector.py,sha256=yDyYceZAUIxv4hhJGAPdIo54zHmrwqkxNZh7F2sRlw0,2810
|
32
35
|
nova/trame/view_model/execution_buttons.py,sha256=MfKSp95D92EqpD48C15cBo6dLO0Yld4FeRZMJNxJf7Y,3551
|
36
|
+
nova/trame/view_model/ornl/neutron_data_selector.py,sha256=WTuz9QO4WzbOftTH0uP9Qxn7nYdsnIoixu1hWHKCVNU,2128
|
33
37
|
nova/trame/view_model/progress_bar.py,sha256=6AUKHF3hfzbdsHqNEnmHRgDcBKY5TT8ywDx9S6ovnsc,2854
|
34
38
|
nova/trame/view_model/remote_file_input.py,sha256=ojEOJ8ZPkajpbAaZi9VLj7g-uBjhb8BMrTdMmwf_J6A,3367
|
35
39
|
nova/trame/view_model/tool_outputs.py,sha256=ev6LY7fJ0H2xAJn9f5ww28c8Kpom2SYc2FbvFcoN4zg,829
|
36
|
-
nova_trame-0.
|
37
|
-
nova_trame-0.
|
38
|
-
nova_trame-0.
|
39
|
-
nova_trame-0.
|
40
|
-
nova_trame-0.
|
40
|
+
nova_trame-0.23.1.dist-info/LICENSE,sha256=Iu5QiDbwNbREg75iYaxIJ_V-zppuv4QFuBhAW-qiAlM,1061
|
41
|
+
nova_trame-0.23.1.dist-info/METADATA,sha256=VVMLgB8JhBIzt_FbCAEUIjPh3Zu8gEqQy091t3Fpd8E,1688
|
42
|
+
nova_trame-0.23.1.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
43
|
+
nova_trame-0.23.1.dist-info/entry_points.txt,sha256=J2AmeSwiTYZ4ZqHHp9HO6v4MaYQTTBPbNh6WtoqOT58,42
|
44
|
+
nova_trame-0.23.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|