mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-14 00:27:12 +00:00
work on routing
This commit is contained in:
parent
3e3359bea1
commit
3379462633
7 changed files with 312 additions and 54 deletions
|
@ -1,7 +1,10 @@
|
|||
use gloo_timers::future::TimeoutFuture;
|
||||
use leptos::{
|
||||
prelude::*,
|
||||
reactive_graph::{computed::AsyncDerived, signal::RwSignal},
|
||||
reactive_graph::{
|
||||
computed::{AsyncDerived, AsyncState},
|
||||
signal::RwSignal,
|
||||
},
|
||||
tachys::log,
|
||||
view, IntoView,
|
||||
};
|
||||
|
@ -30,21 +33,16 @@ pub fn async_example() -> impl IntoView {
|
|||
let a = RwSignal::new(0);
|
||||
let b = RwSignal::new(1);
|
||||
|
||||
let a2 = create_resource(a, |a| wait('A', 1, a));
|
||||
let b2 = create_resource(b, |a| wait('A', 1, b));
|
||||
let a2 = || wait('A', 1, 1);
|
||||
let b2 = || wait('B', 3, 3);
|
||||
|
||||
//let a2 = create_resource(a, |a| wait('A', 1, a));
|
||||
//let b2 = create_resource(b, |a| wait('A', 1, b));
|
||||
|
||||
view! {
|
||||
<button on:click=move |_| {
|
||||
a.update(|n| *n += 1);
|
||||
}>
|
||||
{a}
|
||||
</button>
|
||||
<button on:click=move |_| {
|
||||
b.update(|n| *n += 1);
|
||||
}>
|
||||
{b}
|
||||
</button>
|
||||
<Suspense>
|
||||
<button on:click=move |_| a.update(|n| *n += 1)> {a} </button> " "
|
||||
<button on:click=move |_| b.update(|n| *n += 1)> {b} </button>
|
||||
/*<Suspense>
|
||||
{move || a2().map(move |a2| {
|
||||
b2().map(move |b2| {
|
||||
view! {
|
||||
|
@ -52,7 +50,7 @@ pub fn async_example() -> impl IntoView {
|
|||
}
|
||||
})
|
||||
})}
|
||||
</Suspense>
|
||||
</Suspense>*/
|
||||
<p>
|
||||
//{times}
|
||||
</p>
|
||||
|
|
|
@ -29,7 +29,7 @@ impl Location for RequestUrl {
|
|||
|
||||
fn parse_with_base(url: &str, base: &str) -> Result<Url, Self::Error> {
|
||||
let base = url::Url::parse(base)?;
|
||||
let url = url::Url::options().base_url(Some(&base)).parse(&url)?;
|
||||
let url = url::Url::options().base_url(Some(&base)).parse(url)?;
|
||||
|
||||
let search_params = url
|
||||
.query_pairs()
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use crate::PathSegment;
|
||||
use alloc::vec::Vec;
|
||||
use core::str::Chars;
|
||||
|
||||
mod param_segments;
|
||||
mod static_segment;
|
||||
|
@ -16,11 +15,7 @@ pub use static_segment::*;
|
|||
/// as subsequent segments of the URL and tries to match them all. For a "vertical"
|
||||
/// matching that sees a tuple as alternatives to one another, see [`RouteChild`](super::RouteChild).
|
||||
pub trait PossibleRouteMatch {
|
||||
fn matches(&self, path: &str) -> bool {
|
||||
self.matches_iter(&mut path.chars())
|
||||
}
|
||||
|
||||
fn matches_iter(&self, path: &mut Chars) -> bool;
|
||||
fn matches<'a>(&self, path: &'a str) -> Option<&'a str>;
|
||||
|
||||
fn test<'a>(&self, path: &'a str) -> Option<PartialPathMatch<'a>>;
|
||||
|
||||
|
|
|
@ -7,8 +7,9 @@ use core::str::Chars;
|
|||
pub struct ParamSegment(pub &'static str);
|
||||
|
||||
impl PossibleRouteMatch for ParamSegment {
|
||||
fn matches_iter(&self, test: &mut Chars) -> bool {
|
||||
let mut test = test.peekable();
|
||||
fn matches<'a>(&self, path: &'a str) -> Option<&'a str> {
|
||||
let mut matched_len = 0;
|
||||
let mut test = path.chars().peekable();
|
||||
// match an initial /
|
||||
if test.peek() == Some(&'/') {
|
||||
test.next();
|
||||
|
@ -18,8 +19,9 @@ impl PossibleRouteMatch for ParamSegment {
|
|||
if char == '/' {
|
||||
break;
|
||||
}
|
||||
matched_len += char.len_utf8();
|
||||
}
|
||||
true
|
||||
Some(&path[matched_len..])
|
||||
}
|
||||
|
||||
fn test<'a>(&self, path: &'a str) -> Option<PartialPathMatch<'a>> {
|
||||
|
@ -60,8 +62,8 @@ impl PossibleRouteMatch for ParamSegment {
|
|||
pub struct WildcardSegment(pub &'static str);
|
||||
|
||||
impl PossibleRouteMatch for WildcardSegment {
|
||||
fn matches_iter(&self, _path: &mut Chars) -> bool {
|
||||
true
|
||||
fn matches<'a>(&self, path: &'a str) -> Option<&'a str> {
|
||||
Some(path)
|
||||
}
|
||||
|
||||
fn test<'a>(&self, path: &'a str) -> Option<PartialPathMatch<'a>> {
|
||||
|
@ -101,7 +103,7 @@ mod tests {
|
|||
fn single_param_match() {
|
||||
let path = "/foo";
|
||||
let def = ParamSegment("a");
|
||||
assert!(def.matches(path));
|
||||
assert!(def.matches(path).is_some());
|
||||
let matched = def.test(path).expect("couldn't match route");
|
||||
assert_eq!(matched.matched(), "/foo");
|
||||
assert_eq!(matched.remaining(), "");
|
||||
|
@ -112,7 +114,7 @@ mod tests {
|
|||
fn single_param_match_with_trailing_slash() {
|
||||
let path = "/foo/";
|
||||
let def = ParamSegment("a");
|
||||
assert!(def.matches(path));
|
||||
assert!(def.matches(path).is_some());
|
||||
let matched = def.test(path).expect("couldn't match route");
|
||||
assert_eq!(matched.matched(), "/foo");
|
||||
assert_eq!(matched.remaining(), "/");
|
||||
|
@ -123,7 +125,7 @@ mod tests {
|
|||
fn tuple_of_param_matches() {
|
||||
let path = "/foo/bar";
|
||||
let def = (ParamSegment("a"), ParamSegment("b"));
|
||||
assert!(def.matches(path));
|
||||
assert!(def.matches(path).is_some());
|
||||
let matched = def.test(path).expect("couldn't match route");
|
||||
assert_eq!(matched.matched(), "/foo/bar");
|
||||
assert_eq!(matched.remaining(), "");
|
||||
|
@ -139,7 +141,7 @@ mod tests {
|
|||
StaticSegment("bar"),
|
||||
WildcardSegment("rest"),
|
||||
);
|
||||
assert!(def.matches(path));
|
||||
assert!(def.matches(path).is_some());
|
||||
let matched = def.test(path).expect("couldn't match route");
|
||||
assert_eq!(matched.matched(), "/foo/bar/////");
|
||||
assert_eq!(matched.remaining(), "");
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
use super::{PartialPathMatch, PossibleRouteMatch};
|
||||
use crate::PathSegment;
|
||||
use alloc::{string::String, vec::Vec};
|
||||
use core::str::Chars;
|
||||
|
||||
impl PossibleRouteMatch for () {
|
||||
fn test<'a>(&self, path: &'a str) -> Option<PartialPathMatch<'a>> {
|
||||
Some(PartialPathMatch::new(path, [], ""))
|
||||
}
|
||||
|
||||
fn matches_iter(&self, _path: &mut Chars) -> bool {
|
||||
true
|
||||
fn matches<'a>(&self, path: &'a str) -> Option<&'a str> {
|
||||
Some(path)
|
||||
}
|
||||
|
||||
fn generate_path(&self, _path: &mut Vec<PathSegment>) {}
|
||||
|
@ -19,16 +18,18 @@ impl PossibleRouteMatch for () {
|
|||
pub struct StaticSegment(pub &'static str);
|
||||
|
||||
impl PossibleRouteMatch for StaticSegment {
|
||||
fn matches_iter(&self, test: &mut Chars) -> bool {
|
||||
fn matches<'a>(&self, path: &'a str) -> Option<&'a str> {
|
||||
let mut matched_len = 0;
|
||||
let mut this = self.0.chars();
|
||||
let mut test = test.peekable();
|
||||
let mut test = path.chars().peekable();
|
||||
if test.peek() == Some(&'/') {
|
||||
matched_len += '/'.len_utf8();
|
||||
test.next();
|
||||
}
|
||||
|
||||
// unless this segment is empty, we start by
|
||||
// assuming that it has not actually matched
|
||||
let mut has_matched = self.0.is_empty();
|
||||
let mut has_matched = self.0.is_empty() || self.0 == "/";
|
||||
for char in test {
|
||||
// when we get a closing /, stop matching
|
||||
if char == '/' {
|
||||
|
@ -37,13 +38,14 @@ impl PossibleRouteMatch for StaticSegment {
|
|||
// if the next character in the path doesn't match the
|
||||
// next character in the segment, we don't match
|
||||
else if this.next() != Some(char) {
|
||||
return false;
|
||||
return None;
|
||||
} else {
|
||||
matched_len += char.len_utf8();
|
||||
has_matched = true;
|
||||
}
|
||||
}
|
||||
|
||||
has_matched
|
||||
has_matched.then(|| &path[matched_len..])
|
||||
}
|
||||
|
||||
fn test<'a>(&self, path: &'a str) -> Option<PartialPathMatch<'a>> {
|
||||
|
@ -113,7 +115,7 @@ mod tests {
|
|||
fn single_static_match_with_trailing_slash() {
|
||||
let path = "/foo/";
|
||||
let def = StaticSegment("foo");
|
||||
assert!(def.matches(path));
|
||||
assert!(def.matches(path).is_some());
|
||||
let matched = def.test(path).expect("couldn't match route");
|
||||
assert_eq!(matched.matched(), "/foo");
|
||||
assert_eq!(matched.remaining(), "/");
|
||||
|
@ -124,7 +126,7 @@ mod tests {
|
|||
fn tuple_of_static_matches() {
|
||||
let path = "/foo/bar";
|
||||
let def = (StaticSegment("foo"), StaticSegment("bar"));
|
||||
assert!(def.matches(path));
|
||||
assert!(def.matches(path).is_some());
|
||||
let matched = def.test(path).expect("couldn't match route");
|
||||
assert_eq!(matched.matched(), "/foo/bar");
|
||||
assert_eq!(matched.remaining(), "");
|
||||
|
@ -135,7 +137,7 @@ mod tests {
|
|||
fn tuple_static_mismatch() {
|
||||
let path = "/foo/baz";
|
||||
let def = (StaticSegment("foo"), StaticSegment("bar"));
|
||||
assert!(!def.matches(path));
|
||||
assert!(def.matches(path).is_none());
|
||||
assert!(def.test(path).is_none());
|
||||
}
|
||||
|
||||
|
@ -150,7 +152,7 @@ mod tests {
|
|||
StaticSegment("bar"),
|
||||
(),
|
||||
);
|
||||
assert!(def.matches(path));
|
||||
assert!(def.matches(path).is_some());
|
||||
let matched = def.test(path).expect("couldn't match route");
|
||||
assert_eq!(matched.matched(), "/foo/bar");
|
||||
assert_eq!(matched.remaining(), "");
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use super::{PartialPathMatch, PathSegment, PossibleRouteMatch};
|
||||
use alloc::{string::String, vec::Vec};
|
||||
use core::str::Chars;
|
||||
|
||||
macro_rules! tuples {
|
||||
($($ty:ident),*) => {
|
||||
|
@ -8,11 +7,12 @@ macro_rules! tuples {
|
|||
where
|
||||
$($ty: PossibleRouteMatch),*,
|
||||
{
|
||||
fn matches_iter(&self, path: &mut Chars) -> bool
|
||||
fn matches<'a>(&self, path: &'a str) -> Option<&'a str>
|
||||
{
|
||||
#[allow(non_snake_case)]
|
||||
let ($($ty,)*) = &self;
|
||||
$($ty.matches_iter(path) &&)* true
|
||||
$(let path = $ty.matches(path)?;)*
|
||||
Some(path)
|
||||
}
|
||||
|
||||
fn test<'a>(&self, path: &'a str) -> Option<PartialPathMatch<'a>>
|
||||
|
|
|
@ -6,32 +6,215 @@ pub use horizontal::*;
|
|||
pub use vertical::*;
|
||||
|
||||
pub struct Routes<Children> {
|
||||
base: Cow<'static, str>,
|
||||
base: Option<Cow<'static, str>>,
|
||||
children: Children,
|
||||
}
|
||||
|
||||
impl<Children> Routes<Children> {
|
||||
pub fn new(children: Children) -> Self {
|
||||
Self {
|
||||
base: None,
|
||||
children,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with_base(
|
||||
children: Children,
|
||||
base: impl Into<Cow<'static, str>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
base: Some(base.into()),
|
||||
children,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Children> Routes<Children>
|
||||
where
|
||||
Children: MatchNestedRoutes,
|
||||
{
|
||||
pub fn match_route<'a>(&self, path: &'a str) -> Option<RouteMatch<'a>> {
|
||||
let path = match &self.base {
|
||||
None => path,
|
||||
Some(base) if base.starts_with('/') => {
|
||||
path.trim_start_matches(base.as_ref())
|
||||
}
|
||||
Some(base) => path
|
||||
.trim_start_matches('/')
|
||||
.trim_start_matches(base.as_ref()),
|
||||
};
|
||||
|
||||
let mut matched_nested_routes = Vec::with_capacity(Children::DEPTH);
|
||||
self.children
|
||||
.match_nested_routes(path, &mut matched_nested_routes);
|
||||
|
||||
// TODO check for completeness
|
||||
|
||||
if matched_nested_routes.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(RouteMatch {
|
||||
path,
|
||||
matched_nested_routes,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RouteMatch<'a> {
|
||||
path: &'a str,
|
||||
matched_nested_routes: Vec<NestedRouteMatch<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> RouteMatch<'a> {
|
||||
pub fn path(&self) -> &'a str {
|
||||
self.path
|
||||
}
|
||||
|
||||
pub fn matches(&self) -> &[NestedRouteMatch<'a>] {
|
||||
&self.matched_nested_routes
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NestedRouteMatch<'a> {
|
||||
/// The portion of the full path matched by this nested route.
|
||||
matched_path: String,
|
||||
/// The map of params matched by this route.
|
||||
/// The portion of the full path matched only by this nested route.
|
||||
matched_path: &'a str,
|
||||
/// The map of params matched only by this nested route.
|
||||
params: Params<&'static str>,
|
||||
}
|
||||
|
||||
impl<'a> NestedRouteMatch<'a> {
|
||||
pub fn matched_path(&self) -> &'a str {
|
||||
self.matched_path
|
||||
}
|
||||
|
||||
pub fn matched_params(&self) -> &Params<&'static str> {
|
||||
&self.params
|
||||
}
|
||||
}
|
||||
|
||||
pub trait MatchNestedRoutes {
|
||||
const DEPTH: usize;
|
||||
|
||||
fn matches<'a>(&self, path: &'a str) -> Option<&'a str>;
|
||||
|
||||
fn match_nested_routes<'a>(
|
||||
&self,
|
||||
path: &'a str,
|
||||
matches: &mut Vec<NestedRouteMatch<'a>>,
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub struct NestedRoute<Segments, Children> {
|
||||
segments: Segments,
|
||||
children: Children,
|
||||
pub segments: Segments,
|
||||
pub children: Children,
|
||||
}
|
||||
|
||||
impl MatchNestedRoutes for () {
|
||||
const DEPTH: usize = 0;
|
||||
|
||||
fn matches<'a>(&self, path: &'a str) -> Option<&'a str> {
|
||||
Some(path)
|
||||
}
|
||||
|
||||
fn match_nested_routes<'a>(
|
||||
&self,
|
||||
_path: &'a str,
|
||||
_matches: &mut Vec<NestedRouteMatch<'a>>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl<Segments, Children> MatchNestedRoutes for NestedRoute<Segments, Children>
|
||||
where
|
||||
Segments: PossibleRouteMatch,
|
||||
Children: MatchNestedRoutes,
|
||||
{
|
||||
const DEPTH: usize = Children::DEPTH;
|
||||
|
||||
fn matches<'a>(&self, path: &'a str) -> Option<&'a str> {
|
||||
if let Some(remaining) = self.segments.matches(path) {
|
||||
self.children.matches(remaining)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn match_nested_routes<'a>(
|
||||
&self,
|
||||
path: &'a str,
|
||||
matches: &mut Vec<NestedRouteMatch<'a>>,
|
||||
) {
|
||||
if let Some(remaining) = self.segments.matches(path) {
|
||||
if let partial = self.segments.test(path) {
|
||||
let PartialPathMatch {
|
||||
params, matched, ..
|
||||
} = partial;
|
||||
matches.push(NestedRouteMatch {
|
||||
matched_path: matched,
|
||||
params,
|
||||
});
|
||||
}
|
||||
self.children.match_nested_routes(path, matches);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A> MatchNestedRoutes for (A,)
|
||||
where
|
||||
A: MatchNestedRoutes,
|
||||
{
|
||||
const DEPTH: usize = A::DEPTH;
|
||||
|
||||
fn matches<'a>(&self, path: &'a str) -> Option<&'a str> {
|
||||
self.0.matches(path)
|
||||
}
|
||||
|
||||
fn match_nested_routes<'a>(
|
||||
&self,
|
||||
path: &'a str,
|
||||
matches: &mut Vec<NestedRouteMatch<'a>>,
|
||||
) {
|
||||
self.0.match_nested_routes(path, matches);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{NestedRoute, Routes};
|
||||
use crate::matching::StaticSegment;
|
||||
|
||||
#[test]
|
||||
pub fn does_not_match_none() {
|
||||
let routes = Routes::new(NestedRoute {
|
||||
segments: (),
|
||||
children: (),
|
||||
});
|
||||
let matched = routes.match_route("/");
|
||||
assert!(matched.is_none());
|
||||
let matched = routes.match_route("");
|
||||
assert!(matched.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn matches_single_root_route() {
|
||||
let routes = Routes::new(NestedRoute {
|
||||
segments: StaticSegment("/"),
|
||||
children: (),
|
||||
});
|
||||
let matched = routes.match_route("/");
|
||||
assert!(matched.is_some())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PartialPathMatch<'a> {
|
||||
pub(crate) remaining: &'a str,
|
||||
pub(crate) params: Params<&'static str>,
|
||||
pub(crate) matched: String,
|
||||
pub(crate) params: Vec<(&'static str, &'a str)>,
|
||||
pub(crate) matched: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> PartialPathMatch<'a> {
|
||||
|
@ -63,3 +246,81 @@ impl<'a> PartialPathMatch<'a> {
|
|||
self.matched.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! tuples {
|
||||
($($ty:ident),*) => {
|
||||
impl<$($ty),*> PossibleRouteMatch for ($($ty,)*)
|
||||
where
|
||||
$($ty: PossibleRouteMatch),*,
|
||||
{
|
||||
fn matches_iter(&self, path: &mut Chars) -> bool
|
||||
{
|
||||
#[allow(non_snake_case)]
|
||||
let ($($ty,)*) = &self;
|
||||
$($ty.matches_iter(path) &&)* true
|
||||
}
|
||||
|
||||
fn test<'a>(&self, path: &'a str) -> Option<PartialPathMatch<'a>>
|
||||
{
|
||||
let mut full_params = Vec::new();
|
||||
let mut full_matched = String::new();
|
||||
#[allow(non_snake_case)]
|
||||
let ($($ty,)*) = &self;
|
||||
$(
|
||||
let PartialPathMatch {
|
||||
remaining,
|
||||
matched,
|
||||
params
|
||||
} = $ty.test(path)?;
|
||||
let path = remaining;
|
||||
full_matched.push_str(&matched);
|
||||
full_params.extend(params);
|
||||
)*
|
||||
Some(PartialPathMatch {
|
||||
remaining: path,
|
||||
matched: full_matched,
|
||||
params: full_params
|
||||
})
|
||||
}
|
||||
|
||||
fn generate_path(&self, path: &mut Vec<PathSegment>) {
|
||||
#[allow(non_snake_case)]
|
||||
let ($($ty,)*) = &self;
|
||||
$(
|
||||
$ty.generate_path(path);
|
||||
)*
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
//tuples!(A, B);
|
||||
/*tuples!(A, B, C);
|
||||
tuples!(A, B, C, D);
|
||||
tuples!(A, B, C, D, E);
|
||||
tuples!(A, B, C, D, E, F);
|
||||
tuples!(A, B, C, D, E, F, G);
|
||||
tuples!(A, B, C, D, E, F, G, H);
|
||||
tuples!(A, B, C, D, E, F, G, H, I);
|
||||
tuples!(A, B, C, D, E, F, G, H, I, J);
|
||||
tuples!(A, B, C, D, E, F, G, H, I, J, K);
|
||||
tuples!(A, B, C, D, E, F, G, H, I, J, K, L);
|
||||
tuples!(A, B, C, D, E, F, G, H, I, J, K, L, M);
|
||||
tuples!(A, B, C, D, E, F, G, H, I, J, K, L, M, N);
|
||||
tuples!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
|
||||
tuples!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
|
||||
tuples!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q);
|
||||
tuples!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R);
|
||||
tuples!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S);
|
||||
tuples!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T);
|
||||
tuples!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U);
|
||||
tuples!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V);
|
||||
tuples!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W);
|
||||
tuples!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X);
|
||||
tuples!(
|
||||
A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y
|
||||
);
|
||||
tuples!(
|
||||
A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y,
|
||||
Z
|
||||
);*/
|
||||
|
|
Loading…
Reference in a new issue