nova-trame 0.19.0.dev1__py3-none-any.whl → 0.19.0.dev3__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 List, Optional
5
+ from typing import Any, List, Optional
6
6
  from warnings import warn
7
7
 
8
8
  from pydantic import BaseModel, Field, field_validator, model_validator
@@ -68,6 +68,8 @@ class DataSelectorState(BaseModel, validate_assignment=True):
68
68
  facility: str = Field(default="", title="Facility")
69
69
  instrument: str = Field(default="", title="Instrument")
70
70
  experiment: str = Field(default="", title="Experiment")
71
+ directory: str = Field(default="")
72
+ prefix: str = Field(default="")
71
73
 
72
74
  @field_validator("experiment", mode="after")
73
75
  @classmethod
@@ -99,19 +101,20 @@ class DataSelectorState(BaseModel, validate_assignment=True):
99
101
  class DataSelectorModel:
100
102
  """Manages file system interactions for the DataSelector widget."""
101
103
 
102
- def __init__(self, facility: str, instrument: str) -> None:
104
+ def __init__(self, facility: str, instrument: str, prefix: str) -> None:
103
105
  self.state = DataSelectorState()
104
106
  self.state.facility = facility
105
107
  self.state.instrument = instrument
108
+ self.state.prefix = prefix
106
109
 
107
110
  def get_facilities(self) -> List[str]:
108
- return get_facilities()
111
+ return sorted(get_facilities())
109
112
 
110
113
  def get_instrument_dir(self) -> str:
111
114
  return INSTRUMENTS.get(self.state.facility, {}).get(self.state.instrument, "")
112
115
 
113
116
  def get_instruments(self) -> List[str]:
114
- return get_instruments(self.state.facility)
117
+ return sorted(get_instruments(self.state.facility))
115
118
 
116
119
  def get_experiments(self) -> List[str]:
117
120
  experiments = []
@@ -126,18 +129,67 @@ class DataSelectorModel:
126
129
 
127
130
  return sorted(experiments)
128
131
 
132
+ def get_directories(self) -> List[Any]:
133
+ if not self.state.experiment:
134
+ return []
135
+
136
+ directories = []
137
+
138
+ experiment_path = Path("/") / self.state.facility / self.get_instrument_dir() / self.state.experiment
139
+ try:
140
+ for dirpath, _, _ in os.walk(experiment_path):
141
+ # Get the relative path from the start path
142
+ path_parts = os.path.relpath(dirpath, experiment_path).split(os.sep)
143
+
144
+ # Only create a new entry for top-level directories
145
+ if len(path_parts) == 1 and path_parts[0] != ".": # This indicates a top-level directory
146
+ current_dir = {"path": dirpath, "title": path_parts[0]}
147
+ directories.append(current_dir)
148
+
149
+ # Add subdirectories to the corresponding parent directory
150
+ elif len(path_parts) > 1:
151
+ current_level: Any = directories
152
+ for part in path_parts[:-1]: # Parent directories
153
+ for item in current_level:
154
+ if item["title"] == part:
155
+ if "children" not in item:
156
+ item["children"] = []
157
+ current_level = item["children"]
158
+ break
159
+
160
+ # Add the last part (current directory) as a child
161
+ current_level.append({"path": dirpath, "title": path_parts[-1]})
162
+ except OSError:
163
+ pass
164
+
165
+ return directories
166
+
129
167
  def get_datafiles(self) -> List[str]:
130
168
  datafiles = []
131
169
 
132
- experiment_path = Path("/") / self.state.facility / self.get_instrument_dir() / self.state.experiment / "nexus"
133
170
  try:
134
- for fname in os.listdir(experiment_path):
135
- datafiles.append(str(experiment_path / fname))
171
+ if self.state.prefix:
172
+ datafile_path = str(
173
+ Path("/")
174
+ / self.state.facility
175
+ / self.get_instrument_dir()
176
+ / self.state.experiment
177
+ / self.state.prefix
178
+ )
179
+ else:
180
+ datafile_path = self.state.directory
181
+
182
+ for entry in os.scandir(datafile_path):
183
+ if entry.is_file():
184
+ datafiles.append(entry.path)
136
185
  except OSError:
