Implement single-page app
This commit is contained in:
parent
b4f4db1f81
commit
0d40dd7e1f
39
Cargo.lock
generated
39
Cargo.lock
generated
@ -599,6 +599,7 @@ version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8d5564e570a38b43d78bdc063374a0c3098c4f0d64005b12f9bbe87e869b6d7"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"gloo-events",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
@ -672,6 +673,8 @@ version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "98c4a8d6391675c6b2ee1a6c8d06e8e2d03605c44cec1270675985a4c2a5500b"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
@ -1286,6 +1289,12 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "route-recognizer"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "afab94fb28594581f62d981211a9a4d53cc8130bbcbbb89a0440d9b8e81a7746"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.21"
|
||||
@ -2268,6 +2277,35 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yew-router"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "426ee0486d2572a6c5e39fbdbc48b58d59bb555f3326f54631025266cf04146e"
|
||||
dependencies = [
|
||||
"gloo",
|
||||
"js-sys",
|
||||
"route-recognizer",
|
||||
"serde",
|
||||
"serde_urlencoded",
|
||||
"tracing",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
"yew",
|
||||
"yew-router-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yew-router-macro"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89b249cdb39e0cddaf0644dedc781854524374664793479fdc01e6a65d6e6ae3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yewprint"
|
||||
version = "0.4.0"
|
||||
@ -2299,6 +2337,7 @@ dependencies = [
|
||||
"web-sys",
|
||||
"wee_alloc",
|
||||
"yew",
|
||||
"yew-router",
|
||||
"yewprint",
|
||||
]
|
||||
|
||||
|
@ -28,6 +28,7 @@ js-sys = "0.3"
|
||||
wee_alloc = { version = "0.4.5", optional = false }
|
||||
|
||||
yew = { version = "0.20.0", features = ["csr"] }
|
||||
yew-router = { version = "0.17.0" }
|
||||
yewprint = "0.4.0"
|
||||
gloo = "0.8.0"
|
||||
gloo-events = "0.1"
|
||||
|
30
src/app.rs
30
src/app.rs
@ -1,6 +1,10 @@
|
||||
use yew::prelude::*;
|
||||
use yew_router::prelude::Navigator;
|
||||
use yew_router::prelude::use_navigator;
|
||||
use yewprint::Icon;
|
||||
use crate::page::Page;
|
||||
use crate::page::PageRouter;
|
||||
use crate::page::Pages;
|
||||
use crate::theme::ThemeContext;
|
||||
use crate::theme::ThemeMsg;
|
||||
use crate::theme::ThemeProvider;
|
||||
@ -11,15 +15,20 @@ use crate::component::actionbar::{Actionbar, ActionbarOption};
|
||||
pub fn AppRoot() -> Html {
|
||||
html! {
|
||||
<ThemeProvider>
|
||||
<ThemedApp />
|
||||
<PageRouter>
|
||||
<ThemedApp />
|
||||
</PageRouter>
|
||||
</ThemeProvider>
|
||||
}
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
fn ThemedApp() -> Html {
|
||||
let navigator = use_navigator().unwrap();
|
||||
|
||||
// Helper function for generating tabs
|
||||
fn page_option(current: &Page, page: Page, state: UseStateHandle<Page>) -> ActionbarOption {
|
||||
fn page_option(current: &Page, page: Page, navigator: Navigator, state: UseStateHandle<Page>) -> ActionbarOption {
|
||||
let is_current = *current == page;
|
||||
ActionbarOption::new(
|
||||
page.name(),
|
||||
match page {
|
||||
@ -28,8 +37,11 @@ fn ThemedApp() -> Html {
|
||||
Page::Socials => Icon::SocialMedia,
|
||||
Page::Contact => Icon::Envelope,
|
||||
},
|
||||
Callback::from(move |_| state.set(page)),
|
||||
*current == page
|
||||
Callback::from(move |_| {
|
||||
navigator.push(&page);
|
||||
state.set(page);
|
||||
}),
|
||||
is_current
|
||||
)
|
||||
}
|
||||
|
||||
@ -41,10 +53,10 @@ fn ThemedApp() -> Html {
|
||||
<>
|
||||
<Actionbar>
|
||||
{vec![
|
||||
page_option(¤t_page, Page::Home, page_state.clone()),
|
||||
page_option(¤t_page, Page::Projects, page_state.clone()),
|
||||
page_option(¤t_page, Page::Socials, page_state.clone()),
|
||||
page_option(¤t_page, Page::Contact, page_state),
|
||||
page_option(¤t_page, Page::Home, navigator.clone(), page_state.clone()),
|
||||
page_option(¤t_page, Page::Projects, navigator.clone(), page_state.clone()),
|
||||
page_option(¤t_page, Page::Socials, navigator.clone(), page_state.clone()),
|
||||
page_option(¤t_page, Page::Contact, navigator, page_state),
|
||||
ActionbarOption::new_opt(
|
||||
None,
|
||||
Some(Icon::Flash),
|
||||
@ -56,7 +68,7 @@ fn ThemedApp() -> Html {
|
||||
]}
|
||||
</Actionbar>
|
||||
<div class="main">
|
||||
{current_page.content()}
|
||||
<Pages />
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
use yew::{function_component, Html, html, Callback, Properties, use_state};
|
||||
use yewprint::{Overlay, Icon, IconSize};
|
||||
use yewprint::{Overlay, Icon, IconSize, Intent};
|
||||
|
||||
const CHEVRON_SIZE: f64 = 40.0;
|
||||
const CHEVRON_TYPE: Intent = Intent::Danger;
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub struct ImageDescription {
|
||||
@ -60,7 +61,7 @@ pub fn ImageViewer(props: &ImageViewerProps) -> Html {
|
||||
Some(html! {
|
||||
<Icon
|
||||
icon={yewprint::Icon::ChevronRight}
|
||||
intent={yewprint::Intent::Warning}
|
||||
intent={CHEVRON_TYPE}
|
||||
size={IconSize(CHEVRON_SIZE)}
|
||||
onclick={Callback::from(move |_| select_next.set(*select_next + 1))}
|
||||
class="gallery-next"
|
||||
@ -75,7 +76,7 @@ pub fn ImageViewer(props: &ImageViewerProps) -> Html {
|
||||
Some(html! {
|
||||
<Icon
|
||||
icon={yewprint::Icon::ChevronLeft}
|
||||
intent={yewprint::Intent::Warning}
|
||||
intent={CHEVRON_TYPE}
|
||||
size={IconSize(CHEVRON_SIZE)}
|
||||
onclick={Callback::from(move |_| select_prev.set(*select_prev - 1))}
|
||||
class="gallery-prev"
|
||||
|
@ -3,7 +3,7 @@ use gloo::utils::document;
|
||||
use gloo_net::http::Request;
|
||||
use serde::Deserialize;
|
||||
use yew::{function_component, html, Html, UseStateHandle, use_state, use_effect_with_deps, Properties, Children, use_context, Callback};
|
||||
use yewprint::{Divider, Elevation, Card, Tag, Intent, Icon, Overlay};
|
||||
use yewprint::{Divider, Elevation, Card, Tag, Intent, Icon};
|
||||
|
||||
use crate::{util::log, theme::{ThemeContext, ThemeState}, component::image_viewer::{ImageDescription, ImageViewer}};
|
||||
|
||||
|
@ -1,18 +1,56 @@
|
||||
use yew::prelude::*;
|
||||
use yew_router::prelude::*;
|
||||
|
||||
mod home;
|
||||
mod projects;
|
||||
mod contact;
|
||||
mod socials;
|
||||
|
||||
#[derive(PartialEq, Copy, Clone)]
|
||||
#[derive(PartialEq, Copy, Clone, Routable)]
|
||||
pub enum Page {
|
||||
#[at("/")]
|
||||
Home,
|
||||
|
||||
#[at("/projects")]
|
||||
Projects,
|
||||
|
||||
#[at("/contacts")]
|
||||
Contact,
|
||||
|
||||
#[at("/socials")]
|
||||
Socials
|
||||
}
|
||||
|
||||
fn switch(page: Page) -> Html {
|
||||
match page {
|
||||
Page::Home => html!{<home::Home />},
|
||||
Page::Contact => html!{<contact::Contact />},
|
||||
Page::Socials => html!{<socials::Socials />},
|
||||
Page::Projects => html!{<projects::Projects />},
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct PageRouterProps {
|
||||
pub children: Children
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
pub fn PageRouter(props: &PageRouterProps) -> Html {
|
||||
html! {
|
||||
<BrowserRouter>
|
||||
{props.children.clone()}
|
||||
</BrowserRouter>
|
||||
}
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
pub fn Pages() -> Html {
|
||||
html! {
|
||||
<Switch<Page> render={switch} />
|
||||
}
|
||||
}
|
||||
|
||||
impl Page {
|
||||
pub fn name(&self) -> &'static str{
|
||||
match self {
|
||||
@ -22,13 +60,4 @@ impl Page {
|
||||
Page::Contact => "Contact"
|
||||
}
|
||||
}
|
||||
|
||||
pub fn content(&self) -> Html {
|
||||
match self {
|
||||
Page::Home => html!{<home::Home />},
|
||||
Page::Contact => html!{<contact::Contact />},
|
||||
Page::Socials => html!{<socials::Socials />},
|
||||
Page::Projects => html!{<projects::Projects />},
|
||||
}
|
||||
}
|
||||
}
|
40
static/404.html
Normal file
40
static/404.html
Normal file
@ -0,0 +1,40 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Single Page Apps for GitHub Pages</title>
|
||||
<script type="text/javascript">
|
||||
// Single Page Apps for GitHub Pages
|
||||
// MIT License
|
||||
// https://github.com/rafgraph/spa-github-pages
|
||||
// This script takes the current url and converts the path and query
|
||||
// string into just a query string, and then redirects the browser
|
||||
// to the new url with only a query string and hash fragment,
|
||||
// e.g. https://www.foo.tld/one/two?a=b&c=d#qwe, becomes
|
||||
// https://www.foo.tld/?/one/two&a=b~and~c=d#qwe
|
||||
// Note: this 404.html file must be at least 512 bytes for it to work
|
||||
// with Internet Explorer (it is currently > 512 bytes)
|
||||
y
|
||||
// If you're creating a Project Pages site and NOT using a custom domain,
|
||||
// then set pathSegmentsToKeep to 1 (enterprise users ma need to set it to > 1).
|
||||
// This way the code will only replace the route part of the path, and not
|
||||
// the real directory in which the app resides, for example:
|
||||
// https://username.github.io/repo-name/one/two?a=b&c=d#qwe becomes
|
||||
// https://username.github.io/repo-name/?/one/two&a=b~and~c=d#qwe
|
||||
// Otherwise, leave pathSegmentsToKeep as 0.
|
||||
var pathSegmentsToKeep = window.location.href.indexOf("github.io") >= 0 ? 1 : 0;
|
||||
|
||||
var l = window.location;
|
||||
l.replace(
|
||||
l.protocol + '//' + l.hostname + (l.port ? ':' + l.port : '') +
|
||||
l.pathname.split('/').slice(0, 1 + pathSegmentsToKeep).join('/') + '/?/' +
|
||||
l.pathname.slice(1).split('/').slice(pathSegmentsToKeep).join('/').replace(/&/g, '~and~') +
|
||||
(l.search ? '&' + l.search.slice(1).replace(/&/g, '~and~') : '') +
|
||||
l.hash
|
||||
);
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
@ -3,6 +3,28 @@
|
||||
<head>
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400&display=swap" rel="stylesheet">
|
||||
<script type="text/javascript">
|
||||
// Single Page Apps for GitHub Pages
|
||||
// MIT License
|
||||
// https://github.com/rafgraph/spa-github-pages
|
||||
// This script checks to see if a redirect is present in the query string,
|
||||
// converts it back into the correct url and adds it to the
|
||||
// browser's history using window.history.replaceState(...),
|
||||
// which won't cause the browser to attempt to load the new url.
|
||||
// When the single page app is loaded further down in this file,
|
||||
// the correct url will be waiting in the browser's history for
|
||||
// the single page app to route accordingly.
|
||||
(function(l) {
|
||||
if (l.search[1] === '/' ) {
|
||||
var decoded = l.search.slice(1).split('&').map(function(s) {
|
||||
return s.replace(/~and~/g, '&')
|
||||
}).join('?');
|
||||
window.history.replaceState(null, null,
|
||||
l.pathname.slice(0, -1) + decoded + l.hash
|
||||
);
|
||||
}
|
||||
}(window.location))
|
||||
</script>
|
||||
<meta charset="utf-8"/>
|
||||
<script type="module">
|
||||
import init from "/app.js";
|
||||
|
Loading…
x
Reference in New Issue
Block a user