Compare commits
2 Commits
c5dc6b9dd4
...
a2753bcf66
| Author | SHA1 | Date | |
|---|---|---|---|
| a2753bcf66 | |||
| e36bb1ab55 |
@@ -4,7 +4,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Radio1 Player</title>
|
<title>Radio Player</title>
|
||||||
<link rel="stylesheet" href="styles.css">
|
<link rel="stylesheet" href="styles.css">
|
||||||
<script src="main.js" defer type="module"></script>
|
<script src="main.js" defer type="module"></script>
|
||||||
</head>
|
</head>
|
||||||
@@ -82,8 +82,8 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="track-info">
|
<section class="track-info">
|
||||||
<h2 id="station-name">Radio 1 MB</h2>
|
<h2 id="station-name"></h2>
|
||||||
<p id="station-subtitle">Live Stream</p>
|
<p id="station-subtitle"></p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Visual Progress Bar (Live) -->
|
<!-- Visual Progress Bar (Live) -->
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# Radio1 Player – Glassmorphism UI Redesign (Tauri + HTML)
|
# Radio Player – Glassmorphism UI Redesign (Tauri + HTML)
|
||||||
|
|
||||||
## Objective
|
## Objective
|
||||||
|
|
||||||
Redesign the **Radio1 Player** UI to match a **modern glassmorphism style** inspired by high-end music player apps.
|
Redesign the **Radio Player** UI to match a **modern glassmorphism style** inspired by high-end music player apps.
|
||||||
|
|
||||||
The app is built with:
|
The app is built with:
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ Single centered player card:
|
|||||||
|
|
||||||
```
|
```
|
||||||
┌──────────────────────────────┐
|
┌──────────────────────────────┐
|
||||||
│ Radio1 Player │
|
│ Radio Player │
|
||||||
│ ● Playing / Ready │
|
│ ● Playing / Ready │
|
||||||
│ │
|
│ │
|
||||||
│ [ Station Artwork / Logo ] │
|
│ [ Station Artwork / Logo ] │
|
||||||
@@ -64,7 +64,7 @@ Single centered player card:
|
|||||||
|
|
||||||
### Header
|
### Header
|
||||||
|
|
||||||
* Title: `Radio1 Player`
|
* Title: `RadioPlayer`
|
||||||
* Status indicator:
|
* Status indicator:
|
||||||
|
|
||||||
* `● Ready`
|
* `● Ready`
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
<img src="assets/logo.svg" alt="Radio Player" />
|
<img src="assets/logo.svg" alt="Radio Player" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p id="station">Radio 1 – Live Stream</p>
|
<p id="station">Radio – Live Stream</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="receiver.js"></script>
|
<script src="receiver.js"></script>
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ function loadMedia(url) {
|
|||||||
streamType: 'LIVE',
|
streamType: 'LIVE',
|
||||||
metadata: {
|
metadata: {
|
||||||
metadataType: 0,
|
metadataType: 0,
|
||||||
title: 'Radio 1'
|
title: 'RadioPlayer'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
461
src-tauri/Cargo.lock
generated
461
src-tauri/Cargo.lock
generated
@@ -550,7 +550,7 @@ dependencies = [
|
|||||||
"bitflags 2.10.0",
|
"bitflags 2.10.0",
|
||||||
"core-foundation 0.10.1",
|
"core-foundation 0.10.1",
|
||||||
"core-graphics-types",
|
"core-graphics-types",
|
||||||
"foreign-types",
|
"foreign-types 0.5.0",
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -837,7 +837,7 @@ dependencies = [
|
|||||||
"rustc_version",
|
"rustc_version",
|
||||||
"toml 0.9.10+spec-1.1.0",
|
"toml 0.9.10+spec-1.1.0",
|
||||||
"vswhom",
|
"vswhom",
|
||||||
"winreg",
|
"winreg 0.55.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -988,6 +988,15 @@ version = "1.0.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "foreign-types"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
||||||
|
dependencies = [
|
||||||
|
"foreign-types-shared 0.1.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "foreign-types"
|
name = "foreign-types"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
@@ -995,7 +1004,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
|
checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"foreign-types-macros",
|
"foreign-types-macros",
|
||||||
"foreign-types-shared",
|
"foreign-types-shared 0.3.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1009,6 +1018,12 @@ dependencies = [
|
|||||||
"syn 2.0.111",
|
"syn 2.0.111",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "foreign-types-shared"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "foreign-types-shared"
|
name = "foreign-types-shared"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
@@ -1425,6 +1440,25 @@ dependencies = [
|
|||||||
"syn 2.0.111",
|
"syn 2.0.111",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "h2"
|
||||||
|
version = "0.3.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"fnv",
|
||||||
|
"futures-core",
|
||||||
|
"futures-sink",
|
||||||
|
"futures-util",
|
||||||
|
"http 0.2.12",
|
||||||
|
"indexmap 2.12.1",
|
||||||
|
"slab",
|
||||||
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.12.3"
|
version = "0.12.3"
|
||||||
@@ -1482,6 +1516,17 @@ dependencies = [
|
|||||||
"match_token",
|
"match_token",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "http"
|
||||||
|
version = "0.2.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"fnv",
|
||||||
|
"itoa",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http"
|
name = "http"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
@@ -1492,6 +1537,17 @@ dependencies = [
|
|||||||
"itoa",
|
"itoa",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "http-body"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"http 0.2.12",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http-body"
|
name = "http-body"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
@@ -1499,7 +1555,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
|
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"http",
|
"http 1.4.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1510,8 +1566,8 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"http",
|
"http 1.4.0",
|
||||||
"http-body",
|
"http-body 1.0.1",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1521,6 +1577,36 @@ version = "1.10.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
|
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "httpdate"
|
||||||
|
version = "1.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hyper"
|
||||||
|
version = "0.14.32"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
|
"futures-util",
|
||||||
|
"h2",
|
||||||
|
"http 0.2.12",
|
||||||
|
"http-body 0.4.6",
|
||||||
|
"httparse",
|
||||||
|
"httpdate",
|
||||||
|
"itoa",
|
||||||
|
"pin-project-lite",
|
||||||
|
"socket2 0.5.10",
|
||||||
|
"tokio",
|
||||||
|
"tower-service",
|
||||||
|
"tracing",
|
||||||
|
"want",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper"
|
name = "hyper"
|
||||||
version = "1.8.1"
|
version = "1.8.1"
|
||||||
@@ -1531,8 +1617,8 @@ dependencies = [
|
|||||||
"bytes",
|
"bytes",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"http",
|
"http 1.4.0",
|
||||||
"http-body",
|
"http-body 1.0.1",
|
||||||
"httparse",
|
"httparse",
|
||||||
"itoa",
|
"itoa",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
@@ -1542,6 +1628,33 @@ dependencies = [
|
|||||||
"want",
|
"want",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hyper-rustls"
|
||||||
|
version = "0.24.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590"
|
||||||
|
dependencies = [
|
||||||
|
"futures-util",
|
||||||
|
"http 0.2.12",
|
||||||
|
"hyper 0.14.32",
|
||||||
|
"rustls 0.21.12",
|
||||||
|
"tokio",
|
||||||
|
"tokio-rustls",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hyper-tls"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"hyper 0.14.32",
|
||||||
|
"native-tls",
|
||||||
|
"tokio",
|
||||||
|
"tokio-native-tls",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper-util"
|
name = "hyper-util"
|
||||||
version = "0.1.19"
|
version = "0.1.19"
|
||||||
@@ -1553,14 +1666,14 @@ dependencies = [
|
|||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http",
|
"http 1.4.0",
|
||||||
"http-body",
|
"http-body 1.0.1",
|
||||||
"hyper",
|
"hyper 1.8.1",
|
||||||
"ipnet",
|
"ipnet",
|
||||||
"libc",
|
"libc",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"socket2",
|
"socket2 0.6.1",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
"tracing",
|
"tracing",
|
||||||
@@ -2039,7 +2152,7 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"mio",
|
"mio",
|
||||||
"socket-pktinfo",
|
"socket-pktinfo",
|
||||||
"socket2",
|
"socket2 0.6.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2106,6 +2219,23 @@ dependencies = [
|
|||||||
"windows-sys 0.60.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "native-tls"
|
||||||
|
version = "0.2.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"openssl",
|
||||||
|
"openssl-probe",
|
||||||
|
"openssl-sys",
|
||||||
|
"schannel",
|
||||||
|
"security-framework",
|
||||||
|
"security-framework-sys",
|
||||||
|
"tempfile",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ndk"
|
name = "ndk"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
@@ -2428,12 +2558,50 @@ dependencies = [
|
|||||||
"pathdiff",
|
"pathdiff",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openssl"
|
||||||
|
version = "0.10.75"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.10.0",
|
||||||
|
"cfg-if",
|
||||||
|
"foreign-types 0.3.2",
|
||||||
|
"libc",
|
||||||
|
"once_cell",
|
||||||
|
"openssl-macros",
|
||||||
|
"openssl-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openssl-macros"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.111",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl-probe"
|
name = "openssl-probe"
|
||||||
version = "0.1.6"
|
version = "0.1.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
|
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openssl-sys"
|
||||||
|
version = "0.9.111"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
"pkg-config",
|
||||||
|
"vcpkg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "option-ext"
|
name = "option-ext"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@@ -2907,6 +3075,7 @@ name = "radio-tauri"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"mdns-sd",
|
"mdns-sd",
|
||||||
|
"reqwest 0.11.27",
|
||||||
"rust_cast",
|
"rust_cast",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -3073,6 +3242,50 @@ version = "0.8.8"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
|
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "reqwest"
|
||||||
|
version = "0.11.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.21.7",
|
||||||
|
"bytes",
|
||||||
|
"encoding_rs",
|
||||||
|
"futures-core",
|
||||||
|
"futures-util",
|
||||||
|
"h2",
|
||||||
|
"http 0.2.12",
|
||||||
|
"http-body 0.4.6",
|
||||||
|
"hyper 0.14.32",
|
||||||
|
"hyper-rustls",
|
||||||
|
"hyper-tls",
|
||||||
|
"ipnet",
|
||||||
|
"js-sys",
|
||||||
|
"log",
|
||||||
|
"mime",
|
||||||
|
"native-tls",
|
||||||
|
"once_cell",
|
||||||
|
"percent-encoding",
|
||||||
|
"pin-project-lite",
|
||||||
|
"rustls 0.21.12",
|
||||||
|
"rustls-pemfile 1.0.4",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"serde_urlencoded",
|
||||||
|
"sync_wrapper 0.1.2",
|
||||||
|
"system-configuration",
|
||||||
|
"tokio",
|
||||||
|
"tokio-native-tls",
|
||||||
|
"tokio-rustls",
|
||||||
|
"tower-service",
|
||||||
|
"url",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
|
"web-sys",
|
||||||
|
"webpki-roots",
|
||||||
|
"winreg 0.50.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest"
|
name = "reqwest"
|
||||||
version = "0.12.28"
|
version = "0.12.28"
|
||||||
@@ -3083,10 +3296,10 @@ dependencies = [
|
|||||||
"bytes",
|
"bytes",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http",
|
"http 1.4.0",
|
||||||
"http-body",
|
"http-body 1.0.1",
|
||||||
"http-body-util",
|
"http-body-util",
|
||||||
"hyper",
|
"hyper 1.8.1",
|
||||||
"hyper-util",
|
"hyper-util",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
@@ -3095,7 +3308,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_urlencoded",
|
"serde_urlencoded",
|
||||||
"sync_wrapper",
|
"sync_wrapper 1.0.2",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"tower",
|
"tower",
|
||||||
@@ -3132,7 +3345,7 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"protobuf",
|
"protobuf",
|
||||||
"protobuf-codegen",
|
"protobuf-codegen",
|
||||||
"rustls",
|
"rustls 0.23.35",
|
||||||
"rustls-native-certs",
|
"rustls-native-certs",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
@@ -3174,6 +3387,18 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls"
|
||||||
|
version = "0.21.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"ring",
|
||||||
|
"rustls-webpki 0.101.7",
|
||||||
|
"sct",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls"
|
name = "rustls"
|
||||||
version = "0.23.35"
|
version = "0.23.35"
|
||||||
@@ -3184,7 +3409,7 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"rustls-webpki",
|
"rustls-webpki 0.103.8",
|
||||||
"subtle",
|
"subtle",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
@@ -3196,12 +3421,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5"
|
checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"openssl-probe",
|
"openssl-probe",
|
||||||
"rustls-pemfile",
|
"rustls-pemfile 2.2.0",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"schannel",
|
"schannel",
|
||||||
"security-framework",
|
"security-framework",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls-pemfile"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.21.7",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-pemfile"
|
name = "rustls-pemfile"
|
||||||
version = "2.2.0"
|
version = "2.2.0"
|
||||||
@@ -3220,6 +3454,16 @@ dependencies = [
|
|||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls-webpki"
|
||||||
|
version = "0.101.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765"
|
||||||
|
dependencies = [
|
||||||
|
"ring",
|
||||||
|
"untrusted",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-webpki"
|
name = "rustls-webpki"
|
||||||
version = "0.103.8"
|
version = "0.103.8"
|
||||||
@@ -3319,6 +3563,16 @@ version = "1.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sct"
|
||||||
|
version = "0.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414"
|
||||||
|
dependencies = [
|
||||||
|
"ring",
|
||||||
|
"untrusted",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "security-framework"
|
name = "security-framework"
|
||||||
version = "2.11.1"
|
version = "2.11.1"
|
||||||
@@ -3636,10 +3890,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "927136cc2ae6a1b0e66ac6b1210902b75c3f726db004a73bc18686dcd0dcd22f"
|
checksum = "927136cc2ae6a1b0e66ac6b1210902b75c3f726db004a73bc18686dcd0dcd22f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"socket2",
|
"socket2 0.6.1",
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "socket2"
|
||||||
|
version = "0.5.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
version = "0.6.1"
|
version = "0.6.1"
|
||||||
@@ -3789,6 +4053,12 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sync_wrapper"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sync_wrapper"
|
name = "sync_wrapper"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
@@ -3809,6 +4079,27 @@ dependencies = [
|
|||||||
"syn 2.0.111",
|
"syn 2.0.111",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "system-configuration"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
"core-foundation 0.9.4",
|
||||||
|
"system-configuration-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "system-configuration-sys"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "system-deps"
|
name = "system-deps"
|
||||||
version = "6.2.2"
|
version = "6.2.2"
|
||||||
@@ -3895,7 +4186,7 @@ dependencies = [
|
|||||||
"glob",
|
"glob",
|
||||||
"gtk",
|
"gtk",
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"http",
|
"http 1.4.0",
|
||||||
"jni",
|
"jni",
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
@@ -3909,7 +4200,7 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"plist",
|
"plist",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"reqwest",
|
"reqwest 0.12.28",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_repr",
|
"serde_repr",
|
||||||
@@ -4062,7 +4353,7 @@ dependencies = [
|
|||||||
"cookie",
|
"cookie",
|
||||||
"dpi",
|
"dpi",
|
||||||
"gtk",
|
"gtk",
|
||||||
"http",
|
"http 1.4.0",
|
||||||
"jni",
|
"jni",
|
||||||
"objc2",
|
"objc2",
|
||||||
"objc2-ui-kit",
|
"objc2-ui-kit",
|
||||||
@@ -4085,7 +4376,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "187a3f26f681bdf028f796ccf57cf478c1ee422c50128e5a0a6ebeb3f5910065"
|
checksum = "187a3f26f681bdf028f796ccf57cf478c1ee422c50128e5a0a6ebeb3f5910065"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"gtk",
|
"gtk",
|
||||||
"http",
|
"http 1.4.0",
|
||||||
"jni",
|
"jni",
|
||||||
"log",
|
"log",
|
||||||
"objc2",
|
"objc2",
|
||||||
@@ -4118,7 +4409,7 @@ dependencies = [
|
|||||||
"dunce",
|
"dunce",
|
||||||
"glob",
|
"glob",
|
||||||
"html5ever",
|
"html5ever",
|
||||||
"http",
|
"http 1.4.0",
|
||||||
"infer",
|
"infer",
|
||||||
"json-patch",
|
"json-patch",
|
||||||
"kuchikiki",
|
"kuchikiki",
|
||||||
@@ -4271,7 +4562,7 @@ dependencies = [
|
|||||||
"parking_lot",
|
"parking_lot",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"signal-hook-registry",
|
"signal-hook-registry",
|
||||||
"socket2",
|
"socket2 0.6.1",
|
||||||
"tokio-macros",
|
"tokio-macros",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
@@ -4287,6 +4578,26 @@ dependencies = [
|
|||||||
"syn 2.0.111",
|
"syn 2.0.111",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-native-tls"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
|
||||||
|
dependencies = [
|
||||||
|
"native-tls",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-rustls"
|
||||||
|
version = "0.24.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081"
|
||||||
|
dependencies = [
|
||||||
|
"rustls 0.21.12",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-util"
|
name = "tokio-util"
|
||||||
version = "0.7.17"
|
version = "0.7.17"
|
||||||
@@ -4405,7 +4716,7 @@ dependencies = [
|
|||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"sync_wrapper",
|
"sync_wrapper 1.0.2",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower-layer",
|
"tower-layer",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
@@ -4420,8 +4731,8 @@ dependencies = [
|
|||||||
"bitflags 2.10.0",
|
"bitflags 2.10.0",
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http",
|
"http 1.4.0",
|
||||||
"http-body",
|
"http-body 1.0.1",
|
||||||
"iri-string",
|
"iri-string",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"tower",
|
"tower",
|
||||||
@@ -4630,6 +4941,12 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vcpkg"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version-compare"
|
name = "version-compare"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
@@ -4827,6 +5144,12 @@ dependencies = [
|
|||||||
"system-deps",
|
"system-deps",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "webpki-roots"
|
||||||
|
version = "0.25.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "webview2-com"
|
name = "webview2-com"
|
||||||
version = "0.38.0"
|
version = "0.38.0"
|
||||||
@@ -5069,6 +5392,15 @@ dependencies = [
|
|||||||
"windows-targets 0.42.2",
|
"windows-targets 0.42.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.48.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets 0.48.5",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.52.0"
|
version = "0.52.0"
|
||||||
@@ -5120,6 +5452,21 @@ dependencies = [
|
|||||||
"windows_x86_64_msvc 0.42.2",
|
"windows_x86_64_msvc 0.42.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-targets"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_gnullvm 0.48.5",
|
||||||
|
"windows_aarch64_msvc 0.48.5",
|
||||||
|
"windows_i686_gnu 0.48.5",
|
||||||
|
"windows_i686_msvc 0.48.5",
|
||||||
|
"windows_x86_64_gnu 0.48.5",
|
||||||
|
"windows_x86_64_gnullvm 0.48.5",
|
||||||
|
"windows_x86_64_msvc 0.48.5",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-targets"
|
name = "windows-targets"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
@@ -5177,6 +5524,12 @@ version = "0.42.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
|
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_gnullvm"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_gnullvm"
|
name = "windows_aarch64_gnullvm"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
@@ -5195,6 +5548,12 @@ version = "0.42.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_msvc"
|
name = "windows_aarch64_msvc"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
@@ -5213,6 +5572,12 @@ version = "0.42.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_gnu"
|
name = "windows_i686_gnu"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
@@ -5243,6 +5608,12 @@ version = "0.42.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_msvc"
|
name = "windows_i686_msvc"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
@@ -5261,6 +5632,12 @@ version = "0.42.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
|
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnu"
|
name = "windows_x86_64_gnu"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
@@ -5279,6 +5656,12 @@ version = "0.42.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
|
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnullvm"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnullvm"
|
name = "windows_x86_64_gnullvm"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
@@ -5297,6 +5680,12 @@ version = "0.42.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_msvc"
|
name = "windows_x86_64_msvc"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
@@ -5327,6 +5716,16 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winreg"
|
||||||
|
version = "0.50.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"windows-sys 0.48.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winreg"
|
name = "winreg"
|
||||||
version = "0.55.0"
|
version = "0.55.0"
|
||||||
@@ -5365,7 +5764,7 @@ dependencies = [
|
|||||||
"gdkx11",
|
"gdkx11",
|
||||||
"gtk",
|
"gtk",
|
||||||
"html5ever",
|
"html5ever",
|
||||||
"http",
|
"http 1.4.0",
|
||||||
"javascriptcore-rs",
|
"javascriptcore-rs",
|
||||||
"jni",
|
"jni",
|
||||||
"kuchikiki",
|
"kuchikiki",
|
||||||
|
|||||||
@@ -26,4 +26,5 @@ rust_cast = "0.19.0"
|
|||||||
mdns-sd = "0.17.1"
|
mdns-sd = "0.17.1"
|
||||||
tokio = { version = "1.48.0", features = ["full"] }
|
tokio = { version = "1.48.0", features = ["full"] }
|
||||||
tauri-plugin-shell = "2.3.3"
|
tauri-plugin-shell = "2.3.3"
|
||||||
|
reqwest = { version = "0.11", features = ["json", "rustls-tls"] }
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
Checking radio-tauri v0.1.0 (D:\Sites\Work\Radio1\radio-tauri\src-tauri)
|
Checking radio-tauri v0.1.0 (D:\Sites\Work\RadioPlayer\radio-tauri\src-tauri)
|
||||||
warning: variable does not need to be mutable
|
warning: variable does not need to be mutable
|
||||||
--> src\lib.rs:38:9
|
--> src\lib.rs:38:9
|
||||||
|
|
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use serde_json::json;
|
|||||||
use tauri::{AppHandle, Manager, State};
|
use tauri::{AppHandle, Manager, State};
|
||||||
use tauri_plugin_shell::process::{CommandChild, CommandEvent};
|
use tauri_plugin_shell::process::{CommandChild, CommandEvent};
|
||||||
use tauri_plugin_shell::ShellExt;
|
use tauri_plugin_shell::ShellExt;
|
||||||
|
use reqwest;
|
||||||
|
|
||||||
struct SidecarState {
|
struct SidecarState {
|
||||||
child: Mutex<Option<CommandChild>>,
|
child: Mutex<Option<CommandChild>>,
|
||||||
@@ -115,6 +116,24 @@ async fn cast_set_volume(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn fetch_url(_app: AppHandle, url: String) -> Result<String, String> {
|
||||||
|
// Simple GET with default client, return body text. Errors are stringified for frontend.
|
||||||
|
match reqwest::Client::new().get(&url).send().await {
|
||||||
|
Ok(resp) => {
|
||||||
|
let status = resp.status();
|
||||||
|
if !status.is_success() {
|
||||||
|
return Err(format!("HTTP {} while fetching {}", status, url));
|
||||||
|
}
|
||||||
|
match resp.text().await {
|
||||||
|
Ok(t) => Ok(t),
|
||||||
|
Err(e) => Err(e.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => Err(e.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
@@ -168,7 +187,9 @@ pub fn run() {
|
|||||||
list_cast_devices,
|
list_cast_devices,
|
||||||
cast_play,
|
cast_play,
|
||||||
cast_stop,
|
cast_stop,
|
||||||
cast_set_volume
|
cast_set_volume,
|
||||||
|
// allow frontend to request arbitrary URLs via backend (bypass CORS)
|
||||||
|
fetch_url
|
||||||
])
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Radio1 Player</title>
|
<title>RadioPlayer</title>
|
||||||
<link rel="stylesheet" href="styles.css">
|
<link rel="stylesheet" href="styles.css">
|
||||||
<script src="main.js" defer type="module"></script>
|
<script src="main.js" defer type="module"></script>
|
||||||
</head>
|
</head>
|
||||||
@@ -16,28 +16,27 @@
|
|||||||
|
|
||||||
<main class="glass-card">
|
<main class="glass-card">
|
||||||
<header data-tauri-drag-region>
|
<header data-tauri-drag-region>
|
||||||
<button id="menu-btn" class="icon-btn" aria-label="Menu">
|
<div class="header-top-row">
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
<div class="header-icons-left" aria-hidden="true">
|
||||||
|
<button id="edit-stations-btn" class="icon-btn" title="Edit Stations" aria-label="Edit Stations">
|
||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||||
stroke-linecap="round" stroke-linejoin="round">
|
stroke-linecap="round" stroke-linejoin="round">
|
||||||
<line x1="3" y1="12" x2="21" y2="12"></line>
|
<path d="M12 20h9" />
|
||||||
<line x1="3" y1="6" x2="21" y2="6"></line>
|
<path d="M16.5 3.5a2.121 2.121 0 1 1 3 3L7 19l-4 1 1-4 12.5-12.5z" />
|
||||||
<line x1="3" y1="18" x2="21" y2="18"></line>
|
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<div class="header-info" data-tauri-drag-region>
|
<button id="cast-toggle-btn" class="icon-btn" aria-label="Cast" title="Cast">
|
||||||
<span class="app-title">Radio1 Player</span>
|
|
||||||
<span class="status-indicator" id="status-indicator">
|
|
||||||
<span class="status-dot"></span> <span id="status-text">Ready</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="header-buttons">
|
|
||||||
<button id="cast-toggle-btn" class="icon-btn" aria-label="Cast">
|
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||||
stroke-linecap="round" stroke-linejoin="round">
|
stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path d="M2 16.1A5 5 0 0 1 5.9 20M2 12.05A9 9 0 0 1 9.95 20M2 8V6a14 14 0 0 1 14 14h-2" />
|
<path d="M2 16.1A5 5 0 0 1 5.9 20M2 12.05A9 9 0 0 1 9.95 20M2 8V6a14 14 0 0 1 14 14h-2" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<button id="close-btn" class="icon-btn close-btn" aria-label="Close">
|
</div>
|
||||||
|
|
||||||
|
<!-- status moved below station info -->
|
||||||
|
|
||||||
|
<div class="header-close">
|
||||||
|
<button id="close-btn" class="icon-btn close-btn" aria-label="Close" title="Close">
|
||||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||||
stroke-linecap="round" stroke-linejoin="round">
|
stroke-linecap="round" stroke-linejoin="round">
|
||||||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||||
@@ -45,6 +44,25 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="header-third-row">
|
||||||
|
<div class="header-icons">
|
||||||
|
<button id="edit-stations-btn" class="icon-btn" title="Edit Stations" aria-label="Edit Stations">
|
||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||||
|
stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M12 20h9" />
|
||||||
|
<path d="M16.5 3.5a2.121 2.121 0 1 1 3 3L7 19l-4 1 1-4 12.5-12.5z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button id="cast-toggle-btn" class="icon-btn" aria-label="Cast" title="Cast">
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||||
|
stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M2 16.1A5 5 0 0 1 5.9 20M2 12.05A9 9 0 0 1 9.95 20M2 8V6a14 14 0 0 1 14 14h-2" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<section class="artwork-section">
|
<section class="artwork-section">
|
||||||
@@ -82,8 +100,13 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="track-info">
|
<section class="track-info">
|
||||||
<h2 id="station-name">Radio 1 MB</h2>
|
<h2 id="station-name"></h2>
|
||||||
<p id="station-subtitle">Live Stream</p>
|
<p id="now-playing" class="hidden" aria-live="polite"></p>
|
||||||
|
<p id="station-subtitle"></p>
|
||||||
|
<div id="status-indicator" class="status-indicator-wrap" aria-hidden="true">
|
||||||
|
<span class="status-dot"></span>
|
||||||
|
<span id="status-text"></span>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Visual Progress Bar (Live) -->
|
<!-- Visual Progress Bar (Live) -->
|
||||||
@@ -151,6 +174,36 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Stations Editor Overlay -->
|
||||||
|
<div id="editor-overlay" class="overlay hidden" aria-hidden="true">
|
||||||
|
<div class="modal" role="dialog" aria-modal="true" aria-labelledby="editorTitle">
|
||||||
|
<h2 id="editorTitle">Edit Stations</h2>
|
||||||
|
|
||||||
|
<ul id="editor-list" class="device-list"></ul>
|
||||||
|
|
||||||
|
<form id="add-station-form">
|
||||||
|
<div style="margin-bottom:8px;">
|
||||||
|
<input id="us_title" placeholder="Title" required style="width:100%;padding:10px;border-radius:8px;border:1px solid rgba(255,255,255,0.06);background:transparent;color:inherit">
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom:8px;">
|
||||||
|
<input id="us_url" placeholder="Stream URL" required style="width:100%;padding:10px;border-radius:8px;border:1px solid rgba(255,255,255,0.06);background:transparent;color:inherit">
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom:8px;">
|
||||||
|
<input id="us_logo" placeholder="Logo URL (optional)" style="width:100%;padding:10px;border-radius:8px;border:1px solid rgba(255,255,255,0.06);background:transparent;color:inherit">
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom:12px;">
|
||||||
|
<input id="us_www" placeholder="Website (optional)" style="width:100%;padding:10px;border-radius:8px;border:1px solid rgba(255,255,255,0.06);background:transparent;color:inherit">
|
||||||
|
</div>
|
||||||
|
<input type="hidden" id="us_id">
|
||||||
|
<input type="hidden" id="us_index">
|
||||||
|
<div style="display:flex;gap:8px;">
|
||||||
|
<button id="us_save_btn" class="btn cancel" type="submit" style="flex:1">Save</button>
|
||||||
|
<button id="editor-close-btn" class="btn" type="button" style="flex:0;background:#6b6bff">Close</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
436
src/main.js
436
src/main.js
@@ -12,6 +12,7 @@ const audio = new Audio();
|
|||||||
// UI Elements
|
// UI Elements
|
||||||
const stationNameEl = document.getElementById('station-name');
|
const stationNameEl = document.getElementById('station-name');
|
||||||
const stationSubtitleEl = document.getElementById('station-subtitle');
|
const stationSubtitleEl = document.getElementById('station-subtitle');
|
||||||
|
const nowPlayingEl = document.getElementById('now-playing');
|
||||||
const statusTextEl = document.getElementById('status-text');
|
const statusTextEl = document.getElementById('status-text');
|
||||||
const statusDotEl = document.querySelector('.status-dot');
|
const statusDotEl = document.querySelector('.status-dot');
|
||||||
const playBtn = document.getElementById('play-btn');
|
const playBtn = document.getElementById('play-btn');
|
||||||
@@ -27,16 +28,63 @@ const closeOverlayBtn = document.getElementById('close-overlay');
|
|||||||
const deviceListEl = document.getElementById('device-list');
|
const deviceListEl = document.getElementById('device-list');
|
||||||
const logoTextEl = document.querySelector('.station-logo-text');
|
const logoTextEl = document.querySelector('.station-logo-text');
|
||||||
const logoImgEl = document.getElementById('station-logo-img');
|
const logoImgEl = document.getElementById('station-logo-img');
|
||||||
|
const artworkPlaceholder = document.querySelector('.artwork-placeholder');
|
||||||
|
// Editor elements
|
||||||
|
const editBtn = document.getElementById('edit-stations-btn');
|
||||||
|
const editorOverlay = document.getElementById('editor-overlay');
|
||||||
|
const editorCloseBtn = document.getElementById('editor-close-btn');
|
||||||
|
const editorListEl = document.getElementById('editor-list');
|
||||||
|
const addStationForm = document.getElementById('add-station-form');
|
||||||
|
const usTitle = document.getElementById('us_title');
|
||||||
|
const usUrl = document.getElementById('us_url');
|
||||||
|
const usLogo = document.getElementById('us_logo');
|
||||||
|
const usWww = document.getElementById('us_www');
|
||||||
|
const usId = document.getElementById('us_id');
|
||||||
|
const usIndex = document.getElementById('us_index');
|
||||||
|
|
||||||
// Init
|
// Init
|
||||||
async function init() {
|
async function init() {
|
||||||
|
restoreSavedVolume();
|
||||||
await loadStations();
|
await loadStations();
|
||||||
setupEventListeners();
|
setupEventListeners();
|
||||||
updateUI();
|
updateUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Volume persistence
|
||||||
|
function saveVolumeToStorage(val) {
|
||||||
|
try {
|
||||||
|
localStorage.setItem('volume', String(val));
|
||||||
|
} catch (e) { /* ignore */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSavedVolume() {
|
||||||
|
try {
|
||||||
|
const v = localStorage.getItem('volume');
|
||||||
|
if (!v) return null;
|
||||||
|
const n = Number(v);
|
||||||
|
if (Number.isFinite(n) && n >= 0 && n <= 100) return n;
|
||||||
|
return null;
|
||||||
|
} catch (e) { return null; }
|
||||||
|
}
|
||||||
|
|
||||||
|
function restoreSavedVolume() {
|
||||||
|
const saved = getSavedVolume();
|
||||||
|
if (saved !== null && volumeSlider) {
|
||||||
|
volumeSlider.value = String(saved);
|
||||||
|
volumeValue.textContent = `${saved}%`;
|
||||||
|
const decimals = saved / 100;
|
||||||
|
audio.volume = decimals;
|
||||||
|
// If currently in cast mode and a device is selected, propagate volume
|
||||||
|
if (currentMode === 'cast' && currentCastDevice) {
|
||||||
|
invoke('cast_set_volume', { deviceName: currentCastDevice, volume: decimals }).catch(()=>{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function loadStations() {
|
async function loadStations() {
|
||||||
try {
|
try {
|
||||||
|
// stop any existing pollers before reloading stations
|
||||||
|
stopCurrentSongPollers();
|
||||||
const resp = await fetch('stations.json');
|
const resp = await fetch('stations.json');
|
||||||
const raw = await resp.json();
|
const raw = await resp.json();
|
||||||
|
|
||||||
@@ -62,9 +110,41 @@ async function loadStations() {
|
|||||||
// Filter out disabled stations and those without a stream URL
|
// Filter out disabled stations and those without a stream URL
|
||||||
.filter((s) => s.enabled !== false && s.url && s.url.length > 0);
|
.filter((s) => s.enabled !== false && s.url && s.url.length > 0);
|
||||||
|
|
||||||
|
// Load user-defined stations from localStorage and merge
|
||||||
|
const user = loadUserStations();
|
||||||
|
const userNormalized = user
|
||||||
|
.map((s) => {
|
||||||
|
const name = s.title || s.name || s.id || 'UserStation';
|
||||||
|
const url = s.url || s.liveAudio || s.liveVideo || '';
|
||||||
|
return {
|
||||||
|
id: s.id || `user-${name.replace(/\s+/g, '-')}`,
|
||||||
|
name,
|
||||||
|
url,
|
||||||
|
logo: s.logo || '',
|
||||||
|
enabled: typeof s.enabled === 'boolean' ? s.enabled : true,
|
||||||
|
raw: s,
|
||||||
|
_user: true,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter((s) => s.url && s.url.length > 0);
|
||||||
|
|
||||||
|
// Append user stations after file stations
|
||||||
|
stations = stations.concat(userNormalized);
|
||||||
|
|
||||||
if (stations.length > 0) {
|
if (stations.length > 0) {
|
||||||
|
// Try to restore last selected station by id
|
||||||
|
const lastId = getLastStationId();
|
||||||
|
if (lastId) {
|
||||||
|
const found = stations.findIndex(s => s.id === lastId);
|
||||||
|
if (found >= 0) currentIndex = found;
|
||||||
|
else currentIndex = 0;
|
||||||
|
} else {
|
||||||
currentIndex = 0;
|
currentIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
loadStation(currentIndex);
|
loadStation(currentIndex);
|
||||||
|
// start polling for currentSong endpoints (if any)
|
||||||
|
startCurrentSongPollers();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to load stations', e);
|
console.error('Failed to load stations', e);
|
||||||
@@ -72,6 +152,236 @@ async function loadStations() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Current Song Polling ---
|
||||||
|
const currentSongPollers = new Map(); // stationId -> intervalId
|
||||||
|
|
||||||
|
function stopCurrentSongPollers() {
|
||||||
|
for (const id of currentSongPollers.values()) {
|
||||||
|
clearInterval(id);
|
||||||
|
}
|
||||||
|
currentSongPollers.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
function startCurrentSongPollers() {
|
||||||
|
// Clear existing
|
||||||
|
stopCurrentSongPollers();
|
||||||
|
|
||||||
|
stations.forEach((s, idx) => {
|
||||||
|
const url = s.raw && s.raw.currentSong;
|
||||||
|
if (url && typeof url === 'string' && url.length > 0) {
|
||||||
|
// fetch immediately and then every 10s
|
||||||
|
fetchAndStoreCurrentSong(s, idx, url);
|
||||||
|
const iid = setInterval(() => fetchAndStoreCurrentSong(s, idx, url), 10000);
|
||||||
|
currentSongPollers.set(s.id || idx, iid);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchAndStoreCurrentSong(station, idx, url) {
|
||||||
|
try {
|
||||||
|
let data = null;
|
||||||
|
try {
|
||||||
|
const resp = await fetch(url, { cache: 'no-store' });
|
||||||
|
const ct = resp.headers.get('content-type') || '';
|
||||||
|
if (ct.includes('application/json')) {
|
||||||
|
data = await resp.json();
|
||||||
|
} else {
|
||||||
|
const txt = await resp.text();
|
||||||
|
try { data = JSON.parse(txt); } catch (e) { data = null; }
|
||||||
|
}
|
||||||
|
} catch (fetchErr) {
|
||||||
|
// Possibly blocked by CORS — fall back to backend fetch via Tauri invoke
|
||||||
|
try {
|
||||||
|
const body = await invoke('fetch_url', { url });
|
||||||
|
try { data = JSON.parse(body); } catch (e) { data = null; }
|
||||||
|
} catch (invokeErr) {
|
||||||
|
console.debug('Both fetch and backend fetch failed for', url, fetchErr, invokeErr);
|
||||||
|
data = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data && (data.artist || data.title)) {
|
||||||
|
station.currentSongInfo = { artist: data.artist || '', title: data.title || '' };
|
||||||
|
// update UI if this is the currently loaded station
|
||||||
|
if (idx === currentIndex) updateNowPlayingUI();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// ignore fetch errors silently (network/CORS) but keep console for debugging
|
||||||
|
console.debug('currentSong fetch failed for', url, e.message || e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateNowPlayingUI() {
|
||||||
|
const station = stations[currentIndex];
|
||||||
|
if (!station) return;
|
||||||
|
|
||||||
|
if (nowPlayingEl) {
|
||||||
|
if (station.currentSongInfo && station.currentSongInfo.artist && station.currentSongInfo.title) {
|
||||||
|
nowPlayingEl.textContent = `${station.currentSongInfo.artist} — ${station.currentSongInfo.title}`;
|
||||||
|
nowPlayingEl.classList.remove('hidden');
|
||||||
|
} else {
|
||||||
|
nowPlayingEl.textContent = '';
|
||||||
|
nowPlayingEl.classList.add('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep subtitle for mode/status
|
||||||
|
stationSubtitleEl.textContent = currentMode === 'cast' ? `Casting to ${currentCastDevice}` : 'Live Stream';
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- User Stations (localStorage) ---
|
||||||
|
function loadUserStations() {
|
||||||
|
try {
|
||||||
|
const raw = localStorage.getItem('userStations');
|
||||||
|
if (!raw) return [];
|
||||||
|
return JSON.parse(raw);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error reading user stations', e);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveUserStations(arr) {
|
||||||
|
try {
|
||||||
|
localStorage.setItem('userStations', JSON.stringify(arr || []));
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error saving user stations', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openEditorOverlay() {
|
||||||
|
renderUserStationsList();
|
||||||
|
editorOverlay.classList.remove('hidden');
|
||||||
|
editorOverlay.setAttribute('aria-hidden', 'false');
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeEditorOverlay() {
|
||||||
|
editorOverlay.classList.add('hidden');
|
||||||
|
editorOverlay.setAttribute('aria-hidden', 'true');
|
||||||
|
// clear form
|
||||||
|
addStationForm.reset();
|
||||||
|
usIndex.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderUserStationsList() {
|
||||||
|
const list = loadUserStations();
|
||||||
|
editorListEl.innerHTML = '';
|
||||||
|
if (!list || list.length === 0) {
|
||||||
|
editorListEl.innerHTML = '<li class="device"><div class="device-main">No user stations</div><div class="device-sub">Add your stream using the form below</div></li>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
list.forEach((s, idx) => {
|
||||||
|
const li = document.createElement('li');
|
||||||
|
li.className = 'device';
|
||||||
|
const main = s.title || s.name || s.id || 'User Station';
|
||||||
|
const sub = s.url || '';
|
||||||
|
li.innerHTML = `<div style="display:flex;justify-content:space-between;align-items:center;">
|
||||||
|
<div>
|
||||||
|
<div class=\"device-main\">${main}</div>
|
||||||
|
<div class=\"device-sub\">${sub}</div>
|
||||||
|
</div>
|
||||||
|
<div style=\"display:flex;gap:8px;align-items:center;\">
|
||||||
|
<button data-idx=\"${idx}\" class=\"btn edit-btn\" style=\"background:#6bd3ff;color:#042\">Edit</button>
|
||||||
|
<button data-idx=\"${idx}\" class=\"btn delete-btn\" style=\"background:#ff6b6b\">Delete</button>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
editorListEl.appendChild(li);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Attach handlers
|
||||||
|
editorListEl.querySelectorAll('.edit-btn').forEach(b => {
|
||||||
|
b.addEventListener('click', () => {
|
||||||
|
const idx = Number(b.getAttribute('data-idx'));
|
||||||
|
editUserStation(idx);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
editorListEl.querySelectorAll('.delete-btn').forEach(b => {
|
||||||
|
b.addEventListener('click', () => {
|
||||||
|
const idx = Number(b.getAttribute('data-idx'));
|
||||||
|
deleteUserStation(idx);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function editUserStation(idx) {
|
||||||
|
const list = loadUserStations();
|
||||||
|
const s = list[idx];
|
||||||
|
if (!s) return;
|
||||||
|
usTitle.value = s.title || s.name || '';
|
||||||
|
usUrl.value = s.url || s.liveAudio || '';
|
||||||
|
usLogo.value = s.logo || '';
|
||||||
|
usWww.value = s.www || s.website || '';
|
||||||
|
usId.value = s.id || '';
|
||||||
|
usIndex.value = String(idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteUserStation(idx) {
|
||||||
|
const list = loadUserStations();
|
||||||
|
list.splice(idx, 1);
|
||||||
|
saveUserStations(list);
|
||||||
|
// refresh stations in runtime
|
||||||
|
refreshStationsFromSources();
|
||||||
|
renderUserStationsList();
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshStationsFromSources() {
|
||||||
|
// reload stations.json and user stations into `stations` array
|
||||||
|
// For simplicity, re-run loadStations()
|
||||||
|
loadStations();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Persist last-selected station id between sessions
|
||||||
|
function saveLastStationId(id) {
|
||||||
|
try {
|
||||||
|
if (!id) return;
|
||||||
|
localStorage.setItem('lastStationId', id);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to save last station id', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLastStationId() {
|
||||||
|
try {
|
||||||
|
return localStorage.getItem('lastStationId');
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle form submit (add/update)
|
||||||
|
addStationForm && addStationForm.addEventListener('submit', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const list = loadUserStations();
|
||||||
|
const station = {
|
||||||
|
id: usId.value || `user-${Date.now()}`,
|
||||||
|
title: usTitle.value.trim(),
|
||||||
|
url: usUrl.value.trim(),
|
||||||
|
logo: usLogo.value.trim(),
|
||||||
|
www: usWww.value.trim(),
|
||||||
|
enabled: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const idx = usIndex.value === '' ? -1 : Number(usIndex.value);
|
||||||
|
if (idx >= 0 && idx < list.length) {
|
||||||
|
list[idx] = station;
|
||||||
|
} else {
|
||||||
|
list.push(station);
|
||||||
|
}
|
||||||
|
|
||||||
|
saveUserStations(list);
|
||||||
|
renderUserStationsList();
|
||||||
|
refreshStationsFromSources();
|
||||||
|
addStationForm.reset();
|
||||||
|
usIndex.value = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Editor button handlers
|
||||||
|
editBtn && editBtn.addEventListener('click', openEditorOverlay);
|
||||||
|
editorCloseBtn && editorCloseBtn.addEventListener('click', closeEditorOverlay);
|
||||||
|
|
||||||
|
|
||||||
function setupEventListeners() {
|
function setupEventListeners() {
|
||||||
playBtn.addEventListener('click', togglePlay);
|
playBtn.addEventListener('click', togglePlay);
|
||||||
prevBtn.addEventListener('click', playPrev);
|
prevBtn.addEventListener('click', playPrev);
|
||||||
@@ -94,10 +404,10 @@ function setupEventListeners() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Menu button - explicit functionality or placeholder?
|
// Menu button - explicit functionality or placeholder?
|
||||||
// For now just log or maybe show about
|
// Menu removed — header click opens stations via artwork placeholder
|
||||||
document.getElementById('menu-btn').addEventListener('click', () => {
|
|
||||||
openStationsOverlay();
|
// Click artwork to open stations chooser
|
||||||
});
|
artworkPlaceholder && artworkPlaceholder.addEventListener('click', openStationsOverlay);
|
||||||
|
|
||||||
// Hotkeys?
|
// Hotkeys?
|
||||||
}
|
}
|
||||||
@@ -108,14 +418,28 @@ function loadStation(index) {
|
|||||||
|
|
||||||
stationNameEl.textContent = station.name;
|
stationNameEl.textContent = station.name;
|
||||||
stationSubtitleEl.textContent = currentMode === 'cast' ? `Casting to ${currentCastDevice}` : 'Live Stream';
|
stationSubtitleEl.textContent = currentMode === 'cast' ? `Casting to ${currentCastDevice}` : 'Live Stream';
|
||||||
|
// clear now playing when loading a new station; will be updated by poller if available
|
||||||
|
if (nowPlayingEl) {
|
||||||
|
nowPlayingEl.textContent = '';
|
||||||
|
nowPlayingEl.classList.add('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
// Update Logo Text (First letter or number)
|
// Update Logo Text (First letter or number)
|
||||||
// Simple heuristic: if name has a number, use it, else first letter
|
// Simple heuristic: if name has a number, use it, else first letter
|
||||||
// If station has a logo URL, show the image; otherwise show the text fallback
|
// If station has a logo URL, show the image; otherwise show the text fallback
|
||||||
if (station.logo && station.logo.length > 0) {
|
if (station.logo && station.logo.length > 0) {
|
||||||
|
// Verify the logo exists before showing it
|
||||||
|
checkImageExists(station.logo).then((exists) => {
|
||||||
|
if (exists) {
|
||||||
logoImgEl.src = station.logo;
|
logoImgEl.src = station.logo;
|
||||||
logoImgEl.classList.remove('hidden');
|
logoImgEl.classList.remove('hidden');
|
||||||
logoTextEl.classList.add('hidden');
|
logoTextEl.classList.add('hidden');
|
||||||
|
} else {
|
||||||
|
logoImgEl.src = '';
|
||||||
|
logoImgEl.classList.add('hidden');
|
||||||
|
logoTextEl.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
// Fallback to single-letter/logo text
|
// Fallback to single-letter/logo text
|
||||||
logoImgEl.src = '';
|
logoImgEl.src = '';
|
||||||
@@ -130,6 +454,39 @@ function loadStation(index) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if an image URL is reachable and valid
|
||||||
|
function checkImageExists(url, timeout = 6000) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
if (!url) return resolve(false);
|
||||||
|
try {
|
||||||
|
const img = new Image();
|
||||||
|
let timedOut = false;
|
||||||
|
const t = setTimeout(() => {
|
||||||
|
timedOut = true;
|
||||||
|
img.src = ''; // stop load
|
||||||
|
resolve(false);
|
||||||
|
}, timeout);
|
||||||
|
|
||||||
|
img.onload = () => {
|
||||||
|
if (!timedOut) {
|
||||||
|
clearTimeout(t);
|
||||||
|
resolve(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
img.onerror = () => {
|
||||||
|
if (!timedOut) {
|
||||||
|
clearTimeout(t);
|
||||||
|
resolve(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Bypass caching oddities by assigning after handlers
|
||||||
|
img.src = url;
|
||||||
|
} catch (e) {
|
||||||
|
resolve(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function togglePlay() {
|
async function togglePlay() {
|
||||||
if (isPlaying) {
|
if (isPlaying) {
|
||||||
await stop();
|
await stop();
|
||||||
@@ -202,6 +559,9 @@ async function playNext() {
|
|||||||
currentIndex = (currentIndex + 1) % stations.length;
|
currentIndex = (currentIndex + 1) % stations.length;
|
||||||
loadStation(currentIndex);
|
loadStation(currentIndex);
|
||||||
|
|
||||||
|
// persist selection
|
||||||
|
saveLastStationId(stations[currentIndex].id);
|
||||||
|
|
||||||
if (wasPlaying) await play();
|
if (wasPlaying) await play();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,6 +575,9 @@ async function playPrev() {
|
|||||||
currentIndex = (currentIndex - 1 + stations.length) % stations.length;
|
currentIndex = (currentIndex - 1 + stations.length) % stations.length;
|
||||||
loadStation(currentIndex);
|
loadStation(currentIndex);
|
||||||
|
|
||||||
|
// persist selection
|
||||||
|
saveLastStationId(stations[currentIndex].id);
|
||||||
|
|
||||||
if (wasPlaying) await play();
|
if (wasPlaying) await play();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,12 +610,16 @@ function handleVolumeInput() {
|
|||||||
} else if (currentMode === 'cast' && currentCastDevice) {
|
} else if (currentMode === 'cast' && currentCastDevice) {
|
||||||
invoke('cast_set_volume', { deviceName: currentCastDevice, volume: decimals });
|
invoke('cast_set_volume', { deviceName: currentCastDevice, volume: decimals });
|
||||||
}
|
}
|
||||||
|
// persist volume for next sessions
|
||||||
|
saveVolumeToStorage(Number(val));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cast Logic
|
// Cast Logic
|
||||||
async function openCastOverlay() {
|
async function openCastOverlay() {
|
||||||
castOverlay.classList.remove('hidden');
|
castOverlay.classList.remove('hidden');
|
||||||
castOverlay.setAttribute('aria-hidden', 'false');
|
castOverlay.setAttribute('aria-hidden', 'false');
|
||||||
|
// ensure cast overlay shows linear list style
|
||||||
|
deviceListEl.classList.remove('stations-grid');
|
||||||
deviceListEl.innerHTML = '<li class="device"><div class="device-main">Scanning...</div><div class="device-sub">Searching for speakers</div></li>';
|
deviceListEl.innerHTML = '<li class="device"><div class="device-main">Scanning...</div><div class="device-sub">Searching for speakers</div></li>';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -316,40 +683,75 @@ async function selectCastDevice(deviceName) {
|
|||||||
window.addEventListener('DOMContentLoaded', init);
|
window.addEventListener('DOMContentLoaded', init);
|
||||||
|
|
||||||
// Open overlay and show list of stations (used by menu/hamburger)
|
// Open overlay and show list of stations (used by menu/hamburger)
|
||||||
function openStationsOverlay() {
|
async function openStationsOverlay() {
|
||||||
castOverlay.classList.remove('hidden');
|
castOverlay.classList.remove('hidden');
|
||||||
castOverlay.setAttribute('aria-hidden', 'false');
|
castOverlay.setAttribute('aria-hidden', 'false');
|
||||||
deviceListEl.innerHTML = '<li class="device"><div class="device-main">Loading...</div><div class="device-sub">Preparing stations</div></li>';
|
deviceListEl.innerHTML = '<li class="device"><div class="device-main">Loading...</div><div class="device-sub">Preparing stations</div></li>';
|
||||||
|
|
||||||
// If stations not loaded yet, show message
|
// If stations not loaded yet, show message
|
||||||
if (!stations || stations.length === 0) {
|
if (!stations || stations.length === 0) {
|
||||||
|
deviceListEl.classList.remove('stations-grid');
|
||||||
deviceListEl.innerHTML = '<li class="device"><div class="device-main">No stations found</div><div class="device-sub">Check your stations.json</div></li>';
|
deviceListEl.innerHTML = '<li class="device"><div class="device-main">No stations found</div><div class="device-sub">Check your stations.json</div></li>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Render stations as responsive grid of cards (2-3 per row depending on width)
|
||||||
|
deviceListEl.classList.add('stations-grid');
|
||||||
deviceListEl.innerHTML = '';
|
deviceListEl.innerHTML = '';
|
||||||
|
|
||||||
stations.forEach((s, idx) => {
|
for (let idx = 0; idx < stations.length; idx++) {
|
||||||
|
const s = stations[idx];
|
||||||
const li = document.createElement('li');
|
const li = document.createElement('li');
|
||||||
li.className = 'device' + (currentIndex === idx ? ' selected' : '');
|
li.className = 'station-card' + (currentIndex === idx ? ' selected' : '');
|
||||||
|
|
||||||
|
const logoUrl = s.logo || (s.raw && (s.raw.logo || s.raw.poster)) || '';
|
||||||
|
const title = s.name || s.title || s.id || 'Station';
|
||||||
const subtitle = (s.raw && s.raw.www) ? s.raw.www : (s.id || '');
|
const subtitle = (s.raw && s.raw.www) ? s.raw.www : (s.id || '');
|
||||||
li.innerHTML = `<div class="device-main">${s.name}</div><div class="device-sub">${subtitle}</div>`;
|
|
||||||
|
const left = document.createElement('div');
|
||||||
|
left.className = 'station-card-left';
|
||||||
|
|
||||||
|
// Check if logo exists, otherwise show fallback
|
||||||
|
const hasLogo = await checkImageExists(logoUrl);
|
||||||
|
if (hasLogo) {
|
||||||
|
const img = document.createElement('img');
|
||||||
|
img.className = 'station-card-logo';
|
||||||
|
img.src = logoUrl;
|
||||||
|
img.alt = `${title} logo`;
|
||||||
|
left.appendChild(img);
|
||||||
|
} else {
|
||||||
|
const fb = document.createElement('div');
|
||||||
|
fb.className = 'station-card-fallback';
|
||||||
|
fb.textContent = title.charAt(0).toUpperCase();
|
||||||
|
left.appendChild(fb);
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = document.createElement('div');
|
||||||
|
body.className = 'station-card-body';
|
||||||
|
const tEl = document.createElement('div');
|
||||||
|
tEl.className = 'station-card-title';
|
||||||
|
tEl.textContent = title;
|
||||||
|
const sEl = document.createElement('div');
|
||||||
|
sEl.className = 'station-card-sub';
|
||||||
|
sEl.textContent = subtitle;
|
||||||
|
body.appendChild(tEl);
|
||||||
|
body.appendChild(sEl);
|
||||||
|
|
||||||
|
li.appendChild(left);
|
||||||
|
li.appendChild(body);
|
||||||
|
|
||||||
li.onclick = async () => {
|
li.onclick = async () => {
|
||||||
// Always switch to local playback when selecting from stations menu
|
|
||||||
currentMode = 'local';
|
currentMode = 'local';
|
||||||
currentCastDevice = null;
|
currentCastDevice = null;
|
||||||
castBtn.style.color = 'var(--text-main)';
|
castBtn.style.color = 'var(--text-main)';
|
||||||
|
|
||||||
// Select and play
|
|
||||||
currentIndex = idx;
|
currentIndex = idx;
|
||||||
|
// Remember this selection
|
||||||
|
saveLastStationId(stations[idx].id);
|
||||||
loadStation(currentIndex);
|
loadStation(currentIndex);
|
||||||
closeCastOverlay();
|
closeCastOverlay();
|
||||||
try {
|
try { await play(); } catch (e) { console.error('Failed to play station from grid', e); }
|
||||||
await play();
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Failed to play station from menu', e);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
deviceListEl.appendChild(li);
|
deviceListEl.appendChild(li);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
"poster": "",
|
"poster": "",
|
||||||
"lastSongs": "http://data.radio.si/api/lastsongsxml/radio1/json",
|
"lastSongs": "http://data.radio.si/api/lastsongsxml/radio1/json",
|
||||||
"epg": "http://spored.radio.si/api/now/radio1",
|
"epg": "http://spored.radio.si/api/now/radio1",
|
||||||
|
"currentSong": "https://radio1.si/?handler=CurrentSong",
|
||||||
"defaultText": "www.radio1.si",
|
"defaultText": "www.radio1.si",
|
||||||
"www": "https://www.radio1.si",
|
"www": "https://www.radio1.si",
|
||||||
"mountPoints": [
|
"mountPoints": [
|
||||||
@@ -200,6 +201,7 @@
|
|||||||
"liveAudio": "http://live.radio.si/Radio80",
|
"liveAudio": "http://live.radio.si/Radio80",
|
||||||
"liveVideo": null,
|
"liveVideo": null,
|
||||||
"poster": null,
|
"poster": null,
|
||||||
|
"currentSong": "https://radio80.si/?handler=CurrentSong",
|
||||||
"lastSongs": "http://data.radio.si/api/lastsongsxml/radio80/json",
|
"lastSongs": "http://data.radio.si/api/lastsongsxml/radio80/json",
|
||||||
"epg": "http://spored.radio.si/api/now/radio80",
|
"epg": "http://spored.radio.si/api/now/radio80",
|
||||||
"defaultText": "www.radio80.si",
|
"defaultText": "www.radio80.si",
|
||||||
|
|||||||
269
src/styles.css
269
src/styles.css
@@ -29,6 +29,14 @@ body {
|
|||||||
height: 100vh;
|
height: 100vh;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
background: linear-gradient(-45deg, #7b7fd8, #b57cf2, #8b5cf6, #6930c3, #7b7fd8);
|
background: linear-gradient(-45deg, #7b7fd8, #b57cf2, #8b5cf6, #6930c3, #7b7fd8);
|
||||||
|
.status-indicator-wrap {
|
||||||
|
display:flex;
|
||||||
|
align-items:center;
|
||||||
|
gap:10px;
|
||||||
|
justify-content:center;
|
||||||
|
margin-top:8px;
|
||||||
|
color:var(--text-main);
|
||||||
|
}
|
||||||
background-size: 400% 400%;
|
background-size: 400% 400%;
|
||||||
animation: gradientShift 12s ease-in-out infinite;
|
animation: gradientShift 12s ease-in-out infinite;
|
||||||
font-family: 'Segoe UI', system-ui, sans-serif;
|
font-family: 'Segoe UI', system-ui, sans-serif;
|
||||||
@@ -111,36 +119,96 @@ body {
|
|||||||
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.2);
|
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Make whole card draggable for window movement; interactive children override with no-drag */
|
||||||
|
.glass-card {
|
||||||
|
-webkit-app-region: drag;
|
||||||
|
}
|
||||||
|
|
||||||
/* Header */
|
/* Header */
|
||||||
header {
|
.header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
-webkit-app-region: drag; /* Draggable area */
|
-webkit-app-region: drag; /* Draggable area */
|
||||||
|
padding: 10px 14px 8px 14px;
|
||||||
|
border-radius: 14px;
|
||||||
|
background: linear-gradient(135deg, rgba(60,84,255,0.14), rgba(123,127,216,0.10));
|
||||||
|
border: 1px solid rgba(120,130,255,0.12);
|
||||||
|
box-shadow: 0 10px 30px rgba(28,25,60,0.35), inset 0 1px 0 rgba(255,255,255,0.03);
|
||||||
|
backdrop-filter: blur(8px) saturate(120%);
|
||||||
|
position: relative;
|
||||||
|
z-index: 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header-top {
|
||||||
|
display:flex;
|
||||||
|
justify-content:space-between;
|
||||||
|
align-items:center;
|
||||||
|
width:100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.header-top-row {
|
||||||
|
display:flex;
|
||||||
|
justify-content:space-between;
|
||||||
|
align-items:center;
|
||||||
|
width:100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.header-icons-left { flex: 0 0 auto; display:flex; align-items:center; gap:8px; padding-left:8px; }
|
||||||
|
|
||||||
|
.header-center-status { flex:1; display:flex; justify-content:center; align-items:center; }
|
||||||
|
|
||||||
|
.header-close { flex:0 0 auto; }
|
||||||
|
|
||||||
|
.header-second-row {
|
||||||
|
display:flex;
|
||||||
|
justify-content:center;
|
||||||
|
align-items:center;
|
||||||
|
width:100%;
|
||||||
|
margin-top:6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-indicator-wrap { display:flex; gap:8px; align-items:center; color:var(--text-main); }
|
||||||
|
|
||||||
|
.header-third-row { display:none; }
|
||||||
|
.header-left {
|
||||||
|
justify-content: flex-start;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-right {
|
||||||
|
justify-content: flex-end;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-title { text-align: center; }
|
||||||
|
|
||||||
.header-info {
|
.header-info {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-title {
|
.app-title {
|
||||||
font-weight: 600;
|
font-weight: 700;
|
||||||
font-size: 1rem;
|
font-size: 1.05rem;
|
||||||
color: var(--text-main);
|
color: var(--text-main);
|
||||||
|
letter-spacing: 0.4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-indicator {
|
.status-indicator {
|
||||||
font-size: 0.8rem;
|
font-size: 0.85rem;
|
||||||
color: var(--success);
|
color: var(--success);
|
||||||
margin-top: 4px;
|
margin-top: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-dot {
|
.status-dot {
|
||||||
@@ -152,26 +220,28 @@ header {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.icon-btn {
|
.icon-btn {
|
||||||
background: none;
|
background: rgba(255,255,255,0.02);
|
||||||
border: none;
|
border: 1px solid rgba(255,255,255,0.03);
|
||||||
color: var(--text-main);
|
color: var(--text-main);
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: 50%;
|
border-radius: 10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
transition: background 0.2s;
|
transition: transform 0.12s ease, background 0.12s ease, box-shadow 0.12s ease;
|
||||||
-webkit-app-region: no-drag; /* Buttons clickable */
|
-webkit-app-region: no-drag; /* Buttons clickable */
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-btn:hover {
|
.icon-btn:hover {
|
||||||
background: rgba(255, 255, 255, 0.1);
|
background: linear-gradient(135deg, rgba(255,255,255,0.06), rgba(255,255,255,0.02));
|
||||||
|
transform: translateY(-3px);
|
||||||
|
box-shadow: 0 10px 24px rgba(0,0,0,0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-buttons {
|
.header-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 4px;
|
gap: 8px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
-webkit-app-region: no-drag;
|
-webkit-app-region: no-drag;
|
||||||
}
|
}
|
||||||
@@ -195,8 +265,11 @@ header {
|
|||||||
height: 220px;
|
height: 220px;
|
||||||
border-radius: 24px;
|
border-radius: 24px;
|
||||||
padding: 6px; /* spacing for ring */
|
padding: 6px; /* spacing for ring */
|
||||||
background: linear-gradient(135deg, rgba(255,255,255,0.1), rgba(255,255,255,0));
|
background: linear-gradient(135deg, rgba(255,255,255,0.03), rgba(255,255,255,0.00));
|
||||||
box-shadow: 5px 5px 15px rgba(0,0,0,0.1), inset 1px 1px 2px rgba(255,255,255,0.3);
|
box-shadow: 0 12px 40px rgba(0,0,0,0.32), inset 0 1px 0 rgba(255,255,255,0.03);
|
||||||
|
border: 1px solid rgba(255,255,255,0.08);
|
||||||
|
backdrop-filter: blur(8px) saturate(120%);
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.artwork-placeholder {
|
.artwork-placeholder {
|
||||||
@@ -209,7 +282,28 @@ header {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
box-shadow: inset 0 0 20px rgba(0,0,0,0.2);
|
box-shadow: inset 0 0 30px rgba(0,0,0,0.22);
|
||||||
|
border: 1px solid rgba(255,255,255,0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* glossy inner rim for artwork */
|
||||||
|
.artwork-container::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 6px; /* follows padding to create rim */
|
||||||
|
border-radius: 20px;
|
||||||
|
pointer-events: none;
|
||||||
|
box-shadow: inset 0 1px 0 rgba(255,255,255,0.05), inset 0 -20px 40px rgba(255,255,255,0.02);
|
||||||
|
mix-blend-mode: overlay;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make artwork clickable and give subtle hover feedback */
|
||||||
|
.artwork-placeholder {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.12s ease, box-shadow 0.12s ease;
|
||||||
|
}
|
||||||
|
.artwork-placeholder:hover {
|
||||||
|
box-shadow: 0 18px 40px rgba(255, 255, 0, 0.45), inset 0 0 28px rgba(255,255,255,0.02);
|
||||||
}
|
}
|
||||||
|
|
||||||
.artwork-placeholder {
|
.artwork-placeholder {
|
||||||
@@ -313,6 +407,13 @@ header {
|
|||||||
text-shadow: 0 2px 4px rgba(0,0,0,0.2);
|
text-shadow: 0 2px 4px rgba(0,0,0,0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.now-playing {
|
||||||
|
margin: 6px 0 0;
|
||||||
|
color: var(--text-main);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
.track-info p {
|
.track-info p {
|
||||||
margin: 6px 0 0;
|
margin: 6px 0 0;
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
@@ -449,6 +550,12 @@ header {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Make slider interactive when the parent card is draggable */
|
||||||
|
.slider-container,
|
||||||
|
input[type=range] {
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
}
|
||||||
|
|
||||||
input[type=range] {
|
input[type=range] {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
@@ -541,6 +648,90 @@ input[type=range]::-webkit-slider-thumb {
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Stations grid to show cards (used for stations overlay) */
|
||||||
|
.stations-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.station-card {
|
||||||
|
list-style: none;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
background: linear-gradient(180deg, rgba(255,255,255,0.03), rgba(255,255,255,0.01));
|
||||||
|
border: 1px solid rgba(255,255,255,0.06);
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: center;
|
||||||
|
transition: transform 0.12s ease, box-shadow 0.12s ease, background 0.12s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.station-card:hover {
|
||||||
|
transform: translateY(-6px);
|
||||||
|
box-shadow: 0 18px 40px rgba(0,0,0,0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
.station-card.selected {
|
||||||
|
background: linear-gradient(135deg, #c77dff, #8b5cf6);
|
||||||
|
color: #111;
|
||||||
|
box-shadow: 0 10px 30px rgba(199,125,255,0.22);
|
||||||
|
}
|
||||||
|
|
||||||
|
.station-card-left {
|
||||||
|
width: 56px;
|
||||||
|
height: 56px;
|
||||||
|
flex: 0 0 56px;
|
||||||
|
display:flex;
|
||||||
|
align-items:center;
|
||||||
|
justify-content:center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.station-card-logo {
|
||||||
|
width: 56px;
|
||||||
|
height: 56px;
|
||||||
|
object-fit:contain;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 6px 18px rgba(0,0,0,0.35);
|
||||||
|
background: rgba(255,255,255,0.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
.station-card-fallback {
|
||||||
|
width: 56px;
|
||||||
|
height: 56px;
|
||||||
|
border-radius: 10px;
|
||||||
|
display:flex;
|
||||||
|
align-items:center;
|
||||||
|
justify-content:center;
|
||||||
|
font-weight:800;
|
||||||
|
font-size:1.2rem;
|
||||||
|
background: linear-gradient(135deg, rgba(255,255,255,0.04), rgba(255,255,255,0.02));
|
||||||
|
color: var(--text-main);
|
||||||
|
}
|
||||||
|
|
||||||
|
.station-card-body {
|
||||||
|
display:flex;
|
||||||
|
flex-direction:column;
|
||||||
|
gap:3px;
|
||||||
|
overflow:hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.station-card-title {
|
||||||
|
font-weight:700;
|
||||||
|
font-size:0.95rem;
|
||||||
|
line-height:1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.station-card-sub {
|
||||||
|
font-size:0.8rem;
|
||||||
|
color: rgba(255,255,255,0.7);
|
||||||
|
overflow:hidden;
|
||||||
|
text-overflow:ellipsis;
|
||||||
|
white-space:nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
/* Device row */
|
/* Device row */
|
||||||
.device {
|
.device {
|
||||||
padding: 12px 14px;
|
padding: 12px 14px;
|
||||||
@@ -604,3 +795,51 @@ input[type=range]::-webkit-slider-thumb {
|
|||||||
transform: scale(1.02);
|
transform: scale(1.02);
|
||||||
background: #e17c8d;
|
background: #e17c8d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Editor specific tweaks */
|
||||||
|
.modal form input {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure editor overlay input fields look consistent */
|
||||||
|
#editor-list .device {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.edit-btn, .btn.delete-btn {
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: none;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 700;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#add-station-form button.btn {
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make modal form inputs visible on dark translucent background */
|
||||||
|
.modal input,
|
||||||
|
.modal textarea,
|
||||||
|
.modal select {
|
||||||
|
background: rgba(255,255,255,0.04);
|
||||||
|
border: 1px solid rgba(255,255,255,0.12);
|
||||||
|
color: var(--text-main);
|
||||||
|
padding: 10px 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: inset 0 1px 0 rgba(255,255,255,0.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal input::placeholder,
|
||||||
|
.modal textarea::placeholder {
|
||||||
|
color: rgba(255,255,255,0.55);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
padding: 10px 14px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user