137
186
  pass
138
187
 
139
188
  return sorted(datafiles)
140
189
 
190
+ def set_directory(self, directory_path: str) -> None:
191
+ self.state.directory = directory_path
192
+
141
193
  def set_state(self, facility: Optional[str], instrument: Optional[str], experiment: Optional[str]) -> None:
142
194
  if facility is not None:
143
195
  self.state.facility = facility
@@ -1,8 +1,9 @@
1
1
  """View Implementation for DataSelector."""
2
2
 
3
- from typing import Any, Optional
3
+ from typing import Any, Optional, cast
4
4
 
5
5
  from trame.app import get_server
6
+ from trame.widgets import html
6
7
  from trame.widgets import vuetify3 as vuetify
7
8
 
8
9
  from nova.mvvm.trame_binding import TrameBinding
@@ -12,11 +13,21 @@ from nova.trame.view_model.data_selector import DataSelectorViewModel
12
13
 
13
14
  from .input_field import InputField
14
15
 
16
+ vuetify.enable_lab()
17
+
15
18
 
16
19
  class DataSelector(vuetify.VDataTable):
17
20
  """Allows the user to select datafiles from an IPTS experiment."""
18
21
 
19
- def __init__(self, v_model: str, facility: str = "", instrument: str = "", **kwargs: Any) -> None:
22
+ def __init__(
23
+ self,
24
+ v_model: str,
25
+ facility: str = "",
26
+ instrument: str = "",
27
+ prefix: str = "",
28
+ select_strategy: str = "all",
29
+ **kwargs: Any,
30
+ ) -> None:
20
31
  """Constructor for DataSelector.
21
32
 
22
33
  Parameters
@@ -28,6 +39,12 @@ class DataSelector(vuetify.VDataTable):
28
39
  The facility to restrict data selection to. Options: HFIR, SNS
29
40
  instrument : str, optional
30
41
  The instrument to restrict data selection to. Please use the instrument acronym (e.g. CG-2).
42
+ prefix : str, optional
43
+ A subdirectory within the user's chosen experiment to show files. If not specified, the user will be shown a
44
+ folder browser and will be able to see all files in the experiment that they have access to.
45
+ select_strategy : str, optional
46
+ The selection strategy to pass to the `VDataTable component <https://trame.readthedocs.io/en/latest/trame.widgets.vuetify3.html#trame.widgets.vuetify3.VDataTable>`__.
47
+ If unset, the `all` strategy will be used.
31
48
  **kwargs
32
49
  All other arguments will be passed to the underlying
33
50
  `VDataTable component <https://trame.readthedocs.io/en/latest/trame.widgets.vuetify3.html#trame.widgets.vuetify3.VDataTable>`_.
@@ -39,11 +56,20 @@ class DataSelector(vuetify.VDataTable):
39
56
  if "items" in kwargs:
40
57
  raise AttributeError("The items parameter is not allowed on DataSelector widget.")
41
58
 
59
+ if "label" in kwargs:
60
+ self._label = kwargs["label"]
61
+ else:
62
+ self._label = None
63
+
42
64
  self._v_model = v_model
65
+ self._prefix = prefix
66
+ self._select_strategy = select_strategy
67
+
43
68
  self._state_name = f"nova__dataselector_{self._next_id}_state"
44
69
  self._facilities_name = f"nova__dataselector_{self._next_id}_facilities"
45
70
  self._instruments_name = f"nova__dataselector_{self._next_id}_instruments"
46
71
  self._experiments_name = f"nova__dataselector_{self._next_id}_experiments"
72
+ self._directories_name = f"nova__dataselector_{self._next_id}_directories"
47
73
  self._datafiles_name = f"nova__dataselector_{self._next_id}_datafiles"
48
74
 
49
75
  self.create_model(facility, instrument)
@@ -52,39 +78,74 @@ class DataSelector(vuetify.VDataTable):
52
78
  self.create_ui(facility, instrument, **kwargs)
53
79
 
54
80
  def create_ui(self, facility: str, instrument: str, **kwargs: Any) -> None:
55
- with GridLayout(columns=3):
56
- columns = 3
57
- if facility == "":
58
- columns -= 1
59
- InputField(v_model=f"{self._state_name}.facility", items=(self._facilities_name,), type="autocomplete")
60
- if instrument == "":
61
- columns -= 1
81
+ with html.Div():
82
+ with GridLayout(columns=3):
83
+ columns = 3
84
+ if facility == "":
85
+ columns -= 1
86
+ InputField(
87
+ v_model=f"{self._state_name}.facility", items=(self._facilities_name,), type="autocomplete"
88
+ )
89
+ if instrument == "":
90
+ columns -= 1
91
+ InputField(
92
+ v_model=f"{self._state_name}.instrument", items=(self._instruments_name,), type="autocomplete"
93
+ )
62
94
  InputField(
63
- v_model=f"{self._state_name}.instrument", items=(self._instruments_name,), type="autocomplete"
95
+ v_model=f"{self._state_name}.experiment",
96
+ column_span=columns,
97
+ items=(self._experiments_name,),
98
+ type="autocomplete",
64
99
  )
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
- )
81
- self.items = (self._datafiles_name,)
82
-
83
- if "update_modelValue" not in kwargs:
84
- self.update_modelValue = f"flushState('{self._v_model.split('.')[0]}')"
100
+
101
+ with GridLayout(columns=3, valign="start"):
102
+ if not self._prefix:
103
+ with html.Div():
104
+ vuetify.VListSubheader("Available Directories", classes="justify-center px-0")
105
+ vuetify.VTreeview(
106
+ v_if=(f"{self._directories_name}.length > 0",),
107
+ activatable=True,
108
+ active_strategy="single-independent",
109
+ item_value="path",
110
+ items=(self._directories_name,),
111
+ update_activated=(self._vm.set_directory, "$event"),
112
+ )
113
+ vuetify.VListItem("No directories found", v_else=True)
114
+
115
+ super().__init__(
116
+ v_model=self._v_model,
117
+ column_span=3 if self._prefix else 2,
118
+ headers=("[{ align: 'center', key: 'title', title: 'Available Datafiles' }]",),
119
+ item_title="title",
120
+ item_value="path",
121
+ select_strategy=self._select_strategy,
122
+ show_select=True,
123
+ **kwargs,
124
+ )
125
+ self.items = (self._datafiles_name,)
126
+ if "update_modelValue" not in kwargs:
127
+ self.update_modelValue = f"flushState('{self._v_model.split('.')[0]}')"
128
+
129
+ with cast(
130
+ vuetify.VSelect,
131
+ InputField(
132
+ v_if=f"{self._v_model}.length > 0",
133
+ v_model=self._v_model,
134
+ classes="nova-readonly",
135
+ clearable=True,
136
+ label=self._label,
137
+ readonly=True,
138
+ type="select",
139
+ ),
140
+ ):
141
+ with vuetify.Template(raw_attrs=['v-slot:selection="{ item, index }"']):
142
+ vuetify.VChip("{{ item.title }}", v_if="index < 2")
143
+ html.Span(
144
+ f"(+{{{{ {self._v_model}.length - 2 }}}} others)", v_if="index === 2", classes="text-caption"
145
+ )
85
146
 
86
147
  def create_model(self, facility: str, instrument: str) -> None:
87
- self._model = DataSelectorModel(facility, instrument)
148
+ self._model = DataSelectorModel(facility, instrument, self._prefix)
88
149
 
89
150
  def create_viewmodel(self) -> None:
90
151
  server = get_server(None, client_type="vue3")
@@ -95,6 +156,7 @@ class DataSelector(vuetify.VDataTable):
95
156
  self._vm.facilities_bind.connect(self._facilities_name)
96
157
  self._vm.instruments_bind.connect(self._instruments_name)
97
158
  self._vm.experiments_bind.connect(self._experiments_name)
159
+ self._vm.directories_bind.connect(self._directories_name)
98
160
  self._vm.datafiles_bind.connect(self._datafiles_name)
99
161
 
100
162
  self._vm.update_view()
@@ -10,17 +10,14 @@ from .remote_file_input import RemoteFileInput
10
10
  class FileUpload(vuetify.VBtn):
11
11
  """Component for uploading a file from either the user's filesystem or the server filesystem."""
