mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-28 12:31:55 -05:00
Compare commits
2 Commits
v0.6.12
...
protected-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e4872b7db | ||
|
|
6eb68a8794 |
@@ -1,7 +1,7 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
# utilities
|
||||
# utilities
|
||||
"oco",
|
||||
|
||||
# core
|
||||
|
||||
@@ -47,11 +47,11 @@ CARGO_MAKE_CRATE_WORKSPACE_MEMBERS = [
|
||||
workspace = false
|
||||
description = "Generate the list of workspace members"
|
||||
script = '''
|
||||
examples=$(ls |
|
||||
grep -v .md |
|
||||
grep -v Makefile.toml |
|
||||
grep -v cargo-make |
|
||||
grep -v gtk |
|
||||
examples=$(ls |
|
||||
grep -v .md |
|
||||
grep -v Makefile.toml |
|
||||
grep -v cargo-make |
|
||||
grep -v gtk |
|
||||
jq -R -s -c 'split("\n")[:-1]')
|
||||
echo "CARGO_MAKE_CRATE_WORKSPACE_MEMBERS = $examples"
|
||||
'''
|
||||
|
||||
@@ -8,7 +8,7 @@ leptos = { path = "../../leptos", features = ["csr"] }
|
||||
leptos_meta = { path = "../../meta", features = ["csr"] }
|
||||
leptos_router = { path = "../../router", features = ["csr"] }
|
||||
log = "0.4"
|
||||
gloo-net = { version = "0.5", features = ["http"] }
|
||||
gloo-net = { version = "0.2", features = ["http"] }
|
||||
|
||||
# dependencies for client (enable when csr or hydrate set)
|
||||
wasm-bindgen = { version = "0.2" }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
extend = [
|
||||
{ path = "../cargo-make/main.toml" },
|
||||
{ path = "../cargo-make/trunk_server.toml" },
|
||||
{ path = "../cargo-make/main.toml" },
|
||||
{ path = "../cargo-make/trunk_server.toml" },
|
||||
]
|
||||
|
||||
4
examples/tailwind_csr/Trunk.toml
Normal file
4
examples/tailwind_csr/Trunk.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
[[hooks]]
|
||||
stage = "pre_build"
|
||||
command = "sh"
|
||||
command_arguments = ["-c", "npx tailwindcss -i input.css -o style/output.css"]
|
||||
@@ -1,12 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link data-trunk rel="rust" data-wasm-opt="z" />
|
||||
<link data-trunk rel="icon" type="image/ico" href="/public/favicon.ico" />
|
||||
<link data-trunk rel="tailwind-css" href="/style/tailwind.css" />
|
||||
<link data-trunk rel="css" href="/style/output.css" />
|
||||
<title>Leptos • Counter with Tailwind</title>
|
||||
</head>
|
||||
</head>
|
||||
|
||||
<body></body>
|
||||
|
||||
<body></body>
|
||||
</html>
|
||||
|
||||
604
examples/tailwind_csr/style/output.css
Normal file
604
examples/tailwind_csr/style/output.css
Normal file
@@ -0,0 +1,604 @@
|
||||
/*
|
||||
! tailwindcss v3.3.3 | MIT License | https://tailwindcss.com
|
||||
*/
|
||||
|
||||
/*
|
||||
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
|
||||
2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
|
||||
*/
|
||||
|
||||
*,
|
||||
::before,
|
||||
::after {
|
||||
box-sizing: border-box;
|
||||
/* 1 */
|
||||
border-width: 0;
|
||||
/* 2 */
|
||||
border-style: solid;
|
||||
/* 2 */
|
||||
border-color: #e5e7eb;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
::before,
|
||||
::after {
|
||||
--tw-content: '';
|
||||
}
|
||||
|
||||
/*
|
||||
1. Use a consistent sensible line-height in all browsers.
|
||||
2. Prevent adjustments of font size after orientation changes in iOS.
|
||||
3. Use a more readable tab size.
|
||||
4. Use the user's configured `sans` font-family by default.
|
||||
5. Use the user's configured `sans` font-feature-settings by default.
|
||||
6. Use the user's configured `sans` font-variation-settings by default.
|
||||
*/
|
||||
|
||||
html {
|
||||
line-height: 1.5;
|
||||
/* 1 */
|
||||
-webkit-text-size-adjust: 100%;
|
||||
/* 2 */
|
||||
-moz-tab-size: 4;
|
||||
/* 3 */
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4;
|
||||
/* 3 */
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
/* 4 */
|
||||
font-feature-settings: normal;
|
||||
/* 5 */
|
||||
font-variation-settings: normal;
|
||||
/* 6 */
|
||||
}
|
||||
|
||||
/*
|
||||
1. Remove the margin in all browsers.
|
||||
2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
/* 1 */
|
||||
line-height: inherit;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
1. Add the correct height in Firefox.
|
||||
2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
|
||||
3. Ensure horizontal rules are visible by default.
|
||||
*/
|
||||
|
||||
hr {
|
||||
height: 0;
|
||||
/* 1 */
|
||||
color: inherit;
|
||||
/* 2 */
|
||||
border-top-width: 1px;
|
||||
/* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct text decoration in Chrome, Edge, and Safari.
|
||||
*/
|
||||
|
||||
abbr:where([title]) {
|
||||
-webkit-text-decoration: underline dotted;
|
||||
text-decoration: underline dotted;
|
||||
}
|
||||
|
||||
/*
|
||||
Remove the default font size and weight for headings.
|
||||
*/
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-size: inherit;
|
||||
font-weight: inherit;
|
||||
}
|
||||
|
||||
/*
|
||||
Reset links to optimize for opt-in styling instead of opt-out.
|
||||
*/
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct font weight in Edge and Safari.
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Use the user's configured `mono` font family by default.
|
||||
2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
code,
|
||||
kbd,
|
||||
samp,
|
||||
pre {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
/* 1 */
|
||||
font-size: 1em;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct font size in all browsers.
|
||||
*/
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/*
|
||||
Prevent `sub` and `sup` elements from affecting the line height in all browsers.
|
||||
*/
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
|
||||
2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
|
||||
3. Remove gaps between table borders by default.
|
||||
*/
|
||||
|
||||
table {
|
||||
text-indent: 0;
|
||||
/* 1 */
|
||||
border-color: inherit;
|
||||
/* 2 */
|
||||
border-collapse: collapse;
|
||||
/* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
1. Change the font styles in all browsers.
|
||||
2. Remove the margin in Firefox and Safari.
|
||||
3. Remove default padding in all browsers.
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
font-family: inherit;
|
||||
/* 1 */
|
||||
font-feature-settings: inherit;
|
||||
/* 1 */
|
||||
font-variation-settings: inherit;
|
||||
/* 1 */
|
||||
font-size: 100%;
|
||||
/* 1 */
|
||||
font-weight: inherit;
|
||||
/* 1 */
|
||||
line-height: inherit;
|
||||
/* 1 */
|
||||
color: inherit;
|
||||
/* 1 */
|
||||
margin: 0;
|
||||
/* 2 */
|
||||
padding: 0;
|
||||
/* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
Remove the inheritance of text transform in Edge and Firefox.
|
||||
*/
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Correct the inability to style clickable types in iOS and Safari.
|
||||
2. Remove default button styles.
|
||||
*/
|
||||
|
||||
button,
|
||||
[type='button'],
|
||||
[type='reset'],
|
||||
[type='submit'] {
|
||||
-webkit-appearance: button;
|
||||
/* 1 */
|
||||
background-color: transparent;
|
||||
/* 2 */
|
||||
background-image: none;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Use the modern Firefox focus style for all focusable elements.
|
||||
*/
|
||||
|
||||
:-moz-focusring {
|
||||
outline: auto;
|
||||
}
|
||||
|
||||
/*
|
||||
Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
|
||||
*/
|
||||
|
||||
:-moz-ui-invalid {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct vertical alignment in Chrome and Firefox.
|
||||
*/
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
/*
|
||||
Correct the cursor style of increment and decrement buttons in Safari.
|
||||
*/
|
||||
|
||||
::-webkit-inner-spin-button,
|
||||
::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Correct the odd appearance in Chrome and Safari.
|
||||
2. Correct the outline style in Safari.
|
||||
*/
|
||||
|
||||
[type='search'] {
|
||||
-webkit-appearance: textfield;
|
||||
/* 1 */
|
||||
outline-offset: -2px;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Remove the inner padding in Chrome and Safari on macOS.
|
||||
*/
|
||||
|
||||
::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Correct the inability to style clickable types in iOS and Safari.
|
||||
2. Change font properties to `inherit` in Safari.
|
||||
*/
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
-webkit-appearance: button;
|
||||
/* 1 */
|
||||
font: inherit;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct display in Chrome and Safari.
|
||||
*/
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
/*
|
||||
Removes the default spacing and border for appropriate elements.
|
||||
*/
|
||||
|
||||
blockquote,
|
||||
dl,
|
||||
dd,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
hr,
|
||||
figure,
|
||||
p,
|
||||
pre {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
legend {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul,
|
||||
menu {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
Reset default styling for dialogs.
|
||||
*/
|
||||
|
||||
dialog {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
Prevent resizing textareas horizontally by default.
|
||||
*/
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
|
||||
2. Set the default placeholder color to the user's configured gray 400 color.
|
||||
*/
|
||||
|
||||
input::-moz-placeholder, textarea::-moz-placeholder {
|
||||
opacity: 1;
|
||||
/* 1 */
|
||||
color: #9ca3af;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
input::placeholder,
|
||||
textarea::placeholder {
|
||||
opacity: 1;
|
||||
/* 1 */
|
||||
color: #9ca3af;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Set the default cursor for buttons.
|
||||
*/
|
||||
|
||||
button,
|
||||
[role="button"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/*
|
||||
Make sure disabled buttons don't get the pointer cursor.
|
||||
*/
|
||||
|
||||
:disabled {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
|
||||
2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
|
||||
This can trigger a poorly considered lint error in some tools but is included by design.
|
||||
*/
|
||||
|
||||
img,
|
||||
svg,
|
||||
video,
|
||||
canvas,
|
||||
audio,
|
||||
iframe,
|
||||
embed,
|
||||
object {
|
||||
display: block;
|
||||
/* 1 */
|
||||
vertical-align: middle;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
|
||||
*/
|
||||
|
||||
img,
|
||||
video {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/* Make elements with the HTML hidden attribute stay hidden by default */
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
*, ::before, ::after {
|
||||
--tw-border-spacing-x: 0;
|
||||
--tw-border-spacing-y: 0;
|
||||
--tw-translate-x: 0;
|
||||
--tw-translate-y: 0;
|
||||
--tw-rotate: 0;
|
||||
--tw-skew-x: 0;
|
||||
--tw-skew-y: 0;
|
||||
--tw-scale-x: 1;
|
||||
--tw-scale-y: 1;
|
||||
--tw-pan-x: ;
|
||||
--tw-pan-y: ;
|
||||
--tw-pinch-zoom: ;
|
||||
--tw-scroll-snap-strictness: proximity;
|
||||
--tw-gradient-from-position: ;
|
||||
--tw-gradient-via-position: ;
|
||||
--tw-gradient-to-position: ;
|
||||
--tw-ordinal: ;
|
||||
--tw-slashed-zero: ;
|
||||
--tw-numeric-figure: ;
|
||||
--tw-numeric-spacing: ;
|
||||
--tw-numeric-fraction: ;
|
||||
--tw-ring-inset: ;
|
||||
--tw-ring-offset-width: 0px;
|
||||
--tw-ring-offset-color: #fff;
|
||||
--tw-ring-color: rgb(59 130 246 / 0.5);
|
||||
--tw-ring-offset-shadow: 0 0 #0000;
|
||||
--tw-ring-shadow: 0 0 #0000;
|
||||
--tw-shadow: 0 0 #0000;
|
||||
--tw-shadow-colored: 0 0 #0000;
|
||||
--tw-blur: ;
|
||||
--tw-brightness: ;
|
||||
--tw-contrast: ;
|
||||
--tw-grayscale: ;
|
||||
--tw-hue-rotate: ;
|
||||
--tw-invert: ;
|
||||
--tw-saturate: ;
|
||||
--tw-sepia: ;
|
||||
--tw-drop-shadow: ;
|
||||
--tw-backdrop-blur: ;
|
||||
--tw-backdrop-brightness: ;
|
||||
--tw-backdrop-contrast: ;
|
||||
--tw-backdrop-grayscale: ;
|
||||
--tw-backdrop-hue-rotate: ;
|
||||
--tw-backdrop-invert: ;
|
||||
--tw-backdrop-opacity: ;
|
||||
--tw-backdrop-saturate: ;
|
||||
--tw-backdrop-sepia: ;
|
||||
}
|
||||
|
||||
::backdrop {
|
||||
--tw-border-spacing-x: 0;
|
||||
--tw-border-spacing-y: 0;
|
||||
--tw-translate-x: 0;
|
||||
--tw-translate-y: 0;
|
||||
--tw-rotate: 0;
|
||||
--tw-skew-x: 0;
|
||||
--tw-skew-y: 0;
|
||||
--tw-scale-x: 1;
|
||||
--tw-scale-y: 1;
|
||||
--tw-pan-x: ;
|
||||
--tw-pan-y: ;
|
||||
--tw-pinch-zoom: ;
|
||||
--tw-scroll-snap-strictness: proximity;
|
||||
--tw-gradient-from-position: ;
|
||||
--tw-gradient-via-position: ;
|
||||
--tw-gradient-to-position: ;
|
||||
--tw-ordinal: ;
|
||||
--tw-slashed-zero: ;
|
||||
--tw-numeric-figure: ;
|
||||
--tw-numeric-spacing: ;
|
||||
--tw-numeric-fraction: ;
|
||||
--tw-ring-inset: ;
|
||||
--tw-ring-offset-width: 0px;
|
||||
--tw-ring-offset-color: #fff;
|
||||
--tw-ring-color: rgb(59 130 246 / 0.5);
|
||||
--tw-ring-offset-shadow: 0 0 #0000;
|
||||
--tw-ring-shadow: 0 0 #0000;
|
||||
--tw-shadow: 0 0 #0000;
|
||||
--tw-shadow-colored: 0 0 #0000;
|
||||
--tw-blur: ;
|
||||
--tw-brightness: ;
|
||||
--tw-contrast: ;
|
||||
--tw-grayscale: ;
|
||||
--tw-hue-rotate: ;
|
||||
--tw-invert: ;
|
||||
--tw-saturate: ;
|
||||
--tw-sepia: ;
|
||||
--tw-drop-shadow: ;
|
||||
--tw-backdrop-blur: ;
|
||||
--tw-backdrop-brightness: ;
|
||||
--tw-backdrop-contrast: ;
|
||||
--tw-backdrop-grayscale: ;
|
||||
--tw-backdrop-hue-rotate: ;
|
||||
--tw-backdrop-invert: ;
|
||||
--tw-backdrop-opacity: ;
|
||||
--tw-backdrop-saturate: ;
|
||||
--tw-backdrop-sepia: ;
|
||||
}
|
||||
|
||||
.mx-auto {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.my-0 {
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.max-w-3xl {
|
||||
max-width: 48rem;
|
||||
}
|
||||
|
||||
.rounded-lg {
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.bg-amber-600 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(217 119 6 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.p-6 {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.px-10 {
|
||||
padding-left: 2.5rem;
|
||||
padding-right: 2.5rem;
|
||||
}
|
||||
|
||||
.px-5 {
|
||||
padding-left: 1.25rem;
|
||||
padding-right: 1.25rem;
|
||||
}
|
||||
|
||||
.py-3 {
|
||||
padding-top: 0.75rem;
|
||||
padding-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.pb-10 {
|
||||
padding-bottom: 2.5rem;
|
||||
}
|
||||
|
||||
.text-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.text-4xl {
|
||||
font-size: 2.25rem;
|
||||
line-height: 2.5rem;
|
||||
}
|
||||
|
||||
.text-white {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(255 255 255 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.hover\:bg-sky-700:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(3 105 161 / var(--tw-bg-opacity));
|
||||
}
|
||||
@@ -16,7 +16,7 @@ leptos_macro = { workspace = true }
|
||||
leptos_reactive = { workspace = true }
|
||||
leptos_server = { workspace = true }
|
||||
leptos_config = { workspace = true }
|
||||
leptos-spin-macro = { version = "0.2", optional = true }
|
||||
leptos-spin-macro = { version = "0.1", optional = true }
|
||||
tracing = "0.1"
|
||||
typed-builder = "0.18"
|
||||
typed-builder-macro = "0.18"
|
||||
|
||||
@@ -31,12 +31,10 @@ use std::cell::Cell;
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[cfg_attr(not(debug_assertions), repr(transparent))]
|
||||
pub struct NodeRef<T: ElementDescriptor + 'static> {
|
||||
element: RwSignal<Option<HtmlElement<T>>>,
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: &'static std::panic::Location<'static>,
|
||||
}
|
||||
#[repr(transparent)]
|
||||
pub struct NodeRef<T: ElementDescriptor + 'static>(
|
||||
RwSignal<Option<HtmlElement<T>>>,
|
||||
);
|
||||
|
||||
/// Creates a shared reference to a DOM node created while using the `view`
|
||||
/// macro to create your UI.
|
||||
@@ -67,14 +65,9 @@ pub struct NodeRef<T: ElementDescriptor + 'static> {
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[track_caller]
|
||||
#[inline(always)]
|
||||
pub fn create_node_ref<T: ElementDescriptor + 'static>() -> NodeRef<T> {
|
||||
NodeRef {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
element: create_rw_signal(None),
|
||||
}
|
||||
NodeRef(create_rw_signal(None))
|
||||
}
|
||||
|
||||
impl<T: ElementDescriptor + 'static> NodeRef<T> {
|
||||
@@ -127,7 +120,7 @@ impl<T: ElementDescriptor + 'static> NodeRef<T> {
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
self.element.get()
|
||||
self.0.get()
|
||||
}
|
||||
|
||||
/// Gets the element that is currently stored in the reference.
|
||||
@@ -139,7 +132,7 @@ impl<T: ElementDescriptor + 'static> NodeRef<T> {
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
self.element.get_untracked()
|
||||
self.0.get_untracked()
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
@@ -151,15 +144,13 @@ impl<T: ElementDescriptor + 'static> NodeRef<T> {
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
self.element.update(|current| {
|
||||
self.0.update(|current| {
|
||||
if current.is_some() {
|
||||
#[cfg(debug_assertions)]
|
||||
crate::debug_warn!(
|
||||
"You are setting the NodeRef defined at {}, which has \
|
||||
already been filled It’s possible this is intentional, \
|
||||
but it’s also possible that you’re accidentally using \
|
||||
the same NodeRef for multiple _ref attributes.",
|
||||
self.defined_at
|
||||
"You are setting a NodeRef that has already been filled. \
|
||||
It’s possible this is intentional, but it’s also \
|
||||
possible that you’re accidentally using the same NodeRef \
|
||||
for multiple _ref attributes."
|
||||
);
|
||||
}
|
||||
*current = Some(node.clone());
|
||||
|
||||
@@ -8,11 +8,10 @@ use leptos_hot_reload::parsing::value_to_string;
|
||||
use proc_macro2::{Ident, Span, TokenStream};
|
||||
use quote::{format_ident, quote, quote_spanned, ToTokens, TokenStreamExt};
|
||||
use syn::{
|
||||
parse::Parse, parse_quote, spanned::Spanned, token::Colon,
|
||||
visit_mut::VisitMut, AngleBracketedGenericArguments, Attribute, FnArg,
|
||||
GenericArgument, GenericParam, Item, ItemFn, LitStr, Meta, Pat, PatIdent,
|
||||
Path, PathArguments, ReturnType, Signature, Stmt, Type, TypeImplTrait,
|
||||
TypeParam, TypePath, Visibility,
|
||||
parse::Parse, parse_quote, spanned::Spanned,
|
||||
AngleBracketedGenericArguments, Attribute, FnArg, GenericArgument, Item,
|
||||
ItemFn, LitStr, Meta, Pat, PatIdent, Path, PathArguments, ReturnType,
|
||||
Signature, Stmt, Type, TypePath, Visibility,
|
||||
};
|
||||
|
||||
pub struct Model {
|
||||
@@ -29,7 +28,6 @@ pub struct Model {
|
||||
impl Parse for Model {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
let mut item = ItemFn::parse(input)?;
|
||||
convert_impl_trait_to_generic(&mut item.sig);
|
||||
|
||||
let docs = Docs::new(&item.attrs);
|
||||
|
||||
@@ -180,18 +178,6 @@ impl ToTokens for Model {
|
||||
);
|
||||
|
||||
let component_fn_prop_docs = generate_component_fn_prop_docs(props);
|
||||
let docs_and_prop_docs = if component_fn_prop_docs.is_empty() {
|
||||
// Avoid generating an empty doc line in case the component has no doc and no props.
|
||||
quote! {
|
||||
#docs
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
#docs
|
||||
#[doc = ""]
|
||||
#component_fn_prop_docs
|
||||
}
|
||||
};
|
||||
|
||||
let (
|
||||
tracing_instrument_attr,
|
||||
@@ -516,7 +502,9 @@ impl ToTokens for Model {
|
||||
let output = quote! {
|
||||
#[doc = #builder_name_doc]
|
||||
#[doc = ""]
|
||||
#docs_and_prop_docs
|
||||
#docs
|
||||
#[doc = ""]
|
||||
#component_fn_prop_docs
|
||||
#[derive(::leptos::typed_builder_macro::TypedBuilder #props_derive_serialize)]
|
||||
//#[builder(doc)]
|
||||
#[builder(crate_module_path=::leptos::typed_builder)]
|
||||
@@ -560,7 +548,9 @@ impl ToTokens for Model {
|
||||
|
||||
#into_view
|
||||
|
||||
#docs_and_prop_docs
|
||||
#docs
|
||||
#[doc = ""]
|
||||
#component_fn_prop_docs
|
||||
#[allow(non_snake_case, clippy::too_many_arguments)]
|
||||
#[allow(clippy::needless_lifetimes)]
|
||||
#tracing_instrument_attr
|
||||
@@ -1231,57 +1221,3 @@ fn is_valid_into_view_return_type(ty: &ReturnType) -> bool {
|
||||
pub fn unmodified_fn_name_from_fn_name(ident: &Ident) -> Ident {
|
||||
Ident::new(&format!("__{ident}"), ident.span())
|
||||
}
|
||||
|
||||
/// Converts all `impl Trait`s in a function signature to use generic params instead.
|
||||
fn convert_impl_trait_to_generic(sig: &mut Signature) {
|
||||
fn new_generic_ident(i: usize, span: Span) -> Ident {
|
||||
Ident::new(&format!("__ImplTrait{}", i), span)
|
||||
}
|
||||
|
||||
// First: visit all `impl Trait`s and replace them with new generic params.
|
||||
#[derive(Default)]
|
||||
struct RemoveImplTrait(Vec<TypeImplTrait>);
|
||||
impl VisitMut for RemoveImplTrait {
|
||||
fn visit_type_mut(&mut self, ty: &mut Type) {
|
||||
syn::visit_mut::visit_type_mut(self, ty);
|
||||
if matches!(ty, Type::ImplTrait(_)) {
|
||||
let ident = new_generic_ident(self.0.len(), ty.span());
|
||||
let generic_type = Type::Path(TypePath {
|
||||
qself: None,
|
||||
path: Path::from(ident),
|
||||
});
|
||||
let Type::ImplTrait(impl_trait) =
|
||||
std::mem::replace(ty, generic_type)
|
||||
else {
|
||||
unreachable!();
|
||||
};
|
||||
self.0.push(impl_trait);
|
||||
}
|
||||
}
|
||||
|
||||
// Early exits.
|
||||
fn visit_attribute_mut(&mut self, _: &mut Attribute) {}
|
||||
fn visit_pat_mut(&mut self, _: &mut Pat) {}
|
||||
}
|
||||
let mut visitor = RemoveImplTrait::default();
|
||||
for fn_arg in sig.inputs.iter_mut() {
|
||||
visitor.visit_fn_arg_mut(fn_arg);
|
||||
}
|
||||
let RemoveImplTrait(impl_traits) = visitor;
|
||||
|
||||
// Second: Add the new generic params into the signature.
|
||||
for (i, impl_trait) in impl_traits.into_iter().enumerate() {
|
||||
let span = impl_trait.span();
|
||||
let ident = new_generic_ident(i, span);
|
||||
// We can simply append to the end (only lifetime params must be first).
|
||||
// Note currently default generics are not allowed in `fn`, so not a concern.
|
||||
sig.generics.params.push(GenericParam::Type(TypeParam {
|
||||
attrs: vec![],
|
||||
ident,
|
||||
colon_token: Some(Colon { spans: [span] }),
|
||||
bounds: impl_trait.bounds,
|
||||
eq_token: None,
|
||||
default: None,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ use syn::{
|
||||
|
||||
struct SliceMacroInput {
|
||||
root: syn::Ident,
|
||||
path: Punctuated<syn::Member, Token![.]>,
|
||||
path: Punctuated<syn::Ident, Token![.]>,
|
||||
}
|
||||
|
||||
impl Parse for SliceMacroInput {
|
||||
@@ -19,7 +19,7 @@ impl Parse for SliceMacroInput {
|
||||
let root: syn::Ident = input.parse()?;
|
||||
input.parse::<Token![.]>()?;
|
||||
// do not accept trailing punctuation
|
||||
let path: Punctuated<syn::Member, Token![.]> =
|
||||
let path: Punctuated<syn::Ident, Token![.]> =
|
||||
Punctuated::parse_separated_nonempty(input)?;
|
||||
|
||||
if path.is_empty() {
|
||||
|
||||
@@ -298,38 +298,34 @@ pub(crate) fn element_to_tokens(
|
||||
let children = node
|
||||
.children
|
||||
.iter()
|
||||
.filter_map(|node| match node {
|
||||
Node::Fragment(fragment) => Some(
|
||||
fragment_to_tokens(
|
||||
&fragment.children,
|
||||
true,
|
||||
parent_type,
|
||||
None,
|
||||
global_class,
|
||||
None,
|
||||
)
|
||||
.unwrap_or(quote_spanned! {
|
||||
Span::call_site()=> ::leptos::leptos_dom::Unit
|
||||
}),
|
||||
),
|
||||
Node::Text(node) => Some(quote! { #node }),
|
||||
.map(|node| match node {
|
||||
Node::Fragment(fragment) => fragment_to_tokens(
|
||||
&fragment.children,
|
||||
true,
|
||||
parent_type,
|
||||
None,
|
||||
global_class,
|
||||
None,
|
||||
)
|
||||
.unwrap_or(quote_spanned! {
|
||||
Span::call_site()=> ::leptos::leptos_dom::Unit
|
||||
}),
|
||||
Node::Text(node) => quote! { #node },
|
||||
Node::RawText(node) => {
|
||||
let text = node.to_string_best();
|
||||
let text = syn::LitStr::new(&text, node.span());
|
||||
Some(quote! { #text })
|
||||
quote! { #text }
|
||||
}
|
||||
Node::Block(node) => Some(quote! { #node }),
|
||||
Node::Element(node) => Some(
|
||||
element_to_tokens(
|
||||
node,
|
||||
parent_type,
|
||||
None,
|
||||
global_class,
|
||||
None,
|
||||
)
|
||||
.unwrap_or_default(),
|
||||
),
|
||||
Node::Comment(_) | Node::Doctype(_) => None,
|
||||
Node::Block(node) => quote! { #node },
|
||||
Node::Element(node) => element_to_tokens(
|
||||
node,
|
||||
parent_type,
|
||||
None,
|
||||
global_class,
|
||||
None,
|
||||
)
|
||||
.unwrap_or_default(),
|
||||
Node::Comment(_) | Node::Doctype(_) => quote! {},
|
||||
})
|
||||
.map(|node| quote!(.child(#node)));
|
||||
|
||||
|
||||
@@ -237,17 +237,25 @@ pub(crate) fn component_to_tokens(
|
||||
|
||||
#[allow(unused_mut)] // used in debug
|
||||
let mut component = quote_spanned! {node.span()=>
|
||||
::leptos::component_view(
|
||||
#[allow(clippy::needless_borrows_for_generic_args)]
|
||||
#name_ref,
|
||||
#component_props_builder
|
||||
{
|
||||
let props = #component_props_builder
|
||||
#(#props)*
|
||||
#(#slots)*
|
||||
#children
|
||||
#children;
|
||||
|
||||
#[allow(clippy::let_unit_value, clippy::unit_arg)]
|
||||
let props = props
|
||||
#build
|
||||
#dyn_attrs
|
||||
#(#spread_bindings)*
|
||||
)
|
||||
#(#spread_bindings)*;
|
||||
|
||||
#[allow(unreachable_code)]
|
||||
::leptos::component_view(
|
||||
#[allow(clippy::needless_borrows_for_generic_args)]
|
||||
#name_ref,
|
||||
props
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
// (Temporarily?) removed
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
---
|
||||
source: leptos_macro/src/view/tests.rs
|
||||
assertion_line: 101
|
||||
expression: pretty(result)
|
||||
---
|
||||
fn view() {
|
||||
::leptos::component_view(
|
||||
#[allow(clippy::needless_borrows_for_generic_args)]
|
||||
&SimpleCounter,
|
||||
::leptos::component_props_builder(&SimpleCounter)
|
||||
{
|
||||
let props = ::leptos::component_props_builder(&SimpleCounter)
|
||||
.initial_value(#[allow(unused_braces)] { 0 })
|
||||
.step(#[allow(unused_braces)] { 1 })
|
||||
.build(),
|
||||
)
|
||||
.step(#[allow(unused_braces)] { 1 });
|
||||
#[allow(clippy::let_unit_value, clippy::unit_arg)]
|
||||
let props = props.build();
|
||||
#[allow(unreachable_code)]
|
||||
::leptos::component_view(
|
||||
#[allow(clippy::needless_borrows_for_generic_args)]
|
||||
&SimpleCounter,
|
||||
props,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
---
|
||||
source: leptos_macro/src/view/tests.rs
|
||||
assertion_line: 101
|
||||
expression: pretty(result)
|
||||
---
|
||||
fn view() {
|
||||
::leptos::IntoView::into_view(
|
||||
#[allow(unused_braces)]
|
||||
{
|
||||
::leptos::component_view(
|
||||
#[allow(clippy::needless_borrows_for_generic_args)]
|
||||
&ExternalComponent,
|
||||
::leptos::component_props_builder(&ExternalComponent).build(),
|
||||
)
|
||||
{
|
||||
let props = ::leptos::component_props_builder(&ExternalComponent);
|
||||
#[allow(clippy::let_unit_value, clippy::unit_arg)]
|
||||
let props = props.build();
|
||||
#[allow(unreachable_code)]
|
||||
::leptos::component_view(
|
||||
#[allow(clippy::needless_borrows_for_generic_args)]
|
||||
&ExternalComponent,
|
||||
props,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
.on(
|
||||
@@ -20,3 +27,4 @@ fn view() {
|
||||
move |_: Event| set_value(0),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,89 +1,22 @@
|
||||
---
|
||||
source: leptos_macro/src/view/tests.rs
|
||||
assertion_line: 101
|
||||
expression: result
|
||||
---
|
||||
TokenStream [
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: leptos,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: component_view,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
delimiter: Brace,
|
||||
stream: TokenStream [
|
||||
Punct {
|
||||
char: '#',
|
||||
spacing: Alone,
|
||||
Ident {
|
||||
sym: let,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Bracket,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: allow,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: clippy,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: needless_borrows_for_generic_args,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: '&',
|
||||
spacing: Alone,
|
||||
span: bytes(11..24),
|
||||
},
|
||||
Ident {
|
||||
sym: SimpleCounter,
|
||||
span: bytes(11..24),
|
||||
sym: props,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ',',
|
||||
char: '=',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
@@ -230,6 +163,90 @@ TokenStream [
|
||||
],
|
||||
span: bytes(65..71),
|
||||
},
|
||||
Punct {
|
||||
char: ';',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: '#',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Bracket,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: allow,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: clippy,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: let_unit_value,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ',',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: clippy,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: unit_arg,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: let,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: props,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: '=',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: props,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: '.',
|
||||
spacing: Alone,
|
||||
@@ -244,6 +261,127 @@ TokenStream [
|
||||
stream: TokenStream [],
|
||||
span: bytes(11..24),
|
||||
},
|
||||
Punct {
|
||||
char: ';',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: '#',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Bracket,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: allow,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: unreachable_code,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: leptos,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: component_view,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
stream: TokenStream [
|
||||
Punct {
|
||||
char: '#',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Bracket,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: allow,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: clippy,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: needless_borrows_for_generic_args,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: '&',
|
||||
spacing: Alone,
|
||||
span: bytes(11..24),
|
||||
},
|
||||
Ident {
|
||||
sym: SimpleCounter,
|
||||
span: bytes(11..24),
|
||||
},
|
||||
Punct {
|
||||
char: ',',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: props,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
source: leptos_macro/src/view/tests.rs
|
||||
assertion_line: 101
|
||||
expression: result
|
||||
---
|
||||
TokenStream [
|
||||
@@ -76,87 +77,19 @@ TokenStream [
|
||||
Group {
|
||||
delimiter: Brace,
|
||||
stream: TokenStream [
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: leptos,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: component_view,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
delimiter: Brace,
|
||||
stream: TokenStream [
|
||||
Punct {
|
||||
char: '#',
|
||||
spacing: Alone,
|
||||
Ident {
|
||||
sym: let,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Bracket,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: allow,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: clippy,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: needless_borrows_for_generic_args,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: '&',
|
||||
spacing: Alone,
|
||||
span: bytes(11..28),
|
||||
},
|
||||
Ident {
|
||||
sym: ExternalComponent,
|
||||
span: bytes(11..28),
|
||||
sym: props,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ',',
|
||||
char: '=',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
@@ -203,6 +136,90 @@ TokenStream [
|
||||
],
|
||||
span: bytes(11..28),
|
||||
},
|
||||
Punct {
|
||||
char: ';',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: '#',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Bracket,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: allow,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: clippy,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: let_unit_value,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ',',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: clippy,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: unit_arg,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: let,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: props,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: '=',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: props,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: '.',
|
||||
spacing: Alone,
|
||||
@@ -217,6 +234,127 @@ TokenStream [
|
||||
stream: TokenStream [],
|
||||
span: bytes(11..28),
|
||||
},
|
||||
Punct {
|
||||
char: ';',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: '#',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Bracket,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: allow,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: unreachable_code,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: leptos,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: component_view,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
stream: TokenStream [
|
||||
Punct {
|
||||
char: '#',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Bracket,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: allow,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: clippy,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: needless_borrows_for_generic_args,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: '&',
|
||||
spacing: Alone,
|
||||
span: bytes(11..28),
|
||||
},
|
||||
Ident {
|
||||
sym: ExternalComponent,
|
||||
span: bytes(11..28),
|
||||
},
|
||||
Punct {
|
||||
char: ',',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: props,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
---
|
||||
source: leptos_macro/src/view/tests.rs
|
||||
assertion_line: 101
|
||||
expression: pretty(result)
|
||||
---
|
||||
fn view() {
|
||||
::leptos::component_view(
|
||||
#[allow(clippy::needless_borrows_for_generic_args)]
|
||||
&SimpleCounter,
|
||||
::leptos::component_props_builder(&SimpleCounter)
|
||||
{
|
||||
let props = ::leptos::component_props_builder(&SimpleCounter)
|
||||
.initial_value(#[allow(unused_braces)] { 0 })
|
||||
.step(#[allow(unused_braces)] { 1 })
|
||||
.build(),
|
||||
)
|
||||
.step(#[allow(unused_braces)] { 1 });
|
||||
#[allow(clippy::let_unit_value, clippy::unit_arg)]
|
||||
let props = props.build();
|
||||
#[allow(unreachable_code)]
|
||||
::leptos::component_view(
|
||||
#[allow(clippy::needless_borrows_for_generic_args)]
|
||||
&SimpleCounter,
|
||||
props,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
---
|
||||
source: leptos_macro/src/view/tests.rs
|
||||
assertion_line: 101
|
||||
expression: pretty(result)
|
||||
---
|
||||
fn view() {
|
||||
::leptos::IntoView::into_view(
|
||||
#[allow(unused_braces)]
|
||||
{
|
||||
::leptos::component_view(
|
||||
#[allow(clippy::needless_borrows_for_generic_args)]
|
||||
&ExternalComponent,
|
||||
::leptos::component_props_builder(&ExternalComponent).build(),
|
||||
)
|
||||
{
|
||||
let props = ::leptos::component_props_builder(&ExternalComponent);
|
||||
#[allow(clippy::let_unit_value, clippy::unit_arg)]
|
||||
let props = props.build();
|
||||
#[allow(unreachable_code)]
|
||||
::leptos::component_view(
|
||||
#[allow(clippy::needless_borrows_for_generic_args)]
|
||||
&ExternalComponent,
|
||||
props,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
.on(
|
||||
@@ -20,3 +27,4 @@ fn view() {
|
||||
move |_: Event| set_value(0),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,89 +1,22 @@
|
||||
---
|
||||
source: leptos_macro/src/view/tests.rs
|
||||
assertion_line: 101
|
||||
expression: result
|
||||
---
|
||||
TokenStream [
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: leptos,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: component_view,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
delimiter: Brace,
|
||||
stream: TokenStream [
|
||||
Punct {
|
||||
char: '#',
|
||||
spacing: Alone,
|
||||
Ident {
|
||||
sym: let,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Bracket,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: allow,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: clippy,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: needless_borrows_for_generic_args,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: '&',
|
||||
spacing: Alone,
|
||||
span: bytes(11..24),
|
||||
},
|
||||
Ident {
|
||||
sym: SimpleCounter,
|
||||
span: bytes(11..24),
|
||||
sym: props,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ',',
|
||||
char: '=',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
@@ -230,6 +163,90 @@ TokenStream [
|
||||
],
|
||||
span: bytes(65..71),
|
||||
},
|
||||
Punct {
|
||||
char: ';',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: '#',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Bracket,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: allow,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: clippy,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: let_unit_value,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ',',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: clippy,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: unit_arg,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: let,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: props,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: '=',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: props,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: '.',
|
||||
spacing: Alone,
|
||||
@@ -244,6 +261,127 @@ TokenStream [
|
||||
stream: TokenStream [],
|
||||
span: bytes(11..24),
|
||||
},
|
||||
Punct {
|
||||
char: ';',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: '#',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Bracket,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: allow,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: unreachable_code,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: leptos,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: component_view,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
stream: TokenStream [
|
||||
Punct {
|
||||
char: '#',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Bracket,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: allow,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: clippy,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: needless_borrows_for_generic_args,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: '&',
|
||||
spacing: Alone,
|
||||
span: bytes(11..24),
|
||||
},
|
||||
Ident {
|
||||
sym: SimpleCounter,
|
||||
span: bytes(11..24),
|
||||
},
|
||||
Punct {
|
||||
char: ',',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: props,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
source: leptos_macro/src/view/tests.rs
|
||||
assertion_line: 101
|
||||
expression: result
|
||||
---
|
||||
TokenStream [
|
||||
@@ -76,87 +77,19 @@ TokenStream [
|
||||
Group {
|
||||
delimiter: Brace,
|
||||
stream: TokenStream [
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: leptos,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: component_view,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
delimiter: Brace,
|
||||
stream: TokenStream [
|
||||
Punct {
|
||||
char: '#',
|
||||
spacing: Alone,
|
||||
Ident {
|
||||
sym: let,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Bracket,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: allow,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: clippy,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: needless_borrows_for_generic_args,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: '&',
|
||||
spacing: Alone,
|
||||
span: bytes(11..28),
|
||||
},
|
||||
Ident {
|
||||
sym: ExternalComponent,
|
||||
span: bytes(11..28),
|
||||
sym: props,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ',',
|
||||
char: '=',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
@@ -203,6 +136,90 @@ TokenStream [
|
||||
],
|
||||
span: bytes(11..28),
|
||||
},
|
||||
Punct {
|
||||
char: ';',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: '#',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Bracket,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: allow,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: clippy,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: let_unit_value,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ',',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: clippy,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: unit_arg,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: let,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: props,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: '=',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: props,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: '.',
|
||||
spacing: Alone,
|
||||
@@ -217,6 +234,127 @@ TokenStream [
|
||||
stream: TokenStream [],
|
||||
span: bytes(11..28),
|
||||
},
|
||||
Punct {
|
||||
char: ';',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: '#',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Bracket,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: allow,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: unreachable_code,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: leptos,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: component_view,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
stream: TokenStream [
|
||||
Punct {
|
||||
char: '#',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Bracket,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: allow,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: clippy,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: needless_borrows_for_generic_args,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: '&',
|
||||
spacing: Alone,
|
||||
span: bytes(11..28),
|
||||
},
|
||||
Ident {
|
||||
sym: ExternalComponent,
|
||||
span: bytes(11..28),
|
||||
},
|
||||
Punct {
|
||||
char: ',',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: props,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
---
|
||||
source: leptos_macro/src/view/tests.rs
|
||||
assertion_line: 101
|
||||
expression: pretty(result)
|
||||
---
|
||||
fn view() {
|
||||
::leptos::component_view(
|
||||
#[allow(clippy::needless_borrows_for_generic_args)]
|
||||
&SimpleCounter,
|
||||
::leptos::component_props_builder(&SimpleCounter)
|
||||
{
|
||||
let props = ::leptos::component_props_builder(&SimpleCounter)
|
||||
.initial_value(#[allow(unused_braces)] { 0 })
|
||||
.step(#[allow(unused_braces)] { 1 })
|
||||
.build(),
|
||||
)
|
||||
.step(#[allow(unused_braces)] { 1 });
|
||||
#[allow(clippy::let_unit_value, clippy::unit_arg)]
|
||||
let props = props.build();
|
||||
#[allow(unreachable_code)]
|
||||
::leptos::component_view(
|
||||
#[allow(clippy::needless_borrows_for_generic_args)]
|
||||
&SimpleCounter,
|
||||
props,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
---
|
||||
source: leptos_macro/src/view/tests.rs
|
||||
assertion_line: 101
|
||||
expression: pretty(result)
|
||||
---
|
||||
fn view() {
|
||||
::leptos::IntoView::into_view(
|
||||
#[allow(unused_braces)]
|
||||
{
|
||||
::leptos::component_view(
|
||||
#[allow(clippy::needless_borrows_for_generic_args)]
|
||||
&ExternalComponent,
|
||||
::leptos::component_props_builder(&ExternalComponent).build(),
|
||||
)
|
||||
{
|
||||
let props = ::leptos::component_props_builder(&ExternalComponent);
|
||||
#[allow(clippy::let_unit_value, clippy::unit_arg)]
|
||||
let props = props.build();
|
||||
#[allow(unreachable_code)]
|
||||
::leptos::component_view(
|
||||
#[allow(clippy::needless_borrows_for_generic_args)]
|
||||
&ExternalComponent,
|
||||
props,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
.on(
|
||||
@@ -20,3 +27,4 @@ fn view() {
|
||||
move |_: Event| set_value(0),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,89 +1,22 @@
|
||||
---
|
||||
source: leptos_macro/src/view/tests.rs
|
||||
assertion_line: 101
|
||||
expression: result
|
||||
---
|
||||
TokenStream [
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: leptos,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: component_view,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
delimiter: Brace,
|
||||
stream: TokenStream [
|
||||
Punct {
|
||||
char: '#',
|
||||
spacing: Alone,
|
||||
Ident {
|
||||
sym: let,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Bracket,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: allow,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: clippy,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: needless_borrows_for_generic_args,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: '&',
|
||||
spacing: Alone,
|
||||
span: bytes(11..24),
|
||||
},
|
||||
Ident {
|
||||
sym: SimpleCounter,
|
||||
span: bytes(11..24),
|
||||
sym: props,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ',',
|
||||
char: '=',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
@@ -230,6 +163,90 @@ TokenStream [
|
||||
],
|
||||
span: bytes(65..71),
|
||||
},
|
||||
Punct {
|
||||
char: ';',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: '#',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Bracket,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: allow,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: clippy,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: let_unit_value,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ',',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: clippy,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: unit_arg,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: let,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: props,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: '=',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: props,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: '.',
|
||||
spacing: Alone,
|
||||
@@ -244,6 +261,127 @@ TokenStream [
|
||||
stream: TokenStream [],
|
||||
span: bytes(11..24),
|
||||
},
|
||||
Punct {
|
||||
char: ';',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: '#',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Bracket,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: allow,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: unreachable_code,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: leptos,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: component_view,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
stream: TokenStream [
|
||||
Punct {
|
||||
char: '#',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Bracket,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: allow,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: clippy,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: needless_borrows_for_generic_args,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: '&',
|
||||
spacing: Alone,
|
||||
span: bytes(11..24),
|
||||
},
|
||||
Ident {
|
||||
sym: SimpleCounter,
|
||||
span: bytes(11..24),
|
||||
},
|
||||
Punct {
|
||||
char: ',',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: props,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
source: leptos_macro/src/view/tests.rs
|
||||
assertion_line: 101
|
||||
expression: result
|
||||
---
|
||||
TokenStream [
|
||||
@@ -76,87 +77,19 @@ TokenStream [
|
||||
Group {
|
||||
delimiter: Brace,
|
||||
stream: TokenStream [
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: leptos,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: component_view,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
delimiter: Brace,
|
||||
stream: TokenStream [
|
||||
Punct {
|
||||
char: '#',
|
||||
spacing: Alone,
|
||||
Ident {
|
||||
sym: let,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Bracket,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: allow,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: clippy,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: needless_borrows_for_generic_args,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: '&',
|
||||
spacing: Alone,
|
||||
span: bytes(11..28),
|
||||
},
|
||||
Ident {
|
||||
sym: ExternalComponent,
|
||||
span: bytes(11..28),
|
||||
sym: props,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ',',
|
||||
char: '=',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
@@ -203,6 +136,90 @@ TokenStream [
|
||||
],
|
||||
span: bytes(11..28),
|
||||
},
|
||||
Punct {
|
||||
char: ';',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: '#',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Bracket,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: allow,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: clippy,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: let_unit_value,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ',',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: clippy,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: unit_arg,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: let,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: props,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: '=',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: props,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: '.',
|
||||
spacing: Alone,
|
||||
@@ -217,6 +234,127 @@ TokenStream [
|
||||
stream: TokenStream [],
|
||||
span: bytes(11..28),
|
||||
},
|
||||
Punct {
|
||||
char: ';',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: '#',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Bracket,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: allow,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: unreachable_code,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: leptos,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: component_view,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
stream: TokenStream [
|
||||
Punct {
|
||||
char: '#',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Bracket,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: allow,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Group {
|
||||
delimiter: Parenthesis,
|
||||
stream: TokenStream [
|
||||
Ident {
|
||||
sym: clippy,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Joint,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: ':',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: needless_borrows_for_generic_args,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Punct {
|
||||
char: '&',
|
||||
spacing: Alone,
|
||||
span: bytes(11..28),
|
||||
},
|
||||
Ident {
|
||||
sym: ExternalComponent,
|
||||
span: bytes(11..28),
|
||||
},
|
||||
Punct {
|
||||
char: ',',
|
||||
spacing: Alone,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
Ident {
|
||||
sym: props,
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
],
|
||||
span: bytes(10..82),
|
||||
},
|
||||
|
||||
@@ -8,27 +8,20 @@ fn Component(
|
||||
#[prop(strip_option)] strip_option: Option<u8>,
|
||||
#[prop(default = NonZeroUsize::new(10).unwrap())] default: NonZeroUsize,
|
||||
#[prop(into)] into: String,
|
||||
impl_trait: impl Fn() -> i32 + 'static,
|
||||
) -> impl IntoView {
|
||||
_ = optional;
|
||||
_ = optional_no_strip;
|
||||
_ = strip_option;
|
||||
_ = default;
|
||||
_ = into;
|
||||
_ = impl_trait;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn component() {
|
||||
let cp = ComponentProps::builder()
|
||||
.into("")
|
||||
.strip_option(9)
|
||||
.impl_trait(|| 42)
|
||||
.build();
|
||||
let cp = ComponentProps::builder().into("").strip_option(9).build();
|
||||
assert!(!cp.optional);
|
||||
assert_eq!(cp.optional_no_strip, None);
|
||||
assert_eq!(cp.strip_option, Some(9));
|
||||
assert_eq!(cp.default, NonZeroUsize::new(10).unwrap());
|
||||
assert_eq!(cp.into, "");
|
||||
assert_eq!((cp.impl_trait)(), 42);
|
||||
}
|
||||
|
||||
@@ -10,12 +10,9 @@ pub struct OuterState {
|
||||
#[derive(Clone, PartialEq, Default)]
|
||||
pub struct InnerState {
|
||||
inner_count: i32,
|
||||
inner_tuple: InnerTuple,
|
||||
inner_name: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Default)]
|
||||
pub struct InnerTuple(String);
|
||||
|
||||
#[test]
|
||||
fn green() {
|
||||
let _ = create_runtime();
|
||||
@@ -25,7 +22,7 @@ fn green() {
|
||||
let (_, _) = slice!(outer_signal.count);
|
||||
|
||||
let (_, _) = slice!(outer_signal.inner.inner_count);
|
||||
let (_, _) = slice!(outer_signal.inner.inner_tuple.0);
|
||||
let (_, _) = slice!(outer_signal.inner.inner_name);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -14,7 +14,7 @@ error: expected `.`
|
||||
|
|
||||
= note: this error originates in the macro `slice` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error: unexpected end of input, expected identifier or integer
|
||||
error: unexpected end of input, expected identifier
|
||||
--> tests/slice/red.rs:25:18
|
||||
|
|
||||
25 | let (_, _) = slice!(outer_signal.);
|
||||
@@ -22,7 +22,7 @@ error: unexpected end of input, expected identifier or integer
|
||||
|
|
||||
= note: this error originates in the macro `slice` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error: unexpected end of input, expected identifier or integer
|
||||
error: unexpected end of input, expected identifier
|
||||
--> tests/slice/red.rs:27:18
|
||||
|
|
||||
27 | let (_, _) = slice!(outer_signal.inner.);
|
||||
|
||||
@@ -271,16 +271,6 @@ where
|
||||
///
|
||||
/// Local resources do not load on the server, only in the client’s browser.
|
||||
///
|
||||
/// ## When to use a Local Resource
|
||||
///
|
||||
/// `create_resource` has three different features:
|
||||
/// 1. gives a synchronous API for asynchronous things
|
||||
/// 2. integrates with `Suspense`/`Transition``
|
||||
/// 3. makes your application faster by starting things like DB access or an API request on the server,
|
||||
/// rather than waiting until you've fully loaded the client
|
||||
///
|
||||
/// `create_local_resource` is useful when you can't or don't need to do #3 (serializing data from server
|
||||
/// to client), but still want #1 (synchronous API for async) and #2 (integration with `Suspense`).
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # let runtime = create_runtime();
|
||||
@@ -288,9 +278,7 @@ where
|
||||
/// struct ComplicatedUnserializableStruct {
|
||||
/// // something here that can't be serialized
|
||||
/// }
|
||||
///
|
||||
/// // an async function whose results can't be serialized from the server to the client
|
||||
/// // (for example, opening a connection to the user's device camera)
|
||||
/// // any old async function; maybe this is calling a REST API or something
|
||||
/// async fn setup_complicated_struct() -> ComplicatedUnserializableStruct {
|
||||
/// // do some work
|
||||
/// ComplicatedUnserializableStruct {}
|
||||
|
||||
@@ -294,12 +294,10 @@ impl Runtime {
|
||||
drop(node);
|
||||
}
|
||||
ScopeProperty::Resource(id) => {
|
||||
let value = self.resources.borrow_mut().remove(id);
|
||||
drop(value);
|
||||
self.resources.borrow_mut().remove(id);
|
||||
}
|
||||
ScopeProperty::StoredValue(id) => {
|
||||
let value = self.stored_values.borrow_mut().remove(id);
|
||||
drop(value);
|
||||
self.stored_values.borrow_mut().remove(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,7 +242,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a [MultiAction] to synchronize an imperative `async` call to the synchronous reactive system.
|
||||
/// Creates an [MultiAction] to synchronize an imperative `async` call to the synchronous reactive system.
|
||||
///
|
||||
/// If you’re trying to load data by running an `async` function reactively, you probably
|
||||
/// want to use a [create_resource](leptos_reactive::create_resource) instead. If you’re trying
|
||||
@@ -319,7 +319,7 @@ where
|
||||
}))
|
||||
}
|
||||
|
||||
/// Creates a [MultiAction] that can be used to call a server function.
|
||||
/// Creates an [MultiAction] that can be used to call a server function.
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// # use leptos::*;
|
||||
|
||||
@@ -5,24 +5,3 @@ The `projects` directory is intended as a collective of medium-to-large-scale ex
|
||||
The `examples` directory is included in our CI, and examples are regularly linted and tested. The barrier to entry for the `projects` directory is intended to be lower: Example projects will generally be built against a particular version, and not regularly linted or updated. Hopefully this distinction allows us to accept more examples without worrying about the maintenance burden of constant updates.
|
||||
|
||||
Feel free to submit projects to this directory via PR!
|
||||
|
||||
|
||||
## Index
|
||||
|
||||
### meilisearch-searchbar
|
||||
[Meilisearch](https://www.meilisearch.com/) is a search engine built in Rust that you can self-host. This example shows how to run it alongside a leptos server and present a search bar with autocomplete to the user.
|
||||
|
||||
### nginx-mpmc
|
||||
[Nginx](https://nginx.org/) Multiple Producer Multi Consumer, this example shows how you can use Nginx to provide different clients to the user while running multiple Leptos servers that provide server functions to any of the clients.
|
||||
|
||||
### ory-kratos
|
||||
[Ory](https://www.ory.sh/docs/welcome) is a combination of different authorization services. Ory Kratos is their Identification service, which provides password storage, emailing, login and registration functionality, etc. This example shows running Ory Kratos alongside a leptos server and making use of their UI Node data types in leptos. TODO: This example needs a bit more work to show off SSO passwordless etc
|
||||
|
||||
### tauri-from-scratch
|
||||
This example walks you through in explicit detail how to use [Tauri](https://tauri.app/) to render your Leptos App on non web targets using [WebView](https://en.wikipedia.org/wiki/WebView) while communicating with your leptos server and servering an SSR supported web experience. TODO: It could be simplified since part of the readme includes copying and pasting boilerplate.
|
||||
|
||||
### counter_dwarf_debug
|
||||
This example shows how to add breakpoints within the browser or visual studio code for debugging.
|
||||
|
||||
### bevy3d_ui
|
||||
This example uses the bevy 3d game engine with leptos within webassembly.
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
[package]
|
||||
name = "bevy3d_ui"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
leptos = { version = "0.6.11", features = ["csr"] }
|
||||
leptos_meta = { version = "0.6.11", features = ["csr"] }
|
||||
leptos_router = { version = "0.6.11", features = ["csr"] }
|
||||
console_log = "1"
|
||||
log = "0.4"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
bevy = "0.13.2"
|
||||
crossbeam-channel = "0.5.12"
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen = "0.2"
|
||||
wasm-bindgen-test = "0.3.0"
|
||||
web-sys = "0.3"
|
||||
|
||||
[workspace]
|
||||
# The empty workspace here is to keep rust-analyzer satisfied
|
||||
@@ -1,15 +0,0 @@
|
||||
# Bevy 3D UI Example
|
||||
|
||||
This example combines a leptos UI with a bevy 3D view.
|
||||
Bevy is a 3D game engine written in rust that can be compiled to web assembly by using the wgpu library.
|
||||
The wgpu library in turn can target the newer webgpu standard or the older webgl for web browsers.
|
||||
|
||||
In the case of a desktop application, if you wanted to use a styled ui via leptos and a 3d view via bevy
|
||||
you could also combine this with tauri.
|
||||
|
||||
## Quick Start
|
||||
|
||||
* Run `trunk serve to run the example.
|
||||
* Browse to http://127.0.0.1:8080/
|
||||
|
||||
It's best to use a web browser with webgpu capability for best results such as Chrome or Opera.
|
||||
@@ -1,8 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link data-trunk rel="rust" data-wasm-opt="z"/>
|
||||
<link data-trunk rel="icon" type="image/ico" href="/public/favicon.ico"/>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB |
@@ -1,2 +0,0 @@
|
||||
[toolchain]
|
||||
channel = "stable" # test change
|
||||
@@ -1,38 +0,0 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
/// Event Processor
|
||||
#[derive(Resource)]
|
||||
pub struct EventProcessor<TSender, TReceiver> {
|
||||
pub sender: crossbeam_channel::Sender<TSender>,
|
||||
pub receiver: crossbeam_channel::Receiver<TReceiver>,
|
||||
}
|
||||
|
||||
impl<TSender, TReceiver> Clone for EventProcessor<TSender, TReceiver> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
sender: self.sender.clone(),
|
||||
receiver: self.receiver.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Events sent from the client to bevy
|
||||
#[derive(Debug)]
|
||||
pub enum ClientInEvents {
|
||||
/// Update the 3d model position from the client
|
||||
CounterEvt(CounterEvtData),
|
||||
}
|
||||
|
||||
/// Events sent out from bevy to the client
|
||||
#[derive(Debug)]
|
||||
pub enum PluginOutEvents {
|
||||
/// TODO Feed back to the client an event from bevy
|
||||
Click,
|
||||
}
|
||||
|
||||
/// Input event to update the bevy view from the client
|
||||
#[derive(Clone, Debug, Event)]
|
||||
pub struct CounterEvtData {
|
||||
/// Amount to move on the Y Axis
|
||||
pub value: f32,
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
pub mod events;
|
||||
pub mod plugin;
|
||||
@@ -1,63 +0,0 @@
|
||||
use super::events::*;
|
||||
use bevy::prelude::*;
|
||||
|
||||
/// Events plugin for bevy
|
||||
#[derive(Clone)]
|
||||
pub struct DuplexEventsPlugin {
|
||||
/// Client processor for sending ClientInEvents, receiving PluginOutEvents
|
||||
client_processor: EventProcessor<ClientInEvents, PluginOutEvents>,
|
||||
/// Internal processor for sending PluginOutEvents, receiving ClientInEvents
|
||||
plugin_processor: EventProcessor<PluginOutEvents, ClientInEvents>,
|
||||
}
|
||||
|
||||
impl DuplexEventsPlugin {
|
||||
/// Create a new instance
|
||||
pub fn new() -> DuplexEventsPlugin {
|
||||
// For sending messages from bevy to the client
|
||||
let (bevy_sender, client_receiver) = crossbeam_channel::bounded(50);
|
||||
// For sending message from the client to bevy
|
||||
let (client_sender, bevy_receiver) = crossbeam_channel::bounded(50);
|
||||
let instance = DuplexEventsPlugin {
|
||||
client_processor: EventProcessor {
|
||||
sender: client_sender,
|
||||
receiver: client_receiver,
|
||||
},
|
||||
plugin_processor: EventProcessor {
|
||||
sender: bevy_sender,
|
||||
receiver: bevy_receiver,
|
||||
},
|
||||
};
|
||||
instance
|
||||
}
|
||||
|
||||
/// Get the client event processor
|
||||
pub fn get_processor(
|
||||
&self,
|
||||
) -> EventProcessor<ClientInEvents, PluginOutEvents> {
|
||||
self.client_processor.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Build the bevy plugin and attach
|
||||
impl Plugin for DuplexEventsPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.insert_resource(self.plugin_processor.clone())
|
||||
.init_resource::<Events<CounterEvtData>>()
|
||||
.add_systems(PreUpdate, input_events_system);
|
||||
}
|
||||
}
|
||||
|
||||
/// Send the event to bevy using EventWriter
|
||||
fn input_events_system(
|
||||
int_processor: Res<EventProcessor<PluginOutEvents, ClientInEvents>>,
|
||||
mut counter_event_writer: EventWriter<CounterEvtData>,
|
||||
) {
|
||||
for input_event in int_processor.receiver.try_iter() {
|
||||
match input_event {
|
||||
ClientInEvents::CounterEvt(event) => {
|
||||
// Send event through Bevy's event system
|
||||
counter_event_writer.send(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
pub mod eventqueue;
|
||||
pub mod scene;
|
||||
pub mod state;
|
||||
@@ -1,124 +0,0 @@
|
||||
use super::eventqueue::events::{
|
||||
ClientInEvents, CounterEvtData, EventProcessor, PluginOutEvents,
|
||||
};
|
||||
use super::eventqueue::plugin::DuplexEventsPlugin;
|
||||
use super::state::{Shared, SharedResource, SharedState};
|
||||
use bevy::prelude::*;
|
||||
|
||||
/// Represents the Cube in the scene
|
||||
#[derive(Component, Copy, Clone)]
|
||||
pub struct Cube;
|
||||
|
||||
/// Represents the 3D Scene
|
||||
#[derive(Clone)]
|
||||
pub struct Scene {
|
||||
is_setup: bool,
|
||||
canvas_id: String,
|
||||
evt_plugin: DuplexEventsPlugin,
|
||||
shared_state: Shared<SharedState>,
|
||||
processor: EventProcessor<ClientInEvents, PluginOutEvents>,
|
||||
}
|
||||
|
||||
impl Scene {
|
||||
/// Create a new instance
|
||||
pub fn new(canvas_id: String) -> Scene {
|
||||
let plugin = DuplexEventsPlugin::new();
|
||||
let instance = Scene {
|
||||
is_setup: false,
|
||||
canvas_id: canvas_id,
|
||||
evt_plugin: plugin.clone(),
|
||||
shared_state: SharedState::new(),
|
||||
processor: plugin.get_processor(),
|
||||
};
|
||||
instance
|
||||
}
|
||||
|
||||
/// Get the shared state
|
||||
pub fn get_state(&self) -> Shared<SharedState> {
|
||||
self.shared_state.clone()
|
||||
}
|
||||
|
||||
/// Get the event processor
|
||||
pub fn get_processor(
|
||||
&self,
|
||||
) -> EventProcessor<ClientInEvents, PluginOutEvents> {
|
||||
self.processor.clone()
|
||||
}
|
||||
|
||||
/// Setup and attach the bevy instance to the html canvas element
|
||||
pub fn setup(&mut self) {
|
||||
if self.is_setup == true {
|
||||
return;
|
||||
};
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
||||
primary_window: Some(Window {
|
||||
canvas: Some(self.canvas_id.clone()),
|
||||
..default()
|
||||
}),
|
||||
..default()
|
||||
}))
|
||||
.add_plugins(self.evt_plugin.clone())
|
||||
.insert_resource(SharedResource(self.shared_state.clone()))
|
||||
.add_systems(Startup, setup_scene)
|
||||
.add_systems(Update, handle_bevy_event)
|
||||
.run();
|
||||
self.is_setup = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Setup the scene
|
||||
fn setup_scene(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
resource: Res<SharedResource>,
|
||||
) {
|
||||
let name = resource.0.lock().unwrap().name.clone();
|
||||
// circular base
|
||||
commands.spawn(PbrBundle {
|
||||
mesh: meshes.add(Circle::new(4.0)),
|
||||
material: materials.add(Color::WHITE),
|
||||
transform: Transform::from_rotation(Quat::from_rotation_x(
|
||||
-std::f32::consts::FRAC_PI_2,
|
||||
)),
|
||||
..default()
|
||||
});
|
||||
// cube
|
||||
commands.spawn((
|
||||
PbrBundle {
|
||||
mesh: meshes.add(Cuboid::new(1.0, 1.0, 1.0)),
|
||||
material: materials.add(Color::rgb_u8(124, 144, 255)),
|
||||
transform: Transform::from_xyz(0.0, 0.5, 0.0),
|
||||
..default()
|
||||
},
|
||||
Cube,
|
||||
));
|
||||
// light
|
||||
commands.spawn(PointLightBundle {
|
||||
point_light: PointLight {
|
||||
shadows_enabled: true,
|
||||
..default()
|
||||
},
|
||||
transform: Transform::from_xyz(4.0, 8.0, 4.0),
|
||||
..default()
|
||||
});
|
||||
// camera
|
||||
commands.spawn(Camera3dBundle {
|
||||
transform: Transform::from_xyz(-2.5, 4.5, 9.0)
|
||||
.looking_at(Vec3::ZERO, Vec3::Y),
|
||||
..default()
|
||||
});
|
||||
commands.spawn(TextBundle::from_section(name, TextStyle::default()));
|
||||
}
|
||||
|
||||
/// Move the Cube on event
|
||||
fn handle_bevy_event(
|
||||
mut counter_event_reader: EventReader<CounterEvtData>,
|
||||
mut cube_query: Query<&mut Transform, With<Cube>>,
|
||||
) {
|
||||
let mut cube_transform = cube_query.get_single_mut().expect("no cube :(");
|
||||
for _ev in counter_event_reader.read() {
|
||||
cube_transform.translation += Vec3::new(0.0, _ev.value, 0.0);
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
use bevy::ecs::system::Resource;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
pub type Shared<T> = Arc<Mutex<T>>;
|
||||
|
||||
/// Shared Resource used for Bevy
|
||||
#[derive(Resource)]
|
||||
pub struct SharedResource(pub Shared<SharedState>);
|
||||
|
||||
/// Shared State
|
||||
pub struct SharedState {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl SharedState {
|
||||
/// Get a new shared state
|
||||
pub fn new() -> Arc<Mutex<SharedState>> {
|
||||
let state = SharedState {
|
||||
name: "This can be used for shared state".to_string(),
|
||||
};
|
||||
let shared = Arc::new(Mutex::new(state));
|
||||
shared
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
pub mod bevydemo1;
|
||||
@@ -1,11 +0,0 @@
|
||||
mod demos;
|
||||
mod routes;
|
||||
use leptos::*;
|
||||
use routes::RootPage;
|
||||
|
||||
pub fn main() {
|
||||
// Bevy will output a lot of debug info to the console when this is enabled.
|
||||
//_ = console_log::init_with_level(log::Level::Debug);
|
||||
console_error_panic_hook::set_once();
|
||||
mount_to_body(|| view! { <RootPage/> })
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
use crate::demos::bevydemo1::eventqueue::events::{
|
||||
ClientInEvents, CounterEvtData,
|
||||
};
|
||||
use crate::demos::bevydemo1::scene::Scene;
|
||||
use leptos::*;
|
||||
|
||||
/// 3d view component
|
||||
#[component]
|
||||
pub fn Demo1() -> impl IntoView {
|
||||
// Setup a Counter
|
||||
let initial_value: i32 = 0;
|
||||
let step: i32 = 1;
|
||||
let (value, set_value) = create_signal(initial_value);
|
||||
|
||||
// Setup a bevy 3d scene
|
||||
let scene = Scene::new("#bevy".to_string());
|
||||
let sender = scene.get_processor().sender;
|
||||
let (sender_sig, _set_sender_sig) = create_signal(sender);
|
||||
let (scene_sig, _set_scene_sig) = create_signal(scene);
|
||||
|
||||
// We need to add the 3D view onto the canvas post render.
|
||||
create_effect(move |_| {
|
||||
request_animation_frame(move || {
|
||||
scene_sig.get().setup();
|
||||
});
|
||||
});
|
||||
|
||||
view! {
|
||||
<div>
|
||||
<button on:click=move |_| set_value.set(0)>"Clear"</button>
|
||||
<button on:click=move |_| {
|
||||
set_value.update(|value| *value -= step);
|
||||
let newpos = (step as f32) / 10.0;
|
||||
sender_sig
|
||||
.get()
|
||||
.send(ClientInEvents::CounterEvt(CounterEvtData { value: -newpos }))
|
||||
.expect("could not send event");
|
||||
}>"-1"</button>
|
||||
<span>"Value: " {value} "!"</span>
|
||||
<button on:click=move |_| {
|
||||
set_value.update(|value| *value += step);
|
||||
let newpos = step as f32 / 10.0;
|
||||
sender_sig
|
||||
.get()
|
||||
.send(ClientInEvents::CounterEvt(CounterEvtData { value: newpos }))
|
||||
.expect("could not send event");
|
||||
}>"+1"</button>
|
||||
</div>
|
||||
|
||||
<canvas id="bevy" width="800" height="600"></canvas>
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
pub mod demo1;
|
||||
use demo1::Demo1;
|
||||
use leptos::*;
|
||||
use leptos_meta::{provide_meta_context, Meta, Stylesheet, Title};
|
||||
use leptos_router::*;
|
||||
|
||||
#[component]
|
||||
pub fn RootPage() -> impl IntoView {
|
||||
provide_meta_context();
|
||||
|
||||
view! {
|
||||
<Meta name="charset" content="UTF-8"/>
|
||||
<Meta name="description" content="Leptonic CSR template"/>
|
||||
<Meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<Meta name="theme-color" content="#e66956"/>
|
||||
<Stylesheet href="https://fonts.googleapis.com/css?family=Roboto&display=swap"/>
|
||||
<Title text="Leptos Bevy3D Example"/>
|
||||
<Router>
|
||||
<Routes>
|
||||
<Route path="" view=|| view! { <Demo1/> }/>
|
||||
</Routes>
|
||||
</Router>
|
||||
}
|
||||
}
|
||||
2
projects/counter_dwarf_debug/.gitignore
vendored
2
projects/counter_dwarf_debug/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
# For this example we want to include the vscode files
|
||||
!.vscode
|
||||
19
projects/counter_dwarf_debug/.vscode/launch.json
vendored
19
projects/counter_dwarf_debug/.vscode/launch.json
vendored
@@ -1,19 +0,0 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch Browser Chrome",
|
||||
"request": "launch",
|
||||
"type": "chrome",
|
||||
"url": "http://localhost:4001",
|
||||
"webRoot": "${workspaceFolder}/dist",
|
||||
// Needed to keep the dwarf extension in the browser
|
||||
"userDataDir": false,
|
||||
"preLaunchTask": "trunk: serve",
|
||||
"postDebugTask": "postdebugKill"
|
||||
},
|
||||
]
|
||||
}
|
||||
53
projects/counter_dwarf_debug/.vscode/tasks.json
vendored
53
projects/counter_dwarf_debug/.vscode/tasks.json
vendored
@@ -1,53 +0,0 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
|
||||
// Task to build the sources
|
||||
{
|
||||
"label": "trunk: build",
|
||||
"type": "shell",
|
||||
"command": "trunk",
|
||||
"args": ["build"],
|
||||
"problemMatcher": [
|
||||
"$rustc"
|
||||
],
|
||||
"group": "build",
|
||||
},
|
||||
|
||||
// Task to launch trunk serve for debugging
|
||||
{
|
||||
"label": "trunk: serve",
|
||||
"type": "shell",
|
||||
"command": "trunk",
|
||||
"args": ["serve"],
|
||||
"isBackground": true,
|
||||
"problemMatcher": {
|
||||
"pattern": {
|
||||
"regexp": ".",
|
||||
"file": 1,"line": 1,
|
||||
"column": 1,"message": 1
|
||||
},
|
||||
"background": {
|
||||
"activeOnStart": true,
|
||||
"beginsPattern": ".",
|
||||
"endsPattern": "."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Terminate the trunk serve task
|
||||
{
|
||||
"label": "postdebugKill",
|
||||
"type": "shell",
|
||||
"command": "echo ${input:terminate}",
|
||||
},
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"id": "terminate",
|
||||
"type": "command",
|
||||
"command": "workbench.action.tasks.terminate",
|
||||
"args": "terminateAll"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
[workspace]
|
||||
# The empty workspace here is to keep rust-analyzer satisfied
|
||||
|
||||
[package]
|
||||
name = "counter_dwarf_debug"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
leptos = { path = "../../leptos", features = ["csr"] }
|
||||
console_log = "1"
|
||||
log = "0.4"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen = "0.2"
|
||||
wasm-bindgen-test = "0.3.0"
|
||||
web-sys = "0.3"
|
||||
@@ -1,74 +0,0 @@
|
||||
# Debugging Leptos Counter Example in Browser and VSCode
|
||||
|
||||
This example builds on the simple counter by adding breakpoints and single stepping the source code for debugging.
|
||||
Both within the browser and VSCode.
|
||||
This uses a new feature of wasm called Dwarf which is a form of source code mapping.
|
||||
|
||||
Note variable inspection during the breakpoints doesn't seem to work at this stage.
|
||||
|
||||
## Quick Start
|
||||
|
||||
* Install the requirements below
|
||||
* Open this directory within visual studio code
|
||||
* Add a breakpoint to the code
|
||||
* Launch the example using the visual studio code debug launcher
|
||||
|
||||
## How This Works
|
||||
|
||||
### Html Changes
|
||||
|
||||
First we need to make a change to the index.html file
|
||||
|
||||
From this
|
||||
```html
|
||||
<link data-trunk rel="rust" data-wasm-opt="z"/>
|
||||
```
|
||||
|
||||
To this
|
||||
```html
|
||||
<link data-trunk rel="rust" data-keep-debug="true" data-wasm-opt="z"/>
|
||||
```
|
||||
|
||||
This instructs the rust `trunk` utility to pass a long an option to `wasm-bindgen` called `--keep-debug`
|
||||
This option bundles in a type of sourcemap into the built wasm file.
|
||||
Be aware that this will make the wasm file much larger.
|
||||
|
||||
### Browser Changes
|
||||
|
||||
Next we need to allow the browser to read the DWARF data from the wasm file.
|
||||
For Chrome / Opera there's an extension here that needs to be installed.
|
||||
|
||||
* https://chromewebstore.google.com/detail/cc++-devtools-support-dwa/pdcpmagijalfljmkmjngeonclgbbannb?pli=1
|
||||
|
||||
## Debugging within the Browser
|
||||
|
||||
Within the browser's dev console it should now be possible to view the rust source code and add breakpoints.
|
||||
|
||||

|
||||
|
||||
## Debugging within VSCode
|
||||
|
||||
Note this is still experimental, although I have managed to get breakpoints working under VSCode.
|
||||
So far I've only tried this within a windows environment.
|
||||
|
||||
In order to have the breakpoints land at the correct position.
|
||||
We need to install the following VSCode extension.
|
||||
|
||||
* [WebAssembly DWARF Debugging](https://marketplace.visualstudio.com/items?itemName=ms-vscode.wasm-dwarf-debugging)
|
||||
|
||||
Within the browser launch section under `launch.json` we need to set userDataDir to false in order for the DWARF browser extension to be loaded.
|
||||
```json
|
||||
{
|
||||
"name": "Launch Browser Chrome",
|
||||
"request": "launch",
|
||||
"type": "chrome",
|
||||
"url": "http://localhost:8080",
|
||||
"webRoot": "${workspaceFolder}/dist",
|
||||
// Needed to keep the dwarf extension in the browser
|
||||
"userDataDir": false,
|
||||
},
|
||||
```
|
||||
|
||||
Now we should be able to add breakpoints within visual studio code while debugging the rust wasm.
|
||||
|
||||

|
||||
@@ -1,4 +0,0 @@
|
||||
[serve]
|
||||
address = "127.0.0.1"
|
||||
port = 4001
|
||||
open = false
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 158 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 197 KiB |
@@ -1,8 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link data-trunk rel="rust" data-keep-debug="true" data-wasm-opt="z"/>
|
||||
<link data-trunk rel="icon" type="image/ico" href="/public/favicon.ico"/>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB |
@@ -1,2 +0,0 @@
|
||||
[toolchain]
|
||||
channel = "stable" # test change
|
||||
@@ -1,29 +0,0 @@
|
||||
use leptos::*;
|
||||
|
||||
/// A simple counter component.
|
||||
///
|
||||
/// You can use doc comments like this to document your component.
|
||||
#[component]
|
||||
pub fn SimpleCounter(
|
||||
/// The starting value for the counter
|
||||
initial_value: i32,
|
||||
/// The change that should be applied each time the button is clicked.
|
||||
step: i32,
|
||||
) -> impl IntoView {
|
||||
let (value, set_value) = create_signal(initial_value);
|
||||
|
||||
view! {
|
||||
<div>
|
||||
<button on:click=move |_| set_value.set(0)>"Clear"</button>
|
||||
<button on:click=move |_| set_value.update(|value| *value -= step)>"-1"</button>
|
||||
<span>"Value: " {value} "!"</span>
|
||||
<button on:click=move |_| {
|
||||
// Test Panic
|
||||
//panic!("Test Panic");
|
||||
// In order to breakpoint the below, the code needs to be on it's own line
|
||||
set_value.update(|value| *value += step)
|
||||
}
|
||||
>"+1"</button>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
use counter_dwarf_debug::SimpleCounter;
|
||||
use leptos::*;
|
||||
|
||||
pub fn main() {
|
||||
_ = console_log::init_with_level(log::Level::Debug);
|
||||
console_error_panic_hook::set_once();
|
||||
mount_to_body(|| {
|
||||
view! {
|
||||
<SimpleCounter
|
||||
initial_value=0
|
||||
step=1
|
||||
/>
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
pkg
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# node e2e test tools and outputs
|
||||
node_modules/
|
||||
test-results/
|
||||
end2end/playwright-report/
|
||||
playwright/.cache/
|
||||
|
||||
.secret_key
|
||||
@@ -1,117 +0,0 @@
|
||||
[package]
|
||||
name = "openapi-openai-api-swagger-ui"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
axum = { version = "0.7", optional = true }
|
||||
console_error_panic_hook = "0.1"
|
||||
leptos = { version = "0.6", features = ["nightly"] }
|
||||
leptos_axum = { version = "0.6", optional = true }
|
||||
leptos_meta = { version = "0.6", features = ["nightly"] }
|
||||
leptos_router = { version = "0.6", features = ["nightly"] }
|
||||
tokio = { version = "1", features = ["rt-multi-thread"], optional = true }
|
||||
tower = { version = "0.4", optional = true }
|
||||
tower-http = { version = "0.5", features = ["fs"], optional = true }
|
||||
wasm-bindgen = "=0.2.92"
|
||||
thiserror = "1"
|
||||
tracing = { version = "0.1", optional = true }
|
||||
utoipa = { version = "4.2.0", optional = true, features=["debug"] }
|
||||
utoipa-swagger-ui = { version = "6.0.0", optional = true , features = ["axum"]}
|
||||
http = "1"
|
||||
serde = "1.0.198"
|
||||
serde_json = {version = "1.0.116", optional = true}
|
||||
openai_dive = {version="0.4.7",optional=true}
|
||||
reqwest = "0.12.4"
|
||||
uuid = { version = "1.8.0", features = ["v4"]}
|
||||
|
||||
[features]
|
||||
hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"]
|
||||
ssr = [
|
||||
"dep:openai_dive",
|
||||
"dep:serde_json",
|
||||
"dep:utoipa-swagger-ui",
|
||||
"dep:utoipa",
|
||||
"dep:axum",
|
||||
"dep:tokio",
|
||||
"dep:tower",
|
||||
"dep:tower-http",
|
||||
"dep:leptos_axum",
|
||||
"leptos/ssr",
|
||||
"leptos_meta/ssr",
|
||||
"leptos_router/ssr",
|
||||
"dep:tracing",
|
||||
]
|
||||
|
||||
# Defines a size-optimized profile for the WASM bundle in release mode
|
||||
[profile.wasm-release]
|
||||
inherits = "release"
|
||||
opt-level = 'z'
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
panic = "abort"
|
||||
|
||||
[package.metadata.leptos]
|
||||
# The name used by wasm-bindgen/cargo-leptos for the JS/WASM bundle. Defaults to the crate name
|
||||
output-name = "openapi-swagger-ui"
|
||||
|
||||
# The site root folder is where cargo-leptos generate all output. WARNING: all content of this folder will be erased on a rebuild. Use it in your server setup.
|
||||
site-root = "target/site"
|
||||
|
||||
# The site-root relative folder where all compiled output (JS, WASM and CSS) is written
|
||||
# Defaults to pkg
|
||||
site-pkg-dir = "pkg"
|
||||
|
||||
# [Optional] The source CSS file. If it ends with .sass or .scss then it will be compiled by dart-sass into CSS. The CSS is optimized by Lightning CSS before being written to <site-root>/<site-pkg>/app.css
|
||||
style-file = "style/main.scss"
|
||||
# Assets source dir. All files found here will be copied and synchronized to site-root.
|
||||
# The assets-dir cannot have a sub directory with the same name/path as site-pkg-dir.
|
||||
#
|
||||
# Optional. Env: LEPTOS_ASSETS_DIR.
|
||||
assets-dir = "public"
|
||||
|
||||
# The IP and port (ex: 127.0.0.1:3000) where the server serves the content. Use it in your server setup.
|
||||
site-addr = "127.0.0.1:3000"
|
||||
|
||||
# The port to use for automatic reload monitoring
|
||||
reload-port = 3001
|
||||
|
||||
# [Optional] Command to use when running end2end tests. It will run in the end2end dir.
|
||||
# [Windows] for non-WSL use "npx.cmd playwright test"
|
||||
# This binary name can be checked in Powershell with Get-Command npx
|
||||
end2end-cmd = "npx playwright test"
|
||||
end2end-dir = "end2end"
|
||||
|
||||
# The browserlist query used for optimizing the CSS.
|
||||
browserquery = "defaults"
|
||||
|
||||
# The environment Leptos will run in, usually either "DEV" or "PROD"
|
||||
env = "DEV"
|
||||
|
||||
# The features to use when compiling the bin target
|
||||
#
|
||||
# Optional. Can be over-ridden with the command line parameter --bin-features
|
||||
bin-features = ["ssr"]
|
||||
|
||||
# If the --no-default-features flag should be used when compiling the bin target
|
||||
#
|
||||
# Optional. Defaults to false.
|
||||
bin-default-features = false
|
||||
|
||||
# The features to use when compiling the lib target
|
||||
#
|
||||
# Optional. Can be over-ridden with the command line parameter --lib-features
|
||||
lib-features = ["hydrate"]
|
||||
|
||||
# If the --no-default-features flag should be used when compiling the lib target
|
||||
#
|
||||
# Optional. Defaults to false.
|
||||
lib-default-features = false
|
||||
|
||||
# The profile to use for the lib target when compiling for release
|
||||
#
|
||||
# Optional. Defaults to "release".
|
||||
lib-profile-release = "wasm-release"
|
||||
@@ -1,24 +0,0 @@
|
||||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <https://unlicense.org>
|
||||
@@ -1,15 +0,0 @@
|
||||
#OpenAPI Swagger-Ui OpenAI GPT
|
||||
|
||||
This example shows how to document server functions via OpenAPI schema generated using Utoipa and serve the swagger ui via /swagger-ui endpoint. More than that, this example shows how to take said OpenAPI spec and turn it into a function list to feed to OpenAI's chat completion endpoint to generate the JSON values to feed back into our server functions.
|
||||
|
||||
The example shows an input and if you tell it to do something that is covered, say hello, or generate a list of names it will do that.
|
||||
|
||||
To use the AI part of this project provide your openAPI key in an environment variable when running cargo leptos.
|
||||
|
||||
```sh
|
||||
OPENAI_API_KEY=my_secret_key cargo leptos serve
|
||||
```
|
||||
|
||||
|
||||
## Thoughts, Feedback, Criticism, Comments?
|
||||
Send me any of the above, I'm @sjud on leptos discord. I'm always looking to improve and make these projects more helpful for the community. So please let me know how I can do that. Thanks!
|
||||
@@ -1,74 +0,0 @@
|
||||
{
|
||||
"name": "end2end",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "end2end",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.28.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"version": "1.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.28.0.tgz",
|
||||
"integrity": "sha512-vrHs5DFTPwYox5SGKq/7TDn/S4q6RA1zArd7uhO6EyP9hj3XgZBBM12ktMbnDQNxh/fL1IUKsTNLxihmsU38lQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"playwright-core": "1.28.0"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "18.11.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz",
|
||||
"integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/playwright-core": {
|
||||
"version": "1.28.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.28.0.tgz",
|
||||
"integrity": "sha512-nJLknd28kPBiCNTbqpu6Wmkrh63OEqJSFw9xOfL9qxfNwody7h6/L3O2dZoWQ6Oxcm0VOHjWmGiCUGkc0X3VZA==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@playwright/test": {
|
||||
"version": "1.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.28.0.tgz",
|
||||
"integrity": "sha512-vrHs5DFTPwYox5SGKq/7TDn/S4q6RA1zArd7uhO6EyP9hj3XgZBBM12ktMbnDQNxh/fL1IUKsTNLxihmsU38lQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*",
|
||||
"playwright-core": "1.28.0"
|
||||
}
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "18.11.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz",
|
||||
"integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==",
|
||||
"dev": true
|
||||
},
|
||||
"playwright-core": {
|
||||
"version": "1.28.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.28.0.tgz",
|
||||
"integrity": "sha512-nJLknd28kPBiCNTbqpu6Wmkrh63OEqJSFw9xOfL9qxfNwody7h6/L3O2dZoWQ6Oxcm0VOHjWmGiCUGkc0X3VZA==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"name": "end2end",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.28.0"
|
||||
}
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
import type { PlaywrightTestConfig } from "@playwright/test";
|
||||
import { devices } from "@playwright/test";
|
||||
|
||||
/**
|
||||
* Read environment variables from file.
|
||||
* https://github.com/motdotla/dotenv
|
||||
*/
|
||||
// require('dotenv').config();
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
const config: PlaywrightTestConfig = {
|
||||
testDir: "./tests",
|
||||
/* Maximum time one test can run for. */
|
||||
timeout: 30 * 1000,
|
||||
expect: {
|
||||
/**
|
||||
* Maximum time expect() should wait for the condition to be met.
|
||||
* For example in `await expect(locator).toHaveText();`
|
||||
*/
|
||||
timeout: 5000,
|
||||
},
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: true,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: !!process.env.CI,
|
||||
/* Retry on CI only */
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: "html",
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
|
||||
actionTimeout: 0,
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
// baseURL: 'http://localhost:3000',
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: "on-first-retry",
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: "chromium",
|
||||
use: {
|
||||
...devices["Desktop Chrome"],
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "firefox",
|
||||
use: {
|
||||
...devices["Desktop Firefox"],
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "webkit",
|
||||
use: {
|
||||
...devices["Desktop Safari"],
|
||||
},
|
||||
},
|
||||
|
||||
/* Test against mobile viewports. */
|
||||
// {
|
||||
// name: 'Mobile Chrome',
|
||||
// use: {
|
||||
// ...devices['Pixel 5'],
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// name: 'Mobile Safari',
|
||||
// use: {
|
||||
// ...devices['iPhone 12'],
|
||||
// },
|
||||
// },
|
||||
|
||||
/* Test against branded browsers. */
|
||||
// {
|
||||
// name: 'Microsoft Edge',
|
||||
// use: {
|
||||
// channel: 'msedge',
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// name: 'Google Chrome',
|
||||
// use: {
|
||||
// channel: 'chrome',
|
||||
// },
|
||||
// },
|
||||
],
|
||||
|
||||
/* Folder for test artifacts such as screenshots, videos, traces, etc. */
|
||||
// outputDir: 'test-results/',
|
||||
|
||||
/* Run your local dev server before starting the tests */
|
||||
// webServer: {
|
||||
// command: 'npm run start',
|
||||
// port: 3000,
|
||||
// },
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -1,9 +0,0 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
test("homepage has title and links to intro page", async ({ page }) => {
|
||||
await page.goto("http://localhost:3000/");
|
||||
|
||||
await expect(page).toHaveTitle("Welcome to Leptos");
|
||||
|
||||
await expect(page.locator("h1")).toHaveText("Welcome to Leptos!");
|
||||
});
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB |
@@ -1,3 +0,0 @@
|
||||
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
@@ -1,174 +0,0 @@
|
||||
use crate::error_template::{AppError, ErrorTemplate};
|
||||
use leptos::*;
|
||||
use leptos_meta::*;
|
||||
use leptos_router::*;
|
||||
|
||||
#[component]
|
||||
pub fn App() -> impl IntoView {
|
||||
// Provides context that manages stylesheets, titles, meta tags, etc.
|
||||
provide_meta_context();
|
||||
|
||||
view! {
|
||||
|
||||
|
||||
// injects a stylesheet into the document <head>
|
||||
// id=leptos means cargo-leptos will hot-reload this stylesheet
|
||||
<Stylesheet id="leptos" href="/pkg/openapi-swagger-ui.css"/>
|
||||
|
||||
// sets the document title
|
||||
<Title text="Welcome to Leptos"/>
|
||||
|
||||
// content for this welcome page
|
||||
<Router fallback=|| {
|
||||
let mut outside_errors = Errors::default();
|
||||
outside_errors.insert_with_default_key(AppError::NotFound);
|
||||
view! {
|
||||
<ErrorTemplate outside_errors/>
|
||||
}
|
||||
.into_view()
|
||||
}>
|
||||
<main>
|
||||
<Routes>
|
||||
<Route path="" view=HomePage/>
|
||||
</Routes>
|
||||
</main>
|
||||
</Router>
|
||||
}
|
||||
}
|
||||
|
||||
/// Renders the home page of your application.
|
||||
#[component]
|
||||
fn HomePage() -> impl IntoView {
|
||||
let hello = Action::<HelloWorld,_>::server();
|
||||
view! {
|
||||
<button on:click = move |_| hello.dispatch(HelloWorld{say_whut:SayHello{say:true}})>
|
||||
"hello world"
|
||||
</button>
|
||||
|
||||
|
||||
<ErrorBoundary
|
||||
fallback=|err| view! { <p>{format!("{err:#?}")}</p>}>
|
||||
{
|
||||
move || hello.value().get().map(|h|match h {
|
||||
Ok(h) => h.into_view(),
|
||||
err => err.into_view()
|
||||
})
|
||||
}
|
||||
</ErrorBoundary>
|
||||
|
||||
<AiSayHello/>
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature="ssr",derive(utoipa::ToSchema))]
|
||||
#[derive(Debug,Copy,Clone,serde::Serialize,serde::Deserialize)]
|
||||
pub struct SayHello {
|
||||
say:bool,
|
||||
}
|
||||
|
||||
// the following function comment is what our GPT will get
|
||||
/// Call to say hello world, or call to not say hello world.
|
||||
#[cfg_attr(feature="ssr",utoipa::path(
|
||||
post,
|
||||
path = "/api/hello_world",
|
||||
responses(
|
||||
(status = 200, description = "Hello world from server or maybe not?", body = String),
|
||||
),
|
||||
params(
|
||||
("say_whut" = SayHello, description = "If true then say hello, if false then don't."),
|
||||
)
|
||||
))]
|
||||
#[server(
|
||||
// we need to encoude our server functions as json because that's what openai generates
|
||||
input=server_fn::codec::Json,
|
||||
endpoint="hello_world"
|
||||
)]
|
||||
pub async fn hello_world(say_whut:SayHello) -> Result<String,ServerFnError> {
|
||||
if say_whut.say {
|
||||
Ok("hello world".to_string())
|
||||
} else {
|
||||
Ok("not hello".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Takes a list of names
|
||||
#[cfg_attr(feature="ssr",utoipa::path(
|
||||
post,
|
||||
path = "/api/name_list",
|
||||
responses(
|
||||
(status = 200, description = "The same list you got back", body = String),
|
||||
),
|
||||
params(
|
||||
("list" = Vec<String>, description = "A list of names"),
|
||||
)
|
||||
))]
|
||||
#[server(
|
||||
input=server_fn::codec::Json,
|
||||
endpoint="name_list"
|
||||
)]
|
||||
pub async fn name_list(list:Vec<String>) -> Result<Vec<String>,ServerFnError> {
|
||||
Ok(list)
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[derive(Clone,Debug,PartialEq,serde::Serialize,serde::Deserialize)]
|
||||
pub struct AiServerCall{
|
||||
pub path:String,
|
||||
pub args:String,
|
||||
}
|
||||
|
||||
|
||||
// Don't include our AI function in the OpenAPI
|
||||
#[server]
|
||||
pub async fn ai_msg(msg:String) -> Result<AiServerCall,ServerFnError> {
|
||||
crate::open_ai::call_gpt_with_api(msg).await.get(0).cloned().ok_or(ServerFnError::new("No first message"))
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn AiSayHello() -> impl IntoView {
|
||||
let ai_msg = Action::<AiMsg, _>::server();
|
||||
let result = create_rw_signal(Vec::new());
|
||||
view!{
|
||||
<ActionForm action=ai_msg>
|
||||
<label> "Tell the AI what function to call."
|
||||
<input name="msg"/>
|
||||
</label>
|
||||
<input type="submit"/>
|
||||
</ActionForm>
|
||||
<div>
|
||||
{
|
||||
move || if let Some(Ok(AiServerCall{path,args})) = ai_msg.value().get() {
|
||||
spawn_local(async move {
|
||||
let text =
|
||||
reqwest::Client::new()
|
||||
.post(format!("http://127.0.0.1:3000/api/{}",path))
|
||||
.header("content-type","application/json")
|
||||
.body(args)
|
||||
.send()
|
||||
.await
|
||||
.unwrap()
|
||||
.text()
|
||||
.await
|
||||
.unwrap();
|
||||
result.update(|list|
|
||||
list.push(
|
||||
text
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
<For
|
||||
each=move || result.get()
|
||||
key=|_| uuid::Uuid::new_v4()
|
||||
children=move |s:String| {
|
||||
view! {
|
||||
<p>{s}</p>
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
use http::status::StatusCode;
|
||||
use leptos::*;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Clone, Debug, Error)]
|
||||
pub enum AppError {
|
||||
#[error("Not Found")]
|
||||
NotFound,
|
||||
}
|
||||
|
||||
impl AppError {
|
||||
pub fn status_code(&self) -> StatusCode {
|
||||
match self {
|
||||
AppError::NotFound => StatusCode::NOT_FOUND,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A basic function to display errors served by the error boundaries.
|
||||
// Feel free to do more complicated things here than just displaying the error.
|
||||
#[component]
|
||||
pub fn ErrorTemplate(
|
||||
#[prop(optional)] outside_errors: Option<Errors>,
|
||||
#[prop(optional)] errors: Option<RwSignal<Errors>>,
|
||||
) -> impl IntoView {
|
||||
let errors = match outside_errors {
|
||||
Some(e) => create_rw_signal(e),
|
||||
None => match errors {
|
||||
Some(e) => e,
|
||||
None => panic!("No Errors found and we expected errors!"),
|
||||
},
|
||||
};
|
||||
// Get Errors from Signal
|
||||
let errors = errors.get_untracked();
|
||||
|
||||
// Downcast lets us take a type that implements `std::error::Error`
|
||||
let errors: Vec<AppError> = errors
|
||||
.into_iter()
|
||||
.filter_map(|(_k, v)| v.downcast_ref::<AppError>().cloned())
|
||||
.collect();
|
||||
println!("Errors: {errors:#?}");
|
||||
|
||||
// Only the response code for the first error is actually sent from the server
|
||||
// this may be customized by the specific application
|
||||
#[cfg(feature = "ssr")]
|
||||
{
|
||||
use leptos_axum::ResponseOptions;
|
||||
let response = use_context::<ResponseOptions>();
|
||||
if let Some(response) = response {
|
||||
response.set_status(errors[0].status_code());
|
||||
}
|
||||
}
|
||||
|
||||
view! {
|
||||
<h1>{if errors.len() > 1 {"Errors"} else {"Error"}}</h1>
|
||||
<For
|
||||
// a function that returns the items we're iterating over; a signal is fine
|
||||
each= move || {errors.clone().into_iter().enumerate()}
|
||||
// a unique key for each item as a reference
|
||||
key=|(index, _error)| *index
|
||||
// renders each item to a view
|
||||
children=move |error| {
|
||||
let error_string = error.1.to_string();
|
||||
let error_code= error.1.status_code();
|
||||
view! {
|
||||
<h2>{error_code.to_string()}</h2>
|
||||
<p>"Error: " {error_string}</p>
|
||||
}
|
||||
}
|
||||
/>
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
use axum::{
|
||||
body::Body,
|
||||
extract::State,
|
||||
response::IntoResponse,
|
||||
http::{Request, Response, StatusCode, Uri},
|
||||
};
|
||||
use axum::response::Response as AxumResponse;
|
||||
use tower::ServiceExt;
|
||||
use tower_http::services::ServeDir;
|
||||
use leptos::*;
|
||||
use crate::app::App;
|
||||
|
||||
pub async fn file_and_error_handler(uri: Uri, State(options): State<LeptosOptions>, req: Request<Body>) -> AxumResponse {
|
||||
let root = options.site_root.clone();
|
||||
let res = get_static_file(uri.clone(), &root).await.unwrap();
|
||||
|
||||
if res.status() == StatusCode::OK {
|
||||
res.into_response()
|
||||
} else {
|
||||
let handler = leptos_axum::render_app_to_stream(options.to_owned(), App);
|
||||
handler(req).await.into_response()
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_static_file(
|
||||
uri: Uri,
|
||||
root: &str,
|
||||
) -> Result<Response<Body>, (StatusCode, String)> {
|
||||
let req = Request::builder()
|
||||
.uri(uri.clone())
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
// `ServeDir` implements `tower::Service` so we can call it with `tower::ServiceExt::oneshot`
|
||||
// This path is relative to the cargo root
|
||||
match ServeDir::new(root).oneshot(req).await {
|
||||
Ok(res) => Ok(res.into_response()),
|
||||
Err(err) => Err((
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("Something went wrong: {err}"),
|
||||
)),
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
pub mod app;
|
||||
pub mod error_template;
|
||||
#[cfg(feature = "ssr")]
|
||||
pub mod fileserv;
|
||||
#[cfg(feature="ssr")]
|
||||
pub mod open_ai;
|
||||
#[cfg(feature = "hydrate")]
|
||||
#[wasm_bindgen::prelude::wasm_bindgen]
|
||||
pub fn hydrate() {
|
||||
use crate::app::*;
|
||||
console_error_panic_hook::set_once();
|
||||
leptos::mount_to_body(App);
|
||||
}
|
||||
|
||||
|
||||
#[cfg(feature="ssr")]
|
||||
pub mod api_doc {
|
||||
use crate::app::__path_hello_world;
|
||||
use crate::app::SayHello;
|
||||
use crate::app::__path_name_list;
|
||||
#[derive(utoipa::OpenApi)]
|
||||
#[openapi(
|
||||
info(description = "My Api description"),
|
||||
paths(hello_world,name_list), components(schemas(SayHello)),
|
||||
)]
|
||||
pub struct ApiDoc;
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
#[cfg(feature = "ssr")]
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
use axum::Router;
|
||||
use leptos::*;
|
||||
use leptos_axum::{generate_route_list, LeptosRoutes};
|
||||
use openapi_swagger_ui::app::*;
|
||||
use openapi_swagger_ui::api_doc::ApiDoc;
|
||||
use openapi_swagger_ui::fileserv::file_and_error_handler;
|
||||
use utoipa::OpenApi;
|
||||
|
||||
// Setting get_configuration(None) means we'll be using cargo-leptos's env values
|
||||
// For deployment these variables are:
|
||||
// <https://github.com/leptos-rs/start-axum#executing-a-server-on-a-remote-machine-without-the-toolchain>
|
||||
// Alternately a file can be specified such as Some("Cargo.toml")
|
||||
// The file would need to be included with the executable when moved to deployment
|
||||
let conf = get_configuration(None).await.unwrap();
|
||||
let leptos_options = conf.leptos_options;
|
||||
let addr = leptos_options.site_addr;
|
||||
let routes = generate_route_list(App);
|
||||
|
||||
// build our application with a route
|
||||
let app = Router::new()
|
||||
.leptos_routes(&leptos_options, routes, App)
|
||||
.fallback(file_and_error_handler)
|
||||
.merge(utoipa_swagger_ui::SwaggerUi::new("/swagger-ui")
|
||||
.url("/api-docs/openapi.json", ApiDoc::openapi()))
|
||||
.with_state(leptos_options);
|
||||
|
||||
let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
|
||||
logging::log!("listening on http://{}", &addr);
|
||||
axum::serve(listener, app.into_make_service())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
pub fn main() {
|
||||
// no client-side main function
|
||||
// unless we want this to work with e.g., Trunk for a purely client-side app
|
||||
// see lib.rs for hydration function instead
|
||||
}
|
||||
@@ -1,267 +0,0 @@
|
||||
/*
|
||||
Follows
|
||||
https://cookbook.openai.com/examples/function_calling_with_an_openapi_spec
|
||||
closely
|
||||
*/
|
||||
|
||||
pub static SYSTEM_MESSAGE :&'static str = "
|
||||
You are a helpful assistant.
|
||||
Respond to the following prompt by using function_call and then summarize actions.
|
||||
Ask for clarification if a user request is ambiguous.
|
||||
";
|
||||
use serde_json::Map;
|
||||
use openai_dive::v1::api::Client;
|
||||
use openai_dive::v1::models::Gpt4Engine;
|
||||
use std::env;
|
||||
use openai_dive::v1::resources::chat::{
|
||||
ChatCompletionFunction, ChatCompletionParameters, ChatCompletionTool, ChatCompletionToolType, ChatMessage,
|
||||
ChatMessageContent,Role,
|
||||
};
|
||||
use utoipa::openapi::schema::Array;
|
||||
use serde_json::Value;
|
||||
use utoipa::openapi::schema::SchemaType;
|
||||
use utoipa::openapi::schema::Schema;
|
||||
use utoipa::OpenApi;
|
||||
use serde_json::json;
|
||||
use utoipa::openapi::path::{PathItemType,Parameter};
|
||||
use utoipa::openapi::Required;
|
||||
use utoipa::openapi::schema::Object;
|
||||
use utoipa::openapi::RefOr;
|
||||
pub fn make_openapi_call_via_gpt(message:String) -> ChatCompletionParameters {
|
||||
let docs = super::api_doc::ApiDoc::openapi();
|
||||
let mut functions = vec![];
|
||||
// get each path and it's path item object
|
||||
for (path,path_item) in docs.paths.paths.iter(){
|
||||
// all our server functions are post.
|
||||
let operation = path_item.operations.get(&PathItemType::Post).expect("Expect POST op");
|
||||
// This name will be given to the OpenAI API as part of our functions
|
||||
let name = operation.operation_id.clone().expect("Each operation to have an operation id");
|
||||
|
||||
// we'll use the descrition
|
||||
let desc = operation.description.clone().expect("Each operation to have a description, this is how GPT knows what the functiond does and it is helpful for calling it.");
|
||||
let mut required_list = vec![];
|
||||
let mut properties = serde_json::Map::new();
|
||||
if let Some(params) = operation.parameters.clone() {
|
||||
leptos::logging::log!("{params:#?}");
|
||||
for Parameter{name,description,required,schema,..} in params.into_iter() {
|
||||
if required == Required::True {
|
||||
required_list.push(name.clone());
|
||||
}
|
||||
let description = description.unwrap_or_default();
|
||||
if let Some(RefOr::Ref(utoipa::openapi::schema::Ref{ref_location,..})) = schema {
|
||||
let schema_name = ref_location.split('/').last().expect("Expecting last after split");
|
||||
let RefOr::T(schema) = docs.components
|
||||
.as_ref()
|
||||
.expect("components")
|
||||
.schemas
|
||||
.get(schema_name)
|
||||
.cloned()
|
||||
.expect("{schema_name} to be in components as a schema") else {panic!("expecting T")};
|
||||
let mut output = Map::new();
|
||||
parse_schema_into_openapi_property(name.clone(),schema,&mut output);
|
||||
properties.insert(name,serde_json::Value::Object(output));
|
||||
} else if let Some(RefOr::T(schema)) = schema {
|
||||
let mut output = Map::new();
|
||||
parse_schema_into_openapi_property(name.clone(),schema,&mut output);
|
||||
properties.insert(name.clone(),serde_json::Value::Object(output));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
let parameters = json!({
|
||||
"type": "object",
|
||||
"properties": properties,
|
||||
"required": required_list,
|
||||
});
|
||||
leptos::logging::log!("{parameters}");
|
||||
|
||||
functions.push(
|
||||
ChatCompletionFunction {
|
||||
name,
|
||||
description: Some(desc),
|
||||
parameters,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
ChatCompletionParameters {
|
||||
model: Gpt4Engine::Gpt41106Preview.to_string(),
|
||||
messages: vec![
|
||||
ChatMessage {
|
||||
role:Role::System,
|
||||
content: ChatMessageContent::Text(SYSTEM_MESSAGE.to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
ChatMessage {
|
||||
role:Role::User,
|
||||
content: ChatMessageContent::Text(message),
|
||||
..Default::default()
|
||||
}],
|
||||
tools: Some(functions.into_iter().map(|function|{
|
||||
ChatCompletionTool {
|
||||
r#type: ChatCompletionToolType::Function,
|
||||
function,
|
||||
}
|
||||
}).collect::<Vec<ChatCompletionTool>>()),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn parse_schema_into_openapi_property(
|
||||
name:String,
|
||||
schema:Schema,
|
||||
output: &mut serde_json::Map::<String,serde_json::Value>) {
|
||||
|
||||
let docs = super::api_doc::ApiDoc::openapi();
|
||||
match schema {
|
||||
Schema::Object(Object{
|
||||
schema_type,
|
||||
required,
|
||||
properties,
|
||||
..
|
||||
}) => match schema_type{
|
||||
SchemaType::Object => {
|
||||
output.insert("type".to_string(),Value::String("object".to_string()));
|
||||
output.insert("required".to_string(),Value::Array(required.into_iter()
|
||||
.map(|s|Value::String(s))
|
||||
.collect::<Vec<Value>>()));
|
||||
output.insert("properties".to_string(),{
|
||||
let mut map = Map::new();
|
||||
for (key,val) in properties
|
||||
.into_iter()
|
||||
.map(|(key,val)|{
|
||||
let RefOr::T(schema) = val else {panic!("expecting t")};
|
||||
let mut output = Map::new();
|
||||
parse_schema_into_openapi_property(name.clone(),schema,&mut output);
|
||||
(key,output)
|
||||
}) {
|
||||
map.insert(key,Value::Object(val));
|
||||
}
|
||||
Value::Object(map)
|
||||
});
|
||||
|
||||
},
|
||||
SchemaType::Value => {
|
||||
panic!("not expecting Value here.");
|
||||
|
||||
},
|
||||
SchemaType::String => {
|
||||
output.insert("type".to_string(),serde_json::Value::String("string".to_string()));
|
||||
|
||||
},
|
||||
SchemaType::Integer => {
|
||||
output.insert("type".to_string(),serde_json::Value::String("integer".to_string()));
|
||||
|
||||
},
|
||||
SchemaType::Number => {
|
||||
output.insert("type".to_string(),serde_json::Value::String("number".to_string()));
|
||||
|
||||
},
|
||||
SchemaType::Boolean => {
|
||||
output.insert("type".to_string(),serde_json::Value::String("boolean".to_string()));
|
||||
|
||||
},
|
||||
SchemaType::Array => {
|
||||
output.insert("type".to_string(),serde_json::Value::String("array".to_string()));
|
||||
|
||||
},
|
||||
|
||||
},
|
||||
Schema::Array(Array{schema_type,items,..}) => {
|
||||
match schema_type {
|
||||
SchemaType::Array => {
|
||||
let mut map = Map::new();
|
||||
if let RefOr::Ref(utoipa::openapi::schema::Ref{ref_location,..}) = *items {
|
||||
let schema_name = ref_location.split('/').last().expect("Expecting last after split");
|
||||
let RefOr::T(schema) = docs.components
|
||||
.as_ref()
|
||||
.expect("components")
|
||||
.schemas
|
||||
.get(schema_name)
|
||||
.cloned()
|
||||
.expect("{schema_name} to be in components as a schema") else {panic!("expecting T")};
|
||||
let mut map = Map::new();
|
||||
parse_schema_into_openapi_property(name.clone(),schema,&mut map);
|
||||
output.insert(name.clone(),serde_json::Value::Object(map));
|
||||
} else if let RefOr::T(schema) = *items {
|
||||
let mut map = Map::new();
|
||||
parse_schema_into_openapi_property(name.clone(),schema,&mut map);
|
||||
output.insert(name,serde_json::Value::Object(map));
|
||||
}
|
||||
},
|
||||
_ => panic!("if schema is an array, then I'm expecting schema type to be an array ")
|
||||
}
|
||||
}
|
||||
_ => panic!("I don't know how to handle this yet.")
|
||||
}
|
||||
|
||||
}
|
||||
// let docs = super::api_doc::ApiDoc::openapi();
|
||||
use crate::app::AiServerCall;
|
||||
pub async fn call_gpt_with_api(message:String) -> Vec<AiServerCall> {
|
||||
let api_key = std::env::var("OPENAI_API_KEY").expect("$OPENAI_API_KEY is not set");
|
||||
|
||||
let client = Client::new(api_key);
|
||||
|
||||
let completion_parameters = make_openapi_call_via_gpt(message);
|
||||
|
||||
let result = client.chat().create(completion_parameters).await.unwrap();
|
||||
let message = result.choices[0].message.clone();
|
||||
let mut res = vec![];
|
||||
if let Some(tool_calls) = message.clone().tool_calls {
|
||||
for tool_call in tool_calls {
|
||||
let name = tool_call.function.name;
|
||||
let arguments = tool_call.function.arguments;
|
||||
res.push(AiServerCall{
|
||||
path:name,
|
||||
args:arguments,
|
||||
});
|
||||
}
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
/*
|
||||
def openapi_to_functions(openapi_spec):
|
||||
functions = []
|
||||
|
||||
for path, methods in openapi_spec["paths"].items():
|
||||
for method, spec_with_ref in methods.items():
|
||||
# 1. Resolve JSON references.
|
||||
spec = jsonref.replace_refs(spec_with_ref)
|
||||
|
||||
# 2. Extract a name for the functions.
|
||||
function_name = spec.get("operationId")
|
||||
|
||||
# 3. Extract a description and parameters.
|
||||
desc = spec.get("description") or spec.get("summary", "")
|
||||
|
||||
schema = {"type": "object", "properties": {}}
|
||||
|
||||
req_body = (
|
||||
spec.get("requestBody", {})
|
||||
.get("content", {})
|
||||
.get("application/json", {})
|
||||
.get("schema")
|
||||
)
|
||||
if req_body:
|
||||
schema["properties"]["requestBody"] = req_body
|
||||
|
||||
params = spec.get("parameters", [])
|
||||
if params:
|
||||
param_properties = {
|
||||
param["name"]: param["schema"]
|
||||
for param in params
|
||||
if "schema" in param
|
||||
}
|
||||
schema["properties"]["parameters"] = {
|
||||
"type": "object",
|
||||
"properties": param_properties,
|
||||
}
|
||||
|
||||
functions.append(
|
||||
{"type": "function", "function": {"name": function_name, "description": desc, "parameters": schema}}
|
||||
)
|
||||
|
||||
return functions */
|
||||
@@ -1,4 +0,0 @@
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
text-align: center;
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
POSTGRES_DB=blogs
|
||||
POSTGRES_USER=postgres
|
||||
POSTGRES_PASSWORD=password
|
||||
DATABASE_URL=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:5432/${POSTGRES_DB}?sslmode=disable
|
||||
2
projects/sitemap_axum/.gitignore
vendored
2
projects/sitemap_axum/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
.env
|
||||
Cargo.lock
|
||||
@@ -1,124 +0,0 @@
|
||||
[workspace]
|
||||
# The empty workspace here is to keep rust-analyzer satisfied
|
||||
|
||||
[package]
|
||||
name = "sitemap-axum"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
axum = { version = "0.7", optional = true }
|
||||
console_error_panic_hook = "0.1"
|
||||
leptos = { version = "0.6", features = ["nightly"] }
|
||||
leptos_axum = { version = "0.6", optional = true }
|
||||
leptos_meta = { version = "0.6", features = ["nightly"] }
|
||||
leptos_router = { version = "0.6", features = ["nightly"] }
|
||||
tokio = { version = "1", features = ["rt-multi-thread"], optional = true }
|
||||
tower = { version = "0.4", optional = true }
|
||||
tower-http = { version = "0.5", features = ["fs"], optional = true }
|
||||
wasm-bindgen = "=0.2.92"
|
||||
thiserror = "1"
|
||||
tracing = { version = "0.1", optional = true }
|
||||
http = "1"
|
||||
|
||||
# Example specific crates
|
||||
sqlx = { version = "0.7", features = [
|
||||
"postgres",
|
||||
"runtime-tokio",
|
||||
"tls-rustls",
|
||||
"time",
|
||||
], optional = true }
|
||||
xml = { version = "0.8", optional = true }
|
||||
time = { version = "0.3", features = ["macros", "serde", "formatting"] }
|
||||
dotenvy = "0.15"
|
||||
anyhow = "1"
|
||||
|
||||
[features]
|
||||
hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"]
|
||||
ssr = [
|
||||
"dep:axum",
|
||||
"dep:tokio",
|
||||
"dep:tower",
|
||||
"dep:tower-http",
|
||||
"dep:leptos_axum",
|
||||
"leptos/ssr",
|
||||
"leptos_meta/ssr",
|
||||
"leptos_router/ssr",
|
||||
"dep:tracing",
|
||||
"dep:sqlx",
|
||||
"dep:xml",
|
||||
]
|
||||
|
||||
# Defines a size-optimized profile for the WASM bundle in release mode
|
||||
[profile.wasm-release]
|
||||
inherits = "release"
|
||||
opt-level = 'z'
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
panic = "abort"
|
||||
|
||||
[package.metadata.leptos]
|
||||
# The name used by wasm-bindgen/cargo-leptos for the JS/WASM bundle. Defaults to the crate name
|
||||
output-name = "sitemap-axum"
|
||||
|
||||
# The site root folder is where cargo-leptos generate all output. WARNING: all content of this folder will be erased on a rebuild. Use it in your server setup.
|
||||
site-root = "target/site"
|
||||
|
||||
# The site-root relative folder where all compiled output (JS, WASM and CSS) is written
|
||||
# Defaults to pkg
|
||||
site-pkg-dir = "pkg"
|
||||
|
||||
# [Optional] The source CSS file. If it ends with .sass or .scss then it will be compiled by dart-sass into CSS. The CSS is optimized by Lightning CSS before being written to <site-root>/<site-pkg>/app.css
|
||||
# style-file = "style/main.scss"
|
||||
|
||||
# Assets source dir. All files found here will be copied and synchronized to site-root.
|
||||
# The assets-dir cannot have a sub directory with the same name/path as site-pkg-dir.
|
||||
#
|
||||
# Optional. Env: LEPTOS_ASSETS_DIR.
|
||||
assets-dir = "public"
|
||||
|
||||
# The IP and port (ex: 127.0.0.1:3000) where the server serves the content. Use it in your server setup.
|
||||
site-addr = "127.0.0.1:3000"
|
||||
|
||||
# The port to use for automatic reload monitoring
|
||||
reload-port = 3001
|
||||
|
||||
# [Optional] Command to use when running end2end tests. It will run in the end2end dir.
|
||||
# [Windows] for non-WSL use "npx.cmd playwright test"
|
||||
# This binary name can be checked in Powershell with Get-Command npx
|
||||
# end2end-cmd = "npx playwright test"
|
||||
# end2end-dir = "end2end"
|
||||
|
||||
# The browserlist query used for optimizing the CSS.
|
||||
browserquery = "defaults"
|
||||
|
||||
# The environment Leptos will run in, usually either "DEV" or "PROD"
|
||||
env = "DEV"
|
||||
|
||||
# The features to use when compiling the bin target
|
||||
#
|
||||
# Optional. Can be over-ridden with the command line parameter --bin-features
|
||||
bin-features = ["ssr"]
|
||||
|
||||
# If the --no-default-features flag should be used when compiling the bin target
|
||||
#
|
||||
# Optional. Defaults to false.
|
||||
bin-default-features = false
|
||||
|
||||
# The features to use when compiling the lib target
|
||||
#
|
||||
# Optional. Can be over-ridden with the command line parameter --lib-features
|
||||
lib-features = ["hydrate"]
|
||||
|
||||
# If the --no-default-features flag should be used when compiling the lib target
|
||||
#
|
||||
# Optional. Defaults to false.
|
||||
lib-default-features = false
|
||||
|
||||
# The profile to use for the lib target when compiling for release
|
||||
#
|
||||
# Optional. Defaults to "release".
|
||||
lib-profile-release = "wasm-release"
|
||||
@@ -1,24 +0,0 @@
|
||||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <https://unlicense.org>
|
||||
@@ -1,36 +0,0 @@
|
||||
[tasks.run]
|
||||
command = "cargo"
|
||||
args = ["leptos", "watch"]
|
||||
dependencies = ["start-db"]
|
||||
|
||||
[tasks.start-db]
|
||||
command = "docker"
|
||||
args = ["start", "blog_db"]
|
||||
|
||||
[tasks.run-db]
|
||||
command = "docker"
|
||||
args = [
|
||||
"run",
|
||||
"-d",
|
||||
"--name",
|
||||
"blog_db",
|
||||
"-p",
|
||||
"5432:5432",
|
||||
"--env-file",
|
||||
"./.env",
|
||||
"-v",
|
||||
"./init:/docker-entrypoint-initdb.d",
|
||||
"postgres:latest",
|
||||
]
|
||||
|
||||
[tasks.stop-db]
|
||||
command = "docker"
|
||||
args = ["stop", "blog_db"]
|
||||
|
||||
[tasks.drop-db]
|
||||
command = "docker"
|
||||
args = ["rm", "blog_db"]
|
||||
dependencies = ["stop-db"]
|
||||
|
||||
[tasks.restart-db]
|
||||
dependencies = ["drop-db", "start-db"]
|
||||
@@ -1,20 +0,0 @@
|
||||
# Sitemaps with Axum
|
||||
|
||||
This project demonstrates how to serve a [sitemap](https://developers.google.com/search/docs/crawling-indexing/sitemaps/overview) file using Axum using dynamic data (like blog posts in this case). An example Postgres database is used data source for storing blog post data that can be used to generate a dynamic site map based on blog post slugs. There's lots of [sitemap crates](https://crates.io/search?q=sitemap), though this example uses the [xml](https://crates.io/crates/xml) for example purposes.
|
||||
|
||||
## Quick Start
|
||||
|
||||
We use Docker to provide a Postgres database for this sample, so make sure you have it installed.
|
||||
|
||||
```sh
|
||||
$ docker -v
|
||||
Docker version 25.0.3, build 4debf41
|
||||
```
|
||||
|
||||
Once Docker has started on you local machine, run (make sure to have `cargo-make` installed):
|
||||
|
||||
```sh
|
||||
$ cargo make run
|
||||
```
|
||||
|
||||
This will handle spinning up a Postgres container, initializing the example database, and launching the local dev server.
|
||||
@@ -1,32 +0,0 @@
|
||||
-- The database initialization script is used for defining your local schema as well as postgres
|
||||
-- running within a docker container, where we'll copy this file over and run on startup
|
||||
|
||||
DO
|
||||
$$
|
||||
BEGIN
|
||||
IF
|
||||
NOT EXISTS (SELECT 1 FROM pg_database WHERE datname = 'blogs') THEN
|
||||
CREATE DATABASE blogs;
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
\c blogs;
|
||||
|
||||
DROP TABLE IF EXISTS posts;
|
||||
CREATE TABLE posts
|
||||
(
|
||||
id SERIAL PRIMARY KEY,
|
||||
slug VARCHAR(255) UNIQUE NOT NULL,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
content VARCHAR(255) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
INSERT INTO posts (slug, title, content)
|
||||
VALUES ('first-post', 'First Post', 'This is the content of the first post.'),
|
||||
('second-post', 'Second Post', 'Here is some more content for another post.'),
|
||||
('hello-world', 'Hello World', 'Yet another post to add to our collection.'),
|
||||
('tech-talk', 'Tech Talk', 'Discussing the latest in technology.'),
|
||||
('travel-diaries', 'Travel Diaries', 'Sharing my experiences traveling around the world.');
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB |
@@ -1,3 +0,0 @@
|
||||
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
@@ -1,38 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9">
|
||||
<url>
|
||||
<loc>https://mywebsite.com/blog/first-post</loc>
|
||||
<lastmod>2024-04-23T17:28:07Z</lastmod>
|
||||
<changefreq>yearly</changefreq>
|
||||
<priority>0.5</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://mywebsite.com/blog/second-post</loc>
|
||||
<lastmod>2024-04-23T17:28:07Z</lastmod>
|
||||
<changefreq>yearly</changefreq>
|
||||
<priority>0.5</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://mywebsite.com/blog/hello-world</loc>
|
||||
<lastmod>2024-04-23T17:28:07Z</lastmod>
|
||||
<changefreq>yearly</changefreq>
|
||||
<priority>0.5</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://mywebsite.com/blog/tech-talk</loc>
|
||||
<lastmod>2024-04-23T17:28:07Z</lastmod>
|
||||
<changefreq>yearly</changefreq>
|
||||
<priority>0.5</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://mywebsite.com/blog/travel-diaries</loc>
|
||||
<lastmod>2024-04-23T17:28:07Z</lastmod>
|
||||
<changefreq>yearly</changefreq>
|
||||
<priority>0.5</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://mywebsite.com</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
</urlset>
|
||||
@@ -1,13 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9">
|
||||
<url>
|
||||
<loc>https://mywebsite.com</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://mywebsite.com/about</loc>
|
||||
<changefreq>yearly</changefreq>
|
||||
<priority>0.5</priority>
|
||||
</url>
|
||||
</urlset>
|
||||
@@ -1,48 +0,0 @@
|
||||
use crate::error_template::{AppError, ErrorTemplate};
|
||||
use leptos::*;
|
||||
use leptos_meta::*;
|
||||
use leptos_router::*;
|
||||
|
||||
#[component]
|
||||
pub fn App() -> impl IntoView {
|
||||
// Provides context that manages stylesheets, titles, meta tags, etc.
|
||||
provide_meta_context();
|
||||
|
||||
view! {
|
||||
|
||||
|
||||
// injects a stylesheet into the document <head>
|
||||
// id=leptos means cargo-leptos will hot-reload this stylesheet
|
||||
<Stylesheet id="leptos" href="/pkg/sitemap-axum.css"/>
|
||||
|
||||
// sets the document title
|
||||
<Title text="Welcome to Leptos"/>
|
||||
|
||||
// content for this welcome page
|
||||
<Router fallback=|| {
|
||||
let mut outside_errors = Errors::default();
|
||||
outside_errors.insert_with_default_key(AppError::NotFound);
|
||||
view! {
|
||||
<ErrorTemplate outside_errors/>
|
||||
}
|
||||
.into_view()
|
||||
}>
|
||||
<main>
|
||||
<Routes>
|
||||
<Route path="" view=HomePage/>
|
||||
</Routes>
|
||||
</main>
|
||||
</Router>
|
||||
}
|
||||
}
|
||||
|
||||
/// Renders the home page of your application.
|
||||
#[component]
|
||||
fn HomePage() -> impl IntoView {
|
||||
view! {
|
||||
<h1>"Welcome to Leptos!"</h1>
|
||||
// Typically, you won't route to these files manually - a crawler of sorts will take care of that
|
||||
<a href="http://localhost:3000/sitemap-index.xml">"Generate dynamic sitemap"</a>
|
||||
<a style="padding-left: 1em;" href="http://localhost:3000/sitemap-static.xml">"Go to static sitemap"</a>
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
use http::status::StatusCode;
|
||||
use leptos::*;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Clone, Debug, Error)]
|
||||
pub enum AppError {
|
||||
#[error("Not Found")]
|
||||
NotFound,
|
||||
}
|
||||
|
||||
impl AppError {
|
||||
pub fn status_code(&self) -> StatusCode {
|
||||
match self {
|
||||
AppError::NotFound => StatusCode::NOT_FOUND,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A basic function to display errors served by the error boundaries.
|
||||
// Feel free to do more complicated things here than just displaying the error.
|
||||
#[component]
|
||||
pub fn ErrorTemplate(
|
||||
#[prop(optional)] outside_errors: Option<Errors>,
|
||||
#[prop(optional)] errors: Option<RwSignal<Errors>>,
|
||||
) -> impl IntoView {
|
||||
let errors = match outside_errors {
|
||||
Some(e) => create_rw_signal(e),
|
||||
None => match errors {
|
||||
Some(e) => e,
|
||||
None => panic!("No Errors found and we expected errors!"),
|
||||
},
|
||||
};
|
||||
// Get Errors from Signal
|
||||
let errors = errors.get_untracked();
|
||||
|
||||
// Downcast lets us take a type that implements `std::error::Error`
|
||||
let errors: Vec<AppError> = errors
|
||||
.into_iter()
|
||||
.filter_map(|(_k, v)| v.downcast_ref::<AppError>().cloned())
|
||||
.collect();
|
||||
println!("Errors: {errors:#?}");
|
||||
|
||||
// Only the response code for the first error is actually sent from the server
|
||||
// this may be customized by the specific application
|
||||
#[cfg(feature = "ssr")]
|
||||
{
|
||||
use leptos_axum::ResponseOptions;
|
||||
let response = use_context::<ResponseOptions>();
|
||||
if let Some(response) = response {
|
||||
response.set_status(errors[0].status_code());
|
||||
}
|
||||
}
|
||||
|
||||
view! {
|
||||
<h1>{if errors.len() > 1 {"Errors"} else {"Error"}}</h1>
|
||||
<For
|
||||
// a function that returns the items we're iterating over; a signal is fine
|
||||
each= move || {errors.clone().into_iter().enumerate()}
|
||||
// a unique key for each item as a reference
|
||||
key=|(index, _error)| *index
|
||||
// renders each item to a view
|
||||
children=move |error| {
|
||||
let error_string = error.1.to_string();
|
||||
let error_code= error.1.status_code();
|
||||
view! {
|
||||
<h2>{error_code.to_string()}</h2>
|
||||
<p>"Error: " {error_string}</p>
|
||||
}
|
||||
}
|
||||
/>
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
use crate::app::App;
|
||||
use axum::{
|
||||
body::Body,
|
||||
extract::State,
|
||||
http::{Request, Response, StatusCode, Uri},
|
||||
response::{IntoResponse, Response as AxumResponse},
|
||||
};
|
||||
use leptos::*;
|
||||
use tower::ServiceExt;
|
||||
use tower_http::services::ServeDir;
|
||||
|
||||
pub async fn file_and_error_handler(
|
||||
uri: Uri,
|
||||
State(options): State<LeptosOptions>,
|
||||
req: Request<Body>,
|
||||
) -> AxumResponse {
|
||||
let root = options.site_root.clone();
|
||||
let res = get_static_file(uri.clone(), &root).await.unwrap();
|
||||
|
||||
if res.status() == StatusCode::OK {
|
||||
res.into_response()
|
||||
} else {
|
||||
let handler =
|
||||
leptos_axum::render_app_to_stream(options.to_owned(), App);
|
||||
handler(req).await.into_response()
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_static_file(
|
||||
uri: Uri,
|
||||
root: &str,
|
||||
) -> Result<Response<Body>, (StatusCode, String)> {
|
||||
let req = Request::builder()
|
||||
.uri(uri.clone())
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
// `ServeDir` implements `tower::Service` so we can call it with `tower::ServiceExt::oneshot`
|
||||
// This path is relative to the cargo root
|
||||
match ServeDir::new(root).oneshot(req).await {
|
||||
Ok(res) => Ok(res.into_response()),
|
||||
Err(err) => Err((
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("Something went wrong: {err}"),
|
||||
)),
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
pub mod app;
|
||||
pub mod error_template;
|
||||
#[cfg(feature = "ssr")]
|
||||
pub mod fileserv;
|
||||
#[cfg(feature = "ssr")]
|
||||
pub mod sitemap;
|
||||
|
||||
#[cfg(feature = "hydrate")]
|
||||
#[wasm_bindgen::prelude::wasm_bindgen]
|
||||
pub fn hydrate() {
|
||||
use crate::app::*;
|
||||
console_error_panic_hook::set_once();
|
||||
leptos::mount_to_body(App);
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
#[cfg(feature = "ssr")]
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
use axum::{routing::get, Router};
|
||||
use leptos::*;
|
||||
use leptos_axum::{generate_route_list, LeptosRoutes};
|
||||
use sitemap_axum::{
|
||||
app::*, fileserv::file_and_error_handler, sitemap::generate_sitemap,
|
||||
};
|
||||
use tower_http::services::ServeFile;
|
||||
|
||||
// Setting get_configuration(None) means we'll be using cargo-leptos's env values
|
||||
// For deployment these variables are:
|
||||
// <https://github.com/leptos-rs/start-axum#executing-a-server-on-a-remote-machine-without-the-toolchain>
|
||||
// Alternately a file can be specified such as Some("Cargo.toml")
|
||||
// The file would need to be included with the executable when moved to deployment
|
||||
let conf = get_configuration(None).await.unwrap();
|
||||
let leptos_options = conf.leptos_options;
|
||||
let addr = leptos_options.site_addr;
|
||||
let routes = generate_route_list(App);
|
||||
|
||||
// Build our application with a route
|
||||
let app = Router::new()
|
||||
// We can use Axum to mount a route that serves a sitemap file that we can generate with dynamic data
|
||||
.route("/sitemap-index.xml", get(generate_sitemap))
|
||||
// Using tower's serve file service, we can also serve a static sitemap file for relatively small sites too
|
||||
.route_service(
|
||||
"/sitemap-static.xml",
|
||||
ServeFile::new("sitemap-static.xml"),
|
||||
)
|
||||
.leptos_routes(&leptos_options, routes, App)
|
||||
.fallback(file_and_error_handler)
|
||||
.with_state(leptos_options);
|
||||
|
||||
let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
|
||||
logging::log!("listening on http://{}", &addr);
|
||||
axum::serve(listener, app.into_make_service())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
pub fn main() {
|
||||
// no client-side main function
|
||||
// unless we want this to work with e.g., Trunk for a purely client-side app
|
||||
// see lib.rs for hydration function instead
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
use axum::{
|
||||
body::Body,
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use sqlx::{PgPool, Pool, Postgres};
|
||||
use std::{
|
||||
env::{self, current_dir},
|
||||
fs::File,
|
||||
io::{BufWriter, Read},
|
||||
path::Path,
|
||||
};
|
||||
use time::{format_description, PrimitiveDateTime};
|
||||
use xml::{writer::XmlEvent, EmitterConfig, EventWriter};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Post {
|
||||
slug: String,
|
||||
updated_at: PrimitiveDateTime,
|
||||
}
|
||||
|
||||
/// Generates a sitemap based on data stored in a database containing slugs that we can use to build URLs to the posts themselves.
|
||||
pub async fn generate_sitemap() -> impl IntoResponse {
|
||||
dotenvy::dotenv().ok();
|
||||
|
||||
// Depending on your preference, we can dynamically servce the sitemap file for each,
|
||||
// or generate the file once on the first visit (probably from a bot) and write it to disk
|
||||
// so we can simply serve the created file instead of having to query the database every time
|
||||
let sitemap_path = format!(
|
||||
"{}/sitemap-index.xml",
|
||||
¤t_dir().unwrap().to_str().unwrap()
|
||||
);
|
||||
let path = Path::new(&sitemap_path);
|
||||
|
||||
// If the doesn't exist, we've probably deployed a fresh version of our Leptos site somewhere so we'll generate it on first request
|
||||
if !path.exists() {
|
||||
let pool = PgPool::connect(
|
||||
&env::var("DATABASE_URL").expect("database URL to exist"),
|
||||
)
|
||||
.await
|
||||
.expect("to be able to connect to pool");
|
||||
|
||||
create_sitemap_file(path, pool).await.ok();
|
||||
}
|
||||
|
||||
// Once the file has been written, grab the contents of it and write it out as an XML file in the response
|
||||
let mut file = File::open(sitemap_path).unwrap();
|
||||
let mut contents = vec![];
|
||||
file.read_to_end(&mut contents).ok();
|
||||
let body = Body::from(contents);
|
||||
|
||||
Response::builder()
|
||||
.header("Content-Type", "application/xml")
|
||||
// Cache control can be helpful for cases where your site might be deployed occassionally and the original
|
||||
// sitemap that was generated can be cached with a header
|
||||
.header("Cache-Control", "max-age=86400")
|
||||
.body(body)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
async fn create_sitemap_file(
|
||||
path: &Path,
|
||||
pool: Pool<Postgres>,
|
||||
) -> anyhow::Result<()> {
|
||||
let file = File::create(path).expect("sitemap file to be created");
|
||||
let file = BufWriter::new(file);
|
||||
let mut writer = EmitterConfig::new()
|
||||
.perform_indent(true)
|
||||
.create_writer(file);
|
||||
|
||||
writer
|
||||
.write(
|
||||
XmlEvent::start_element("urlset")
|
||||
.attr("xmlns", "http://www.sitemaps.org/schemas/sitemap/0.9")
|
||||
.attr("xmlns:xhtml", "http://www.w3.org/1999/xhtml")
|
||||
.attr(
|
||||
"xmlns:image",
|
||||
"http://www.google.com/schemas/sitemap-image/1.1",
|
||||
)
|
||||
.attr(
|
||||
"xmlns:video",
|
||||
"http://www.google.com/schemas/sitemap-video/1.1",
|
||||
)
|
||||
.attr(
|
||||
"xmlns:news",
|
||||
"http://www.google.com/schemas/sitemap-news/0.9",
|
||||
),
|
||||
)
|
||||
.expect("xml header to be written");
|
||||
|
||||
// We could also pull this from configuration or an environment variable
|
||||
let app_url = "https://mywebsite.com";
|
||||
|
||||
// First, read all the blog entries so we can get the slug for building,
|
||||
// URLs and the updated date to determine the change frequency
|
||||
sqlx::query_as!(
|
||||
Post,
|
||||
r#"
|
||||
SELECT slug,
|
||||
updated_at
|
||||
FROM posts
|
||||
ORDER BY updated_at DESC
|
||||
"#
|
||||
)
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
.expect("")
|
||||
.into_iter()
|
||||
.try_for_each(|p| write_post_entry(p, app_url, &mut writer))?;
|
||||
|
||||
// Next, write the static pages and close the XML stream
|
||||
write_static_page_entry(app_url, &mut writer)?;
|
||||
|
||||
writer.write(XmlEvent::end_element())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_post_entry(
|
||||
post: Post,
|
||||
app_url: &str,
|
||||
writer: &mut EventWriter<BufWriter<File>>,
|
||||
) -> anyhow::Result<()> {
|
||||
let format = format_description::parse(
|
||||
"[year]-[month]-[day]T[hour]:[minute]:[second]Z",
|
||||
)?;
|
||||
let parsed_date = post.updated_at.format(&format)?;
|
||||
let route = format!("{}/blog/{}", app_url, post.slug);
|
||||
|
||||
writer.write(XmlEvent::start_element("url"))?;
|
||||
writer.write(XmlEvent::start_element("loc"))?;
|
||||
writer.write(XmlEvent::characters(&route))?;
|
||||
writer.write(XmlEvent::end_element())?;
|
||||
writer.write(XmlEvent::start_element("lastmod"))?;
|
||||
writer.write(XmlEvent::characters(&parsed_date))?;
|
||||
writer.write(XmlEvent::end_element())?;
|
||||
writer.write(XmlEvent::start_element("changefreq"))?;
|
||||
writer.write(XmlEvent::characters("yearly"))?;
|
||||
writer.write(XmlEvent::end_element())?;
|
||||
writer.write(XmlEvent::start_element("priority"))?;
|
||||
writer.write(XmlEvent::characters("0.5"))?;
|
||||
writer.write(XmlEvent::end_element())?;
|
||||
writer.write(XmlEvent::end_element())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_static_page_entry(
|
||||
route: &str,
|
||||
writer: &mut EventWriter<BufWriter<File>>,
|
||||
) -> anyhow::Result<()> {
|
||||
write_entry(route, "weekly", "0.8", writer)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_entry(
|
||||
route: &str,
|
||||
change_frequency: &str,
|
||||
priority: &str,
|
||||
writer: &mut EventWriter<BufWriter<File>>,
|
||||
) -> anyhow::Result<()> {
|
||||
writer.write(XmlEvent::start_element("url"))?;
|
||||
writer.write(XmlEvent::start_element("loc"))?;
|
||||
writer.write(XmlEvent::characters(route))?;
|
||||
writer.write(XmlEvent::end_element())?;
|
||||
writer.write(XmlEvent::start_element("changefreq"))?;
|
||||
writer.write(XmlEvent::characters(change_frequency))?;
|
||||
writer.write(XmlEvent::end_element())?;
|
||||
writer.write(XmlEvent::start_element("priority"))?;
|
||||
writer.write(XmlEvent::characters(priority))?;
|
||||
writer.write(XmlEvent::end_element())?;
|
||||
writer.write(XmlEvent::end_element())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -30,6 +30,8 @@ impl ParamsMap {
|
||||
/// Inserts a value into the map.
|
||||
#[inline(always)]
|
||||
pub fn insert(&mut self, key: String, value: String) -> Option<String> {
|
||||
use crate::history::url::unescape;
|
||||
let value = unescape(&value);
|
||||
self.0.insert(key, value)
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ pub fn unescape(s: &str) -> String {
|
||||
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
pub fn unescape(s: &str) -> String {
|
||||
js_sys::decode_uri_component(s).unwrap().into()
|
||||
js_sys::decode_uri(s).unwrap().into()
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
@@ -36,7 +36,7 @@ pub fn escape(s: &str) -> String {
|
||||
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
pub fn escape(s: &str) -> String {
|
||||
js_sys::encode_uri_component(s).as_string().unwrap()
|
||||
js_sys::encode_uri(s).as_string().unwrap()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Implementation based on Solid Router
|
||||
// see <https://github.com/solidjs/solid-router/blob/main/src/utils.ts>
|
||||
|
||||
use crate::{unescape, ParamsMap};
|
||||
use crate::ParamsMap;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[doc(hidden)]
|
||||
@@ -68,10 +68,7 @@ impl Matcher {
|
||||
self.segments.iter().zip(loc_segments.iter())
|
||||
{
|
||||
if let Some(param_name) = segment.strip_prefix(':') {
|
||||
params.insert(
|
||||
param_name.into(),
|
||||
unescape(*loc_segment).into(),
|
||||
);
|
||||
params.insert(param_name.into(), (*loc_segment).into());
|
||||
} else if segment != loc_segment {
|
||||
// if any segment doesn't match and isn't a param, there's no path match
|
||||
return None;
|
||||
|
||||
@@ -33,7 +33,6 @@ use syn::__private::ToTokens;
|
||||
/// - `endpoint`: specifies the exact path at which the server function handler will be mounted,
|
||||
/// relative to the prefix (defaults to the function name followed by unique hash)
|
||||
/// - `input`: the encoding for the arguments (defaults to `PostUrl`)
|
||||
/// - `input_derive`: a list of derives to be added on the generated input struct (defaults to `(Clone, serde::Serialize, serde::Deserialize)` if `input` is set to a custom struct, won't have an effect otherwise)
|
||||
/// - `output`: the encoding for the response (defaults to `Json`)
|
||||
/// - `client`: a custom `Client` implementation that will be used for this server fn
|
||||
/// - `encoding`: (legacy, may be deprecated in future) specifies the encoding, which may be one
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user