feat(ui): comment out gpui ui code and improve iced ui logic

This commit is contained in:
uttarayan21
2025-12-09 23:46:00 +05:30
parent 73fcf9bad1
commit d75a2fb7e4
2 changed files with 320 additions and 310 deletions

View File

@@ -1,262 +1,262 @@
use ::tap::*; // use ::tap::*;
//
use std::{collections::BTreeMap, sync::Arc}; // use std::{collections::BTreeMap, sync::Arc};
//
use gpui::{ // use gpui::{
App, Application, Bounds, ClickEvent, Context, ImageId, ImageSource, RenderImage, Resource, // App, Application, Bounds, ClickEvent, Context, ImageId, ImageSource, RenderImage, Resource,
SharedString, Window, WindowBounds, WindowOptions, actions, div, prelude::*, px, rgb, size, // SharedString, Window, WindowBounds, WindowOptions, actions, div, prelude::*, px, rgb, size,
}; // };
//
#[derive(Clone, Debug)] // #[derive(Clone, Debug)]
pub struct AppState { // pub struct AppState {
pub title: SharedString, // pub title: SharedString,
pub items: BTreeMap<SharedString, Item>, // pub items: BTreeMap<SharedString, Item>,
pub item_ids: BTreeMap<usize, SharedString>, // pub item_ids: BTreeMap<usize, SharedString>,
pub current_item: Option<SharedString>, // pub current_item: Option<SharedString>,
pub errors: Vec<String>, // pub errors: Vec<String>,
pub jellyfin_client: api::JellyfinClient, // pub jellyfin_client: api::JellyfinClient,
} // }
//
#[derive(Clone, Debug)] // #[derive(Clone, Debug)]
pub struct Item { // pub struct Item {
pub id: SharedString, // pub id: SharedString,
pub name: SharedString, // pub name: SharedString,
pub item_type: SharedString, // pub item_type: SharedString,
pub media_type: SharedString, // pub media_type: SharedString,
} // }
//
impl Render for AppState { // impl Render for AppState {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { // fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
div() // div()
.flex() // .flex()
.flex_col() // .flex_col()
.size_full() // .size_full()
.justify_center() // .justify_center()
.text_color(rgb(0xffffff)) // .text_color(rgb(0xffffff))
.child(Self::header()) // .child(Self::header())
.child(Self::body(self, window, cx)) // .child(Self::body(self, window, cx))
.child(Self::footer()) // .child(Self::footer())
} // }
} // }
//
actions!(jello_actions, [OpenItem, OnLoadItem, MouseDownEvent]); // actions!(jello_actions, [OpenItem, OnLoadItem, MouseDownEvent]);
//
impl AppState { // impl AppState {
fn new(title: impl AsRef<str>, jellyfin_client: api::JellyfinClient) -> Self { // fn new(title: impl AsRef<str>, jellyfin_client: api::JellyfinClient) -> Self {
AppState { // AppState {
title: SharedString::new(title.as_ref()), // title: SharedString::new(title.as_ref()),
items: BTreeMap::new(), // items: BTreeMap::new(),
item_ids: BTreeMap::new(), // item_ids: BTreeMap::new(),
current_item: None, // current_item: None,
errors: Vec::new(), // errors: Vec::new(),
jellyfin_client, // jellyfin_client,
} // }
} // }
//
// fn on_mouse_down( // // fn on_mouse_down(
// &mut self, // // &mut self,
// event: &MouseDownEvent, // // event: &MouseDownEvent,
// window: &mut Window, // // window: &mut Window,
// cx: &mut Context<Self>, // // cx: &mut Context<Self>,
// ) { // // ) {
// // Handle mouse down event // // // Handle mouse down event
// } // // }
//
fn load_item(id: usize) -> impl Fn(&mut Self, &ClickEvent, &mut Window, &mut Context<Self>) { // fn load_item(id: usize) -> impl Fn(&mut Self, &ClickEvent, &mut Window, &mut Context<Self>) {
move |state: &mut Self, event: &ClickEvent, window: &mut Window, cx: &mut Context<Self>| { // move |state: &mut Self, event: &ClickEvent, window: &mut Window, cx: &mut Context<Self>| {
let item_id = id; // let item_id = id;
cx.spawn(async move |entity, app| { // cx.spawn(async move |entity, app| {
tracing::info!("Loading item with id: {}", item_id); // tracing::info!("Loading item with id: {}", item_id);
}); // });
} // }
} // }
//
fn hover_item(id: usize) -> impl Fn(&mut Self, &bool, &mut Window, &mut Context<Self>) { // fn hover_item(id: usize) -> impl Fn(&mut Self, &bool, &mut Window, &mut Context<Self>) {
move |state: &mut Self, item: &bool, window: &mut Window, cx: &mut Context<Self>| { // move |state: &mut Self, item: &bool, window: &mut Window, cx: &mut Context<Self>| {
dbg!("Hovering over item: {:?}", id); // dbg!("Hovering over item: {:?}", id);
} // }
} // }
//
fn header() -> impl IntoElement { // fn header() -> impl IntoElement {
div() // div()
.flex() // .flex()
.flex_row() // .flex_row()
.w_full() // .w_full()
.justify_end() // .justify_end()
.h_20() // .h_20()
.border_10() // .border_10()
.bg(rgb(0x333333)) // .bg(rgb(0x333333))
.child(Self::button("Refresh")) // .child(Self::button("Refresh"))
} // }
//
fn footer() -> impl IntoElement { // fn footer() -> impl IntoElement {
div().flex().flex_row().w_full().h_20().bg(rgb(0x333333)) // div().flex().flex_row().w_full().h_20().bg(rgb(0x333333))
} // }
//
fn body(&mut self, window: &mut Window, cx: &mut Context<AppState>) -> impl IntoElement { // fn body(&mut self, window: &mut Window, cx: &mut Context<AppState>) -> impl IntoElement {
div() // div()
.flex() // .flex()
.flex_row() // .flex_row()
.size_full() // .size_full()
.child(Self::content(self, window, cx)) // .child(Self::content(self, window, cx))
.child(Self::sidebar(self, window, cx)) // .child(Self::sidebar(self, window, cx))
} // }
//
fn button(label: &str) -> impl IntoElement { // fn button(label: &str) -> impl IntoElement {
div() // div()
.flex() // .flex()
.justify_center() // .justify_center()
.items_center() // .items_center()
.bg(rgb(0xff00ff)) // .bg(rgb(0xff00ff))
.text_color(rgb(0xffffff)) // .text_color(rgb(0xffffff))
.border_5() // .border_5()
.rounded_lg() // .rounded_lg()
.child(label.to_string()) // .child(label.to_string())
} // }
//
fn content(&mut self, window: &mut Window, cx: &mut Context<AppState>) -> impl IntoElement { // fn content(&mut self, window: &mut Window, cx: &mut Context<AppState>) -> impl IntoElement {
div() // div()
.debug_below() // .debug_below()
.w_3_4() // .w_3_4()
// .flex() // // .flex()
// .flex_wrap() // // .flex_wrap()
.bg(rgb(0x111111)) // .bg(rgb(0x111111))
.justify_start() // .justify_start()
.items_start() // .items_start()
.overflow_hidden() // .overflow_hidden()
.child( // .child(
div() // div()
.size_full() // .size_full()
.flex() // .flex()
.flex_wrap() // .flex_wrap()
.justify_start() // .justify_start()
.items_start() // .items_start()
.content_start() // .content_start()
.gap_y_10() // .gap_y_10()
.gap_x_10() // .gap_x_10()
.border_t_10() // .border_t_10()
.p_5() // .p_5()
.child(Self::card(cx, 1)) // .child(Self::card(cx, 1))
.child(Self::card(cx, 2)) // .child(Self::card(cx, 2))
.child(Self::card(cx, 3)) // .child(Self::card(cx, 3))
.child(Self::card(cx, 4)) // .child(Self::card(cx, 4))
.child(Self::card(cx, 5)) // .child(Self::card(cx, 5))
.child(Self::card(cx, 6)) // .child(Self::card(cx, 6))
.child(Self::card(cx, 7)) // .child(Self::card(cx, 7))
.child(Self::card(cx, 8)) // .child(Self::card(cx, 8))
.child(Self::card(cx, 9)), // .child(Self::card(cx, 9)),
) // )
} // }
//
fn sidebar(&mut self, window: &mut Window, cx: &mut Context<AppState>) -> impl IntoElement { // fn sidebar(&mut self, window: &mut Window, cx: &mut Context<AppState>) -> impl IntoElement {
div() // div()
.flex() // .flex()
.flex_col() // .flex_col()
.w_1_4() // .w_1_4()
.min_w_1_6() // .min_w_1_6()
.bg(rgb(0x222222)) // .bg(rgb(0x222222))
.child(div().size_full().bg(gpui::yellow())) // .child(div().size_full().bg(gpui::yellow()))
} // }
//
fn card(cx: &mut Context<AppState>, number: usize) -> impl IntoElement { // fn card(cx: &mut Context<AppState>, number: usize) -> impl IntoElement {
div() // div()
.id(number) // .id(number)
.on_click(cx.listener(Self::load_item(number))) // .on_click(cx.listener(Self::load_item(number)))
.on_hover(cx.listener(Self::hover_item(number))) // .on_hover(cx.listener(Self::hover_item(number)))
.flex() // .flex()
.flex_col() // .flex_col()
.w_48() // .w_48()
.h_64() // .h_64()
.p_10() // .p_10()
.bg(rgb(0xff00ff)) // .bg(rgb(0xff00ff))
.rounded_lg() // .rounded_lg()
} // }
} // }
//
pub fn ui(jellyfin_client: api::JellyfinClient) { // pub fn ui(jellyfin_client: api::JellyfinClient) {
Application::new().run(|cx: &mut App| { // Application::new().run(|cx: &mut App| {
let bounds = Bounds::centered(None, size(px(500.0), px(500.0)), cx); // let bounds = Bounds::centered(None, size(px(500.0), px(500.0)), cx);
cx.open_window( // cx.open_window(
WindowOptions { // WindowOptions {
window_bounds: Some(WindowBounds::Windowed(bounds)), // window_bounds: Some(WindowBounds::Windowed(bounds)),
..Default::default() // ..Default::default()
}, // },
|_, cx| cx.new(|_| AppState::new("Jello Media Browser", jellyfin_client)), // |_, cx| cx.new(|_| AppState::new("Jello Media Browser", jellyfin_client)),
) // )
.expect("Failed to open window"); // .expect("Failed to open window");
}) // })
} // }
//
#[derive(Clone, Debug)] // #[derive(Clone, Debug)]
pub struct Card { // pub struct Card {
pub id: usize, // pub id: usize,
pub title: SharedString, // pub title: SharedString,
pub description: SharedString, // pub description: SharedString,
pub image: SharedString, // pub image: SharedString,
pub image_blurhash: BlurHash, // pub image_blurhash: BlurHash,
pub media_type: SharedString, // pub media_type: SharedString,
pub loading: bool, // pub loading: bool,
} // }
//
impl Render for Card { // impl Render for Card {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement { // fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
div() // div()
.id(self.id) // .id(self.id)
.flex() // .flex()
.flex_col() // .flex_col()
.w_48() // .w_48()
.h_64() // .h_64()
.p_10() // .p_10()
.bg(rgb(0xff00ff)) // .bg(rgb(0xff00ff))
.rounded_lg() // .rounded_lg()
.pipe(|card| { // .pipe(|card| {
if self.loading { // if self.loading {
card.child(self.image_blurhash.clone()) // card.child(self.image_blurhash.clone())
} else { // } else {
card.child(gpui::img(self.image.clone())) // card.child(gpui::img(self.image.clone()))
} // }
}) // })
} // }
} // }
//
#[derive(Clone, Debug)] // #[derive(Clone, Debug)]
pub struct BlurHash { // pub struct BlurHash {
pub id: ImageId, // pub id: ImageId,
pub data: Arc<RenderImage>, // pub data: Arc<RenderImage>,
} // }
//
impl BlurHash { // impl BlurHash {
pub fn new( // pub fn new(
data: impl AsRef<str>, // data: impl AsRef<str>,
width: u32, // width: u32,
height: u32, // height: u32,
punch: f32, // punch: f32,
) -> Result<Self, error_stack::Report<crate::Error>> { // ) -> Result<Self, error_stack::Report<crate::Error>> {
use error_stack::ResultExt; // use error_stack::ResultExt;
let decoded = // let decoded =
blurhash::decode(data.as_ref(), width, height, punch).change_context(crate::Error)?; // blurhash::decode(data.as_ref(), width, height, punch).change_context(crate::Error)?;
let buffer = image::RgbaImage::from_raw(width, height, decoded) // let buffer = image::RgbaImage::from_raw(width, height, decoded)
.ok_or(crate::Error) // .ok_or(crate::Error)
.attach("Failed to convert")?; // .attach("Failed to convert")?;
let frame = image::Frame::new(buffer); // let frame = image::Frame::new(buffer);
let render_image = RenderImage::new([frame]); // let render_image = RenderImage::new([frame]);
Ok(Self { // Ok(Self {
id: render_image.id, // id: render_image.id,
data: Arc::from(render_image), // data: Arc::from(render_image),
}) // })
} // }
} // }
//
impl Render for BlurHash { // impl Render for BlurHash {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement { // fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
gpui::img(ImageSource::Render(self.data.clone())) // gpui::img(ImageSource::Render(self.data.clone()))
} // }
} // }
//
impl IntoElement for BlurHash { // impl IntoElement for BlurHash {
type Element = gpui::Img; // type Element = gpui::Img;
//
fn into_element(self) -> Self::Element { // fn into_element(self) -> Self::Element {
gpui::img(ImageSource::Render(self.data.clone())) // gpui::img(ImageSource::Render(self.data.clone()))
} // }
} // }