12
12
 
13
- def __init__(
14
- self, v_model: Optional[str] = None, base_paths: Optional[List[str]] = None, label: str = "", **kwargs: Any
15
- ) -> None:
13
+ def __init__(self, v_model: str, base_paths: Optional[List[str]] = None, label: str = "", **kwargs: Any) -> None:
16
14
  """Constructor for FileUpload.
17
15
 
18
16
  Parameters
19
17
  ----------
20
- v_model : str, optional
18
+ v_model : str
21
19
  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.
20
+ state variable will contain the file contents.
24
21
  base_paths: list[str], optional
25
22
  Passed to :ref:`RemoteFileInput <api_remotefileinput>`.
26
23
  label : str, optional
@@ -44,20 +41,22 @@ class FileUpload(vuetify.VBtn):
44
41
  self.create_ui()
45
42
 
46
43
  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:
44
+ self.local_file_input = vuetify.VFileInput(
45
+ v_model=(self._v_model, None),
46
+ classes="d-none",
47
+ ref=self._ref_name,
49
48
  # 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 }};"
49
+ update_modelValue=(
50
+ f"{self._v_model}.text().then((contents) => {{"
51
+ f" {self._v_model} = contents;"
53
52
  f" flushState('{self._v_model.split('.')[0]}');"
54
53
  "});"
55
- )
54
+ ),
55
+ )
56
56
  self.remote_file_input = RemoteFileInput(
57
- v_model=self._v_model,
58
- base_paths=self._base_paths,
59
- input_props={"classes": "d-none"},
57
+ v_model=self._v_model, base_paths=self._base_paths, input_props={"classes": "d-none"}, return_contents=True
60
58
  )
59
+
61
60
  with self:
62
61
  with vuetify.VMenu(activator="parent"):
63
62
  with vuetify.VList():
@@ -31,6 +31,7 @@ class RemoteFileInput:
31
31
  dialog_props: Optional[dict[str, Any]] = None,
32
32
  extensions: Optional[list[str]] = None,
33
33
  input_props: Optional[dict[str, Any]] = None,
34
+ return_contents: bool = False,
34
35
  ) -> None:
35
36
  """Constructor for RemoteFileInput.
