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

update(FilePreview): Open image preview in high quality #1698

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 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
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
b689aba
Merge branch 'dev' into 1692-taskchat-show-full-resolution-when-viewi…
dariusc93 Jan 15, 2024
53a6efe
refactor(FilePreview): update code
lgmarchi Jan 15, 2024
14e41b6
Merge branch 'dev' into 1692-taskchat-show-full-resolution-when-viewi…
dariusc93 Jan 16, 2024
482a31c
Merge branch 'dev' into 1692-taskchat-show-full-resolution-when-viewi…
dariusc93 Jan 17, 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
4 changes: 4 additions & 0 deletions common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub mod sounds;
pub mod state;
pub mod testing;
pub mod upload_file_channel;
pub mod utils;
pub mod warp_runner;

use anyhow::bail;
Expand Down Expand Up @@ -90,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 @@ -147,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
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
pub fn download_file_for_better_preview(file_name: String) {
let file_name_with_extension = format!("{}", cx.props.filename);
let temp_dir = STATIC_ARGS.temp_files.join(file_name_with_extension);
if !temp_dir.exists() {
cx.props.on_press.call(Some(temp_dir.clone()));
}
let temp_path_as_string = temp_dir.clone().to_string_lossy().to_string();
}
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";
32 changes: 32 additions & 0 deletions common/src/utils/lifecycle.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use dioxus::prelude::*;

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,
destroy: D,
) -> &LifeCycle<D> {
cx.use_hook(|| {
cx.spawn(async move {
// This will be run once the component is mounted
std::future::ready::<()>(()).await;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, does the task need to yield first before calling create?

create();
Copy link
Contributor

@dariusc93 dariusc93 Jan 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something ive overlooked here is that youre calling this in a async task. While im not sure what the context behind it might be for every use case, if blocking functions are called, it might be best to spawn a blocking task here so it does not block the executor by using tokio::task::spawn_blocking

});
LifeCycle {
ondestroy: Some(destroy),
}
})
}

