Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
### Added

* `ResourceTable`
* "Select file" button to `DropImageFile` component

### Fixed

Expand Down
6 changes: 2 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
50 changes: 17 additions & 33 deletions src/button.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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! {
Expand All @@ -100,7 +84,7 @@ pub fn TableButton(label: String, on_click: Rc<dyn Fn() + 'static>) {
dom! {
<div
on_click={move |_| on_click()}
css={css!{"
css={css! {"
padding: 8px 16px;
background: #232323;
color: #fff;
Expand Down
154 changes: 124 additions & 30 deletions src/drop_image_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,39 +27,123 @@ pub fn DropImageFile(
let item_clone = item.clone();
let params = params.clone();
let callback = params.callback.clone();
let image_view = view_deps.render_value(move |(original, item, base64_date)| match item {
Some(item) => {
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() {
&params.revert_label
} else {
&params.cancel_label
};
dom! {
<div css={image_css}>
<button on_click={restore}>{restore_text}</button>
<img css={&params.img_css} src={base64_date} />
{ message }
</div>
}
}
None => match original {
Some(original) => {
dom! { <div><img css={&params.img_css} src={original} /></div> }
});

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() {
&params.revert_label
} else {
&params.cancel_label
};
let select_button = if select_label.is_empty() {
None
} else {
Some(dom! {
<label css={&btn_css}>
<input
css={hidden_input_css}
type="file"
accept={accept}
on_change_file={make_on_change_file()}
/>
{select_label}
</label>
})
};
dom! {
<div css={flex_column}>
<button css={btn_css}on_click={restore}>{restore_text}</button>
{..select_button}
<img css={&params.img_css} src={base64_date} />
{ message }
</div>
}
}
None => dom! { <div>{&params.no_image_text}</div> },
},
None => match original {
Some(original) => {
if select_label.is_empty() {
dom! { <div><img css={&params.img_css} src={original} /></div> }
} else {
dom! {
<div css={flex_column}>
<img css={&params.img_css} src={original} />
<label css={btn_css}>
<input
css={hidden_input_css}
type="file"
accept={accept}
on_change_file={make_on_change_file()}
/>
{select_label}
</label>
</div>
}
}
}
None => {
if select_label.is_empty() {
dom! { <div>{&params.no_image_text}</div> }
} else {
dom! {
<div css={flex_column}>
{&params.no_image_text}
<label css={btn_css}>
<input
css={hidden_input_css}
type="file"
accept={accept}
on_change_file={make_on_change_file()}
/>
{select_label}
</label>
</div>
}
}
}
},
}
});

let item = item.clone();
Expand Down Expand Up @@ -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 {
Expand All @@ -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(),
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/form/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ pub fn Form<T>(
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;
}
Expand Down
6 changes: 3 additions & 3 deletions src/form/render/fields.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ pub(in super::super) fn fields<'a>(

if section.new_group {
vec![
dom! { <hr css={css!{"width: 100%; grid-column: 1 / 3;"}}/> },
dom! { <hr css={css! {"width: 100%; grid-column: 1 / 3;"}}/> },
section_rendered,
]
} else {
Expand All @@ -66,9 +66,9 @@ fn render_field(field: &DataField, validation_errors: &Value<ValidationErrors>)
})
};
dom! {
<div css={css!{"display: flex; flex-flow: column nowrap;"}}>
<div css={css! {"display: flex; flex-flow: column nowrap;"}}>
<Field {field} />
<span css={css!{"color: red;"}}>{val_error}</span>
<span css={css! {"color: red;"}}>{val_error}</span>
</div>
}
}
2 changes: 1 addition & 1 deletion src/input/input_with_button.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;"}}
/// />
/// };
/// ```
Expand Down
6 changes: 3 additions & 3 deletions src/resource_table/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,20 +125,20 @@ impl<Model: Clone + PartialEq + Default + 'static, ModelForm: Clone + 'static>
"} + &props.table_css;

dom! {
<div css={css!{"
<div css={css! {"
padding: 10px;
width: 100%;
"}}>
<div css={table_css}>
<div css={css!{"
<div css={css! {"
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px;
border-bottom: 1px solid #eee;
gap: 10px;
"}}>
<h2 css={css!{"margin: 0; font-size: 1.2rem; color: #333;"}}>{props.title.clone()}</h2>
<h2 css={css! {"margin: 0; font-size: 1.2rem; color: #333;"}}>{props.title.clone()}</h2>

{..props.render_filters.map(|render_filters| render_filters())}

Expand Down
Loading
Loading