36
37
 
@@ -53,6 +54,9 @@ class RemoteFileInput:
53
54
  Only files with these extensions will be shown by default. The user can still choose to view all files.
54
55
  input_props : dict[str, typing.Any], optional
55
56
  Props to be passed to InputField.
57
+ return_contents : bool
58
+ If true, then the v_model will contain the contents of the file. If false, then the v_model will contain the
59
+ path of the file.
56
60
 
57
61
  Raises
58
62
  ------
@@ -74,6 +78,7 @@ class RemoteFileInput:
74
78
  self.dialog_props = dict(dialog_props) if dialog_props else {}
75
79
  self.extensions = extensions if extensions else []
76
80
  self.input_props = dict(input_props) if input_props else {}
81
+ self.return_contents = return_contents
77
82
 
78
83
  if "__events" not in self.input_props:
79
84
  self.input_props["__events"] = []
@@ -179,18 +184,27 @@ class RemoteFileInput:
179
184
  else:
180
185
  model_name = self.v_model
181
186
 
187
+ self.set_v_model = client.JSEval(
188
+ exec=f"{model_name} = $event; flushState('{model_name.split('.')[0].split('[')[0]}');"
189
+ ).exec
190
+
182
191
  self.vm = RemoteFileInputViewModel(self.model, binding)