impl<D: FnOnce()> Drop for LifeCycle<D> {
fn drop(&mut self) {
let f = self.ondestroy.take().unwrap();
f();
}
}
14 changes: 14 additions & 0 deletions common/src/utils/local_file_path.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
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_ALL_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 {
format!(
"{}{}",
PREFIX_TO_WORK_ON_ALL_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
@@ -0,0 +1,3 @@
pub mod img_dimensions_preview;
pub mod lifecycle;
pub mod local_file_path;
60 changes: 42 additions & 18 deletions kit/src/components/embeds/file_embed/mod.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
use std::ffi::OsStr;
use std::fs;
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::utils::img_dimensions_preview::IMAGE_MAX_HEIGHT;
use common::utils::img_dimensions_preview::IMAGE_MAX_WIDTH;
use common::utils::lifecycle::use_component_lifecycle;
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,7 +60,7 @@ 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>,
}
Expand Down Expand Up @@ -156,6 +162,17 @@ pub fn FileEmbed<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> {
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);
let temp_dir2 = temp_dir.clone();

use_component_lifecycle(
cx,
|| {},
move || {
let _ = fs::remove_file(temp_dir2.clone());
},
);

cx.render(rsx! (
div {
Expand Down Expand Up @@ -184,23 +201,30 @@ 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(),
},
fullscreen_preview.then(|| {
if !temp_dir.exists() {
cx.props.on_press.call(Some(temp_dir.clone()));
}
)),
let temp_file_path_as_string = get_fixed_path_to_load_local_file(temp_dir.clone());
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: format_args!("{}", if temp_dir.exists()
{ temp_file_path_as_string}
else {large_thumbnail} ),
Comment on lines +219 to +221
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may be able to avoid using format_args (or format) if both variables are strings.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I will fix it.

max_height: IMAGE_MAX_HEIGHT,
max_width: IMAGE_MAX_WIDTH,
onclick: move |e| e.stop_propagation(),
},
}
)}),
div {
class: "image-container",
aria_label: "message-image-container",
Expand Down Expand Up @@ -347,7 +371,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
8 changes: 6 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 @@ -169,7 +170,10 @@ pub fn Message<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> {
.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
10 changes: 9 additions & 1 deletion ui/src/bootstrap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,19 @@ pub fn configure_logger(production_mode: bool, log_to_file: bool) {
}

pub fn create_uplink_dirs() {
// Guarantee all files in temp files directory will be deleted
if STATIC_ARGS.temp_files.exists() {
std::fs::remove_dir_all(&STATIC_ARGS.temp_files)
.expect("Error removing temp files directory");
}
// Initializes the cache dir if needed
std::fs::create_dir_all(&STATIC_ARGS.uplink_path).expect("Error creating Uplink directory");
std::fs::create_dir_all(&STATIC_ARGS.warp_path).expect("Error creating Warp directory");
std::fs::create_dir_all(&STATIC_ARGS.themes_path).expect("error creating themes directory");
std::fs::create_dir_all(&STATIC_ARGS.fonts_path).expect("error fonts themes directory");
std::fs::create_dir_all(&STATIC_ARGS.fonts_path)
.expect("error creating fonts themes directory");
std::fs::create_dir_all(&STATIC_ARGS.temp_files)
.expect("error creatings temporary files directory");
}

pub fn platform_quirks() {
Expand Down
25 changes: 19 additions & 6 deletions ui/src/components/crop_image_tool/circle_format_tool/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
use common::{icons::outline::Shape, language::get_local_text, STATIC_ARGS};
use common::{
icons::outline::Shape, language::get_local_text, utils::lifecycle::use_component_lifecycle,
STATIC_ARGS,
};
use dioxus::prelude::*;
use kit::{
elements::{button::Button, label::Label, range::Range, Appearance},
layout::modal::Modal,
};
use std::path::PathBuf;
use once_cell::sync::Lazy;
use std::{fs, path::PathBuf};
use tokio::io::AsyncWriteExt;

use crate::components::crop_image_tool::b64_encode;
Expand All @@ -13,6 +17,8 @@ const ADJUST_CROP_CIRCLE_SIZE_SCRIPT: &str = include_str!("./adjust_crop_circle_
const GET_IMAGE_DIMENSIONS_SCRIPT: &str = include_str!("../get_image_dimensions.js");
const SAVE_CROPPED_IMAGE_SCRIPT: &str = include_str!("./save_cropped_image.js");
const MOVE_IMAGE_SCRIPT: &str = include_str!("../move_image.js");
static CROPPED_IMAGE_PATH: Lazy<PathBuf> =
Lazy::new(|| STATIC_ARGS.temp_files.join("cropped_image.png"));

#[derive(Debug, Clone)]
struct ImageDimensions {
Expand Down Expand Up @@ -67,6 +73,14 @@ pub fn CropCircleImageModal<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> {
}
});

use_component_lifecycle(
cx,
|| {},
move || {
let _ = fs::remove_file(CROPPED_IMAGE_PATH.clone());
},
);

return cx.render(rsx!(div {
Modal {
open: *crop_image.clone(),
Expand Down Expand Up @@ -132,8 +146,7 @@ pub fn CropCircleImageModal<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> {
return;
},
};
let cropped_image_path = STATIC_ARGS.uplink_path.join("cropped_image.png");
let mut file = match tokio::fs::File::create(cropped_image_path.clone()).await {
let mut file = match tokio::fs::File::create(CROPPED_IMAGE_PATH.clone()).await {
Ok(file) => file,
Err(e) => {
log::error!("Error creating cropped image file: {}", e);
Expand All @@ -149,7 +162,7 @@ pub fn CropCircleImageModal<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> {
log::error!("Error syncing cropped image file. {}", e);
return;
}
match tokio::fs::metadata(&cropped_image_path).await {
match tokio::fs::metadata(&CROPPED_IMAGE_PATH.clone()).await {
Ok(metadata) => {
if metadata.len() == 0 {
log::error!("Cropped image file is empty.");
Expand All @@ -161,7 +174,7 @@ pub fn CropCircleImageModal<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> {
return;
}
}
cropped_image_pathbuf.with_mut(|f| *f = cropped_image_path.clone());
cropped_image_pathbuf.with_mut(|f| *f = CROPPED_IMAGE_PATH.clone());
clicked_button_to_crop.set(true);
}
}
Expand Down
23 changes: 18 additions & 5 deletions ui/src/components/crop_image_tool/rectangle_format_tool/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
use common::{icons::outline::Shape, language::get_local_text, STATIC_ARGS};
use common::{
icons::outline::Shape, language::get_local_text, utils::lifecycle::use_component_lifecycle,
STATIC_ARGS,
};
use dioxus::prelude::*;
use kit::{
elements::{button::Button, label::Label, range::Range, Appearance},
layout::modal::Modal,
};
use std::path::PathBuf;
use once_cell::sync::Lazy;
use std::{fs, path::PathBuf};
use tokio::io::AsyncWriteExt;

use crate::components::crop_image_tool::b64_encode;
Expand All @@ -13,6 +17,8 @@ const ADJUST_CROP_RECTANGLE_SIZE_SCRIPT: &str = include_str!("./adjust_crop_rect
const GET_IMAGE_DIMENSIONS_SCRIPT: &str = include_str!("../get_image_dimensions.js");
const SAVE_CROPPED_IMAGE_SCRIPT: &str = include_str!("./save_cropped_image.js");
const MOVE_IMAGE_SCRIPT: &str = include_str!("../move_image.js");
static CROPPED_IMAGE_PATH: Lazy<PathBuf> =
Lazy::new(|| STATIC_ARGS.temp_files.join("cropped_image_for_banner.png"));

#[derive(Debug, Clone)]
struct ImageDimensions {
Expand Down Expand Up @@ -67,6 +73,14 @@ pub fn CropRectImageModal<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> {
}
});

use_component_lifecycle(
cx,
|| {},
move || {
let _ = fs::remove_file(CROPPED_IMAGE_PATH.clone());
},
);

return cx.render(rsx!(div {
Modal {
open: *crop_image.clone(),
Expand Down Expand Up @@ -133,8 +147,7 @@ pub fn CropRectImageModal<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> {
return;
},
};
let cropped_image_path = STATIC_ARGS.uplink_path.join("cropped_image_for_banner.png");
let mut file = match tokio::fs::File::create(cropped_image_path.clone()).await {
let mut file = match tokio::fs::File::create(CROPPED_IMAGE_PATH.clone()).await {
Ok(file) => file,
Err(e) => {
log::error!("Error creating cropped image file: {}", e);
Expand All @@ -152,7 +165,7 @@ pub fn CropRectImageModal<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> {
return;
}

cropped_image_pathbuf.with_mut(|f| *f = cropped_image_path.clone());
cropped_image_pathbuf.with_mut(|f| *f = CROPPED_IMAGE_PATH.clone());
clicked_button_to_crop.set(true);
}
}
Expand Down
Loading
Loading