tune up player and stations, meet backend with frontend
This commit is contained in:
parent
47168d1cbc
commit
4f36c097ac
370
Cargo.lock
generated
370
Cargo.lock
generated
@ -26,6 +26,61 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "askama"
|
||||||
|
version = "0.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b79091df18a97caea757e28cd2d5fda49c6cd4bd01ddffd7ff01ace0c0ad2c28"
|
||||||
|
dependencies = [
|
||||||
|
"askama_derive",
|
||||||
|
"askama_escape",
|
||||||
|
"humansize",
|
||||||
|
"num-traits",
|
||||||
|
"percent-encoding",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "askama_axum"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a41603f7cdbf5ac4af60760f17253eb6adf6ec5b6f14a7ed830cf687d375f163"
|
||||||
|
dependencies = [
|
||||||
|
"askama",
|
||||||
|
"axum-core",
|
||||||
|
"http",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "askama_derive"
|
||||||
|
version = "0.12.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "19fe8d6cb13c4714962c072ea496f3392015f0989b1a2847bb4b2d9effd71d83"
|
||||||
|
dependencies = [
|
||||||
|
"askama_parser",
|
||||||
|
"basic-toml",
|
||||||
|
"mime",
|
||||||
|
"mime_guess",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"serde",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "askama_escape"
|
||||||
|
version = "0.10.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "askama_parser"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "acb1161c6b64d1c3d83108213c2a2533a342ac225aabd0bda218278c2ddb00c0"
|
||||||
|
dependencies = [
|
||||||
|
"nom",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-compression"
|
name = "async-compression"
|
||||||
version = "0.4.12"
|
version = "0.4.12"
|
||||||
@ -144,12 +199,40 @@ dependencies = [
|
|||||||
"windows-targets",
|
"windows-targets",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "basic-toml"
|
||||||
|
version = "0.1.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "823388e228f614e9558c6804262db37960ec8821856535f5c3f59913140558f8"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.6.0"
|
version = "2.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
|
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "block-buffer"
|
||||||
|
version = "0.10.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bstr"
|
||||||
|
version = "1.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bufstream"
|
name = "bufstream"
|
||||||
version = "0.1.4"
|
version = "0.1.4"
|
||||||
@ -168,6 +251,15 @@ version = "1.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cpufeatures"
|
||||||
|
version = "0.2.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crc32fast"
|
name = "crc32fast"
|
||||||
version = "1.4.2"
|
version = "1.4.2"
|
||||||
@ -177,6 +269,41 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-deque"
|
||||||
|
version = "0.8.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-epoch",
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-epoch"
|
||||||
|
version = "0.9.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-utils"
|
||||||
|
version = "0.8.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crypto-common"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
"typenum",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deranged"
|
name = "deranged"
|
||||||
version = "0.3.11"
|
version = "0.3.11"
|
||||||
@ -186,11 +313,44 @@ dependencies = [
|
|||||||
"powerfmt",
|
"powerfmt",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "derive_more"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05"
|
||||||
|
dependencies = [
|
||||||
|
"derive_more-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "derive_more-impl"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"unicode-xid",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "digest"
|
||||||
|
version = "0.10.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||||
|
dependencies = [
|
||||||
|
"block-buffer",
|
||||||
|
"crypto-common",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "elnafo-radio"
|
name = "elnafo-radio"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum",
|
"axum",
|
||||||
|
"elnafo-radio-frontend",
|
||||||
|
"mime_guess",
|
||||||
"mpd",
|
"mpd",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@ -202,6 +362,18 @@ dependencies = [
|
|||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "elnafo-radio-frontend"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"askama",
|
||||||
|
"askama_axum",
|
||||||
|
"derive_more",
|
||||||
|
"ignore",
|
||||||
|
"npm_rs",
|
||||||
|
"rust-embed",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "equivalent"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
@ -272,12 +444,35 @@ dependencies = [
|
|||||||
"pin-utils",
|
"pin-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "generic-array"
|
||||||
|
version = "0.14.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||||
|
dependencies = [
|
||||||
|
"typenum",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gimli"
|
name = "gimli"
|
||||||
version = "0.31.0"
|
version = "0.31.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64"
|
checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "globset"
|
||||||
|
version = "0.4.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"bstr",
|
||||||
|
"log",
|
||||||
|
"regex-automata 0.4.7",
|
||||||
|
"regex-syntax 0.8.4",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "h2"
|
name = "h2"
|
||||||
version = "0.4.6"
|
version = "0.4.6"
|
||||||
@ -355,6 +550,15 @@ version = "1.0.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "humansize"
|
||||||
|
version = "2.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7"
|
||||||
|
dependencies = [
|
||||||
|
"libm",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper"
|
name = "hyper"
|
||||||
version = "1.4.1"
|
version = "1.4.1"
|
||||||
@ -392,6 +596,22 @@ dependencies = [
|
|||||||
"tower-service",
|
"tower-service",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ignore"
|
||||||
|
version = "0.4.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-deque",
|
||||||
|
"globset",
|
||||||
|
"log",
|
||||||
|
"memchr",
|
||||||
|
"regex-automata 0.4.7",
|
||||||
|
"same-file",
|
||||||
|
"walkdir",
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
@ -420,6 +640,12 @@ version = "0.2.158"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
|
checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libm"
|
||||||
|
version = "0.2.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.22"
|
version = "0.4.22"
|
||||||
@ -453,6 +679,22 @@ version = "0.3.17"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mime_guess"
|
||||||
|
version = "2.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
|
||||||
|
dependencies = [
|
||||||
|
"mime",
|
||||||
|
"unicase",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "minimal-lexical"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
@ -483,6 +725,25 @@ dependencies = [
|
|||||||
"bufstream",
|
"bufstream",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nom"
|
||||||
|
version = "7.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
"minimal-lexical",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "npm_rs"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a1454347ca3c562570eff8af4a09445783dc4b7ccd00853390a7f88f76037b55"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nu-ansi-term"
|
name = "nu-ansi-term"
|
||||||
version = "0.46.0"
|
version = "0.46.0"
|
||||||
@ -499,6 +760,15 @@ version = "0.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "object"
|
name = "object"
|
||||||
version = "0.36.4"
|
version = "0.36.4"
|
||||||
@ -626,6 +896,40 @@ version = "0.8.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
|
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rust-embed"
|
||||||
|
version = "8.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fa66af4a4fdd5e7ebc276f115e895611a34739a9c1c01028383d612d550953c0"
|
||||||
|
dependencies = [
|
||||||
|
"rust-embed-impl",
|
||||||
|
"rust-embed-utils",
|
||||||
|
"walkdir",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rust-embed-impl"
|
||||||
|
version = "8.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6125dbc8867951125eec87294137f4e9c2c96566e61bf72c45095a7c77761478"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"rust-embed-utils",
|
||||||
|
"syn",
|
||||||
|
"walkdir",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rust-embed-utils"
|
||||||
|
version = "8.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2e5347777e9aacb56039b0e1f28785929a8a3b709e87482e7442c72e7c12529d"
|
||||||
|
dependencies = [
|
||||||
|
"sha2",
|
||||||
|
"walkdir",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-demangle"
|
name = "rustc-demangle"
|
||||||
version = "0.1.24"
|
version = "0.1.24"
|
||||||
@ -644,6 +948,15 @@ version = "1.0.18"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
|
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "same-file"
|
||||||
|
version = "1.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.210"
|
version = "1.0.210"
|
||||||
@ -707,6 +1020,17 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sha2"
|
||||||
|
version = "0.10.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cpufeatures",
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sharded-slab"
|
name = "sharded-slab"
|
||||||
version = "0.1.7"
|
version = "0.1.7"
|
||||||
@ -993,18 +1317,55 @@ dependencies = [
|
|||||||
"tracing-log",
|
"tracing-log",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typenum"
|
||||||
|
version = "1.17.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicase"
|
||||||
|
version = "2.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89"
|
||||||
|
dependencies = [
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.13"
|
version = "1.0.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
|
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-xid"
|
||||||
|
version = "0.2.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "valuable"
|
name = "valuable"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "version_check"
|
||||||
|
version = "0.9.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "walkdir"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
||||||
|
dependencies = [
|
||||||
|
"same-file",
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
version = "0.11.0+wasi-snapshot-preview1"
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
@ -1027,6 +1388,15 @@ version = "0.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-util"
|
||||||
|
version = "0.1.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi-x86_64-pc-windows-gnu"
|
name = "winapi-x86_64-pc-windows-gnu"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
|
@ -10,7 +10,8 @@ repository = "https://vcs.elnafo.ru/L-Nafaryus/elnafo-radio"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { version = "0.7.6", features = ["http2", "macros"] }
|
axum = { version = "0.7.6", features = ["http2", "macros"] }
|
||||||
#elnafo-radio-frontend = { version = "0.1.0", path = "crates/frontend" }
|
elnafo-radio-frontend = { version = "0.1.0", path = "crates/frontend" }
|
||||||
|
mime_guess = "2.0.5"
|
||||||
mpd = "0.1.0"
|
mpd = "0.1.0"
|
||||||
serde = { version = "1.0.210", features = ["derive"] }
|
serde = { version = "1.0.210", features = ["derive"] }
|
||||||
serde_json = "1.0.128"
|
serde_json = "1.0.128"
|
||||||
@ -22,6 +23,6 @@ tracing = "0.1.40"
|
|||||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||||
|
|
||||||
|
|
||||||
#[workspace]
|
[workspace]
|
||||||
#members = ["crates/frontend"]
|
members = ["crates/frontend"]
|
||||||
#resolver = "2"
|
resolver = "2"
|
||||||
|
1
crates/frontend/.gitignore
vendored
1
crates/frontend/.gitignore
vendored
@ -4,5 +4,6 @@ node_modules/
|
|||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
*.mjs
|
*.mjs
|
||||||
*.log
|
*.log
|
||||||
|
/dist
|
||||||
|
|
||||||
openapi.json
|
openapi.json
|
||||||
|
@ -11,4 +11,5 @@ npm_rs = "1.0.0"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
askama = { version = "0.12.1", features = ["with-axum"] }
|
askama = { version = "0.12.1", features = ["with-axum"] }
|
||||||
askama_axum = "0.4.0"
|
askama_axum = "0.4.0"
|
||||||
|
derive_more = { version = "1.0.0", features = ["display"] }
|
||||||
rust-embed = "8.3.0"
|
rust-embed = "8.3.0"
|
||||||
|
@ -2,9 +2,9 @@
|
|||||||
<html lang="en" class="h-full">
|
<html lang="en" class="h-full">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<link rel="icon" href="/resources/assets/logo.svg">
|
<link rel="icon" href="/assets/vinyl.svg" type="image/svg+xml" sizes="16x16 32x32">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Materia Dev</title>
|
<title>Elnafo Radio Dev</title>
|
||||||
</head>
|
</head>
|
||||||
<body class="h-full text-zinc-200 font-sans ">
|
<body class="h-full text-zinc-200 font-sans ">
|
||||||
<div id="app" class="flex flex-col h-full"></div>
|
<div id="app" class="flex flex-col h-full"></div>
|
||||||
|
50
crates/frontend/src/assets/logo.svg
Normal file
50
crates/frontend/src/assets/logo.svg
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg fill="#000000" width="128" height="128" viewBox="0 0 187.62987 187.62987" version="1.1" id="svg1" xml:space="preserve"
|
||||||
|
xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<defs id="defs1" />
|
||||||
|
<g id="layer1" transform="translate(-254,-327.14301)">
|
||||||
|
<g id="g22" style="display:inline" transform="translate(-12.05508,278.08297)">
|
||||||
|
<path id="path29"
|
||||||
|
style="fill:#000000;fill-opacity:0.500678;stroke:none;stroke-width:1;stroke-dasharray:none;stroke-opacity:0.250793"
|
||||||
|
d="M 359.87002,49.060055 A 93.814941,93.814941 0 0 0 266.05508,142.875 93.814941,93.814941 0 0 0 359.87002,236.68995 93.814941,93.814941 0 0 0 453.68497,142.875 93.814941,93.814941 0 0 0 359.87002,49.060055 Z m 0,92.013505 a 1.801514,1.801514 0 0 1 1.80144,1.80144 1.801514,1.801514 0 0 1 -1.80144,1.80144 1.801514,1.801514 0 0 1 -1.80144,-1.80144 1.801514,1.801514 0 0 1 1.80144,-1.80144 z" />
|
||||||
|
<path id="path31"
|
||||||
|
style="fill:#000000;fill-opacity:0.500678;stroke:none;stroke-width:1.10062;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 359.87002,111.42486 a 31.450164,31.450164 0 0 0 -31.45014,31.45014 31.450164,31.450164 0 0 0 31.45014,31.45014 31.450164,31.450164 0 0 0 31.45014,-31.45014 31.450164,31.450164 0 0 0 -31.45014,-31.45014 z m 0,29.46744 a 1.9827772,1.9827772 0 0 1 1.9827,1.9827 1.9827772,1.9827772 0 0 1 -1.9827,1.9827 1.9827772,1.9827772 0 0 1 -1.9827,-1.9827 1.9827772,1.9827772 0 0 1 1.9827,-1.9827 z" />
|
||||||
|
<g id="g21"
|
||||||
|
style="fill:none;fill-opacity:0.500678;stroke:#878787;stroke-width:2;stroke-opacity:0.304089">
|
||||||
|
<path
|
||||||
|
d="m 293.5304,142.70602 c -0.005,0.0563 -0.0101,0.11265 -0.015,0.16898 m 0,0 c 0,36.64663 29.70797,66.3546 66.3546,66.35461 m 0,0 c 0.0563,-0.005 0.11267,-0.01 0.16899,-0.015 m 0,-8.90592 c -0.0563,0.005 -0.11265,0.0101 -0.16899,0.015 M 302.42131,142.875 c 0.005,-0.0563 0.01,-0.11266 0.015,-0.16898"
|
||||||
|
style="fill:none;fill-opacity:0.500678;stroke:#878787;stroke-width:2;stroke-opacity:0.304089"
|
||||||
|
id="path21" />
|
||||||
|
<path
|
||||||
|
d="m 359.87006,218.07661 c 0.0563,-0.004 0.11267,-0.009 0.16899,-0.0134 m 0,-8.84856 c -0.0563,0.005 -0.11265,0.0101 -0.16899,0.015 m -66.3546,-66.35461 c 0.005,-0.0563 0.01,-0.11266 0.015,-0.16898 m -8.84861,-4e-5 c -0.005,0.0563 -0.009,0.11265 -0.0134,0.16898 m 0,0 c 10e-6,41.5327 33.66891,75.2016 75.20161,75.20161"
|
||||||
|
style="fill:none;fill-opacity:0.500678;stroke:#878787;stroke-width:2;stroke-opacity:0.304089"
|
||||||
|
id="path20" />
|
||||||
|
<path
|
||||||
|
d="m 284.66841,142.87497 c 0.004,-0.0563 0.009,-0.11266 0.0134,-0.16898 m -8.72505,0 c -0.005,0.0563 -0.009,0.11265 -0.0134,0.16898 m 0,0 c -4e-5,46.35143 37.57523,83.9267 83.92666,83.92666 m 0,0 c 0.0563,-0.004 0.11266,-0.009 0.16899,-0.0134 m 0,0 v -1e-5 m 0,-8.72504 c -0.0563,0.005 -0.11265,0.009 -0.16899,0.0134"
|
||||||
|
style="fill:none;fill-opacity:0.500678;stroke:#878787;stroke-width:2;stroke-opacity:0.304089"
|
||||||
|
id="path19" />
|
||||||
|
<path
|
||||||
|
d="m 302.43632,142.70602 c -0.005,0.0563 -0.0101,0.11265 -0.015,0.16898 m 0,0 c 1.3e-4,31.72798 25.72071,57.44856 57.44869,57.44869 m 0,0 c 0.0563,-0.005 0.11267,-0.01 0.16899,-0.015 m 0,-9.79165 c -0.0563,0.006 -0.11265,0.0111 -0.16899,0.0165 m 0,0 c -26.32115,8e-5 -47.65867,-21.33744 -47.65859,-47.65859 m 0,0 c 0.005,-0.0563 0.0109,-0.11266 0.0165,-0.16898 m 0,0 5e-5,5e-5"
|
||||||
|
style="fill:none;fill-opacity:0.500678;stroke:#878787;stroke-width:2;stroke-opacity:0.304089"
|
||||||
|
id="path12" />
|
||||||
|
<path
|
||||||
|
d="m 359.87006,85.426307 c -0.0563,0.0049 -0.11266,0.0099 -0.16898,0.01499 m 0,0 -4e-5,-1e-6 m 0,9.791651 c 0.0563,-0.0056 0.11264,-0.01113 0.16898,-0.01654 m 0,0 c 26.32115,-7.9e-5 47.65867,21.337443 47.65859,47.658593"
|
||||||
|
style="fill:none;fill-opacity:0.500678;stroke:#878787;stroke-width:2;stroke-opacity:0.304089"
|
||||||
|
id="path8" />
|
||||||
|
<path
|
||||||
|
d="m 359.87002,76.52039 c -0.0563,0.0049 -0.11266,0.0099 -0.16898,0.01499 m 0,8.905916 c 0.0563,-0.0051 0.11265,-0.01008 0.16898,-0.01499 m 0,0 c 31.72819,-1.6e-4 57.44908,25.720504 57.44921,57.448694"
|
||||||
|
style="fill:none;fill-opacity:0.500678;stroke:#878787;stroke-width:2;stroke-opacity:0.304089"
|
||||||
|
id="path6" />
|
||||||
|
<path
|
||||||
|
d="m 359.70104,76.53538 c 0.0563,-0.0051 0.11265,-0.01006 0.16898,-0.01499 m 0,0 c 36.64664,2e-6 66.35461,29.70797 66.35461,66.35461 m 0,0 c -0.005,0.0563 -0.01,0.11266 -0.015,0.16898 m 8.84856,0 c 0.005,-0.0563 0.009,-0.11265 0.0134,-0.16898 m 0,0 c -1e-5,-41.5327 -33.66891,-75.201604 -75.20161,-75.201615 m 0,0 c -0.0563,0.0044 -0.11266,0.0089 -0.16898,0.01344 m 0,0 h 4e-5"
|
||||||
|
style="fill:none;fill-opacity:0.500678;stroke:#878787;stroke-width:2;stroke-opacity:0.304089"
|
||||||
|
id="path4" />
|
||||||
|
<path
|
||||||
|
d="m 435.07163,142.875 c -0.004,0.0563 -0.009,0.11265 -0.0134,0.16898 m 8.72505,0 c 0.005,-0.0563 0.009,-0.11265 0.0134,-0.16898 m 0,0 c 4e-5,-46.351434 -37.57523,-83.926703 -83.92666,-83.926664 m 0,0 c -0.0563,0.0044 -0.11266,0.0089 -0.16898,0.01344 m 0,8.725049 c 0.0563,-0.0045 0.11265,-0.009 0.16898,-0.01344"
|
||||||
|
style="fill:none;fill-opacity:0.500678;stroke:#979797;stroke-width:2;stroke-opacity:0.304089"
|
||||||
|
id="path2" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 5.8 KiB |
@ -27,7 +27,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
@apply pt-1 pb-1 pl-3 pr-3 sm:pt-2 sm:pb-2 sm:pl-5 sm:pr-5 rounded bg-ctp-peach border hover:bg-ctp-overlay0/20 text-ctp-blue cursor-pointer;
|
@apply pt-1 pb-1 pl-3 pr-3 sm:pt-2 sm:pb-2 sm:pl-5 sm:pr-5 rounded-md bg-ctp-peach border hover:bg-ctp-surface0/20 text-ctp-blue cursor-pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.link-button {
|
.link-button {
|
||||||
@ -51,19 +51,3 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer utilities {
|
|
||||||
.bg-grid {
|
|
||||||
|
|
||||||
background:
|
|
||||||
linear-gradient(180deg, rgba(0, 0, 0, 0) 0px, rgba(187, 65, 143, 1) 10%,
|
|
||||||
rgba(187, 65, 143, 1) 2px, rgba(0, 0, 0, 0) 0px),
|
|
||||||
linear-gradient(90deg, rgba(0, 0, 0, 0) 0px, rgba(187, 65, 143, 1) 10%,
|
|
||||||
rgba(187, 65, 143, 1) 2px, rgba(0, 0, 0, 0) 0px);
|
|
||||||
background-size: 2em 4em, 6em 2em;
|
|
||||||
transform: perspective(500px) rotateX(60deg) scale(0.5);
|
|
||||||
transform-origin: 50% 0%;
|
|
||||||
z-index: -1;
|
|
||||||
|
|
||||||
@apply absolute w-[250%] -left-[75%] h-[200%];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -30,6 +30,8 @@ export type StationInfo = {
|
|||||||
export type SongInfo = {
|
export type SongInfo = {
|
||||||
artist: string | null;
|
artist: string | null;
|
||||||
title: string | null;
|
title: string | null;
|
||||||
|
duration: number | null;
|
||||||
|
elapsed: number | null;
|
||||||
tags: Array<[string, string]>;
|
tags: Array<[string, string]>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -38,7 +40,7 @@ export const stationsInfo = <ThrowOnError extends boolean = false>(options?: Opt
|
|||||||
url: '/api/stations'
|
url: '/api/stations'
|
||||||
}); };
|
}); };
|
||||||
|
|
||||||
export const songStatus = <ThrowOnError extends boolean = false>(options?: Options<unknown, ThrowOnError>) => { return (options?.client ?? client).get<SongInfo | null, unknown, ThrowOnError>({
|
export const songStatus = <ThrowOnError extends boolean = false>(options?: Options<unknown, ThrowOnError>) => { return (options?.client ?? client).post<SongInfo | null, unknown, ThrowOnError>({
|
||||||
...options,
|
...options,
|
||||||
url: '/api/status'
|
url: '/api/status',
|
||||||
}); };
|
}); };
|
||||||
|
7
crates/frontend/src/components/DiscordIcon.vue
Normal file
7
crates/frontend/src/components/DiscordIcon.vue
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<template>
|
||||||
|
<svg class="w-6 h-6" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg" fill="currentColor" stroke="none">
|
||||||
|
<path
|
||||||
|
d="M20.992 20.163c-1.511-0.099-2.699-1.349-2.699-2.877 0-0.051 0.001-0.102 0.004-0.153l-0 0.007c-0.003-0.048-0.005-0.104-0.005-0.161 0-1.525 1.19-2.771 2.692-2.862l0.008-0c1.509 0.082 2.701 1.325 2.701 2.847 0 0.062-0.002 0.123-0.006 0.184l0-0.008c0.003 0.050 0.005 0.109 0.005 0.168 0 1.523-1.191 2.768-2.693 2.854l-0.008 0zM11.026 20.163c-1.511-0.099-2.699-1.349-2.699-2.877 0-0.051 0.001-0.102 0.004-0.153l-0 0.007c-0.003-0.048-0.005-0.104-0.005-0.161 0-1.525 1.19-2.771 2.692-2.862l0.008-0c1.509 0.082 2.701 1.325 2.701 2.847 0 0.062-0.002 0.123-0.006 0.184l0-0.008c0.003 0.048 0.005 0.104 0.005 0.161 0 1.525-1.19 2.771-2.692 2.862l-0.008 0zM26.393 6.465c-1.763-0.832-3.811-1.49-5.955-1.871l-0.149-0.022c-0.005-0.001-0.011-0.002-0.017-0.002-0.035 0-0.065 0.019-0.081 0.047l-0 0c-0.234 0.411-0.488 0.924-0.717 1.45l-0.043 0.111c-1.030-0.165-2.218-0.259-3.428-0.259s-2.398 0.094-3.557 0.275l0.129-0.017c-0.27-0.63-0.528-1.142-0.813-1.638l0.041 0.077c-0.017-0.029-0.048-0.047-0.083-0.047-0.005 0-0.011 0-0.016 0.001l0.001-0c-2.293 0.403-4.342 1.060-6.256 1.957l0.151-0.064c-0.017 0.007-0.031 0.019-0.040 0.034l-0 0c-2.854 4.041-4.562 9.069-4.562 14.496 0 0.907 0.048 1.802 0.141 2.684l-0.009-0.11c0.003 0.029 0.018 0.053 0.039 0.070l0 0c2.14 1.601 4.628 2.891 7.313 3.738l0.176 0.048c0.008 0.003 0.018 0.004 0.028 0.004 0.032 0 0.060-0.015 0.077-0.038l0-0c0.535-0.72 1.044-1.536 1.485-2.392l0.047-0.1c0.006-0.012 0.010-0.027 0.010-0.043 0-0.041-0.026-0.075-0.062-0.089l-0.001-0c-0.912-0.352-1.683-0.727-2.417-1.157l0.077 0.042c-0.029-0.017-0.048-0.048-0.048-0.083 0-0.031 0.015-0.059 0.038-0.076l0-0c0.157-0.118 0.315-0.24 0.465-0.364 0.016-0.013 0.037-0.021 0.059-0.021 0.014 0 0.027 0.003 0.038 0.008l-0.001-0c2.208 1.061 4.8 1.681 7.536 1.681s5.329-0.62 7.643-1.727l-0.107 0.046c0.012-0.006 0.025-0.009 0.040-0.009 0.022 0 0.043 0.008 0.059 0.021l-0-0c0.15 0.124 0.307 0.248 0.466 0.365 0.023 0.018 0.038 0.046 0.038 0.077 0 0.035-0.019 0.065-0.046 0.082l-0 0c-0.661 0.395-1.432 0.769-2.235 1.078l-0.105 0.036c-0.036 0.014-0.062 0.049-0.062 0.089 0 0.016 0.004 0.031 0.011 0.044l-0-0.001c0.501 0.96 1.009 1.775 1.571 2.548l-0.040-0.057c0.017 0.024 0.046 0.040 0.077 0.040 0.010 0 0.020-0.002 0.029-0.004l-0.001 0c2.865-0.892 5.358-2.182 7.566-3.832l-0.065 0.047c0.022-0.016 0.036-0.041 0.039-0.069l0-0c0.087-0.784 0.136-1.694 0.136-2.615 0-5.415-1.712-10.43-4.623-14.534l0.052 0.078c-0.008-0.016-0.022-0.029-0.038-0.036l-0-0z">
|
||||||
|
</path>
|
||||||
|
</svg>
|
||||||
|
</template>
|
@ -1,7 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" class="h-4 w-4">
|
<svg class="w-4 h-4" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="currentColor">
|
||||||
|
<g>
|
||||||
<path
|
<path
|
||||||
d="M4.61824 16.3959C5.01386 16 5.55038 15.7776 6.10981 15.7776C6.66932 15.7777 7.20584 16.0001 7.60137 16.396C7.99699 16.7919 8.21926 17.3289 8.21926 17.8888C8.21926 18.4487 7.99699 18.9857 7.60137 19.3816C7.20584 19.7776 6.66932 20 6.10981 20C5.55038 20 5.01386 19.7776 4.61824 19.3818C4.22236 18.9859 4 18.4489 4 17.8888C4 17.3288 4.22236 16.7917 4.61824 16.3959ZM20.4547 19.9436C20.0442 19.9436 19.6505 19.7803 19.3603 19.4897C19.07 19.1992 18.907 18.8051 18.9071 18.3943C18.9111 16.6164 18.5563 14.8561 17.8641 13.2187C16.8486 10.8089 15.1447 8.75272 12.9663 7.30797C10.7879 5.86328 8.23187 5.0945 5.61851 5.09759C5.20772 5.09819 4.81364 4.93521 4.52292 4.64468C4.23226 4.35423 4.06898 3.95992 4.06898 3.5488C4.06898 3.13768 4.23226 2.74346 4.52292 2.45292C4.81366 2.16238 5.20772 1.99948 5.61851 2.00001C7.77018 1.99785 9.901 2.42086 11.8889 3.24482C13.8767 4.06878 15.6824 5.27739 17.2025 6.80155C18.7253 8.32267 19.9332 10.1297 20.7564 12.1192C21.5796 14.1087 22.0022 16.241 22 18.3945C22.001 18.8051 21.8387 19.1994 21.5487 19.4901C21.2588 19.7808 20.8652 19.9439 20.4547 19.9436ZM13.6308 19.9436C13.2202 19.9436 12.8265 19.7803 12.5363 19.4897C12.246 19.1992 12.083 18.8051 12.0831 18.3943C12.0834 17.2585 11.7848 16.1426 11.2174 15.1593C10.6501 14.1756 9.83395 13.3589 8.851 12.7911C7.86813 12.2233 6.75325 11.9247 5.61835 11.925C5.06539 11.925 4.55444 11.6297 4.27792 11.1504C4.00148 10.6712 4.00148 10.0807 4.27792 9.60148C4.55444 9.12219 5.06539 8.82692 5.61835 8.82692C7.49753 8.82761 9.33491 9.38206 10.9011 10.4211C12.4674 11.4602 13.6932 12.938 14.4256 14.6698C14.9251 15.8477 15.1811 17.1147 15.178 18.3942C15.1781 18.8051 15.0152 19.1991 14.7249 19.4897C14.4347 19.7802 14.0413 19.9436 13.6308 19.9436Z"
|
d="M9 .75A.75.75 0 019.75 0h4.5c.206 0 .393.083.529.218l.001.002.002.001A.748.748 0 0115 .75v4.5a.75.75 0 01-1.5 0V2.56L7.28 8.78a.75.75 0 01-1.06-1.06l6.22-6.22H9.75A.75.75 0 019 .75z" />
|
||||||
fill="currentColor"></path>
|
<path
|
||||||
|
d="M3.25 3.5a.75.75 0 00-.75.75v7.5c0 .414.336.75.75.75h7.5a.75.75 0 00.75-.75v-4a.75.75 0 011.5 0v4A2.25 2.25 0 0110.75 14h-7.5A2.25 2.25 0 011 11.75v-7.5A2.25 2.25 0 013.25 2h4a.75.75 0 010 1.5h-4z" />
|
||||||
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
7
crates/frontend/src/components/GitIcon.vue
Normal file
7
crates/frontend/src/components/GitIcon.vue
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<template>
|
||||||
|
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
d="M30.428 14.663l-13.095-13.094c-0.35-0.349-0.833-0.565-1.367-0.565s-1.017 0.216-1.367 0.565l0-0-2.713 2.718 3.449 3.449c0.22-0.077 0.473-0.121 0.737-0.121 1.269 0 2.297 1.028 2.297 2.297 0 0.269-0.046 0.526-0.131 0.766l0.005-0.016 3.322 3.324c0.222-0.079 0.479-0.125 0.746-0.125 1.268 0 2.296 1.028 2.296 2.296s-1.028 2.296-2.296 2.296c-1.268 0-2.296-1.028-2.296-2.296 0-0.313 0.063-0.611 0.176-0.883l-0.006 0.015-3.11-3.094v8.154c0.764 0.385 1.279 1.163 1.279 2.061 0 1.27-1.030 2.3-2.3 2.3s-2.3-1.030-2.3-2.3c0-0.634 0.256-1.207 0.671-1.623l-0 0c0.211-0.211 0.462-0.382 0.741-0.502l0.015-0.006v-8.234c-0.842-0.354-1.422-1.173-1.422-2.126 0-0.32 0.065-0.624 0.183-0.901l-0.006 0.015-3.389-3.405-8.98 8.974c-0.348 0.351-0.562 0.834-0.562 1.368s0.215 1.017 0.563 1.368l13.096 13.092c0.349 0.35 0.832 0.566 1.366 0.566s1.016-0.216 1.366-0.566l13.034-13.034c0.35-0.349 0.567-0.833 0.567-1.366s-0.217-1.017-0.567-1.366l-0-0z">
|
||||||
|
</path>
|
||||||
|
</svg>
|
||||||
|
</template>
|
@ -1,48 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { defineProps, defineEmits, ref } from "vue";
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
isOpen: Boolean,
|
|
||||||
});
|
|
||||||
|
|
||||||
const emit = defineEmits(["close-modal"]);
|
|
||||||
|
|
||||||
const closeModal = (action) => {
|
|
||||||
emit("close-modal", action);
|
|
||||||
};
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div v-if="isOpen" class="modal-mask">
|
|
||||||
<div class="modal-wrapper">
|
|
||||||
<div class="modal-container">
|
|
||||||
<div class="flex mb-4">
|
|
||||||
<slot name="header"></slot>
|
|
||||||
<button class="mr-0 ml-auto button" @click="closeModal">x</button>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<slot name="content"></slot>
|
|
||||||
</div>
|
|
||||||
<div class="flex mt-4">
|
|
||||||
<slot name="footer"></slot>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.modal-mask {
|
|
||||||
@apply fixed z-[666] top-0 left-0 w-full h-full;
|
|
||||||
@apply bg-ctp-crust bg-opacity-50;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-wrapper {}
|
|
||||||
|
|
||||||
.modal-container {
|
|
||||||
@apply flex-grow w-full lg:w-[calc(100%-100px)] max-w-[1000px] ml-auto mr-auto lg:mt-20 lg:mb-20 p-5;
|
|
||||||
@apply bg-ctp-crust shadow-ctp-overlay0 border rounded border-ctp-surface1 text-ctp-text;
|
|
||||||
@apply h-screen lg:h-full;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="relative h-12">
|
<div class="h-12 mx-auto bg-ctp-peach rounded-b-xl">
|
||||||
<nav class="absolute w-full h-full flex justify-between items-center m-0 pl-3 pr-3 bg-ctp-peach">
|
<nav class="w-full h-full max-w-[1200px] flex justify-between items-center mx-auto my-0 pl-3 pr-3">
|
||||||
<div class="items-center m-0 flex">
|
<div class="items-center m-0 flex">
|
||||||
<slot name="left"></slot>
|
<slot name="left"></slot>
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,16 +6,15 @@ import { api, store } from "@";
|
|||||||
import { ref, onUpdated, onMounted } from "vue";
|
import { ref, onUpdated, onMounted } from "vue";
|
||||||
|
|
||||||
const player = store.usePlayer();
|
const player = store.usePlayer();
|
||||||
const audioRef = ref(null);
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
player.register(audioRef.value);
|
player.create();
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex sticky w-full mx-auto bottom-2 px-2">
|
<div class="flex sticky w-full mx-auto bottom-2 px-2 max-w-[1200px]">
|
||||||
<div class="flex rounded-md m-2 bg-ctp-base mx-auto w-full border-2 border-ctp-mantle">
|
<div class="flex rounded-md m-2 bg-ctp-base mx-auto w-full border-2 border-ctp-mantle">
|
||||||
<div class="flex items-center justify-center">
|
<div class="flex items-center justify-center">
|
||||||
<div class="h-20 flex rounded-l-md aspect-square items-center justify-center bg-ctp-overlay0">
|
<div class="h-20 flex rounded-l-md aspect-square items-center justify-center bg-ctp-overlay0">
|
||||||
@ -23,11 +22,12 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full flex flex-col justify-center px-4 py-4">
|
<div class="w-full flex flex-col justify-center px-4 py-4">
|
||||||
<span class="flex-grow font-bold">{{ player.station ? "ASD" : "Unknown" }}</span>
|
<span class="flex-grow font-bold">{{ player.songInfo ? player.songInfo.artist : "Unknown" }}</span>
|
||||||
<span class="text-ctp-subtext0">Location</span>
|
<span class="text-ctp-subtext0">{{ player.songInfo ? player.songInfo.title : "Unknown" }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex w-8 h-20 p-2">
|
<div class="flex w-8 h-20 p-2">
|
||||||
<input type="range" class="slider" />
|
<input type="range" class="slider" min="0" max="100" value="100"
|
||||||
|
@input="player.volume($event.target.value)" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<button @click="player.toggle()"
|
<button @click="player.toggle()"
|
||||||
@ -37,7 +37,6 @@ onMounted(() => {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<audio class="hidden" ref="audioRef" />
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -59,10 +58,10 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.slider::-webkit-progress-value {
|
.slider::-webkit-progress-value {
|
||||||
@apply bg-ctp-peach/20 w-4 rounded-full;
|
@apply bg-ctp-peach/30 w-4 rounded-full;
|
||||||
}
|
}
|
||||||
|
|
||||||
.slider::-moz-range-progress {
|
.slider::-moz-range-progress {
|
||||||
@apply bg-ctp-peach/20 w-4 rounded-full;
|
@apply bg-ctp-peach/30 w-4 rounded-full;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { api } from "@";
|
import { api } from "@";
|
||||||
import PlayIcon from "@/components/PlayIcon.vue";
|
import PlayIcon from "@/components/PlayIcon.vue";
|
||||||
|
import PauseIcon from "@/components/PauseIcon.vue";
|
||||||
import ExternalIcon from "@/components/ExternalIcon.vue";
|
import ExternalIcon from "@/components/ExternalIcon.vue";
|
||||||
import LocationIcon from "@/components/LocationIcon.vue"
|
import LocationIcon from "@/components/LocationIcon.vue"
|
||||||
import TicketIcon from "@/components/TicketIcon.vue";
|
import TicketIcon from "@/components/TicketIcon.vue";
|
||||||
@ -8,8 +9,10 @@ import VinylIcon from "@/components/VinylIcon.vue";
|
|||||||
import InfoIcon from "@/components/InfoIcon.vue";
|
import InfoIcon from "@/components/InfoIcon.vue";
|
||||||
import Tooltip from "@/components/Tooltip.vue";
|
import Tooltip from "@/components/Tooltip.vue";
|
||||||
import { store } from "@";
|
import { store } from "@";
|
||||||
|
import { onMounted, ref } from "vue";
|
||||||
|
|
||||||
const player = store.usePlayer();
|
const player = store.usePlayer();
|
||||||
|
const isPlayable = ref(null);
|
||||||
|
|
||||||
const { stationInfo } = defineProps({
|
const { stationInfo } = defineProps({
|
||||||
stationInfo: api.StationInfo,
|
stationInfo: api.StationInfo,
|
||||||
@ -17,19 +20,26 @@ const { stationInfo } = defineProps({
|
|||||||
|
|
||||||
const status = () => {
|
const status = () => {
|
||||||
if (stationInfo.status === api.StationStatus.Receive) {
|
if (stationInfo.status === api.StationStatus.Receive) {
|
||||||
return api.StationStatus.Online;
|
isPlayable.value = api.StationStatus.Online;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (stationInfo.playback === api.Playback.Stopped || stationInfo.playback === api.Playback.Paused) {
|
if (stationInfo.playback === api.Playback.Stopped || stationInfo.playback === api.Playback.Paused) {
|
||||||
return api.StationStatus.Offline;
|
isPlayable.value = api.StationStatus.Offline;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
return stationInfo.status;
|
isPlayable.value = stationInfo.status;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
status();
|
||||||
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex rounded-xl m-2 bg-ctp-base">
|
<div class="flex rounded-xl m-2 bg-ctp-base">
|
||||||
<div class="flex items-center justify-center">
|
<div class="flex items-center justify-center">
|
||||||
<div class="h-20 sm:h-36 flex rounded-l-xl aspect-square items-center justify-center bg-ctp-maroon/50">
|
<div class="h-20 sm:h-36 flex rounded-l-xl aspect-square items-center justify-center bg-ctp-peach/50">
|
||||||
<VinylIcon class="p-1" />
|
<VinylIcon class="p-1" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -54,17 +64,19 @@ const status = () => {
|
|||||||
<div class="flex items-center justify-center space-x-2">
|
<div class="flex items-center justify-center space-x-2">
|
||||||
<button
|
<button
|
||||||
class="p-1.5 sm:p-2.5 w-8 sm:w-10 inline-flex rounded-md items-center justify-center bg-ctp-mantle hover:bg-ctp-mantle/50">
|
class="p-1.5 sm:p-2.5 w-8 sm:w-10 inline-flex rounded-md items-center justify-center bg-ctp-mantle hover:bg-ctp-mantle/50">
|
||||||
<Tooltip :text="status()">
|
<Tooltip :text="stationInfo.status">
|
||||||
<InfoIcon class="text-ctp-peach" />
|
<InfoIcon class="text-ctp-peach" />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<a :href="stationInfo.url"
|
||||||
class="p-1.5 sm:p-2.5 w-8 sm:w-10 inline-flex rounded-md items-center justify-center bg-ctp-mantle hover:bg-ctp-mantle/50">
|
class="p-1.5 sm:p-2.5 w-8 sm:w-10 inline-flex rounded-md items-center justify-center bg-ctp-mantle hover:bg-ctp-mantle/50 cursor-pointer">
|
||||||
<ExternalIcon class="text-ctp-peach" />
|
<ExternalIcon class="text-ctp-peach" />
|
||||||
</button>
|
</a>
|
||||||
<button v-if="status()" @click="player.load(stationInfo, true)"
|
<button v-if="isPlayable === api.StationStatus.Online"
|
||||||
|
@click="player.playing ? (player.station?.id === stationInfo.id ? player.pause() : player.play(stationInfo)) : player.play(stationInfo)"
|
||||||
class="p-1.5 sm:p-2.5 w-8 sm:w-10 inline-flex rounded-md items-center justify-center bg-ctp-mantle hover:bg-ctp-mantle/50">
|
class="p-1.5 sm:p-2.5 w-8 sm:w-10 inline-flex rounded-md items-center justify-center bg-ctp-mantle hover:bg-ctp-mantle/50">
|
||||||
<PlayIcon class="text-ctp-peach" />
|
<PauseIcon v-if="player.station?.id === stationInfo.id && player.playing" class="text-ctp-peach" />
|
||||||
|
<PlayIcon v-else class="text-ctp-peach" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -10,31 +10,40 @@
|
|||||||
<path id="path31"
|
<path id="path31"
|
||||||
style="fill:#000000;fill-opacity:0.500678;stroke:none;stroke-width:1.10062;stroke-dasharray:none;stroke-opacity:1"
|
style="fill:#000000;fill-opacity:0.500678;stroke:none;stroke-width:1.10062;stroke-dasharray:none;stroke-opacity:1"
|
||||||
d="m 359.87002,111.42486 a 31.450164,31.450164 0 0 0 -31.45014,31.45014 31.450164,31.450164 0 0 0 31.45014,31.45014 31.450164,31.450164 0 0 0 31.45014,-31.45014 31.450164,31.450164 0 0 0 -31.45014,-31.45014 z m 0,29.46744 a 1.9827772,1.9827772 0 0 1 1.9827,1.9827 1.9827772,1.9827772 0 0 1 -1.9827,1.9827 1.9827772,1.9827772 0 0 1 -1.9827,-1.9827 1.9827772,1.9827772 0 0 1 1.9827,-1.9827 z" />
|
d="m 359.87002,111.42486 a 31.450164,31.450164 0 0 0 -31.45014,31.45014 31.450164,31.450164 0 0 0 31.45014,31.45014 31.450164,31.450164 0 0 0 31.45014,-31.45014 31.450164,31.450164 0 0 0 -31.45014,-31.45014 z m 0,29.46744 a 1.9827772,1.9827772 0 0 1 1.9827,1.9827 1.9827772,1.9827772 0 0 1 -1.9827,1.9827 1.9827772,1.9827772 0 0 1 -1.9827,-1.9827 1.9827772,1.9827772 0 0 1 1.9827,-1.9827 z" />
|
||||||
<g id="g21" style="fill:none;fill-opacity:0.500678;stroke:#676767;stroke-opacity:0.304089">
|
<g id="g21"
|
||||||
|
style="fill:none;fill-opacity:0.500678;stroke:#878787;stroke-width:2;stroke-opacity:0.304089">
|
||||||
<path
|
<path
|
||||||
d="m 293.5304,142.70602 c -0.005,0.0563 -0.0101,0.11265 -0.015,0.16898 m 0,0 c 0,36.64663 29.70797,66.3546 66.3546,66.35461 m 0,0 c 0.0563,-0.005 0.11267,-0.01 0.16899,-0.015 m 0,-8.90592 c -0.0563,0.005 -0.11265,0.0101 -0.16899,0.015 M 302.42131,142.875 c 0.005,-0.0563 0.01,-0.11266 0.015,-0.16898"
|
d="m 293.5304,142.70602 c -0.005,0.0563 -0.0101,0.11265 -0.015,0.16898 m 0,0 c 0,36.64663 29.70797,66.3546 66.3546,66.35461 m 0,0 c 0.0563,-0.005 0.11267,-0.01 0.16899,-0.015 m 0,-8.90592 c -0.0563,0.005 -0.11265,0.0101 -0.16899,0.015 M 302.42131,142.875 c 0.005,-0.0563 0.01,-0.11266 0.015,-0.16898"
|
||||||
style="fill:none;fill-opacity:0.500678;stroke:#676767;stroke-opacity:0.304089" id="path21" />
|
style="fill:none;fill-opacity:0.500678;stroke:#878787;stroke-width:2;stroke-opacity:0.304089"
|
||||||
|
id="path21" />
|
||||||
<path
|
<path
|
||||||
d="m 359.87006,218.07661 c 0.0563,-0.004 0.11267,-0.009 0.16899,-0.0134 m 0,-8.84856 c -0.0563,0.005 -0.11265,0.0101 -0.16899,0.015 m -66.3546,-66.35461 c 0.005,-0.0563 0.01,-0.11266 0.015,-0.16898 m -8.84861,-4e-5 c -0.005,0.0563 -0.009,0.11265 -0.0134,0.16898 m 0,0 c 10e-6,41.5327 33.66891,75.2016 75.20161,75.20161"
|
d="m 359.87006,218.07661 c 0.0563,-0.004 0.11267,-0.009 0.16899,-0.0134 m 0,-8.84856 c -0.0563,0.005 -0.11265,0.0101 -0.16899,0.015 m -66.3546,-66.35461 c 0.005,-0.0563 0.01,-0.11266 0.015,-0.16898 m -8.84861,-4e-5 c -0.005,0.0563 -0.009,0.11265 -0.0134,0.16898 m 0,0 c 10e-6,41.5327 33.66891,75.2016 75.20161,75.20161"
|
||||||
style="fill:none;fill-opacity:0.500678;stroke:#676767;stroke-opacity:0.304089" id="path20" />
|
style="fill:none;fill-opacity:0.500678;stroke:#878787;stroke-width:2;stroke-opacity:0.304089"
|
||||||
|
id="path20" />
|
||||||
<path
|
<path
|
||||||
d="m 284.66841,142.87497 c 0.004,-0.0563 0.009,-0.11266 0.0134,-0.16898 m -8.72505,0 c -0.005,0.0563 -0.009,0.11265 -0.0134,0.16898 m 0,0 c -4e-5,46.35143 37.57523,83.9267 83.92666,83.92666 m 0,0 c 0.0563,-0.004 0.11266,-0.009 0.16899,-0.0134 m 0,0 v -1e-5 m 0,-8.72504 c -0.0563,0.005 -0.11265,0.009 -0.16899,0.0134"
|
d="m 284.66841,142.87497 c 0.004,-0.0563 0.009,-0.11266 0.0134,-0.16898 m -8.72505,0 c -0.005,0.0563 -0.009,0.11265 -0.0134,0.16898 m 0,0 c -4e-5,46.35143 37.57523,83.9267 83.92666,83.92666 m 0,0 c 0.0563,-0.004 0.11266,-0.009 0.16899,-0.0134 m 0,0 v -1e-5 m 0,-8.72504 c -0.0563,0.005 -0.11265,0.009 -0.16899,0.0134"
|
||||||
style="fill:none;fill-opacity:0.500678;stroke:#676767;stroke-opacity:0.304089" id="path19" />
|
style="fill:none;fill-opacity:0.500678;stroke:#878787;stroke-width:2;stroke-opacity:0.304089"
|
||||||
|
id="path19" />
|
||||||
<path
|
<path
|
||||||
d="m 302.43632,142.70602 c -0.005,0.0563 -0.0101,0.11265 -0.015,0.16898 m 0,0 c 1.3e-4,31.72798 25.72071,57.44856 57.44869,57.44869 m 0,0 c 0.0563,-0.005 0.11267,-0.01 0.16899,-0.015 m 0,-9.79165 c -0.0563,0.006 -0.11265,0.0111 -0.16899,0.0165 m 0,0 c -26.32115,8e-5 -47.65867,-21.33744 -47.65859,-47.65859 m 0,0 c 0.005,-0.0563 0.0109,-0.11266 0.0165,-0.16898 m 0,0 5e-5,5e-5"
|
d="m 302.43632,142.70602 c -0.005,0.0563 -0.0101,0.11265 -0.015,0.16898 m 0,0 c 1.3e-4,31.72798 25.72071,57.44856 57.44869,57.44869 m 0,0 c 0.0563,-0.005 0.11267,-0.01 0.16899,-0.015 m 0,-9.79165 c -0.0563,0.006 -0.11265,0.0111 -0.16899,0.0165 m 0,0 c -26.32115,8e-5 -47.65867,-21.33744 -47.65859,-47.65859 m 0,0 c 0.005,-0.0563 0.0109,-0.11266 0.0165,-0.16898 m 0,0 5e-5,5e-5"
|
||||||
style="fill:none;fill-opacity:0.500678;stroke:#676767;stroke-opacity:0.304089" id="path12" />
|
style="fill:none;fill-opacity:0.500678;stroke:#878787;stroke-width:2;stroke-opacity:0.304089"
|
||||||
|
id="path12" />
|
||||||
<path
|
<path
|
||||||
d="m 359.87006,85.426307 c -0.0563,0.0049 -0.11266,0.0099 -0.16898,0.01499 m 0,0 -4e-5,-1e-6 m 0,9.791651 c 0.0563,-0.0056 0.11264,-0.01113 0.16898,-0.01654 m 0,0 c 26.32115,-7.9e-5 47.65867,21.337443 47.65859,47.658593"
|
d="m 359.87006,85.426307 c -0.0563,0.0049 -0.11266,0.0099 -0.16898,0.01499 m 0,0 -4e-5,-1e-6 m 0,9.791651 c 0.0563,-0.0056 0.11264,-0.01113 0.16898,-0.01654 m 0,0 c 26.32115,-7.9e-5 47.65867,21.337443 47.65859,47.658593"
|
||||||
style="fill:none;fill-opacity:0.500678;stroke:#676767;stroke-opacity:0.304089" id="path8" />
|
style="fill:none;fill-opacity:0.500678;stroke:#878787;stroke-width:2;stroke-opacity:0.304089"
|
||||||
|
id="path8" />
|
||||||
<path
|
<path
|
||||||
d="m 359.87002,76.52039 c -0.0563,0.0049 -0.11266,0.0099 -0.16898,0.01499 m 0,8.905916 c 0.0563,-0.0051 0.11265,-0.01008 0.16898,-0.01499 m 0,0 c 31.72819,-1.6e-4 57.44908,25.720504 57.44921,57.448694"
|
d="m 359.87002,76.52039 c -0.0563,0.0049 -0.11266,0.0099 -0.16898,0.01499 m 0,8.905916 c 0.0563,-0.0051 0.11265,-0.01008 0.16898,-0.01499 m 0,0 c 31.72819,-1.6e-4 57.44908,25.720504 57.44921,57.448694"
|
||||||
style="fill:none;fill-opacity:0.500678;stroke:#676767;stroke-opacity:0.304089" id="path6" />
|
style="fill:none;fill-opacity:0.500678;stroke:#878787;stroke-width:2;stroke-opacity:0.304089"
|
||||||
|
id="path6" />
|
||||||
<path
|
<path
|
||||||
d="m 359.70104,76.53538 c 0.0563,-0.0051 0.11265,-0.01006 0.16898,-0.01499 m 0,0 c 36.64664,2e-6 66.35461,29.70797 66.35461,66.35461 m 0,0 c -0.005,0.0563 -0.01,0.11266 -0.015,0.16898 m 8.84856,0 c 0.005,-0.0563 0.009,-0.11265 0.0134,-0.16898 m 0,0 c -1e-5,-41.5327 -33.66891,-75.201604 -75.20161,-75.201615 m 0,0 c -0.0563,0.0044 -0.11266,0.0089 -0.16898,0.01344 m 0,0 h 4e-5"
|
d="m 359.70104,76.53538 c 0.0563,-0.0051 0.11265,-0.01006 0.16898,-0.01499 m 0,0 c 36.64664,2e-6 66.35461,29.70797 66.35461,66.35461 m 0,0 c -0.005,0.0563 -0.01,0.11266 -0.015,0.16898 m 8.84856,0 c 0.005,-0.0563 0.009,-0.11265 0.0134,-0.16898 m 0,0 c -1e-5,-41.5327 -33.66891,-75.201604 -75.20161,-75.201615 m 0,0 c -0.0563,0.0044 -0.11266,0.0089 -0.16898,0.01344 m 0,0 h 4e-5"
|
||||||
style="fill:none;fill-opacity:0.500678;stroke:#676767;stroke-opacity:0.304089" id="path4" />
|
style="fill:none;fill-opacity:0.500678;stroke:#878787;stroke-width:2;stroke-opacity:0.304089"
|
||||||
|
id="path4" />
|
||||||
<path
|
<path
|
||||||
d="m 435.07163,142.875 c -0.004,0.0563 -0.009,0.11265 -0.0134,0.16898 m 8.72505,0 c 0.005,-0.0563 0.009,-0.11265 0.0134,-0.16898 m 0,0 c 4e-5,-46.351434 -37.57523,-83.926703 -83.92666,-83.926664 m 0,0 c -0.0563,0.0044 -0.11266,0.0089 -0.16898,0.01344 m 0,8.725049 c 0.0563,-0.0045 0.11265,-0.009 0.16898,-0.01344"
|
d="m 435.07163,142.875 c -0.004,0.0563 -0.009,0.11265 -0.0134,0.16898 m 8.72505,0 c 0.005,-0.0563 0.009,-0.11265 0.0134,-0.16898 m 0,0 c 4e-5,-46.351434 -37.57523,-83.926703 -83.92666,-83.926664 m 0,0 c -0.0563,0.0044 -0.11266,0.0089 -0.16898,0.01344 m 0,8.725049 c 0.0563,-0.0045 0.11265,-0.009 0.16898,-0.01344"
|
||||||
style="fill:none;fill-opacity:0.500678;stroke:#676767;stroke-opacity:0.304089" id="path2" />
|
style="fill:none;fill-opacity:0.500678;stroke:#979797;stroke-width:2;stroke-opacity:0.304089"
|
||||||
|
id="path2" />
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
|
@ -1,9 +1,24 @@
|
|||||||
export * as plugins from "@/plugins";
|
|
||||||
export { router } from "@/router";
|
export { router } from "@/router";
|
||||||
export { client } from "@/client.ts";
|
export { client } from "@/client.ts";
|
||||||
export * as api from "@/client.ts";
|
export * as api from "@/client.ts";
|
||||||
export * as schemas from "@/client/schemas.gen.ts";
|
|
||||||
export * as api_types from "@/client/types.gen.ts";
|
|
||||||
export * as store from "@/store";
|
export * as store from "@/store";
|
||||||
export * as style from "@/assets/style.css";
|
|
||||||
export * as types from "@/types";
|
export * from "@/assets/style.css";
|
||||||
|
export * from "@/assets/logo.svg";
|
||||||
|
|
||||||
|
|
||||||
|
// Used for debug
|
||||||
|
export const devel = import.meta.hot;
|
||||||
|
|
||||||
|
// Retrieve content from meta tags
|
||||||
|
export const headMeta = (metaName: string): string | null => {
|
||||||
|
let meta = document.getElementsByTagName('meta');
|
||||||
|
|
||||||
|
for (let item of meta) {
|
||||||
|
if (item.getAttribute("name") === metaName) {
|
||||||
|
return item.getAttribute("content");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
40
crates/frontend/src/lib.rs
Normal file
40
crates/frontend/src/lib.rs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
extern crate derive_more;
|
||||||
|
use askama_axum::Template;
|
||||||
|
use derive_more::Display;
|
||||||
|
use rust_embed::RustEmbed;
|
||||||
|
|
||||||
|
#[derive(RustEmbed)]
|
||||||
|
#[folder = "dist/assets/"]
|
||||||
|
pub struct Assets;
|
||||||
|
|
||||||
|
#[derive(Display, Clone)]
|
||||||
|
#[display("({}, {})", name, content)]
|
||||||
|
pub struct Meta {
|
||||||
|
pub name: String,
|
||||||
|
pub content: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "base.html")]
|
||||||
|
pub struct BaseTemplate {
|
||||||
|
pub view: String,
|
||||||
|
pub title: String,
|
||||||
|
pub meta: Option<Vec<Meta>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_render() {
|
||||||
|
println!(
|
||||||
|
"{}",
|
||||||
|
BaseTemplate {
|
||||||
|
view: String::from("home"),
|
||||||
|
title: String::from("test"),
|
||||||
|
meta: Some(vec![Meta {
|
||||||
|
name: String::from("github"),
|
||||||
|
content: String::from("https://github.com")
|
||||||
|
}])
|
||||||
|
}
|
||||||
|
.render()
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
}
|
@ -2,20 +2,16 @@ import App from "@/App.vue";
|
|||||||
|
|
||||||
import { createApp } from "vue";
|
import { createApp } from "vue";
|
||||||
import { createPinia } from "pinia";
|
import { createPinia } from "pinia";
|
||||||
import { plugins, router, client, style } from "@";
|
import { devel, router, client } from "@";
|
||||||
|
|
||||||
|
|
||||||
const debug = import.meta.hot;
|
|
||||||
|
|
||||||
client.setConfig({
|
client.setConfig({
|
||||||
baseURL: debug ? "http://localhost:54605" : "/",
|
baseURL: devel ? "http://localhost:54605" : "/",
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
createApp(App)
|
createApp(App)
|
||||||
.use(createPinia())
|
.use(createPinia())
|
||||||
.use(router)
|
.use(router)
|
||||||
.directive("click-outside", plugins.clickOutside)
|
|
||||||
.directive("tooltip", plugins.tooltip)
|
|
||||||
.mount('#app');
|
.mount('#app');
|
||||||
|
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
export const clickOutside = {
|
|
||||||
beforeMount: function(element: any, binding: any) {
|
|
||||||
element.clickOutsideEvent = function(event: any) {
|
|
||||||
if (!(element == event.target || element.contains(event.target))) {
|
|
||||||
binding.value(event);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
document.body.addEventListener("click", element.clickOutsideEvent);
|
|
||||||
document.body.addEventListener("contextmenu", element.clickOutsideEvent);
|
|
||||||
},
|
|
||||||
unmounted: function(element: any) {
|
|
||||||
document.body.removeEventListener("click", element.clickOutsideEvent);
|
|
||||||
document.body.removeEventListener("contextmenu", element.clickOutsideEvent);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const tooltip = {
|
|
||||||
beforeMount: function (element: any, binding: any) {
|
|
||||||
element.tooltip = function (event) {
|
|
||||||
let target = event.target;
|
|
||||||
if (target.offsetWidth < target.scrollWidth) {
|
|
||||||
target.setAttribute('title', binding.value?.text ? binding.value.text : event.target.textContent);
|
|
||||||
} else {
|
|
||||||
target.hasAttribute('title') && target.removeAttribute('title');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
document.body.addEventListener('mouseover', element.tooltip);
|
|
||||||
},
|
|
||||||
unmounted: function(element: any) {
|
|
||||||
document.body.removeEventListener("mouseover", element.tooltip);
|
|
||||||
}
|
|
||||||
};
|
|
@ -1 +0,0 @@
|
|||||||
export * from "@/plugins/directives";
|
|
@ -1,5 +1,5 @@
|
|||||||
import { createRouter, createWebHistory, useRoute } from "vue-router";
|
import { createRouter, createWebHistory, useRoute } from "vue-router";
|
||||||
import { store, api, schemas } from "@";
|
import { store, api } from "@";
|
||||||
|
|
||||||
|
|
||||||
export const router = createRouter({
|
export const router = createRouter({
|
||||||
|
@ -4,36 +4,57 @@ import { useRoute } from "vue-router";
|
|||||||
import axios, { CancelToken } from "axios";
|
import axios, { CancelToken } from "axios";
|
||||||
import { api } from "@";
|
import { api } from "@";
|
||||||
|
|
||||||
|
type TimerId = number;
|
||||||
|
|
||||||
export const usePlayer = defineStore("player", () => {
|
export const usePlayer = defineStore("player", () => {
|
||||||
const station = ref(null);
|
const stations: Ref<api.StationInfo[]> = ref(null);
|
||||||
const playing = ref(false);
|
const station: Ref<api.StationInfo> = ref(null);
|
||||||
const instance = ref(null);
|
const playing: Ref<boolean> = ref(false);
|
||||||
|
const instance: Ref<HTMLAudioElement> = ref(null);
|
||||||
|
const songInfo: Ref<api.SongInfo> = ref(null);
|
||||||
|
const updateTimer: Ref<TimerId> = ref(null);
|
||||||
|
|
||||||
const register = (_instance) => {
|
const create = () => {
|
||||||
instance.value = _instance;
|
let audio: HTMLAudioElement = new Audio();
|
||||||
};
|
audio.id = "audioPlayer";
|
||||||
|
audio.className = "hidden";
|
||||||
const load = (station: api.StationInfo, start: bool = false) => {
|
audio.preload = "auto";
|
||||||
station.value = station;
|
audio.onplay = async () => {
|
||||||
instance.value.src = station.value.url;
|
|
||||||
instance.value.load();
|
|
||||||
if (start) {
|
|
||||||
play();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const play = () => {
|
|
||||||
if (!instance.value.src)
|
|
||||||
return;
|
|
||||||
instance.value.play();
|
|
||||||
playing.value = true;
|
playing.value = true;
|
||||||
|
await update();
|
||||||
|
};
|
||||||
|
audio.onpause = () => {
|
||||||
|
playing.value = false;
|
||||||
|
clearTimeout(updateTimer.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
document.body.appendChild(audio);
|
||||||
|
instance.value = audio;
|
||||||
|
};
|
||||||
|
|
||||||
|
const update = async () => {
|
||||||
|
await refreshSongInfo();
|
||||||
|
clearTimeout(updateTimer.value);
|
||||||
|
updateTimer.value = setTimeout(update, (songInfo.value.duration - songInfo.value.elapsed) * 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const play = (_station: api.StationInfo | null = null) => {
|
||||||
|
if (_station) {
|
||||||
|
station.value = _station;
|
||||||
|
instance.value.src = _station.url;
|
||||||
|
}
|
||||||
|
if (!instance.value.src){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
instance.value.load();
|
||||||
|
instance.value.play();
|
||||||
};
|
};
|
||||||
|
|
||||||
const pause = () => {
|
const pause = () => {
|
||||||
if (!instance.value.src)
|
if (!instance.value.src) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
instance.value.pause();
|
instance.value.pause();
|
||||||
playing.value = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggle = () => {
|
const toggle = () => {
|
||||||
@ -44,6 +65,39 @@ export const usePlayer = defineStore("player", () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const volume = (value: number) => {
|
||||||
|
instance.value.volume = Math.min(Math.max(value * 0.01, 0), 1);
|
||||||
|
};
|
||||||
|
|
||||||
return { station, playing, load, instance, register, play, pause, toggle };
|
const volumeEvent = (event: Event) => {
|
||||||
|
volume(event.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const refreshStations = async (error: Ref<object | null> | null = null) => {
|
||||||
|
await api.stationsInfo({ throwOnError: true })
|
||||||
|
.then(async res => {
|
||||||
|
stations.value = res.data;
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
if (error) {
|
||||||
|
error.value = "Failed to retrieve stations";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const refreshSongInfo = async (error: Ref<object | null> | null = null) => {
|
||||||
|
await api.songStatus ({ body: { id: station.value.id }, throwOnError: true })
|
||||||
|
.then(async res => {
|
||||||
|
songInfo.value = res.data;
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
songInfo.value = null;
|
||||||
|
if (error) {
|
||||||
|
error.value = "Failed to retrieve song info";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
return { stations, station, playing, create, play, pause, toggle, volume, volumeEvent, songInfo, refreshStations, refreshSongInfo };
|
||||||
});
|
});
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
import { api_types } from "@";
|
|
||||||
import { CancelToken } from "axios";
|
|
||||||
|
|
||||||
export type Error = object;
|
|
||||||
|
|
||||||
export type RepositoryItemMeta = {
|
|
||||||
selected: bool;
|
|
||||||
clickCount: number;
|
|
||||||
clickTimer: (object | null);
|
|
||||||
};
|
|
||||||
|
|
||||||
export type RepositoryItemType = "directory" | "file";
|
|
||||||
|
|
||||||
export type RepositoryItemInfo = {
|
|
||||||
info: (api_types.DirectoryInfo | api_types.FileInfo);
|
|
||||||
meta: RepositoryItemMeta;
|
|
||||||
type: RepositoryItemType;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type RepositoryContent = RepositoryItemInfo[];
|
|
||||||
|
|
||||||
export type UploadFileStatus = "success" | "fail" | "transfer" | "idle";
|
|
||||||
|
|
||||||
export type UploadFile = {
|
|
||||||
content: File;
|
|
||||||
status: UploadFileStatus;
|
|
||||||
progress: number;
|
|
||||||
cancel: CancelToken | null;
|
|
||||||
error: string | null;
|
|
||||||
};
|
|
@ -1,22 +1,45 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from "vue";
|
import { ref, onMounted } from "vue";
|
||||||
import { router, api, store } from "@";
|
import { headMeta, devel } from "@";
|
||||||
import NavBar from "@/components/NavBar.vue";
|
import NavBar from "@/components/NavBar.vue";
|
||||||
import DocumentationIcon from "@/components/DocumentationIcon.vue";
|
import DocumentationIcon from "@/components/DocumentationIcon.vue";
|
||||||
|
import DiscordIcon from "@/components/DiscordIcon.vue";
|
||||||
|
import GitIcon from "@/components/GitIcon.vue";
|
||||||
|
import VinylIcon from "@/components/VinylIcon.vue";
|
||||||
import Player from "@/components/Player.vue";
|
import Player from "@/components/Player.vue";
|
||||||
import Error from "@/components/Error.vue";
|
import Error from "@/components/Error.vue";
|
||||||
|
|
||||||
|
|
||||||
|
const title = ref(null);
|
||||||
|
const author = ref(null);
|
||||||
|
const discord = ref(null);
|
||||||
|
const git = ref(null);
|
||||||
|
const documentation = ref(null);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
title.value = document.getElementsByTagName("title")[0].text;
|
||||||
|
author.value = devel ? "L-Nafaryus" : headMeta("author");
|
||||||
|
discord.value = devel ? "http://example.com" : headMeta("discord");
|
||||||
|
git.value = devel ? "http://example.com" : headMeta("git");
|
||||||
|
documentation.value = devel ? "http://example.com" : headMeta("documentation");
|
||||||
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<VinylIcon class="absolute h-full mx-auto w-full -z-10 opacity-15" />
|
||||||
<div class="flex-grow">
|
<div class="flex-grow">
|
||||||
<NavBar>
|
<NavBar>
|
||||||
<template #left>
|
<template #left>
|
||||||
<RouterLink class="link-button" to="/">Home</RouterLink>
|
<RouterLink class="link-button font-bold" to="/">{{ title }}</RouterLink>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #right>
|
<template #right>
|
||||||
<RouterLink class="link-button" to="/auth/signin">Sign In</RouterLink>
|
<a v-if="discord" :href="discord"
|
||||||
|
class="flex justify-center items-center space-x-1 text-ctp-surface0 link-button font-bold">
|
||||||
|
<DiscordIcon />
|
||||||
|
<span class="hidden sm:inline">Discord</span>
|
||||||
|
</a>
|
||||||
</template>
|
</template>
|
||||||
</NavBar>
|
</NavBar>
|
||||||
|
|
||||||
@ -27,14 +50,22 @@ import Error from "@/components/Error.vue";
|
|||||||
|
|
||||||
<Player />
|
<Player />
|
||||||
|
|
||||||
<footer class="flex justify-between pb-2 pt-2 pl-5 pr-5 bg-ctp-mantle">
|
<footer class="pb-2 pt-2 pl-5 pr-5 bg-ctp-mantle">
|
||||||
<a href="https://vcs.elnafo.ru/L-Nafaryus" class="text-ctp-peach">Made by L-Nafaryus, 2024</a>
|
<div class="flex justify-between max-w-[1200px] mx-auto">
|
||||||
<div>
|
<span v-if="author" class="text-ctp-peach font-bold items-center flex">Made by {{ author }}</span>
|
||||||
|
<div class="flex space-x-1">
|
||||||
|
<a v-if="documentation" :href="documentation"
|
||||||
|
class="flex justify-center items-center space-x-1 text-ctp-peach link-button font-bold bg-ctp-mantle">
|
||||||
|
<DocumentationIcon />
|
||||||
|
<span class="hidden sm:inline">Docs</span>
|
||||||
|
</a>
|
||||||
|
<a v-if="git" :href="git"
|
||||||
|
class="flex justify-center items-center space-x-1 text-ctp-peach link-button font-bold bg-ctp-mantle">
|
||||||
|
<GitIcon />
|
||||||
|
<span class="hidden sm:inline">Git</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<a href="/api/docs" class="flex justify-center items-center space-x-1 text-ctp-peach">
|
</div>
|
||||||
<DocumentationIcon />
|
|
||||||
<span>API</span>
|
|
||||||
</a>
|
|
||||||
</footer>
|
</footer>
|
||||||
</template>
|
</template>
|
||||||
|
@ -5,41 +5,20 @@ import ExternalIcon from "@/components/ExternalIcon.vue";
|
|||||||
import LocationIcon from "@/components/LocationIcon.vue";
|
import LocationIcon from "@/components/LocationIcon.vue";
|
||||||
import Error from "@/components/Error.vue";
|
import Error from "@/components/Error.vue";
|
||||||
import Station from "@/components/Station.vue";
|
import Station from "@/components/Station.vue";
|
||||||
import { api } from "@";
|
import { api, store } from "@";
|
||||||
import { ref, onMounted, onUnmounted } from "vue";
|
import { ref, onMounted, onUnmounted } from "vue";
|
||||||
|
|
||||||
|
const player = store.usePlayer();
|
||||||
const error = ref(null);
|
const error = ref(null);
|
||||||
const stations = ref(null);
|
|
||||||
const update = ref(null);
|
|
||||||
|
|
||||||
const stationsInfo = async () => {
|
|
||||||
error.value = null;
|
|
||||||
|
|
||||||
await api.stationsInfo({ throwOnError: true })
|
|
||||||
.then(async stationsInfo => {
|
|
||||||
stations.value = stationsInfo.data;
|
|
||||||
console.log(stations.value);
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
stations.value = null;
|
|
||||||
error.value = "Failed to get stations list";
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await stationsInfo();
|
await player.refreshStations(error);
|
||||||
update.value = setInterval(stationsInfo, 10000);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
clearInterval(update.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Base>
|
<Base>
|
||||||
<Error :value="error" />
|
<Error :value="error" />
|
||||||
<Station :stationInfo="station" v-for="station in stations" />
|
<Station :stationInfo="station" v-for="station in player.stations" />
|
||||||
</Base>
|
</Base>
|
||||||
</template>
|
</template>
|
||||||
|
@ -2,13 +2,16 @@
|
|||||||
<html lang="en" class="h-full">
|
<html lang="en" class="h-full">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<link rel="icon" href="/resources/assets/logo.svg">
|
<link rel="icon" type="image/svg+xml" sizes="16x16 32x32" href="/assets/logo.svg">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Materia</title>
|
<title>{{ title }}</title>
|
||||||
<script type="module" crossorigin src="/resources/assets/index.js"></script>
|
{% for item in meta.as_deref().unwrap_or([]) %}
|
||||||
<link rel="stylesheet" crossorigin href="/resources/assets/index.css">
|
<meta name="{{ item.name }}" content="{{ item.content }}">
|
||||||
|
{% endfor %}
|
||||||
|
<script type="module" crossorigin src="/assets/index.js"></script>
|
||||||
|
<link rel="stylesheet" crossorigin href="/assets/index.css">
|
||||||
</head>
|
</head>
|
||||||
<body class="h-full bg-zinc-900 text-zinc-200 font-sans">
|
<body class="h-full text-zinc-200 font-sans">
|
||||||
<div id="{{ view }}" class="flex flex-col h-full"></div>
|
<div id="{{ view }}" class="flex flex-col h-full"></div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -13,9 +13,9 @@ export default defineConfig({
|
|||||||
//outDir: path.resolve(__dirname, "./frontend"),
|
//outDir: path.resolve(__dirname, "./frontend"),
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
output: {
|
output: {
|
||||||
entryFileNames: "resources/assets/[name].js",
|
entryFileNames: "assets/[name].js",
|
||||||
assetFileNames: "resources/assets/[name][extname]",
|
assetFileNames: "assets/[name][extname]",
|
||||||
chunkFileNames: "resources/assets/[name].js"
|
chunkFileNames: "assets/[name].js"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
29
src/api.rs
29
src/api.rs
@ -1,8 +1,11 @@
|
|||||||
use axum::{
|
use axum::{
|
||||||
extract::DefaultBodyLimit,
|
extract::DefaultBodyLimit,
|
||||||
extract::State,
|
extract::State,
|
||||||
http::{header::*, Method, StatusCode},
|
http::{
|
||||||
response::IntoResponse,
|
header::{self, *},
|
||||||
|
Method, StatusCode, Uri,
|
||||||
|
},
|
||||||
|
response::{IntoResponse, Response},
|
||||||
routing::{get, post},
|
routing::{get, post},
|
||||||
Json, Router,
|
Json, Router,
|
||||||
};
|
};
|
||||||
@ -31,7 +34,7 @@ pub fn routes(state: Arc<Context>) -> Router {
|
|||||||
Router::new()
|
Router::new()
|
||||||
.route("/healthcheck", get(healthcheck))
|
.route("/healthcheck", get(healthcheck))
|
||||||
.route("/stations", get(stations))
|
.route("/stations", get(stations))
|
||||||
.route("/status", get(status))
|
.route("/status", post(status))
|
||||||
.layer(cors)
|
.layer(cors)
|
||||||
.fallback(fallback)
|
.fallback(fallback)
|
||||||
.with_state(state)
|
.with_state(state)
|
||||||
@ -48,6 +51,8 @@ pub enum Playback {
|
|||||||
pub struct SongInfo {
|
pub struct SongInfo {
|
||||||
pub artist: Option<String>,
|
pub artist: Option<String>,
|
||||||
pub title: Option<String>,
|
pub title: Option<String>,
|
||||||
|
pub duration: Option<u64>,
|
||||||
|
pub elapsed: Option<u64>,
|
||||||
pub tags: Vec<(String, String)>,
|
pub tags: Vec<(String, String)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,14 +150,22 @@ pub async fn status(
|
|||||||
|
|
||||||
if let Ok(mut client) = connection {
|
if let Ok(mut client) = connection {
|
||||||
if let Ok(Some(current)) = client.currentsong() {
|
if let Ok(Some(current)) = client.currentsong() {
|
||||||
return (
|
let mut info = SongInfo {
|
||||||
StatusCode::OK,
|
|
||||||
Json(Some(SongInfo {
|
|
||||||
artist: current.artist,
|
artist: current.artist,
|
||||||
title: current.title,
|
title: current.title,
|
||||||
|
duration: None,
|
||||||
|
elapsed: None,
|
||||||
tags: current.tags,
|
tags: current.tags,
|
||||||
})),
|
};
|
||||||
);
|
|
||||||
|
if let Ok(status) = client.status() {
|
||||||
|
if let Some(time) = status.time {
|
||||||
|
info.duration = Some(time.1.as_secs());
|
||||||
|
info.elapsed = Some(time.0.as_secs());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (StatusCode::OK, Json(Some(info)));
|
||||||
} else {
|
} else {
|
||||||
return (StatusCode::OK, Json(None));
|
return (StatusCode::OK, Json(None));
|
||||||
}
|
}
|
||||||
|
@ -29,8 +29,15 @@ pub struct Server {
|
|||||||
pub port: i32,
|
pub port: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Base {
|
||||||
|
pub title: String,
|
||||||
|
pub meta: Option<Vec<(String, String)>>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
|
pub base: Base,
|
||||||
pub server: Server,
|
pub server: Server,
|
||||||
pub stations: Option<Vec<Station>>,
|
pub stations: Option<Vec<Station>>,
|
||||||
}
|
}
|
||||||
@ -65,6 +72,10 @@ impl Config {
|
|||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Config {
|
Config {
|
||||||
|
base: Base {
|
||||||
|
title: String::from("Elnafo Radio"),
|
||||||
|
meta: None,
|
||||||
|
},
|
||||||
server: Server {
|
server: Server {
|
||||||
address: String::from("127.0.0.1"),
|
address: String::from("127.0.0.1"),
|
||||||
port: 54605,
|
port: 54605,
|
||||||
|
89
src/main.rs
89
src/main.rs
@ -1,7 +1,16 @@
|
|||||||
pub mod api;
|
pub mod api;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
|
|
||||||
use axum::{http::Uri, response::IntoResponse, routing::get, Router};
|
use axum::{
|
||||||
|
extract::State,
|
||||||
|
http::{
|
||||||
|
header::{self, *},
|
||||||
|
Method, StatusCode, Uri,
|
||||||
|
},
|
||||||
|
response::{IntoResponse, Response},
|
||||||
|
routing::get,
|
||||||
|
Router,
|
||||||
|
};
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tower_http::trace::{self, TraceLayer};
|
use tower_http::trace::{self, TraceLayer};
|
||||||
@ -35,7 +44,12 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
config: config.clone(),
|
config: config.clone(),
|
||||||
});
|
});
|
||||||
|
|
||||||
let app = Router::new().nest("/api", api::routes(state)).layer(
|
let app = Router::new()
|
||||||
|
.route("/", get(frontend_handler))
|
||||||
|
.with_state(state.clone())
|
||||||
|
.nest("/api", api::routes(state))
|
||||||
|
.route("/assets/*file", get(assets))
|
||||||
|
.layer(
|
||||||
TraceLayer::new_for_http()
|
TraceLayer::new_for_http()
|
||||||
.make_span_with(trace::DefaultMakeSpan::new().level(Level::INFO))
|
.make_span_with(trace::DefaultMakeSpan::new().level(Level::INFO))
|
||||||
.on_response(trace::DefaultOnResponse::new().level(Level::INFO)),
|
.on_response(trace::DefaultOnResponse::new().level(Level::INFO)),
|
||||||
@ -44,11 +58,76 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
let address: SocketAddr =
|
let address: SocketAddr =
|
||||||
format!("{}:{}", config.server.address, config.server.port).parse()?;
|
format!("{}:{}", config.server.address, config.server.port).parse()?;
|
||||||
|
|
||||||
let lister = tokio::net::TcpListener::bind(&address).await?;
|
let listener = tokio::net::TcpListener::bind(&address).await?;
|
||||||
|
|
||||||
println!("Listening on {}", address);
|
println!("Listening on http://{}", address);
|
||||||
|
|
||||||
axum::serve(lister, app.into_make_service()).await?;
|
axum::serve(listener, app.into_make_service()).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn assets(uri: Uri) -> Result<impl IntoResponse, ResourceError> {
|
||||||
|
let path = uri.path().trim_start_matches("/assets/").to_string();
|
||||||
|
|
||||||
|
match elnafo_radio_frontend::Assets::get(&path) {
|
||||||
|
Some(content) => {
|
||||||
|
let mime = mime_guess::from_path(path).first_or_octet_stream();
|
||||||
|
Ok(([(header::CONTENT_TYPE, mime.as_ref())], content.data).into_response())
|
||||||
|
}
|
||||||
|
None => Err(ResourceError::NotFound),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn frontend_handler(State(state): State<Arc<Context>>, _: Uri) -> impl IntoResponse {
|
||||||
|
use elnafo_radio_frontend::Meta;
|
||||||
|
|
||||||
|
elnafo_radio_frontend::BaseTemplate {
|
||||||
|
view: String::from("app"),
|
||||||
|
title: state.config.base.title.clone(),
|
||||||
|
meta: if let Some(meta) = &state.config.base.meta {
|
||||||
|
Some(
|
||||||
|
meta.iter()
|
||||||
|
.map(|kv| Meta {
|
||||||
|
name: kv.0.clone(),
|
||||||
|
content: kv.1.clone(),
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ResourceError {
|
||||||
|
NotFound,
|
||||||
|
NotExists,
|
||||||
|
BadFormat,
|
||||||
|
BadContent,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for ResourceError {}
|
||||||
|
|
||||||
|
impl std::fmt::Display for ResourceError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::NotFound | Self::NotExists => write!(f, "Resource was not found"),
|
||||||
|
Self::BadFormat => write!(f, "Cannot determine file format"),
|
||||||
|
Self::BadContent => write!(f, "Failed to read a file content"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoResponse for ResourceError {
|
||||||
|
fn into_response(self) -> Response {
|
||||||
|
let status = match self {
|
||||||
|
Self::NotFound => StatusCode::NOT_FOUND,
|
||||||
|
Self::NotExists => StatusCode::NO_CONTENT,
|
||||||
|
Self::BadFormat | Self::BadContent => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
};
|
||||||
|
|
||||||
|
(status, format!("{}", self)).into_response()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user