From 40fb6f2617c52f2d377bc1069b3f18c78458abf0 Mon Sep 17 00:00:00 2001 From: Exidex <16986685+exidex@users.noreply.github.com> Date: Sun, 11 May 2025 13:25:13 +0200 Subject: [PATCH] Implement entrypoint search aliases --- CHANGELOG.md | 16 +- rust/client/src/ui/search_list.rs | 12 ++ rust/client/src/ui/theme/container.rs | 20 ++ rust/client/src/ui/theme/mod.rs | 13 ++ rust/common/src/model.rs | 1 + rust/common/src/rpc/backend_api.rs | 9 + .../src/components/shortcut_selector.rs | 2 +- .../management_client/src/theme/text_input.rs | 29 ++- rust/management_client/src/ui.rs | 21 +- rust/management_client/src/views/plugins.rs | 127 +++++++++--- .../src/views/plugins/table.rs | 100 +++++++++- rust/server/src/plugins/data_db_repository.rs | 9 + rust/server/src/plugins/js.rs | 1 + rust/server/src/plugins/mod.rs | 25 ++- rust/server/src/plugins/settings.rs | 75 ++++++- rust/server/src/rpc.rs | 19 ++ rust/server/src/search.rs | 185 +++++++++++++----- 17 files changed, 565 insertions(+), 99 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ff3241..eb6ad17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,17 +9,23 @@ For changes in `@project-gauntlet/tools` see [separate CHANGELOG.md](https://git ## [Unreleased] +### General +- It is now possible to assign custom alias to entrypoints which is used for search - Windows in "Opened windows" view entrypoint are now sorted following "most recently focused on the top" order + +### Plugins - Plugin manifest property `entrypoint.*.actions.*.shortcut` is now optional -- Add `` component to display SVG images -- **BREAKING CHANGE**: Renamed TS types: `ImageSource` to `DataSource`, `ImageSourceUrl` to `DataSourceUrl`, `ImageSourceAsset` to `DataSourceAsset` +- Added `` component to display SVG images +- **BREAKING CHANGE**: Renamed TypeScript types: `ImageSource` to `DataSource`, `ImageSourceUrl` to `DataSourceUrl`, `ImageSourceAsset` to `DataSourceAsset` + +### UI/UX improvements +- Made font size in the Settings UI a little smaller ### Fixes -- Fixed crash when closing inline view due to Action being run +- Fixed crash when closing inline view due to Action being run, again... - Fixed shortcut assignment error not being shown for global entrypoint shortcuts - Fixed crash on X11 when trying to assign shortcut that is already used by another application -- Unified `Vec` usage to `ArrayBuffer` in JS - - Fixes `icon` in EntrypointGenerator requiring `number[]` +- Fixed `icon` in `EntrypointGenerator` requiring `number[]` instead of declared `ArrayBuffer` - Fixed text selection not being visible when selecting text in form view text fields - Fixed plugin runtime crash when using `assetDataSync()` function diff --git a/rust/client/src/ui/search_list.rs b/rust/client/src/ui/search_list.rs index aa28dbe..c8a5533 100644 --- a/rust/client/src/ui/search_list.rs +++ b/rust/client/src/ui/search_list.rs @@ -7,6 +7,7 @@ use gauntlet_common::model::SearchResultAccessory; use gauntlet_common::model::SearchResultEntrypointType; use gauntlet_common::model::TextAccessoryWidget; use iced::advanced::image::Handle; +use iced::color; use iced::widget::button; use iced::widget::column; use iced::widget::container; @@ -14,7 +15,9 @@ use iced::widget::horizontal_space; use iced::widget::row; use iced::widget::text; use iced::widget::text::Shaping; +use iced::widget::text_input; use iced::Alignment; +use iced::Font; use iced::Length; use crate::ui::scroll_handle::ScrollHandle; @@ -67,6 +70,15 @@ pub fn search_list<'a>( button_content.push(entrypoint_name); button_content.push(plugin_name_text); + + if let Some(alias) = &search_result.entrypoint_alias { + let alias: Element<_> = text(alias.clone()).shaping(Shaping::Advanced).into(); + + let alias: Element<_> = container(alias).themed(ContainerStyle::MainListItemAlias).into(); + + button_content.push(alias); + } + button_content.push(spacer); if search_result.entrypoint_accessories.len() > 0 { diff --git a/rust/client/src/ui/theme/container.rs b/rust/client/src/ui/theme/container.rs index 4d37af8..5cc7971 100644 --- a/rust/client/src/ui/theme/container.rs +++ b/rust/client/src/ui/theme/container.rs @@ -36,6 +36,7 @@ pub enum ContainerStyle { Main, MainList, MainListInner, + MainListItemAlias, MainListItemIcon, MainListItemSubText, MainListItemText, @@ -81,6 +82,7 @@ pub enum ContainerStyleInner { ActionShortcutModifier, ContentCodeBlockText, Main, + MainListItemAlias, Root, ContentImage, RootBottomPanel, @@ -257,6 +259,20 @@ impl container::Catalog for GauntletComplexTheme { shadow: Default::default(), } } + ContainerStyleInner::MainListItemAlias => { + let theme = &self.main_list_item_alias; + + Style { + text_color: None, + background: None, + border: Border { + color: theme.background_color.clone().into(), + width: 2.0, + radius: theme.border_radius.into(), + }, + shadow: Default::default(), + } + } } } } @@ -334,6 +350,10 @@ impl<'a, Message: 'a> ThemableWidget<'a, Message> for Container<'a, Message, Gau ContainerStyle::MainListItemText => self.padding(theme.main_list_item_text.padding.to_iced()), ContainerStyle::MainListItemSubText => self.padding(theme.main_list_item_sub_text.padding.to_iced()), ContainerStyle::MainListItemIcon => self.padding(theme.main_list_item_icon.padding.to_iced()), + ContainerStyle::MainListItemAlias => { + self.padding(theme.main_list_item_alias.padding.to_iced()) + .class(ContainerStyleInner::MainListItemAlias) + } ContainerStyle::MainList => self.padding(theme.main_list.padding.to_iced()), ContainerStyle::MainListInner => self.padding(theme.main_list_inner.padding.to_iced()), ContainerStyle::MainSearchBar => self.padding(theme.main_search_bar.padding.to_iced()), diff --git a/rust/client/src/ui/theme/mod.rs b/rust/client/src/ui/theme/mod.rs index ec779b5..28a4414 100644 --- a/rust/client/src/ui/theme/mod.rs +++ b/rust/client/src/ui/theme/mod.rs @@ -81,6 +81,7 @@ pub struct GauntletComplexTheme { main_list: ThemePaddingOnly, main_list_inner: ThemePaddingOnly, main_list_item: ThemeButton, + main_list_item_alias: ThemeEntrypointAlias, main_list_item_icon: ThemePaddingOnly, main_list_item_sub_text: ThemePaddingTextColor, main_list_item_text: ThemePaddingOnly, @@ -450,6 +451,11 @@ impl GauntletComplexTheme { border_width: 0.0, border_color: Color::TRANSPARENT, }, + main_list_item_alias: ThemeEntrypointAlias { + padding: padding_axis(4.0, 6.0), + background_color: background_100, + border_radius: content.border.radius, + }, main_list_item_text: ThemePaddingOnly { padding: padding_all(4.0), }, @@ -768,6 +774,13 @@ pub struct ThemePaddingTextColor { text_color: Color, } +#[derive(Debug, Clone)] +pub struct ThemeEntrypointAlias { + padding: ThemePadding, + background_color: Color, + border_radius: f32, +} + #[derive(Debug, Clone)] pub struct ThemePaddingTextColorSize { padding: ThemePadding, diff --git a/rust/common/src/model.rs b/rust/common/src/model.rs index d0d4ae9..145fc8f 100644 --- a/rust/common/src/model.rs +++ b/rust/common/src/model.rs @@ -113,6 +113,7 @@ pub struct SearchResult { pub entrypoint_type: SearchResultEntrypointType, pub entrypoint_actions: Vec, pub entrypoint_accessories: Vec, + pub entrypoint_alias: Option, } #[derive(Debug, Clone)] diff --git a/rust/common/src/rpc/backend_api.rs b/rust/common/src/rpc/backend_api.rs index 806cd83..567c858 100644 --- a/rust/common/src/rpc/backend_api.rs +++ b/rust/common/src/rpc/backend_api.rs @@ -142,6 +142,15 @@ pub trait BackendForSettingsApi { &self, ) -> RequestResult)>>; + async fn set_entrypoint_search_alias( + &self, + plugin_id: PluginId, + entrypoint_id: EntrypointId, + alias: Option, + ) -> RequestResult<()>; + + async fn get_entrypoint_search_aliases(&self) -> RequestResult>; + async fn set_theme(&self, theme: SettingsTheme) -> RequestResult<()>; async fn get_theme(&self) -> RequestResult; diff --git a/rust/management_client/src/components/shortcut_selector.rs b/rust/management_client/src/components/shortcut_selector.rs index 943879f..5c2453f 100644 --- a/rust/management_client/src/components/shortcut_selector.rs +++ b/rust/management_client/src/components/shortcut_selector.rs @@ -393,7 +393,7 @@ pub fn render_shortcut<'a, Message: 'a>(shortcut: &ShortcutData, in_table: bool) } } else { if in_table { - content.push(text("Record Shortcut").class(TextStyle::Subtitle).into()); + content.push(text("Record Shortcut").size(14).class(TextStyle::Subtitle).into()); if let Some(error) = &shortcut.error { content.push(horizontal_space().width(Length::Fill).into()); diff --git a/rust/management_client/src/theme/text_input.rs b/rust/management_client/src/theme/text_input.rs index 80eaadb..c45e447 100644 --- a/rust/management_client/src/theme/text_input.rs +++ b/rust/management_client/src/theme/text_input.rs @@ -6,13 +6,16 @@ use iced::Border; use crate::theme::GauntletSettingsTheme; use crate::theme::BACKGROUND_DARKER; +use crate::theme::BACKGROUND_LIGHTER; use crate::theme::BACKGROUND_LIGHTEST; +use crate::theme::BUTTON_BORDER_RADIUS; use crate::theme::TEXT_DARKER; use crate::theme::TEXT_LIGHTEST; use crate::theme::TRANSPARENT; pub enum TextInputStyle { FormInput, + EntrypointAlias, } impl text_input::Catalog for GauntletSettingsTheme { @@ -22,7 +25,31 @@ impl text_input::Catalog for GauntletSettingsTheme { TextInputStyle::FormInput } - fn style(&self, _class: &Self::Class<'_>, status: Status) -> Style { + fn style(&self, class: &Self::Class<'_>, status: Status) -> Style { + match class { + TextInputStyle::EntrypointAlias => { + let border = if let Status::Focused | Status::Hovered = status { + Border { + radius: BUTTON_BORDER_RADIUS.into(), + width: 2.0, + color: BACKGROUND_LIGHTER.to_iced(), + } + } else { + Border::default() + }; + + return Style { + background: Background::Color(TRANSPARENT.to_iced().into()), + border, + icon: TEXT_LIGHTEST.to_iced(), + placeholder: TEXT_DARKER.to_iced(), + value: TEXT_LIGHTEST.to_iced(), + selection: BACKGROUND_LIGHTEST.to_iced(), + }; + } + TextInputStyle::FormInput => {} + } + let active = Style { background: Background::Color(TRANSPARENT.to_iced().into()), border: Border { diff --git a/rust/management_client/src/ui.rs b/rust/management_client/src/ui.rs index 1054c33..37d72f4 100644 --- a/rust/management_client/src/ui.rs +++ b/rust/management_client/src/ui.rs @@ -63,7 +63,7 @@ pub fn run() { view, ) .window(window::Settings { - size: Size::new(1000.0, 600.0), + size: Size::new(1150.0, 700.0), ..Default::default() }) .subscription(subscription) @@ -261,16 +261,21 @@ fn update(state: &mut ManagementAppModel, message: ManagementAppMsg) -> Task, HashMap<(PluginId, EntrypointId), (PhysicalShortcut, Option)>, + HashMap<(PluginId, EntrypointId), String>, ), RemovePlugin { plugin_id: PluginId, @@ -91,6 +92,7 @@ pub struct ManagementAppPluginsState { preference_user_data: HashMap<(PluginId, Option, String), PluginPreferenceUserDataState>, selected_item: SelectedItem, global_entrypoint_shortcuts: HashMap<(PluginId, EntrypointId), (PhysicalShortcut, Option)>, + entrypoint_search_aliases: HashMap<(PluginId, EntrypointId), String>, } impl ManagementAppPluginsState { @@ -127,6 +129,7 @@ impl ManagementAppPluginsState { selected_item: select_item, table_state: PluginTableState::new(), global_entrypoint_shortcuts: HashMap::new(), + entrypoint_search_aliases: HashMap::new(), } } @@ -157,16 +160,21 @@ impl ManagementAppPluginsState { let plugins = backend_client.plugins().await?; let global_entrypoint_shortcuts = backend_client.get_global_entrypoint_shortcuts().await?; + let entrypoint_aliases = backend_client.get_entrypoint_search_aliases().await?; - Ok((plugins, global_entrypoint_shortcuts)) + Ok((plugins, global_entrypoint_shortcuts, entrypoint_aliases)) }, |result| { - handle_backend_error(result, |(plugins, global_entrypoint_shortcuts)| { - ManagementAppPluginMsgOut::Inner(ManagementAppPluginMsgIn::PluginsReloaded( - plugins, - global_entrypoint_shortcuts, - )) - }) + handle_backend_error( + result, + |(plugins, global_entrypoint_shortcuts, entrypoint_aliases)| { + ManagementAppPluginMsgOut::Inner(ManagementAppPluginMsgIn::PluginsReloaded( + plugins, + global_entrypoint_shortcuts, + entrypoint_aliases, + )) + }, + ) }, ) } @@ -186,16 +194,21 @@ impl ManagementAppPluginsState { let plugins = backend_client.plugins().await?; let global_entrypoint_shortcuts = backend_client.get_global_entrypoint_shortcuts().await?; + let entrypoint_aliases = backend_client.get_entrypoint_search_aliases().await?; - Ok((plugins, global_entrypoint_shortcuts)) + Ok((plugins, global_entrypoint_shortcuts, entrypoint_aliases)) }, |result| { - handle_backend_error(result, |(plugins, global_entrypoint_shortcuts)| { - ManagementAppPluginMsgOut::Inner(ManagementAppPluginMsgIn::PluginsReloaded( - plugins, - global_entrypoint_shortcuts, - )) - }) + handle_backend_error( + result, + |(plugins, global_entrypoint_shortcuts, entrypoint_aliases)| { + ManagementAppPluginMsgOut::Inner(ManagementAppPluginMsgIn::PluginsReloaded( + plugins, + global_entrypoint_shortcuts, + entrypoint_aliases, + )) + }, + ) }, ) } @@ -232,16 +245,51 @@ impl ManagementAppPluginsState { let plugins = backend_client.plugins().await?; let global_entrypoint_shortcuts = backend_client.get_global_entrypoint_shortcuts().await?; + let entrypoint_aliases = backend_client.get_entrypoint_search_aliases().await?; - Ok((plugins, global_entrypoint_shortcuts)) + Ok((plugins, global_entrypoint_shortcuts, entrypoint_aliases)) }, |result| { - handle_backend_error(result, |(plugins, global_entrypoint_shortcuts)| { - ManagementAppPluginMsgOut::Inner(ManagementAppPluginMsgIn::PluginsReloaded( - plugins, - global_entrypoint_shortcuts, - )) - }) + handle_backend_error( + result, + |(plugins, global_entrypoint_shortcuts, entrypoint_aliases)| { + ManagementAppPluginMsgOut::Inner(ManagementAppPluginMsgIn::PluginsReloaded( + plugins, + global_entrypoint_shortcuts, + entrypoint_aliases, + )) + }, + ) + }, + ) + } + PluginTableMsgOut::AliasChanged(plugin_id, entrypoint_id, shortcut) => { + let mut backend_client = backend_api.clone(); + + Task::perform( + async move { + backend_client + .set_entrypoint_search_alias(plugin_id, entrypoint_id, shortcut) + .await?; + + let plugins = backend_client.plugins().await?; + let global_entrypoint_shortcuts = + backend_client.get_global_entrypoint_shortcuts().await?; + let entrypoint_aliases = backend_client.get_entrypoint_search_aliases().await?; + + Ok((plugins, global_entrypoint_shortcuts, entrypoint_aliases)) + }, + |result| { + handle_backend_error( + result, + |(plugins, global_entrypoint_shortcuts, entrypoint_aliases)| { + ManagementAppPluginMsgOut::Inner(ManagementAppPluginMsgIn::PluginsReloaded( + plugins, + global_entrypoint_shortcuts, + entrypoint_aliases, + )) + }, + ) }, ) } @@ -257,7 +305,11 @@ impl ManagementAppPluginsState { plugin_data.plugins.clone() }; - self.apply_plugin_fetch(plugins, self.global_entrypoint_shortcuts.clone()); + self.apply_plugin_fetch( + plugins, + self.global_entrypoint_shortcuts.clone(), + self.entrypoint_search_aliases.clone(), + ); Task::none() } @@ -278,7 +330,11 @@ impl ManagementAppPluginsState { plugin_data.plugins.clone() }; - self.apply_plugin_fetch(plugins, self.global_entrypoint_shortcuts.clone()); + self.apply_plugin_fetch( + plugins, + self.global_entrypoint_shortcuts.clone(), + self.entrypoint_search_aliases.clone(), + ); Task::none() } @@ -321,21 +377,23 @@ impl ManagementAppPluginsState { async move { let plugins = backend_api.plugins().await?; let global_entrypoint_shortcuts = backend_api.get_global_entrypoint_shortcuts().await?; + let entrypoint_aliases = backend_api.get_entrypoint_search_aliases().await?; - Ok((plugins, global_entrypoint_shortcuts)) + Ok((plugins, global_entrypoint_shortcuts, entrypoint_aliases)) }, |result| { - handle_backend_error(result, |(plugins, global_entrypoint_shortcuts)| { + handle_backend_error(result, |(plugins, global_entrypoint_shortcuts, entrypoint_aliases)| { ManagementAppPluginMsgOut::Inner(ManagementAppPluginMsgIn::PluginsReloaded( plugins, global_entrypoint_shortcuts, + entrypoint_aliases, )) }) }, ) } - ManagementAppPluginMsgIn::PluginsReloaded(plugins, shortcuts) => { - self.apply_plugin_fetch(plugins, shortcuts); + ManagementAppPluginMsgIn::PluginsReloaded(plugins, shortcuts, entrypoint_aliases) => { + self.apply_plugin_fetch(plugins, shortcuts, entrypoint_aliases); Task::none() } @@ -350,14 +408,16 @@ impl ManagementAppPluginsState { let plugins = backend_client.plugins().await?; let global_entrypoint_shortcuts = backend_client.get_global_entrypoint_shortcuts().await?; + let entrypoint_aliases = backend_client.get_entrypoint_search_aliases().await?; - Ok((plugins, global_entrypoint_shortcuts)) + Ok((plugins, global_entrypoint_shortcuts, entrypoint_aliases)) }, |result| { - handle_backend_error(result, |(plugins, global_entrypoint_shortcuts)| { + handle_backend_error(result, |(plugins, global_entrypoint_shortcuts, entrypoint_aliases)| { ManagementAppPluginMsgOut::Inner(ManagementAppPluginMsgIn::PluginsReloaded( plugins, global_entrypoint_shortcuts, + entrypoint_aliases, )) }) }, @@ -381,6 +441,7 @@ impl ManagementAppPluginsState { &mut self, plugins: HashMap, global_entrypoint_shortcuts: HashMap<(PluginId, EntrypointId), (PhysicalShortcut, Option)>, + entrypoint_search_aliases: HashMap<(PluginId, EntrypointId), String>, ) { self.global_entrypoint_shortcuts = global_entrypoint_shortcuts.clone(); @@ -458,8 +519,12 @@ impl ManagementAppPluginsState { plugin_refs.sort_by_key(|(plugin, _)| &plugin.plugin_name); - self.table_state - .apply_plugin_reload(self.plugin_data.clone(), plugin_refs, global_entrypoint_shortcuts) + self.table_state.apply_plugin_reload( + self.plugin_data.clone(), + plugin_refs, + global_entrypoint_shortcuts, + entrypoint_search_aliases, + ) } pub fn view(&self) -> Element { diff --git a/rust/management_client/src/views/plugins/table.rs b/rust/management_client/src/views/plugins/table.rs index aa325fe..d6562ab 100644 --- a/rust/management_client/src/views/plugins/table.rs +++ b/rust/management_client/src/views/plugins/table.rs @@ -17,6 +17,7 @@ use iced::widget::row; use iced::widget::scrollable; use iced::widget::scrollable::Id; use iced::widget::text; +use iced::widget::text_input; use iced::widget::value; use iced::widget::Space; use iced::Alignment; @@ -31,6 +32,8 @@ use crate::components::shortcut_selector::shortcut_selector; use crate::components::shortcut_selector::ShortcutData; use crate::theme::button::ButtonStyle; use crate::theme::container::ContainerStyle; +use crate::theme::text::TextStyle; +use crate::theme::text_input::TextInputStyle; use crate::theme::Element; use crate::theme::GauntletSettingsTheme; use crate::views::plugins::PluginDataContainer; @@ -50,6 +53,7 @@ pub enum PluginTableMsgIn { entrypoint_id: EntrypointId, }, ShortcutCaptured(PluginId, EntrypointId, Option), + AliasChanged(PluginId, EntrypointId, String), } pub enum PluginTableMsgOut { @@ -71,6 +75,7 @@ pub enum PluginTableMsgOut { entrypoint_id: EntrypointId, }, ShortcutCaptured(PluginId, EntrypointId, Option), + AliasChanged(PluginId, EntrypointId, Option), } pub struct PluginTableState { @@ -86,6 +91,7 @@ impl PluginTableState { columns: vec![ Column::new(ColumnKind::Name), Column::new(ColumnKind::Type), + Column::new(ColumnKind::Alias), Column::new(ColumnKind::Shortcut), Column::new(ColumnKind::EnableToggle), ], @@ -132,6 +138,12 @@ impl PluginTableState { PluginTableMsgIn::ShortcutCaptured(plugin_id, entrypoint_id, shortcut) => { Task::done(PluginTableMsgOut::ShortcutCaptured(plugin_id, entrypoint_id, shortcut)) } + PluginTableMsgIn::AliasChanged(plugin_id, entrypoint_id, alias) => { + let alias = alias.trim().to_owned(); + let alias = if alias.is_empty() { None } else { Some(alias) }; + + Task::done(PluginTableMsgOut::AliasChanged(plugin_id, entrypoint_id, alias)) + } } } @@ -140,6 +152,7 @@ impl PluginTableState { plugin_data: Rc>, plugin_refs: Vec<(&SettingsPlugin, &SettingsPluginData)>, global_entrypoint_shortcuts: HashMap<(PluginId, EntrypointId), (PhysicalShortcut, Option)>, + entrypoint_search_aliases: HashMap<(PluginId, EntrypointId), String>, ) { self.rows = plugin_refs .iter() @@ -162,11 +175,16 @@ impl PluginTableState { let shortcut = global_entrypoint_shortcut.map(|(shortcut, _)| shortcut).cloned(); let error = global_entrypoint_shortcut.map(|(_, error)| error).cloned().flatten(); + let search_alias = entrypoint_search_aliases + .get(&(plugin.plugin_id.clone(), entrypoint.entrypoint_id.clone())) + .cloned(); + let entrypoint_row = Row::Entrypoint { plugin_data: plugin_data.clone(), plugin_id: plugin.plugin_id.clone(), entrypoint_id: entrypoint.entrypoint_id.clone(), shortcut_data: ShortcutData { shortcut, error }, + search_alias, }; result.push(entrypoint_row); @@ -192,12 +210,17 @@ impl PluginTableState { let shortcut = global_entrypoint_shortcut.map(|(shortcut, _)| shortcut).cloned(); let error = global_entrypoint_shortcut.map(|(_, error)| error).cloned().flatten(); + let search_alias = entrypoint_search_aliases + .get(&(plugin.plugin_id.clone(), data.entrypoint_id.clone())) + .cloned(); + let generated_entrypoint_row = Row::GeneratedEntrypoint { plugin_data: plugin_data.clone(), plugin_id: plugin.plugin_id.clone(), generator_entrypoint_id: entrypoint.entrypoint_id.clone(), generated_entrypoint_id: data.entrypoint_id.clone(), shortcut_data: ShortcutData { shortcut, error }, + search_alias, }; result.push(generated_entrypoint_row); @@ -247,6 +270,7 @@ enum Row { plugin_id: PluginId, entrypoint_id: EntrypointId, shortcut_data: ShortcutData, + search_alias: Option, }, GeneratedEntrypoint { plugin_data: Rc>, @@ -254,6 +278,7 @@ enum Row { generator_entrypoint_id: EntrypointId, generated_entrypoint_id: EntrypointId, shortcut_data: ShortcutData, + search_alias: Option, }, } @@ -261,6 +286,7 @@ enum ColumnKind { Name, Type, Shortcut, + Alias, EnableToggle, } @@ -292,6 +318,12 @@ impl<'a> table::Column<'a, PluginTableMsgIn, GauntletSettingsTheme, Renderer> fo .align_y(Alignment::Center) .into() } + ColumnKind::Alias => { + container(text("Alias")) + .height(Length::Fixed(30.0)) + .align_y(Alignment::Center) + .into() + } ColumnKind::Shortcut => { container(text("Shortcut")) .height(Length::Fixed(30.0)) @@ -385,7 +417,7 @@ impl<'a> table::Column<'a, PluginTableMsgIn, GauntletSettingsTheme, Renderer> fo let plugin_data = plugin_data.borrow(); let plugin = plugin_data.plugins.get(&plugin_id).unwrap(); - let plugin_name = text(plugin.plugin_name.to_string()).shaping(Shaping::Advanced); + let plugin_name = text(plugin.plugin_name.to_string()).shaping(Shaping::Advanced).size(14); container(plugin_name).align_y(Alignment::Center).into() } @@ -401,6 +433,7 @@ impl<'a> table::Column<'a, PluginTableMsgIn, GauntletSettingsTheme, Renderer> fo let text: Element<_> = text(entrypoint.entrypoint_name.to_string()) .shaping(Shaping::Advanced) + .size(14) .into(); let space: Element<_> = @@ -429,6 +462,7 @@ impl<'a> table::Column<'a, PluginTableMsgIn, GauntletSettingsTheme, Renderer> fo let text: Element<_> = text(generated_entrypoint.entrypoint_name.to_string()) .shaping(Shaping::Advanced) + .size(14) .into(); let space: Element<_> = Space::with_width(Length::Fixed(65.0)).into(); @@ -481,7 +515,7 @@ impl<'a> table::Column<'a, PluginTableMsgIn, GauntletSettingsTheme, Renderer> fo } ColumnKind::Type => { let content: Element<_> = match row_entry { - Row::Plugin { .. } => container(text("Plugin")).align_y(Alignment::Center).into(), + Row::Plugin { .. } => container(text("Plugin").size(14)).align_y(Alignment::Center).into(), Row::Entrypoint { plugin_data, plugin_id, @@ -499,11 +533,13 @@ impl<'a> table::Column<'a, PluginTableMsgIn, GauntletSettingsTheme, Renderer> fo SettingsEntrypointType::EntrypointGenerator => "Generator", }; - container(text(entrypoint_type.to_string())) + container(text(entrypoint_type.to_string()).size(14)) .align_y(Alignment::Center) .into() } - Row::GeneratedEntrypoint { .. } => container(text("Generated")).align_y(Alignment::Center).into(), + Row::GeneratedEntrypoint { .. } => { + container(text("Generated").size(14)).align_y(Alignment::Center).into() + } }; let msg = match &row_entry { @@ -552,6 +588,7 @@ impl<'a> table::Column<'a, PluginTableMsgIn, GauntletSettingsTheme, Renderer> fo plugin_id, entrypoint_id, shortcut_data, + .. } => { let plugin_data = plugin_data.borrow(); let plugin = plugin_data.plugins.get(&plugin_id).unwrap(); @@ -607,6 +644,58 @@ impl<'a> table::Column<'a, PluginTableMsgIn, GauntletSettingsTheme, Renderer> fo } } } + ColumnKind::Alias => { + match row_entry { + Row::Plugin { .. } => horizontal_space().into(), + Row::Entrypoint { + plugin_data, + plugin_id, + entrypoint_id, + search_alias, + .. + } => { + let plugin_data = plugin_data.borrow(); + let plugin = plugin_data.plugins.get(&plugin_id).unwrap(); + let entrypoint = plugin.entrypoints.get(&entrypoint_id).unwrap(); + + if let SettingsEntrypointType::View | SettingsEntrypointType::Command = + entrypoint.entrypoint_type + { + let input = text_input("Add Alias", search_alias.as_deref().unwrap_or("")) + .class(TextInputStyle::EntrypointAlias) + .padding(padding::all(12.0).left(7.0)) + .size(14) + .on_input(move |alias| { + PluginTableMsgIn::AliasChanged(plugin_id.clone(), entrypoint_id.clone(), alias) + }); + + container(input).height(Length::Fixed(40.0)).width(Length::Fill).into() + } else { + horizontal_space().into() + } + } + Row::GeneratedEntrypoint { + plugin_id, + generated_entrypoint_id, + search_alias, + .. + } => { + let input = text_input("Add Alias", search_alias.as_deref().unwrap_or("")) + .class(TextInputStyle::EntrypointAlias) + .padding(padding::all(12.0).left(7.0)) + .size(14) + .on_input(move |alias| { + PluginTableMsgIn::AliasChanged( + plugin_id.clone(), + generated_entrypoint_id.clone(), + alias, + ) + }); + + container(input).height(Length::Fixed(40.0)).width(Length::Fill).into() + } + } + } ColumnKind::EnableToggle => { let (enabled, show_checkbox, plugin_id, entrypoint_id) = match &row_entry { Row::Plugin { plugin_data, plugin_id } => { @@ -674,7 +763,8 @@ impl<'a> table::Column<'a, PluginTableMsgIn, GauntletSettingsTheme, Renderer> fo match self.kind { ColumnKind::Name => 300.0, ColumnKind::Type => 100.0, - ColumnKind::Shortcut => 200.0, + ColumnKind::Shortcut => 190.0, + ColumnKind::Alias => 120.0, ColumnKind::EnableToggle => 75.0, } } diff --git a/rust/server/src/plugins/data_db_repository.rs b/rust/server/src/plugins/data_db_repository.rs index 85d6072..07b4679 100644 --- a/rust/server/src/plugins/data_db_repository.rs +++ b/rust/server/src/plugins/data_db_repository.rs @@ -242,6 +242,13 @@ pub struct DbSettingsGlobalEntrypointShortcutData { pub shortcut: DbSettingsGlobalShortcutData, } +#[derive(Debug, Deserialize, Serialize)] +pub struct DbSettingsEntrypointSearchAliasData { + pub plugin_id: String, + pub entrypoint_id: String, + pub alias: String, +} + #[derive(Debug, Deserialize, Serialize)] pub struct DbSettings { // none means auto-detect @@ -251,6 +258,7 @@ pub struct DbSettings { // none is unset, if whole settings object is unset, it is likely a first start and default shortcut will be used pub global_shortcut: Option, pub global_entrypoint_shortcuts: Option>, + pub entrypoint_search_aliases: Option>, } impl Default for DbSettings { @@ -281,6 +289,7 @@ impl Default for DbSettings { error: None, }), global_entrypoint_shortcuts: None, + entrypoint_search_aliases: None, } } } diff --git a/rust/server/src/plugins/js.rs b/rust/server/src/plugins/js.rs index 8f53f61..d02418f 100644 --- a/rust/server/src/plugins/js.rs +++ b/rust/server/src/plugins/js.rs @@ -827,6 +827,7 @@ impl BackendForPluginRuntimeApi for BackendForPluginRuntimeApiImpl { generated_search_items, refresh_search_list, ) + .await .context("error when updating search index")?; Ok(()) diff --git a/rust/server/src/plugins/mod.rs b/rust/server/src/plugins/mod.rs index 0502621..c015e99 100644 --- a/rust/server/src/plugins/mod.rs +++ b/rust/server/src/plugins/mod.rs @@ -91,7 +91,7 @@ mod loader; pub mod plugin_manifest; mod run_status; mod runtime; -mod settings; +pub mod settings; pub mod theme; static BUNDLED_PLUGINS: [(&str, Dir); 1] = [( @@ -124,9 +124,9 @@ impl ApplicationManager { let config_reader = ConfigReader::new(dirs.clone(), db_repository.clone()); let icon_cache = IconCache::new(dirs.clone()); let run_status_holder = RunStatusHolder::new(); - let search_index = SearchIndex::create_index(frontend_api.clone())?; let clipboard = Clipboard::new()?; let settings = Settings::new(dirs.clone(), db_repository.clone(), frontend_api.clone())?; + let search_index = SearchIndex::create_index(frontend_api.clone(), settings.clone())?; let (command_broadcaster, _) = tokio::sync::broadcast::channel::(100); @@ -617,6 +617,27 @@ impl ApplicationManager { self.settings.global_entrypoint_shortcuts().await } + pub async fn set_entrypoint_search_alias( + &self, + plugin_id: PluginId, + entrypoint_id: EntrypointId, + alias: Option, + ) -> anyhow::Result<()> { + self.settings + .set_entrypoint_search_alias(plugin_id.clone(), entrypoint_id.clone(), alias.clone()) + .await?; + + self.search_index + .set_entrypoint_search_alias(plugin_id, entrypoint_id, alias) + .await?; + + Ok(()) + } + + pub async fn get_entrypoint_search_aliases(&self) -> anyhow::Result> { + self.settings.entrypoint_search_aliases().await + } + pub async fn set_theme(&self, theme: SettingsTheme) -> anyhow::Result<()> { self.settings.set_theme_setting(theme).await } diff --git a/rust/server/src/plugins/settings.rs b/rust/server/src/plugins/settings.rs index 0e2c157..7384a23 100644 --- a/rust/server/src/plugins/settings.rs +++ b/rust/server/src/plugins/settings.rs @@ -1,5 +1,7 @@ use std::collections::hash_map::Entry; use std::collections::HashMap; +use std::rc::Rc; +use std::sync::Arc; use anyhow::anyhow; use dark_light::Mode; @@ -15,6 +17,7 @@ use gauntlet_common::rpc::frontend_api::FrontendApi; use gauntlet_common::rpc::frontend_api::FrontendApiProxy; use crate::plugins::data_db_repository::DataDbRepository; +use crate::plugins::data_db_repository::DbSettingsEntrypointSearchAliasData; use crate::plugins::data_db_repository::DbSettingsGlobalEntrypointShortcutData; use crate::plugins::data_db_repository::DbSettingsGlobalShortcutData; use crate::plugins::data_db_repository::DbSettingsShortcut; @@ -23,11 +26,12 @@ use crate::plugins::data_db_repository::DbWindowPositionMode; use crate::plugins::theme::read_theme_file; use crate::plugins::theme::BundledThemes; +#[derive(Clone)] pub struct Settings { dirs: Dirs, repository: DataDbRepository, frontend_api: FrontendApiProxy, - themes: BundledThemes, + themes: Arc, } impl Settings { @@ -36,7 +40,7 @@ impl Settings { dirs, repository, frontend_api, - themes: BundledThemes::new()?, + themes: Arc::new(BundledThemes::new()?), }) } @@ -242,6 +246,73 @@ impl Settings { Ok(()) } + pub async fn entrypoint_search_aliases(&self) -> anyhow::Result> { + let mut settings = self.repository.get_settings().await?; + + let data: HashMap<_, _> = settings + .entrypoint_search_aliases + .unwrap_or_default() + .into_iter() + .map(|data| { + ( + ( + PluginId::from_string(data.plugin_id), + EntrypointId::from_string(data.entrypoint_id), + ), + data.alias, + ) + }) + .collect(); + + Ok(data) + } + + pub async fn set_entrypoint_search_alias( + &self, + plugin_id: PluginId, + entrypoint_id: EntrypointId, + alias: Option, + ) -> anyhow::Result<()> { + let mut settings = self.repository.get_settings().await?; + + let mut alias_data: HashMap<_, _> = settings + .entrypoint_search_aliases + .unwrap_or_default() + .into_iter() + .map(|data| { + ( + ( + PluginId::from_string(data.plugin_id), + EntrypointId::from_string(data.entrypoint_id), + ), + data.alias, + ) + }) + .collect(); + + match alias { + None => alias_data.remove(&(plugin_id, entrypoint_id)), + Some(alias) => alias_data.insert((plugin_id, entrypoint_id), alias), + }; + + let alias_data = alias_data + .into_iter() + .map(|((plugin_id, entrypoint_id), alias)| { + DbSettingsEntrypointSearchAliasData { + plugin_id: plugin_id.to_string(), + entrypoint_id: entrypoint_id.to_string(), + alias, + } + }) + .collect(); + + settings.entrypoint_search_aliases = Some(alias_data); + + self.repository.set_settings(settings).await?; + + Ok(()) + } + pub async fn effective_theme(&self) -> anyhow::Result { if let Some(theme) = read_theme_file(self.dirs.theme_file()) { return Ok(theme); diff --git a/rust/server/src/rpc.rs b/rust/server/src/rpc.rs index fbd91d2..7855da8 100644 --- a/rust/server/src/rpc.rs +++ b/rust/server/src/rpc.rs @@ -173,6 +173,25 @@ impl BackendForSettingsApi for BackendServerImpl { .map_err(Into::into) } + async fn set_entrypoint_search_alias( + &self, + plugin_id: PluginId, + entrypoint_id: EntrypointId, + alias: Option, + ) -> RequestResult<()> { + self.application_manager + .set_entrypoint_search_alias(plugin_id, entrypoint_id, alias) + .await + .map_err(Into::into) + } + + async fn get_entrypoint_search_aliases(&self) -> RequestResult> { + self.application_manager + .get_entrypoint_search_aliases() + .await + .map_err(Into::into) + } + async fn set_theme(&self, theme: SettingsTheme) -> RequestResult<()> { self.application_manager.set_theme(theme).await.map_err(Into::into) } diff --git a/rust/server/src/search.rs b/rust/server/src/search.rs index 836be91..0d71009 100644 --- a/rust/server/src/search.rs +++ b/rust/server/src/search.rs @@ -4,6 +4,7 @@ use std::sync::Arc; use std::sync::Mutex; use std::sync::MutexGuard; +use anyhow::anyhow; use gauntlet_common::model::EntrypointId; use gauntlet_common::model::PhysicalShortcut; use gauntlet_common::model::PluginId; @@ -30,9 +31,12 @@ use tantivy::IndexWriter; use tantivy::ReloadPolicy; use tantivy::Searcher; +use crate::plugins::settings::Settings; + #[derive(Clone)] pub struct SearchIndex { frontend_api: FrontendApiProxy, + settings: Settings, index: Index, index_reader: IndexReader, index_writer_mutex: Arc>, @@ -43,6 +47,7 @@ pub struct SearchIndex { entrypoint_id: Field, plugin_name: Field, plugin_id: Field, + entrypoint_alias: Field, } struct PluginData { @@ -58,6 +63,7 @@ struct EntrypointData { frecency: f64, actions: Vec, accessories: Vec, + search_alias: Option, } struct EntrypointActionData { @@ -119,7 +125,7 @@ pub enum SearchIndexItemActionActionType { } impl SearchIndex { - pub fn create_index(frontend_api: FrontendApiProxy) -> tantivy::Result { + pub fn create_index(frontend_api: FrontendApiProxy, settings: Settings) -> tantivy::Result { let schema = { let mut schema_builder = Schema::builder(); @@ -127,6 +133,7 @@ impl SearchIndex { schema_builder.add_text_field("entrypoint_id", STRING | STORED); schema_builder.add_text_field("plugin_name", TEXT | STORED); schema_builder.add_text_field("plugin_id", STRING | STORED); + schema_builder.add_text_field("entrypoint_alias", TEXT | STORED); schema_builder.build() }; @@ -139,6 +146,9 @@ impl SearchIndex { .expect("entrypoint_id field should exist"); let plugin_name = schema.get_field("plugin_name").expect("plugin_name field should exist"); let plugin_id = schema.get_field("plugin_id").expect("plugin_id field should exist"); + let entrypoint_alias = schema + .get_field("entrypoint_alias") + .expect("plugin_id field should exist"); let index = Index::create_in_ram(schema.clone()); @@ -146,6 +156,7 @@ impl SearchIndex { Ok(Self { frontend_api, + settings, index, index_reader, index_writer_mutex: Arc::new(Mutex::new(())), @@ -154,6 +165,7 @@ impl SearchIndex { entrypoint_id, plugin_name, plugin_id, + entrypoint_alias, }) } @@ -176,37 +188,81 @@ impl SearchIndex { Ok(()) } - pub fn save_for_plugin( + pub async fn set_entrypoint_search_alias( + &self, + plugin_id: PluginId, + entrypoint_id: EntrypointId, + alias: Option, + ) -> anyhow::Result<()> { + tracing::debug!( + "Updating the entrypoint search alias in search index for plugin {:?} - {:?}", + plugin_id, + entrypoint_id + ); + + // writer panics if another writer exists + let _guard = self.index_writer_mutex.lock().expect("lock is poisoned"); + let mut plugins = self.entrypoint_data.lock().expect("lock is poisoned"); + + let Some(plugin_data) = plugins.get_mut(&plugin_id) else { + return Ok(()); + }; + + let Some(entrypoint_data) = plugin_data.entrypoints.get_mut(&entrypoint_id) else { + return Ok(()); + }; + + entrypoint_data.search_alias = alias; + + let mut index_writer = self.index.writer::(15_000_000)?; + let query = Box::new(BooleanQuery::union(vec![ + Box::new(TermQuery::new( + Term::from_field_text(self.plugin_id, &plugin_id.to_string()), + IndexRecordOption::Basic, + )), + Box::new(TermQuery::new( + Term::from_field_text(self.entrypoint_id, &entrypoint_id.to_string()), + IndexRecordOption::Basic, + )), + ])); + + index_writer.delete_query(query)?; + + let mut document = doc!( + self.entrypoint_name => entrypoint_data.entrypoint_name.clone(), + self.entrypoint_id => entrypoint_id.to_string(), + self.plugin_name => plugin_data.plugin_name.clone(), + self.plugin_id => plugin_id.to_string(), + ); + + if let Some(alias) = &entrypoint_data.search_alias { + document.add_field_value(self.entrypoint_alias, alias.clone()) + } + + index_writer.add_document(document)?; + + index_writer.commit()?; + self.index_reader.reload()?; + + Ok(()) + } + + pub async fn save_for_plugin( &self, plugin_id: PluginId, plugin_name: String, search_items: Vec, refresh_search_list: bool, - ) -> tantivy::Result<()> { + ) -> anyhow::Result<()> { tracing::debug!("Reloading search index for plugin {:?}", plugin_id); + let aliases = self.settings.entrypoint_search_aliases().await?; + // writer panics if another writer exists let _guard = self.index_writer_mutex.lock().expect("lock is poisoned"); let mut entrypoint_data = self.entrypoint_data.lock().expect("lock is poisoned"); - let mut index_writer = self.index.writer::(15_000_000)?; - - index_writer.delete_query(Box::new(TermQuery::new( - Term::from_field_text(self.plugin_id, &plugin_id.to_string()), - IndexRecordOption::Basic, - )))?; - - for search_item in &search_items { - index_writer.add_document(doc!( - self.entrypoint_name => search_item.entrypoint_name.clone(), - self.entrypoint_id => search_item.entrypoint_id.to_string(), - self.plugin_name => plugin_name.clone(), - self.plugin_id => plugin_id.to_string(), - ))?; - } - - index_writer.commit()?; - self.index_reader.reload()?; + let entrypoint_ids: Vec<_> = search_items.iter().map(|item| item.entrypoint_id.clone()).collect(); let data = search_items .into_iter() @@ -235,6 +291,7 @@ impl SearchIndex { frecency: item.entrypoint_frecency, actions, accessories: item.entrypoint_accessories, + search_alias: aliases.get(&(plugin_id.clone(), item.entrypoint_id.clone())).cloned(), }; (item.entrypoint_id.clone(), data) @@ -249,6 +306,34 @@ impl SearchIndex { }, ); + let mut index_writer = self.index.writer::(15_000_000)?; + + index_writer.delete_query(Box::new(TermQuery::new( + Term::from_field_text(self.plugin_id, &plugin_id.to_string()), + IndexRecordOption::Basic, + )))?; + + for entrypoint_id in entrypoint_ids { + let plugin_data = entrypoint_data.get(&plugin_id).unwrap(); + let entrypoint_data = plugin_data.entrypoints.get(&entrypoint_id).unwrap(); + + let mut document = doc!( + self.entrypoint_name => entrypoint_data.entrypoint_name.clone(), + self.entrypoint_id => entrypoint_id.to_string(), + self.plugin_name => plugin_data.plugin_name.clone(), + self.plugin_id => plugin_id.to_string(), + ); + + if let Some(alias) = &entrypoint_data.search_alias { + document.add_field_value(self.entrypoint_alias, alias.clone()) + } + + index_writer.add_document(document)?; + } + + index_writer.commit()?; + self.index_reader.reload()?; + if refresh_search_list { let mut frontend_api = self.frontend_api.clone(); tokio::spawn(async move { @@ -319,7 +404,12 @@ impl SearchIndex { let searcher = self.index_reader.searcher(); - let query_parser = QueryParser::new(self.index.tokenizers().clone(), self.entrypoint_name, self.plugin_name); + let query_parser = QueryParser::new( + self.index.tokenizers().clone(), + self.entrypoint_name, + self.plugin_name, + self.entrypoint_alias, + ); let query = query_parser.create_query(query); @@ -367,23 +457,12 @@ impl SearchIndex { collector: TopDocs, searcher: &Searcher, ) -> anyhow::Result> { - let get_str_field = |retrieved_doc: &TantivyDocument, field: Field| -> String { + let get_str_field = |retrieved_doc: &TantivyDocument, field: Field| -> Option { retrieved_doc .get_first(field) - .unwrap_or_else(|| { - panic!( - "there should be a field with name {:?}", - searcher.schema().get_field_name(field) - ) - }) - .as_str() - .unwrap_or_else(|| { - panic!( - "field with name {:?} should contain string", - searcher.schema().get_field_name(field) - ) - }) - .to_owned() + .map(|value| value.as_str()) + .flatten() + .map(|value| value.to_owned()) }; let result = searcher @@ -394,10 +473,18 @@ impl SearchIndex { .doc::(doc_address) .expect("index should contain just searched results"); - let entrypoint_id = EntrypointId::from_string(get_str_field(&retrieved_doc, self.entrypoint_id)); - let plugin_id = PluginId::from_string(get_str_field(&retrieved_doc, self.plugin_id)); - let entrypoint_name = get_str_field(&retrieved_doc, self.entrypoint_name); - let plugin_name = get_str_field(&retrieved_doc, self.plugin_name); + let entrypoint_id = get_str_field(&retrieved_doc, self.entrypoint_id) + .ok_or(anyhow!("document must contain entrypoint id"))?; + let plugin_id = + get_str_field(&retrieved_doc, self.plugin_id).ok_or(anyhow!("document must contain plugin id"))?; + let entrypoint_name = get_str_field(&retrieved_doc, self.entrypoint_name) + .ok_or(anyhow!("document must contain entrypoint name"))?; + let plugin_name = get_str_field(&retrieved_doc, self.plugin_name) + .ok_or(anyhow!("document must contain plugin name"))?; + let entrypoint_alias = get_str_field(&retrieved_doc, self.entrypoint_alias); + + let entrypoint_id = EntrypointId::from_string(entrypoint_id); + let plugin_id = PluginId::from_string(plugin_id); let entrypoint_data = entrypoint_data .get(&plugin_id) @@ -436,11 +523,12 @@ impl SearchIndex { plugin_id, entrypoint_actions, entrypoint_accessories, + entrypoint_alias, }; - (result_item, entrypoint_data.frecency) + Ok((result_item, entrypoint_data.frecency)) }) - .collect::>(); + .collect::>>()?; Ok(result) } @@ -450,14 +538,21 @@ struct QueryParser { tokenizer_manager: TokenizerManager, entrypoint_name: Field, plugin_name: Field, + entrypoint_alias: Field, } impl QueryParser { - fn new(tokenizer_manager: TokenizerManager, entrypoint_name: Field, plugin_name: Field) -> Self { + fn new( + tokenizer_manager: TokenizerManager, + entrypoint_name: Field, + plugin_name: Field, + entrypoint_alias: Field, + ) -> Self { Self { tokenizer_manager, entrypoint_name, plugin_name, + entrypoint_alias, } } @@ -486,10 +581,12 @@ impl QueryParser { let entrypoint_name_terms = terms_fn(self.entrypoint_name); let plugin_name_terms = terms_fn(self.plugin_name); + let entrypoint_alias_terms = terms_fn(self.entrypoint_alias); Box::new(BooleanQuery::union(vec![ Box::new(entrypoint_name_terms), Box::new(plugin_name_terms), + Box::new(entrypoint_alias_terms), ])) }