feat: base frame
This commit is contained in:
Vendored
+1
@@ -11,5 +11,6 @@ declare module 'vue' {
|
|||||||
Greet: typeof import('./src/components/Greet.vue')['default']
|
Greet: typeof import('./src/components/Greet.vue')['default']
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
|
TablerDeviceWatch: typeof import('./src/components/icons/TablerDeviceWatch.vue')['default']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,12 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tauri-apps/cli": "^1",
|
"@tauri-apps/cli": "^1",
|
||||||
"@vitejs/plugin-vue": "^5.0.5",
|
"@vitejs/plugin-vue": "^5.0.5",
|
||||||
|
"autoprefixer": "^10.4.19",
|
||||||
|
"postcss": "^8.4.39",
|
||||||
|
"sass": "^1.77.8",
|
||||||
|
"tailwindcss": "^3.4.4",
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.2.2",
|
||||||
|
"unplugin-vue-components": "^0.27.2",
|
||||||
"vite": "^5.3.1",
|
"vite": "^5.3.1",
|
||||||
"vue-tsc": "^2.0.22"
|
"vue-tsc": "^2.0.22"
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+1018
-5
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,6 @@
|
|||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
Generated
+11
@@ -2670,6 +2670,15 @@ dependencies = [
|
|||||||
"lazy_static",
|
"lazy_static",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-registry"
|
||||||
|
version = "1.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "simd-adler32"
|
name = "simd-adler32"
|
||||||
version = "0.3.7"
|
version = "0.3.7"
|
||||||
@@ -3216,7 +3225,9 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
"mio",
|
"mio",
|
||||||
"num_cpus",
|
"num_cpus",
|
||||||
|
"parking_lot",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
|
"signal-hook-registry",
|
||||||
"socket2",
|
"socket2",
|
||||||
"tokio-macros",
|
"tokio-macros",
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ tauri = { version = "1", features = [ "window-show", "window-hide", "window-maxi
|
|||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
btleplug = { version = "0.11.5", features = ["serde"] }
|
btleplug = { version = "0.11.5", features = ["serde"] }
|
||||||
tokio = { version = "1.38.0", features = ["rt", "rt-multi-thread", "macros"] }
|
tokio = { version = "1.38.0", features = ["full"] }
|
||||||
futures = "0.3.30"
|
futures = "0.3.30"
|
||||||
uuid = "1.10.0"
|
uuid = "1.10.0"
|
||||||
lazy_static = "1.5.0"
|
lazy_static = "1.5.0"
|
||||||
|
|||||||
+126
-39
@@ -1,13 +1,17 @@
|
|||||||
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||||
|
|
||||||
use tauri::{AppHandle, Manager, State};
|
|
||||||
use tokio;
|
|
||||||
use tokio::sync::{Mutex};
|
|
||||||
use btleplug::api::{Central, ScanFilter, Manager as _, Peripheral as _};
|
|
||||||
use btleplug::api::bleuuid::uuid_from_u16;
|
use btleplug::api::bleuuid::uuid_from_u16;
|
||||||
|
use btleplug::api::CentralEvent::{
|
||||||
|
DeviceConnected, DeviceDisconnected, DeviceDiscovered, DeviceUpdated,
|
||||||
|
ManufacturerDataAdvertisement, ServiceDataAdvertisement, ServicesAdvertisement,
|
||||||
|
};
|
||||||
|
use btleplug::api::{Central, Manager as _, Peripheral as _, ScanFilter};
|
||||||
use btleplug::platform::{Adapter, Manager as BtleManager, Peripheral};
|
use btleplug::platform::{Adapter, Manager as BtleManager, Peripheral};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
|
use tauri::{AppHandle, Manager, State};
|
||||||
|
use tokio;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
#[derive(serde::Serialize, Clone)]
|
#[derive(serde::Serialize, Clone)]
|
||||||
struct BleDevice {
|
struct BleDevice {
|
||||||
@@ -15,30 +19,90 @@ struct BleDevice {
|
|||||||
address: String,
|
address: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct BleConnection {
|
struct BleConnection<'a> {
|
||||||
manager: Mutex<Option<BtleManager>>,
|
central: Mutex<Option<Adapter>>,
|
||||||
adapter: Mutex<Option<Adapter>>,
|
peripheral: &'a Mutex<Option<Peripheral>>,
|
||||||
peripheral: Mutex<Option<Peripheral>>,
|
scan_devices: Mutex<Vec<BleDevice>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn scan_devices(
|
async fn request_central_events<'a>(
|
||||||
connection: State<'_, BleConnection>
|
app_handle: AppHandle,
|
||||||
) -> Result<String, String> {
|
connection: State<'a, BleConnection<'a>>,
|
||||||
let adapter = connection.adapter.lock().await;
|
) -> Result<bool, String> {
|
||||||
let adapter = adapter.as_ref().unwrap();
|
let central = connection.central.lock().await;
|
||||||
adapter.start_scan(ScanFilter::default()).await.unwrap();
|
let peripheral = connection.peripheral.lock().await;
|
||||||
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
|
let central = central.as_ref().unwrap();
|
||||||
|
let central = central.clone();
|
||||||
|
|
||||||
|
let mut event_stream = central.events().await.unwrap();
|
||||||
|
|
||||||
|
tauri::async_runtime::spawn(async move {
|
||||||
|
while let Some(event) = event_stream.next().await {
|
||||||
|
if let DeviceDiscovered(_) | DeviceUpdated(_) = event.clone() {
|
||||||
let mut devices = vec![];
|
let mut devices = vec![];
|
||||||
for p in adapter.peripherals().await.unwrap() {
|
for p in central.peripherals().await.unwrap() {
|
||||||
devices.push(BleDevice {
|
devices.push(BleDevice {
|
||||||
name: p.properties().await.unwrap().unwrap().local_name.unwrap_or("Unknown".to_string()),
|
name: p
|
||||||
|
.properties()
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap()
|
||||||
|
.local_name
|
||||||
|
.unwrap_or("Unknown".to_string()),
|
||||||
address: p.address().to_string(),
|
address: p.address().to_string(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
println!("Devices: {:?}", serde_json::to_string(&devices).unwrap());
|
||||||
|
app_handle
|
||||||
|
.emit_all("scan-list-update", serde_json::to_string(&devices).unwrap())
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
if let DeviceConnected(_) = event.clone() {
|
||||||
|
let peripheral = peripheral.as_ref().unwrap().clone();
|
||||||
|
app_handle
|
||||||
|
.emit_all(
|
||||||
|
"device-connected",
|
||||||
|
serde_json::to_string(&BleDevice {
|
||||||
|
name: peripheral
|
||||||
|
.properties()
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap()
|
||||||
|
.local_name
|
||||||
|
.unwrap_or("Unknown".to_string()),
|
||||||
|
address: peripheral.address().to_string(),
|
||||||
|
})
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
Ok(serde_json::to_string(&devices).unwrap())
|
Ok(true)
|
||||||
|
}
|
||||||
|
#[tauri::command]
|
||||||
|
async fn start_scan(connection: State<'_, BleConnection>) -> Result<bool, String> {
|
||||||
|
let central = connection.central.lock().await;
|
||||||
|
let central = central.as_ref().unwrap();
|
||||||
|
central
|
||||||
|
.start_scan(ScanFilter::default())
|
||||||
|
.await
|
||||||
|
.unwrap_or_else(|_| {
|
||||||
|
println!("Failed to start scan");
|
||||||
|
});
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn stop_scan(connection: State<'_, BleConnection>) -> Result<bool, String> {
|
||||||
|
let central = connection.central.lock().await;
|
||||||
|
let central = central.as_ref().unwrap();
|
||||||
|
central.stop_scan().await.unwrap_or_else(|_| {
|
||||||
|
println!("Failed to stop scan");
|
||||||
|
});
|
||||||
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@@ -47,12 +111,17 @@ async fn connect(
|
|||||||
connection: State<'_, BleConnection>,
|
connection: State<'_, BleConnection>,
|
||||||
app_handle: AppHandle,
|
app_handle: AppHandle,
|
||||||
) -> Result<bool, String> {
|
) -> Result<bool, String> {
|
||||||
let adapter = connection.adapter.lock().await;
|
let central = connection.central.lock().await;
|
||||||
let adapter = adapter.as_ref().unwrap();
|
let central = central.as_ref().unwrap();
|
||||||
// adapter.start_scan(ScanFilter::default()).await.unwrap();
|
|
||||||
// tokio::time::sleep(std::time::Duration::from_secs(2)).await;
|
|
||||||
|
|
||||||
let peripheral = adapter.peripherals().await.unwrap().into_iter().find(|p| p.address().to_string() == address).unwrap().clone();
|
let peripheral = central
|
||||||
|
.peripherals()
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.into_iter()
|
||||||
|
.find(|p| p.address().to_string() == address)
|
||||||
|
.unwrap()
|
||||||
|
.clone();
|
||||||
peripheral.connect().await.unwrap();
|
peripheral.connect().await.unwrap();
|
||||||
|
|
||||||
*connection.peripheral.lock().await = Some(peripheral.clone());
|
*connection.peripheral.lock().await = Some(peripheral.clone());
|
||||||
@@ -60,16 +129,21 @@ async fn connect(
|
|||||||
peripheral.discover_services().await.unwrap_or_else(|_| {
|
peripheral.discover_services().await.unwrap_or_else(|_| {
|
||||||
println!("Failed to discover services");
|
println!("Failed to discover services");
|
||||||
});
|
});
|
||||||
let service = peripheral.services()
|
let service = peripheral
|
||||||
|
.services()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.find(|s| s.uuid == uuid_from_u16(0x180D))
|
.find(|s| s.uuid == uuid_from_u16(0x180D))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let characteristic = service.characteristics
|
let characteristic = service
|
||||||
|
.characteristics
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.find(|c| c.uuid == uuid_from_u16(0x2A37))
|
.find(|c| c.uuid == uuid_from_u16(0x2A37))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
peripheral.subscribe(&characteristic).await.unwrap_or_else(|_| {
|
peripheral
|
||||||
|
.subscribe(&characteristic)
|
||||||
|
.await
|
||||||
|
.unwrap_or_else(|_| {
|
||||||
println!("Failed to subscribe to characteristic");
|
println!("Failed to subscribe to characteristic");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -77,7 +151,9 @@ async fn connect(
|
|||||||
let mut notification_stream = peripheral.notifications().await.unwrap();
|
let mut notification_stream = peripheral.notifications().await.unwrap();
|
||||||
while let Some(notification) = notification_stream.next().await {
|
while let Some(notification) = notification_stream.next().await {
|
||||||
if notification.uuid == uuid_from_u16(0x2A37) {
|
if notification.uuid == uuid_from_u16(0x2A37) {
|
||||||
app_handle.emit_all("heart-rate", notification.value[1]).unwrap();
|
app_handle
|
||||||
|
.emit_all("heart-rate", notification.value[1])
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -86,9 +162,7 @@ async fn connect(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn disconnect(
|
async fn disconnect(connection: State<'_, BleConnection>) -> Result<bool, String> {
|
||||||
connection: State<'_, BleConnection>
|
|
||||||
) -> Result<bool, String> {
|
|
||||||
let connection = connection.peripheral.lock().await;
|
let connection = connection.peripheral.lock().await;
|
||||||
let connection = connection.as_ref().unwrap();
|
let connection = connection.as_ref().unwrap();
|
||||||
|
|
||||||
@@ -98,31 +172,44 @@ async fn disconnect(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn get_connected_device(
|
async fn get_connected_device(connection: State<'_, BleConnection>) -> Result<String, String> {
|
||||||
connection: State<'_, BleConnection>
|
|
||||||
) -> Result<String, String> {
|
|
||||||
let connection = connection.peripheral.lock().await;
|
let connection = connection.peripheral.lock().await;
|
||||||
let connection = connection.as_ref().unwrap();
|
let connection = connection.as_ref().unwrap();
|
||||||
|
|
||||||
Ok(serde_json::to_string(&BleDevice {
|
Ok(serde_json::to_string(&BleDevice {
|
||||||
name: connection.properties().await.unwrap().unwrap().local_name.unwrap_or("Unknown".to_string()),
|
name: connection
|
||||||
|
.properties()
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap()
|
||||||
|
.local_name
|
||||||
|
.unwrap_or("Unknown".to_string()),
|
||||||
address: connection.address().to_string(),
|
address: connection.address().to_string(),
|
||||||
}).unwrap())
|
})
|
||||||
|
.unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let ble_manager = BtleManager::new().await.unwrap();
|
let ble_manager = BtleManager::new().await.unwrap();
|
||||||
let adapter = ble_manager.adapters().await.unwrap().into_iter().next().unwrap();
|
let central = ble_manager
|
||||||
|
.adapters()
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
.manage(BleConnection {
|
.manage(BleConnection {
|
||||||
manager: Mutex::new(Some(ble_manager)),
|
central: Mutex::new(Some(central)),
|
||||||
adapter: Mutex::new(Some(adapter)),
|
|
||||||
peripheral: Default::default(),
|
peripheral: Default::default(),
|
||||||
|
scan_devices: Default::default(),
|
||||||
})
|
})
|
||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
scan_devices,
|
request_central_events,
|
||||||
|
start_scan,
|
||||||
|
stop_scan,
|
||||||
connect,
|
connect,
|
||||||
disconnect,
|
disconnect,
|
||||||
get_connected_device
|
get_connected_device
|
||||||
|
|||||||
@@ -32,9 +32,9 @@
|
|||||||
{
|
{
|
||||||
"title": "Heartbeat Cat",
|
"title": "Heartbeat Cat",
|
||||||
"minWidth": 800,
|
"minWidth": 800,
|
||||||
"minHeight": 600,
|
"minHeight": 500,
|
||||||
"width": 800,
|
"width": 800,
|
||||||
"height": 600
|
"height": 500
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"security": {
|
"security": {
|
||||||
|
|||||||
+21
-6
@@ -1,11 +1,22 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { invoke } from "@tauri-apps/api/tauri";
|
||||||
import { listen } from "@tauri-apps/api/event";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
import { RouterView } from "vue-router";
|
|
||||||
import DrawerContainer from "./components/DrawerContainer.vue";
|
invoke("request_central_events").then(res => {
|
||||||
|
console.log('central event listening', res);
|
||||||
|
})
|
||||||
|
|
||||||
listen("heart-rate", (hr) => {
|
listen("heart-rate", (hr) => {
|
||||||
console.log('Heart Rate', hr);
|
console.log('Heart Rate', hr);
|
||||||
})
|
})
|
||||||
|
|
||||||
|
listen("scan-list-update", (event) => {
|
||||||
|
console.log('scan-list-update', event.payload);
|
||||||
|
})
|
||||||
|
|
||||||
|
listen("device-connected", (event) => {
|
||||||
|
console.log('device-connected', event);
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -13,7 +24,7 @@ listen("heart-rate", (hr) => {
|
|||||||
<DrawerContainer>
|
<DrawerContainer>
|
||||||
<RouterView v-slot="{ Component }">
|
<RouterView v-slot="{ Component }">
|
||||||
<Transition name="scale" mode="out-in">
|
<Transition name="scale" mode="out-in">
|
||||||
<KeepAlive>
|
<KeepAlive :exclude="['settings']">
|
||||||
<component :is="Component" />
|
<component :is="Component" />
|
||||||
</KeepAlive>
|
</KeepAlive>
|
||||||
</Transition>
|
</Transition>
|
||||||
@@ -23,6 +34,9 @@ listen("heart-rate", (hr) => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@import './assets/css/font.css';
|
||||||
|
@import './assets/css/style.css';
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--app-background: #f8f8f8;
|
--app-background: #f8f8f8;
|
||||||
|
|
||||||
@@ -65,13 +79,14 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
font-family: Rubik, 'Noto Sans SC', sans-serif;
|
||||||
|
font-weight: 400;
|
||||||
background-color: var(--app-background);
|
background-color: var(--app-background);
|
||||||
font-family: MiSans, sans-serif;
|
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-top-bar {
|
.titlebar {
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
-webkit-app-region: drag;
|
-webkit-app-region: drag;
|
||||||
@@ -90,7 +105,7 @@ body {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-top-bar .icon {
|
.titlebar .icon {
|
||||||
height: calc(var(--title-bar-height) - 6px);
|
height: calc(var(--title-bar-height) - 6px);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
/* noto-sans-sc-200 - chinese-simplified_latin */
|
||||||
|
@font-face {
|
||||||
|
font-display: swap;
|
||||||
|
/* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||||
|
font-family: 'Noto Sans SC';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 200;
|
||||||
|
src: url('../fonts/noto-sans-sc-v36-chinese-simplified_latin-200.woff2') format('woff2');
|
||||||
|
/* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* noto-sans-sc-regular - chinese-simplified_latin */
|
||||||
|
@font-face {
|
||||||
|
font-display: swap;
|
||||||
|
/* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||||
|
font-family: 'Noto Sans SC';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: url('../fonts/noto-sans-sc-v36-chinese-simplified_latin-regular.woff2') format('woff2');
|
||||||
|
/* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* noto-sans-sc-600 - chinese-simplified_latin */
|
||||||
|
@font-face {
|
||||||
|
font-display: swap;
|
||||||
|
/* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||||
|
font-family: 'Noto Sans SC';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
src: url('../fonts/noto-sans-sc-v36-chinese-simplified_latin-600.woff2') format('woff2');
|
||||||
|
/* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* noto-sans-sc-800 - chinese-simplified_latin */
|
||||||
|
@font-face {
|
||||||
|
font-display: swap;
|
||||||
|
/* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||||
|
font-family: 'Noto Sans SC';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 800;
|
||||||
|
src: url('../fonts/noto-sans-sc-v36-chinese-simplified_latin-800.woff2') format('woff2');
|
||||||
|
/* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* rubik-300 - latin */
|
||||||
|
@font-face {
|
||||||
|
font-display: swap;
|
||||||
|
/* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||||
|
font-family: 'Rubik';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 300;
|
||||||
|
src: url('../fonts/rubik-v28-latin-300.woff2') format('woff2');
|
||||||
|
/* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* rubik-300italic - latin */
|
||||||
|
@font-face {
|
||||||
|
font-display: swap;
|
||||||
|
/* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||||
|
font-family: 'Rubik';
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 300;
|
||||||
|
src: url('../fonts/rubik-v28-latin-300italic.woff2') format('woff2');
|
||||||
|
/* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* rubik-regular - latin */
|
||||||
|
@font-face {
|
||||||
|
font-display: swap;
|
||||||
|
/* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||||
|
font-family: 'Rubik';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: url('../fonts/rubik-v28-latin-regular.woff2') format('woff2');
|
||||||
|
/* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* rubik-italic - latin */
|
||||||
|
@font-face {
|
||||||
|
font-display: swap;
|
||||||
|
/* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||||
|
font-family: 'Rubik';
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 400;
|
||||||
|
src: url('../fonts/rubik-v28-latin-italic.woff2') format('woff2');
|
||||||
|
/* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* rubik-600 - latin */
|
||||||
|
@font-face {
|
||||||
|
font-display: swap;
|
||||||
|
/* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||||
|
font-family: 'Rubik';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
src: url('../fonts/rubik-v28-latin-600.woff2') format('woff2');
|
||||||
|
/* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* rubik-600italic - latin */
|
||||||
|
@font-face {
|
||||||
|
font-display: swap;
|
||||||
|
/* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||||
|
font-family: 'Rubik';
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 600;
|
||||||
|
src: url('../fonts/rubik-v28-latin-600italic.woff2') format('woff2');
|
||||||
|
/* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
* {
|
||||||
|
-webkit-user-drag: none;
|
||||||
|
@apply select-none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer utilities {
|
||||||
|
.allow-drag {
|
||||||
|
-webkit-user-drag: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.allow-select {
|
||||||
|
@apply select-text;
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -3,9 +3,9 @@ import { ref } from 'vue'
|
|||||||
|
|
||||||
const navList = ref([
|
const navList = ref([
|
||||||
{ title: '设备连接', path: '/' },
|
{ title: '设备连接', path: '/' },
|
||||||
{ title: '心率曲线', path: '/hr-chart' },
|
{ title: '心率曲线', path: '/charts' },
|
||||||
{ title: '桌面组件', path: '/widgets' },
|
{ title: '桌面组件', path: '/widgets' },
|
||||||
{ title: '推流插件', path: '/streaming-plugin' },
|
{ title: '推流插件', path: '/streaming-plugins' },
|
||||||
{ title: '设置', path: '/settings' },
|
{ title: '设置', path: '/settings' },
|
||||||
])
|
])
|
||||||
</script>
|
</script>
|
||||||
@@ -14,25 +14,48 @@ const navList = ref([
|
|||||||
<div class="drawer">
|
<div class="drawer">
|
||||||
<div class="side">
|
<div class="side">
|
||||||
<div class="nav">
|
<div class="nav">
|
||||||
<router-link
|
<router-link v-for="item in navList" :key="item.path" :to="item.path" class="nav-item"
|
||||||
v-for="item in navList"
|
active-class="active">
|
||||||
:key="item.path"
|
<span>{{ item.title }}</span>
|
||||||
:to="item.path"
|
|
||||||
class="nav-item"
|
|
||||||
active-class="active"
|
|
||||||
>
|
|
||||||
{{ item.title }}
|
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
<div class="status"></div>
|
<div class="status">
|
||||||
|
status
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.drawer {
|
.drawer {
|
||||||
display: flex;
|
background-color: var(--app-background);
|
||||||
height: 100%;
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='28' height='49' viewBox='0 0 28 49'%3E%3Cg fill-rule='evenodd'%3E%3Cg id='hexagons' fill='%23ccc' fill-opacity='0.1' fill-rule='nonzero'%3E%3Cpath d='M13.99 9.25l13 7.5v15l-13 7.5L1 31.75v-15l12.99-7.5zM3 17.9v12.7l10.99 6.34 11-6.35V17.9l-11-6.34L3 17.9zM0 15l12.98-7.5V0h-2v6.35L0 12.69v2.3zm0 18.5L12.98 41v8h-2v-6.85L0 35.81v-2.3zM15 0v7.5L27.99 15H28v-2.31h-.01L17 6.35V0h-2zm0 49v-8l12.99-7.5H28v2.31h-.01L17 42.15V49h-2z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
|
||||||
|
@apply w-full h-screen flex;
|
||||||
|
|
||||||
|
.side {
|
||||||
|
background-color: var(--drawer-bar-background);
|
||||||
|
@apply w-[120px] flex flex-col;
|
||||||
|
|
||||||
|
.nav {
|
||||||
|
@apply flex-1 flex flex-col;
|
||||||
|
|
||||||
|
.nav-item {
|
||||||
|
background-color: var(--drawer-bar-item-background);
|
||||||
|
@apply flex justify-center items-center px-1 py-3.5 text-xs text-neutral-500 font-semibold transition-colors duration-150;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
/* background-color: var(--drawer-bar-item-active-background); */
|
||||||
|
@apply text-pink-400 border-l-4 border-pink-400 bg-pink-400/10 pl-0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
@apply flex-1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ async function disconnect() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function scan() {
|
async function scan() {
|
||||||
invoke("scan_devices").then(res => {
|
invoke("start_scan").then(res => {
|
||||||
console.log(JSON.parse(res as string));
|
console.log(JSON.parse(res as string));
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
|
||||||
|
<template>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 9a3 3 0 0 1 3-3h6a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H9a3 3 0 0 1-3-3zm3 9v3h6v-3M9 6V3h6v3"></path></svg>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'TablerDeviceWatch'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
+7
-2
@@ -2,9 +2,14 @@ import { createApp } from "vue";
|
|||||||
import { createRouter, createWebHistory } from "vue-router";
|
import { createRouter, createWebHistory } from "vue-router";
|
||||||
|
|
||||||
import App from "./App.vue";
|
import App from "./App.vue";
|
||||||
import Index from "./pages/index.vue";
|
|
||||||
|
|
||||||
const routes = [{ path: "/", component: Index }];
|
const routes = [
|
||||||
|
{ path: "/", component: () => import("./pages/index.vue") },
|
||||||
|
{ path: "/charts", component: () => import("./pages/charts.vue") },
|
||||||
|
{ path: "/widgets", component: () => import("./pages/widgets.vue") },
|
||||||
|
{ path: "/streaming-plugins", component: () => import("./pages/streaming-plugins.vue") },
|
||||||
|
{ path: "/settings", component: () => import("./pages/settings.vue") },
|
||||||
|
];
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(),
|
history: createWebHistory(),
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
Charts
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
+1
-1
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
index
|
<Greet />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
Settings Page
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
Streaming Plugins
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
Widgets Page
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
export default {
|
||||||
|
content: ["./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}"],
|
||||||
|
theme: {
|
||||||
|
extend: {},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
};
|
||||||
+1
-1
@@ -20,6 +20,6 @@
|
|||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
|
"include": ["components.d.ts", "src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
|
||||||
"references": [{ "path": "./tsconfig.node.json" }]
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-1
@@ -1,9 +1,15 @@
|
|||||||
import { defineConfig } from "vite";
|
import { defineConfig } from "vite";
|
||||||
import vue from "@vitejs/plugin-vue";
|
import vue from "@vitejs/plugin-vue";
|
||||||
|
import Components from "unplugin-vue-components/vite";
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig(async () => ({
|
export default defineConfig(async () => ({
|
||||||
plugins: [vue()],
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
Components({
|
||||||
|
dts: true,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
|
||||||
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
|
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
|
||||||
//
|
//
|
||||||
|
|||||||
Reference in New Issue
Block a user