-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
update(CropImage): Create crop tool for banner image (#1317)
Co-authored-by: Darius Clark <[email protected]> Co-authored-by: Sara Tavares <[email protected]> Co-authored-by: Darius <[email protected]>
- Loading branch information
1 parent
21c6c3d
commit a00b1de
Showing
10 changed files
with
574 additions
and
223 deletions.
There are no files selected for viewing
File renamed without changes.
228 changes: 228 additions & 0 deletions
228
ui/src/components/crop_image_tool/circle_format_tool/mod.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,228 @@ | ||
use common::{icons::outline::Shape, language::get_local_text, STATIC_ARGS}; | ||
use dioxus::prelude::*; | ||
use kit::{ | ||
elements::{button::Button, label::Label, range::Range, Appearance}, | ||
layout::modal::Modal, | ||
}; | ||
use std::path::PathBuf; | ||
use tokio::io::AsyncWriteExt; | ||
|
||
const ADJUST_CROP_CIRCLE_SIZE_SCRIPT: &str = include_str!("./adjust_crop_circle_size.js"); | ||
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"); | ||
|
||
#[derive(Debug, Clone)] | ||
struct ImageDimensions { | ||
height: i64, | ||
width: i64, | ||
} | ||
|
||
#[derive(Props)] | ||
pub struct Props<'a> { | ||
pub large_thumbnail: String, | ||
pub on_cancel: EventHandler<'a, ()>, | ||
pub on_crop: EventHandler<'a, PathBuf>, | ||
} | ||
|
||
#[allow(non_snake_case)] | ||
pub fn CropCircleImageModal<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> { | ||
let large_thumbnail = use_ref(cx, || cx.props.large_thumbnail.clone()); | ||
|
||
let image_scale: &UseRef<f32> = use_ref(cx, || 1.0); | ||
let crop_image = use_state(cx, || true); | ||
let cropped_image_pathbuf = use_ref(cx, PathBuf::new); | ||
let clicked_button_to_crop = use_state(cx, || false); | ||
|
||
let image_dimensions = use_ref(cx, || ImageDimensions { | ||
height: 0, | ||
width: 0, | ||
}); | ||
|
||
if *clicked_button_to_crop.get() { | ||
cx.props.on_crop.call(cropped_image_pathbuf.read().clone()); | ||
clicked_button_to_crop.set(false); | ||
crop_image.set(false); | ||
} | ||
|
||
let eval = use_eval(cx); | ||
|
||
use_future(cx, (), |_| { | ||
to_owned![eval, image_dimensions]; | ||
async move { | ||
while image_dimensions.read().width == 0 && image_dimensions.read().height == 0 { | ||
if let Ok(r) = eval(GET_IMAGE_DIMENSIONS_SCRIPT) { | ||
if let Ok(val) = r.join().await { | ||
*image_dimensions.write_silent() = ImageDimensions { | ||
height: val["height"].as_i64().unwrap_or_default(), | ||
width: val["width"].as_i64().unwrap_or_default(), | ||
}; | ||
} | ||
}; | ||
} | ||
let _ = eval(ADJUST_CROP_CIRCLE_SIZE_SCRIPT); | ||
let _ = eval(MOVE_IMAGE_SCRIPT); | ||
} | ||
}); | ||
|
||
return cx.render(rsx!(div { | ||
Modal { | ||
open: *crop_image.clone(), | ||
onclose: move |_| { | ||
// Not close if user clicks outside modal | ||
}, | ||
transparent: false, | ||
show_close_button: false, | ||
close_on_click_inside_modal: false, | ||
dont_pad: false, | ||
div { | ||
max_height: "85vh", | ||
max_width: "80vw", | ||
padding: "16px", | ||
onclick: move |_| {}, | ||
div { | ||
id: "crop-image-topbar", | ||
background: "var(--secondary)", | ||
height: "70px", | ||
border_radius: "12px", | ||
div { | ||
id: "crop-image-topbar-left", | ||
padding: "16px", | ||
display: "inline-flex", | ||
align_items: "center", | ||
div { | ||
class: "crop-image-topbar-left-title", | ||
Label { | ||
text: get_local_text("settings.please-select-area-you-want-to-crop") | ||
} | ||
}, | ||
Button { | ||
appearance: Appearance::DangerAlternative, | ||
icon: Shape::XMark, | ||
onpress: move |_| { | ||
cx.props.on_cancel.call(()); | ||
crop_image.set(false); | ||
} | ||
}, | ||
div { | ||
margin_right: "16px", | ||
} | ||
Button { | ||
appearance: Appearance::Success, | ||
icon: Shape::Check, | ||
onpress: move |_| { | ||
cx.spawn({ | ||
to_owned![eval, image_scale, cropped_image_pathbuf, clicked_button_to_crop]; | ||
async move { | ||
let save_image_cropped_js = SAVE_CROPPED_IMAGE_SCRIPT | ||
.replace("$IMAGE_SCALE", (1.0 / *image_scale.read()).to_string().as_str()); | ||
if let Ok(r) = eval(&save_image_cropped_js) { | ||
if let Ok(val) = r.join().await { | ||
let thumbnail = val.as_str().unwrap_or_default(); | ||
let base64_string = thumbnail.trim_matches('\"'); | ||
let decoded_bytes = match base64::decode(base64_string) { | ||
Ok(bytes) => bytes, | ||
Err(e) => { | ||
log::error!("Error decoding base64 string for cropped image: {}", e); | ||
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 { | ||
Ok(file) => file, | ||
Err(e) => { | ||
log::error!("Error creating cropped image file: {}", e); | ||
return; | ||
}, | ||
}; | ||
|
||
if let Err(e) = file.write_all(&decoded_bytes).await { | ||
log::error!("Error writing cropped image file. {}", e); | ||
return; | ||
} | ||
if let Err(e) = file.sync_all().await { | ||
log::error!("Error syncing cropped image file. {}", e); | ||
return; | ||
} | ||
match tokio::fs::metadata(&cropped_image_path).await { | ||
Ok(metadata) => { | ||
if metadata.len() == 0 { | ||
log::error!("Cropped image file is empty."); | ||
return; | ||
} | ||
} | ||
Err(e) => { | ||
log::error!("Error getting metadata for cropped image file: {}", e); | ||
return; | ||
} | ||
} | ||
cropped_image_pathbuf.with_mut(|f| *f = cropped_image_path.clone()); | ||
clicked_button_to_crop.set(true); | ||
} | ||
} | ||
} | ||
}); | ||
} | ||
} | ||
}, | ||
} | ||
div { | ||
class: "container", | ||
margin_bottom: "16px", | ||
text_align: "center", | ||
padding: "16px", | ||
div { | ||
id: "image-crop-box-container", | ||
display: "inline-flex", | ||
div { | ||
id: "img-parent-div", | ||
overflow: "hidden", | ||
width: "auto", | ||
height: "auto", | ||
border: "3px solid var(--secondary)", | ||
img { | ||
id: "image-preview-modal-file-embed", | ||
alt: "draggable image", | ||
aria_label: "image-preview-modal-file-embed", | ||
src: format_args!("{}", large_thumbnail.read()), | ||
transform: format_args!("scale({})", image_scale.read()), | ||
overflow: "hidden", | ||
transition: "transform 0.2s ease", | ||
max_height: "50vh", | ||
max_width: "50vw", | ||
display: "inline-block", | ||
vertical_align: "middle", | ||
cursor: "move", | ||
position: "relative", | ||
z_index: "-1", | ||
onclick: move |e| e.stop_propagation(), | ||
}, | ||
} | ||
div { | ||
id: "crop-box", | ||
class: "crop-box", | ||
}, | ||
div { | ||
id: "shadow-img-mask", | ||
class: "shadow-img-mask", | ||
} | ||
} | ||
}, | ||
div { | ||
class: "range-background", | ||
Range { | ||
initial_value: 1.0, | ||
min: 1.0, | ||
max: 5.0, | ||
step: 0.1, | ||
with_buttons: true, | ||
onchange: move |size_f32| { | ||
*image_scale.write() = size_f32; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
}, | ||
)); | ||
} |
File renamed without changes.
Oops, something went wrong.