solara-ui 1.39.0__py2.py3-none-any.whl → 1.41.0__py2.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.
- solara/__init__.py +1 -1
- solara/__main__.py +23 -10
- solara/components/__init__.py +1 -0
- solara/components/component_vue.py +3 -2
- solara/components/input_text_area.py +86 -0
- solara/components/markdown.py +1 -1
- solara/hooks/use_thread.py +4 -4
- solara/lab/components/chat.py +8 -2
- solara/server/assets/style.css +4 -1
- solara/server/flask.py +1 -1
- solara/server/jupyter/server_extension.py +11 -1
- solara/server/jupyter/solara.py +91 -0
- solara/server/patch.py +1 -0
- solara/server/pyinstaller/__init__.py +9 -0
- solara/server/pyinstaller/hook-ipyreact.py +5 -0
- solara/server/pyinstaller/hook-ipyvuetify.py +5 -0
- solara/server/pyinstaller/hook-solara.py +9 -0
- solara/server/qt.py +113 -0
- solara/server/server.py +6 -1
- solara/server/settings.py +1 -0
- solara/server/starlette.py +18 -6
- solara/server/static/highlight-dark.css +1 -1
- solara/server/static/main-vuetify.js +11 -1
- solara/server/static/solara_bootstrap.py +1 -1
- solara/server/templates/loader-solara.html +1 -1
- solara/server/templates/solara.html.j2 +36 -7
- solara/website/assets/custom.css +20 -57
- solara/website/components/__init__.py +2 -2
- solara/website/components/algolia_api.vue +23 -6
- solara/website/components/breadcrumbs.py +28 -0
- solara/website/components/contact.py +144 -0
- solara/website/components/docs.py +11 -9
- solara/website/components/header.py +31 -20
- solara/website/components/markdown.py +12 -1
- solara/website/components/markdown_nav.vue +34 -0
- solara/website/components/sidebar.py +9 -1
- solara/website/pages/__init__.py +93 -254
- solara/website/pages/about/__init__.py +9 -0
- solara/website/pages/about/about.md +3 -0
- solara/website/pages/careers/__init__.py +27 -0
- solara/website/pages/changelog/__init__.py +2 -2
- solara/website/pages/changelog/changelog.md +12 -0
- solara/website/pages/contact/__init__.py +30 -6
- solara/website/pages/documentation/__init__.py +25 -33
- solara/website/pages/documentation/advanced/content/10-howto/40-embed.md +2 -1
- solara/website/pages/documentation/advanced/content/15-reference/41-asset-files.md +1 -1
- solara/website/pages/documentation/advanced/content/20-understanding/40-routing.md +17 -1
- solara/website/pages/documentation/advanced/content/20-understanding/50-solara-server.md +2 -1
- solara/website/pages/documentation/advanced/content/30-enterprise/00-overview.md +1 -1
- solara/website/pages/documentation/advanced/content/30-enterprise/10-oauth.md +5 -2
- solara/website/pages/documentation/api/hooks/use_thread.md +6 -0
- solara/website/pages/documentation/components/data/pivot_table.py +2 -2
- solara/website/pages/documentation/components/input/input.py +2 -0
- solara/website/pages/documentation/components/output/sql_code.py +3 -3
- solara/website/pages/documentation/examples/__init__.py +14 -22
- solara/website/pages/documentation/examples/ai/chatbot.py +1 -1
- solara/website/pages/documentation/examples/general/vue_component.py +1 -1
- solara/website/pages/documentation/examples/libraries/altair.py +1 -0
- solara/website/pages/documentation/examples/libraries/bqplot.py +1 -1
- solara/website/pages/documentation/examples/libraries/ipyleaflet.py +1 -1
- solara/website/pages/documentation/examples/libraries/ipyleaflet_advanced.py +1 -1
- solara/website/pages/documentation/examples/utilities/countdown_timer.py +18 -20
- solara/website/pages/documentation/examples/visualization/annotator.py +1 -3
- solara/website/pages/documentation/examples/visualization/linked_views.py +4 -4
- solara/website/pages/documentation/getting_started/content/00-quickstart.md +18 -0
- solara/website/pages/documentation/getting_started/content/04-tutorials/_jupyter_dashboard_1.ipynb +2 -2
- solara/website/pages/documentation/getting_started/content/05-fundamentals/10-components.md +19 -14
- solara/website/pages/documentation/getting_started/content/05-fundamentals/50-state-management.md +205 -15
- solara/website/pages/documentation/getting_started/content/07-deploying/10-self-hosted.md +3 -1
- solara/website/pages/home.vue +1199 -0
- solara/website/pages/our_team/__init__.py +83 -0
- solara/website/pages/pricing/__init__.py +31 -0
- solara/website/pages/roadmap/__init__.py +11 -0
- solara/website/pages/roadmap/roadmap.md +41 -0
- solara/website/pages/scale_ipywidgets.py +45 -0
- solara/widgets/vue/navigator.vue +46 -16
- solara/widgets/vue/vegalite.vue +18 -0
- {solara_ui-1.39.0.dist-info → solara_ui-1.41.0.dist-info}/METADATA +2 -2
- {solara_ui-1.39.0.dist-info → solara_ui-1.41.0.dist-info}/RECORD +83 -66
- solara/website/components/hero.py +0 -15
- solara/website/pages/contact/contact.md +0 -17
- {solara_ui-1.39.0.data → solara_ui-1.41.0.data}/data/etc/jupyter/jupyter_notebook_config.d/solara.json +0 -0
- {solara_ui-1.39.0.data → solara_ui-1.41.0.data}/data/etc/jupyter/jupyter_server_config.d/solara.json +0 -0
- {solara_ui-1.39.0.dist-info → solara_ui-1.41.0.dist-info}/WHEEL +0 -0
- {solara_ui-1.39.0.dist-info → solara_ui-1.41.0.dist-info}/licenses/LICENSE +0 -0
solara/server/starlette.py
CHANGED
|
@@ -391,16 +391,16 @@ See also https://solara.dev/documentation/getting_started/deploying/self-hosted
|
|
|
391
391
|
"""
|
|
392
392
|
if "script-name" in request.headers:
|
|
393
393
|
msg += f"""It looks like the reverse proxy sets the script-name header to {request.headers['script-name']!r}
|
|
394
|
-
|
|
394
|
+
"""
|
|
395
395
|
if "x-script-name" in request.headers:
|
|
396
396
|
msg += f"""It looks like the reverse proxy sets the x-script-name header to {request.headers['x-script-name']!r}
|
|
397
|
-
|
|
397
|
+
"""
|
|
398
398
|
if configured_root_path:
|
|
399
399
|
msg += f"""It looks like the root path was configured to {configured_root_path!r} in the settings
|
|
400
|
-
|
|
400
|
+
"""
|
|
401
401
|
if root_path_asgi:
|
|
402
402
|
msg += f"""It looks like the root path set by the asgi framework was configured to {root_path_asgi!r}
|
|
403
|
-
|
|
403
|
+
"""
|
|
404
404
|
warnings.warn(msg)
|
|
405
405
|
if host and forwarded_host and forwarded_proto:
|
|
406
406
|
port = request.base_url.port
|
|
@@ -451,6 +451,18 @@ See also https://solara.dev/documentation/getting_started/deploying/self-hosted
|
|
|
451
451
|
if request.scope["scheme"] == "https" or request.headers.get("x-forwarded-proto", "http") == "https" or request.base_url.hostname == "localhost":
|
|
452
452
|
samesite = "none"
|
|
453
453
|
secure = True
|
|
454
|
+
elif request.base_url.hostname != "localhost":
|
|
455
|
+
warnings.warn(f"""Cookies with samesite=none require https, but according to the asgi framework, the scheme is {request.scope['scheme']!r}
|
|
456
|
+
and the x-forwarded-proto header is {request.headers.get('x-forwarded-proto', 'http')!r}. We will fallback to samesite=lax.
|
|
457
|
+
|
|
458
|
+
If you embed solara in an iframe, make sure you forward the x-forwarded-proto header correctly so that the session cookie can be set.
|
|
459
|
+
|
|
460
|
+
See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite for more information on samesite cookies.
|
|
461
|
+
|
|
462
|
+
Also check out the following Solara documentation:
|
|
463
|
+
* https://solara.dev/documentation/getting_started/deploying/self-hosted
|
|
464
|
+
* https://solara.dev/documentation/advanced/howto/embed
|
|
465
|
+
""")
|
|
454
466
|
response.set_cookie(
|
|
455
467
|
server.COOKIE_KEY_SESSION_ID,
|
|
456
468
|
value=session_id,
|
|
@@ -483,9 +495,9 @@ class StaticNbFiles(StaticFilesOptionalAuth):
|
|
|
483
495
|
# from https://github.com/encode/starlette/pull/1377/files
|
|
484
496
|
def lookup_path(self, path: str) -> typing.Tuple[str, typing.Optional[os.stat_result]]:
|
|
485
497
|
for directory in self.all_directories:
|
|
498
|
+
directory = os.path.realpath(directory)
|
|
486
499
|
original_path = os.path.join(directory, path)
|
|
487
500
|
full_path = os.path.realpath(original_path)
|
|
488
|
-
directory = os.path.realpath(directory)
|
|
489
501
|
# return early if someone tries to access a file outside of the directory
|
|
490
502
|
if not path_is_child_of(Path(original_path), Path(directory)):
|
|
491
503
|
return "", None
|
|
@@ -668,7 +680,7 @@ routes = [
|
|
|
668
680
|
*([Mount(f"/{cdn_url_path}", app=StaticCdn(directory=settings.assets.proxy_cache_dir))] if solara.settings.assets.proxy else []),
|
|
669
681
|
Mount(f"{prefix}/static/public", app=StaticPublic()),
|
|
670
682
|
Mount(f"{prefix}/static/assets", app=StaticAssets()),
|
|
671
|
-
Mount(f"{prefix}/
|
|
683
|
+
Mount(f"{prefix}/jupyter/nbextensions", app=StaticNbFiles()),
|
|
672
684
|
Mount(f"{prefix}/static", app=StaticFilesOptionalAuth(directory=server.solara_static)),
|
|
673
685
|
Route("/{fullpath:path}", endpoint=root),
|
|
674
686
|
]
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
and then remove the overlapping first lines with highlight.css
|
|
3
3
|
*/
|
|
4
4
|
.theme--dark.v-application .highlight .hll { background-color: #ffffcc }
|
|
5
|
-
.theme--dark.v-application .highlight { background: #
|
|
5
|
+
.theme--dark.v-application .highlight { background: #1d1f22; color: #ABB2BF }
|
|
6
6
|
.theme--dark.v-application .highlight .c { color: #7F848E } /* Comment */
|
|
7
7
|
.theme--dark.v-application .highlight .err { color: #ABB2BF } /* Error */
|
|
8
8
|
.theme--dark.v-application .highlight .esc { color: #ABB2BF } /* Escape */
|
|
@@ -127,7 +127,7 @@ async function solaraInit(mountId, appName) {
|
|
|
127
127
|
window.navigator.sendBeacon(close_url);
|
|
128
128
|
}
|
|
129
129
|
});
|
|
130
|
-
let kernel = await solara.connectKernel(solara.
|
|
130
|
+
let kernel = await solara.connectKernel(solara.jupyterRootPath, kernelId)
|
|
131
131
|
if (!kernel) {
|
|
132
132
|
return;
|
|
133
133
|
}
|
|
@@ -138,15 +138,25 @@ async function solaraInit(mountId, appName) {
|
|
|
138
138
|
});
|
|
139
139
|
|
|
140
140
|
window.addEventListener('solara.router', function (event) {
|
|
141
|
+
app.$data.urlHasChanged = true;
|
|
141
142
|
if(kernel.status == 'busy') {
|
|
142
143
|
app.$data.loadingPage = true;
|
|
143
144
|
}
|
|
144
145
|
});
|
|
145
146
|
kernel.statusChanged.connect(() => {
|
|
147
|
+
// When navigation is triggered from the front-end, kernel.status becoming busy and
|
|
148
|
+
// solara.router event happen in a different order than when navigating through Python, so
|
|
149
|
+
// if the URL has changed when the kernel becomes busy, we set loadingPage to true
|
|
150
|
+
if (kernel.status == 'busy' && app.$data.urlHasChanged) {
|
|
151
|
+
app.$data.loadingPage = true;
|
|
152
|
+
}
|
|
146
153
|
// the first idle after a loadingPage == true (a router event)
|
|
147
154
|
// will be used as indicator that the page is loaded
|
|
148
155
|
if (app.$data.loadingPage && kernel.status == 'idle') {
|
|
149
156
|
app.$data.loadingPage = false;
|
|
157
|
+
app.$data.urlHasChanged = false;
|
|
158
|
+
const event = new Event('solara.pageReady');
|
|
159
|
+
window.dispatchEvent(event);
|
|
150
160
|
}
|
|
151
161
|
});
|
|
152
162
|
|
|
@@ -119,7 +119,7 @@ async def main():
|
|
|
119
119
|
]
|
|
120
120
|
for dep in requirements:
|
|
121
121
|
await micropip.install(dep, keep_going=True)
|
|
122
|
-
await micropip.install("/wheels/solara-1.
|
|
122
|
+
await micropip.install("/wheels/solara-1.41.0-py2.py3-none-any.whl", keep_going=True)
|
|
123
123
|
import solara
|
|
124
124
|
|
|
125
125
|
el = solara.Warning("lala")
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
<jupyter-widget-mount-point mount-id="solara-main">
|
|
33
33
|
A widget with mount-id="solara-main" should go here
|
|
34
34
|
</jupyter-widget-mount-point>
|
|
35
|
-
<div style="position: absolute; right: 0px; bottom: 0px; padding: 10px;">
|
|
35
|
+
<div v-if="showBanner" style="position: absolute; right: 0px; bottom: 0px; padding: 10px;">
|
|
36
36
|
<b>This website runs on <a href="https://solara.dev">Solara</a></b>
|
|
37
37
|
</div>
|
|
38
38
|
</div>
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
|
|
32
32
|
<script id="jupyter-config-data" type="application/json">
|
|
33
33
|
{
|
|
34
|
-
"baseUrl": "{{
|
|
34
|
+
"baseUrl": "{{jupyter_root_path}}/",
|
|
35
35
|
"kernelId": "1234"
|
|
36
36
|
}
|
|
37
37
|
</script>
|
|
@@ -172,7 +172,7 @@
|
|
|
172
172
|
</script>
|
|
173
173
|
{% endraw -%}
|
|
174
174
|
|
|
175
|
-
<body data-base-url="{{root_path}}/
|
|
175
|
+
<body data-base-url="{{root_path}}/jupyter/">
|
|
176
176
|
{% if perform_check %}
|
|
177
177
|
<iframe src="https://solara.dev/static/public/success.html?system=solara&check=html&version={{solara_version}}" style="display: none"></iframe>
|
|
178
178
|
{% endif %}
|
|
@@ -243,12 +243,13 @@
|
|
|
243
243
|
{% endif %}
|
|
244
244
|
<script>
|
|
245
245
|
solara.rootPath = {{ root_path | tojson | safe}};
|
|
246
|
+
solara.jupyterRootPath = {{ jupyter_root_path | tojson | safe}};
|
|
246
247
|
solara.cdn = {{ cdn | tojson | safe }};
|
|
247
248
|
// the vue templates expect it to not have a trailing slash
|
|
248
249
|
solara.cdn = solara.cdn.replace(/\/$/, '');
|
|
249
250
|
// keep this for backwards compatibility
|
|
250
251
|
window.solara_cdn = solara.cdn;
|
|
251
|
-
console.log("
|
|
252
|
+
console.log("solara config", {rootPath: solara.rootPath, jupyterRootPath: solara.jupyterRootPath, cdn: solara.cdn});
|
|
252
253
|
|
|
253
254
|
async function changeThemeCSS(theme) {
|
|
254
255
|
let css = await fetch(`${solara.rootPath}/static/assets/theme-${theme}.css`).then(r => r.text());
|
|
@@ -269,6 +270,12 @@
|
|
|
269
270
|
solara.production = {{ production | tojson | safe }};
|
|
270
271
|
const themeVariants = ['light', 'dark', 'auto']
|
|
271
272
|
solara.preRendered = {{ pre_rendered_html | safe | length | tojson }} > 0
|
|
273
|
+
if(solara.preRendered) {
|
|
274
|
+
document.body.classList.add('solara-ssg', 'solara-pre-rendered');
|
|
275
|
+
} else {
|
|
276
|
+
document.body.classList.add('solara-no-ssg');
|
|
277
|
+
}
|
|
278
|
+
|
|
272
279
|
</script>
|
|
273
280
|
|
|
274
281
|
<script>
|
|
@@ -289,6 +296,10 @@
|
|
|
289
296
|
this.forceUpdateTrigger += 1;
|
|
290
297
|
original_$forceUpdate();
|
|
291
298
|
});
|
|
299
|
+
// in case we are showing a popop (e.g. using ipypopout), hide the banner
|
|
300
|
+
if(searchParams.has('modelid')) {
|
|
301
|
+
this.showBanner = false;
|
|
302
|
+
}
|
|
292
303
|
},
|
|
293
304
|
mounted() {
|
|
294
305
|
document.querySelector('#app').removeAttribute("style");
|
|
@@ -329,8 +340,25 @@
|
|
|
329
340
|
// if preRendered, the app is not mounted yet
|
|
330
341
|
// so we mount it when loading becomes false
|
|
331
342
|
if (solara.preRendered && !this.mounted) {
|
|
332
|
-
|
|
333
|
-
|
|
343
|
+
function waitForAnimation(element) {
|
|
344
|
+
return new Promise((resolve) => {
|
|
345
|
+
element.addEventListener('animationend', resolve, { once: true });
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function waitForAllAnimations(elements) {
|
|
350
|
+
const animationPromises = Array.from(elements).map(element => waitForAnimation(element));
|
|
351
|
+
return Promise.all(animationPromises);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
(async () => {
|
|
355
|
+
await waitForAllAnimations(document.querySelectorAll('.solara-ssg-wait-for-animation'));
|
|
356
|
+
console.log("animation finished");
|
|
357
|
+
document.body.classList.remove('solara-pre-rendered');
|
|
358
|
+
document.body.classList.add('solara-pre-rendered-finished');
|
|
359
|
+
this.isMounted = true;
|
|
360
|
+
this.$mount("#app")
|
|
361
|
+
})();
|
|
334
362
|
} else {
|
|
335
363
|
this.isMounted = true;
|
|
336
364
|
}
|
|
@@ -389,6 +417,7 @@
|
|
|
389
417
|
loadingPage: false,
|
|
390
418
|
_lastBusyTimer: null,
|
|
391
419
|
kernelBusyLong: false,
|
|
420
|
+
showBanner: theme.show_banner,
|
|
392
421
|
// outputMessages: [{ name: 'stderr', text: 'lala' }],
|
|
393
422
|
}
|
|
394
423
|
}
|
|
@@ -418,7 +447,7 @@
|
|
|
418
447
|
{% endif -%}
|
|
419
448
|
nbextensionHashes = {{ resources.nbextensions_hashes | tojson | safe }};
|
|
420
449
|
requirejs.config({
|
|
421
|
-
baseUrl: '{{
|
|
450
|
+
baseUrl: '{{jupyter_root_path}}',
|
|
422
451
|
waitSeconds: 3000,
|
|
423
452
|
map: {
|
|
424
453
|
'*': {
|
|
@@ -443,7 +472,7 @@
|
|
|
443
472
|
});
|
|
444
473
|
requirejs([
|
|
445
474
|
{% for ext in resources.nbextensions if ext != 'jupyter-vuetify/extension' and ext != 'jupyter-vue/extension' -%}
|
|
446
|
-
"{{
|
|
475
|
+
"{{jupyter_root_path}}/nbextensions/{{ ext }}.js",
|
|
447
476
|
{% endfor %}
|
|
448
477
|
]);
|
|
449
478
|
(async function () {
|
solara/website/assets/custom.css
CHANGED
|
@@ -34,6 +34,17 @@
|
|
|
34
34
|
--docs-social-twitter: #000000;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
.theme--dark {
|
|
38
|
+
--color-border-appbar: hsla(0,0%,100%,.12);
|
|
39
|
+
--color-material-background: #1e1e1e;
|
|
40
|
+
--color-text-fade: hsla(0,0%,100%,.7);
|
|
41
|
+
}
|
|
42
|
+
.theme--light {
|
|
43
|
+
--color-border-appbar: rgba(0,0,0,.12);
|
|
44
|
+
--color-material-background: #fff;
|
|
45
|
+
--color-text-fade: rgba(0,0,0,.6);
|
|
46
|
+
}
|
|
47
|
+
|
|
37
48
|
html,
|
|
38
49
|
body,
|
|
39
50
|
p,
|
|
@@ -199,9 +210,8 @@ blockquote p:last-child {
|
|
|
199
210
|
}
|
|
200
211
|
.v-sheet.docs-card-container{
|
|
201
212
|
width: 100%;
|
|
202
|
-
padding: 0 10%;
|
|
203
213
|
justify-content: start;
|
|
204
|
-
align-content:
|
|
214
|
+
align-content: stretch;
|
|
205
215
|
flex-wrap: wrap !important;
|
|
206
216
|
flex-grow: 1;
|
|
207
217
|
row-gap: 20px;
|
|
@@ -211,11 +221,14 @@ blockquote p:last-child {
|
|
|
211
221
|
}
|
|
212
222
|
.v-sheet.docs-card-container > a > .docs-card{
|
|
213
223
|
display: flex;
|
|
214
|
-
|
|
215
|
-
height:
|
|
224
|
+
flex-grow: 1;
|
|
225
|
+
height: 4rem;
|
|
216
226
|
border-radius: 16px !important;
|
|
217
227
|
color: var(--color-grey-light);
|
|
218
228
|
}
|
|
229
|
+
.v-sheet.docs-card-container > a > .docs-card > h2 {
|
|
230
|
+
padding: 0 24px;
|
|
231
|
+
}
|
|
219
232
|
.v-sheet.docs-card-container > a .docs-card-icon{
|
|
220
233
|
padding-right: 1.5rem;
|
|
221
234
|
transition: ease padding 0.25s;
|
|
@@ -274,6 +287,8 @@ blockquote p:last-child {
|
|
|
274
287
|
/* header */
|
|
275
288
|
.header-logo {
|
|
276
289
|
display: block;
|
|
290
|
+
height: 100%;
|
|
291
|
+
margin: 0;
|
|
277
292
|
}
|
|
278
293
|
.news {
|
|
279
294
|
background-color: var(--color-primary);
|
|
@@ -289,46 +304,6 @@ blockquote p:last-child {
|
|
|
289
304
|
color: white !important;
|
|
290
305
|
}
|
|
291
306
|
|
|
292
|
-
/* hero */
|
|
293
|
-
.hero {
|
|
294
|
-
background: var(--color-primary-lightest) url(https://dxhl76zpt6fap.cloudfront.net/public/hero.webp);
|
|
295
|
-
background-size: cover;
|
|
296
|
-
min-height: 660px;
|
|
297
|
-
padding: 7rem 0;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
.theme--dark .hero {
|
|
301
|
-
background: var(--dark-color-primary-lightest) url(https://dxhl76zpt6fap.cloudfront.net/public/hero.webp);
|
|
302
|
-
background-size: cover;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
.hero h1 {
|
|
306
|
-
font-size: 4.5rem;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
.hero h2 {
|
|
310
|
-
padding-left: 150px;
|
|
311
|
-
padding-right: 150px;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
.hero b {
|
|
315
|
-
color: var(--color-primary);
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
.theme--dark .hero b {
|
|
319
|
-
color: var(--dark-color-primary);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
@media screen and (max-width: 960px) {
|
|
323
|
-
.hero {
|
|
324
|
-
padding: 4rem 0;
|
|
325
|
-
min-height: auto;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
.hero h1 {
|
|
329
|
-
font-size: 3rem;
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
307
|
|
|
333
308
|
/* fixes padding issue on drawer menu */
|
|
334
309
|
.v-list-group__items {
|
|
@@ -362,7 +337,7 @@ blockquote p:last-child {
|
|
|
362
337
|
|
|
363
338
|
.menu li a:link {
|
|
364
339
|
display: block;
|
|
365
|
-
padding:
|
|
340
|
+
padding: 0.75rem;
|
|
366
341
|
}
|
|
367
342
|
|
|
368
343
|
.menu .active {
|
|
@@ -427,11 +402,6 @@ blockquote p:last-child {
|
|
|
427
402
|
font-weight: bolder;
|
|
428
403
|
}
|
|
429
404
|
|
|
430
|
-
.solara-autorouter-content {
|
|
431
|
-
padding-top: 2rem;
|
|
432
|
-
max-width: 100%;
|
|
433
|
-
}
|
|
434
|
-
|
|
435
405
|
.jp-RenderedHTMLCommon {
|
|
436
406
|
padding-right: 0;
|
|
437
407
|
}
|
|
@@ -457,13 +427,6 @@ blockquote p:last-child {
|
|
|
457
427
|
/* MOBILE */
|
|
458
428
|
|
|
459
429
|
@media screen and (aspect-ratio < 1) {
|
|
460
|
-
.hero{
|
|
461
|
-
background-position-x: 29%;
|
|
462
|
-
}
|
|
463
|
-
.hero h2{
|
|
464
|
-
padding-left: 0;
|
|
465
|
-
padding-right: 0;
|
|
466
|
-
}
|
|
467
430
|
.container.fill-height > .ma-8.row{
|
|
468
431
|
max-width: 80%;
|
|
469
432
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from .docs import CategoryLayout, Gallery, NoPage, SubCategoryLayout, WithCode # noqa
|
|
2
2
|
from .header import Header
|
|
3
|
-
from .
|
|
3
|
+
from .markdown import MarkdownWithMetadata
|
|
4
4
|
|
|
5
|
-
__all__ = ["Header", "
|
|
5
|
+
__all__ = ["Header", "MarkdownWithMetadata", "CategoryLayout", "Gallery", "NoPage", "SubCategoryLayout", "WithCode"]
|
|
@@ -7,14 +7,19 @@
|
|
|
7
7
|
v-model="query"
|
|
8
8
|
prepend-inner-icon="mdi-magnify"
|
|
9
9
|
hide-details
|
|
10
|
+
dense
|
|
10
11
|
:placeholder="mac ? '⌘K to search' : 'Ctrl+K to search'"
|
|
12
|
+
filled
|
|
11
13
|
outlined
|
|
12
|
-
rounded
|
|
13
14
|
clearable
|
|
14
15
|
ref="search"
|
|
15
16
|
style="flex-grow: 1; max-width: 650px;"
|
|
16
17
|
@click="show($event, on);"
|
|
17
|
-
@keyup.enter="item = 0"
|
|
18
|
+
@keyup.enter="hoverItem === null ? item = 0 : item = hoverItem"
|
|
19
|
+
@keyup.esc="close();"
|
|
20
|
+
@keyup.down="hoverItem = hoverItem == null ? 0 : Math.min(hoverItem + 1, results.hits.length - 1, 9)"
|
|
21
|
+
@keyup.up="hoverItem = hoverItem == null ? 9 : Math.max(hoverItem - 1, 0)"
|
|
22
|
+
@focus="selectText();"
|
|
18
23
|
class="algolia"
|
|
19
24
|
></v-text-field>
|
|
20
25
|
</template>
|
|
@@ -27,7 +32,7 @@
|
|
|
27
32
|
</v-list>
|
|
28
33
|
<v-list v-else :style="{width: menuWidth + 'px'}">
|
|
29
34
|
<v-list-item-group v-model="item">
|
|
30
|
-
<v-list-item v-for="(element, index) in this.results.hits" :key="element['url']">
|
|
35
|
+
<v-list-item v-for="(element, index) in this.results.hits" :key="element['url']" :input-value="index === hoverItem">
|
|
31
36
|
<v-list-item-content>
|
|
32
37
|
<v-list-item-title>
|
|
33
38
|
{{ element.hierarchy.lvl1 }}
|
|
@@ -39,7 +44,7 @@
|
|
|
39
44
|
</v-list-item-content>
|
|
40
45
|
</v-list-item>
|
|
41
46
|
</v-list-item-group>
|
|
42
|
-
<v-list-item>
|
|
47
|
+
<v-list-item v-if="this.results.nbHits > 10">
|
|
43
48
|
<v-list-item-content>
|
|
44
49
|
<v-list-item-title>And {{ this.results.nbHits - 10}} More...</v-list-item-title>
|
|
45
50
|
</v-list-item-content>
|
|
@@ -58,7 +63,6 @@ module.exports = {
|
|
|
58
63
|
window.search = this;
|
|
59
64
|
this.updateMenuWidth();
|
|
60
65
|
window.addEventListener('resize', this.updateMenuWidth);
|
|
61
|
-
|
|
62
66
|
},
|
|
63
67
|
watch: {
|
|
64
68
|
query ( value ) {
|
|
@@ -66,12 +70,17 @@ module.exports = {
|
|
|
66
70
|
this.search();
|
|
67
71
|
},
|
|
68
72
|
item ( value ) {
|
|
69
|
-
if (
|
|
73
|
+
if ( value === null ) return;
|
|
74
|
+
if ( this.results.hits != null && this.results.hits.length >= value ) {
|
|
70
75
|
let url = this.results.hits[value].url;
|
|
71
76
|
if (url.startsWith("https://solara.dev")) {
|
|
72
77
|
url = url.slice(18);
|
|
73
78
|
}
|
|
74
79
|
solara.router.push( url );
|
|
80
|
+
this.close();
|
|
81
|
+
// reset the search
|
|
82
|
+
this.item = null;
|
|
83
|
+
this.hoverItem = null;
|
|
75
84
|
}
|
|
76
85
|
},
|
|
77
86
|
},
|
|
@@ -82,6 +91,10 @@ module.exports = {
|
|
|
82
91
|
on.click( e )
|
|
83
92
|
} );
|
|
84
93
|
},
|
|
94
|
+
close() {
|
|
95
|
+
this.show_results = false;
|
|
96
|
+
this.$refs.search.blur();
|
|
97
|
+
},
|
|
85
98
|
initSearch() {
|
|
86
99
|
this.client = this.algoliasearch( '9KW9L7O5EQ', '647ca12ba642437cc40c2adee4a78d08' );
|
|
87
100
|
this.index = this.client.initIndex( 'solara' );
|
|
@@ -107,6 +120,9 @@ module.exports = {
|
|
|
107
120
|
this.$set(this.results, []);
|
|
108
121
|
}
|
|
109
122
|
},
|
|
123
|
+
selectText() {
|
|
124
|
+
this.$refs.search.$refs.input.select();
|
|
125
|
+
},
|
|
110
126
|
getSnippet( element ) {
|
|
111
127
|
if (element.type == "content") {
|
|
112
128
|
return element._highlightResult.content.value;
|
|
@@ -176,6 +192,7 @@ module.exports = {
|
|
|
176
192
|
query: '',
|
|
177
193
|
results: [],
|
|
178
194
|
item: null,
|
|
195
|
+
hoverItem: null,
|
|
179
196
|
show_results: false,
|
|
180
197
|
mac: false,
|
|
181
198
|
menuWidth: 0,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
import solara
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@solara.component
|
|
6
|
+
def BreadCrumbs():
|
|
7
|
+
router = solara.use_router()
|
|
8
|
+
routes = router.path_routes
|
|
9
|
+
|
|
10
|
+
with solara.Row(style={"align-items": "center", "flex-wrap": "wrap"}) as main:
|
|
11
|
+
for i, route in enumerate(routes):
|
|
12
|
+
if i == len(routes) - 1:
|
|
13
|
+
solara.Text(route.label or route.path, style={"color": "var(--color-text-fade)"})
|
|
14
|
+
else:
|
|
15
|
+
with solara.Link(solara.resolve_path(route), style={"color": "var(--color-text-fade)"}):
|
|
16
|
+
solara.Text(route.label or route.path)
|
|
17
|
+
if i != len(routes) - 1:
|
|
18
|
+
solara.Text("/", style={"font-size": "1.5rem", "color": "var(--color-text-fade)"})
|
|
19
|
+
return main
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _resolve_path_to_route(path_to_find: List[str], all_routes: List[solara.Route], routes: List[solara.Route] = []):
|
|
23
|
+
if len(path_to_find) == 0:
|
|
24
|
+
return routes
|
|
25
|
+
for route in all_routes:
|
|
26
|
+
if path_to_find[0] == route.path:
|
|
27
|
+
routes += [route]
|
|
28
|
+
return _resolve_path_to_route(path_to_find[1:], route.children, routes)
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import requests
|
|
4
|
+
|
|
5
|
+
from typing import Any, Dict, Optional
|
|
6
|
+
import solara
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
postmark_api_key = None
|
|
10
|
+
contact_email_address = None
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
postmark_api_key = os.environ["POSTMARK_API_KEY"]
|
|
14
|
+
contact_email_address = os.environ["SOLARA_CONTACT_EMAIL_ADDRESS"]
|
|
15
|
+
except Exception:
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@solara.component
|
|
20
|
+
def Contact(
|
|
21
|
+
style: Dict[str, Any] = {},
|
|
22
|
+
title="Contact Us",
|
|
23
|
+
subtitle="We'd love to hear from you!",
|
|
24
|
+
submit_label="Submit",
|
|
25
|
+
email_subject="Contact Form Submission",
|
|
26
|
+
):
|
|
27
|
+
first_name = solara.use_reactive("")
|
|
28
|
+
last_name = solara.use_reactive("")
|
|
29
|
+
email = solara.use_reactive("")
|
|
30
|
+
company = solara.use_reactive("")
|
|
31
|
+
message = solara.use_reactive("")
|
|
32
|
+
error: solara.Reactive[Optional[str]] = solara.use_reactive(None)
|
|
33
|
+
success = solara.use_reactive(False)
|
|
34
|
+
|
|
35
|
+
def send(*_ignore):
|
|
36
|
+
if postmark_api_key is None or contact_email_address is None:
|
|
37
|
+
error.set("Email service not properly configured. Please contact the site administrator at solara@widgetti.io.")
|
|
38
|
+
elif not first_name.value or not last_name.value or not email.value or not message.value:
|
|
39
|
+
error.set("Please fill out all required fields.")
|
|
40
|
+
else:
|
|
41
|
+
# Create the email content
|
|
42
|
+
msg = {}
|
|
43
|
+
msg["From"] = contact_email_address
|
|
44
|
+
msg["To"] = contact_email_address
|
|
45
|
+
msg["Subject"] = email_subject
|
|
46
|
+
msg["ReplyTo"] = email.value
|
|
47
|
+
|
|
48
|
+
# Email body
|
|
49
|
+
msg["HtmlBody"] = f"""
|
|
50
|
+
<b>First Name</b>: {first_name.value}<br />
|
|
51
|
+
<b>Last Name</b>: {last_name.value}<br />
|
|
52
|
+
<b>Email</b>: {email.value}<br />
|
|
53
|
+
<b>Company</b>: {company.value}<br />
|
|
54
|
+
<b>Message</b>: {message.value}<br />
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
# Send emails
|
|
58
|
+
try:
|
|
59
|
+
requests.post(
|
|
60
|
+
"https://api.postmarkapp.com/email",
|
|
61
|
+
headers={
|
|
62
|
+
"Accept": "application/json",
|
|
63
|
+
"Content-Type": "application/json",
|
|
64
|
+
"X-Postmark-Server-Token": postmark_api_key,
|
|
65
|
+
},
|
|
66
|
+
data=json.dumps(msg),
|
|
67
|
+
)
|
|
68
|
+
requests.post(
|
|
69
|
+
"https://api.postmarkapp.com/email",
|
|
70
|
+
headers={
|
|
71
|
+
"Accept": "application/json",
|
|
72
|
+
"Content-Type": "application/json",
|
|
73
|
+
"X-Postmark-Server-Token": postmark_api_key,
|
|
74
|
+
},
|
|
75
|
+
data=json.dumps(
|
|
76
|
+
{
|
|
77
|
+
"From": contact_email_address,
|
|
78
|
+
"To": email.value,
|
|
79
|
+
"Subject": "Thank you for contacting Solara",
|
|
80
|
+
"HtmlBody": f"""
|
|
81
|
+
<p>Hi {first_name.value},</p>
|
|
82
|
+
<p>Thank you for contacting us! We will get back to you as soon as possible.</p>
|
|
83
|
+
<p>Best regards,<br />The Solara Team</p>
|
|
84
|
+
""",
|
|
85
|
+
}
|
|
86
|
+
),
|
|
87
|
+
)
|
|
88
|
+
except Exception as e:
|
|
89
|
+
error.set(f"Error sending email: {e}")
|
|
90
|
+
else:
|
|
91
|
+
success.set(True)
|
|
92
|
+
error.set(None)
|
|
93
|
+
first_name.set("")
|
|
94
|
+
last_name.set("")
|
|
95
|
+
email.set("")
|
|
96
|
+
company.set("")
|
|
97
|
+
message.set("")
|
|
98
|
+
|
|
99
|
+
with solara.Card(title=title, style={"width": "100%", "max-width": "1024px", **style}):
|
|
100
|
+
solara.Markdown(subtitle)
|
|
101
|
+
solara.Text("* Required fields")
|
|
102
|
+
with solara.Row():
|
|
103
|
+
solara.InputText(label="First Name *", value=first_name)
|
|
104
|
+
solara.InputText(label="Last Name *", value=last_name)
|
|
105
|
+
with solara.Row():
|
|
106
|
+
solara.InputText(label="Email *", value=email)
|
|
107
|
+
solara.InputText(label="Company", value=company)
|
|
108
|
+
solara.v.Textarea(placeholder="Message *", v_model=message.value, on_v_model=message.set)
|
|
109
|
+
with solara.CardActions():
|
|
110
|
+
solara.Button(label=submit_label, color="primary", on_click=send)
|
|
111
|
+
solara.Button(
|
|
112
|
+
label="Clear",
|
|
113
|
+
color="secondary",
|
|
114
|
+
text=True,
|
|
115
|
+
on_click=lambda: [first_name.set(""), last_name.set(""), email.set(""), company.set(""), message.set("")],
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
solara.Style(
|
|
119
|
+
"""
|
|
120
|
+
.v-snack__wrapper {
|
|
121
|
+
box-shadow: none;
|
|
122
|
+
}
|
|
123
|
+
"""
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
with solara.v.Snackbar(
|
|
127
|
+
v_model=error.value is not None,
|
|
128
|
+
timeout=5000,
|
|
129
|
+
on_v_model=lambda *_: error.set(None),
|
|
130
|
+
left=True,
|
|
131
|
+
color="error",
|
|
132
|
+
):
|
|
133
|
+
solara.Markdown(error.value or "", style={"--dark-color-text": "white", "--color-text": "white"})
|
|
134
|
+
solara.Button(icon=True, icon_name="mdi-close", color="white", on_click=lambda: error.set(None))
|
|
135
|
+
|
|
136
|
+
with solara.v.Snackbar(
|
|
137
|
+
v_model=success.value,
|
|
138
|
+
timeout=5000,
|
|
139
|
+
on_v_model=lambda *_: success.set(False),
|
|
140
|
+
left=True,
|
|
141
|
+
color="success",
|
|
142
|
+
):
|
|
143
|
+
solara.Markdown("Your message has been sent!", style={"--dark-color-text": "white", "--color-text": "white"})
|
|
144
|
+
solara.Button(icon=True, icon_name="mdi-close", color="white", on_click=lambda: success.set(False))
|