nova-trame 1.0.0rc5__py3-none-any.whl → 1.0.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.

Potentially problematic release.


This version of nova-trame might be problematic. Click here for more details.

@@ -91,7 +91,7 @@ class NeutronDataSelector(DataSelector):
91
91
  if data_source == "oncat" and subdirectory:
92
92
  warn("subdirectory will be ignored since data will be pulled from ONCat.", stacklevel=1)
93
93
 
94
- if isinstance(facility, str) and allow_custom_directories:
94
+ if isinstance(facility, str) and facility and allow_custom_directories:
95
95
  warn("allow_custom_directories will be ignored since the facility parameter is fixed.", stacklevel=1)
96
96
 
97
97
  self._facility = facility
@@ -319,3 +319,9 @@ class NeutronDataSelector(DataSelector):
319
319
  experiment=set_state_param(self.state, (self._selected_experiment_name,), experiment),
320
320
  )
321
321
  self._vm.reset()
322
+
323
+ def set_state(self, *args: Any, **kwargs: Any) -> None:
324
+ raise TypeError(
325
+ "The set_state method has been removed. Please use update_facility, update_instrument, and "
326
+ "update_experiment instead."
327
+ )
@@ -13,7 +13,7 @@ from warnings import warn
13
13
 
14
14
  import tornado
15
15
  from aiohttp import ClientSession, WSMsgType, web
16
- from matplotlib import get_data_path
16
+ from matplotlib import get_data_path, rcParams
17
17
  from matplotlib.backends.backend_webagg import FigureManagerWebAgg, new_figure_manager_given_figure # type: ignore
18
18
  from matplotlib.figure import Figure
19
19
  from trame.app import get_server
@@ -56,7 +56,10 @@ class _MPLApplication(tornado.web.Application):
56
56
  self.supports_binary = message["value"]
57
57
  else:
58
58
  manager = self.application.manager # type: ignore
59
- manager.handle_json(message)
59
+ try:
60
+ manager.handle_json(message)
61
+ except Exception:
62
+ manager.refresh_all()
60
63
 
61
64
  def send_json(self, content: Any) -> None:
62
65
  set_event_loop(self.application.loop) # type: ignore
@@ -229,7 +232,6 @@ class MatplotlibFigure(matplotlib.Figure):
229
232
  else:
230
233
  kwargs["classes"] = "flex-1-1"
231
234
  if webagg:
232
- self._initial_resize = True
233
235
  if "id" in kwargs:
234
236
  kwargs.pop("id")
235
237
  warn("id parameter to MatplotlibFigure is ignored when webagg=True.", stacklevel=1)
@@ -264,7 +266,7 @@ class MatplotlibFigure(matplotlib.Figure):
264
266
  f"window.document.querySelectorAll('.nova-mpl').forEach((item) => {{ item.style.display = ''; }});"
265
267
  "window.trame.trigger("
266
268
  f" '{self._id}_resize',"
267
- f" [height, width, window.devicePixelRatio]"
269
+ f" [height, width]"
268
270
  ");"
269
271
  )
270
272
  self._resize_figure = client.JSEval(exec=self._trigger).exec
@@ -280,29 +282,21 @@ class MatplotlibFigure(matplotlib.Figure):
280
282
  ).exec
281
283
 
282
284
  @self._server.controller.trigger(f"{self._id}_resize")
283
- def resize_figure(height: int, width: int, device_pixel_ratio: float) -> None:
285
+ def resize_figure(height: int, width: int) -> None:
284
286
  if self._figure:
285
- # This is the browser standard assumption for DPI.
286
- dpi = 96
287
-
288
287
  if self._webagg:
289
288
  # Reserve space for the controls injected by webagg.
290
289
  height -= 48
291
290
  width -= 4
292
291
 
293
- if not self._initial_resize:
294
- # Handle device pixel ratio for retina displays
295
- dpi = int(dpi * device_pixel_ratio)
296
- height = int(height * device_pixel_ratio)
297
- width = int(width * device_pixel_ratio)
298
-
299
292
  if height <= 0 or width <= 0:
