diff --git a/CHANGES.md b/CHANGES.md index d5dacb5..6c2da3d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,7 @@ ### Added * `ResourceTable` +* "Select file" button to `DropImageFile` component ### Fixed diff --git a/Cargo.lock b/Cargo.lock index 2ccc45c..d30d405 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -519,8 +519,7 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "vertigo" version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ad8484c79e7f9b7a44b5f3738aa7e7dabdf707a5b8ea944b1f667f786a5ab49" +source = "git+https://github.com/vertigo-web/vertigo?branch=master#a16511b7f57738c6593bf23d321f8f8bbc98bc9b" dependencies = [ "base64", "log", @@ -563,8 +562,7 @@ dependencies = [ [[package]] name = "vertigo-macro" version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b05ec1f45b26c2d5e8cbae596b6971b3992d878a13f73aedc7c5798fd1f869d" +source = "git+https://github.com/vertigo-web/vertigo?branch=master#a16511b7f57738c6593bf23d321f8f8bbc98bc9b" dependencies = [ "base64", "crc", diff --git a/Cargo.toml b/Cargo.toml index 33e39bb..225177f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,3 +19,6 @@ edition = "2024" base64 = "0.22" either = "1.15" vertigo = "0.11" + +[patch.crates-io] +vertigo = { git = "https://github.com/vertigo-web/vertigo", branch = "master" } diff --git a/src/button.rs b/src/button.rs index 98e3d00..587c11e 100644 --- a/src/button.rs +++ b/src/button.rs @@ -23,66 +23,50 @@ pub fn Button( variant: ButtonVariant, ) { let style = match (variant, color) { - (ButtonVariant::Text, ButtonColor::Primary) => css!( - " + (ButtonVariant::Text, ButtonColor::Primary) => css! {" font-size: 0.8rem; cursor: pointer; font-weight: 600; color: #007bff; :hover { text-decoration: underline; } - " - ), - (ButtonVariant::Text, ButtonColor::Success) => css!( - " + "}, + (ButtonVariant::Text, ButtonColor::Success) => css! {" font-size: 0.8rem; cursor: pointer; font-weight: 600; color: #28a745; :hover { text-decoration: underline; } - " - ), - (ButtonVariant::Text, ButtonColor::Danger) => css!( - " + "}, + (ButtonVariant::Text, ButtonColor::Danger) => css! {" font-size: 0.8rem; cursor: pointer; font-weight: 600; color: #dc3545; :hover { text-decoration: underline; } - " - ), - (ButtonVariant::Text, ButtonColor::Secondary) => css!( - " + "}, + (ButtonVariant::Text, ButtonColor::Secondary) => css! {" font-size: 0.8rem; cursor: pointer; font-weight: 600; color: #6c757d; :hover { text-decoration: underline; } - " - ), - (ButtonVariant::Outline, ButtonColor::Danger) => css!( - " + "}, + (ButtonVariant::Outline, ButtonColor::Danger) => css! {" font-size: 0.8rem; cursor: pointer; font-weight: 700; background: #fff; padding: 4px 12px; border-radius: 4px; border: 1px solid #dc3545; color: #dc3545; :hover { background: #dc3545; color: #fff; } - " - ), - (ButtonVariant::Outline, ButtonColor::Primary) => css!( - " + "}, + (ButtonVariant::Outline, ButtonColor::Primary) => css! {" font-size: 0.8rem; cursor: pointer; font-weight: 700; background: #fff; padding: 4px 12px; border-radius: 4px; border: 1px solid #007bff; color: #007bff; :hover { background: #007bff; color: #fff; } - " - ), - (ButtonVariant::Outline, ButtonColor::Success) => css!( - " + "}, + (ButtonVariant::Outline, ButtonColor::Success) => css! {" font-size: 0.8rem; cursor: pointer; font-weight: 700; background: #fff; padding: 4px 12px; border-radius: 4px; border: 1px solid #28a745; color: #28a745; :hover { background: #28a745; color: #fff; } - " - ), - (ButtonVariant::Outline, ButtonColor::Secondary) => css!( - " + "}, + (ButtonVariant::Outline, ButtonColor::Secondary) => css! {" font-size: 0.8rem; cursor: pointer; font-weight: 700; background: #fff; padding: 4px 12px; border-radius: 4px; border: 1px solid #6c757d; color: #6c757d; :hover { background: #6c757d; color: #fff; } - " - ), + "}, }; dom! { @@ -100,7 +84,7 @@ pub fn TableButton(label: String, on_click: Rc) { dom! {
{ - let message = format_line(&item); - let image_css = css! {" - display: flex; - flex-flow: column; - "}; - let restore = bind!(item_clone, callback, |_| { - if let Some(callback) = &callback { - callback(None); + + // Shared handler factory for the file-input-select path (cloned per render branch) + let item_for_select = item.clone(); + let callback_for_select = params.callback.clone(); + let make_on_change_file = Rc::new(move || { + let item = item_for_select.clone(); + let callback = callback_for_select.clone(); + move |event: DropFileEvent| { + for file in event.items { + if let Some(cb) = callback.as_deref() { + cb(Some(file)); } else { - item_clone.set(None); + item.set(Some(file)); } - }); - let restore_text = if original.is_some() { - ¶ms.revert_label - } else { - ¶ms.cancel_label - }; - dom! { -
- - - { message } -
} } - None => match original { - Some(original) => { - dom! {
} + }); + + let image_view = view_deps.render_value(move |(original, item, base64_date)| { + let hidden_input_css = css! {"display: none;"}; + let btn_css = css! {" + text-align: center; + display: inline-block; + cursor: pointer; + padding: 4px 12px; + border: 1px solid currentColor; + border-radius: 4px; + font-size: 0.85em; + margin-top: 6px; + margin-bottom: 6px; + "}; + let select_label = params.select_label.clone(); + let accept = params.accept.clone(); + let flex_column = css! {"display: flex; flex-direction: column; align-items: center;"}; + + match item { + Some(item) => { + let message = format_line(&item); + let restore = bind!(item_clone, callback, |_| { + if let Some(callback) = &callback { + callback(None); + } else { + item_clone.set(None); + } + }); + let restore_text = if original.is_some() { + ¶ms.revert_label + } else { + ¶ms.cancel_label + }; + let select_button = if select_label.is_empty() { + None + } else { + Some(dom! { + + }) + }; + dom! { +
+ + {..select_button} + + { message } +
+ } } - None => dom! {
{¶ms.no_image_text}
}, - }, + None => match original { + Some(original) => { + if select_label.is_empty() { + dom! {
} + } else { + dom! { +
+ + +
+ } + } + } + None => { + if select_label.is_empty() { + dom! {
{¶ms.no_image_text}
} + } else { + dom! { +
+ {¶ms.no_image_text} + +
+ } + } + } + }, + } }); let item = item.clone(); @@ -93,6 +177,10 @@ pub struct DropImageFileParams { pub dropzone_css: Css, pub dropzone_add_css: Css, pub img_css: Css, + /// Label for the "select file" button. Set to empty string to hide the button. + pub select_label: String, + /// Value for the `accept` attribute on the hidden file input (e.g. `"image/*"`). + pub accept: String, } impl Default for DropImageFileParams { @@ -112,8 +200,14 @@ impl Default for DropImageFileParams { padding: 10px; "}, - dropzone_add_css: css!(""), - img_css: css!(""), + dropzone_add_css: css! {""}, + img_css: css! {" + max-width: 100%; + max-height: 320px; + object-fit: contain; + "}, + select_label: "Select file...".to_string(), + accept: "image/*".to_string(), } } } diff --git a/src/form/mod.rs b/src/form/mod.rs index 79a3b60..0027bc5 100644 --- a/src/form/mod.rs +++ b/src/form/mod.rs @@ -166,7 +166,7 @@ pub fn Form( if controls.is_empty() { None } else { - let mut css_controls = css!("grid-column: span 2;"); + let mut css_controls = css! {"grid-column: span 2;"}; if let Some(custom_css) = &c_config.css { css_controls += custom_css; } diff --git a/src/form/render/fields.rs b/src/form/render/fields.rs index 754061a..9caa27f 100644 --- a/src/form/render/fields.rs +++ b/src/form/render/fields.rs @@ -49,7 +49,7 @@ pub(in super::super) fn fields<'a>( if section.new_group { vec![ - dom! {
}, + dom! {
}, section_rendered, ] } else { @@ -66,9 +66,9 @@ fn render_field(field: &DataField, validation_errors: &Value) }) }; dom! { -
+
- {val_error} + {val_error}
} } diff --git a/src/input/input_with_button.rs b/src/input/input_with_button.rs index 1d39a3d..dec446e 100644 --- a/src/input/input_with_button.rs +++ b/src/input/input_with_button.rs @@ -26,7 +26,7 @@ use vertigo::{AttrGroup, Value, bind, component, computed_tuple, dom, transactio /// button_label: "Load".to_string(), /// ..Default::default() /// }} -/// input:css={css!("width: 300px;")} +/// input:css={css! {"width: 300px;"}} /// /> /// }; /// ``` diff --git a/src/resource_table/mod.rs b/src/resource_table/mod.rs index db5f81e..41a8fd3 100644 --- a/src/resource_table/mod.rs +++ b/src/resource_table/mod.rs @@ -125,12 +125,12 @@ impl "} + &props.table_css; dom! { -
-
border-bottom: 1px solid #eee; gap: 10px; "}}> -

{props.title.clone()}

+

{props.title.clone()}

{..props.render_filters.map(|render_filters| render_filters())} diff --git a/src/resource_table/row_form.rs b/src/resource_table/row_form.rs index 2ae210b..0b1f847 100644 --- a/src/resource_table/row_form.rs +++ b/src/resource_table/row_form.rs @@ -79,7 +79,7 @@ pub fn row_form( state.clone().render_value(move |current_state| { let error_view = error.clone().render_value(|err| { if let Some(err) = err { - dom! {
{err}
} + dom! {
{err}
} } else { dom! {
} } @@ -136,10 +136,7 @@ pub fn row_form( }; dom! { -
+