Create initial interface app
This commit is contained in:
parent
5210c0a5e6
commit
2e1cd7dc16
9 changed files with 3100 additions and 6 deletions
1
app/.gitignore
vendored
Normal file
1
app/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/target
|
||||
2762
app/Cargo.lock
generated
Normal file
2762
app/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
12
app/Cargo.toml
Normal file
12
app/Cargo.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "jukebox"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.22.1"
|
||||
maud = "0.27.0"
|
||||
serde = "1.0.228"
|
||||
serde_json = "1.0.145"
|
||||
tao = { version = "0.34.3", default-features = false, features = ["rwh_06"] }
|
||||
wry = { version = "0.53.3", default-features = false, features = ["os-webview", "protocol"] }
|
||||
19
app/src/app.js
Normal file
19
app/src/app.js
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
function removePlaying() {
|
||||
for (const el of document.getElementsByClassName("playing")) {
|
||||
el.classList.add("fadeout");
|
||||
el.addEventListener("transitionend", () => el.remove());
|
||||
}
|
||||
}
|
||||
|
||||
function showPlaying({ cover, title, artist, album }) {
|
||||
removePlaying();
|
||||
const container = document.createElement("div");
|
||||
container.classList.add("playing");
|
||||
container.innerHTML = `
|
||||
<img class="cover" src="${cover}">
|
||||
<span class="title">${title}</span>
|
||||
<span class="artist">${artist}</span>
|
||||
<span class="album">${album}</span>
|
||||
`;
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
134
app/src/main.rs
Normal file
134
app/src/main.rs
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
use std::{thread, time::Duration};
|
||||
|
||||
use base64::prelude::*;
|
||||
use maud::{Markup, PreEscaped, html};
|
||||
use serde::Serialize;
|
||||
use tao::{
|
||||
event::Event,
|
||||
event_loop::{ControlFlow, EventLoopBuilder},
|
||||
platform::unix::WindowExtUnix,
|
||||
window::{Fullscreen, WindowBuilder},
|
||||
};
|
||||
use wry::{WebViewBuilder, WebViewBuilderExtUnix};
|
||||
|
||||
static STYLESHEET: &str = include_str!("stylesheet.css");
|
||||
static SCRIPT: &str = include_str!("app.js");
|
||||
static RALEWAY: &[u8] = include_bytes!("Raleway.ttf");
|
||||
static RALEWAY_ITALIC: &[u8] = include_bytes!("Raleway-Italic.ttf");
|
||||
static ALBUM_COVER_1: &[u8] = include_bytes!("album01.jpg");
|
||||
static ALBUM_COVER_2: &[u8] = include_bytes!("album02.jpg");
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(untagged)]
|
||||
enum JukeboxEvent {
|
||||
ShowPlaying {
|
||||
cover: String,
|
||||
title: String,
|
||||
artist: String,
|
||||
album: String,
|
||||
},
|
||||
RemovePlaying,
|
||||
}
|
||||
|
||||
impl JukeboxEvent {
|
||||
fn get_invocation_name(&self) -> &'static str {
|
||||
match self {
|
||||
JukeboxEvent::ShowPlaying { .. } => "showPlaying",
|
||||
JukeboxEvent::RemovePlaying => "removePlaying",
|
||||
}
|
||||
}
|
||||
|
||||
fn get_invocation(&self) -> String {
|
||||
return format!(
|
||||
"{}({});",
|
||||
self.get_invocation_name(),
|
||||
serde_json::to_string(&self).unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let event_loop = EventLoopBuilder::<JukeboxEvent>::with_user_event().build();
|
||||
let proxy = event_loop.create_proxy();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_fullscreen(Some(Fullscreen::Borderless(None)))
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
let vbox = window.default_vbox().unwrap();
|
||||
|
||||
let webview = WebViewBuilder::new()
|
||||
.with_html(webapp())
|
||||
.with_focused(true)
|
||||
.build_gtk(vbox)
|
||||
.unwrap();
|
||||
|
||||
thread::spawn(move || {
|
||||
thread::sleep(Duration::from_secs(2));
|
||||
loop {
|
||||
proxy
|
||||
.send_event(JukeboxEvent::ShowPlaying {
|
||||
cover: build_data_url("image/jpeg", ALBUM_COVER_1),
|
||||
title: String::from("The Great Gig in the Sky"),
|
||||
artist: String::from("Pink Floyd"),
|
||||
album: String::from("The Dark Side of the Moon"),
|
||||
})
|
||||
.unwrap();
|
||||
thread::sleep(Duration::from_secs(3));
|
||||
proxy.send_event(JukeboxEvent::RemovePlaying).unwrap();
|
||||
thread::sleep(Duration::from_secs(3));
|
||||
proxy
|
||||
.send_event(JukeboxEvent::ShowPlaying {
|
||||
cover: build_data_url("image/jpeg", ALBUM_COVER_2),
|
||||
title: String::from("Boulevard of Broken Dreams"),
|
||||
artist: String::from("Green Day"),
|
||||
album: String::from("American Idiot"),
|
||||
})
|
||||
.unwrap();
|
||||
thread::sleep(Duration::from_secs(3));
|
||||
}
|
||||
});
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
match event {
|
||||
Event::UserEvent(jukebox_event) => {
|
||||
let js = jukebox_event.get_invocation();
|
||||
webview.evaluate_script(&js).unwrap();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn build_data_url(mimetype: &str, input: impl AsRef<[u8]>) -> String {
|
||||
format!(
|
||||
"data:{};base64,{}",
|
||||
mimetype,
|
||||
BASE64_STANDARD.encode(&input)
|
||||
)
|
||||
}
|
||||
|
||||
fn webapp() -> Markup {
|
||||
let b64 = build_data_url("font/ttf;charset=utf-8", RALEWAY);
|
||||
let b64_italic = build_data_url("font/ttf;charset=utf-8", RALEWAY_ITALIC);
|
||||
let fontface_defs = format!(
|
||||
r#"
|
||||
@font-face {{
|
||||
font-family: 'Raleway';
|
||||
src: url('{b64}') format('truetype');
|
||||
font-style: normal;
|
||||
}}
|
||||
@font-face {{
|
||||
font-family: 'Raleway';
|
||||
src: url('{b64_italic}') format('truetype');
|
||||
font-style: italic;
|
||||
}}
|
||||
"#
|
||||
);
|
||||
html! {
|
||||
style { (PreEscaped(fontface_defs)) }
|
||||
style { (PreEscaped(STYLESHEET)) }
|
||||
script { (PreEscaped(SCRIPT)) }
|
||||
}
|
||||
}
|
||||
71
app/src/stylesheet.css
Normal file
71
app/src/stylesheet.css
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
:root {
|
||||
background-color: #456798;
|
||||
color: white;
|
||||
font-family: 'Raleway';
|
||||
user-select: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@keyframes show-playing {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes remove-playing {
|
||||
from {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: translateX(-20px);
|
||||
}
|
||||
}
|
||||
|
||||
.playing {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
animation: show-playing 0.3s ease-out forwards;
|
||||
&.fadeout {
|
||||
animation: remove-playing 0.3s ease-out forwards;
|
||||
}
|
||||
span {
|
||||
text-shadow: 4px 4px 4px #0000001a;
|
||||
}
|
||||
.cover {
|
||||
width: 400px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 8px 8px 8px #0000003a;
|
||||
margin-bottom: 0.6rem;
|
||||
}
|
||||
.title {
|
||||
font-size: 2rem;
|
||||
font-weight: 450;
|
||||
}
|
||||
.artist {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
.album {
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue