nova-trame 0.15.0__py3-none-any.whl → 0.17.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.
@@ -70,7 +70,7 @@ class InputField:
70
70
  "rules": (f"[(v) => trigger('validate_pydantic_field', ['{field}', v, index])]",),
71
71
  }
72
72
 
73
- if field_type == "select" and issubclass(field_info.annotation, Enum):
73
+ if field_type in ["autocomplete", "combobox", "select"] and issubclass(field_info.annotation, Enum):
74
74
  args |= {"items": str([option.value for option in field_info.annotation])}
75
75
 
76
76
  if debounce > 0 and throttle > 0:
@@ -123,25 +123,39 @@ class InputField:
123
123
  Number of milliseconds to wait after the last user interaction with this field before attempting to update
124
124
  the Trame state. If set to 0, then no debouncing will occur. If set to -1, then the environment variable
125
125
  `NOVA_TRAME_DEFAULT_DEBOUNCE` will be used to set this (defaults to 0). See the `Lodash Docs
126
- <https://lodash.com/docs/4.17.15#debounce>`_ for details.
126
+ <https://lodash.com/docs/4.17.15#debounce>`__ for details.
127
127
  throttle : int
128
128
  Number of milliseconds to wait between updates to the Trame state when the user is interacting with this
129
129
  field. If set to 0, then no throttling will occur. If set to -1, then the environment variable
130
130
  `NOVA_TRAME_DEFAULT_THROTTLE` will be used to set this (defaults to 0). See the `Lodash Docs
131
- <https://lodash.com/docs/4.17.15#throttle>`_ for details.
131
+ <https://lodash.com/docs/4.17.15#throttle>`__ for details.
132
132
  type : str
133
133
  The type of input to create. This can be any of the following:
134
134
 
135
- - autocomplete
136
- - autoscroll (produces a textarea that automatically scrolls to the bottom as new content is added)
135
+ - autocomplete - Produces a dropdown menu that supports autocompletion as the user types. Items can be \
136
+ automatically populated (see select option for details).
137
+ - autoscroll - Produces a textarea that automatically scrolls to the bottom as new content is added.
137
138
  - checkbox
138
- - combobox
139
+ - combobox - Produces a dropdown menu that supports autocompletion as the user types and allows users to \
140
+ add new items. Items can be automatically populated (see select option for details).
139
141
  - file
140
142
  - input
141
143
  - otp
142
144
  - radio
143
145
  - range-slider
144
- - select
146
+ - select - Produces a dropdown menu. This menu can have items automatically populated if the v_model is \
147
+ connected to a Pydantic field that uses an Enum type. Otherwise, you must specify the items parameter \
148
+ to `InputField`.
149
+
150
+ .. literalinclude:: ../tests/test_input_field.py
151
+ :start-after: items autopopulation start
152
+ :end-before: items autopopulation end
153
+ :dedent:
154
+
155
+ .. code-block:: python
156
+
157
+ InputField(v_model="dropdown.enum_field", type="select")
158
+
145
159
  - slider
146
160
  - switch
147
161
  - textarea
@@ -203,7 +217,6 @@ class InputField:
203
217
  items = kwargs.pop("items", None)
204
218
  if isinstance(items, str):
205
219
  items = (items,)
206
-
207
220
  input = vuetify.VSelect(items=items, **kwargs)
208
221
  case "slider":
209
222
  input = vuetify.VSlider(**kwargs)
@@ -1,3 +1,4 @@
1
1
  from .interactive_2d_plot import Interactive2DPlot
2
+ from .matplotlib_figure import MatplotlibFigure
2
3
 
3
- __all__ = ["Interactive2DPlot"]
4
+ __all__ = ["Interactive2DPlot", "MatplotlibFigure"]
@@ -0,0 +1,307 @@
1
+ """View implementation for MatplotlibFigure."""
2
+
3
+ import json
4
+ import os
5
+ import socketserver
6
+ from asyncio import FIRST_COMPLETED, new_event_loop, set_event_loop, wait
7
+ from io import BytesIO
8
+ from mimetypes import types_map
9
+ from pathlib import Path
10
+ from threading import Thread
11
+ from typing import Any, Optional
12
+
13
+ import tornado
14
+ from aiohttp import ClientSession, WSMsgType, web
15
+ from matplotlib import get_data_path
16
+ from matplotlib.backends.backend_webagg import FigureManagerWebAgg, new_figure_manager_given_figure # type: ignore
17
+ from matplotlib.figure import Figure
18
+ from trame.app import get_server
19
+ from trame.widgets import client, html, matplotlib
20
+ from wslink.backends.aiohttp import WebAppServer
21
+
22
+
23
+ class _MPLApplication(tornado.web.Application):
24
+ """Tornado application compatible with Matplotlib's WebAgg backend."""
25
+
26
+ class WebSocket(tornado.websocket.WebSocketHandler):
27
+ """Implements the WebSocket manager for WebAgg."""
28
+
29
+ supports_binary = True
30
+
31
+ def check_origin(self, origin: Any) -> bool:
32
+ return True
33
+
34
+ def open(self, *args: Any, **kwargs: Any) -> None:
35
+ # Register the websocket with the FigureManager.
36
+ manager = self.application.manager # type: ignore
37
+ manager.add_web_socket(self)
38
+ if hasattr(self, "set_nodelay"):
39
+ self.set_nodelay(True)
40
+
41
+ def on_close(self) -> None:
42
+ # When the socket is closed, deregister the websocket with
43
+ # the FigureManager.
44
+ manager = self.application.manager # type: ignore
45
+ manager.remove_web_socket(self)
46
+
47
+ def on_message(self, message: Any) -> None:
48
+ # The 'supports_binary' message is relevant to the
49
+ # websocket itself. The other messages get passed along
50
+ # to matplotlib as-is.
51
+
52
+ # Every message has a "type" and a "figure_id".
53
+ message = json.loads(message)
54
+ if message["type"] == "supports_binary":
55
+ self.supports_binary = message["value"]
56
+ else:
57
+ manager = self.application.manager # type: ignore
58
+ manager.handle_json(message)
59
+
60
+ def send_json(self, content: Any) -> None:
61
+ set_event_loop(self.application.loop) # type: ignore
62
+ self.write_message(json.dumps(content))
63
+
64
+ def send_binary(self, blob: Any) -> None:
65
+ set_event_loop(self.application.loop) # type: ignore
66
+ if self.supports_binary:
67
+ self.write_message(blob, binary=True)
68
+ else:
69
+ data_uri = "data:image/png;base64," + blob.encode("base64").replace("\n", "")
70
+ self.write_message(data_uri)
71
+
72
+ def __init__(self, figure: Figure) -> None:
73
+ self.figure = figure
74
+ self.manager = new_figure_manager_given_figure(id(figure), figure)
75
+ self.loop = new_event_loop()
76
+
77
+ super().__init__([("/ws", self.WebSocket)])
78
+
79
+
80
+ class MatplotlibFigure(matplotlib.Figure):
81
+ """Creates an interactive Matplotlib Figure using the WebAgg backend.
82
+
83
+ By default, this will leverage the built-in Trame widget for Matplotlib support. This built-in widget can display
84
+ poor performance for detailed plots due to it being locked into using SVG rendering.
85
+
86
+ If you experience this, then you can use the `webagg` parameter to enable the WebAgg backend for Matplotlib. This
87
+ will switch to server-side rendering leveraging the Anti-Grain Geometry engine.
88
+
89
+ .. code-block:: python
90
+
91
+ my_figure = matplotlib.figure.Figure()
92
+ MatplotlibFigure(my_figure) # Display SVG-based plot in Trame
93
+ MatplotlibFigure(my_figure, webagg=True) # Display WebAgg-based plot in Trame
94
+ """
95
+
96
+ mpl_initialized = False
97
+ mpl_instances: dict[int, "MatplotlibFigure"] = {}
98
+
99
+ @classmethod
100
+ def _get_free_port(cls) -> int:
101
+ with socketserver.TCPServer(("localhost", 0), None) as s: # type: ignore
102
+ return s.server_address[1]
103
+
104
+ @classmethod
105
+ def _setup_mpl(cls) -> None:
106
+ server = get_server(None, client_type="vue3")
107
+
108
+ @server.controller.add("on_server_bind")
109
+ def _add_routes(wslink_server: WebAppServer) -> None:
110
+ # matplotlib WebAgg serves JS that will reference the base URL of the application, so we need to add
111
+ # endpoints to the main server to handle these requests.
112
+ wslink_server.app.add_routes(
113
+ [
114
+ web.get("/_images/{image}", cls._mpl_image_endpoint),
115
+ web.get("/download/{port}/{format}", cls._mpl_download_endpoint),
116
+ web.get("/mpl/{port}", cls._mpl_ws_proxy_endpoint),
117
+ ]
118
+ )
119
+
120
+ # The CSS and JS files, on the other hand, can be preloaded into the page which is simpler.
121
+ css_path = Path(FigureManagerWebAgg.get_static_file_path(), "css")
122
+ for fname in os.listdir(css_path):
123
+ with open(Path(css_path, fname)) as css_file:
124
+ content = css_file.read()
125
+ client.Style(content)
126
+ client.Script(FigureManagerWebAgg.get_javascript())
127
+
128
+ MatplotlibFigure.mpl_initialized = True
129
+
130
+ @classmethod
131
+ async def _mpl_download_endpoint(cls, request: web.Request) -> web.Response:
132
+ # We use the websocket port to differentiate between plots if there are multiple WebAgg figures.
133
+ port = request.match_info.get("port", "")
134
+ format = request.match_info.get("format", "png")
135
+
136
+ if not port or int(port) not in MatplotlibFigure.mpl_instances:
137
+ raise web.HTTPNotFound
138
+
139
+ instance = MatplotlibFigure.mpl_instances[int(port)]
140
+
141
+ buff = BytesIO()
142
+ if instance._figure:
143
+ instance._figure.savefig(buff, format=format)
144
+ buff.seek(0)
145
+
146
+ return web.Response(body=buff.read(), headers={"Content-Type": types_map.get(format, "binary")})
147
+
148
+ @classmethod
149
+ async def _mpl_image_endpoint(cls, request: web.Request) -> web.Response:
150
+ image_name = request.match_info.get("image", None)
151
+
152
+ if image_name:
153
+ try:
154
+ with open(Path(get_data_path(), "images", image_name), mode="rb") as image_file:
155
+ image_data = image_file.read()
156
+ return web.Response(body=image_data)
157
+ except OSError as err:
158
+ raise web.HTTPNotFound from err
159
+
160
+ raise web.HTTPNotFound
161
+
162
+ @classmethod
163
+ async def _mpl_ws_proxy_endpoint(cls, request: web.Request) -> web.WebSocketResponse:
164
+ # The WebAgg backend assumes a tornado-based WebSocket handler, so we need to proxy it to work with Trame's
165
+ # aiohttp setup.
166
+ port = request.match_info.get("port", "")
167
+
168
+ # Initialize the proxy
169
+ ws_server = web.WebSocketResponse()
170
+ await ws_server.prepare(request)
171
+
172
+ # Connect to the tornado WebSocket handler
173
+ client_session = ClientSession(cookies=request.cookies)
174
+ async with client_session.ws_connect(
175
+ f"http://localhost:{port}/ws",
176
+ ) as ws_client:
177
+
178
+ async def ws_forward(ws_from: Any, ws_to: Any) -> None:
179
+ # The browser will send text messages for rendering requests and the server will send bytes to transmit
180
+ # rendered image data.
181
+ async for msg in ws_from:
182
+ if msg.type == WSMsgType.TEXT:
183
+ await ws_to.send_str(msg.data)
184
+ elif msg.type == WSMsgType.BINARY:
185
+ await ws_to.send_bytes(msg.data)
186
+ else:
187
+ raise ValueError("unexpected message type: %s", print(msg))
188
+
189
+ # Forward websocket data in both directions
190
+ await wait(
191
+ [ws_forward(ws_server, ws_client), ws_forward(ws_client, ws_server)], return_when=FIRST_COMPLETED
192
+ )
193
+ await client_session.close() # Ensure the connection is cleaned up when the Trame client disconnects.
194
+
195
+ return ws_server
196
+
197
+ def __init__(self, figure: Optional[Figure] = None, webagg: bool = False, **kwargs: Any) -> None:
198
+ """Creates a Matplotlib figure in the Trame UI.
199
+
200
+ Parameters
201
+ ----------
202
+ figure : `altair.Chart <https://altair-viz.github.io/user_guide/generated/toplevel/altair.Chart.html#altair.Chart>`_
203
+ Altair chart object
204
+ webagg : bool
205
+ If true, then the WebAgg backend for Matplotlib is used. If not, then the default Trame matplotlib plugin
206
+ is used.
207
+ kwargs
208
+ Arguments to be passed to `AbstractElement <https://trame.readthedocs.io/en/latest/core.widget.html#trame_client.widgets.core.AbstractElement>`_
209
+
210
+ Returns
211
+ -------
212
+ None
213
+ """
214
+ self._webagg = webagg
215
+ if webagg:
216
+ self._port = MatplotlibFigure._get_free_port()
217
+ if "classes" in kwargs:
218
+ kwargs["classes"] += " nova-mpl"
219
+ else:
220
+ kwargs["classes"] = "nova-mpl"
221
+
222
+ html.Div(id=f"nova_mpl_{self._port}", **kwargs)
223
+
224
+ self._server = get_server(None, client_type="vue3")
225
+
226
+ self._figure = figure
227
+ self._initialized = False
228
+
229
+ if not MatplotlibFigure.mpl_initialized:
230
+ MatplotlibFigure._setup_mpl()
231
+ MatplotlibFigure.mpl_instances[self._port] = self
232
+
233
+ self.update()
234
+ else:
235
+ super().__init__(figure, **kwargs)
236
+
237
+ def update(self, figure: Optional[Figure] = None) -> None:
238
+ if self._webagg:
239
+ if figure:
240
+ self._figure = figure
241
+
242
+ if self._figure is not None and not self._initialized:
243
+ self._setup_figure_websocket()
244
+ self._setup_figure_javascript()
245
+
246
+ self._initialized = True
247
+
248
+ # Re-render the figure in the UI
249
+ if self._figure is not None:
250
+ self._figure.canvas.draw_idle()
251
+ self._figure.canvas.flush_events()
252
+ else:
253
+ super().update(figure)
254
+
255
+ def _setup_figure_websocket(self) -> None:
256
+ thread = Thread(target=self._mpl_run_ws_server, daemon=True)
257
+ thread.start()
258
+
259
+ def _setup_figure_javascript(self) -> None:
260
+ figure_js = """
261
+ function ondownload_%(port)d(figure, format) {
262
+ window.open('download/%(port)d/' + format, '_blank');
263
+ };
264
+
265
+ function ready_%(port)d(fn) {
266
+ if (document.getElementById("nova_mpl_%(port)d") === null) {
267
+ setTimeout(() => {
268
+ ready_%(port)d(fn);
269
+ }, 100);
270
+ } else {
271
+ fn();
272
+ }
273
+ }
274
+
275
+ ready_%(port)d(
276
+ function() {
277
+ var websocket_type = mpl.get_websocket_type();
278
+ var websocket = new websocket_type(`ws://${window.location.host}/mpl/%(port)d`);
279
+
280
+ var fig = new mpl.figure(
281
+ // A unique numeric identifier for the figure
282
+ %(port)d,
283
+ // A websocket object (or something that behaves like one)
284
+ websocket,
285
+ // A function called when a file type is selected for download
286
+ ondownload_%(port)d,
287
+ // The HTML element in which to place the figure
288
+ document.getElementById("nova_mpl_%(port)d")
289
+ );
290
+ }
291
+ );
292
+ """
293
+
294
+ client.Script(figure_js % {"port": self._port}) # TODO
295
+
296
+ def _mpl_run_ws_server(self) -> None:
297
+ if not self._figure:
298
+ return
299
+
300
+ application = _MPLApplication(self._figure)
301
+
302
+ http_server = tornado.httpserver.HTTPServer(application)
303
+ sockets = tornado.netutil.bind_sockets(self._port, "")
304
+ http_server.add_sockets(sockets)
305
+
306
+ ioloop = tornado.ioloop.IOLoop.instance()
307
+ ioloop.start()
@@ -4,10 +4,28 @@ html {
4
4
 
5
5
  .d-grid {
6
6
  display: grid;
7
- gap: 0.25em;
7
+ gap: 0.8em;
8
8
  grid-auto-rows: auto;
9
9
  }
10
10
 
11
+ .v-toolbar {
12
+ box-shadow: none !important;
13
+ }
14
+
15
+ .v-tab.v-btn {
16
+ height: 30px !important;
17
+ min-width: fit-content !important;
18
+ padding: 10px !important;
19
+ }
20
+
21
+ .mpl-message, .ui-dialog-titlebar {
22
+ display: none !important;
23
+ }
24
+
25
+ .nova-mpl * {
26
+ resize: none !important;
27
+ }
28
+
11
29
  @media only screen and (max-width: 959px) {
12
30
  .d-grid {
13
31
  grid-template-columns: repeat(1, 1fr) !important;
@@ -20,7 +38,15 @@ html {
20
38
 
21
39
  /* Global font sizes can't be set through the Vuetify configuration. */
22
40
  .v-theme--CompactTheme {
23
- font-size: 0.85rem;
41
+ font-size: 0.75rem;
42
+
43
+ &.v-btn--variant-elevated.bg-primary {
44
+ box-shadow: none !important;
45
+ background-color: transparent !important;
46
+ color: green !important;
47
+ border: solid 1px lightgrey;
48
+ border-radius: 4px;
49
+ }
24
50
 
25
51
  .v-card-title,
26
52
  .v-list-item-title,
@@ -29,7 +55,34 @@ html {
29
55
  }
30
56
 
31
57
  .v-btn {
32
- min-width: 0px;
33
- padding: 0 8px;
58
+ min-width: 0px !important;
59
+ padding: 5px 5px !important;
60
+ box-shadow: none !important;
61
+ }
62
+
63
+ .v-btn__content {
64
+ text-transform: none;
65
+ }
66
+
67
+
68
+ .v-label {
69
+ font-size: 0.75rem;
70
+ }
71
+
72
+ .v-field__input {
73
+ min-height: 30px;
74
+ padding: 5px;
75
+ }
76
+
77
+ .v-field {
78
+ margin: 8px 4px 8px 4px;
79
+ }
80
+
81
+ .v-field--active .v-label {
82
+ color: green;
83
+ }
84
+
85
+ .d-grid {
86
+ align-items:center;
34
87
  }
35
88
  }
@@ -56,7 +56,8 @@
56
56
  },
57
57
  "VFileInput": {
58
58
  "color": "primary",
59
- "prependIcon": false
59
+ "prependIcon": false,
60
+ "variant": "outlined"
60
61
  },
61
62
  "VLabel": {
62
63
  "style": {
@@ -91,7 +92,8 @@
91
92
  "color": "primary"
92
93
  },
93
94
  "VSelect": {
94
- "color": "primary"
95
+ "color": "primary",
96
+ "variant": "outlined"
95
97
  },
96
98
  "VSlider": {
97
99
  "color": "primary"
@@ -106,10 +108,12 @@
106
108
  "color": "primary"
107
109
  },
108
110
  "VTextarea": {
109
- "color": "primary"
111
+ "color": "primary",
112
+ "variant": "outlined"
110
113
  },
111
114
  "VTextField": {
112
- "color": "primary"
115
+ "color": "primary",
116
+ "variant": "outlined"
113
117
  },
114
118
  "VWindowItem": {
115
119
  "reverseTransition": "fade-transition",
@@ -152,18 +156,16 @@
152
156
  "secondary": "#f48e5c"
153
157
  },
154
158
  "defaults": {
155
- "global": {
156
- "density": "compact"
157
- },
158
159
  "VBadge": {
159
160
  "dot": true
160
161
  },
161
162
  "VBtn": {
162
- "density": "default"
163
+ "size": "small"
163
164
  },
164
165
  "VTextField": {
165
166
  "VBtn": {
166
- "size": "small"
167
+ "size": "small",
168
+ "variant": "outlined"
167
169
  }
168
170
  }
169
171
  },
@@ -172,9 +174,13 @@
172
174
  }
173
175
  },
174
176
  "variations": {
175
- "colors": ["primary", "secondary", "accent"],
177
+ "colors": [
178
+ "primary",
179
+ "secondary",
180
+ "accent"
181
+ ],
176
182
  "darken": 5,
177
183
  "lighten": 5
178
184
  }
179
185
  }
180
- }
186
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: nova-trame
3
- Version: 0.15.0
3
+ Version: 0.17.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
@@ -13,12 +13,16 @@ Classifier: Programming Language :: Python :: 3.10
13
13
  Classifier: Programming Language :: Python :: 3.11
14
14
  Classifier: Programming Language :: Python :: 3.12
15
15
  Classifier: Programming Language :: Python :: 3.13
16
+ Requires-Dist: altair
16
17
  Requires-Dist: libsass
17
18
  Requires-Dist: mergedeep
18
19
  Requires-Dist: nova-mvvm
19
20
  Requires-Dist: pydantic
20
21
  Requires-Dist: tomli
22
+ Requires-Dist: tornado
21
23
  Requires-Dist: trame
24
+ Requires-Dist: trame-matplotlib
25
+ Requires-Dist: trame-plotly
22
26
  Requires-Dist: trame-vega
23
27
  Requires-Dist: trame-vuetify
24
28
  Description-Content-Type: text/markdown
@@ -2,26 +2,27 @@ nova/__init__.py,sha256=ED6jHcYiuYpr_0vjGz0zx2lrrmJT9sDJCzIljoDfmlM,65
2
2
  nova/trame/__init__.py,sha256=gFrAg1qva5PIqR5TjvPzAxLx103IKipJLqp3XXvrQL8,59
3
3
  nova/trame/model/remote_file_input.py,sha256=9KAf31ZHzpsh_aXUrNcF81Q5jvUZDWCzW1QATKls-Jk,3675
4
4
  nova/trame/view/components/__init__.py,sha256=fopr6mVqcpDcVYK9ue7SLUHyswgvRPcFESTq86mu1R8,128
