Added main gui components.
This commit is contained in:
commit
3b152d2251
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target
|
||||
.DS_Store
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
5426
Cargo.lock
generated
Normal file
5426
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
32
Cargo.toml
Normal file
32
Cargo.toml
Normal file
@ -0,0 +1,32 @@
|
||||
[package]
|
||||
name = "niom-webrtc2"
|
||||
version = "0.1.0"
|
||||
authors = ["ghost <ma-koenig@gmx.net>"]
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
dioxus = { version = "0.6.0", features = [] }
|
||||
console_error_panic_hook = "0.1.7"
|
||||
tracing = "0.1"
|
||||
dioxus-logger = "0.6.2"
|
||||
log = "0.4.27"
|
||||
|
||||
[features]
|
||||
default = ["web"]
|
||||
web = ["dioxus/web"]
|
||||
desktop = ["dioxus/desktop"]
|
||||
mobile = ["dioxus/mobile"]
|
||||
|
||||
[profile]
|
||||
|
||||
[profile.wasm-dev]
|
||||
inherits = "dev"
|
||||
opt-level = 1
|
||||
|
||||
[profile.server-dev]
|
||||
inherits = "dev"
|
||||
|
||||
[profile.android-dev]
|
||||
inherits = "dev"
|
||||
21
Dioxus.toml
Normal file
21
Dioxus.toml
Normal file
@ -0,0 +1,21 @@
|
||||
[application]
|
||||
|
||||
[web.app]
|
||||
|
||||
# HTML title tag content
|
||||
title = "niom-webrtc2"
|
||||
|
||||
# include `assets` in web platform
|
||||
[web.resource]
|
||||
|
||||
# Additional CSS style files
|
||||
style = []
|
||||
|
||||
# Additional JavaScript files
|
||||
script = []
|
||||
|
||||
[web.resource.dev]
|
||||
|
||||
# Javascript code file
|
||||
# serve: [dev-server] only
|
||||
script = []
|
||||
25
README.md
Normal file
25
README.md
Normal file
@ -0,0 +1,25 @@
|
||||
# Development
|
||||
|
||||
Your new bare-bones project includes minimal organization with a single `main.rs` file and a few assets.
|
||||
|
||||
```
|
||||
project/
|
||||
├─ assets/ # Any assets that are used by the app should be placed here
|
||||
├─ src/
|
||||
│ ├─ main.rs # main.rs is the entry point to your application and currently contains all components for the app
|
||||
├─ Cargo.toml # The Cargo.toml file defines the dependencies and feature flags for your project
|
||||
```
|
||||
|
||||
### Serving Your App
|
||||
|
||||
Run the following command in the root of your project to start developing with the default platform:
|
||||
|
||||
```bash
|
||||
dx serve
|
||||
```
|
||||
|
||||
To run for a different platform, use the `--platform platform` flag. E.g.
|
||||
```bash
|
||||
dx serve --platform desktop
|
||||
```
|
||||
|
||||
BIN
assets/favicon.ico
Normal file
BIN
assets/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 130 KiB |
20
assets/header.svg
Normal file
20
assets/header.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 23 KiB |
204
assets/main.css
Normal file
204
assets/main.css
Normal file
@ -0,0 +1,204 @@
|
||||
/* Basis-Styling */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background-color: #f5f5f5;
|
||||
color: #333;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.app-container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
header {
|
||||
text-align: center;
|
||||
margin-bottom: 40px;
|
||||
padding: 20px;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
header h1 {
|
||||
color: #2563eb;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
header p {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* Main Content */
|
||||
.main-content {
|
||||
display: grid;
|
||||
gap: 20px;
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
/* Connection Panel */
|
||||
.connection-panel {
|
||||
background: white;
|
||||
padding: 24px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.connection-panel h2 {
|
||||
margin-bottom: 20px;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.input-group label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: 500;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.input-group input {
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
border: 2px solid #e5e7eb;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
.input-group input:focus {
|
||||
outline: none;
|
||||
border-color: #2563eb;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
button {
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.connect-btn {
|
||||
background-color: #2563eb;
|
||||
color: white;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.connect-btn:hover {
|
||||
background-color: #1d4ed8;
|
||||
}
|
||||
|
||||
/* Call Controls */
|
||||
.call-controls {
|
||||
background: white;
|
||||
padding: 24px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.call-controls h2 {
|
||||
margin-bottom: 20px;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.control-buttons {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.control-buttons button {
|
||||
flex: 1;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.primary {
|
||||
background-color: #10b981;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.primary:hover {
|
||||
background-color: #059669;
|
||||
}
|
||||
|
||||
.danger {
|
||||
background-color: #ef4444;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.danger:hover {
|
||||
background-color: #dc2626;
|
||||
}
|
||||
|
||||
.mute-btn {
|
||||
background-color: #6b7280;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.mute-btn:hover {
|
||||
background-color: #4b5563;
|
||||
}
|
||||
|
||||
/* Status Display */
|
||||
.status-display {
|
||||
background: white;
|
||||
padding: 24px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.status-display h2 {
|
||||
margin-bottom: 20px;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.status-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid #f3f4f6;
|
||||
}
|
||||
|
||||
.status-label {
|
||||
font-weight: 500;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.status-value {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-value.disconnected {
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.status-value.connected {
|
||||
color: #10b981;
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
@media (min-width: 768px) {
|
||||
.main-content {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.status-display {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
}
|
||||
8
clippy.toml
Normal file
8
clippy.toml
Normal file
@ -0,0 +1,8 @@
|
||||
await-holding-invalid-types = [
|
||||
"generational_box::GenerationalRef",
|
||||
{ path = "generational_box::GenerationalRef", reason = "Reads should not be held over an await point. This will cause any writes to fail while the await is pending since the read borrow is still active." },
|
||||
"generational_box::GenerationalRefMut",
|
||||
{ path = "generational_box::GenerationalRefMut", reason = "Write should not be held over an await point. This will cause any reads or writes to fail while the await is pending since the write borrow is still active." },
|
||||
"dioxus_signals::Write",
|
||||
{ path = "dioxus_signals::Write", reason = "Write should not be held over an await point. This will cause any reads or writes to fail while the await is pending since the write borrow is still active." },
|
||||
]
|
||||
224
src/main.rs
Normal file
224
src/main.rs
Normal file
@ -0,0 +1,224 @@
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
const FAVICON: Asset = asset!("/assets/favicon.ico");
|
||||
const MAIN_CSS: Asset = asset!("/assets/main.css");
|
||||
const HEADER_SVG: Asset = asset!("/assets/header.svg");
|
||||
|
||||
fn main() {
|
||||
// Logger initialisieren für besseres Debugging
|
||||
// dioxus_logger::init(log::Level::Info).expect("failed to init logger");
|
||||
// console_error_panic_hook::set_once();
|
||||
|
||||
dioxus::launch(App);
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn App() -> Element {
|
||||
rsx! {
|
||||
document::Link { rel: "icon", href: FAVICON }
|
||||
document::Link { rel: "stylesheet", href: MAIN_CSS }
|
||||
Content {}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Content() ->Element {
|
||||
let mut connected = use_signal(|| false); // Status: Verbindung aufgebaut?
|
||||
let mut audio_enabled = use_signal(|| true); // Status: Mikro aktiviert?
|
||||
|
||||
// State for Peer IDs
|
||||
let mut local_peer_id = use_signal(|| generate_peer_id());
|
||||
let mut remote_peer_id = use_signal(|| String::new());
|
||||
|
||||
rsx! {
|
||||
div {
|
||||
class: "app-container",
|
||||
|
||||
// Header
|
||||
header {
|
||||
h1 { "Voice Chat MVP" }
|
||||
p { "WebRTC-basierter Sprachchat mit Ende-zu-Ende-Verschlüsselung" }
|
||||
}
|
||||
|
||||
main {
|
||||
class: "main-content",
|
||||
|
||||
// Connection Panel
|
||||
ConnectionPanel {
|
||||
connected,
|
||||
on_connect: move |_| connected.set(true)
|
||||
}
|
||||
|
||||
// Call Controls
|
||||
CallControls {
|
||||
audio_enabled,
|
||||
on_toggle_audio: move |_| audio_enabled.set(!audio_enabled())
|
||||
}
|
||||
|
||||
// Status Display
|
||||
StatusDisplay {
|
||||
connected,
|
||||
audio_enabled
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Komponente für Verbindungseinstellungen
|
||||
#[component]
|
||||
fn ConnectionPanel(connected: Signal<bool>, on_connect: EventHandler<MouseEvent>) -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
class: "connection-panel",
|
||||
h2 { "Verbindung" }
|
||||
|
||||
div {
|
||||
class: "input-group",
|
||||
label { "for": "peer-id", "Peer ID:" }
|
||||
input {
|
||||
id: "peer-id",
|
||||
r#type: "text",
|
||||
placeholder: "Wird autmatisch generiert"
|
||||
}
|
||||
}
|
||||
|
||||
div {
|
||||
class: "input-group",
|
||||
label { "for": "remote-peer", "Remote Peer-ID:" }
|
||||
input {
|
||||
id: "remote-peer",
|
||||
r#type: "text",
|
||||
placeholder: "ID des anderen Teilnehmers eingeben"
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
class: "connect-btn",
|
||||
onclick: on_connect,
|
||||
disabled: connected(),
|
||||
if connected() {
|
||||
"Verbunden"
|
||||
}
|
||||
else {
|
||||
"Mit Signaling Server verbinden"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Komponente für Anruf-Steuerungen
|
||||
#[component]
|
||||
fn CallControls(audio_enabled: Signal<bool>, on_toggle_audio: EventHandler<MouseEvent>) -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
class: "call-controls",
|
||||
h2 { "Anruf-Steuerung" }
|
||||
|
||||
div {
|
||||
class: "control-buttons",
|
||||
button {
|
||||
class: "call-btn primary",
|
||||
"Anruf starten"
|
||||
}
|
||||
button {
|
||||
class: "mute-btn",
|
||||
onclick: on_toggle_audio,
|
||||
if audio_enabled() {
|
||||
"Mute"
|
||||
}
|
||||
else {
|
||||
"Unmute"
|
||||
}
|
||||
}
|
||||
button {
|
||||
class: "end-btn danger",
|
||||
"Anruf beenden"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Komponente für Status-Anzeige
|
||||
#[component]
|
||||
fn StatusDisplay(connected: Signal<bool>, audio_enabled: Signal<bool>) -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
class: "status-display",
|
||||
h2 { "Status" }
|
||||
|
||||
div {
|
||||
class: "status-item",
|
||||
span {
|
||||
class: "status-label",
|
||||
"Signaling Server:"
|
||||
}
|
||||
span {
|
||||
class:
|
||||
if connected() {
|
||||
"status-value connected"
|
||||
}
|
||||
else {
|
||||
"status-value disconnected"
|
||||
},
|
||||
|
||||
if connected() {
|
||||
"Verbunden"
|
||||
}
|
||||
else {
|
||||
"Getrennt"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div {
|
||||
class: "status-item",
|
||||
span {
|
||||
class: "status-label",
|
||||
"WebRTC Verbindung:"
|
||||
}
|
||||
span {
|
||||
class: "status-value disconnected",
|
||||
"Nicht verbunden"
|
||||
}
|
||||
}
|
||||
|
||||
div {
|
||||
class: "status-item",
|
||||
span {
|
||||
class: "status-label",
|
||||
"Audio Status:"
|
||||
}
|
||||
span {
|
||||
class:
|
||||
if audio_enabled() {
|
||||
"status-value"
|
||||
}
|
||||
else {
|
||||
"status-value disconnected"
|
||||
},
|
||||
if audio_enabled() {
|
||||
"Aktiviert"
|
||||
}
|
||||
else {
|
||||
"Stumm"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_peer_id() -> String {
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
static COUNTER: AtomicU32 = AtomicU32::new(1);
|
||||
let id = COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||
|
||||
return format!("peer-{:06}", id);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user