Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Files): Preview videos files in Storage and Chats #1699

Merged
merged 86 commits into from
Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
a5948b0
feat(FileEmbed): Get full image on preview (WIP)
lgmarchi Jan 10, 2024
d3b5fe4
feat(FileEmbed): Create temp file to save temporary files correctly, …
lgmarchi Jan 10, 2024
20d35a8
feat(File): Made sames changes for Constellation, and crop images for…
lgmarchi Jan 10, 2024
883c1da
feat(File): delete file from temp_dir when it Element get out of scope
lgmarchi Jan 10, 2024
6bc2362
refactor(FilePreview): Organize better code
lgmarchi Jan 10, 2024
b18b0e3
fix clippy
lgmarchi Jan 10, 2024
a2978d3
fmt check
lgmarchi Jan 10, 2024
24808b4
Merge branch 'dev' into 1692-taskchat-show-full-resolution-when-viewi…
lgmarchi Jan 10, 2024
6e16101
refactor(CropImage): Save cropped images in temp_file and delete them…
lgmarchi Jan 10, 2024
fbbdd75
Merge remote-tracking branch 'origin/1692-taskchat-show-full-resoluti…
lgmarchi Jan 10, 2024
4247933
Merge branch 'dev' into 1692-taskchat-show-full-resolution-when-viewi…
luisecm Jan 10, 2024
252dcbf
Merge branch 'dev' into 1692-taskchat-show-full-resolution-when-viewi…
phillsatellite Jan 11, 2024
ae7f736
feat(Files): Add possibility to preview video files
lgmarchi Jan 11, 2024
06c6d90
feat(Files): Add possibility to preview video files in chats (WIP)
lgmarchi Jan 11, 2024
372e703
Commit to test on windows with Phill
lgmarchi Jan 11, 2024
ae41ffb
Merge remote-tracking branch 'origin/1692-taskchat-show-full-resoluti…
lgmarchi Jan 11, 2024
a92565a
refactor(Files): Not load full image on Windows OS for now, just thum…
lgmarchi Jan 11, 2024
ea3c0b4
Commit to test on windows with Phill 2
lgmarchi Jan 11, 2024
a092616
Commit to test on windows with Phill 3
lgmarchi Jan 11, 2024
a4a82b1
Commit to test on windows with Phill 4
lgmarchi Jan 11, 2024
409db76
Commit to test on windows with Phill 5
lgmarchi Jan 11, 2024
07ebf60
Commit to test on windows with Phill 6
lgmarchi Jan 11, 2024
12399d2
Commit to test on windows with Phill 7
lgmarchi Jan 11, 2024
05c107c
Commit to test on windows with Sheldon 1
lgmarchi Jan 11, 2024
54ad231
Commit to test on windows with Sheldon 2
lgmarchi Jan 11, 2024
20f81aa
refactor(Files): Fix paths to work on Windows OS
lgmarchi Jan 11, 2024
7bace92
refactor(Files): Improve function name
lgmarchi Jan 11, 2024
ae88525
refactor(Files): Hide download status toast notification if download …
lgmarchi Jan 11, 2024
c0ee8ec
fix clippy
lgmarchi Jan 11, 2024
730d988
fix fmt
lgmarchi Jan 11, 2024
b795d7a
Merge branch '1692-taskchat-show-full-resolution-when-viewing-image-i…
lgmarchi Jan 11, 2024
6b42fe3
refactor(Files): Improve code in FileEmbed, remove unnecessary functi…
lgmarchi Jan 11, 2024
d9868de
refactor(Files): Remove play button for now
lgmarchi Jan 12, 2024
0f282b4
fix clippy
lgmarchi Jan 12, 2024
06af0e9
fix fmt
lgmarchi Jan 12, 2024
f776e88
refactor(Files): Move clear files to inside file_preview.rs
lgmarchi Jan 12, 2024
ddf980b
refactor(VideoFiles): Let it faster to preview videos, and rename vid…
lgmarchi Jan 13, 2024
7103442
Merge remote-tracking branch 'origin/dev' into preview-videos-files
lgmarchi Jan 13, 2024
78a08a7
refactor(VideoFiles): Organize code to have modal to preview files ju…
lgmarchi Jan 13, 2024
72f9b6c
Merge remote-tracking branch 'origin/dev' into preview-videos-files
lgmarchi Jan 13, 2024
a98711c
refactor(FilePreview): Add better rules for FilePreview, now with a l…
lgmarchi Jan 13, 2024
1e00670
refactor(FilePreview): Change time necessary to wait image to downloa…
lgmarchi Jan 15, 2024
c542fbe
refactor(FilePreview): Improve code
lgmarchi Jan 15, 2024
f6cc671
fix clippy
lgmarchi Jan 15, 2024
d55b6df
refactor(FilePreview): Improve UI for videos
lgmarchi Jan 16, 2024
65dedf5
refactor(FilePreview): Improve UI for videos addding play button on v…
lgmarchi Jan 16, 2024
b89e0b3
Merge branch 'dev' into preview-videos-files
lgmarchi Jan 16, 2024
49c687e
refactor(FilePreview): Remove unnecessary condition in logic
lgmarchi Jan 16, 2024
ae5dd98
Merge branch 'dev' into preview-videos-files
lgmarchi Jan 16, 2024
f45ed4d
Merge branch 'dev' into preview-videos-files
dariusc93 Jan 17, 2024
19eb284
refactor(FilePreview): Remove use_state dioxus from FileEmbed, not ne…
lgmarchi Jan 17, 2024
cbbc879
Merge remote-tracking branch 'origin/preview-videos-files' into previ…
lgmarchi Jan 17, 2024
cf7922a
Merge remote-tracking branch 'origin/dev' into preview-videos-files
lgmarchi Jan 17, 2024
e4095d4
refactor(FilePreview): Improve cursor on elements that can be clickab…
lgmarchi Jan 17, 2024
16f0380
refactor(FilePreview): Improve position of icons of download and vide…
lgmarchi Jan 17, 2024
ff07f64
Merge branch 'dev' into preview-videos-files
lgmarchi Jan 17, 2024
fff81c9
Merge remote-tracking branch 'origin/preview-videos-files' into previ…
lgmarchi Jan 17, 2024
bd4d7be
refactor(FilePreview): Fix icon position
lgmarchi Jan 17, 2024
73da2ec
fix fmt
lgmarchi Jan 17, 2024
e87bfb6
Merge branch 'dev' into preview-videos-files
stavares843 Jan 17, 2024
27c4165
Merge branch 'dev' into preview-videos-files
dariusc93 Jan 18, 2024
3c2d307
refactor(FilePreview): Fix minus button on attachments
lgmarchi Jan 18, 2024
fe6067e
Merge remote-tracking branch 'origin/preview-videos-files' into previ…
lgmarchi Jan 18, 2024
bcfbfd3
fix fmt
lgmarchi Jan 18, 2024
dd13f8b
fix clippy
lgmarchi Jan 18, 2024
d39216a
Merge branch 'dev' into preview-videos-files
lgmarchi Jan 18, 2024
9acf92a
Merge branch 'dev' into preview-videos-files
stavares843 Jan 18, 2024
82ced57
Merge branch 'dev' into preview-videos-files
lgmarchi Jan 19, 2024
8607be8
refactor(FilePreview): Fix not showing download button in images in m…
lgmarchi Jan 19, 2024
f7e4062
Merge remote-tracking branch 'origin/preview-videos-files' into previ…
lgmarchi Jan 19, 2024
52db9f5
refactor(FilePreview): Fix height and center document on attachments …
lgmarchi Jan 19, 2024
33c104a
refactor(FilePreview): Run videos in attachments and fix position of …
lgmarchi Jan 19, 2024
0af4c9f
Merge branch 'dev' into preview-videos-files
lgmarchi Jan 19, 2024
e2f3109
refactor(FilePreview): Fix DocumentVideoFile icon
lgmarchi Jan 19, 2024
a0c8cb8
Merge branch 'dev' into preview-videos-files
dariusc93 Jan 20, 2024
bc58ff2
refactor(FilePreview): Let files at same size on attachments
lgmarchi Jan 22, 2024
25b45b8
refactor(FilePreview): Fix UI
lgmarchi Jan 22, 2024
db0d7f1
Merge remote-tracking branch 'origin/preview-videos-files' into previ…
lgmarchi Jan 22, 2024
4b94694
refactor(FilePreview): Remove unnecessary code
lgmarchi Jan 22, 2024
68e5985
refactor(FilePreview): Fix extensions with uppercase
lgmarchi Jan 22, 2024
3807ae9
Merge remote-tracking branch 'origin/dev' into preview-videos-files
lgmarchi Jan 22, 2024
9d55caf
testing
lgmarchi Jan 22, 2024
50c88f5
refactor(FilePreview): Fix merge
lgmarchi Jan 23, 2024
520c2d3
Merge branch 'dev' into preview-videos-files
lgmarchi Jan 23, 2024
83631ba
Merge branch 'dev' into preview-videos-files
dariusc93 Jan 23, 2024
f1d47fd
Merge branch 'dev' into preview-videos-files
dariusc93 Jan 23, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
312 changes: 152 additions & 160 deletions Cargo.lock

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ pub struct StaticArgs {
/// ~/.uplink/.user
/// contains the following: warp (folder), state.json, debug.log
pub uplink_path: PathBuf,
/// Directory for temporary files and deleted everytime app is closed or opened
pub temp_files: PathBuf,
/// custom themes for the user
pub themes_path: PathBuf,
/// custom fonts for the user
Expand Down Expand Up @@ -148,6 +150,7 @@ pub static STATIC_ARGS: Lazy<StaticArgs> = Lazy::new(|| {
StaticArgs {
dot_uplink: uplink_container.clone(),
uplink_path: uplink_path.clone(), // TODO: Should this be "User path" instead?
temp_files: uplink_container.join("temp_files"),
themes_path: uplink_container.join("themes"),
fonts_path: uplink_container.join("fonts"),
cache_path: uplink_path.join("state.json"),
Expand Down Expand Up @@ -194,6 +197,12 @@ pub const VIDEO_FILE_EXTENSIONS: &[&str] = &[

pub const DOC_EXTENSIONS: &[&str] = &[".doc", ".docx", ".pdf", ".txt"];

pub fn is_video(file_name: &str) -> bool {
VIDEO_FILE_EXTENSIONS
.iter()
.any(|x| file_name.to_lowercase().ends_with(x))
}

pub fn get_images_dir() -> anyhow::Result<PathBuf> {
if !cfg!(feature = "production_mode") {
return Ok(Path::new("ui").join("extra").join("images"));
Expand Down
19 changes: 19 additions & 0 deletions common/src/utils/clear_temp_files_dir.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use std::{fs, io};
use warp::logging::tracing::log;

use crate::STATIC_ARGS;

pub fn clear_temp_files_directory() -> io::Result<()> {
let temp_files_dir = fs::read_dir(STATIC_ARGS.temp_files.clone())?;
for entry in temp_files_dir {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
fs::remove_dir_all(&path)?;
} else {
fs::remove_file(&path)?;
}
}
log::debug!("Temporary files directory cleared");
Ok(())
}
3 changes: 3 additions & 0 deletions common/src/utils/img_dimensions_preview.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub const IMAGE_MAX_WIDTH: &str = "80vw";

pub const IMAGE_MAX_HEIGHT: &str = "80vh";
3 changes: 3 additions & 0 deletions common/src/utils/lifecycle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ pub struct LifeCycle<D: FnOnce()> {
ondestroy: Option<D>,
}

/// It works like a useEffect hook, but it will be called only once
/// when the component is mounted
/// and when the component is unmounted
pub fn use_component_lifecycle<C: FnOnce() + 'static, D: FnOnce() + 'static>(
cx: &ScopeState,
create: C,
Expand Down
18 changes: 18 additions & 0 deletions common/src/utils/local_file_path.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use std::path::PathBuf;

/// It will work to load local files in img or video tags, but will ignore drive
const PREFIX_TO_WORK_ON_WINDOWS_OS: &str = "http://dioxus.";

/// This function is used to treat local file path if it needs
/// to be loaded in img or video tags for example
pub fn get_fixed_path_to_load_local_file(path: PathBuf) -> String {
if !cfg!(target_os = "windows") {
path.to_string_lossy().to_string()
} else {
format!(
"{}{}",
PREFIX_TO_WORK_ON_WINDOWS_OS,
path.to_string_lossy().to_string().replace('\\', "/")
)
}
}
3 changes: 3 additions & 0 deletions common/src/utils/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
pub mod clear_temp_files_dir;
pub mod img_dimensions_preview;
pub mod lifecycle;
pub mod local_file_path;
109 changes: 48 additions & 61 deletions kit/src/components/embeds/file_embed/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ use std::path::PathBuf;

use crate::elements::button::Button;
use crate::elements::Appearance;
use crate::layout::modal::Modal;
use common::icons::outline::Shape as Icon;
use common::icons::Icon as IconElement;
use common::is_video;
use common::utils::local_file_path::get_fixed_path_to_load_local_file;
use common::STATIC_ARGS;
use dioxus_html::input_data::keyboard_types::Modifiers;

use dioxus::prelude::*;
Expand Down Expand Up @@ -54,15 +56,14 @@ pub struct Props<'a> {
download_pending: Option<bool>,

// called shen the icon is clicked
on_press: EventHandler<'a, ()>,
on_press: EventHandler<'a, Option<PathBuf>>,

progress: Option<&'a Progression>,
}

#[allow(non_snake_case)]
pub fn FileEmbed<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> {
//log::trace!("rendering file embed: {}", cx.props.filename);
let fullscreen_preview = use_state(cx, || false);
let file_extension = std::path::Path::new(&cx.props.filename)
.extension()
.and_then(OsStr::to_str)
Expand All @@ -86,8 +87,10 @@ pub fn FileEmbed<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> {

let with_download_button = if let Some(with_download_button) = cx.props.with_download_button {
with_download_button
} else if let Some(is_from_attachments) = cx.props.is_from_attachments {
is_from_attachments
} else {
true
false
};

let is_pending = cx.props.progress.is_some();
Expand Down Expand Up @@ -154,8 +157,12 @@ pub fn FileEmbed<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> {
};
let remote = cx.props.remote.unwrap_or_default();
let thumbnail = cx.props.thumbnail.clone().unwrap_or_default();
let large_thumbnail = thumbnail.clone(); // TODO: This should be the source of the image
let has_thumbnail = !thumbnail.is_empty();
let file_name_with_extension = cx.props.filename.to_string();
let temp_dir = STATIC_ARGS
.temp_files
.join(file_name_with_extension.clone());
let is_video = is_video(&file_name_with_extension);

cx.render(rsx! (
div {
Expand Down Expand Up @@ -184,31 +191,14 @@ pub fn FileEmbed<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> {
aria_label: "file-icon",
if has_thumbnail {
rsx!(
fullscreen_preview.then(|| rsx!(
Modal {
open: *fullscreen_preview.clone(),
onclose: move |_| fullscreen_preview.set(false),
transparent: false,
close_on_click_inside_modal: true,
dont_pad: true,
img {
id: "image-preview-modal-file-embed",
aria_label: "image-preview-modal-file-embed",
src: "{large_thumbnail}",
max_height: "80vh",
max_width: "80vw",
onclick: move |e| e.stop_propagation(),
},
}
)),
div {
class: "image-container",
aria_label: "message-image-container",
img {
aria_label: "message-image",
onclick: move |mouse_event_data: Event<MouseData>|
if mouse_event_data.modifiers() != Modifiers::CONTROL {
fullscreen_preview.set(true)
if mouse_event_data.modifiers() != Modifiers::CONTROL && !is_from_attachments {
cx.props.on_press.call(Some(temp_dir.clone()));
},
class: format_args!(
"image {} expandable-image",
Expand All @@ -218,19 +208,27 @@ pub fn FileEmbed<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> {
),
src: "{thumbnail}",
},
show_download_button_if_enabled(cx, with_download_button, btn_icon),
show_download_or_minus_button_if_enabled(cx, with_download_button, btn_icon),
}
)
} else if let Some(filepath) = cx.props.filepath.clone() {
let thubmnail = get_file_thumbnail_if_is_image(filepath, filename.clone());
if thubmnail.is_empty() {
let is_image_or_video = is_image(filename.clone()) || is_video;
if is_image_or_video && filepath.exists() {
let fixed_path = get_fixed_path_to_load_local_file(filepath.clone());
rsx!(img {
class: "image-preview-modal",
aria_label: "image-preview-modal",
src: "{fixed_path}",
onclick: move |e| e.stop_propagation(),
})
} else {
rsx!(
div {
height: "60px",
width: "60px",
margin: "30px 0",
IconElement {
icon: cx.props.attachment_icon.unwrap_or(Icon::Document)
icon: cx.props.attachment_icon.unwrap_or(if is_video {Icon::DocumentMedia} else {Icon::Document})
}
if !file_extension_is_empty {
rsx!( label {
Expand All @@ -240,32 +238,37 @@ pub fn FileEmbed<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> {
}
}
)
} else {
rsx!(img {
aria_label: "image-preview-modal",
src: "{thubmnail}",
onclick: move |e| e.stop_propagation(),
})
}
} else {
rsx!(
div {
class: "document-container",
height: "60px",
onclick: move |mouse_event_data: Event<MouseData>| {
if mouse_event_data.modifiers() != Modifiers::CONTROL && is_video && !is_from_attachments {
cx.props.on_press.call(Some(temp_dir.clone()));
}
},
IconElement {
icon: cx.props.attachment_icon.unwrap_or(Icon::Document)
icon: cx.props.attachment_icon.unwrap_or(if is_video {Icon::DocumentMedia} else {Icon::Document})
}
if !file_extension_is_empty {
rsx!( label {
class: "file-embed-type",
"{file_extension}"
})
}
if !is_from_attachments {
rsx!( div {
class: "button-position",
show_download_or_minus_button_if_enabled(cx, with_download_button, btn_icon),
})
}
}
)
}
}
if !has_thumbnail || is_from_attachments {
rsx!( div {
div {
class: "file-info",
width: "100%",
aria_label: "file-info",
Expand All @@ -281,9 +284,9 @@ pub fn FileEmbed<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> {
"{file_description}"
}
},
show_download_button_if_enabled(cx, with_download_button, btn_icon),
)
}
if !has_thumbnail && is_from_attachments {
rsx!(show_download_or_minus_button_if_enabled(cx, with_download_button, btn_icon))
}
if is_pending {
rsx!(div {
class: "upload-bar",
Expand All @@ -298,14 +301,7 @@ pub fn FileEmbed<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> {
))
}

fn get_file_thumbnail_if_is_image(filepath: PathBuf, filename: String) -> String {
let file = match std::fs::read(filepath) {
Ok(file) => file,
Err(_) => {
return String::new();
}
};

fn is_image(filename: String) -> bool {
let parts_of_filename: Vec<&str> = filename.split('.').collect();
let mime = match parts_of_filename.last() {
Some(m) => match *m {
Expand All @@ -319,22 +315,13 @@ fn get_file_thumbnail_if_is_image(filepath: PathBuf, filename: String) -> String
};

if mime.is_empty() {
return String::new();
return false;
}

let image = match &file.len() {
0 => "".to_string(),
_ => {
let prefix = format!("data:{mime};base64,");
let base64_image = base64::encode(&file);
let img = prefix + base64_image.as_str();
img
}
};
image
true
}

fn show_download_button_if_enabled<'a>(
fn show_download_or_minus_button_if_enabled<'a>(
cx: Scope<'a, Props<'a>>,
with_download_button: bool,
btn_icon: common::icons::outline::Shape,
Expand All @@ -347,7 +334,7 @@ fn show_download_button_if_enabled<'a>(
icon: btn_icon,
appearance: Appearance::Primary,
aria_label: "attachment-button".into(),
onpress: move |_| cx.props.on_press.call(()),
onpress: move |_| cx.props.on_press.call(None),
}
}
))
Expand Down
18 changes: 18 additions & 0 deletions kit/src/components/embeds/file_embed/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,24 @@
height: fit-content;
}

.document-container {
position: relative;

.button-position {
position: absolute;
top: -15%;
left: -23%;

svg {
margin-top: 0;
stroke: var(--text-color-dark);
fill: transparent;
height: 1.1rem;
width: 1.1rem;
}
}
}

.image {
width: 100%;
max-width: 350px;
Expand Down
9 changes: 7 additions & 2 deletions kit/src/components/message/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::path::PathBuf;
use std::{collections::HashSet, str::FromStr};

use common::language::{get_local_text, get_local_text_with_args};
Expand Down Expand Up @@ -91,7 +92,7 @@ pub struct Props<'a> {
attachments_pending_download: Option<HashSet<File>>,

/// called when an attachment is downloaded
on_download: EventHandler<'a, File>,
on_download: EventHandler<'a, (File, Option<PathBuf>)>,

/// called when editing is completed
on_edit: EventHandler<'a, String>,
Expand Down Expand Up @@ -163,13 +164,17 @@ pub fn Message<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> {
thumbnail: thumbnail_to_base64(file),
big: true,
remote: is_remote,
with_download_button: true,
download_pending: cx
.props
.attachments_pending_download
.as_ref()
.map(|x| x.contains(file))
.unwrap_or(false),
on_press: move |_| cx.props.on_download.call(file.clone()),
on_press: move |temp_dir_option| cx
.props
.on_download
.call((file.clone(), temp_dir_option)),
})
})
});
Expand Down
Loading
Loading