nova-trame 0.13.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.
- nova/__init__.py +1 -0
- nova/trame/__init__.py +3 -0
- nova/trame/model/remote_file_input.py +109 -0
- nova/trame/view/components/__init__.py +4 -0
- nova/trame/view/components/input_field.py +302 -0
- nova/trame/view/components/remote_file_input.py +191 -0
- nova/trame/view/components/visualization/__init__.py +3 -0
- nova/trame/view/components/visualization/interactive_2d_plot.py +85 -0
- nova/trame/view/layouts/__init__.py +5 -0
- nova/trame/view/layouts/grid.py +148 -0
- nova/trame/view/layouts/hbox.py +79 -0
- nova/trame/view/layouts/vbox.py +79 -0
- nova/trame/view/theme/__init__.py +3 -0
- nova/trame/view/theme/assets/core_style.scss +30 -0
- nova/trame/view/theme/assets/favicon.png +0 -0
- nova/trame/view/theme/assets/vuetify_config.json +180 -0
- nova/trame/view/theme/theme.py +262 -0
- nova/trame/view/utilities/local_storage.py +102 -0
- nova/trame/view_model/remote_file_input.py +93 -0
- nova_trame-0.13.1.dist-info/LICENSE +21 -0
- nova_trame-0.13.1.dist-info/METADATA +40 -0
- nova_trame-0.13.1.dist-info/RECORD +24 -0
- nova_trame-0.13.1.dist-info/WHEEL +4 -0
- nova_trame-0.13.1.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,262 @@
|
|
1
|
+
"""Implementation of ThemedApp."""
|
2
|
+
|
3
|
+
import json
|
4
|
+
import logging
|
5
|
+
from asyncio import create_task
|
6
|
+
from functools import partial
|
7
|
+
from pathlib import Path
|
8
|
+
from typing import Optional
|
9
|
+
|
10
|
+
import sass
|
11
|
+
from mergedeep import Strategy, merge
|
12
|
+
from trame.app import get_server
|
13
|
+
from trame.assets.local import LocalFileManager
|
14
|
+
from trame.ui.vuetify3 import VAppLayout
|
15
|
+
from trame.widgets import client
|
16
|
+
from trame.widgets import vuetify3 as vuetify
|
17
|
+
from trame_client.widgets import html
|
18
|
+
from trame_server.core import Server
|
19
|
+
from trame_server.state import State
|
20
|
+
|
21
|
+
from nova.mvvm.pydantic_utils import validate_pydantic_parameter
|
22
|
+
from nova.trame.view.utilities.local_storage import LocalStorageManager
|
23
|
+
|
24
|
+
THEME_PATH = Path(__file__).parent
|
25
|
+
|
26
|
+
logger = logging.getLogger(__name__)
|
27
|
+
logger.setLevel(logging.INFO)
|
28
|
+
|
29
|
+
|
30
|
+
class ThemedApp:
|
31
|
+
"""Automatically injects theming into your Trame application.
|
32
|
+
|
33
|
+
You should always inherit from this class when you define your Trame application.
|
34
|
+
|
35
|
+
Currently, it supports two themes:
|
36
|
+
|
37
|
+
1. ModernTheme - The recommended theme for most applications. Leverages ORNL brand colors and a typical Vuetify \
|
38
|
+
appearance.
|
39
|
+
2. CompactTheme - Similar to ModernTheme but with a smaller global font size and reduced margins and paddings on \
|
40
|
+
all components.
|
41
|
+
"""
|
42
|
+
|
43
|
+
def __init__(
|
44
|
+
self, layout: str = "default", server: Server = None, vuetify_config_overrides: Optional[dict] = None
|
45
|
+
) -> None:
|
46
|
+
"""Constructor for the ThemedApp class.
|
47
|
+
|
48
|
+
Parameters
|
49
|
+
----------
|
50
|
+
layout : str
|
51
|
+
The layout to use. Current options are: :code:`default` and :code:`two-column`
|
52
|
+
server : `trame_server.core.Server \
|
53
|
+
<https://trame.readthedocs.io/en/latest/core.server.html#trame_server.core.Server>`_, optional
|
54
|
+
The Trame server to use. If not provided, a new server will be created.
|
55
|
+
vuetify_config_overrides : dict, optional
|
56
|
+
`Vuetify Configuration <https://vuetifyjs.com/en/features/global-configuration/>`__
|
57
|
+
that will override anything set in our default configuration. You should only use this if you don't want to
|
58
|
+
use one of our predefined themes. If you just want to set your color palette without providing a full
|
59
|
+
Vuetify configuration, then you can set use the following to only set the color palette used by our
|
60
|
+
:code:`ModernTheme`:
|
61
|
+
|
62
|
+
.. code-block:: json
|
63
|
+
|
64
|
+
{
|
65
|
+
"primary": "#f00",
|
66
|
+
"secondary": "#0f0",
|
67
|
+
"accent": "#00f",
|
68
|
+
}
|
69
|
+
|
70
|
+
Returns
|
71
|
+
-------
|
72
|
+
None
|
73
|
+
"""
|
74
|
+
self.server = get_server(server, client_type="vue3")
|
75
|
+
self.local_storage: Optional[LocalStorageManager] = None
|
76
|
+
if vuetify_config_overrides is None:
|
77
|
+
vuetify_config_overrides = {}
|
78
|
+
|
79
|
+
self.css = None
|
80
|
+
try:
|
81
|
+
with open(THEME_PATH / "assets" / "core_style.scss", "r") as scss_file:
|
82
|
+
self.css = sass.compile(string=scss_file.read())
|
83
|
+
except Exception as e:
|
84
|
+
logger.warning("Could not load base scss stylesheet.")
|
85
|
+
logger.error(e)
|
86
|
+
|
87
|
+
theme_path = THEME_PATH / "assets" / "vuetify_config.json"
|
88
|
+
try:
|
89
|
+
with open(theme_path, "r") as vuetify_config:
|
90
|
+
self.vuetify_config = json.load(vuetify_config)
|
91
|
+
|
92
|
+
merge(
|
93
|
+
self.vuetify_config,
|
94
|
+
vuetify_config_overrides,
|
95
|
+
strategy=Strategy.REPLACE,
|
96
|
+
)
|
97
|
+
except Exception as e:
|
98
|
+
logger.warning(f"Could not load vuetify config from {theme_path}.")
|
99
|
+
logger.error(e)
|
100
|
+
for shortcut in ["primary", "secondary", "accent"]:
|
101
|
+
if shortcut in self.vuetify_config:
|
102
|
+
self.vuetify_config["theme"]["themes"]["ModernTheme"]["colors"][shortcut] = self.vuetify_config[
|
103
|
+
shortcut
|
104
|
+
]
|
105
|
+
|
106
|
+
# Since this is only intended for theming Trame apps, I don't think we need to invoke the MVVM framework here,
|
107
|
+
# and working directly with the Trame state makes this easier for me to manage.
|
108
|
+
self.state.nova__menu = False
|
109
|
+
self.state.nova__defaults = self.vuetify_config["theme"]["themes"]["ModernTheme"].get("defaults", {})
|
110
|
+
self.state.nova__theme = "ModernTheme"
|
111
|
+
self.state.trame__favicon = LocalFileManager(__file__).url("favicon", "./assets/favicon.png")
|
112
|
+
|
113
|
+
@property
|
114
|
+
def state(self) -> State:
|
115
|
+
return self.server.state
|
116
|
+
|
117
|
+
def init_mantid(self) -> None:
|
118
|
+
"""Initializes MantidManager.
|
119
|
+
|
120
|
+
This doesn't happen by default because Mantid is a large dependency.
|
121
|
+
"""
|
122
|
+
pass
|
123
|
+
|
124
|
+
async def _init_theme(self) -> None:
|
125
|
+
if self.local_storage:
|
126
|
+
theme = await self.local_storage.get("nova__theme")
|
127
|
+
if theme and theme in self.vuetify_config["theme"]["themes"]:
|
128
|
+
self.set_theme(theme, False)
|
129
|
+
|
130
|
+
async def init_theme(self) -> None:
|
131
|
+
create_task(self._init_theme())
|
132
|
+
|
133
|
+
def set_theme(self, theme: Optional[str], force: bool = True) -> None:
|
134
|
+
"""Sets the theme of the application.
|
135
|
+
|
136
|
+
Parameters
|
137
|
+
----------
|
138
|
+
theme : str, optional
|
139
|
+
The new theme to use. If the theme is not found, the default theme will be used.
|
140
|
+
force : bool, optional
|
141
|
+
If True, the theme will be set even if the theme selection menu is disabled.
|
142
|
+
|
143
|
+
Returns
|
144
|
+
-------
|
145
|
+
None
|
146
|
+
"""
|
147
|
+
if theme not in self.vuetify_config["theme"]["themes"]:
|
148
|
+
raise ValueError(
|
149
|
+
f"Theme '{theme}' not found in the Vuetify configuration. "
|
150
|
+
"For a list of available themes, please visit "
|
151
|
+
"https://nova-application-development.readthedocs.io/en/stable/api.html#nova.trame.ThemedApp."
|
152
|
+
)
|
153
|
+
|
154
|
+
# I set force to True by default as I want the user to be able to say self.set_theme('MyTheme')
|
155
|
+
# while still blocking theme.py calls to set_theme if the selection menu is disabled.
|
156
|
+
if self.state.nova__menu or force:
|
157
|
+
with self.state:
|
158
|
+
self.state.nova__defaults = self.vuetify_config["theme"]["themes"].get(theme, {}).get("defaults", {})
|
159
|
+
self.state.nova__theme = theme
|
160
|
+
|
161
|
+
# We only want to sync to localStorage if the user is selecting and we want to preserve the selection.
|
162
|
+
if self.state.nova__menu and self.local_storage:
|
163
|
+
self.local_storage.set("nova__theme", theme)
|
164
|
+
|
165
|
+
def create_ui(self) -> VAppLayout:
|
166
|
+
"""Creates the base UI into which you will inject your content.
|
167
|
+
|
168
|
+
You should always call this method from your application class that inherits from :code:`ThemedApp`.
|
169
|
+
|
170
|
+
Returns
|
171
|
+
-------
|
172
|
+
`trame_client.ui.core.AbstractLayout <https://trame.readthedocs.io/en/latest/core.ui.html#trame_client.ui.core.AbstractLayout>`_
|
173
|
+
"""
|
174
|
+
with VAppLayout(self.server, vuetify_config=self.vuetify_config) as layout:
|
175
|
+
self.local_storage = LocalStorageManager(self.server.controller)
|
176
|
+
|
177
|
+
client.ClientTriggers(mounted=self.init_theme)
|
178
|
+
client.Style(self.css)
|
179
|
+
|
180
|
+
with vuetify.VDefaultsProvider(defaults=("nova__defaults",)) as defaults:
|
181
|
+
layout.defaults = defaults
|
182
|
+
|
183
|
+
with vuetify.VThemeProvider(theme=("nova__theme",)) as theme:
|
184
|
+
layout.theme = theme
|
185
|
+
|
186
|
+
with vuetify.VAppBar() as toolbar:
|
187
|
+
layout.toolbar = toolbar
|
188
|
+
|
189
|
+
with vuetify.VAppBarTitle() as toolbar_title:
|
190
|
+
layout.toolbar_title = toolbar_title
|
191
|
+
vuetify.VSpacer()
|
192
|
+
with html.Div(classes="mr-2") as actions:
|
193
|
+
layout.actions = actions
|
194
|
+
|
195
|
+
with vuetify.VMenu(
|
196
|
+
v_if="nova__menu",
|
197
|
+
close_delay=10000,
|
198
|
+
open_on_hover=True,
|
199
|
+
) as theme_menu:
|
200
|
+
layout.theme_menu = theme_menu
|
201
|
+
|
202
|
+
with vuetify.Template(v_slot_activator="{ props }"):
|
203
|
+
vuetify.VBtn(
|
204
|
+
v_bind="props",
|
205
|
+
classes="mr-2",
|
206
|
+
icon="mdi-brush-variant",
|
207
|
+
)
|
208
|
+
|
209
|
+
with vuetify.VList(width=200):
|
210
|
+
vuetify.VListSubheader("Select Theme")
|
211
|
+
vuetify.VDivider()
|
212
|
+
|
213
|
+
for theme in self.vuetify_config.get("theme", {}).get("themes", {}).values():
|
214
|
+
with vuetify.VListItem(click=partial(self.set_theme, theme["value"])):
|
215
|
+
vuetify.VListItemTitle(theme["title"])
|
216
|
+
vuetify.VListItemSubtitle(
|
217
|
+
"Selected",
|
218
|
+
v_if=f"nova__theme === '{theme['value']}'",
|
219
|
+
)
|
220
|
+
|
221
|
+
with vuetify.VMain(classes="align-stretch d-flex flex-column h-screen"):
|
222
|
+
# [slot override example]
|
223
|
+
layout.pre_content = vuetify.VSheet(classes="bg-background ")
|
224
|
+
# [slot override example complete]
|
225
|
+
with vuetify.VContainer(classes="flex-1-1 overflow-hidden pt-0 pb-0", fluid=True):
|
226
|
+
layout.content = html.Div(classes="h-100 overflow-y-auto pb-1 ")
|
227
|
+
layout.post_content = vuetify.VSheet(classes="bg-background ")
|
228
|
+
|
229
|
+
with vuetify.VFooter(
|
230
|
+
app=True,
|
231
|
+
classes="my-0 px-1 py-0 text-center justify-center",
|
232
|
+
border=True,
|
233
|
+
) as footer:
|
234
|
+
layout.footer = footer
|
235
|
+
|
236
|
+
vuetify.VProgressCircular(
|
237
|
+
classes="mr-1",
|
238
|
+
color="primary",
|
239
|
+
indeterminate=("!!galaxy_running",),
|
240
|
+
size=16,
|
241
|
+
width=3,
|
242
|
+
)
|
243
|
+
html.A(
|
244
|
+
"Powered by Calvera",
|
245
|
+
classes="text-grey-lighten-1 text-caption text-decoration-none",
|
246
|
+
href=("galaxy_url",),
|
247
|
+
target="_blank",
|
248
|
+
)
|
249
|
+
vuetify.VSpacer()
|
250
|
+
footer.add_child(
|
251
|
+
'<a href="https://www.ornl.gov/" '
|
252
|
+
'class="text-grey-lighten-1 text-caption text-decoration-none" '
|
253
|
+
'target="_blank">© 2024 ORNL</a>'
|
254
|
+
)
|
255
|
+
|
256
|
+
@self.server.controller.trigger("validate_pydantic_field")
|
257
|
+
def validate_pydantic_field(name: str, value: str, index: int) -> bool:
|
258
|
+
if "[index]" in name:
|
259
|
+
name = name.replace("[index]", f"[{str(index)}]")
|
260
|
+
return validate_pydantic_parameter(name, value)
|
261
|
+
|
262
|
+
return layout
|
@@ -0,0 +1,102 @@
|
|
1
|
+
"""Implementation of LocalStorageManager."""
|
2
|
+
|
3
|
+
from asyncio import sleep
|
4
|
+
from typing import Any, Optional
|
5
|
+
|
6
|
+
from trame.widgets import client
|
7
|
+
from trame_server.core import Controller
|
8
|
+
|
9
|
+
|
10
|
+
class LocalStorageManager:
|
11
|
+
"""Allows manipulation of window.localStorage from your Python code.
|
12
|
+
|
13
|
+
LocalStorageManager requires a Trame layout to exist in order to work properly. Because of this, it's strongly
|
14
|
+
recommended that you don't use this class directly. Instead, ThemedApp automatically creates an instance of this
|
15
|
+
class and stores it in ThemedApp.local_storage, through which it can safely be used.
|
16
|
+
"""
|
17
|
+
|
18
|
+
def __init__(self, ctrl: Controller) -> None:
|
19
|
+
"""Constructor for the LocalStorageManager class.
|
20
|
+
|
21
|
+
Parameters
|
22
|
+
----------
|
23
|
+
ctrl : `trame_server.core.Controller <https://trame.readthedocs.io/en/latest/core.controller.html#trame_server.controller.Controller>`_
|
24
|
+
The Trame controller.
|
25
|
+
|
26
|
+
Returns
|
27
|
+
-------
|
28
|
+
None
|
29
|
+
"""
|
30
|
+
self.js_get = client.JSEval(
|
31
|
+
exec=(
|
32
|
+
"window.trame.trigger("
|
33
|
+
" 'nova__local_storage_trigger', "
|
34
|
+
" [$event.key, window.localStorage.getItem($event.key)]"
|
35
|
+
");"
|
36
|
+
)
|
37
|
+
).exec
|
38
|
+
self.js_remove = client.JSEval(exec="window.localStorage.removeItem($event.key);").exec
|
39
|
+
self.js_set = client.JSEval(exec="window.localStorage.setItem($event.key, $event.value);").exec
|
40
|
+
|
41
|
+
self._ready: dict[str, bool] = {}
|
42
|
+
self._values: dict[str, str] = {}
|
43
|
+
|
44
|
+
@ctrl.trigger("nova__local_storage_trigger")
|
45
|
+
def _(key: str, value: str) -> None:
|
46
|
+
self._ready[key] = True
|
47
|
+
self._values[key] = value
|
48
|
+
|
49
|
+
async def get(self, key: str) -> Optional[str]:
|
50
|
+
"""Gets the value of a key from window.localStorage.
|
51
|
+
|
52
|
+
You cannot call this from the main Trame coroutine because this waits on a response from the browser that must
|
53
|
+
be processed by the main coroutine. Instead, you should call this from another thread or coroutine, typically
|
54
|
+
with :code:`asyncio.create_task`.
|
55
|
+
|
56
|
+
Parameters
|
57
|
+
----------
|
58
|
+
key : str
|
59
|
+
The key to get the value of.
|
60
|
+
|
61
|
+
Returns
|
62
|
+
-------
|
63
|
+
Optional[str]
|
64
|
+
The value of the key from window.localStorage.
|
65
|
+
"""
|
66
|
+
self._ready[key] = False
|
67
|
+
self.js_get({"key": key})
|
68
|
+
|
69
|
+
while not self._ready[key]:
|
70
|
+
await sleep(0.1)
|
71
|
+
|
72
|
+
return self._values[key]
|
73
|
+
|
74
|
+
def remove(self, key: str) -> None:
|
75
|
+
"""Removes a key from window.localStorage.
|
76
|
+
|
77
|
+
Parameters
|
78
|
+
----------
|
79
|
+
key : str
|
80
|
+
The key to remove.
|
81
|
+
|
82
|
+
Returns
|
83
|
+
-------
|
84
|
+
None
|
85
|
+
"""
|
86
|
+
self.js_remove({"key": key})
|
87
|
+
|
88
|
+
def set(self, key: str, value: Any) -> None:
|
89
|
+
"""Sets the value of a key in window.localStorage.
|
90
|
+
|
91
|
+
Parameters
|
92
|
+
----------
|
93
|
+
key : str
|
94
|
+
The key to set the value of.
|
95
|
+
value : typing.Any
|
96
|
+
The value to set. This value will be coerced to a string before being stored.
|
97
|
+
|
98
|
+
Returns
|
99
|
+
-------
|
100
|
+
None
|
101
|
+
"""
|
102
|
+
self.js_set({"key": key, "value": value})
|
@@ -0,0 +1,93 @@
|
|
1
|
+
"""View model for RemoteFileInput."""
|
2
|
+
|
3
|
+
from typing import Any, Union
|
4
|
+
|
5
|
+
from nova.mvvm.interface import BindingInterface
|
6
|
+
from nova.trame.model.remote_file_input import RemoteFileInputModel
|
7
|
+
|
8
|
+
|
9
|
+
class RemoteFileInputViewModel:
|
10
|
+
"""Manages the view state of RemoteFileInput."""
|
11
|
+
|
12
|
+
counter = 0
|
13
|
+
|
14
|
+
def __init__(self, model: RemoteFileInputModel, binding: BindingInterface) -> None:
|
15
|
+
"""Creates a new RemoteFileInputViewModel."""
|
16
|
+
self.model = model
|
17
|
+
|
18
|
+
# Needed to keep state variables separated if this class is instantiated multiple times.
|
19
|
+
self.id = RemoteFileInputViewModel.counter
|
20
|
+
RemoteFileInputViewModel.counter += 1
|
21
|
+
|
22
|
+
self.showing_all_files = False
|
23
|
+
self.showing_base_paths = True
|
24
|
+
self.previous_value = ""
|
25
|
+
self.value = ""
|
26
|
+
self.dialog_bind = binding.new_bind()
|
27
|
+
self.file_list_bind = binding.new_bind()
|
28
|
+
self.filter_bind = binding.new_bind()
|
29
|
+
self.showing_all_bind = binding.new_bind()
|
30
|
+
self.valid_selection_bind = binding.new_bind()
|
31
|
+
self.on_close_bind = binding.new_bind()
|
32
|
+
self.on_update_bind = binding.new_bind()
|
33
|
+
|
34
|
+
def open_dialog(self) -> None:
|
35
|
+
self.previous_value = self.value
|
36
|
+
self.populate_file_list()
|
37
|
+
|
38
|
+
def close_dialog(self, cancel: bool = False) -> None:
|
39
|
+
if not cancel:
|
40
|
+
self.on_update_bind.update_in_view(self.value)
|
41
|
+
else:
|
42
|
+
self.value = self.previous_value
|
43
|
+
|
44
|
+
self.filter_bind.update_in_view(self.value)
|
45
|
+
self.on_close_bind.update_in_view(None)
|
46
|
+
|
47
|
+
def filter_paths(self, filter: str) -> None:
|
48
|
+
self.populate_file_list(filter)
|
49
|
+
|
50
|
+
def get_dialog_state_name(self) -> str:
|
51
|
+
return f"nova__dialog_{self.id}"
|
52
|
+
|
53
|
+
def get_file_list_state_name(self) -> str:
|
54
|
+
return f"nova__file_list_{self.id}"
|
55
|
+
|
56
|
+
def get_filter_state_name(self) -> str:
|
57
|
+
return f"nova__filter_{self.id}"
|
58
|
+
|
59
|
+
def get_showing_all_state_name(self) -> str:
|
60
|
+
return f"nova__showing_all_{self.id}"
|
61
|
+
|
62
|
+
def get_valid_selection_state_name(self) -> str:
|
63
|
+
return f"nova__valid_selection_{self.id}"
|
64
|
+
|
65
|
+
def init_view(self) -> None:
|
66
|
+
self.dialog_bind.update_in_view(False)
|
67
|
+
self.valid_selection_bind.update_in_view(False)
|
68
|
+
self.showing_all_bind.update_in_view(self.showing_all_files)
|
69
|
+
|
70
|
+
def set_value(self, value: str) -> None:
|
71
|
+
self.value = value
|
72
|
+
|
73
|
+
def toggle_showing_all_files(self) -> None:
|
74
|
+
self.showing_all_files = not self.showing_all_files
|
75
|
+
self.showing_all_bind.update_in_view(self.showing_all_files)
|
76
|
+
self.populate_file_list()
|
77
|
+
|
78
|
+
def populate_file_list(self, filter: str = "") -> None:
|
79
|
+
files = self.scan_current_path(filter)
|
80
|
+
self.file_list_bind.update_in_view(files)
|
81
|
+
|
82
|
+
def scan_current_path(self, filter: str) -> list[dict[str, Any]]:
|
83
|
+
files, self.showing_base_paths = self.model.scan_current_path(self.value, self.showing_all_files, filter)
|
84
|
+
|
85
|
+
return files
|
86
|
+
|
87
|
+
def select_file(self, file: Union[dict[str, str], str]) -> None:
|
88
|
+
new_path = self.model.select_file(file, self.value, self.showing_base_paths)
|
89
|
+
self.set_value(new_path)
|
90
|
+
self.filter_bind.update_in_view(self.value)
|
91
|
+
|
92
|
+
self.valid_selection_bind.update_in_view(self.model.valid_selection(new_path))
|
93
|
+
self.populate_file_list()
|
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2024 ONRL
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
@@ -0,0 +1,40 @@
|
|
1
|
+
Metadata-Version: 2.3
|
2
|
+
Name: nova-trame
|
3
|
+
Version: 0.13.1
|
4
|
+
Summary: A Python Package for injecting curated themes and custom components into Trame applications
|
5
|
+
License: MIT
|
6
|
+
Keywords: NDIP,Python,Trame,Vuetify
|
7
|
+
Author: Duggan, John
|
8
|
+
Author-email: dugganjw@ornl.gov
|
9
|
+
Requires-Python: >=3.10,<4.0
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
16
|
+
Requires-Dist: libsass
|
17
|
+
Requires-Dist: mergedeep
|
18
|
+
Requires-Dist: nova-mvvm
|
19
|
+
Requires-Dist: pydantic
|
20
|
+
Requires-Dist: tomli
|
21
|
+
Requires-Dist: trame
|
22
|
+
Requires-Dist: trame-vega
|
23
|
+
Requires-Dist: trame-vuetify
|
24
|
+
Description-Content-Type: text/markdown
|
25
|
+
|
26
|
+
nova-trame
|
27
|
+
==========
|
28
|
+
|
29
|
+
`nova-trame` is a Python package for streamlining development of Trame applications used in the NOVA project.
|
30
|
+
|
31
|
+
You can install this package directly with
|
32
|
+
|
33
|
+
```commandline
|
34
|
+
pip install nova-trame
|
35
|
+
```
|
36
|
+
|
37
|
+
A user guide, examples, and a full API for this package can be found at https://nova-application-development.readthedocs.io/en/stable/.
|
38
|
+
|
39
|
+
Developers: please read [this document](DEVELOPMENT.md)
|
40
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
nova/__init__.py,sha256=ED6jHcYiuYpr_0vjGz0zx2lrrmJT9sDJCzIljoDfmlM,65
|
2
|
+
nova/trame/__init__.py,sha256=gFrAg1qva5PIqR5TjvPzAxLx103IKipJLqp3XXvrQL8,59
|
3
|
+
nova/trame/model/remote_file_input.py,sha256=9KAf31ZHzpsh_aXUrNcF81Q5jvUZDWCzW1QATKls-Jk,3675
|
4
|
+
nova/trame/view/components/__init__.py,sha256=fopr6mVqcpDcVYK9ue7SLUHyswgvRPcFESTq86mu1R8,128
|
5
|
+
nova/trame/view/components/input_field.py,sha256=YyDJJKHBsAdLN-GFOhXEpwNs5aJHb0BBbstcQ6nbAQE,12680
|
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
|
8
|
+
nova/trame/view/components/visualization/interactive_2d_plot.py,sha256=foZCMoqbuahT5dtqIQvm8C4ZJcY9P211eJEcpQJltmM,3421
|
9
|
+
nova/trame/view/layouts/__init__.py,sha256=cMrlB5YMUoK8EGB83b34UU0kPTVrH8AxsYvKRtpUNEc,141
|
10
|
+
nova/trame/view/layouts/grid.py,sha256=k-QHuH31XeAVDuMKUMoAMVnAM-Yavq7kdLYOC1ZrGTQ,5021
|
11
|
+
nova/trame/view/layouts/hbox.py,sha256=r5irhFX6YWTWN4V4NwNQx6mheyM8p6PVcJbrbhvOAwo,2625
|
12
|
+
nova/trame/view/layouts/vbox.py,sha256=Q4EvrtGJORyNF6AnCLGXToy8XU6yofiO5_kt7hK-AYs,2626
|
13
|
+
nova/trame/view/theme/__init__.py,sha256=70_marDlTigIcPEOGiJb2JTs-8b2sGM5SlY7XBPtBDM,54
|
14
|
+
nova/trame/view/theme/assets/core_style.scss,sha256=AktysiiCYLeiTzCTtYwkksiUVmqb4S23RlDcW8L1ebI,518
|
15
|
+
nova/trame/view/theme/assets/favicon.png,sha256=Xbp1nUmhcBDeObjsebEbEAraPDZ_M163M_ZLtm5AbQc,1927
|
16
|
+
nova/trame/view/theme/assets/vuetify_config.json,sha256=7WGV6rO7hv2sapGsX9yy1d-dINshYFXRNX99D9I3dKQ,4780
|
17
|
+
nova/trame/view/theme/theme.py,sha256=-5lq8u34eQ9j1bMeB5b63BiAdpi92WI2o-fJKcrwl3o,11401
|
18
|
+
nova/trame/view/utilities/local_storage.py,sha256=vD8f2VZIpxhIKjZwEaD7siiPCTZO4cw9AfhwdawwYLY,3218
|
19
|
+
nova/trame/view_model/remote_file_input.py,sha256=WHWCQkZBGeKLe1aTPbtVNI8tn-PDt64mi1-561uuBpQ,3320
|
20
|
+
nova_trame-0.13.1.dist-info/LICENSE,sha256=MOqZ8tPMKy8ZETJ2-HEvFTZ7dYNlg3gXmBkV-Y9i8bw,1061
|
21
|
+
nova_trame-0.13.1.dist-info/METADATA,sha256=ivedQKUq-ZJ2jk1e0aXkeHjbvtxXWLyOBGNl-lijv5E,1240
|
22
|
+
nova_trame-0.13.1.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
|
23
|
+
nova_trame-0.13.1.dist-info/entry_points.txt,sha256=J2AmeSwiTYZ4ZqHHp9HO6v4MaYQTTBPbNh6WtoqOT58,42
|
24
|
+
nova_trame-0.13.1.dist-info/RECORD,,
|