5
- nova/trame/view/components/input_field.py,sha256=8q18MyegDl0ni2Wpb6TwvTJ4UYoKSNINq9TilE5EnbE,14956
5
+ nova/trame/view/components/input_field.py,sha256=ncVVSzdJwH_-KP24I2rCqcb6v3J2hPhNTXr8Lb1EZ_U,15931
6
6
  nova/trame/view/components/remote_file_input.py,sha256=k2yrwkell_g0sGnWR9XLL1LxkmFLr8-AGhduo8E-N4A,8669
7
- nova/trame/view/components/visualization/__init__.py,sha256=kDX1fkbtAgXSGlqhlMNhYYoYrq-hfS636smjgLsh6gg,84
7
+ nova/trame/view/components/visualization/__init__.py,sha256=reqkkbhD5uSksHHlhVMy1qNUCwSekS5HlXk6wCREYxU,152
8
8
  nova/trame/view/components/visualization/interactive_2d_plot.py,sha256=foZCMoqbuahT5dtqIQvm8C4ZJcY9P211eJEcpQJltmM,3421
9
+ nova/trame/view/components/visualization/matplotlib_figure.py,sha256=yop7Kd_MylUiCwEial2jOYESbvchrYhrpSmRowUhePY,12003
9
10
  nova/trame/view/layouts/__init__.py,sha256=cMrlB5YMUoK8EGB83b34UU0kPTVrH8AxsYvKRtpUNEc,141