300
293
  return
301
294
 
302
295
  if self._webagg:
303
- self._initial_resize = False
304
-
305
- self._figure.set_dpi(dpi)
296
+ # Webagg does not respect the Figure object's DPI.
297
+ dpi = rcParams["figure.dpi"]
298
+ else:
299
+ dpi = self._figure.get_dpi()
306
300
  new_width = width / dpi
307
301
  new_height = height / dpi
308
302
  current_size = self._figure.get_size_inches()
@@ -12,6 +12,13 @@ html {
12
12
  box-shadow: none !important;
13
13
  }
14
14
 
15
+ .exit-button {
16
+ .v-btn__content,
17
+ .v-btn__prepend i {
18
+ font-size: 0.875rem !important;
19
+ }
20
+ }
21
+
15
22
  .mpl-message, .ui-dialog-titlebar {
16
23
  display: none !important;
17
24
  }
@@ -20,6 +27,10 @@ html {
20
27
  white-space: pre-wrap;
21
28
  }
22
29
 
30
+ .nova-pre-content:empty {
31
+ margin-bottom: 12px !important;
32
+ }
33
+
23
34
  .nova-data-selector {
24
35
  .v-list-group {
25
36
  --prepend-width: 12px !important;
@@ -86,6 +97,8 @@ html {
86
97
 
87
98
  @media only screen and (max-width: 959px) {
88
99
  .d-grid {
100
+ /* This forces all grid rows to have the same height. If one doesn't want this to happen, then they should use the box layouts. :) */
101
+ grid-auto-rows: 1fr;
89
102
  grid-template-columns: repeat(1, 1fr) !important;
90
103
  }
91
104
 
@@ -183,7 +196,7 @@ html {
183
196
  font-size: 1rem;
184
197
  }
185
198
 
186
- .v-btn {
199
+ .v-btn:not(.exit-button) {
187
200
  min-width: 0px !important;
188
201
  padding: 5px 5px !important;
189
202
  box-shadow: none !important;
@@ -4,11 +4,15 @@ class RevoGrid {
4
4
  this.modelKey = modelKey
5
5
  this.dataKey = dataKey
6
6
  this.stateKey = stateKey
7
+ this.lastSelection = null
8
+ this.shiftPressed = false
7
9
 
8
10
  this.grid = document.querySelector(`#${this.id}`)
9
11
  this.grid.addEventListener('viewportscroll', () => {
10
12
  this.updateCheckboxes()
11
13
  })
14
+
15
+ this.initShiftKeyListeners()
12
16
  }
13
17
 
14
18
  updateCheckboxes() {
@@ -66,11 +70,32 @@ class RevoGrid {
66
70
  const path = props.data[props.rowIndex].path
67
71
  const index = modelValue.indexOf(path)
68
72
 
69
- // We need to assign instead of modifying in place in order for the Trame watcher to pick up changes.
73
+ // I use _.set instead of modifying the modelValue in place in order for the Trame watcher to properly detect the change.
70
74
  if (e.target.checked && index < 0) {
71
- _.set(trameState, this.modelKey, _.concat(modelValue, path))
75
+ const newIndex = props.data.findIndex((entry) => entry.path === path)
76
+
77
+ if (this.shiftPressed && this.lastSelection !== null) {
78
+ let newPaths = []
79
+ // JavaScript doesn't allow a backwards step during slice, so we need to order the start/stop correctly.
80
+ if (this.lastSelection < newIndex) {
81
+ newPaths = props.data.slice(this.lastSelection, newIndex + 1)
82
+ } else {
83
+ newPaths = props.data.slice(newIndex, this.lastSelection)
84
+ }
85
+ // Exclude paths that are already selected to avoid duplicates.
86
+ newPaths = newPaths.map((entry) => entry.path).filter((path) => !modelValue.includes(path))
87
+
88
+ _.set(trameState, this.modelKey, _.concat(modelValue, newPaths))
89
+ } else {
90
+ _.set(trameState, this.modelKey, _.concat(modelValue, path))
91
+ }
92
+
93
+ this.lastSelection = newIndex
72
94
  } else if (index >= 0) {
73
95
  _.set(trameState, this.modelKey, modelValue.toSpliced(index, 1))
96
+
97
+ // Only allow range selection if the last action was to select a file.
98
+ this.lastSelection = null
74
99
  }
75
100
 
76
101
  // Update the UI
@@ -103,6 +128,18 @@ class RevoGrid {
103
128
 
104
129
  return [inputVNode, 'Available Datafiles']
105
130
  }
131
+
132
+ initShiftKeyListeners() {
133
+ window.document.addEventListener('keydown', (e) => {
134
+ this.shiftPressed = e.shiftKey
135
+ })
136
+
137
+ window.document.addEventListener('keyup', (e) => {
138
+ if (e.key === 'Shift') {
139
+ this.shiftPressed = false
140
+ }
141
+ })
142
+ }
106
143
  }
107
144
 
108
145
  class RevoGridManager {
@@ -1,9 +1,11 @@
1
1
  """Components used to control the lifecycle of a Themed Application."""
2
2
 
3
3
  import logging
4
+ from asyncio import sleep
4
5
  from typing import Any
5
6
 
6
7
  from trame.app import get_server
8
+ from trame.widgets import client
7
9
  from trame.widgets import vuetify3 as vuetify
8
10
 
9
11
  logger = logging.getLogger(__name__)
@@ -20,6 +22,10 @@ class ExitButton:
20
22
  self.server.state.nova_show_stop_jobs_on_exit_checkbox = False
21
23
  self.server.state.nova_running_jobs = []
22
24
  self.server.state.nova_show_exit_progress = False
25
+ # Note that window.close() will fail in many situations due to security concerns: https://developer.mozilla.org/en-US/docs/Web/API/Window/close
26
+ # This is simply a best effort to close the tab. My hope is that it will generally work when people are running
27
+ # applications through our dashboard since tabs are opened via target="_blank" links.
28
+ self.close_browser = client.JSEval(exec="window.close();").exec
23
29
  self.exit_application_callback = exit_callback
24
30
  self.job_status_callback = job_status_callback
25
31
  self.create_ui()
@@ -28,9 +34,10 @@ class ExitButton:
28
34
  with vuetify.VBtn(
29
35
  "Exit",
30
36
  prepend_icon="mdi-close-box",
31
- classes="mr-4 bg-error",
37
+ classes="exit-button mr-4 bg-secondary",
32
38
  id="shutdown_app_theme_button",
33
39
  color="white",
40
+ size="default",
34
41
  click=self.open_exit_dialog,
35
42
  ):
36
43
  with vuetify.VDialog(v_model="nova_show_exit_dialog", persistent="true"):
@@ -51,7 +58,7 @@ class ExitButton:
51
58
  with vuetify.VCardActions(v_if="!nova_show_exit_progress"):
52
59
  vuetify.VBtn(
53
60
  "Exit App",
54
- click=self.exit_application_callback,
61
+ click=self.exit_application,
55
62
  color="error",
56
63
  )
57
64
  vuetify.VBtn(
@@ -65,6 +72,13 @@ class ExitButton:
65
72
  )
66
73
  vuetify.VProgressCircular(indeterminate=True)
67
74
 
75
+ async def exit_application(self) -> None:
76
+ self.close_browser()
77
+
78
+ # sleep gives time for the Trame server to communicate the close request to the browser.
79
+ await sleep(0.1)
80
+ await self.exit_application_callback()
81
+
68
82
  async def open_exit_dialog(self) -> None:
69
83
  self.server.state.nova_show_exit_dialog = True
70
84
  await self.job_status_callback()
@@ -3,6 +3,7 @@
3
3
  import asyncio
4
4
  import json
5
5
  import logging
6
+ import os
6
7
  import sys
7
8
  from asyncio import create_task
8
9
  from functools import partial
@@ -210,6 +211,10 @@ class ThemedApp:
210
211
  -------
211
212
  `trame_client.ui.core.AbstractLayout <https://trame.readthedocs.io/en/latest/core.ui.html#trame_client.ui.core.AbstractLayout>`_
212
213
  """
214
+ # This detects if Poetry is running Python so that we can show links to NOVA resources during development.
215
+ # Poetry should not be used in production.
216
+ show_nova_resources = os.environ.get("VIRTUAL_ENV") is not None
217
+
213
218
  with VAppLayout(self.server, vuetify_config=self.vuetify_config) as layout:
214
219
  self.local_storage = LocalStorageManager(self.server.controller)
215
220
 
@@ -225,8 +230,32 @@ class ThemedApp:
225
230
  with vuetify.VAppBar() as toolbar:
226
231
  layout.toolbar = toolbar
227
232
 
228
- with vuetify.VAppBarTitle() as toolbar_title:
233
+ with vuetify.VAppBarTitle(classes="flex-0-1") as toolbar_title:
229
234
  layout.toolbar_title = toolbar_title
235
+
236
+ if show_nova_resources:
237
+ vuetify.VBtn(
238
+ "NOVA Examples",
239
+ classes="ml-4",
240
+ href="https://github.com/nova-model/nova-examples/",
241
+ __properties=["target"],
242
+ target="_blank",
243
+ )
244
+ html.Div("·", classes="mx-1")
245
+ vuetify.VBtn(
246
+ "NOVA Tutorial",
247
+ href="https://nova.ornl.gov/tutorial/",
248
+ __properties=["target"],
249
+ target="_blank",
250
+ )
251
+ html.Div("·", classes="mx-1")
252
+ vuetify.VBtn(
253
+ "NOVA Documentation",
254
+ href="https://nova-application-development.readthedocs.io/en/latest/",
255
+ __properties=["target"],
256
+ target="_blank",
257
+ )
258
+
230
259
  vuetify.VSpacer()
231
260
  with html.Div(classes="mr-2") as actions:
232
261
  layout.actions = actions
@@ -259,7 +288,7 @@ class ThemedApp:
259
288
 
260
289
  with vuetify.VMain(classes="align-stretch d-flex flex-column h-screen"):
261
290
  # [slot override example]
262
- layout.pre_content = vuetify.VSheet(classes="bg-background flex-0-1 mt-1 ")
291
+ layout.pre_content = vuetify.VSheet(classes="bg-background flex-0-1 mt-1 nova-pre-content ")
263
292
  # [slot override example complete]
264
293
  with vuetify.VContainer(classes="flex-1-1 overflow-hidden pt-0 pb-2", fluid=True):
265
294
  layout.content = vuetify.VCard(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nova-trame
3
- Version: 1.0.0rc5
3
+ Version: 1.0.1
4
4
  Summary: A Python Package for injecting curated themes and custom components into Trame applications
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -38,7 +38,7 @@ Description-Content-Type: text/markdown
38
38
  nova-trame
39
39
  ==========
40
40
 
41
- `nova-trame` is a Python package for streamlining development of Trame applications used in the NOVA project.
41
+ `nova-trame` is a Python package for streamlining development of Trame applications used in the NOVA framework.
42
42
 
43
43
  You can install this package directly with
44
44
 
@@ -46,6 +46,13 @@ You can install this package directly with
46
46
  pip install nova-trame
47
47
  ```
48
48
 
49
+ Once installed, you can check that it's working by running the widget gallery we use for visually testing components available through NOVA and Trame. We use [Poetry](https://python-poetry.org/) internally and recommend it for running the gallery.
50
+
51
+ ```commandline
52
+ poetry install
53
+ poetry run app
54
+ ```
55
+
49
56
  A user guide, examples, and a full API for this package can be found at [https://nova-application-development.readthedocs.io/en/stable/](https://nova-application-development.readthedocs.io/projects/nova-trame/en/stable/).
50
57
 
51
58
  Developers: please read [this document](DEVELOPMENT.md)
@@ -12,27 +12,27 @@ nova/trame/view/components/execution_buttons.py,sha256=Br6uAmE5bY67TTYc5ZTHECNJ_
12
12
  nova/trame/view/components/file_upload.py,sha256=WOaFXeNNwN0DYZJr-W6vWdBiTpr7m-lq3WKJaHmeMe8,4560
13
13
  nova/trame/view/components/input_field.py,sha256=xzCmNEoB4ljGx99-gGgTV0UwriwtS8ce22zPA4QneZw,17372
14
14
  nova/trame/view/components/ornl/__init__.py,sha256=HnxzzSsxw0vQSDCVFfWsAxx1n3HnU37LMuQkfiewmSU,90
15
- nova/trame/view/components/ornl/neutron_data_selector.py,sha256=duiPbROIYsU6h0oZP27wSce6BJuW1mdT8mGoxY05lyw,16542
15
+ nova/trame/view/components/ornl/neutron_data_selector.py,sha256=3IxEYM3wK7RskxbjDG94JeWBCEWXBeF3xBDWt3Bh1OU,16797
16
16
  nova/trame/view/components/progress_bar.py,sha256=zhbJwPy_HPQ8YL-ISN8sCRUQ7qY6qqo9wiV59BmvL8I,3038
17
17
  nova/trame/view/components/remote_file_input.py,sha256=mcz_bmI2rD8gdmIOKLhlzfj-XoWBwC99T9ZgQORaKqE,14674
18
18
  nova/trame/view/components/tool_outputs.py,sha256=IbYV4VjrkWAE354Bh5KH76SPsxGLIkOXChijS4-ce_Y,2408
19
19
  nova/trame/view/components/visualization/__init__.py,sha256=reqkkbhD5uSksHHlhVMy1qNUCwSekS5HlXk6wCREYxU,152
20
20
  nova/trame/view/components/visualization/interactive_2d_plot.py,sha256=z2s1janxAclpMEdDJk3z-CQ6r3KPNoR_SXPx9ppWnuQ,3481
21
- nova/trame/view/components/visualization/matplotlib_figure.py,sha256=GYfX7hlEWQ4Dghk8Z1dmUQr6jgktWirhOYb0drtonM4,16297
21
+ nova/trame/view/components/visualization/matplotlib_figure.py,sha256=8JgbF6elZC8EFZn-c2mLqGSyg0Lb4NLVAKJVSBb9-5g,16010
22
22
  nova/trame/view/layouts/__init__.py,sha256=cMrlB5YMUoK8EGB83b34UU0kPTVrH8AxsYvKRtpUNEc,141
23
23
  nova/trame/view/layouts/grid.py,sha256=DU5u5JTE0ulzCaJsEWyTenBH9lOQD7mtoC6RZXxDTPE,6110
24
24
  nova/trame/view/layouts/hbox.py,sha256=w6ow7Qzmq4slODz_9f7kEigCVPE2PhUmPODedYH34f4,3850
25
25
  nova/trame/view/layouts/utils.py,sha256=Hg34VQWTG3yHBsgNvmfatR4J-uL3cko7UxSJpT-h3JI,376
26
26
  nova/trame/view/layouts/vbox.py,sha256=Kkci79zDKF6qNH4HeDPYquJcixXx3BS63NVmS3FlOiw,3851
27
27
  nova/trame/view/theme/__init__.py,sha256=70_marDlTigIcPEOGiJb2JTs-8b2sGM5SlY7XBPtBDM,54
28
- nova/trame/view/theme/assets/core_style.scss,sha256=lZK8zghy4ExmNuFI-rfq7qt2S7bqObzM_MBviOlWP5c,4481
28
+ nova/trame/view/theme/assets/core_style.scss,sha256=4elBD5n0Mrgl_-U7NycisPaIDGLiU8MR-7nSml2i6Q0,4844
29
29
  nova/trame/view/theme/assets/favicon.png,sha256=Xbp1nUmhcBDeObjsebEbEAraPDZ_M163M_ZLtm5AbQc,1927
30
30
  nova/trame/view/theme/assets/js/delay_manager.js,sha256=BN4OL88QsyZG4XQ1sTorHpN1rwD4GnWoVKHvl5F5ydo,776
31
31
  nova/trame/view/theme/assets/js/lodash.min.js,sha256=KCyAYJ-fsqtp_HMwbjhy6IKjlA5lrVrtWt1JdMsC57k,73016
32
- nova/trame/view/theme/assets/js/revo_grid.js,sha256=fbuEWO8etw-xgo9tjJGjJXdd5wL8qpgabPmrnU6Jp8k,4081
32
+ nova/trame/view/theme/assets/js/revo_grid.js,sha256=73v-GP4IUIg8OgMnRCoHB6ERFU18nx0VYaEecV_7u-o,5695
33
33
  nova/trame/view/theme/assets/vuetify_config.json,sha256=a0FSgpLYWGFlRGSMhMq61MyDFBEBwvz55G4qjkM08cs,5627
34
- nova/trame/view/theme/exit_button.py,sha256=Kqv1GVJZGrSsj6_JFjGU3vm3iNuMolLC2T1x2IsdmV0,3094
35
- nova/trame/view/theme/theme.py,sha256=Jeoi-qrokSO-dDYpEvicbqfwzalhhbG-4HplD8NFj6s,13269
34
+ nova/trame/view/theme/exit_button.py,sha256=GrgCMLytIrDTWAMtkuFv5JaIBpslFrmI5k2izklSqQs,3872
35
+ nova/trame/view/theme/theme.py,sha256=YecxrUw5I-7NDTyC8jrVdpb49U1TMzIbwO7jDid_jN0,14715
36
36
  nova/trame/view/utilities/local_storage.py,sha256=vD8f2VZIpxhIKjZwEaD7siiPCTZO4cw9AfhwdawwYLY,3218
37
37
  nova/trame/view_model/data_selector.py,sha256=jAtq5hpohQ6YiLBbgLJfNUzWZBpN2bjCG_c_FCJu2ns,3186
38
38
  nova/trame/view_model/execution_buttons.py,sha256=MfKSp95D92EqpD48C15cBo6dLO0Yld4FeRZMJNxJf7Y,3551
@@ -40,8 +40,8 @@ nova/trame/view_model/ornl/neutron_data_selector.py,sha256=PIKQyzcHpwu81DNk3d8Af
40
40
  nova/trame/view_model/progress_bar.py,sha256=6AUKHF3hfzbdsHqNEnmHRgDcBKY5TT8ywDx9S6ovnsc,2854
41
41
  nova/trame/view_model/remote_file_input.py,sha256=zWOflmCDJYYR_pacHphwzricV667GSRokh-mlxpBAOo,3646
42
42
  nova/trame/view_model/tool_outputs.py,sha256=ev6LY7fJ0H2xAJn9f5ww28c8Kpom2SYc2FbvFcoN4zg,829
43
- nova_trame-1.0.0rc5.dist-info/METADATA,sha256=E07ThBY0ho0JJE66zWp3IQJadp1e-nydsrAnUz93BTo,1798
44
- nova_trame-1.0.0rc5.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
45
- nova_trame-1.0.0rc5.dist-info/entry_points.txt,sha256=J2AmeSwiTYZ4ZqHHp9HO6v4MaYQTTBPbNh6WtoqOT58,42
46
- nova_trame-1.0.0rc5.dist-info/licenses/LICENSE,sha256=Iu5QiDbwNbREg75iYaxIJ_V-zppuv4QFuBhAW-qiAlM,1061
47
- nova_trame-1.0.0rc5.dist-info/RECORD,,
43
+ nova_trame-1.0.1.dist-info/METADATA,sha256=nuokD6J6QRyTeX4MUlD2JoYA01sJ5uMz6j5OXDBsXig,2096
44
+ nova_trame-1.0.1.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
45
+ nova_trame-1.0.1.dist-info/entry_points.txt,sha256=J2AmeSwiTYZ4ZqHHp9HO6v4MaYQTTBPbNh6WtoqOT58,42
46
+ nova_trame-1.0.1.dist-info/licenses/LICENSE,sha256=Iu5QiDbwNbREg75iYaxIJ_V-zppuv4QFuBhAW-qiAlM,1061
47
+ nova_trame-1.0.1.dist-info/RECORD,,