From 651d18ca80bb9048d05f8d7d69853d23929ad73b Mon Sep 17 00:00:00 2001 From: Kayori Date: Sat, 7 Sep 2024 00:26:11 +0200 Subject: [PATCH] Added some more handlers and weird nostalgia f query thing --- .gitignore | 4 +- .vscode/launch.json | 45 ++++++ src/handlers/boot/get_services_handler.rs | 2 +- .../common/alive_pcb_tracker_handler.rs | 2 +- src/handlers/common/get_messages_handler.rs | 48 ++++++ src/handlers/common/mod.rs | 6 +- .../common/ota/list_package_handler.rs | 48 ++++++ src/handlers/common/ota/mod.rs | 2 + .../common/ota/progress_dl_status_handler.rs | 47 ++++++ src/handlers/common/put_pcb_event_handler.rs | 142 ++++++++++++++++++ src/handlers/core_e_amuse_request_handler.rs | 8 + src/handlers/handler.rs | 1 - src/main.rs | 24 ++- src/parsers/header_parser.rs | 2 +- src/routes/ea/core_route.rs | 28 ++-- src/routes/ea/e_amusement_route.rs | 23 +++ src/routes/ea/mod.rs | 3 +- web/bun.lockb | Bin 139229 -> 165405 bytes web/package.json | 4 + web/postcss.config.js | 6 + web/src/assets/base.css | 86 ----------- web/src/assets/main.css | 38 +---- web/src/components/HelloWorld.vue | 2 +- web/tailwind.config.js | 10 ++ ....timestamp-1725557239767-9dda8c8f827e5.mjs | 21 --- ....timestamp-1725611137987-504512dd3fecb.mjs | 21 --- ....timestamp-1725611236802-6f31184b59262.mjs | 21 --- ....timestamp-1725611959441-fab86b25a0ef7.mjs | 21 --- ....timestamp-1725621790222-41fa60a8534ba.mjs | 21 --- 29 files changed, 430 insertions(+), 256 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 src/handlers/common/get_messages_handler.rs create mode 100644 src/handlers/common/ota/list_package_handler.rs create mode 100644 src/handlers/common/ota/mod.rs create mode 100644 src/handlers/common/ota/progress_dl_status_handler.rs create mode 100644 src/handlers/common/put_pcb_event_handler.rs create mode 100644 src/routes/ea/e_amusement_route.rs create mode 100644 web/postcss.config.js delete mode 100644 web/src/assets/base.css create mode 100644 web/tailwind.config.js delete mode 100644 web/vite.config.ts.timestamp-1725557239767-9dda8c8f827e5.mjs delete mode 100644 web/vite.config.ts.timestamp-1725611137987-504512dd3fecb.mjs delete mode 100644 web/vite.config.ts.timestamp-1725611236802-6f31184b59262.mjs delete mode 100644 web/vite.config.ts.timestamp-1725611959441-fab86b25a0ef7.mjs delete mode 100644 web/vite.config.ts.timestamp-1725621790222-41fa60a8534ba.mjs diff --git a/.gitignore b/.gitignore index 30cb2d2..cb91d78 100644 --- a/.gitignore +++ b/.gitignore @@ -171,4 +171,6 @@ docs/_book # TODO: where does this rule come from? test/ -static/ \ No newline at end of file +static/ + +bun.lockb \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..e8663b8 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,45 @@ +{ + // 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": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug executable 'medusa'", + "cargo": { + "args": [ + "build", + "--bin=medusa", + "--package=medusa" + ], + "filter": { + "name": "medusa", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in executable 'medusa'", + "cargo": { + "args": [ + "test", + "--no-run", + "--bin=medusa", + "--package=medusa" + ], + "filter": { + "name": "medusa", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/src/handlers/boot/get_services_handler.rs b/src/handlers/boot/get_services_handler.rs index a16a784..0f180b8 100644 --- a/src/handlers/boot/get_services_handler.rs +++ b/src/handlers/boot/get_services_handler.rs @@ -189,7 +189,7 @@ impl Handler for GetServicesHandler { } async fn handle(&self, model: String, _body: String) -> String { - let common_url = "http://127.0.0.1:8000"; + let common_url = "http://127.0.0.1:8000/ea"; let listening_address = "http://127.0.0.1:8000"; let services = GetServicesHandler::create_core_services_element( diff --git a/src/handlers/common/alive_pcb_tracker_handler.rs b/src/handlers/common/alive_pcb_tracker_handler.rs index cc47116..8c31cd3 100644 --- a/src/handlers/common/alive_pcb_tracker_handler.rs +++ b/src/handlers/common/alive_pcb_tracker_handler.rs @@ -47,8 +47,8 @@ impl Handler for AlivePcbTrackerHandler { .attr("time", &utc_now); writer.write(pcb_tracker).unwrap(); - writer.write(XmlEvent::end_element()).unwrap(); + writer.write(XmlEvent::end_element()).unwrap(); String::from_utf8(buffer.into_inner()).unwrap() diff --git a/src/handlers/common/get_messages_handler.rs b/src/handlers/common/get_messages_handler.rs new file mode 100644 index 0000000..f2ef529 --- /dev/null +++ b/src/handlers/common/get_messages_handler.rs @@ -0,0 +1,48 @@ +use std::io::Cursor; +use crate::handlers::handler::Handler; +use xml::writer::{EmitterConfig, XmlEvent}; + +pub struct GetMessageHandler { + module: String, + method: String, +} + +impl GetMessageHandler { + pub fn new() -> Self { + GetMessageHandler { + module: "message".to_string(), + method: "get".to_string(), + } + } +} + +#[async_trait] +impl Handler for GetMessageHandler { + fn module(&self) -> &str { + &self.module + } + + fn method(&self) -> &str { + &self.method + } + + async fn handle(&self, _model: String, _body: String) -> String { + let mut buffer = Cursor::new(vec![]); + let mut writer = EmitterConfig::new() + .perform_indent(true) + .create_writer(&mut buffer); + + writer.write(XmlEvent::start_element("response")).unwrap(); + + let message = XmlEvent::start_element("message") + .attr("expire", "300") + .attr("status", "0"); + + writer.write(message).unwrap(); + writer.write(XmlEvent::end_element()).unwrap(); + + writer.write(XmlEvent::end_element()).unwrap(); + + String::from_utf8(buffer.into_inner()).unwrap() + } +} diff --git a/src/handlers/common/mod.rs b/src/handlers/common/mod.rs index dd43bc2..1681a77 100644 --- a/src/handlers/common/mod.rs +++ b/src/handlers/common/mod.rs @@ -1,2 +1,6 @@ -pub mod alive_pcb_tracker_handler; \ No newline at end of file +pub mod alive_pcb_tracker_handler; +pub mod get_messages_handler; +pub mod put_pcb_event_handler; + +pub mod ota; \ No newline at end of file diff --git a/src/handlers/common/ota/list_package_handler.rs b/src/handlers/common/ota/list_package_handler.rs new file mode 100644 index 0000000..84ed4d1 --- /dev/null +++ b/src/handlers/common/ota/list_package_handler.rs @@ -0,0 +1,48 @@ +use std::io::Cursor; +use crate::handlers::handler::Handler; +use xml::writer::{EmitterConfig, XmlEvent}; + +pub struct ListPackageHandler { + module: String, + method: String, +} + +impl ListPackageHandler { + pub fn new() -> Self { + ListPackageHandler { + module: "package".to_string(), + method: "list".to_string(), + } + } +} + +#[async_trait] +impl Handler for ListPackageHandler { + fn module(&self) -> &str { + &self.module + } + + fn method(&self) -> &str { + &self.method + } + + async fn handle(&self, _model: String, _body: String) -> String { + let mut buffer = Cursor::new(vec![]); + let mut writer = EmitterConfig::new() + .perform_indent(true) + .create_writer(&mut buffer); + + writer.write(XmlEvent::start_element("response")).unwrap(); + + let message = XmlEvent::start_element("package") + .attr("expire", "600") + .attr("status", "0"); + + writer.write(message).unwrap(); + writer.write(XmlEvent::end_element()).unwrap(); + + writer.write(XmlEvent::end_element()).unwrap(); + + String::from_utf8(buffer.into_inner()).unwrap() + } +} diff --git a/src/handlers/common/ota/mod.rs b/src/handlers/common/ota/mod.rs new file mode 100644 index 0000000..ff7b27a --- /dev/null +++ b/src/handlers/common/ota/mod.rs @@ -0,0 +1,2 @@ +pub mod list_package_handler; +pub mod progress_dl_status_handler; \ No newline at end of file diff --git a/src/handlers/common/ota/progress_dl_status_handler.rs b/src/handlers/common/ota/progress_dl_status_handler.rs new file mode 100644 index 0000000..2464fda --- /dev/null +++ b/src/handlers/common/ota/progress_dl_status_handler.rs @@ -0,0 +1,47 @@ +use std::io::Cursor; +use crate::handlers::handler::Handler; +use xml::writer::{EmitterConfig, XmlEvent}; + +pub struct ProgressDLStatusHandler { + module: String, + method: String, +} + +impl ProgressDLStatusHandler { + pub fn new() -> Self { + ProgressDLStatusHandler { + module: "dlstatus".to_string(), + method: "progress".to_string(), + } + } +} + +#[async_trait] +impl Handler for ProgressDLStatusHandler { + fn module(&self) -> &str { + &self.module + } + + fn method(&self) -> &str { + &self.method + } + + async fn handle(&self, _model: String, _body: String) -> String { + let mut buffer = Cursor::new(vec![]); + let mut writer = EmitterConfig::new() + .perform_indent(true) + .create_writer(&mut buffer); + + writer.write(XmlEvent::start_element("response")).unwrap(); + + let message = XmlEvent::start_element("dlstatus") + .attr("status", "0"); + + writer.write(message).unwrap(); + writer.write(XmlEvent::end_element()).unwrap(); + + writer.write(XmlEvent::end_element()).unwrap(); + + String::from_utf8(buffer.into_inner()).unwrap() + } +} diff --git a/src/handlers/common/put_pcb_event_handler.rs b/src/handlers/common/put_pcb_event_handler.rs new file mode 100644 index 0000000..abd52d5 --- /dev/null +++ b/src/handlers/common/put_pcb_event_handler.rs @@ -0,0 +1,142 @@ +use std::io::{BufReader, Cursor}; +use chrono::DateTime; +use xml::{reader::{EventReader, XmlEvent}, EmitterConfig}; + +use crate::handlers::handler::Handler; + + + +struct PcbEvent { + tag: String, + src_id: String, + model: String, + name: String, + time: String, +} + +pub struct PutPcbEventHandler { + module: String, + method: String, +} + +impl PutPcbEventHandler { + pub fn new() -> Self { + PutPcbEventHandler { + module: "pcbevent".to_string(), + method: "put".to_string(), + } + } + + fn unix_timestamp_to_datetime(timestamp: f64) -> String { + let naive = DateTime::from_timestamp(timestamp as i64, 0); + match naive { + Some(time) => time.format("%Y-%m-%d %H:%M:%S").to_string(), + None => "".to_string(), + } + } + + fn parse_pcb_event(xml_data: &str) -> Option { + let reader = EventReader::new(BufReader::new(xml_data.as_bytes())); + let mut pcb_event = PcbEvent { + tag: String::new(), + src_id: String::new(), + model: String::new(), + name: String::new(), + time: String::new(), + }; + + let mut in_pcbevent = false; + let mut in_item = false; + let mut current_tag = String::new(); + + for event in reader { + match event { + Ok(XmlEvent::StartElement { + name, attributes, .. + }) => { + if name.local_name == "pcbevent" { + in_pcbevent = true; + for attr in attributes { + match attr.name.local_name.as_str() { + "tag" => pcb_event.tag = attr.value.clone(), + "srcid" => pcb_event.src_id = attr.value.clone(), + "model" => pcb_event.model = attr.value.clone(), + _ => {} + } + } + } else if in_pcbevent && name.local_name == "item" { + in_item = true; + } + current_tag = name.local_name.to_string(); + } + Ok(XmlEvent::Characters(content)) => { + if in_item { + match current_tag.as_str() { + "name" => pcb_event.name = content.to_string(), + "time" => { + if let Ok(timestamp) = content.parse::() { + pcb_event.time = Self::unix_timestamp_to_datetime(timestamp); + } + } + _ => {} + } + } + } + Ok(XmlEvent::EndElement { name }) => { + if name.local_name == "item" { + in_item = false; + } else if name.local_name == "pcbevent" { + in_pcbevent = false; + } + } + Err(e) => { + println!("Error: {}", e); + return None; + } + _ => {} + } + } + + Some(pcb_event) + } +} + +#[async_trait] +impl Handler for PutPcbEventHandler { + fn module(&self) -> &str { + &self.module + } + + fn method(&self) -> &str { + &self.method + } + + async fn handle(&self, _model: String, body: String) -> String { + let pcb_event_read = Self::parse_pcb_event(body.as_str()).unwrap(); + + println!("{}", format!( + "PcbEvent {{ tag: '{}', src_id: '{}', model: '{}', name: '{}', time: '{}' }}", + pcb_event_read.tag, + pcb_event_read.src_id, + pcb_event_read.model, + pcb_event_read.name, + pcb_event_read.time + )); + + let mut buffer = Cursor::new(vec![]); + let mut writer = EmitterConfig::new() + .perform_indent(true) + .create_writer(&mut buffer); + + writer.write(xml::writer::XmlEvent::start_element("response")).unwrap(); + + let pcb_event = xml::writer::XmlEvent::start_element("pcbevent").attr("status", "0"); + + writer.write(pcb_event).unwrap(); + writer.write(xml::writer::XmlEvent::end_element()).unwrap(); + + writer.write(xml::writer::XmlEvent::end_element()).unwrap(); + + String::from_utf8(buffer.into_inner()).unwrap() + } +} diff --git a/src/handlers/core_e_amuse_request_handler.rs b/src/handlers/core_e_amuse_request_handler.rs index 7dbdf84..166a1e8 100644 --- a/src/handlers/core_e_amuse_request_handler.rs +++ b/src/handlers/core_e_amuse_request_handler.rs @@ -77,13 +77,21 @@ pub async fn handle_e_amuse_request(model: &str, module: &str, method: &str, dat let mut response_xml = String::new(); + let mut handler_found = false; + for handler in handlers.iter() { if handler.module() == module && handler.method() == method { let model = model.to_string(); let xml_string = xml_string.clone(); response_xml = handler.handle(model, xml_string).await; + handler_found = true; } } + + if !handler_found { + eprintln!("No handler found for module: {} and method: {}", module, method); + return Err(BadRequest("No handler found".to_string())); + } let response = ResponseGenerator::generate_response(response_xml.into(), is_compressed, info.clone()); // Return the binary data wrapped in BinaryResponse diff --git a/src/handlers/handler.rs b/src/handlers/handler.rs index 64b138d..4f68d04 100644 --- a/src/handlers/handler.rs +++ b/src/handlers/handler.rs @@ -1,4 +1,3 @@ -use std::{future::Future, sync::Arc}; #[async_trait] pub trait Handler: Send + Sync { diff --git a/src/main.rs b/src/main.rs index 17c09aa..366fb7d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,11 +9,15 @@ mod routes; use handlers::boot::get_services_handler::GetServicesHandler; use handlers::common::alive_pcb_tracker_handler::AlivePcbTrackerHandler; +use handlers::common::get_messages_handler::GetMessageHandler; +use handlers::common::ota::list_package_handler::ListPackageHandler; +use handlers::common::ota::progress_dl_status_handler::ProgressDLStatusHandler; +use handlers::common::put_pcb_event_handler::PutPcbEventHandler; use handlers::handler::Handler; use rocket::fs::{relative, FileServer, NamedFile}; -use routes::ea::core_route::handle_nostalgia_request; use routes::ea::core_route::handle_query_request; use routes::ea::core_route::handle_slash_request; +use routes::ea::e_amusement_route::handle_ea_query_request; #[macro_use] extern crate lazy_static; #[macro_use] extern crate rocket; @@ -24,12 +28,22 @@ async fn index() -> Option { } fn register_handlers() -> Vec> { - let mut handlers: Vec> = Vec::new(); let get_services_handler = Arc::new(GetServicesHandler::new()); let alive_pcb_tracker_handler = Arc::new(AlivePcbTrackerHandler::new()); - handlers.push(get_services_handler); - handlers.push(alive_pcb_tracker_handler); + let get_messages_handler = Arc::new(GetMessageHandler::new()); + let put_pcb_event_handler = Arc::new(PutPcbEventHandler::new()); + let list_package_handler = Arc::new(ListPackageHandler::new()); + let progress_dl_status_handler = Arc::new(ProgressDLStatusHandler::new()); + + let handlers: Vec> = vec![ + get_services_handler, + alive_pcb_tracker_handler, + get_messages_handler, + put_pcb_event_handler, + list_package_handler, + progress_dl_status_handler + ]; handlers } @@ -40,6 +54,6 @@ fn rocket() -> _ { rocket::build() .manage(handlers) - .mount("/", routes![index, handle_query_request, handle_slash_request, handle_slash_request, handle_nostalgia_request]) + .mount("/", routes![index, handle_query_request, handle_slash_request, handle_ea_query_request]) .mount("/", FileServer::from(relative!("static"))) } diff --git a/src/parsers/header_parser.rs b/src/parsers/header_parser.rs index 52ffb50..0f916d0 100644 --- a/src/parsers/header_parser.rs +++ b/src/parsers/header_parser.rs @@ -33,7 +33,7 @@ impl<'r> FromRequest<'r> for EAMuseHeaders { let is_compressed = matches!(is_compressed_header, Some(value) if value == "true"); // Get the `info` header, or set a default if it's not found - let info = info_header.unwrap_or("default_info").to_string(); + let info = info_header.unwrap_or("").to_string(); // Construct the EAMuseHeaders struct let eamuse_headers = EAMuseHeaders { diff --git a/src/routes/ea/core_route.rs b/src/routes/ea/core_route.rs index 50d2295..1ee23f7 100644 --- a/src/routes/ea/core_route.rs +++ b/src/routes/ea/core_route.rs @@ -14,25 +14,19 @@ e_amuse_headers: EAMuseHeaders, handlers: &State>>) -> Resu handle_e_amuse_request(model, module, method, data, is_compressed, info, handlers.to_vec()).await } -#[post("/?&&", data = "")] -pub async fn handle_query_request(model: &str, module: &str, method: &str, data: Data<'_>, +#[post("/?&&&", data = "")] +pub async fn handle_query_request(model: &str, f: Option, module: Option, method: Option, data: Data<'_>, e_amuse_headers: EAMuseHeaders, handlers: &State>>) -> Result> { let is_compressed = e_amuse_headers.is_compressed; let info = e_amuse_headers.info; - handle_e_amuse_request(model, module, method, data, is_compressed, info, handlers.to_vec()).await + if f.is_some() { + let f = f.unwrap(); + let f_split = f.split('.').collect::>(); + let module = Some(f_split[0].to_string()); + let method = Some(f_split[1].to_string()); + return handle_e_amuse_request(model, module.unwrap().as_str(), method.unwrap().as_str(), data, is_compressed, info, handlers.to_vec()).await; + } + + handle_e_amuse_request(model, module.unwrap().as_str(), method.unwrap().as_str(), data, is_compressed, info, handlers.to_vec()).await } - -// TODO weird nostalgia request url -#[post("/?", data = "")] -pub async fn handle_nostalgia_request(model: &str, f: &str, data: Data<'_>, -e_amuse_headers: EAMuseHeaders, handlers: &State>>) -> Result> { - let is_compressed = e_amuse_headers.is_compressed; - let info = e_amuse_headers.info; - - let f_splt = f.split('.').collect::>(); - let module = f_splt[0]; - let service = f_splt[1]; - - handle_e_amuse_request(model, module, service, data, is_compressed, info, handlers.to_vec()).await -} \ No newline at end of file diff --git a/src/routes/ea/e_amusement_route.rs b/src/routes/ea/e_amusement_route.rs new file mode 100644 index 0000000..b05f51a --- /dev/null +++ b/src/routes/ea/e_amusement_route.rs @@ -0,0 +1,23 @@ +use std::sync::Arc; +use rocket::response::status::BadRequest; +use rocket::data::Data; +use rocket::State; + +use crate::{handlers::{core_e_amuse_request_handler::{handle_e_amuse_request, BinaryResponse}, handler::Handler}, parsers::header_parser::EAMuseHeaders}; + +#[post("/ea/<_module>?&&&", data = "")] +pub async fn handle_ea_query_request(_module: &str, model: &str, f: Option, module: Option, method: Option, data: Data<'_>, +e_amuse_headers: EAMuseHeaders, handlers: &State>>) -> Result> { + let is_compressed = e_amuse_headers.is_compressed; + let info = e_amuse_headers.info; + + if f.is_some() { + let f = f.unwrap(); + let f_split = f.split('.').collect::>(); + let module = Some(f_split[0].to_string()); + let method = Some(f_split[1].to_string()); + return handle_e_amuse_request(model, module.unwrap().as_str(), method.unwrap().as_str(), data, is_compressed, info, handlers.to_vec()).await; + } + + handle_e_amuse_request(model, module.unwrap().as_str(), method.unwrap().as_str(), data, is_compressed, info, handlers.to_vec()).await +} \ No newline at end of file diff --git a/src/routes/ea/mod.rs b/src/routes/ea/mod.rs index 8f4ef2c..bd3c7cb 100644 --- a/src/routes/ea/mod.rs +++ b/src/routes/ea/mod.rs @@ -1,2 +1,3 @@ -pub mod core_route; \ No newline at end of file +pub mod core_route; +pub mod e_amusement_route; \ No newline at end of file diff --git a/web/bun.lockb b/web/bun.lockb index c66bc685ba5d528fef6242f3029a696bf2064b8b..13530a021b50960d401f971ef36765f2a2287d6a 100644 GIT binary patch delta 38124 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 delta 22475 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 diff --git a/web/package.json b/web/package.json index 2ac35e4..17c09c8 100644 --- a/web/package.json +++ b/web/package.json @@ -25,10 +25,14 @@ "@vue/eslint-config-prettier": "^9.0.0", "@vue/eslint-config-typescript": "^13.0.0", "@vue/tsconfig": "^0.5.1", + "autoprefixer": "^10.4.20", + "daisyui": "^4.12.10", "eslint": "^8.57.0", "eslint-plugin-vue": "^9.23.0", "npm-run-all2": "^6.2.0", + "postcss": "^8.4.45", "prettier": "^3.2.5", + "tailwindcss": "^3.4.10", "typescript": "~5.4.0", "vite": "^5.3.1", "vite-plugin-vue-devtools": "^7.3.1", diff --git a/web/postcss.config.js b/web/postcss.config.js new file mode 100644 index 0000000..2e7af2b --- /dev/null +++ b/web/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/web/src/assets/base.css b/web/src/assets/base.css deleted file mode 100644 index 8816868..0000000 --- a/web/src/assets/base.css +++ /dev/null @@ -1,86 +0,0 @@ -/* color palette from */ -:root { - --vt-c-white: #ffffff; - --vt-c-white-soft: #f8f8f8; - --vt-c-white-mute: #f2f2f2; - - --vt-c-black: #181818; - --vt-c-black-soft: #222222; - --vt-c-black-mute: #282828; - - --vt-c-indigo: #2c3e50; - - --vt-c-divider-light-1: rgba(60, 60, 60, 0.29); - --vt-c-divider-light-2: rgba(60, 60, 60, 0.12); - --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65); - --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48); - - --vt-c-text-light-1: var(--vt-c-indigo); - --vt-c-text-light-2: rgba(60, 60, 60, 0.66); - --vt-c-text-dark-1: var(--vt-c-white); - --vt-c-text-dark-2: rgba(235, 235, 235, 0.64); -} - -/* semantic color variables for this project */ -:root { - --color-background: var(--vt-c-white); - --color-background-soft: var(--vt-c-white-soft); - --color-background-mute: var(--vt-c-white-mute); - - --color-border: var(--vt-c-divider-light-2); - --color-border-hover: var(--vt-c-divider-light-1); - - --color-heading: var(--vt-c-text-light-1); - --color-text: var(--vt-c-text-light-1); - - --section-gap: 160px; -} - -@media (prefers-color-scheme: dark) { - :root { - --color-background: var(--vt-c-black); - --color-background-soft: var(--vt-c-black-soft); - --color-background-mute: var(--vt-c-black-mute); - - --color-border: var(--vt-c-divider-dark-2); - --color-border-hover: var(--vt-c-divider-dark-1); - - --color-heading: var(--vt-c-text-dark-1); - --color-text: var(--vt-c-text-dark-2); - } -} - -*, -*::before, -*::after { - box-sizing: border-box; - margin: 0; - font-weight: normal; -} - -body { - min-height: 100vh; - color: var(--color-text); - background: var(--color-background); - transition: - color 0.5s, - background-color 0.5s; - line-height: 1.6; - font-family: - Inter, - -apple-system, - BlinkMacSystemFont, - 'Segoe UI', - Roboto, - Oxygen, - Ubuntu, - Cantarell, - 'Fira Sans', - 'Droid Sans', - 'Helvetica Neue', - sans-serif; - font-size: 15px; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} diff --git a/web/src/assets/main.css b/web/src/assets/main.css index 36fb845..bd6213e 100644 --- a/web/src/assets/main.css +++ b/web/src/assets/main.css @@ -1,35 +1,3 @@ -@import './base.css'; - -#app { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - font-weight: normal; -} - -a, -.green { - text-decoration: none; - color: hsla(160, 100%, 37%, 1); - transition: 0.4s; - padding: 3px; -} - -@media (hover: hover) { - a:hover { - background-color: hsla(160, 100%, 37%, 0.2); - } -} - -@media (min-width: 1024px) { - body { - display: flex; - place-items: center; - } - - #app { - display: grid; - grid-template-columns: 1fr 1fr; - padding: 0 2rem; - } -} +@tailwind base; +@tailwind components; +@tailwind utilities; \ No newline at end of file diff --git a/web/src/components/HelloWorld.vue b/web/src/components/HelloWorld.vue index 38d821e..b6a59ab 100644 --- a/web/src/components/HelloWorld.vue +++ b/web/src/components/HelloWorld.vue @@ -6,7 +6,7 @@ defineProps<{