10
11
  nova/trame/view/layouts/grid.py,sha256=k-QHuH31XeAVDuMKUMoAMVnAM-Yavq7kdLYOC1ZrGTQ,5021
11
12
  nova/trame/view/layouts/hbox.py,sha256=r5irhFX6YWTWN4V4NwNQx6mheyM8p6PVcJbrbhvOAwo,2625
12
13
  nova/trame/view/layouts/vbox.py,sha256=Q4EvrtGJORyNF6AnCLGXToy8XU6yofiO5_kt7hK-AYs,2626
13
14
  nova/trame/view/theme/__init__.py,sha256=70_marDlTigIcPEOGiJb2JTs-8b2sGM5SlY7XBPtBDM,54
14
- nova/trame/view/theme/assets/core_style.scss,sha256=vr5L55sKj-eWeom4NaO3sYKtPgtM5O-KlYk1k15uI3Q,586
15
+ nova/trame/view/theme/assets/core_style.scss,sha256=OtiO9E4FWFPkSL25LG6lwHyjKwywOsODRe9aOvL3Fj4,1513
15
16
  nova/trame/view/theme/assets/favicon.png,sha256=Xbp1nUmhcBDeObjsebEbEAraPDZ_M163M_ZLtm5AbQc,1927
16
17
  nova/trame/view/theme/assets/js/delay_manager.js,sha256=vmb34DZ5YCQIlRW9Tf2M_uvJW6HFCmtlKZ5e_TPR8yg,536