View File

@@ -190,29 +190,33 @@ fn update(state: &mut State, message: Message) -> Task<Message> {
// if let Some(client) = state.jellyfin_client.clone() { // if let Some(client) = state.jellyfin_client.clone() {
match message { match message {
Message::Settings(msg) => settings::update(&mut state.settings, msg), Message::Settings(msg) => settings::update(&mut state.settings, msg),
Message::OpenItem(id) if let Some(client) = state.jellyfin_client.clone() => { Message::OpenItem(id) => {
use api::jellyfin::BaseItemKind::*; if let Some(client) = state.jellyfin_client.clone() {
if let Some(cached) = id.as_ref().and_then(|id| state.cache.get(id)) use api::jellyfin::BaseItemKind::*;
&& matches!(cached._type, Video | Movie | Episode) if let Some(cached) = id.as_ref().and_then(|id| state.cache.get(id))
{ && matches!(cached._type, Video | Movie | Episode)
let url = client {
.stream_url(id.expect("ID exists")) let url = client
.expect("Failed to get stream URL"); .stream_url(id.expect("ID exists"))
Task::done(Message::Video(video::VideoMessage::Open(url))) .expect("Failed to get stream URL");
Task::done(Message::Video(video::VideoMessage::Open(url)))
} else {
Task::perform(
async move {
let items: Result<Vec<Item>, api::JellyfinApiError> = client
.items(id)
.await
.map(|items| items.into_iter().map(Item::from).collect());
(id, items)
},
|(msg, items)| match items {
Err(e) => Message::Error(format!("Failed to load item: {}", e)),
Ok(items) => Message::LoadedItem(msg, items),
},
)
}
} else { } else {
Task::perform( Task::none()
async move {
let items: Result<Vec<Item>, api::JellyfinApiError> = client
.items(id)
.await
.map(|items| items.into_iter().map(Item::from).collect());
(id, items)
},
|(msg, items)| match items {
Err(e) => Message::Error(format!("Failed to load item: {}", e)),
Ok(items) => Message::LoadedItem(msg, items),
},
)
} }
} }
Message::LoadedItem(id, items) => { Message::LoadedItem(id, items) => {
@@ -221,23 +225,25 @@ fn update(state: &mut State, message: Message) -> Task<Message> {
state.current = id; state.current = id;
Task::none() Task::none()
} }
Message::Refresh if let Some(client) = state.jellyfin_client.clone() => { Message::Refresh => {
// Handle refresh logic if let Some(client) = state.jellyfin_client.clone() {
// let client = state.jellyfin_client.clone(); let current = state.current;
let current = state.current; Task::perform(
Task::perform( async move {
async move { let items: Result<Vec<Item>, api::JellyfinApiError> = client
let items: Result<Vec<Item>, api::JellyfinApiError> = client .items(current)
.items(current) .await
.await .map(|items| items.into_iter().map(Item::from).collect());
.map(|items| items.into_iter().map(Item::from).collect()); (current, items)
(current, items) },
}, |(msg, items)| match items {
|(msg, items)| match items { Err(e) => Message::Error(format!("Failed to refresh items: {}", e)),
Err(e) => Message::Error(format!("Failed to refresh items: {}", e)), Ok(items) => Message::LoadedItem(msg, items),
Ok(items) => Message::LoadedItem(msg, items), },
}, )
) } else {
Task::none()
}
} }
Message::Error(err) => { Message::Error(err) => {
tracing::error!("Error: {}", err); tracing::error!("Error: {}", err);
@@ -266,17 +272,21 @@ fn update(state: &mut State, message: Message) -> Task<Message> {
// Handle search query change // Handle search query change
Task::none() Task::none()
} }
Message::Search if let Some(client) = state.jellyfin_client.clone() => { Message::Search => {
// Handle search action // Handle search action
// let client = state.jellyfin_client.clone(); // let client = state.jellyfin_client.clone();
let query = state.query.clone().unwrap_or_default(); if let Some(client) = state.jellyfin_client.clone() {
Task::perform(async move { client.search(query).await }, |r| match r { let query = state.query.clone().unwrap_or_default();
Err(e) => Message::Error(format!("Search failed: {}", e)), Task::perform(async move { client.search(query).await }, |r| match r {
Ok(items) => { Err(e) => Message::Error(format!("Search failed: {}", e)),
let items = items.into_iter().map(Item::from).collect(); Ok(items) => {
Message::LoadedItem(None, items) let items = items.into_iter().map(Item::from).collect();
} Message::LoadedItem(None, items)
}) }
})
} else {
Task::none()
}
} }
Message::Video(msg) => video::update(state, msg), Message::Video(msg) => video::update(state, msg),
_ => todo!(), _ => todo!(),