added visuals for connected satellites

Signed-off-by: Nickiel12 <nickiel@nickiel.net>
This commit is contained in:
Nickiel12 2024-09-18 14:24:22 -07:00
parent 1777b5e39f
commit bac744d506
11 changed files with 334 additions and 36 deletions

View file

@ -3,9 +3,10 @@ use std::sync::{atomic::AtomicBool, Arc};
use std::time::Duration; use std::time::Duration;
use async_channel::{Receiver, Sender, TryRecvError}; use async_channel::{Receiver, Sender, TryRecvError};
use serde::{Deserialize, Serialize};
use tauri::Manager; use tauri::Manager;
use tokio::runtime::Handle; use tokio::runtime::Handle;
use tracing::{error, info}; use tracing::{debug, error, info, warn};
use vcs_common::ApplicationMessage; use vcs_common::ApplicationMessage;
use crate::config::AppConfig; use crate::config::AppConfig;
@ -85,6 +86,18 @@ impl AppState {
} }
} }
pub fn update_satellite_names(&self) {
let new_names = self
.camera_satellites
.iter()
.map(|x| SatelliteName { name: x.name.clone(), is_connected: x.is_connected() })
.collect::<Vec<SatelliteName>>();
send_ui_message(
"satellite_names".to_owned(),
serde_json::to_string(&new_names).unwrap(),
);
}
pub async fn check_alive_things(&mut self) { pub async fn check_alive_things(&mut self) {
if !self if !self
.joystick_task_is_alive .joystick_task_is_alive
@ -96,12 +109,28 @@ impl AppState {
)); ));
} }
let mut resend_names = false;
for i in self
.camera_satellites
.iter_mut()
.filter(|x| x.was_connected && !x.is_connected())
{
info!("new dead connection found, cleaning up!");
i.was_connected = false;
resend_names = true;
}
if resend_names {
info!("Refreshing UI connection names");
self.update_satellite_names();
}
for i in self for i in self
.camera_satellites .camera_satellites
.iter_mut() .iter_mut()
.filter(|x| !x.is_connected() && x.try_connecting) .filter(|x| !x.is_connected() && x.try_connecting)
{ {
i.connect(self.runtime.clone()).await i.connect(self.runtime.clone()).await;
} }
} }
} }
@ -134,6 +163,8 @@ pub async fn run_main_event_loop(
state.runtime.clone(), state.runtime.clone(),
); );
}); });
// Use this as a 'ui is ready' event as well
state.update_satellite_names();
} }
ApplicationEvent::Close => { ApplicationEvent::Close => {
state.mec.close(); // cleanup is handled on reading from a closed mec state.mec.close(); // cleanup is handled on reading from a closed mec
@ -194,6 +225,7 @@ pub async fn run_main_event_loop(
} }
} }
let mut update_names: bool = false;
for connection in state.camera_satellites.iter_mut() { for connection in state.camera_satellites.iter_mut() {
match connection.try_next().await { match connection.try_next().await {
Some(msg) => match msg { Some(msg) => match msg {
@ -207,7 +239,11 @@ pub async fn run_main_event_loop(
info!("Was not able to send name to remote? {:?}", e); info!("Was not able to send name to remote? {:?}", e);
} }
} }
ApplicationMessage::NameRequest(Some(name)) => connection.name = name, ApplicationMessage::NameRequest(Some(name)) => {
warn!("Got a name update!");
connection.name = name;
update_names = true;
}
ApplicationMessage::ChangeTrackingID(_) => {} ApplicationMessage::ChangeTrackingID(_) => {}
ApplicationMessage::ManualMovementOverride(_) => {} ApplicationMessage::ManualMovementOverride(_) => {}
ApplicationMessage::TrackingBoxes(_update) => {} ApplicationMessage::TrackingBoxes(_update) => {}
@ -248,6 +284,9 @@ pub async fn run_main_event_loop(
None => {} None => {}
} }
} }
if update_names {
state.update_satellite_names();
}
} }
info!("Closing the MEC loop"); info!("Closing the MEC loop");
state state
@ -269,10 +308,33 @@ pub fn send_frontend_message(message: String) {
if e.is_none() { if e.is_none() {
return; return;
} else { } else {
let handle = e.take().unwrap(); e.as_mut()
handle .unwrap()
.emit_all("frontend_message", message) .emit_all("frontend_message", message)
.expect("Could not send message to the tauri frontend!"); .expect("Could not send message to the tauri frontend!");
} }
} }
} }
#[derive(Serialize, Deserialize)]
struct SatelliteName {
pub is_connected: bool,
pub name: String,
}
pub fn send_ui_message(handle_name: String, message: String) {
if let Ok(mut e) = APP_HANDLE.lock() {
if e.is_none() {
error!("Could not get app handle!");
return;
} else {
info!("sending event '{}' with payload '{}'", handle_name, message);
e.as_mut()
.unwrap()
.emit_all(&handle_name, message)
.expect("Could not send message to the tauri frontend!");
}
} else {
error!("Could not get lock on APP_HANDLE!");
}
}