17
18
  nova/trame/view/theme/assets/js/lodash.debounce.min.js,sha256=GLzlQH04WDUNYN7i39ttHHejSdu-CpAvfWgDgKDn-OY,4448
18
19
  nova/trame/view/theme/assets/js/lodash.throttle.min.js,sha256=9csqjX-M-LVGJnF3z4ha1R_36O5AfkFE8rPHkxmt3tE,4677
19
- nova/trame/view/theme/assets/vuetify_config.json,sha256=7WGV6rO7hv2sapGsX9yy1d-dINshYFXRNX99D9I3dKQ,4780
20
+ nova/trame/view/theme/assets/vuetify_config.json,sha256=T2AtYBCXsYj5KDMUP0drO2wF9AVVGX4gHbt7LLovPzw,4927
20
21
  nova/trame/view/theme/theme.py,sha256=18tHChrB1_qosjIiiLxMwWYRDTdXfgxEP4-4yJ8E3Cw,11893
21
22
  nova/trame/view/utilities/local_storage.py,sha256=vD8f2VZIpxhIKjZwEaD7siiPCTZO4cw9AfhwdawwYLY,3218
22
23
  nova/trame/view_model/remote_file_input.py,sha256=WHWCQkZBGeKLe1aTPbtVNI8tn-PDt64mi1-561uuBpQ,3320
