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.
- nova/trame/view/components/input_field.py +21 -8
- nova/trame/view/components/visualization/__init__.py +2 -1
- nova/trame/view/components/visualization/matplotlib_figure.py +307 -0
- nova/trame/view/theme/assets/core_style.scss +57 -4
- nova/trame/view/theme/assets/vuetify_config.json +17 -11
- {nova_trame-0.15.0.dist-info → nova_trame-0.17.0.dist-info}/METADATA +5 -1
- {nova_trame-0.15.0.dist-info → nova_trame-0.17.0.dist-info}/RECORD +10 -9
- {nova_trame-0.15.0.dist-info → nova_trame-0.17.0.dist-info}/WHEEL +1 -1
- {nova_trame-0.15.0.dist-info → nova_trame-0.17.0.dist-info}/LICENSE +0 -0
- {nova_trame-0.15.0.dist-info → nova_trame-0.17.0.dist-info}/entry_points.txt +0 -0
@@ -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
|
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>`
|
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>`
|
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
|
-
|
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)
|
@@ -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.
|
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.
|
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:
|
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
|
-
"
|
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": [
|
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.
|
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=
|
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=
|
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=
|
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=
|
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.
|
24
|
-
nova_trame-0.
|
25
|
-
nova_trame-0.
|
26
|
-
nova_trame-0.
|
27
|
-
nova_trame-0.
|
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,,
|
File without changes
|
File without changes
|