183
192
 
184
193
  self.vm.dialog_bind.connect(self.vm.get_dialog_state_name())
185
194
  self.vm.file_list_bind.connect(self.vm.get_file_list_state_name())
186
195
  self.vm.filter_bind.connect(self.vm.get_filter_state_name())
187
196
  self.vm.on_close_bind.connect(client.JSEval(exec=f"{self.vm.get_dialog_state_name()} = false;").exec)
188
- self.vm.on_update_bind.connect(
189
- client.JSEval(exec=f"{model_name} = $event; flushState('{model_name.split('.')[0].split('[')[0]}');").exec
190
- )
197
+ if self.return_contents:
198
+ self.vm.on_update_bind.connect(self.read_file)
199
+ else:
200
+ self.vm.on_update_bind.connect(self.set_v_model)
191
201
  self.vm.showing_all_bind.connect(self.vm.get_showing_all_state_name())
192
202
  self.vm.valid_selection_bind.connect(self.vm.get_valid_selection_state_name())
193
203
 
204
+ def read_file(self, file_path: str) -> None:
205
+ with open(file_path, mode="r") as file:
206
+ self.set_v_model(file.read())
207
+
194
208
  def select_file(self, value: str) -> None:
195
209
  """Programmatically set the v_model value."""
196
210
  self.vm.select_file(value)
@@ -20,6 +20,12 @@ html {
20
20
  resize: none !important;
21
21
  }
22
22
 