23
- nova_trame-0.15.0.dist-info/LICENSE,sha256=Iu5QiDbwNbREg75iYaxIJ_V-zppuv4QFuBhAW-qiAlM,1061
24
- nova_trame-0.15.0.dist-info/METADATA,sha256=Acdlyzs80ZpAlhUnCw5iFfw7N2zgGRu7B1eR1g9ZLlI,1240
25
- nova_trame-0.15.0.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
26
- nova_trame-0.15.0.dist-info/entry_points.txt,sha256=J2AmeSwiTYZ4ZqHHp9HO6v4MaYQTTBPbNh6WtoqOT58,42
27
- nova_trame-0.15.0.dist-info/RECORD,,
24
+ nova_trame-0.17.0.dist-info/LICENSE,sha256=Iu5QiDbwNbREg75iYaxIJ_V-zppuv4QFuBhAW-qiAlM,1061
25
+ nova_trame-0.17.0.dist-info/METADATA,sha256=09C979OR2HAKktULaLY5efoYa_EpCIjhLlLlWOHn1Ow,1345
26
+ nova_trame-0.17.0.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
27
+ nova_trame-0.17.0.dist-info/entry_points.txt,sha256=J2AmeSwiTYZ4ZqHHp9HO6v4MaYQTTBPbNh6WtoqOT58,42
28
+ nova_trame-0.17.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.1.1
2
+ Generator: poetry-core 2.1.2
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any