使用自定义属性来实现主题,增加阅读进度
This commit is contained in:
parent
4cea2c3e43
commit
cd67ab3f2e
@ -1,26 +1,20 @@
|
||||
.light{
|
||||
html[data-theme="light"]{
|
||||
--accent-color:#3F51B5;
|
||||
color:black;
|
||||
background-color:white;
|
||||
}
|
||||
|
||||
.dark{
|
||||
html[data-theme="dark"]{
|
||||
--accent-color:#4d648d;
|
||||
color:white;
|
||||
}
|
||||
|
||||
html.dark{
|
||||
background-color:black;
|
||||
}
|
||||
|
||||
html.light{
|
||||
background-color:white;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'AlimamaTi';
|
||||
src: url('/assets/fonts/AlimamaFangYuanTiVF.ttf') format('truetype');
|
||||
font-display: swap;
|
||||
font-weight: 900;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
html {
|
||||
|
@ -558,6 +558,10 @@ video {
|
||||
position: static;
|
||||
}
|
||||
|
||||
.fixed {
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
.absolute {
|
||||
position: absolute;
|
||||
}
|
||||
@ -566,10 +570,22 @@ video {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.left-0 {
|
||||
left: 0px;
|
||||
}
|
||||
|
||||
.right-0 {
|
||||
right: 0px;
|
||||
}
|
||||
|
||||
.right-8 {
|
||||
right: 2rem;
|
||||
}
|
||||
|
||||
.top-0 {
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
.top-5 {
|
||||
top: 1.25rem;
|
||||
}
|
||||
@ -587,10 +603,6 @@ video {
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.mb-5 {
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.ml-1 {
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
@ -599,6 +611,10 @@ video {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.mt-14 {
|
||||
margin-top: 3.5rem;
|
||||
}
|
||||
|
||||
.mt-2 {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
@ -615,6 +631,10 @@ video {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.h-1 {
|
||||
height: 0.25rem;
|
||||
}
|
||||
@ -623,6 +643,14 @@ video {
|
||||
height: 3rem;
|
||||
}
|
||||
|
||||
.h-7 {
|
||||
height: 1.75rem;
|
||||
}
|
||||
|
||||
.h-\[999px\] {
|
||||
height: 999px;
|
||||
}
|
||||
|
||||
.w-\[20rem\] {
|
||||
width: 20rem;
|
||||
}
|
||||
@ -639,6 +667,10 @@ video {
|
||||
min-width: 0px;
|
||||
}
|
||||
|
||||
.min-w-7 {
|
||||
min-width: 1.75rem;
|
||||
}
|
||||
|
||||
.flex-shrink-0 {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
@ -691,6 +723,10 @@ video {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.gap-1 {
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.gap-10 {
|
||||
gap: 2.5rem;
|
||||
}
|
||||
@ -713,6 +749,10 @@ video {
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.rounded-full {
|
||||
border-radius: 9999px;
|
||||
}
|
||||
|
||||
.rounded-md {
|
||||
border-radius: 0.375rem;
|
||||
}
|
||||
@ -753,6 +793,10 @@ video {
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.bg-accent {
|
||||
background-color: var(--accent-color);
|
||||
}
|
||||
|
||||
.bg-blue-400 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(96 165 250 / var(--tw-bg-opacity, 1));
|
||||
@ -788,6 +832,10 @@ video {
|
||||
background-color: rgb(148 163 184 / var(--tw-bg-opacity, 1));
|
||||
}
|
||||
|
||||
.p-2 {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.px-3 {
|
||||
padding-left: 0.75rem;
|
||||
padding-right: 0.75rem;
|
||||
@ -817,6 +865,11 @@ video {
|
||||
line-height: 1.75rem;
|
||||
}
|
||||
|
||||
.text-xs {
|
||||
font-size: 0.75rem;
|
||||
line-height: 1rem;
|
||||
}
|
||||
|
||||
.text-blue-600 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(37 99 235 / var(--tw-text-opacity, 1));
|
||||
@ -857,6 +910,10 @@ video {
|
||||
transition-duration: 200ms;
|
||||
}
|
||||
|
||||
.duration-300 {
|
||||
transition-duration: 300ms;
|
||||
}
|
||||
|
||||
.ease-in-out {
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
@ -905,11 +962,6 @@ video {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.dark\:text-gray-200:is(.dark *) {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(229 231 235 / var(--tw-text-opacity, 1));
|
||||
}
|
||||
|
||||
@media not all and (min-width: 640px) {
|
||||
.max-sm\:w-\[12rem\] {
|
||||
width: 12rem;
|
||||
|
@ -1,23 +1,59 @@
|
||||
use super::Toggle;
|
||||
use crate::utils::dom::{add_element_class, get_document, remove_element_class};
|
||||
use crate::Route;
|
||||
use dioxus::prelude::*;
|
||||
use dioxus::{logger::tracing, prelude::*};
|
||||
use wasm_bindgen::{prelude::Closure, JsCast};
|
||||
|
||||
#[component]
|
||||
pub fn Navbar() -> Element {
|
||||
let mut progress_signal = use_signal(|| 0);
|
||||
let mut progress_hover = use_signal(|| false);
|
||||
|
||||
use_effect(move || {
|
||||
let window = web_sys::window().unwrap();
|
||||
let document = get_document().unwrap();
|
||||
let document_element = document.document_element().unwrap();
|
||||
let closure: Closure<dyn FnMut(_)> = Closure::wrap(Box::new(move |_: web_sys::Event| {
|
||||
if progress_hover() {
|
||||
return;
|
||||
}
|
||||
let screen_height = document_element.scroll_height() as f64;
|
||||
let inner_height = window.inner_height().unwrap().as_f64().unwrap();
|
||||
let scrool_height = window.scroll_y().unwrap();
|
||||
let max_scroll_height = screen_height - inner_height;
|
||||
|
||||
let percent = if max_scroll_height > 0.0 {
|
||||
scrool_height / max_scroll_height
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
let percent = (percent.clamp(0.0, 1.0) * 100.0) as i32;
|
||||
if percent > 0 {
|
||||
let _ = remove_element_class("nav .progress", "hidden");
|
||||
} else {
|
||||
let _ = add_element_class("nav .progress", "hidden");
|
||||
}
|
||||
progress_signal.set(percent);
|
||||
}) as Box<dyn FnMut(_)>);
|
||||
document
|
||||
.add_event_listener_with_callback("scroll", closure.as_ref().unchecked_ref())
|
||||
.unwrap();
|
||||
closure.forget();
|
||||
});
|
||||
rsx! {
|
||||
nav {
|
||||
class: "border-red-500 bg-slate-400 mb-5",
|
||||
div {
|
||||
nav {
|
||||
class: "border-red-500 bg-slate-400 text-xl fixed top-0 left-0 right-0",
|
||||
div {
|
||||
class: "grid grid-cols-3 items-center text-center w-[80%] mx-auto h-12",
|
||||
div {
|
||||
class:"justify-self-start text-xl",
|
||||
class:"justify-self-start",
|
||||
Link {
|
||||
to: Route::Home {},
|
||||
"echoer"
|
||||
}
|
||||
}
|
||||
div {
|
||||
class:"text-xl flex gap-10 justify-center",
|
||||
class:"flex gap-10 justify-center",
|
||||
Link {
|
||||
to: Route::Home {},
|
||||
"首页"
|
||||
@ -31,18 +67,46 @@ pub fn Navbar() -> Element {
|
||||
"首页"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
div {
|
||||
class:"justify-self-end",
|
||||
class:"justify-self-end flex items-center gap-1",
|
||||
Toggle {
|
||||
height: 35,
|
||||
width: 35
|
||||
height: 30,
|
||||
width: 30
|
||||
}
|
||||
div {
|
||||
class:"progress bg-accent h-7 p-2 rounded-full text-xs flex items-center justify-center min-w-7 transition-all duration-300 hidden",
|
||||
onmouseenter:move|_|{
|
||||
progress_hover.set(true);
|
||||
},
|
||||
onmouseleave:move|_|{
|
||||
progress_hover.set(false);
|
||||
},
|
||||
onclick:move|_|{
|
||||
progress_hover.set(false);
|
||||
let window = web_sys::window().unwrap();
|
||||
window.scroll_to_with_x_and_y(0.0, 0.0);
|
||||
|
||||
},
|
||||
{
|
||||
if progress_hover(){
|
||||
"↑".to_string()
|
||||
}
|
||||
else{
|
||||
if progress_signal()==100{
|
||||
"返回顶部".to_string()
|
||||
}
|
||||
else {
|
||||
progress_signal().to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
div {
|
||||
class:"w-[80%] mx-auto",
|
||||
class:"w-[80%] mx-auto mt-14",
|
||||
Outlet::<Route> {}
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,10 @@
|
||||
use std::format;
|
||||
|
||||
use crate::utils::dom::add_element_class;
|
||||
use common::helps::generate_random_string;
|
||||
use crate::Route;
|
||||
use common::helps::generate_random_string;
|
||||
use dioxus::{logger::tracing, prelude::*};
|
||||
use dioxus_free_icons::icons::bs_icons::{BsCheckCircle, BsInfoCircle, BsXCircle, BsXLg};
|
||||
use dioxus_free_icons::Icon;
|
||||
use std::format;
|
||||
use web_sys::window;
|
||||
|
||||
#[derive(PartialEq, Clone)]
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::utils::dom::{
|
||||
add_element_class, get_local_storage_value, get_media_theme, remove_element_class,
|
||||
remove_local_storage_value, set_local_storage_value,
|
||||
get_local_storage_value, get_media_theme, remove_local_storage_value, set_element_dataset,
|
||||
set_local_storage_value,
|
||||
};
|
||||
use dioxus::{logger::tracing, prelude::*};
|
||||
use dioxus_free_icons::icons::bs_icons::BsMoonStars;
|
||||
@ -39,9 +39,8 @@ pub fn Toggle(props: ToggleProps) -> Element {
|
||||
target_theme="light".to_string()
|
||||
};
|
||||
|
||||
let _=remove_element_class("html", &theme_context().0);
|
||||
theme_context.set(ThemeProvider(target_theme.clone()));
|
||||
let _=add_element_class("html", &target_theme);
|
||||
let _=set_element_dataset("html","theme", &target_theme);
|
||||
if target_theme==system_theme {
|
||||
let _=remove_local_storage_value("theme");
|
||||
}else{
|
||||
|
@ -1,16 +1,15 @@
|
||||
use dioxus::{logger::tracing, prelude::*};
|
||||
use wasm_bindgen::prelude::*;
|
||||
mod utils;
|
||||
use wasm_bindgen::{prelude::Closure, JsCast};
|
||||
mod components;
|
||||
mod utils;
|
||||
mod views;
|
||||
use utils::dom::{add_element_class, get_media_theme, remove_element_class};
|
||||
use crate::utils::error::CustomResult;
|
||||
use components::notification::{Notification, NotificationProvider};
|
||||
use components::theme_toggle::{get_theme, ThemeProvider};
|
||||
use components::Navbar;
|
||||
use utils::dom::{get_media_theme, set_element_dataset};
|
||||
use views::Home;
|
||||
|
||||
|
||||
|
||||
#[derive(Debug, Clone, Routable, PartialEq)]
|
||||
enum Route {
|
||||
#[layout(Notification)]
|
||||
@ -35,7 +34,7 @@ fn App() -> Element {
|
||||
let mut is_dark_context = use_context::<Signal<ThemeProvider>>();
|
||||
|
||||
use_effect(move || {
|
||||
let _ = add_element_class("html", &theme);
|
||||
let _ = set_element_dataset("html", "theme", &theme);
|
||||
let window = web_sys::window().unwrap();
|
||||
let media_query = window
|
||||
.match_media("(prefers-color-scheme: dark)")
|
||||
@ -43,8 +42,7 @@ fn App() -> Element {
|
||||
.unwrap();
|
||||
let closure = Closure::wrap(Box::new(move |_: web_sys::MediaQueryListEvent| {
|
||||
let theme = get_media_theme().unwrap_or_else(|_| "light".to_string());
|
||||
let _ = remove_element_class("html", &is_dark_context().0);
|
||||
let _ = add_element_class("html", &theme);
|
||||
let _ = set_element_dataset("html", "theme", &theme);
|
||||
is_dark_context.set(ThemeProvider(theme));
|
||||
}) as Box<dyn FnMut(_)>);
|
||||
media_query
|
||||
|
@ -10,13 +10,13 @@ fn get_storage() -> CustomResult<Storage> {
|
||||
.local_storage()?
|
||||
.ok_or("获取浏览器Storge对象失败".into_custom_error())
|
||||
}
|
||||
fn get_document() -> CustomResult<Document> {
|
||||
pub fn get_document() -> CustomResult<Document> {
|
||||
get_window()?
|
||||
.document()
|
||||
.ok_or("获取浏览器Document对象失败".into_custom_error())
|
||||
}
|
||||
|
||||
fn get_element(ele_name: &str) -> CustomResult<Element> {
|
||||
pub fn get_element(ele_name: &str) -> CustomResult<Element> {
|
||||
get_document()?
|
||||
.query_selector(ele_name)?
|
||||
.ok_or(format!("获取元素{}失败", ele_name).into_custom_error())
|
||||
@ -57,6 +57,22 @@ pub fn remove_element_class(ele_name: &str, class_name: &str) -> CustomResult<()
|
||||
|
||||
pub fn remove_element(ele_name: &str) -> CustomResult<()> {
|
||||
let e = get_element(ele_name)?;
|
||||
let _ = e.parent_node().ok_or("无法获取父节点".into_custom_error())?.remove_child(&e)?;
|
||||
let _ = e
|
||||
.parent_node()
|
||||
.ok_or("无法获取父节点".into_custom_error())?
|
||||
.remove_child(&e)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_element_dataset(
|
||||
ele_name: &str,
|
||||
attribute_name: &str,
|
||||
attribute_value: &str,
|
||||
) -> CustomResult<()> {
|
||||
Ok(get_element(ele_name)?
|
||||
.set_attribute(&format!("data-{}", attribute_name), attribute_value)?)
|
||||
}
|
||||
|
||||
pub fn remove_element_dataset(ele_name: &str, attribute_name: &str) -> CustomResult<()> {
|
||||
Ok(get_element(ele_name)?.remove_attribute(attribute_name)?)
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
use wasm_bindgen::JsValue;
|
||||
use common::error::CommonError;
|
||||
use dioxus::prelude::RenderError;
|
||||
use wasm_bindgen::JsValue;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CustomError(pub String);
|
||||
|
||||
pub trait CustomErrorInto {
|
||||
@ -13,7 +15,6 @@ impl CustomErrorInto for &str {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl From<CommonError> for CustomError {
|
||||
fn from(error: CommonError) -> Self {
|
||||
CustomError(error.0)
|
||||
@ -30,5 +31,10 @@ impl From<JsValue> for CustomError {
|
||||
}
|
||||
}
|
||||
|
||||
pub type CustomResult<T> = Result<T, CustomError>;
|
||||
impl From<RenderError> for CustomError {
|
||||
fn from(error: RenderError) -> Self {
|
||||
CustomError(error.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub type CustomResult<T> = Result<T, CustomError>;
|
||||
|
@ -5,7 +5,7 @@ use dioxus::prelude::*;
|
||||
pub fn Home() -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
class:"border-2 border-red-500",
|
||||
class:"border-2 border-red-500 h-[999px]",
|
||||
"hello,world"
|
||||
ul {
|
||||
li { "nihao" }
|
||||
|
@ -24,5 +24,5 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
darkMode: ['class'],
|
||||
darkMode: ['data-theme="dark"'],
|
||||
};
|
||||
|
@ -1,4 +1,8 @@
|
||||
use surrealdb::{engine::any::{connect,Any}, Surreal,opt::auth::Root};
|
||||
use surrealdb::{
|
||||
engine::any::{connect, Any},
|
||||
opt::auth::Root,
|
||||
Surreal,
|
||||
};
|
||||
|
||||
use crate::utils::error::CustomResult;
|
||||
|
||||
@ -24,7 +28,7 @@ impl Database {
|
||||
Ok(Self { client })
|
||||
}
|
||||
|
||||
pub fn get_client(&self) -> &Surreal<Any> {
|
||||
pub fn get_client(&self) -> &Surreal<Any> {
|
||||
&self.client
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user