mirror of
https://github.com/adoyle0/thaw.git
synced 2025-01-22 22:09:22 -05:00
feat: optimized routing
This commit is contained in:
parent
008da2e5b1
commit
9e433c5e67
4 changed files with 123 additions and 150 deletions
113
demo/src/app.rs
113
demo/src/app.rs
|
@ -3,7 +3,7 @@ use leptos::{prelude::*, reactive_graph::wrappers::write::SignalSetter};
|
|||
use leptos_meta::provide_meta_context;
|
||||
use leptos_router::{
|
||||
components::{ParentRoute, Route, Router, Routes},
|
||||
StaticSegment,
|
||||
path,
|
||||
};
|
||||
// use leptos_use::{
|
||||
// storage::use_local_storage,
|
||||
|
@ -41,79 +41,72 @@ fn TheRouter(is_routing: RwSignal<bool>) -> impl IntoView {
|
|||
|
||||
view! {
|
||||
<Routes fallback=|| "404">
|
||||
<Route path=StaticSegment("") view=Home/>
|
||||
<ParentRoute path=StaticSegment("guide") view=ComponentsPage>
|
||||
<Route path=StaticSegment("installation") view=InstallationMdPage/>
|
||||
<Route path=StaticSegment("server-sider-rendering") view=ServerSiderRenderingMdPage/>
|
||||
<Route path=(StaticSegment("development"), StaticSegment("components")) view=DevelopmentComponentsMdPage/>
|
||||
<Route path=path!("/") view=Home/>
|
||||
<ParentRoute path=path!("/guide") view=ComponentsPage>
|
||||
<Route path=path!("/installation") view=InstallationMdPage/>
|
||||
<Route path=path!("/server-sider-rendering") view=ServerSiderRenderingMdPage/>
|
||||
<Route path=path!("/development/components") view=DevelopmentComponentsMdPage/>
|
||||
</ParentRoute>
|
||||
<ParentRoute path=StaticSegment("components") view=ComponentsPage>
|
||||
|
||||
// // <Route path="/tabbar" view=TabbarPage/>
|
||||
// // <Route path="/nav-bar" view=NavBarPage/>
|
||||
// // <Route path="/toast" view=ToastPage/>
|
||||
<ParentRoute path=path!("/components") view=ComponentsPage>
|
||||
{
|
||||
view! {
|
||||
<Route path=StaticSegment("accordion") view=AccordionMdPage/>
|
||||
<Route path=StaticSegment("anchor") view=AnchorMdPage/>
|
||||
<Route path=StaticSegment("auto-complete") view=AutoCompleteMdPage/>
|
||||
<Route path=StaticSegment("avatar") view=AvatarMdPage/>
|
||||
<Route path=StaticSegment("back-top") view=BackTopMdPage/>
|
||||
<Route path=StaticSegment("badge") view=BadgeMdPage/>
|
||||
<Route path=StaticSegment("breadcrumb") view=BreadcrumbMdPage/>
|
||||
<Route path=StaticSegment("button") view=ButtonMdPage/>
|
||||
<Route path=StaticSegment("calendar") view=CalendarMdPage/>
|
||||
<Route path=StaticSegment("card") view=CardMdPage/>
|
||||
<Route path=StaticSegment("checkbox") view=CheckboxMdPage/>
|
||||
<Route path=StaticSegment("color-picker") view=ColorPickerMdPage/>
|
||||
<Route path=StaticSegment("combobox") view=ComboboxMdPage/>
|
||||
<Route path=StaticSegment("config-provider") view=ConfigProviderMdPage/>
|
||||
<Route path=path!("/accordion") view=AccordionMdPage/>
|
||||
<Route path=path!("/anchor") view=AnchorMdPage/>
|
||||
<Route path=path!("/auto-complete") view=AutoCompleteMdPage/>
|
||||
<Route path=path!("/avatar") view=AvatarMdPage/>
|
||||
<Route path=path!("/back-top") view=BackTopMdPage/>
|
||||
<Route path=path!("/badge") view=BadgeMdPage/>
|
||||
<Route path=path!("/breadcrumb") view=BreadcrumbMdPage/>
|
||||
<Route path=path!("/button") view=ButtonMdPage/>
|
||||
<Route path=path!("/calendar") view=CalendarMdPage/>
|
||||
<Route path=path!("/card") view=CardMdPage/>
|
||||
<Route path=path!("/checkbox") view=CheckboxMdPage/>
|
||||
<Route path=path!("/color-picker") view=ColorPickerMdPage/>
|
||||
<Route path=path!("/combobox") view=ComboboxMdPage/>
|
||||
<Route path=path!("/config-provider") view=ConfigProviderMdPage/>
|
||||
}
|
||||
}
|
||||
{
|
||||
view! {
|
||||
<Route path=StaticSegment("date-picker") view=DatePickerMdPage/>
|
||||
<Route path=StaticSegment("dialog") view=DialogMdPage/>
|
||||
<Route path=StaticSegment("divider") view=DividerMdPage/>
|
||||
<Route path=StaticSegment("drawer") view=DrawerMdPage/>
|
||||
<Route path=StaticSegment("menu") view=MenuMdPage/>
|
||||
<Route path=StaticSegment("grid") view=GridMdPage/>
|
||||
<Route path=StaticSegment("icon") view=IconMdPage/>
|
||||
<Route path=StaticSegment("image") view=ImageMdPage/>
|
||||
<Route path=StaticSegment("input") view=InputMdPage/>
|
||||
<Route path=StaticSegment("layout") view=LayoutMdPage/>
|
||||
<Route path=StaticSegment("loading-bar") view=LoadingBarMdPage/>
|
||||
<Route path=StaticSegment("message-bar") view=MessageBarMdPage/>
|
||||
<Route path=StaticSegment("nav") view=NavMdPage/>
|
||||
<Route path=StaticSegment("pagination") view=PaginationMdPage/>
|
||||
<Route path=StaticSegment("popover") view=PopoverMdPage/>
|
||||
<Route path=StaticSegment("progress-bar") view=ProgressBarMdPage/>
|
||||
<Route path=path!("date-picker") view=DatePickerMdPage/>
|
||||
<Route path=path!("/dialog") view=DialogMdPage/>
|
||||
<Route path=path!("/divider") view=DividerMdPage/>
|
||||
<Route path=path!("/drawer") view=DrawerMdPage/>
|
||||
<Route path=path!("/menu") view=MenuMdPage/>
|
||||
<Route path=path!("/grid") view=GridMdPage/>
|
||||
<Route path=path!("/icon") view=IconMdPage/>
|
||||
<Route path=path!("/image") view=ImageMdPage/>
|
||||
<Route path=path!("/input") view=InputMdPage/>
|
||||
<Route path=path!("/layout") view=LayoutMdPage/>
|
||||
<Route path=path!("/loading-bar") view=LoadingBarMdPage/>
|
||||
<Route path=path!("/message-bar") view=MessageBarMdPage/>
|
||||
<Route path=path!("/nav") view=NavMdPage/>
|
||||
<Route path=path!("/pagination") view=PaginationMdPage/>
|
||||
<Route path=path!("/popover") view=PopoverMdPage/>
|
||||
<Route path=path!("/progress-bar") view=ProgressBarMdPage/>
|
||||
}
|
||||
}
|
||||
{
|
||||
view! {
|
||||
<Route path=StaticSegment("radio") view=RadioMdPage/>
|
||||
<Route path=StaticSegment("scrollbar") view=ScrollbarMdPage/>
|
||||
<Route path=StaticSegment("skeleton") view=SkeletonMdPage/>
|
||||
<Route path=StaticSegment("slider") view=SliderMdPage/>
|
||||
<Route path=StaticSegment("space") view=SpaceMdPage/>
|
||||
<Route path=StaticSegment("spin-button") view=SpinButtonMdPage/>
|
||||
<Route path=StaticSegment("spinner") view=SpinnerMdPage/>
|
||||
<Route path=StaticSegment("switch") view=SwitchMdPage/>
|
||||
<Route path=path!("/radio") view=RadioMdPage/>
|
||||
<Route path=path!("/scrollbar") view=ScrollbarMdPage/>
|
||||
<Route path=path!("/skeleton") view=SkeletonMdPage/>
|
||||
<Route path=path!("/slider") view=SliderMdPage/>
|
||||
<Route path=path!("/space") view=SpaceMdPage/>
|
||||
<Route path=path!("/spin-button") view=SpinButtonMdPage/>
|
||||
<Route path=path!("/spinner") view=SpinnerMdPage/>
|
||||
<Route path=path!("/switch") view=SwitchMdPage/>
|
||||
<Route path=path!("/tab-list") view=TabListMdPage/>
|
||||
<Route path=path!("/table") view=TableMdPage/>
|
||||
<Route path=path!("/tag") view=TagMdPage/>
|
||||
<Route path=path!("/text") view=TextMdPage/>
|
||||
<Route path=path!("/textarea") view=TextareaMdPage/>
|
||||
<Route path=path!("/time-picker") view=TimePickerMdPage/>
|
||||
<Route path=path!("/toast") view=ToastMdPage />
|
||||
<Route path=path!("/upload") view=UploadMdPage/>
|
||||
}
|
||||
}
|
||||
<Route path=StaticSegment("tab-list") view=TabListMdPage/>
|
||||
<Route path=StaticSegment("table") view=TableMdPage/>
|
||||
<Route path=StaticSegment("tag") view=TagMdPage/>
|
||||
<Route path=StaticSegment("text") view=TextMdPage/>
|
||||
<Route path=StaticSegment("textarea") view=TextareaMdPage/>
|
||||
<Route path=StaticSegment("time-picker") view=TimePickerMdPage/>
|
||||
<Route path=StaticSegment("toast") view=ToastMdPage />
|
||||
<Route path=StaticSegment("upload") view=UploadMdPage/>
|
||||
</ParentRoute>
|
||||
// <Route path="/mobile/tabbar" view=TabbarDemoPage/>
|
||||
// <Route path="/mobile/nav-bar" view=NavBarDemoPage/>
|
||||
// <Route path="/mobile/toast" view=ToastDemoPage/>
|
||||
</Routes>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -166,8 +166,8 @@ pub fn SiteHeader() -> impl IntoView {
|
|||
on_select=move |value : String| match value.as_str() {
|
||||
"Dark" => change_theme(MouseEvent::new("click").unwrap()),
|
||||
"Light" => change_theme(MouseEvent::new("click").unwrap()),
|
||||
"github" => { _ = window().open_with_url("http://github.com/thaw-ui/thaw"); },//FIXME: breaks page
|
||||
"discord" => { _ = window().open_with_url("https://discord.gg/YPxuprzu6M"); },//FIXME: breaks page
|
||||
"github" => { _ = window().open_with_url("http://github.com/thaw-ui/thaw"); },
|
||||
"discord" => { _ = window().open_with_url("https://discord.gg/YPxuprzu6M"); },
|
||||
_ => navigate_signal.get()(&value, Default::default())
|
||||
|
||||
}
|
||||
|
@ -183,16 +183,16 @@ pub fn SiteHeader() -> impl IntoView {
|
|||
<MenuItem icon=icondata::AiGithubOutlined value="github">"Github"</MenuItem>
|
||||
<MenuItem icon=icondata::BiDiscordAlt value="discord">"Discord"</MenuItem>
|
||||
{
|
||||
use crate::pages::{gen_menu_data, MenuGroupOption, MenuItemOption};
|
||||
gen_menu_data().into_iter().map(|data| {
|
||||
let MenuGroupOption { label, children } = data;
|
||||
use crate::pages::{gen_nav_data, NavGroupOption, NavItemOption};
|
||||
gen_nav_data().into_iter().map(|data| {
|
||||
let NavGroupOption { label, children } = data;
|
||||
view! {
|
||||
<Caption1Strong style="margin-inline-start: 10px; margin-top: 10px; display: block">
|
||||
{label}
|
||||
</Caption1Strong>
|
||||
{
|
||||
children.into_iter().map(|item| {
|
||||
let MenuItemOption { label, value } = item;
|
||||
let NavItemOption { label, value } = item;
|
||||
view! {
|
||||
<MenuItem value=value>{label}</MenuItem>
|
||||
}
|
||||
|
@ -246,8 +246,7 @@ struct AutoCompleteOption {
|
|||
}
|
||||
|
||||
fn gen_search_all_options() -> Vec<AutoCompleteOption> {
|
||||
use crate::pages::gen_menu_data;
|
||||
gen_menu_data()
|
||||
crate::pages::gen_nav_data()
|
||||
.into_iter()
|
||||
.flat_map(|group| {
|
||||
group.children.into_iter().map(|item| AutoCompleteOption {
|
||||
|
|
|
@ -58,15 +58,15 @@ pub fn ComponentsPage() -> impl IntoView {
|
|||
<div class="demo-components__sider">
|
||||
<NavDrawer selected_value=select_name>
|
||||
{
|
||||
gen_menu_data().into_iter().map(|data| {
|
||||
let MenuGroupOption { label, children } = data;
|
||||
gen_nav_data().into_iter().map(|data| {
|
||||
let NavGroupOption { label, children } = data;
|
||||
view! {
|
||||
<Caption1Strong style="margin-inline-start: 10px; margin-top: 10px; display: inline-block">
|
||||
{label}
|
||||
</Caption1Strong>
|
||||
{
|
||||
children.into_iter().map(|item| {
|
||||
let MenuItemOption { label, value } = item;
|
||||
let NavItemOption { label, value } = item;
|
||||
view! {
|
||||
<NavItem value>{label}</NavItem>
|
||||
}
|
||||
|
@ -85,245 +85,228 @@ pub fn ComponentsPage() -> impl IntoView {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) struct MenuGroupOption {
|
||||
pub(crate) struct NavGroupOption {
|
||||
pub label: String,
|
||||
pub children: Vec<MenuItemOption>,
|
||||
pub children: Vec<NavItemOption>,
|
||||
}
|
||||
|
||||
pub(crate) struct MenuItemOption {
|
||||
pub(crate) struct NavItemOption {
|
||||
pub label: String,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
pub(crate) fn gen_menu_data() -> Vec<MenuGroupOption> {
|
||||
pub(crate) fn gen_nav_data() -> Vec<NavGroupOption> {
|
||||
vec![
|
||||
MenuGroupOption {
|
||||
NavGroupOption {
|
||||
label: "Getting Started".into(),
|
||||
children: vec![
|
||||
MenuItemOption {
|
||||
NavItemOption {
|
||||
value: "/guide/installation".into(),
|
||||
label: "Installation".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
NavItemOption {
|
||||
value: "/guide/server-sider-rendering".into(),
|
||||
label: "Server Sider Rendering".into(),
|
||||
},
|
||||
],
|
||||
},
|
||||
// MenuGroupOption {
|
||||
// NavGroupOption {
|
||||
// label: "Development".into(),
|
||||
// children: vec![
|
||||
// MenuItemOption {
|
||||
// NavItemOption {
|
||||
// value: "/guide/development/components".into(),
|
||||
// label: "Components".into(),
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
MenuGroupOption {
|
||||
NavGroupOption {
|
||||
label: "Components".into(),
|
||||
children: vec![
|
||||
MenuItemOption {
|
||||
NavItemOption {
|
||||
value: "/components/accordion".into(),
|
||||
label: "Accordion".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
NavItemOption {
|
||||
value: "/components/anchor".into(),
|
||||
label: "Anchor".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
NavItemOption {
|
||||
value: "/components/auto-complete".into(),
|
||||
label: "Auto Complete".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
NavItemOption {
|
||||
value: "/components/avatar".into(),
|
||||
label: "Avatar".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
NavItemOption {
|
||||
value: "/components/back-top".into(),
|
||||
label: "Back Top".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
NavItemOption {
|
||||
value: "/components/badge".into(),
|
||||
label: "Badge".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
NavItemOption {
|
||||
value: "/components/breadcrumb".into(),
|
||||
label: "Breadcrumb".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
NavItemOption {
|
||||
value: "/components/button".into(),
|
||||
label: "Button".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
NavItemOption {
|
||||
value: "/components/calendar".into(),
|
||||
label: "Calendar".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
NavItemOption {
|
||||
value: "/components/card".into(),
|
||||
label: "Card".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
NavItemOption {
|
||||
value: "/components/checkbox".into(),
|
||||
label: "Checkbox".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
NavItemOption {
|
||||
value: "/components/color-picker".into(),
|
||||
label: "Color Picker".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
NavItemOption {
|
||||
value: "/components/combobox".into(),
|
||||
label: "Combobox".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
NavItemOption {
|
||||
value: "/components/config-provider".into(),
|
||||
label: "Config Provider".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
NavItemOption {
|
||||
value: "/components/date-picker".into(),
|
||||
label: "Date Picker".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
NavItemOption {
|
||||
value: "/components/dialog".into(),
|
||||
label: "Dialog".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
NavItemOption {
|
||||
value: "/components/divider".into(),
|
||||
label: "Divider".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
NavItemOption {
|
||||
value: "/components/drawer".into(),
|
||||
label: "Drawer".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
NavItemOption {
|
||||
value: "/components/grid".into(),
|
||||
label: "Grid".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
NavItemOption {
|
||||
value: "/components/icon".into(),
|
||||
label: "Icon".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
NavItemOption {
|
||||
value: "/components/image".into(),
|
||||
label: "Image".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
NavItemOption {
|
||||
value: "/components/input".into(),
|
||||
label: "Input".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
NavItemOption {
|
||||
value: "/components/layout".into(),
|
||||
label: "Layout".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
NavItemOption {
|
||||
value: "/components/loading-bar".into(),
|
||||
label: "Loading Bar".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
NavItemOption {
|
||||
value: "/components/menu".into(),
|
||||
label: "Menu".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
NavItemOption {
|
||||
value: "/components/message-bar".into(),
|
||||
label: "Message Bar".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
NavItemOption {
|
||||
value: "/components/nav".into(),
|
||||
label: "Nav".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "pagination".into(),
|
||||
NavItemOption {
|
||||
value: "/components/pagination".into(),
|
||||
label: "Pagination".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
NavItemOption {
|
||||
value: "/components/popover".into(),
|
||||
label: "Popover".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
NavItemOption {
|
||||
value: "/components/progress-bar".into(),
|
||||
label: "ProgressBar".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
NavItemOption {
|
||||
value: "/components/radio".into(),
|
||||
label: "Radio".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
NavItemOption {
|
||||
value: "/components/scrollbar".into(),
|
||||
label: "Scrollbar".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
NavItemOption {
|
||||
value: "/components/skeleton".into(),
|
||||
label: "Skeleton".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
NavItemOption {
|
||||
value: "/components/slider".into(),
|
||||
label: "Slider".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
NavItemOption {
|
||||
value: "/components/space".into(),
|
||||
label: "Space".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
NavItemOption {
|
||||
value: "/components/spin-button".into(),
|
||||
label: "Spin Button".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
NavItemOption {
|
||||
value: "/components/spinner".into(),
|
||||
label: "Spinner".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
NavItemOption {
|
||||
value: "/components/switch".into(),
|
||||
label: "Switch".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
NavItemOption {
|
||||
value: "/components/tab-list".into(),
|
||||
label: "Tab List".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
NavItemOption {
|
||||
value: "/components/table".into(),
|
||||
label: "Table".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
NavItemOption {
|
||||
value: "/components/tag".into(),
|
||||
label: "Tag".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
NavItemOption {
|
||||
value: "/components/text".into(),
|
||||
label: "Text".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
NavItemOption {
|
||||
value: "/components/textarea".into(),
|
||||
label: "Textarea".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
NavItemOption {
|
||||
value: "/components/time-picker".into(),
|
||||
label: "Time Picker".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
NavItemOption {
|
||||
value: "/components/toast".into(),
|
||||
label: "Toast".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
NavItemOption {
|
||||
value: "/components/upload".into(),
|
||||
label: "Upload".into(),
|
||||
},
|
||||
],
|
||||
},
|
||||
// MenuGroupOption {
|
||||
// label: "Mobile Components".into(),
|
||||
// children: vec![
|
||||
// MenuItemOption {
|
||||
// value: "/components/nav-bar".into(),
|
||||
// label: "Nav Bar".into(),
|
||||
// },
|
||||
// MenuItemOption {
|
||||
// value: "/components/tabbar".into(),
|
||||
// label: "Tabbar".into(),
|
||||
// },
|
||||
// MenuItemOption {
|
||||
// value: "/components/toast".into(),
|
||||
// label: "Toast".into(),
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
]
|
||||
}
|
||||
|
|
|
@ -80,7 +80,7 @@ pub fn Icon(
|
|||
icon_data.set(Some(icon.data.to_string()));
|
||||
});
|
||||
|
||||
let svg = view! {
|
||||
view! {
|
||||
<svg
|
||||
class=class_list!["thaw-icon", class.map(|c| move || c.get())]
|
||||
style=move || take_signal(icon_style).unwrap_or_default()
|
||||
|
@ -94,12 +94,10 @@ pub fn Icon(
|
|||
stroke-width=move || take(icon_stroke_width)
|
||||
stroke=move || take(icon_stroke)
|
||||
fill=move || take(icon_fill)
|
||||
// inner_html=move || take(icon_data)
|
||||
inner_html=move || take(icon_data)
|
||||
on:click=on_click
|
||||
></svg>
|
||||
};
|
||||
|
||||
svg.inner_html(move || take(icon_data))
|
||||
}
|
||||
}
|
||||
|
||||
fn take_signal(signal: RwSignal<Option<MaybeSignal<String>>>) -> Option<String> {
|
||||
|
|
Loading…
Add table
Reference in a new issue