View file

@ -17,6 +17,7 @@ pub struct SatelliteConnection {
pub name: String, pub name: String,
pub retry_attempts: usize, pub retry_attempts: usize,
pub try_connecting: bool, pub try_connecting: bool,
pub was_connected: bool,
connection: ConnectionString, connection: ConnectionString,
currently_connecting: bool, currently_connecting: bool,
@ -35,6 +36,8 @@ impl SatelliteConnection {
retry_attempts: 0, retry_attempts: 0,
try_connecting: false, try_connecting: false,
// external flag for 'have a checked this since it disocnnected'
was_connected: false,
currently_connecting: false, currently_connecting: false,
@ -94,6 +97,7 @@ impl SatelliteConnection {
self.from_socket = Some(recvr); self.from_socket = Some(recvr);
self.socket_is_dead = is_alive; self.socket_is_dead = is_alive;
self.currently_connecting = false; self.currently_connecting = false;
self.was_connected = true;
} }
Err(e) => { Err(e) => {
self.retry_attempts += 1; self.retry_attempts += 1;
@ -143,14 +147,6 @@ impl SatelliteConnection {
if self.from_socket.is_some() { if self.from_socket.is_some() {
match self.from_socket.as_ref().unwrap().try_recv() { match self.from_socket.as_ref().unwrap().try_recv() {
Ok(msg) => match msg { Ok(msg) => match msg {
ApplicationMessage::NameRequest(Some(name)) => {
self.name = name;
None
}
ApplicationMessage::NameRequest(None) => {
warn!("Got a request for a name, ignoring");
None
}
_ => Some(msg), _ => Some(msg),
}, },
Err(TryRecvError::Empty) => None, Err(TryRecvError::Empty) => None,

View file

@ -1,6 +1,6 @@
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
module.exports = { module.exports = {
content: ["./ui/**/*.html"], content: ["./ui/**/*.html", "./ui/**/*.js"],
theme: { theme: {
extend: { extend: {
aspectRatio: { aspectRatio: {

View file

@ -33,6 +33,9 @@
<button id="camera_connect_button" class="rounded-full font-semibold mx-3 mt-2 px-4 py-2 text-white bg-cyan-600">Connect to Camera</button> <button id="camera_connect_button" class="rounded-full font-semibold mx-3 mt-2 px-4 py-2 text-white bg-cyan-600">Connect to Camera</button>
<button class="rounded-full font-semibold mx-3 mt-2 px-4 py-2 text-white bg-cyan-600">Connect to Computer</button> <button class="rounded-full font-semibold mx-3 mt-2 px-4 py-2 text-white bg-cyan-600">Connect to Computer</button>
<ul id="connections_list" class="m-3 border-y-1 border-neutral-800 bg-neutral-400">
<li>No Camera Connections</li>
</ul>
</div> </div>
<div class="bg-emerald-700 h-dvh w-7/8"> <div class="bg-emerald-700 h-dvh w-7/8">

View file

@ -0,0 +1,41 @@
import { invoke, event } from "./node_modules/@tauri-apps/api/index.js";
async function setup_listeners() {
await listen_for_new_satellites();
}
async function listen_for_new_satellites() {
const new_satellite = await event.listen("satellite_names", async (msg) => {
const payload = JSON.parse(msg.payload);
console.log(payload);
let cam_button = document.getElementById("camera_connect_button");
cam_button.innerText = "Connect to Camera";
cam_button.classList.add("text-semibold");
cam_button.classList.add("text-white");
cam_button.classList.remove("text-neutral-400")
let out_ul = document.getElementById("connections_list");
out_ul.innerHTML = "";
payload.forEach((item) => {
let li = document.createElement("li");
li.classList.add("flex", "py-2");
if (!item.is_connected) {
li.classList.add("text-neutral-300", "bg-neutral-500");
li.innerHTML = `<span class="ms-2 text-sm"><i class="inline-block ms-1 me-2" data-feather="wifi-off"></i>${item.name}</span>`;
} else {
li.innerHTML = `<span class="ms-2 text-sm"><i class="inline-block ms-1 me-2" data-feather="wifi"></i>${item.name}</span>`;
li.classList.add("text-white");
}
out_ul.appendChild(li);
feather.replace();
})
});
}
export { setup_listeners };

View file

@ -1,4 +1,5 @@
import { invoke } from "./@tauri-apps/api/index.js"; import { invoke, event } from "./node_modules/@tauri-apps/api/index.js";
import { setup_listeners } from "./async_listeners.js";
import { rtc_init } from "./rtc.js"; import { rtc_init } from "./rtc.js";
function call_camera_connect() { function call_camera_connect() {
@ -13,10 +14,27 @@ function call_camera_connect() {
.catch((e) => console.error(e)); .catch((e) => console.error(e));
} }
function supports_webrtc() {
var isWebRTCSupported = false;
['RTCPeerConnection', 'webkitRTCPeerConnection', 'mozRTCPeerConnection', 'RTCIceGatherer'].forEach(function(item) {
if (isWebRTCSupported) {
return;
}
if (item in window) {
isWebRTCSupported = true;
}
});
return isWebRTCSupported;
}
async function init() { async function init() {
console.log("Setting up"); console.log("Setting up");
document.getElementById("camera_connect_button").addEventListener("click", call_camera_connect); document.getElementById("camera_connect_button").addEventListener("click", call_camera_connect);
await setup_listeners();
let webrtc_support = supports_webrtc(); let webrtc_support = supports_webrtc();
invoke("supports_webrtc", { has_support: webrtc_support }); invoke("supports_webrtc", { has_support: webrtc_support });
@ -25,20 +43,5 @@ async function init() {
} }
} }
function supports_webrtc() {
var isWebRTCSupported = false;
['RTCPeerConnection', 'webkitRTCPeerConnection', 'mozRTCPeerConnection', 'RTCIceGatherer'].forEach(function(item) {
if (isWebRTCSupported) {
return;
}
if (item in window) {
isWebRTCSupported = true;
}
});
return isWebRTCSupported;
}
export { init }; export { init };

View file

@ -1,5 +1,5 @@
/* /*
! tailwindcss v3.4.1 | MIT License | https://tailwindcss.com ! tailwindcss v3.4.10 | MIT License | https://tailwindcss.com
*/ */
/* /*
@ -211,6 +211,8 @@ textarea {
/* 1 */ /* 1 */
line-height: inherit; line-height: inherit;
/* 1 */ /* 1 */
letter-spacing: inherit;
/* 1 */
color: inherit; color: inherit;
/* 1 */ /* 1 */
margin: 0; margin: 0;
@ -234,9 +236,9 @@ select {
*/ */
button, button,
[type='button'], input:where([type='button']),
[type='reset'], input:where([type='reset']),
[type='submit'] { input:where([type='submit']) {
-webkit-appearance: button; -webkit-appearance: button;
/* 1 */ /* 1 */
background-color: transparent; background-color: transparent;
@ -492,6 +494,10 @@ video {
--tw-backdrop-opacity: ; --tw-backdrop-opacity: ;
--tw-backdrop-saturate: ; --tw-backdrop-saturate: ;
--tw-backdrop-sepia: ; --tw-backdrop-sepia: ;
--tw-contain-size: ;
--tw-contain-layout: ;
--tw-contain-paint: ;
--tw-contain-style: ;
} }
::backdrop { ::backdrop {
@ -542,12 +548,74 @@ video {
--tw-backdrop-opacity: ; --tw-backdrop-opacity: ;
--tw-backdrop-saturate: ; --tw-backdrop-saturate: ;
--tw-backdrop-sepia: ; --tw-backdrop-sepia: ;
--tw-contain-size: ;
--tw-contain-layout: ;
--tw-contain-paint: ;
--tw-contain-style: ;
}
.container {
width: 100%;
}
@media (min-width: 640px) {
.container {
max-width: 640px;
}
}
@media (min-width: 768px) {
.container {
max-width: 768px;
}
}
@media (min-width: 1024px) {
.container {
max-width: 1024px;
}
}
@media (min-width: 1280px) {
.container {
max-width: 1280px;
}
}
@media (min-width: 1536px) {
.container {
max-width: 1536px;
}
}
.visible {
visibility: visible;
}
.collapse {
visibility: collapse;
}
.static {
position: static;
}
.fixed {
position: fixed;
}
.absolute {
position: absolute;
} }
.relative { .relative {
position: relative; position: relative;
} }
.m-3 {
margin: 0.75rem;
}
.mx-3 { .mx-3 {
margin-left: 0.75rem; margin-left: 0.75rem;
margin-right: 0.75rem; margin-right: 0.75rem;
@ -558,6 +626,11 @@ video {
margin-right: auto; margin-right: auto;
} }
.mx-1 {
margin-left: 0.25rem;
margin-right: 0.25rem;
}
.me-0 { .me-0 {
margin-inline-end: 0px; margin-inline-end: 0px;
} }
@ -570,10 +643,46 @@ video {
margin-top: 0.5rem; margin-top: 0.5rem;
} }
.ms-2 {
margin-inline-start: 0.5rem;
}
.me-2 {
margin-inline-end: 0.5rem;
}
.ms-1 {
margin-inline-start: 0.25rem;
}
.block {
display: block;
}
.inline-block {
display: inline-block;
}
.inline {
display: inline;
}
.flex { .flex {
display: flex; display: flex;
} }
.table {
display: table;
}
.grid {
display: grid;
}
.contents {
display: contents;
}
.hidden { .hidden {
display: none; display: none;
} }
@ -594,6 +703,14 @@ video {
flex: 1 1 0%; flex: 1 1 0%;
} }
.transform {
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.resize {
resize: both;
}
.flex-row { .flex-row {
flex-direction: row; flex-direction: row;
} }
@ -628,6 +745,11 @@ video {
border-radius: 0.375rem; border-radius: 0.375rem;
} }
.border-neutral-800 {
--tw-border-opacity: 1;
border-color: rgb(38 38 38 / var(--tw-border-opacity));
}
.bg-cyan-600 { .bg-cyan-600 {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(8 145 178 / var(--tw-bg-opacity)); background-color: rgb(8 145 178 / var(--tw-bg-opacity));
@ -653,6 +775,16 @@ video {
background-color: rgb(212 212 212 / var(--tw-bg-opacity)); background-color: rgb(212 212 212 / var(--tw-bg-opacity));
} }
.bg-neutral-400 {
--tw-bg-opacity: 1;
background-color: rgb(163 163 163 / var(--tw-bg-opacity));
}
.bg-neutral-500 {
--tw-bg-opacity: 1;
background-color: rgb(115 115 115 / var(--tw-bg-opacity));
}
.bg-slate-800 { .bg-slate-800 {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(30 41 59 / var(--tw-bg-opacity)); background-color: rgb(30 41 59 / var(--tw-bg-opacity));
@ -695,11 +827,20 @@ video {
font-weight: 600; font-weight: 600;
} }
.italic {
font-style: italic;
}
.text-gray-300 { .text-gray-300 {
--tw-text-opacity: 1; --tw-text-opacity: 1;
color: rgb(209 213 219 / var(--tw-text-opacity)); color: rgb(209 213 219 / var(--tw-text-opacity));
} }
.text-neutral-300 {
--tw-text-opacity: 1;
color: rgb(212 212 212 / var(--tw-text-opacity));
}
.text-neutral-400 { .text-neutral-400 {
--tw-text-opacity: 1; --tw-text-opacity: 1;
color: rgb(163 163 163 / var(--tw-text-opacity)); color: rgb(163 163 163 / var(--tw-text-opacity));
@ -710,6 +851,31 @@ video {
color: rgb(255 255 255 / var(--tw-text-opacity)); color: rgb(255 255 255 / var(--tw-text-opacity));
} }
.underline {
text-decoration-line: underline;
}
.shadow {
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}
.ring {
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
}
.blur {
--tw-blur: blur(8px);
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
}
.filter {
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
}
.transition-colors { .transition-colors {
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke; transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);

View file

@ -9,9 +9,35 @@
"version": "1.0.0", "version": "1.0.0",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@floating-ui/dom": "^1.6.11",
"@tauri-apps/api": "^1.6.0" "@tauri-apps/api": "^1.6.0"
} }
}, },
"node_modules/@floating-ui/core": {
"version": "1.6.8",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.8.tgz",
"integrity": "sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==",
"license": "MIT",
"dependencies": {
"@floating-ui/utils": "^0.2.8"
}
},
"node_modules/@floating-ui/dom": {
"version": "1.6.11",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.11.tgz",
"integrity": "sha512-qkMCxSR24v2vGkhYDo/UzxfJN3D4syqSjyuTFz6C7XcpU1pASPRieNI0Kj5VP3/503mOfYiGY891ugBX1GlABQ==",
"license": "MIT",
"dependencies": {
"@floating-ui/core": "^1.6.0",
"@floating-ui/utils": "^0.2.8"
}
},
"node_modules/@floating-ui/utils": {
"version": "0.2.8",
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.8.tgz",
"integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==",
"license": "MIT"
},
"node_modules/@tauri-apps/api": { "node_modules/@tauri-apps/api": {
"version": "1.6.0", "version": "1.6.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-1.6.0.tgz", "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-1.6.0.tgz",

View file

@ -9,6 +9,7 @@
"license": "ISC", "license": "ISC",
"description": "", "description": "",
"dependencies": { "dependencies": {
"@floating-ui/dom": "^1.6.11",
"@tauri-apps/api": "^1.6.0" "@tauri-apps/api": "^1.6.0"
} }
} }

View file

@ -1,6 +1,6 @@
import { event } from "./@tauri-apps/api/index.js"; import { event } from "./node_modules/@tauri-apps/api/index.js";
async function rtc_init() { async function rtc_init() {
const videoview = document.getElementById("remoteview"); const videoview = document.getElementById("remoteview");