23
+ .nova-readonly {
24
+ .v-select__menu-icon {
25
+ display: none;
26
+ }
27
+ }
28
+
23
29
  @media only screen and (max-width: 959px) {
24
30
  .d-grid {
25
31
  grid-template-columns: repeat(1, 1fr) !important;
@@ -42,6 +48,16 @@ html {
42
48
  border-radius: 4px;
43
49
  }
44
50
 
51
+ .v-data-table {
52
+ td {
53
+ vertical-align: middle;
54
+ }
55
+
56
+ .v-divider {
57
+ margin: 0;
58
+ }
59
+ }
60
+
45
61
  .v-tab.v-btn {
46
62
  height: 30px !important;
47
63
  min-width: fit-content !important;
@@ -85,4 +101,23 @@ html {
85
101
  .d-grid {
86
102
  align-items:center;
87
103
  }
104
+
105
+ .v-treeview {
106
+ .v-list-item {
107
+ text-align: left;
108
+
109
+ .v-list-item__prepend {
110
+ width: 40px;
111
+ }
112
+
113
+ .v-btn {
114
+ height: 24px;
115
+ width: 24px;
116
+
117
+ .v-icon {
118
+ font-size: 16px;
119
+ }
120
+ }
121
+ }
122
+ }
88
123
  }
@@ -178,10 +178,13 @@
178
178
  },
179
179
  "VTextField": {
180
180
  "VBtn": {
181
- "size": "small",
181
+ "size": "default",
182
182
  "variant": "outlined"
183
183
  },
184
184
  "variant": "outlined"
185
+ },
186
+ "VTreeview": {
187
+ "density": "compact"
185
188
  }
186
189
  },
187
190
  "title": "Compact",
@@ -1,5 +1,6 @@
1
1
  """View model implementation for the DataSelector widget."""
2
2
 
3
+ import os
3
4
  from typing import Any, Optional
4
5
 
5
6
  from nova.mvvm.interface import BindingInterface
@@ -16,8 +17,13 @@ class DataSelectorViewModel:
16
17
  self.facilities_bind = binding.new_bind()
17
18
  self.instruments_bind = binding.new_bind()
18
19
  self.experiments_bind = binding.new_bind()
20
+ self.directories_bind = binding.new_bind()
19
21
  self.datafiles_bind = binding.new_bind()
20
22
 
23
+ def set_directory(self, directory_path: str) -> None:
24
+ self.model.set_directory(directory_path)
25
+ self.update_view()
26
+
21
27
  def set_state(self, facility: Optional[str], instrument: Optional[str], experiment: Optional[str]) -> None:
22
28
  self.model.set_state(facility, instrument, experiment)
23
29
  self.update_view()
@@ -27,7 +33,8 @@ class DataSelectorViewModel:
27
33
  self.facilities_bind.update_in_view(self.model.get_facilities())
28
34
  self.instruments_bind.update_in_view(self.model.get_instruments())
29
35
  self.experiments_bind.update_in_view(self.model.get_experiments())
36
+ self.directories_bind.update_in_view(self.model.get_directories())
30
37
 
31
38
  datafile_paths = self.model.get_datafiles()
32
- datafile_options = [{"file": datafile} for datafile in datafile_paths]
39
+ datafile_options = [{"path": datafile, "title": os.path.basename(datafile)} for datafile in datafile_paths]
33
40
  self.datafiles_bind.update_in_view(datafile_options)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: nova-trame
3
- Version: 0.19.0.dev1
3
+ Version: 0.19.0.dev3
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,12 +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=xM4j_BrKXClRbeDjfcCQEAnWWYuUod7GxiF1WXDpfls,4620
3
+ nova/trame/model/data_selector.py,sha256=8BnnPvdYZTmCOJFuWOs_RhQ1NvYqWFdPmWP09WWP5oA,6689
4
4
  nova/trame/model/remote_file_input.py,sha256=9KAf31ZHzpsh_aXUrNcF81Q5jvUZDWCzW1QATKls-Jk,3675
5
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
6
+ nova/trame/view/components/data_selector.py,sha256=IkRu3BKwiEDrE9E2-GApUuajlb3YOsFmQbJsyNRnmfk,7929
7
+ nova/trame/view/components/file_upload.py,sha256=XbiSx2txpdohwxGyP-ecTbIgMPv6siUARJ7nXhXaiAc,2827
8
8
  nova/trame/view/components/input_field.py,sha256=ncVVSzdJwH_-KP24I2rCqcb6v3J2hPhNTXr8Lb1EZ_U,15931
9
- nova/trame/view/components/remote_file_input.py,sha256=0qi4PnmqUBIdUv36dUqYsTWRQ4fHUJ0nlg9oXzA3Qfs,8929
9
+ nova/trame/view/components/remote_file_input.py,sha256=lFchhhoMo9EgSr7pSlh2LEm8NZO1XXrBcfh_BLGZBV0,9492
10
10
  nova/trame/view/components/visualization/__init__.py,sha256=reqkkbhD5uSksHHlhVMy1qNUCwSekS5HlXk6wCREYxU,152
11
11
  nova/trame/view/components/visualization/interactive_2d_plot.py,sha256=foZCMoqbuahT5dtqIQvm8C4ZJcY9P211eJEcpQJltmM,3421
12
12
  nova/trame/view/components/visualization/matplotlib_figure.py,sha256=yop7Kd_MylUiCwEial2jOYESbvchrYhrpSmRowUhePY,12003
@@ -15,18 +15,18 @@ nova/trame/view/layouts/grid.py,sha256=k-QHuH31XeAVDuMKUMoAMVnAM-Yavq7kdLYOC1ZrG
15
15
  nova/trame/view/layouts/hbox.py,sha256=r5irhFX6YWTWN4V4NwNQx6mheyM8p6PVcJbrbhvOAwo,2625
16
16
  nova/trame/view/layouts/vbox.py,sha256=Q4EvrtGJORyNF6AnCLGXToy8XU6yofiO5_kt7hK-AYs,2626
17
17
  nova/trame/view/theme/__init__.py,sha256=70_marDlTigIcPEOGiJb2JTs-8b2sGM5SlY7XBPtBDM,54
18
- nova/trame/view/theme/assets/core_style.scss,sha256=zZj7klB_iieNNTdh3WADpRbCA6WII0-nG86MKFEBYWY,1533
18
+ nova/trame/view/theme/assets/core_style.scss,sha256=bP9tv5B3vivjEMXFPTGJ7rXc1aXjnOXsvnkaBUyRmUs,2095
19
19
  nova/trame/view/theme/assets/favicon.png,sha256=Xbp1nUmhcBDeObjsebEbEAraPDZ_M163M_ZLtm5AbQc,1927
20
20
  nova/trame/view/theme/assets/js/delay_manager.js,sha256=vmb34DZ5YCQIlRW9Tf2M_uvJW6HFCmtlKZ5e_TPR8yg,536
21
21
  nova/trame/view/theme/assets/js/lodash.debounce.min.js,sha256=GLzlQH04WDUNYN7i39ttHHejSdu-CpAvfWgDgKDn-OY,4448
22
22
  nova/trame/view/theme/assets/js/lodash.throttle.min.js,sha256=9csqjX-M-LVGJnF3z4ha1R_36O5AfkFE8rPHkxmt3tE,4677
23
- nova/trame/view/theme/assets/vuetify_config.json,sha256=_8NSthN18SXPlPr6bU--W8ktoYN6rUkhhcHv15egz8E,5462
23
+ nova/trame/view/theme/assets/vuetify_config.json,sha256=HuR23Ds1dATt5fFUFMolFoLRjINLzg7AiLgQpQvuabc,5567
24
24
  nova/trame/view/theme/theme.py,sha256=OFUtq1IWriFcDu-346J67ZrSES8IOI9PTY_4Vwg7bZQ,11820
25
25
  nova/trame/view/utilities/local_storage.py,sha256=vD8f2VZIpxhIKjZwEaD7siiPCTZO4cw9AfhwdawwYLY,3218
26
- nova/trame/view_model/data_selector.py,sha256=baijpcbKCUk0BITPpbhHR6nAG-SF5ho2EyEfARTvDAo,1441
26
+ nova/trame/view_model/data_selector.py,sha256=Bpkfjd78ZIkexHNF_aA9PfizRBzkeOuoSE7ZsBFcSOs,1749
27
27
  nova/trame/view_model/remote_file_input.py,sha256=ojEOJ8ZPkajpbAaZi9VLj7g-uBjhb8BMrTdMmwf_J6A,3367
28
- nova_trame-0.19.0.dev1.dist-info/LICENSE,sha256=Iu5QiDbwNbREg75iYaxIJ_V-zppuv4QFuBhAW-qiAlM,1061
29
- nova_trame-0.19.0.dev1.dist-info/METADATA,sha256=HgDywkkcsOCZfKqRpASo7tJ_uJYn7TA_ld48eXXWmao,1451
30
- nova_trame-0.19.0.dev1.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
31
- nova_trame-0.19.0.dev1.dist-info/entry_points.txt,sha256=J2AmeSwiTYZ4ZqHHp9HO6v4MaYQTTBPbNh6WtoqOT58,42
32
- nova_trame-0.19.0.dev1.dist-info/RECORD,,
28
+ nova_trame-0.19.0.dev3.dist-info/LICENSE,sha256=Iu5QiDbwNbREg75iYaxIJ_V-zppuv4QFuBhAW-qiAlM,1061
29
+ nova_trame-0.19.0.dev3.dist-info/METADATA,sha256=4VzPhPq4jNYEE_EXPMNQKYh71P-7rNOe__U4_g7YeD8,1451
30
+ nova_trame-0.19.0.dev3.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
31
+ nova_trame-0.19.0.dev3.dist-info/entry_points.txt,sha256=J2AmeSwiTYZ4ZqHHp9HO6v4MaYQTTBPbNh6WtoqOT58,42
32
+ nova_trame-0.19.0.dev3.dist-info/RECORD,,