nova-trame 0.20.4__py3-none-any.whl → 0.22.0__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 +40 -40
- nova/trame/view/components/data_selector.py +2 -0
- nova/trame/view/components/progress_bar.py +20 -0
- nova/trame/view/theme/assets/core_style.scss +15 -0
- nova/trame/view_model/data_selector.py +30 -1
- nova/trame/view_model/progress_bar.py +15 -8
- {nova_trame-0.20.4.dist-info → nova_trame-0.22.0.dist-info}/METADATA +1 -1
- {nova_trame-0.20.4.dist-info → nova_trame-0.22.0.dist-info}/RECORD +11 -11
- {nova_trame-0.20.4.dist-info → nova_trame-0.22.0.dist-info}/LICENSE +0 -0
- {nova_trame-0.20.4.dist-info → nova_trame-0.22.0.dist-info}/WHEEL +0 -0
- {nova_trame-0.20.4.dist-info → nova_trame-0.22.0.dist-info}/entry_points.txt +0 -0
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
import os
|
4
4
|
from pathlib import Path
|
5
|
-
from typing import Any, List, Optional
|
5
|
+
from typing import Any, Dict, List, Optional
|
6
6
|
from warnings import warn
|
7
7
|
|
8
8
|
from natsort import natsorted
|
@@ -140,7 +140,7 @@ class DataSelectorModel:
|
|
140
140
|
|
141
141
|
return natsorted(experiments)
|
142
142
|
|
143
|
-
def sort_directories(self, directories: List[Any]) -> List[Any]:
|
143
|
+
def sort_directories(self, directories: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
144
144
|
# Sort the current level of dictionaries
|
145
145
|
sorted_dirs = natsorted(directories, key=lambda x: x["title"])
|
146
146
|
|
@@ -164,9 +164,11 @@ class DataSelectorModel:
|
|
164
164
|
|
165
165
|
return Path(self.state.custom_directory)
|
166
166
|
|
167
|
-
def get_directories(self) -> List[str]:
|
167
|
+
def get_directories(self, base_path: Optional[Path] = None) -> List[Dict[str, Any]]:
|
168
168
|
using_custom_directory = self.state.facility == CUSTOM_DIRECTORIES_LABEL
|
169
|
-
if
|
169
|
+
if base_path:
|
170
|
+
pass
|
171
|
+
elif using_custom_directory:
|
170
172
|
base_path = self.get_custom_directory_path()
|
171
173
|
else:
|
172
174
|
base_path = self.get_experiment_directory_path()
|
@@ -176,34 +178,31 @@ class DataSelectorModel:
|
|
176
178
|
|
177
179
|
directories = []
|
178
180
|
try:
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
# Add the last part (current directory) as a child
|
206
|
-
current_level.append({"path": dirpath, "title": path_parts[-1]})
|
181
|
+
for dirpath, dirs, _ in os.walk(base_path):
|
182
|
+
# Get the relative path from the start path
|
183
|
+
path_parts = os.path.relpath(dirpath, base_path).split(os.sep)
|
184
|
+
|
185
|
+
if len(path_parts) > 1:
|
186
|
+
dirs.clear()
|
187
|
+
|
188
|
+
# Only create a new entry for top-level directories
|
189
|
+
if len(path_parts) == 1 and path_parts[0] != ".": # This indicates a top-level directory
|
190
|
+
current_dir = {"path": dirpath, "title": path_parts[0]}
|
191
|
+
directories.append(current_dir)
|
192
|
+
|
193
|
+
# Add subdirectories to the corresponding parent directory
|
194
|
+
elif len(path_parts) > 1:
|
195
|
+
current_level: Any = directories
|
196
|
+
for part in path_parts[:-1]: # Parent directories
|
197
|
+
for item in current_level:
|
198
|
+
if item["title"] == part:
|
199
|
+
if "children" not in item:
|
200
|
+
item["children"] = []
|
201
|
+
current_level = item["children"]
|
202
|
+
break
|
203
|
+
|
204
|
+
# Add the last part (current directory) as a child
|
205
|
+
current_level.append({"path": dirpath, "title": path_parts[-1]})
|
207
206
|
except OSError:
|
208
207
|
pass
|
209
208
|
|
@@ -212,17 +211,18 @@ class DataSelectorModel:
|
|
212
211
|
def get_datafiles(self) -> List[str]:
|
213
212
|
datafiles = []
|
214
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)
|
218
|
+
else:
|
219
|
+
return []
|
220
|
+
|
215
221
|
try:
|
216
222
|
if self.state.prefix:
|
217
|
-
datafile_path = str(
|
218
|
-
Path("/")
|
219
|
-
/ self.state.facility
|
220
|
-
/ self.get_instrument_dir()
|
221
|
-
/ self.state.experiment
|
222
|
-
/ self.state.prefix
|
223
|
-
)
|
223
|
+
datafile_path = str(base_path / self.state.prefix)
|
224
224
|
else:
|
225
|
-
datafile_path = self.state.directory
|
225
|
+
datafile_path = str(base_path / self.state.directory)
|
226
226
|
|
227
227
|
for entry in os.scandir(datafile_path):
|
228
228
|
if entry.is_file():
|
@@ -133,8 +133,10 @@ class DataSelector(datagrid.VGrid):
|
|
133
133
|
activatable=True,
|
134
134
|
active_strategy="single-independent",
|
135
135
|
classes="flex-1-0 h-0 overflow-y-auto",
|
136
|
+
fluid=True,
|
136
137
|
item_value="path",
|
137
138
|
items=(self._directories_name,),
|
139
|
+
click_open=(self._vm.expand_directory, "[$event.path]"),
|
138
140
|
update_activated=(self._vm.set_directory, "$event"),
|
139
141
|
)
|
140
142
|
vuetify.VListItem("No directories found", classes="flex-0-1 text-center", v_else=True)
|
@@ -45,6 +45,26 @@ class ProgressBar:
|
|
45
45
|
v_show=(f"{self.id}.show_progress",),
|
46
46
|
):
|
47
47
|
html.H5(v_text=f"{self.id}.details")
|
48
|
+
with vuetify.VMenu(
|
49
|
+
max_width=900,
|
50
|
+
location="bottom",
|
51
|
+
no_click_animation=True,
|
52
|
+
close_on_content_click=False,
|
53
|
+
open_on_hover=True,
|
54
|
+
v_show=False,
|
55
|
+
):
|
56
|
+
with vuetify.Template(v_slot_activator="{ props }"):
|
57
|
+
vuetify.VIcon(
|
58
|
+
"mdi-information",
|
59
|
+
v_show=f"{self.id}.show_full_details",
|
60
|
+
v_bind="props",
|
61
|
+
classes="ml-2",
|
62
|
+
color="primary",
|
63
|
+
)
|
64
|
+
|
65
|
+
with vuetify.VCard(classes="bg-grey"):
|
66
|
+
vuetify.VCardText(f"{{{{ {self.id}.full_details }}}}", classes="display-linebreaks")
|
67
|
+
|
48
68
|
with vuetify.VProgressLinear(
|
49
69
|
height="25",
|
50
70
|
model_value="100",
|
@@ -16,6 +16,18 @@ html {
|
|
16
16
|
display: none !important;
|
17
17
|
}
|
18
18
|
|
19
|
+
.display-linebreaks {
|
20
|
+
white-space: pre-wrap;
|
21
|
+
}
|
22
|
+
|
23
|
+
.nova-data-selector {
|
24
|
+
.v-list-group {
|
25
|
+
.v-treeview-item {
|
26
|
+
--indent-padding: 1em !important;
|
27
|
+
}
|
28
|
+
}
|
29
|
+
}
|
30
|
+
|
19
31
|
.nova-data-selector revo-grid {
|
20
32
|
font-family: 'Roboto', sans-serif;
|
21
33
|
font-size: 0.75rem !important;
|
@@ -209,3 +221,6 @@ html {
|
|
209
221
|
}
|
210
222
|
}
|
211
223
|
}
|
224
|
+
|
225
|
+
|
226
|
+
.v-progress-linear__content { pointer-events: all !important; }
|
@@ -1,6 +1,7 @@
|
|
1
1
|
"""View model implementation for the DataSelector widget."""
|
2
2
|
|
3
3
|
import os
|
4
|
+
from pathlib import Path
|
4
5
|
from typing import Any, Dict, List, Optional
|
5
6
|
|
6
7
|
from nova.mvvm.interface import BindingInterface
|
@@ -14,6 +15,8 @@ class DataSelectorViewModel:
|
|
14
15
|
self.model = model
|
15
16
|
|
16
17
|
self.datafiles: List[Dict[str, Any]] = []
|
18
|
+
self.directories: List[Dict[str, Any]] = []
|
19
|
+
self.expanded: List[str] = []
|
17
20
|
|
18
21
|
self.state_bind = binding.new_bind(self.model.state, callback_after_update=self.on_state_updated)
|
19
22
|
self.facilities_bind = binding.new_bind()
|
@@ -23,6 +26,30 @@ class DataSelectorViewModel:
|
|
23
26
|
self.datafiles_bind = binding.new_bind()
|
24
27
|
self.reset_bind = binding.new_bind()
|
25
28
|
|
29
|
+
def expand_directory(self, paths: List[str]) -> None:
|
30
|
+
if paths[-1] in self.expanded:
|
31
|
+
return
|
32
|
+
|
33
|
+
# Query for the new subdirectories to display in the view
|
34
|
+
new_directories = self.model.get_directories(Path(paths[-1]))
|
35
|
+
|
36
|
+
# Find the entry in the existing directories that corresponds to the directory to expand
|
37
|
+
current_level: Dict[str, Any] = {}
|
38
|
+
children: List[Dict[str, Any]] = self.directories
|
39
|
+
for current_path in paths:
|
40
|
+
if current_level:
|
41
|
+
children = current_level["children"]
|
42
|
+
|
43
|
+
for entry in children:
|
44
|
+
if current_path == entry["path"]:
|
45
|
+
current_level = entry
|
46
|
+
break
|
47
|
+
current_level["children"] = new_directories
|
48
|
+
|
49
|
+
# Mark this directory as expanded and display the new content
|
50
|
+
self.expanded.append(paths[-1])
|
51
|
+
self.directories_bind.update_in_view(self.directories)
|
52
|
+
|
26
53
|
def set_directory(self, directory_path: str = "") -> None:
|
27
54
|
self.model.set_directory(directory_path)
|
28
55
|
self.update_view()
|
@@ -33,6 +60,8 @@ class DataSelectorViewModel:
|
|
33
60
|
|
34
61
|
def reset(self) -> None:
|
35
62
|
self.model.set_directory("")
|
63
|
+
self.directories = self.model.get_directories()
|
64
|
+
self.expanded = []
|
36
65
|
self.reset_bind.update_in_view(None)
|
37
66
|
|
38
67
|
def on_state_updated(self, results: Dict[str, Any]) -> None:
|
@@ -55,7 +84,7 @@ class DataSelectorViewModel:
|
|
55
84
|
self.facilities_bind.update_in_view(self.model.get_facilities())
|
56
85
|
self.instruments_bind.update_in_view(self.model.get_instruments())
|
57
86
|
self.experiments_bind.update_in_view(self.model.get_experiments())
|
58
|
-
self.directories_bind.update_in_view(self.
|
87
|
+
self.directories_bind.update_in_view(self.directories)
|
59
88
|
|
60
89
|
self.datafiles = [
|
61
90
|
{"path": datafile, "title": os.path.basename(datafile)} for datafile in self.model.get_datafiles()
|
@@ -1,6 +1,7 @@
|
|
1
1
|
"""Module for the JobProgress ViewModel."""
|
2
2
|
|
3
|
-
|
3
|
+
import json
|
4
|
+
from typing import Any, Dict, Tuple
|
4
5
|
|
5
6
|
import blinker
|
6
7
|
from pydantic import BaseModel
|
@@ -10,7 +11,7 @@ from nova.common.signals import Signal, get_signal_id
|
|
10
11
|
from nova.mvvm.interface import BindingInterface
|
11
12
|
|
12
13
|
|
13
|
-
def details_from_state(state: WorkState) -> str:
|
14
|
+
def details_from_state(state: WorkState, details: Dict[str, Any]) -> Tuple[str, str]:
|
14
15
|
work_state_map = {
|
15
16
|
WorkState.NOT_STARTED: "job not started",
|
16
17
|
WorkState.UPLOADING_DATA: "uploading data",
|
@@ -24,9 +25,12 @@ def details_from_state(state: WorkState) -> str:
|
|
24
25
|
WorkState.CANCELING: "canceling job",
|
25
26
|
}
|
26
27
|
if state in work_state_map:
|
27
|
-
|
28
|
+
state_str = work_state_map[state]
|
28
29
|
else:
|
29
|
-
|
30
|
+
state_str = state.value
|
31
|
+
|
32
|
+
full_details_dict = json.dumps(details.get("original_dict", {}), indent=4)
|
33
|
+
return state_str, full_details_dict
|
30
34
|
|
31
35
|
|
32
36
|
class ProgressState(BaseModel):
|
@@ -34,11 +38,13 @@ class ProgressState(BaseModel):
|
|
34
38
|
|
35
39
|
progress: str = ""
|
36
40
|
details: str = ""
|
41
|
+
full_details: str = ""
|
37
42
|
show_progress: bool = False
|
43
|
+
show_full_details: bool = False
|
38
44
|
show_failed: bool = False
|
39
45
|
show_ok: bool = False
|
40
46
|
|
41
|
-
def update_from_workstate(self, state: WorkState) -> None:
|
47
|
+
def update_from_workstate(self, state: WorkState, details: Dict[str, Any]) -> None:
|
42
48
|
progress = "0"
|
43
49
|
match state:
|
44
50
|
case WorkState.UPLOADING_DATA:
|
@@ -58,7 +64,8 @@ class ProgressState(BaseModel):
|
|
58
64
|
self.show_failed = state == WorkState.ERROR
|
59
65
|
self.show_ok = state == WorkState.FINISHED
|
60
66
|
self.progress = progress
|
61
|
-
self.details = details_from_state(state)
|
67
|
+
self.details, self.full_details = details_from_state(state, details)
|
68
|
+
self.show_full_details = self.full_details != "{}"
|
62
69
|
|
63
70
|
|
64
71
|
class ProgressBarViewModel:
|
@@ -70,6 +77,6 @@ class ProgressBarViewModel:
|
|
70
77
|
self.progress_signal = blinker.signal(get_signal_id(id, Signal.PROGRESS))
|
71
78
|
self.progress_signal.connect(self.update_state, weak=False)
|
72
79
|
|
73
|
-
async def update_state(self, _sender: Any, state: WorkState, details: str) -> None:
|
74
|
-
self.progress_state.update_from_workstate(state)
|
80
|
+
async def update_state(self, _sender: Any, state: WorkState, details: Dict[str, Any]) -> None:
|
81
|
+
self.progress_state.update_from_workstate(state, details)
|
75
82
|
self.progress_state_bind.update_in_view(self.progress_state)
|
@@ -1,13 +1,13 @@
|
|
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=UnLBCp_jJ523QxTR3R8iun2Ogq4D0G0lxmtW9e_zwOM,8938
|
4
4
|
nova/trame/model/remote_file_input.py,sha256=9KAf31ZHzpsh_aXUrNcF81Q5jvUZDWCzW1QATKls-Jk,3675
|
5
5
|
nova/trame/view/components/__init__.py,sha256=60BeS69aOrFnkptjuD17rfPE1f4Z35iBH56TRmW5MW8,451
|
6
|
-
nova/trame/view/components/data_selector.py,sha256=
|
6
|
+
nova/trame/view/components/data_selector.py,sha256=lgZjyT_jc3eE19HNgz_Hdog5bWvXZJ3IfxxiSBkZ7hE,11222
|
7
7
|
nova/trame/view/components/execution_buttons.py,sha256=fIkrWKI3jFZqk3GHhtmYh3nK2c-HOXpD3D3zd_TUpi0,4049
|
8
8
|
nova/trame/view/components/file_upload.py,sha256=7VcpfA6zmiqMDLkwVPlb35Tf0IUTBN1xsHpoUFnSr1w,3111
|
9
9
|
nova/trame/view/components/input_field.py,sha256=q6WQ_N-BOlimUL9zgazDlsDfK28FrrKjH4he8e_HzRA,16088
|
10
|
-
nova/trame/view/components/progress_bar.py,sha256=
|
10
|
+
nova/trame/view/components/progress_bar.py,sha256=fCfPw4MPAvORaeFOXugreok4GLpDVZGMkqvnv-AhMxg,2967
|
11
11
|
nova/trame/view/components/remote_file_input.py,sha256=ByrBFj8svyWezcardCWrS_4Ag3fgTYNg_11lDW1FIA8,9669
|
12
12
|
nova/trame/view/components/tool_outputs.py,sha256=-6pDURd2l_FK_8EWa9BI3KhU_KJXJ6uyJ_rW4nQVc08,2337
|
13
13
|
nova/trame/view/components/visualization/__init__.py,sha256=reqkkbhD5uSksHHlhVMy1qNUCwSekS5HlXk6wCREYxU,152
|
@@ -19,7 +19,7 @@ nova/trame/view/layouts/hbox.py,sha256=qlOMp_iOropIkC9Jxa6D89b7OPv0pNvJ73tUEzddy
|
|
19
19
|
nova/trame/view/layouts/utils.py,sha256=Hg34VQWTG3yHBsgNvmfatR4J-uL3cko7UxSJpT-h3JI,376
|
20
20
|
nova/trame/view/layouts/vbox.py,sha256=hzhzPu99R2fAclMe-FwHZseJWk7iailZ31bKdGhi1hk,3514
|
21
21
|
nova/trame/view/theme/__init__.py,sha256=70_marDlTigIcPEOGiJb2JTs-8b2sGM5SlY7XBPtBDM,54
|
22
|
-
nova/trame/view/theme/assets/core_style.scss,sha256=
|
22
|
+
nova/trame/view/theme/assets/core_style.scss,sha256=lK86Fp55oAMDh1eUHA-DTeGGZi0uUYOseIyJUTj-0A0,4081
|
23
23
|
nova/trame/view/theme/assets/favicon.png,sha256=Xbp1nUmhcBDeObjsebEbEAraPDZ_M163M_ZLtm5AbQc,1927
|
24
24
|
nova/trame/view/theme/assets/js/delay_manager.js,sha256=mRV6KoO8-Bxq3tG5Bh9CQYy-CRVbkj3IYlqNb-Og7cI,526
|
25
25
|
nova/trame/view/theme/assets/js/lodash.min.js,sha256=KCyAYJ-fsqtp_HMwbjhy6IKjlA5lrVrtWt1JdMsC57k,73016
|
@@ -27,13 +27,13 @@ nova/trame/view/theme/assets/js/revo_grid.js,sha256=WBsmoslu9qI5DHZkHkJam2AVgdiB
|
|
27
27
|
nova/trame/view/theme/assets/vuetify_config.json,sha256=a0FSgpLYWGFlRGSMhMq61MyDFBEBwvz55G4qjkM08cs,5627
|
28
28
|
nova/trame/view/theme/theme.py,sha256=0KzBJgAZRwlnwzCIf7gUjDY-gbhON7b2h3CMh2_9HY4,11746
|
29
29
|
nova/trame/view/utilities/local_storage.py,sha256=vD8f2VZIpxhIKjZwEaD7siiPCTZO4cw9AfhwdawwYLY,3218
|
30
|
-
nova/trame/view_model/data_selector.py,sha256=
|
30
|
+
nova/trame/view_model/data_selector.py,sha256=RyMHml1K_pupH4JtXnGxAaYTYYwNoEVus7Abdpqwueo,3698
|
31
31
|
nova/trame/view_model/execution_buttons.py,sha256=MfKSp95D92EqpD48C15cBo6dLO0Yld4FeRZMJNxJf7Y,3551
|
32
|
-
nova/trame/view_model/progress_bar.py,sha256=
|
32
|
+
nova/trame/view_model/progress_bar.py,sha256=6AUKHF3hfzbdsHqNEnmHRgDcBKY5TT8ywDx9S6ovnsc,2854
|
33
33
|
nova/trame/view_model/remote_file_input.py,sha256=ojEOJ8ZPkajpbAaZi9VLj7g-uBjhb8BMrTdMmwf_J6A,3367
|
34
34
|
nova/trame/view_model/tool_outputs.py,sha256=ev6LY7fJ0H2xAJn9f5ww28c8Kpom2SYc2FbvFcoN4zg,829
|
35
|
-
nova_trame-0.
|
36
|
-
nova_trame-0.
|
37
|
-
nova_trame-0.
|
38
|
-
nova_trame-0.
|
39
|
-
nova_trame-0.
|
35
|
+
nova_trame-0.22.0.dist-info/LICENSE,sha256=Iu5QiDbwNbREg75iYaxIJ_V-zppuv4QFuBhAW-qiAlM,1061
|
36
|
+
nova_trame-0.22.0.dist-info/METADATA,sha256=7ySWYYUkEE99UeZnMvMMwskNWHHhHjrrNv0_5dYRU0s,1603
|
37
|
+
nova_trame-0.22.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
38
|
+
nova_trame-0.22.0.dist-info/entry_points.txt,sha256=J2AmeSwiTYZ4ZqHHp9HO6v4MaYQTTBPbNh6WtoqOT58,42
|
39
|
+
nova_trame-0.22.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|