//! File previews (milestone 4): browser-native media (image/PDF/video/audio) //! and read-only CodeMirror for text/code. use std::sync::{Arc, Mutex}; use leptos::prelude::*; use wasm_bindgen::JsValue; use wasm_bindgen_futures::spawn_local; use web_sys::MouseEvent; use crate::api; use crate::cm; /// What kind of preview a file gets. #[derive(Clone, Copy, PartialEq, Eq)] pub enum PreviewKind { Image, Pdf, Video, Audio, Text, } const IMAGE_EXTS: &[&str] = &[ "png", "jpg", "jpeg", "gif", "webp", "bmp", "svg", "ico", "avif", "tiff", "tif", ]; const VIDEO_EXTS: &[&str] = &["mp4", "webm", "ogv", "mov", "m4v"]; const AUDIO_EXTS: &[&str] = &["mp3", "wav", "ogg", "m4a", "flac", "aac", "opus"]; fn ext_of(name: &str) -> String { let base = name.rsplit('/').next().unwrap_or(name); base.rsplit('.').next().unwrap_or("").to_lowercase() } /// Decide how to preview a file by name. `None` = no preview (download only). pub fn preview_kind(name: &str) -> Option { let ext = ext_of(name); if IMAGE_EXTS.contains(&ext.as_str()) { return Some(PreviewKind::Image); } if ext == "pdf" { return Some(PreviewKind::Pdf); } if VIDEO_EXTS.contains(&ext.as_str()) { return Some(PreviewKind::Video); } if AUDIO_EXTS.contains(&ext.as_str()) { return Some(PreviewKind::Audio); } if cm::is_text(name) { return Some(PreviewKind::Text); } None } /// A file the user wants to preview. #[derive(Clone)] pub struct PreviewTarget { pub root_id: i64, /// Path relative to the root (e.g. "docs/a.txt"). pub path: String, /// Display name (used for the title + language detection). pub name: String, #[allow(dead_code)] pub size: u64, } /// The preview overlay. #[component] pub fn PreviewModal( target: PreviewTarget, kind: PreviewKind, close: Callback<()>, ) -> impl IntoView { let t = target; let url = match kind { PreviewKind::Text => String::new(), _ => api::preview_url(t.root_id, &t.path), }; let name = t.name.clone(); // Escape closes the preview. { let c = close; let _h = window_event_listener(leptos::ev::keydown, move |ev: web_sys::KeyboardEvent| { if ev.key() == "Escape" { c.run(()); } }); } view! {