feat: media editor uses quick actions dropdown like bDS2 (closes #9)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -432,13 +432,19 @@ impl BdsApp {
|
|||||||
&& let Some(state) = self.media_editors.get_mut(&tab_id)
|
&& let Some(state) = self.media_editors.get_mut(&tab_id)
|
||||||
{
|
{
|
||||||
match msg {
|
match msg {
|
||||||
|
MediaEditorMsg::ToggleQuickActions => {
|
||||||
|
state.quick_actions_open = !state.quick_actions_open;
|
||||||
|
}
|
||||||
MediaEditorMsg::AnalyzeWithAi => {
|
MediaEditorMsg::AnalyzeWithAi => {
|
||||||
|
state.quick_actions_open = false;
|
||||||
deferred = DeferredMediaAction::Analyze(tab_id.clone());
|
deferred = DeferredMediaAction::Analyze(tab_id.clone());
|
||||||
}
|
}
|
||||||
MediaEditorMsg::DetectLanguage => {
|
MediaEditorMsg::DetectLanguage => {
|
||||||
|
state.quick_actions_open = false;
|
||||||
deferred = DeferredMediaAction::DetectLanguage(tab_id.clone());
|
deferred = DeferredMediaAction::DetectLanguage(tab_id.clone());
|
||||||
}
|
}
|
||||||
MediaEditorMsg::TranslateMetadata => {
|
MediaEditorMsg::TranslateMetadata => {
|
||||||
|
state.quick_actions_open = false;
|
||||||
deferred = DeferredMediaAction::OpenTranslate(tab_id.clone());
|
deferred = DeferredMediaAction::OpenTranslate(tab_id.clone());
|
||||||
}
|
}
|
||||||
MediaEditorMsg::TranslateTo(target_language) => {
|
MediaEditorMsg::TranslateTo(target_language) => {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ use crate::app::Message;
|
|||||||
use crate::components::inputs;
|
use crate::components::inputs;
|
||||||
use crate::i18n::t;
|
use crate::i18n::t;
|
||||||
use crate::views::post_editor::TranslationFlag;
|
use crate::views::post_editor::TranslationFlag;
|
||||||
|
use crate::views::status_bar;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct LinkedPostItem {
|
pub struct LinkedPostItem {
|
||||||
@@ -59,6 +60,7 @@ pub struct MediaEditorState {
|
|||||||
pub post_picker_open: bool,
|
pub post_picker_open: bool,
|
||||||
pub post_picker_search: String,
|
pub post_picker_search: String,
|
||||||
pub post_picker_results: Vec<LinkedPostItem>,
|
pub post_picker_results: Vec<LinkedPostItem>,
|
||||||
|
pub quick_actions_open: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MediaEditorState {
|
impl MediaEditorState {
|
||||||
@@ -111,6 +113,7 @@ impl MediaEditorState {
|
|||||||
post_picker_open: false,
|
post_picker_open: false,
|
||||||
post_picker_search: String::new(),
|
post_picker_search: String::new(),
|
||||||
post_picker_results: Vec::new(),
|
post_picker_results: Vec::new(),
|
||||||
|
quick_actions_open: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,6 +206,7 @@ impl MediaEditorState {
|
|||||||
/// Media editor messages.
|
/// Media editor messages.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum MediaEditorMsg {
|
pub enum MediaEditorMsg {
|
||||||
|
ToggleQuickActions,
|
||||||
AnalyzeWithAi,
|
AnalyzeWithAi,
|
||||||
DetectLanguage,
|
DetectLanguage,
|
||||||
TranslateMetadata,
|
TranslateMetadata,
|
||||||
@@ -231,31 +235,19 @@ pub fn view<'a>(
|
|||||||
data_dir: Option<&Path>,
|
data_dir: Option<&Path>,
|
||||||
ai_enabled: bool,
|
ai_enabled: bool,
|
||||||
) -> Element<'a, Message> {
|
) -> Element<'a, Message> {
|
||||||
let header = inputs::toolbar(
|
let header_row = inputs::toolbar(
|
||||||
vec![text(state.original_name.clone()).size(18).into()],
|
vec![text(state.original_name.clone()).size(18).into()],
|
||||||
vec![
|
vec![
|
||||||
if state.mime_type.starts_with("image/") {
|
button(text(t(locale, "editor.quickActions")).size(13))
|
||||||
button(text(t(locale, "editor.aiAnalyze")).size(13))
|
|
||||||
.on_press_maybe(
|
.on_press_maybe(
|
||||||
ai_enabled.then_some(Message::MediaEditor(MediaEditorMsg::AnalyzeWithAi)),
|
ai_enabled
|
||||||
)
|
.then_some(Message::MediaEditor(MediaEditorMsg::ToggleQuickActions)),
|
||||||
.style(inputs::secondary_button)
|
|
||||||
.padding([6, 16])
|
|
||||||
.into()
|
|
||||||
} else {
|
|
||||||
Space::new(0, 0).into()
|
|
||||||
},
|
|
||||||
button(text(t(locale, "editor.detectLanguage")).size(13))
|
|
||||||
.on_press_maybe(
|
|
||||||
ai_enabled.then_some(Message::MediaEditor(MediaEditorMsg::DetectLanguage)),
|
|
||||||
)
|
)
|
||||||
.style(inputs::secondary_button)
|
.style(inputs::secondary_button)
|
||||||
.padding([6, 16])
|
.padding([6, 16])
|
||||||
.into(),
|
.into(),
|
||||||
button(text(t(locale, "editor.translate")).size(13))
|
button(text(t(locale, "editor.replaceFile")).size(13))
|
||||||
.on_press_maybe(
|
.on_press(Message::MediaEditor(MediaEditorMsg::ReplaceFile))
|
||||||
ai_enabled.then_some(Message::MediaEditor(MediaEditorMsg::TranslateMetadata)),
|
|
||||||
)
|
|
||||||
.style(inputs::secondary_button)
|
.style(inputs::secondary_button)
|
||||||
.padding([6, 16])
|
.padding([6, 16])
|
||||||
.into(),
|
.into(),
|
||||||
@@ -264,11 +256,6 @@ pub fn view<'a>(
|
|||||||
.style(inputs::primary_button)
|
.style(inputs::primary_button)
|
||||||
.padding([6, 16])
|
.padding([6, 16])
|
||||||
.into(),
|
.into(),
|
||||||
button(text(t(locale, "editor.replaceFile")).size(13))
|
|
||||||
.on_press(Message::MediaEditor(MediaEditorMsg::ReplaceFile))
|
|
||||||
.style(inputs::secondary_button)
|
|
||||||
.padding([6, 16])
|
|
||||||
.into(),
|
|
||||||
button(text(t(locale, "modal.confirmDelete.delete")).size(13))
|
button(text(t(locale, "modal.confirmDelete.delete")).size(13))
|
||||||
.on_press(Message::MediaEditor(MediaEditorMsg::Delete))
|
.on_press(Message::MediaEditor(MediaEditorMsg::Delete))
|
||||||
.style(inputs::danger_button)
|
.style(inputs::danger_button)
|
||||||
@@ -277,6 +264,39 @@ pub fn view<'a>(
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let header_menu: Element<'a, Message> = if state.quick_actions_open {
|
||||||
|
let mut items: Vec<Element<'a, Message>> = Vec::new();
|
||||||
|
if state.mime_type.starts_with("image/") {
|
||||||
|
items.push(quick_action_item(
|
||||||
|
t(locale, "editor.aiAnalyze"),
|
||||||
|
MediaEditorMsg::AnalyzeWithAi,
|
||||||
|
ai_enabled,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
items.push(quick_action_item(
|
||||||
|
t(locale, "editor.detectLanguage"),
|
||||||
|
MediaEditorMsg::DetectLanguage,
|
||||||
|
ai_enabled,
|
||||||
|
));
|
||||||
|
items.push(quick_action_item(
|
||||||
|
t(locale, "editor.translate"),
|
||||||
|
MediaEditorMsg::TranslateMetadata,
|
||||||
|
ai_enabled,
|
||||||
|
));
|
||||||
|
row![
|
||||||
|
Space::with_width(Length::Fill),
|
||||||
|
container(column(items).spacing(4))
|
||||||
|
.padding(8)
|
||||||
|
.style(status_bar::dropdown_bg),
|
||||||
|
]
|
||||||
|
.align_y(iced::Alignment::Start)
|
||||||
|
.into()
|
||||||
|
} else {
|
||||||
|
Space::new(0, 0).into()
|
||||||
|
};
|
||||||
|
|
||||||
|
let header = column![header_row, header_menu].spacing(6);
|
||||||
|
|
||||||
// Translation flags bar
|
// Translation flags bar
|
||||||
let flags = state.translation_flags();
|
let flags = state.translation_flags();
|
||||||
let flags_bar: Element<'a, Message> = if flags.is_empty() {
|
let flags_bar: Element<'a, Message> = if flags.is_empty() {
|
||||||
@@ -529,6 +549,15 @@ pub fn view<'a>(
|
|||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn quick_action_item<'a>(label: String, msg: MediaEditorMsg, enabled: bool) -> Element<'a, Message> {
|
||||||
|
button(text(label).size(12).shaping(Shaping::Advanced))
|
||||||
|
.on_press_maybe(enabled.then_some(Message::MediaEditor(msg)))
|
||||||
|
.padding([6, 12])
|
||||||
|
.style(status_bar::dropdown_item)
|
||||||
|
.width(Length::Fixed(220.0))
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
fn no_preview<'a>(locale: UiLocale) -> Element<'a, Message> {
|
fn no_preview<'a>(locale: UiLocale) -> Element<'a, Message> {
|
||||||
container(
|
container(
|
||||||
text(t(locale, "editor.noPreviewAvailable"))
|
text(t(locale, "editor.noPreviewAvailable"))
|
||||||
@@ -581,6 +610,12 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn quick_actions_menu_starts_closed() {
|
||||||
|
let state = MediaEditorState::from_media(&make_media(), &["en".to_string()], &[], Vec::new());
|
||||||
|
assert!(!state.quick_actions_open);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn translation_flags_include_configured_languages_without_translations() {
|
fn translation_flags_include_configured_languages_without_translations() {
|
||||||
let state = MediaEditorState::from_media(
|
let state = MediaEditorState::from_media(
|
||||||
|
|||||||
Reference in New Issue
Block a user