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.
@@ -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 using_custom_directory:
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
- if using_custom_directory:
180
- for entry in os.listdir(base_path):
181
- path = base_path / entry
182
- if os.path.isdir(path):
183
- directories.append({"path": str(path), "title": entry})
184
- else:
185
- for dirpath, _, _ in os.walk(base_path):
186
- # Get the relative path from the start path
187
- path_parts = os.path.relpath(dirpath, base_path).split(os.sep)
188
-
189
- # Only create a new entry for top-level directories
190
- if len(path_parts) == 1 and path_parts[0] != ".": # This indicates a top-level directory
191
- current_dir = {"path": dirpath, "title": path_parts[0]}
192
- directories.append(current_dir)
193
-
194
- # Add subdirectories to the corresponding parent directory
195
- elif len(path_parts) > 1:
196
- current_level: Any = directories
197
- for part in path_parts[:-1]: # Parent directories
198
- for item in current_level:
199
- if item["title"] == part:
200
- if "children" not in item:
201
- item["children"] = []
202
- current_level = item["children"]
203
- break
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.model.get_directories())
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
- from typing import Any
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
- return work_state_map[state]
28
+ state_str = work_state_map[state]
28
29
  else:
29
- return state.value
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,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: nova-trame
3
- Version: 0.20.4
3
+ Version: 0.22.0
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
@@ -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=p-4SuK4IQpsd_JrzZ-tysba7GYpjIyAbsxLcBgrw5h4,8997
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=sAA0v9lLhqkuC4WqRYe8c_banXz8ZOZEgf24NzG5Kjk,11097
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=Sh5cOPaMWrFq8KTWEDui1dIbK53BPtGG2RZOSKEaoJ4,2186
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=7tU0EVIg9egxq1XjhqNFq_nUwSFSHAhqivgLdbf9oj4,3829
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=llI7u8121R78dkecS4D2OI604LWlpByWn7OL9cckI9Q,2561
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=L7ED6TDn5v2142iu-qt3i-jUg_5JEhLyC476t2OtohU,2467
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.20.4.dist-info/LICENSE,sha256=Iu5QiDbwNbREg75iYaxIJ_V-zppuv4QFuBhAW-qiAlM,1061
36
- nova_trame-0.20.4.dist-info/METADATA,sha256=48Ob64yY74oD4ppjh9h_Kf01diDGKDx1uKdHzIZaIUM,1603
37
- nova_trame-0.20.4.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
38
- nova_trame-0.20.4.dist-info/entry_points.txt,sha256=J2AmeSwiTYZ4ZqHHp9HO6v4MaYQTTBPbNh6WtoqOT58,42
39
- nova_trame-0.20.4.dist-info/RECORD,,
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,,