nova-trame 0.18.0.dev0__py3-none-any.whl → 0.19.0.dev0__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 +101 -56
- nova/trame/view/components/__init__.py +2 -1
- nova/trame/view/components/data_selector.py +71 -8
- nova/trame/view/components/file_upload.py +79 -0
- nova/trame/view/components/remote_file_input.py +10 -1
- nova/trame/view/theme/assets/core_style.scss +6 -6
- nova/trame/view/theme/assets/vuetify_config.json +20 -9
- nova/trame/view/theme/theme.py +5 -9
- nova/trame/view_model/data_selector.py +6 -3
- nova/trame/view_model/remote_file_input.py +2 -0
- {nova_trame-0.18.0.dev0.dist-info → nova_trame-0.19.0.dev0.dist-info}/METADATA +2 -1
- {nova_trame-0.18.0.dev0.dist-info → nova_trame-0.19.0.dev0.dist-info}/RECORD +15 -14
- {nova_trame-0.18.0.dev0.dist-info → nova_trame-0.19.0.dev0.dist-info}/LICENSE +0 -0
- {nova_trame-0.18.0.dev0.dist-info → nova_trame-0.19.0.dev0.dist-info}/WHEEL +0 -0
- {nova_trame-0.18.0.dev0.dist-info → nova_trame-0.19.0.dev0.dist-info}/entry_points.txt +0 -0
@@ -2,65 +2,99 @@
|
|
2
2
|
|
3
3
|
import os
|
4
4
|
from pathlib import Path
|
5
|
-
from typing import List
|
5
|
+
from typing import List, Optional
|
6
|
+
from warnings import warn
|
6
7
|
|
7
|
-
from pydantic import BaseModel, Field
|
8
|
+
from pydantic import BaseModel, Field, field_validator, model_validator
|
9
|
+
from typing_extensions import Self
|
8
10
|
|
9
|
-
FACILITIES = ["HFIR", "SNS"]
|
10
11
|
INSTRUMENTS = {
|
11
|
-
"HFIR":
|
12
|
-
"CG1A",
|
13
|
-
"CG1B",
|
14
|
-
"CG1D",
|
15
|
-
"CG2",
|
16
|
-
"CG3",
|
17
|
-
"CG4B",
|
18
|
-
"CG4C",
|
19
|
-
"CG4D",
|
20
|
-
"HB1",
|
21
|
-
"HB1A",
|
22
|
-
"HB2A",
|
23
|
-
"HB2B",
|
24
|
-
"HB2C",
|
25
|
-
"HB3",
|
26
|
-
"HB3A",
|
27
|
-
"NOWG",
|
28
|
-
"NOWV",
|
29
|
-
|
30
|
-
"SNS":
|
31
|
-
"ARCS",
|
32
|
-
"BL0",
|
33
|
-
"BSS",
|
34
|
-
"CNCS",
|
35
|
-
"CORELLI",
|
36
|
-
"EQSANS",
|
37
|
-
"HYS",
|
38
|
-
"
|
39
|
-
"
|
40
|
-
"
|
41
|
-
"
|
42
|
-
"
|
43
|
-
"
|
44
|
-
"
|
45
|
-
"
|
46
|
-
"
|
47
|
-
"
|
48
|
-
"
|
49
|
-
"
|
50
|
-
"
|
51
|
-
"
|
52
|
-
|
53
|
-
],
|
12
|
+
"HFIR": {
|
13
|
+
"CG-1A": "CG1A",
|
14
|
+
"CG-1B": "CG1B",
|
15
|
+
"CG-1D": "CG1D",
|
16
|
+
"CG-2": "CG2",
|
17
|
+
"CG-3": "CG3",
|
18
|
+
"CG-4B": "CG4B",
|
19
|
+
"CG-4C": "CG4C",
|
20
|
+
"CG-4D": "CG4D",
|
21
|
+
"HB-1": "HB1",
|
22
|
+
"HB-1A": "HB1A",
|
23
|
+
"HB-2A": "HB2A",
|
24
|
+
"HB-2B": "HB2B",
|
25
|
+
"HB-2C": "HB2C",
|
26
|
+
"HB-3": "HB3",
|
27
|
+
"HB-3A": "HB3A",
|
28
|
+
"NOW-G": "NOWG",
|
29
|
+
"NOW-V": "NOWV",
|
30
|
+
},
|
31
|
+
"SNS": {
|
32
|
+
"BL-18": "ARCS",
|
33
|
+
"BL-0": "BL0",
|
34
|
+
"BL-2": "BSS",
|
35
|
+
"BL-5": "CNCS",
|
36
|
+
"BL-9": "CORELLI",
|
37
|
+
"BL-6": "EQSANS",
|
38
|
+
"BL-14B": "HYS",
|
39
|
+
"BL-11B": "MANDI",
|
40
|
+
"BL-1B": "NOM",
|
41
|
+
"NOW-G": "NOWG",
|
42
|
+
"BL-15": "NSE",
|
43
|
+
"BL-11A": "PG3",
|
44
|
+
"BL-4B": "REF_L",
|
45
|
+
"BL-4A": "REF_M",
|
46
|
+
"BL-17": "SEQ",
|
47
|
+
"BL-3": "SNAP",
|
48
|
+
"BL-12": "TOPAZ",
|
49
|
+
"BL-1A": "USANS",
|
50
|
+
"BL-10": "VENUS",
|
51
|
+
"BL-16B": "VIS",
|
52
|
+
"BL-7": "VULCAN",
|
53
|
+
},
|
54
54
|
}
|
55
55
|
|
56
56
|
|
57
|
-
|
57
|
+
def get_facilities() -> List[str]:
|
58
|
+
return list(INSTRUMENTS.keys())
|
59
|
+
|
60
|
+
|
61
|
+
def get_instruments(facility: str) -> List[str]:
|
62
|
+
return list(INSTRUMENTS.get(facility, {}).keys())
|
63
|
+
|
64
|
+
|
65
|
+
class DataSelectorState(BaseModel, validate_assignment=True):
|
58
66
|
"""Selection state for identifying datafiles."""
|
59
67
|
|
60
68
|
facility: str = Field(default="", title="Facility")
|
61
69
|
instrument: str = Field(default="", title="Instrument")
|
62
70
|
experiment: str = Field(default="", title="Experiment")
|
63
71
|
|
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 = 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 = get_instruments(self.facility)
|
86
|
+
if self.instrument 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
|
+
|
64
98
|
|
65
99
|
class DataSelectorModel:
|
66
100
|
"""Manages file system interactions for the DataSelector widget."""
|
@@ -71,32 +105,43 @@ class DataSelectorModel:
|
|
71
105
|
self.state.instrument = instrument
|
72
106
|
|
73
107
|
def get_facilities(self) -> List[str]:
|
74
|
-
return
|
108
|
+
return get_facilities()
|
109
|
+
|
110
|
+
def get_instrument_dir(self) -> str:
|
111
|
+
return INSTRUMENTS.get(self.state.facility, {}).get(self.state.instrument, "")
|
75
112
|
|
76
113
|
def get_instruments(self) -> List[str]:
|
77
|
-
return
|
114
|
+
return get_instruments(self.state.facility)
|
78
115
|
|
79
116
|
def get_experiments(self) -> List[str]:
|
80
117
|
experiments = []
|
81
118
|
|
82
|
-
instrument_path = Path("/") / self.state.facility / self.
|
119
|
+
instrument_path = Path("/") / self.state.facility / self.get_instrument_dir()
|
83
120
|
try:
|
84
121
|
for dirname in os.listdir(instrument_path):
|
85
|
-
if dirname.startswith("IPTS-"):
|
122
|
+
if dirname.startswith("IPTS-") and os.access(instrument_path / dirname, mode=os.R_OK):
|
86
123
|
experiments.append(dirname)
|
87
|
-
except
|
124
|
+
except OSError:
|
88
125
|
pass
|
89
126
|
|
90
|
-
return experiments
|
127
|
+
return sorted(experiments)
|
91
128
|
|
92
129
|
def get_datafiles(self) -> List[str]:
|
93
130
|
datafiles = []
|
94
131
|
|
95
|
-
experiment_path = Path("/") / self.state.facility / self.
|
132
|
+
experiment_path = Path("/") / self.state.facility / self.get_instrument_dir() / self.state.experiment / "nexus"
|
96
133
|
try:
|
97
134
|
for fname in os.listdir(experiment_path):
|
98
135
|
datafiles.append(str(experiment_path / fname))
|
99
|
-
except
|
136
|
+
except OSError:
|
100
137
|
pass
|
101
138
|
|
102
|
-
return datafiles
|
139
|
+
return sorted(datafiles)
|
140
|
+
|
141
|
+
def set_state(self, facility: Optional[str], instrument: Optional[str], experiment: Optional[str]) -> None:
|
142
|
+
if facility is not None:
|
143
|
+
self.state.facility = facility
|
144
|
+
if instrument is not None:
|
145
|
+
self.state.instrument = instrument
|
146
|
+
if experiment is not None:
|
147
|
+
self.state.experiment = experiment
|
@@ -1,5 +1,6 @@
|
|
1
1
|
from .data_selector import DataSelector
|
2
|
+
from .file_upload import FileUpload
|
2
3
|
from .input_field import InputField
|
3
4
|
from .remote_file_input import RemoteFileInput
|
4
5
|
|
5
|
-
__all__ = ["DataSelector", "InputField", "RemoteFileInput"]
|
6
|
+
__all__ = ["DataSelector", "FileUpload", "InputField", "RemoteFileInput"]
|
@@ -1,25 +1,45 @@
|
|
1
1
|
"""View Implementation for DataSelector."""
|
2
2
|
|
3
|
-
from typing import Any
|
3
|
+
from typing import Any, Optional
|
4
4
|
|
5
5
|
from trame.app import get_server
|
6
6
|
from trame.widgets import vuetify3 as vuetify
|
7
7
|
|
8
8
|
from nova.mvvm.trame_binding import TrameBinding
|
9
9
|
from nova.trame.model.data_selector import DataSelectorModel
|
10
|
-
from nova.trame.view.layouts import
|
10
|
+
from nova.trame.view.layouts import GridLayout
|
11
11
|
from nova.trame.view_model.data_selector import DataSelectorViewModel
|
12
12
|
|
13
13
|
from .input_field import InputField
|
14
14
|
|
15
15
|
|
16
|
-
class DataSelector(vuetify.
|
16
|
+
class DataSelector(vuetify.VDataTable):
|
17
17
|
"""Allows the user to select datafiles from an IPTS experiment."""
|
18
18
|
|
19
|
-
def __init__(self, facility: str = "", instrument: str = "", **kwargs: Any) -> None:
|
19
|
+
def __init__(self, v_model: str, facility: str = "", instrument: str = "", **kwargs: Any) -> None:
|
20
|
+
"""Constructor for DataSelector.
|
21
|
+
|
22
|
+
Parameters
|
23
|
+
----------
|
24
|
+
v_model : str
|
25
|
+
The name of the state variable to bind to this widget. The state variable will contain a list of the files
|
26
|
+
selected by the user.
|
27
|
+
facility : str, optional
|
28
|
+
The facility to restrict data selection to. Options: HFIR, SNS
|
29
|
+
instrument : str, optional
|
30
|
+
The instrument to restrict data selection to. Please use the instrument acronym (e.g. CG-2).
|
31
|
+
**kwargs
|
32
|
+
All other arguments will be passed to the underlying
|
33
|
+
`VDataTable component <https://trame.readthedocs.io/en/latest/trame.widgets.vuetify3.html#trame.widgets.vuetify3.VDataTable>`_.
|
34
|
+
|
35
|
+
Returns
|
36
|
+
-------
|
37
|
+
None
|
38
|
+
"""
|
20
39
|
if "items" in kwargs:
|
21
40
|
raise AttributeError("The items parameter is not allowed on DataSelector widget.")
|
22
41
|
|
42
|
+
self._v_model = v_model
|
23
43
|
self._state_name = f"nova__dataselector_{self._next_id}_state"
|
24
44
|
self._facilities_name = f"nova__dataselector_{self._next_id}_facilities"
|
25
45
|
self._instruments_name = f"nova__dataselector_{self._next_id}_instruments"
|
@@ -32,18 +52,37 @@ class DataSelector(vuetify.VAutocomplete):
|
|
32
52
|
self.create_ui(facility, instrument, **kwargs)
|
33
53
|
|
34
54
|
def create_ui(self, facility: str, instrument: str, **kwargs: Any) -> None:
|
35
|
-
with
|
55
|
+
with GridLayout(columns=3):
|
56
|
+
columns = 3
|
36
57
|
if facility == "":
|
58
|
+
columns -= 1
|
37
59
|
InputField(v_model=f"{self._state_name}.facility", items=(self._facilities_name,), type="autocomplete")
|
38
60
|
if instrument == "":
|
61
|
+
columns -= 1
|
39
62
|
InputField(
|
40
63
|
v_model=f"{self._state_name}.instrument", items=(self._instruments_name,), type="autocomplete"
|
41
64
|
)
|
42
|
-
InputField(
|
43
|
-
|
44
|
-
|
65
|
+
InputField(
|
66
|
+
v_model=f"{self._state_name}.experiment",
|
67
|
+
column_span=columns,
|
68
|
+
items=(self._experiments_name,),
|
69
|
+
type="autocomplete",
|
70
|
+
)
|
71
|
+
|
72
|
+
super().__init__(
|
73
|
+
v_model=self._v_model,
|
74
|
+
column_span=3,
|
75
|
+
headers=("[{ align: 'center', key: 'file', title: 'Available Datafiles' }]",),
|
76
|
+
item_value="file",
|
77
|
+
select_strategy="all",
|
78
|
+
show_select=True,
|
79
|
+
**kwargs,
|
80
|
+
)
|
45
81
|
self.items = (self._datafiles_name,)
|
46
82
|
|
83
|
+
if "update_modelValue" not in kwargs:
|
84
|
+
self.update_modelValue = f"flushState('{self._v_model.split('.')[0]}')"
|
85
|
+
|
47
86
|
def create_model(self, facility: str, instrument: str) -> None:
|
48
87
|
self._model = DataSelectorModel(facility, instrument)
|
49
88
|
|
@@ -59,3 +98,27 @@ class DataSelector(vuetify.VAutocomplete):
|
|
59
98
|
self._vm.datafiles_bind.connect(self._datafiles_name)
|
60
99
|
|
61
100
|
self._vm.update_view()
|
101
|
+
|
102
|
+
def set_state(
|
103
|
+
self, facility: Optional[str] = None, instrument: Optional[str] = None, experiment: Optional[str] = None
|
104
|
+
) -> None:
|
105
|
+
"""Programmatically set the facility, instrument, and/or experiment to restrict data selection to.
|
106
|
+
|
107
|
+
If a parameter is None, then it will not be updated.
|
108
|
+
|
109
|
+
Parameters
|
110
|
+
----------
|
111
|
+
facility : str, optional
|
112
|
+
The facility to restrict data selection to. Options: HFIR, SNS
|
113
|
+
instrument : str, optional
|
114
|
+
The instrument to restrict data selection to. Must be at the selected facility.
|
115
|
+
experiment : str, optional
|
116
|
+
The experiment to restrict data selection to. Must begin with "IPTS-". It is your responsibility to validate
|
117
|
+
that the provided experiment exists within the instrument directory. If it doesn't then no datafiles will be
|
118
|
+
shown to the user.
|
119
|
+
|
120
|
+
Returns
|
121
|
+
-------
|
122
|
+
None
|
123
|
+
"""
|
124
|
+
self._vm.set_state(facility, instrument, experiment)
|
@@ -0,0 +1,79 @@
|
|
1
|
+
"""View implementation for FileUpload."""
|
2
|
+
|
3
|
+
from typing import Any, List, Optional
|
4
|
+
|
5
|
+
from trame.widgets import vuetify3 as vuetify
|
6
|
+
|
7
|
+
from .remote_file_input import RemoteFileInput
|
8
|
+
|
9
|
+
|
10
|
+
class FileUpload(vuetify.VBtn):
|
11
|
+
"""Component for uploading a file from either the user's filesystem or the server filesystem."""
|
12
|
+
|
13
|
+
def __init__(
|
14
|
+
self, v_model: Optional[str] = None, base_paths: Optional[List[str]] = None, label: str = "", **kwargs: Any
|
15
|
+
) -> None:
|
16
|
+
"""Constructor for FileUpload.
|
17
|
+
|
18
|
+
Parameters
|
19
|
+
----------
|
20
|
+
v_model : str, optional
|
21
|
+
The state variable to set when the user uploads their file. If uploaded from the user's machine, then the
|
22
|
+
state variable will contain a dictionary with the file contents and metadata. If uploaded from the server
|
23
|
+
filesystem, then the state variable will contain a path to the file.
|
24
|
+
base_paths: list[str], optional
|
25
|
+
Passed to :ref:`RemoteFileInput <api_remotefileinput>`.
|
26
|
+
label : str, optional
|
27
|
+
The text to display on the upload button.
|
28
|
+
**kwargs
|
29
|
+
All other arguments will be passed to the underlying
|
30
|
+
`Button component <https://trame.readthedocs.io/en/latest/trame.widgets.vuetify3.html#trame.widgets.vuetify3.VBtn>`_.
|
31
|
+
|
32
|
+
Returns
|
33
|
+
-------
|
34
|
+
None
|
35
|
+
"""
|
36
|
+
self._v_model = v_model
|
37
|
+
if base_paths:
|
38
|
+
self._base_paths = base_paths
|
39
|
+
else:
|
40
|
+
self._base_paths = ["/"]
|
41
|
+
self._ref_name = f"nova__fileupload_{self._next_id}"
|
42
|
+
|
43
|
+
super().__init__(label, **kwargs)
|
44
|
+
self.create_ui()
|
45
|
+
|
46
|
+
def create_ui(self) -> None:
|
47
|
+
self.local_file_input = vuetify.VFileInput(v_model=(self._v_model, None), classes="d-none", ref=self._ref_name)
|
48
|
+
if self._v_model:
|
49
|
+
# Serialize the content in a way that will work with nova-mvvm and then push it to the server.
|
50
|
+
self.local_file_input.update_modelValue = (
|
51
|
+
f"{self._v_model}.text().then((contents) => {{ "
|
52
|
+
f" {self._v_model} = {{ contents: contents }};"
|
53
|
+
f" flushState('{self._v_model.split('.')[0]}');"
|
54
|
+
"});"
|
55
|
+
)
|
56
|
+
self.remote_file_input = RemoteFileInput(
|
57
|
+
v_model=self._v_model,
|
58
|
+
base_paths=self._base_paths,
|
59
|
+
input_props={"classes": "d-none"},
|
60
|
+
)
|
61
|
+
with self:
|
62
|
+
with vuetify.VMenu(activator="parent"):
|
63
|
+
with vuetify.VList():
|
64
|
+
vuetify.VListItem("From Local Machine", click=f"trame.refs.{self._ref_name}.click()")
|
65
|
+
vuetify.VListItem("From Analysis Cluster", click=self.remote_file_input.open_dialog)
|
66
|
+
|
67
|
+
def select_file(self, value: str) -> None:
|
68
|
+
"""Programmatically set the RemoteFileInput path.
|
69
|
+
|
70
|
+
Parameters
|
71
|
+
----------
|
72
|
+
value: str
|
73
|
+
The new value for the RemoteFileInput.
|
74
|
+
|
75
|
+
Returns
|
76
|
+
-------
|
77
|
+
None
|
78
|
+
"""
|
79
|
+
self.remote_file_input.select_file(value)
|
@@ -10,9 +10,10 @@ from trame_client.widgets.core import AbstractElement
|
|
10
10
|
|
11
11
|
from nova.mvvm.trame_binding import TrameBinding
|
12
12
|
from nova.trame.model.remote_file_input import RemoteFileInputModel
|
13
|
-
from nova.trame.view.components import InputField
|
14
13
|
from nova.trame.view_model.remote_file_input import RemoteFileInputViewModel
|
15
14
|
|
15
|
+
from .input_field import InputField
|
16
|
+
|
16
17
|
|
17
18
|
class RemoteFileInput:
|
18
19
|
"""Generates a file selection dialog for picking files off of the server.
|
@@ -189,3 +190,11 @@ class RemoteFileInput:
|
|
189
190
|
)
|
190
191
|
self.vm.showing_all_bind.connect(self.vm.get_showing_all_state_name())
|
191
192
|
self.vm.valid_selection_bind.connect(self.vm.get_valid_selection_state_name())
|
193
|
+
|
194
|
+
def select_file(self, value: str) -> None:
|
195
|
+
"""Programmatically set the v_model value."""
|
196
|
+
self.vm.select_file(value)
|
197
|
+
|
198
|
+
def open_dialog(self) -> None:
|
199
|
+
"""Programmatically opens the dialog for selecting a file."""
|
200
|
+
self.vm.open_dialog()
|
@@ -12,12 +12,6 @@ html {
|
|
12
12
|
box-shadow: none !important;
|
13
13
|
}
|
14
14
|
|
15
|
-
.v-tab.v-btn {
|
16
|
-
height: 30px !important;
|
17
|
-
min-width: fit-content !important;
|
18
|
-
padding: 10px !important;
|
19
|
-
}
|
20
|
-
|
21
15
|
.mpl-message, .ui-dialog-titlebar {
|
22
16
|
display: none !important;
|
23
17
|
}
|
@@ -48,6 +42,12 @@ html {
|
|
48
42
|
border-radius: 4px;
|
49
43
|
}
|
50
44
|
|
45
|
+
.v-tab.v-btn {
|
46
|
+
height: 30px !important;
|
47
|
+
min-width: fit-content !important;
|
48
|
+
padding: 10px !important;
|
49
|
+
}
|
50
|
+
|
51
51
|
.v-card-title,
|
52
52
|
.v-list-item-title,
|
53
53
|
.v-toolbar-title {
|
@@ -56,8 +56,7 @@
|
|
56
56
|
},
|
57
57
|
"VFileInput": {
|
58
58
|
"color": "primary",
|
59
|
-
"prependIcon": false
|
60
|
-
"variant": "outlined"
|
59
|
+
"prependIcon": false
|
61
60
|
},
|
62
61
|
"VLabel": {
|
63
62
|
"style": {
|
@@ -92,8 +91,7 @@
|
|
92
91
|
"color": "primary"
|
93
92
|
},
|
94
93
|
"VSelect": {
|
95
|
-
"color": "primary"
|
96
|
-
"variant": "outlined"
|
94
|
+
"color": "primary"
|
97
95
|
},
|
98
96
|
"VSlider": {
|
99
97
|
"color": "primary"
|
@@ -108,12 +106,10 @@
|
|
108
106
|
"color": "primary"
|
109
107
|
},
|
110
108
|
"VTextarea": {
|
111
|
-
"color": "primary"
|
112
|
-
"variant": "outlined"
|
109
|
+
"color": "primary"
|
113
110
|
},
|
114
111
|
"VTextField": {
|
115
|
-
"color": "primary"
|
116
|
-
"variant": "outlined"
|
112
|
+
"color": "primary"
|
117
113
|
},
|
118
114
|
"VWindowItem": {
|
119
115
|
"reverseTransition": "fade-transition",
|
@@ -156,12 +152,27 @@
|
|
156
152
|
"secondary": "#f48e5c"
|
157
153
|
},
|
158
154
|
"defaults": {
|
155
|
+
"VAutocomplete": {
|
156
|
+
"variant": "outlined"
|
157
|
+
},
|
159
158
|
"VBadge": {
|
160
159
|
"dot": true
|
161
160
|
},
|
162
161
|
"VBtn": {
|
163
162
|
"size": "small"
|
164
163
|
},
|
164
|
+
"VCombobox": {
|
165
|
+
"variant": "outlined"
|
166
|
+
},
|
167
|
+
"VFileInput": {
|
168
|
+
"variant": "outlined"
|
169
|
+
},
|
170
|
+
"VSelect": {
|
171
|
+
"variant": "outlined"
|
172
|
+
},
|
173
|
+
"VTextarea": {
|
174
|
+
"variant": "outlined"
|
175
|
+
},
|
165
176
|
"VTextField": {
|
166
177
|
"VBtn": {
|
167
178
|
"size": "small",
|
@@ -183,4 +194,4 @@
|
|
183
194
|
"lighten": 5
|
184
195
|
}
|
185
196
|
}
|
186
|
-
}
|
197
|
+
}
|
nova/trame/view/theme/theme.py
CHANGED
@@ -31,13 +31,6 @@ class ThemedApp:
|
|
31
31
|
"""Automatically injects theming into your Trame application.
|
32
32
|
|
33
33
|
You should always inherit from this class when you define your Trame application.
|
34
|
-
|
35
|
-
Currently, it supports two themes:
|
36
|
-
|
37
|
-
1. ModernTheme - The recommended theme for most applications. Leverages ORNL brand colors and a typical Vuetify \
|
38
|
-
appearance.
|
39
|
-
2. CompactTheme - Similar to ModernTheme but with a smaller global font size and reduced margins and paddings on \
|
40
|
-
all components.
|
41
34
|
"""
|
42
35
|
|
43
36
|
def __init__(
|
@@ -151,7 +144,10 @@ class ThemedApp:
|
|
151
144
|
Parameters
|
152
145
|
----------
|
153
146
|
theme : str, optional
|
154
|
-
The new theme to use. If the theme is not found, the default theme will be used.
|
147
|
+
The new theme to use. If the theme is not found, the default theme will be used. The available options are:
|
148
|
+
|
149
|
+
1. ModernTheme (default) - Leverages ORNL brand colors and a typical Vuetify appearance.
|
150
|
+
2. CompactTheme - Similar to ModernTheme but with a smaller global font size and increased density.
|
155
151
|
force : bool, optional
|
156
152
|
If True, the theme will be set even if the theme selection menu is disabled.
|
157
153
|
|
@@ -237,7 +233,7 @@ class ThemedApp:
|
|
237
233
|
# [slot override example]
|
238
234
|
layout.pre_content = vuetify.VSheet(classes="bg-background ")
|
239
235
|
# [slot override example complete]
|
240
|
-
with vuetify.VContainer(classes="flex-
|
236
|
+
with vuetify.VContainer(classes="flex-0-1 overflow-hidden pt-0 pb-0", fluid=True):
|
241
237
|
layout.content = html.Div(classes="h-100 overflow-y-auto pb-1 ")
|
242
238
|
layout.post_content = vuetify.VSheet(classes="bg-background ")
|
243
239
|
|
@@ -1,7 +1,6 @@
|
|
1
1
|
"""View model implementation for the DataSelector widget."""
|
2
2
|
|
3
|
-
import
|
4
|
-
from typing import Any
|
3
|
+
from typing import Any, Optional
|
5
4
|
|
6
5
|
from nova.mvvm.interface import BindingInterface
|
7
6
|
from nova.trame.model.data_selector import DataSelectorModel
|
@@ -19,6 +18,10 @@ class DataSelectorViewModel:
|
|
19
18
|
self.experiments_bind = binding.new_bind()
|
20
19
|
self.datafiles_bind = binding.new_bind()
|
21
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
|
+
|
22
25
|
def update_view(self, _: Any = None) -> None:
|
23
26
|
self.state_bind.update_in_view(self.model.state)
|
24
27
|
self.facilities_bind.update_in_view(self.model.get_facilities())
|
@@ -26,5 +29,5 @@ class DataSelectorViewModel:
|
|
26
29
|
self.experiments_bind.update_in_view(self.model.get_experiments())
|
27
30
|
|
28
31
|
datafile_paths = self.model.get_datafiles()
|
29
|
-
datafile_options = [{"
|
32
|
+
datafile_options = [{"file": datafile} for datafile in datafile_paths]
|
30
33
|
self.datafiles_bind.update_in_view(datafile_options)
|
@@ -35,6 +35,8 @@ class RemoteFileInputViewModel:
|
|
35
35
|
self.previous_value = self.value
|
36
36
|
self.populate_file_list()
|
37
37
|
|
38
|
+
self.dialog_bind.update_in_view(True)
|
39
|
+
|
38
40
|
def close_dialog(self, cancel: bool = False) -> None:
|
39
41
|
if not cancel:
|
40
42
|
self.on_update_bind.update_in_view(self.value)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: nova-trame
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.19.0.dev0
|
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
|
@@ -25,6 +25,7 @@ Requires-Dist: trame-matplotlib
|
|
25
25
|
Requires-Dist: trame-plotly
|
26
26
|
Requires-Dist: trame-vega
|
27
27
|
Requires-Dist: trame-vuetify
|
28
|
+
Project-URL: Changelog, https://code.ornl.gov/ndip/public-packages/nova-trame/blob/main/CHANGELOG.md
|
28
29
|
Description-Content-Type: text/markdown
|
29
30
|
|
30
31
|
nova-trame
|
@@ -1,11 +1,12 @@
|
|
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=xM4j_BrKXClRbeDjfcCQEAnWWYuUod7GxiF1WXDpfls,4620
|
4
4
|
nova/trame/model/remote_file_input.py,sha256=9KAf31ZHzpsh_aXUrNcF81Q5jvUZDWCzW1QATKls-Jk,3675
|
5
|
-
nova/trame/view/components/__init__.py,sha256=
|
6
|
-
nova/trame/view/components/data_selector.py,sha256
|
5
|
+
nova/trame/view/components/__init__.py,sha256=u8yzshFp_TmuC1g9TRxKjy_BdGWMIzPQouI52hzcr2U,234
|
6
|
+
nova/trame/view/components/data_selector.py,sha256=-u73LayMwepOVLsWx6P_z5tBTPGX0ekzlI9fFVv2sNk,5107
|
7
|
+
nova/trame/view/components/file_upload.py,sha256=MafxZQE9fIuDFoizW2I4jAb7n82DPNaOvVhrrPEMbq8,3029
|
7
8
|
nova/trame/view/components/input_field.py,sha256=ncVVSzdJwH_-KP24I2rCqcb6v3J2hPhNTXr8Lb1EZ_U,15931
|
8
|
-
nova/trame/view/components/remote_file_input.py,sha256=
|
9
|
+
nova/trame/view/components/remote_file_input.py,sha256=0qi4PnmqUBIdUv36dUqYsTWRQ4fHUJ0nlg9oXzA3Qfs,8929
|
9
10
|
nova/trame/view/components/visualization/__init__.py,sha256=reqkkbhD5uSksHHlhVMy1qNUCwSekS5HlXk6wCREYxU,152
|
10
11
|
nova/trame/view/components/visualization/interactive_2d_plot.py,sha256=foZCMoqbuahT5dtqIQvm8C4ZJcY9P211eJEcpQJltmM,3421
|
11
12
|
nova/trame/view/components/visualization/matplotlib_figure.py,sha256=yop7Kd_MylUiCwEial2jOYESbvchrYhrpSmRowUhePY,12003
|
@@ -14,18 +15,18 @@ nova/trame/view/layouts/grid.py,sha256=k-QHuH31XeAVDuMKUMoAMVnAM-Yavq7kdLYOC1ZrG
|
|
14
15
|
nova/trame/view/layouts/hbox.py,sha256=r5irhFX6YWTWN4V4NwNQx6mheyM8p6PVcJbrbhvOAwo,2625
|
15
16
|
nova/trame/view/layouts/vbox.py,sha256=Q4EvrtGJORyNF6AnCLGXToy8XU6yofiO5_kt7hK-AYs,2626
|
16
17
|
nova/trame/view/theme/__init__.py,sha256=70_marDlTigIcPEOGiJb2JTs-8b2sGM5SlY7XBPtBDM,54
|
17
|
-
nova/trame/view/theme/assets/core_style.scss,sha256=
|
18
|
+
nova/trame/view/theme/assets/core_style.scss,sha256=zZj7klB_iieNNTdh3WADpRbCA6WII0-nG86MKFEBYWY,1533
|
18
19
|
nova/trame/view/theme/assets/favicon.png,sha256=Xbp1nUmhcBDeObjsebEbEAraPDZ_M163M_ZLtm5AbQc,1927
|
19
20
|
nova/trame/view/theme/assets/js/delay_manager.js,sha256=vmb34DZ5YCQIlRW9Tf2M_uvJW6HFCmtlKZ5e_TPR8yg,536
|
20
21
|
nova/trame/view/theme/assets/js/lodash.debounce.min.js,sha256=GLzlQH04WDUNYN7i39ttHHejSdu-CpAvfWgDgKDn-OY,4448
|
21
22
|
nova/trame/view/theme/assets/js/lodash.throttle.min.js,sha256=9csqjX-M-LVGJnF3z4ha1R_36O5AfkFE8rPHkxmt3tE,4677
|
22
|
-
nova/trame/view/theme/assets/vuetify_config.json,sha256=
|
23
|
-
nova/trame/view/theme/theme.py,sha256=
|
23
|
+
nova/trame/view/theme/assets/vuetify_config.json,sha256=axz6cEWE_v1fO9XbM7cCKJghJp2DbU0lH6TuKYEG9j0,5311
|
24
|
+
nova/trame/view/theme/theme.py,sha256=OFUtq1IWriFcDu-346J67ZrSES8IOI9PTY_4Vwg7bZQ,11820
|
24
25
|
nova/trame/view/utilities/local_storage.py,sha256=vD8f2VZIpxhIKjZwEaD7siiPCTZO4cw9AfhwdawwYLY,3218
|
25
|
-
nova/trame/view_model/data_selector.py,sha256=
|
26
|
-
nova/trame/view_model/remote_file_input.py,sha256=
|
27
|
-
nova_trame-0.
|
28
|
-
nova_trame-0.
|
29
|
-
nova_trame-0.
|
30
|
-
nova_trame-0.
|
31
|
-
nova_trame-0.
|
26
|
+
nova/trame/view_model/data_selector.py,sha256=baijpcbKCUk0BITPpbhHR6nAG-SF5ho2EyEfARTvDAo,1441
|
27
|
+
nova/trame/view_model/remote_file_input.py,sha256=ojEOJ8ZPkajpbAaZi9VLj7g-uBjhb8BMrTdMmwf_J6A,3367
|
28
|
+
nova_trame-0.19.0.dev0.dist-info/LICENSE,sha256=Iu5QiDbwNbREg75iYaxIJ_V-zppuv4QFuBhAW-qiAlM,1061
|
29
|
+
nova_trame-0.19.0.dev0.dist-info/METADATA,sha256=Sf87sw5O7ATqba0bt-KUVtkGrkmY6oKmueowQAKFPw0,1451
|
30
|
+
nova_trame-0.19.0.dev0.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
|
31
|
+
nova_trame-0.19.0.dev0.dist-info/entry_points.txt,sha256=J2AmeSwiTYZ4ZqHHp9HO6v4MaYQTTBPbNh6WtoqOT58,42
|
32
|
+
nova_trame-0.19.0.dev0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|