preview.rs
⎇
Raw
1//! File previews (milestone 4): browser-native media (image/PDF/video/audio).
2//! Text files open in the editor instead.
3
4use api_types::FileKind;
5use leptos::prelude::*;
6use web_sys::MouseEvent;
7
8use crate::api;
9use crate::components::icon::Icon;
10use crate::icons::IconName;
11
12/// What kind of preview a file gets.
13#[derive(Clone, Copy, PartialEq, Eq)]
14pub enum PreviewKind {
15 Image,
16 Pdf,
17 Video,
18 Audio,
19}
20
21/// Which viewer opens this entry, from the server's sniffed [`FileKind`].
22/// `None` = nothing we can preview here: text goes to the editor, archives
23/// and binaries are download-only.
24pub fn preview_kind(kind: FileKind) -> Option<PreviewKind> {
25 match kind {
26 FileKind::Image => Some(PreviewKind::Image),
27 FileKind::Pdf => Some(PreviewKind::Pdf),
28 FileKind::Video => Some(PreviewKind::Video),
29 FileKind::Audio => Some(PreviewKind::Audio),
30 FileKind::Dir | FileKind::Archive | FileKind::Binary | FileKind::Text => None,
31 }
32}
33
34/// A file the user wants to preview.
35#[derive(Clone)]
36pub struct PreviewTarget {
37 pub root_id: i64,
38 /// Path relative to the root (e.g. "docs/a.txt").
39 pub path: String,
40 /// Display name (used for the title + language detection).
41 pub name: String,
42}
43
44/// The preview overlay.
45#[component]
46pub fn PreviewModal(
47 target: PreviewTarget,
48 kind: PreviewKind,
49 close: Callback<()>,
50) -> impl IntoView {
51 let t = target;
52 let url = api::preview_url(t.root_id, &t.path);
53 let name = t.name.clone();
54 // Escape closes the preview.
55 {
56 let c = close;
57 crate::util::owned_window_listener(
58 leptos::ev::keydown,
59 move |ev: web_sys::KeyboardEvent| {
60 if ev.key() == "Escape" {
61 c.run(());
62 }
63 },
64 );
65 }
66 view! {
67 <div class="modal-overlay preview-overlay" on:click=move |_| close.run(())>
68 <div class="modal preview-modal" on:click=move |e: MouseEvent| e.stop_propagation()>
69 <div class="preview-head">
70 <span class="preview-title">{name}</span>
71 <button class="preview-close" title="Close" aria-label="Close" on:click=move |_| close.run(())>
72 <Icon name=IconName::Close class="ic-close".to_string()/>
73 </button>
74 </div>
75 <div class="preview-body">
76 {move || match kind {
77 PreviewKind::Image => {
78 view! { <img class="preview-img" src=url.clone() alt=t.name.clone()/> }
79 .into_view()
80 .into_any()
81 }
82 PreviewKind::Pdf => {
83 view! { <iframe class="preview-pdf" src=url.clone() title=t.name.clone()/> }
84 .into_view()
85 .into_any()
86 }
87 PreviewKind::Video => {
88 view! { <video class="preview-video" src=url.clone() controls=true/> }
89 .into_view()
90 .into_any()
91 }
92 PreviewKind::Audio => {
93 view! {
94 <div class="preview-audio">
95 <audio src=url.clone() controls=true/>
96 </div>
97 }
98 .into_view()
99 .into_any()
100 }
101 }}
102 </div>
103 </div>
104 </div>
105 }
106}
107