Implement entrypoint search aliases

This commit is contained in:
Exidex 2025-05-11 13:25:13 +02:00
parent 3064f7f297
commit 40fb6f2617
No known key found for this signature in database
GPG key ID: AC63AA86DD4F2D45
17 changed files with 565 additions and 99 deletions

View file

@ -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 `<Content.Svg/>` component to display SVG images
- **BREAKING CHANGE**: Renamed TS types: `ImageSource` to `DataSource`, `ImageSourceUrl` to `DataSourceUrl`, `ImageSourceAsset` to `DataSourceAsset`
- Added `<Content.Svg/>` 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<u8>` 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

View file

@ -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 {

View file

@ -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()),

View file

@ -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,

View file

@ -113,6 +113,7 @@ pub struct SearchResult {
pub entrypoint_type: SearchResultEntrypointType,
pub entrypoint_actions: Vec<SearchResultEntrypointAction>,
pub entrypoint_accessories: Vec<SearchResultAccessory>,
pub entrypoint_alias: Option<String>,
}
#[derive(Debug, Clone)]

View file

@ -142,6 +142,15 @@ pub trait BackendForSettingsApi {
&self,
) -> RequestResult<HashMap<(PluginId, EntrypointId), (PhysicalShortcut, Option<String>)>>;
async fn set_entrypoint_search_alias(
&self,
plugin_id: PluginId,
entrypoint_id: EntrypointId,
alias: Option<String>,
) -> RequestResult<()>;
async fn get_entrypoint_search_aliases(&self) -> RequestResult<HashMap<(PluginId, EntrypointId), String>>;
async fn set_theme(&self, theme: SettingsTheme) -> RequestResult<()>;
async fn get_theme(&self) -> RequestResult<SettingsTheme>;

View file

@ -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());

View file

@ -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 {

View file

@ -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<Man
async move {
let plugins = backend_api.plugins().await?;
let global_entrypoint_shortcuts = backend_api.get_global_entrypoint_shortcuts().await?;
let entrypoint_search_aliases = backend_api.get_entrypoint_search_aliases().await?;
Ok((plugins, global_entrypoint_shortcuts))
Ok((plugins, global_entrypoint_shortcuts, entrypoint_search_aliases))
},
|result| {
handle_backend_error(result, |(plugins, global_entrypoint_shortcuts)| {
ManagementAppMsg::Plugin(ManagementAppPluginMsgIn::PluginsReloaded(
plugins,
global_entrypoint_shortcuts,
))
})
handle_backend_error(
result,
|(plugins, global_entrypoint_shortcuts, entrypoint_search_aliases)| {
ManagementAppMsg::Plugin(ManagementAppPluginMsgIn::PluginsReloaded(
plugins,
global_entrypoint_shortcuts,
entrypoint_search_aliases,
))
},
)
},
)
}

View file

@ -61,6 +61,7 @@ pub enum ManagementAppPluginMsgIn {
PluginsReloaded(
HashMap<PluginId, SettingsPlugin>,
HashMap<(PluginId, EntrypointId), (PhysicalShortcut, Option<String>)>,
HashMap<(PluginId, EntrypointId), String>,
),
RemovePlugin {
plugin_id: PluginId,
@ -91,6 +92,7 @@ pub struct ManagementAppPluginsState {
preference_user_data: HashMap<(PluginId, Option<EntrypointId>, String), PluginPreferenceUserDataState>,
selected_item: SelectedItem,
global_entrypoint_shortcuts: HashMap<(PluginId, EntrypointId), (PhysicalShortcut, Option<String>)>,
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<PluginId, SettingsPlugin>,
global_entrypoint_shortcuts: HashMap<(PluginId, EntrypointId), (PhysicalShortcut, Option<String>)>,
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<ManagementAppPluginMsgIn> {

View file

@ -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<PhysicalShortcut>),
AliasChanged(PluginId, EntrypointId, String),
}
pub enum PluginTableMsgOut {
@ -71,6 +75,7 @@ pub enum PluginTableMsgOut {
entrypoint_id: EntrypointId,
},
ShortcutCaptured(PluginId, EntrypointId, Option<PhysicalShortcut>),
AliasChanged(PluginId, EntrypointId, Option<String>),
}
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<RefCell<PluginDataContainer>>,
plugin_refs: Vec<(&SettingsPlugin, &SettingsPluginData)>,
global_entrypoint_shortcuts: HashMap<(PluginId, EntrypointId), (PhysicalShortcut, Option<String>)>,
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<String>,
},
GeneratedEntrypoint {
plugin_data: Rc<RefCell<PluginDataContainer>>,
@ -254,6 +278,7 @@ enum Row {
generator_entrypoint_id: EntrypointId,
generated_entrypoint_id: EntrypointId,
shortcut_data: ShortcutData,
search_alias: Option<String>,
},
}
@ -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,
}
}

View file

@ -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<DbSettingsGlobalShortcutData>,
pub global_entrypoint_shortcuts: Option<Vec<DbSettingsGlobalEntrypointShortcutData>>,
pub entrypoint_search_aliases: Option<Vec<DbSettingsEntrypointSearchAliasData>>,
}
impl Default for DbSettings {
@ -281,6 +289,7 @@ impl Default for DbSettings {
error: None,
}),
global_entrypoint_shortcuts: None,
entrypoint_search_aliases: None,
}
}
}

View file

@ -827,6 +827,7 @@ impl BackendForPluginRuntimeApi for BackendForPluginRuntimeApiImpl {
generated_search_items,
refresh_search_list,
)
.await
.context("error when updating search index")?;
Ok(())

View file

@ -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::<PluginCommand>(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<String>,
) -> 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<HashMap<(PluginId, EntrypointId), String>> {
self.settings.entrypoint_search_aliases().await
}
pub async fn set_theme(&self, theme: SettingsTheme) -> anyhow::Result<()> {
self.settings.set_theme_setting(theme).await
}

View file

@ -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<BundledThemes>,
}
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<HashMap<(PluginId, EntrypointId), String>> {
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<String>,
) -> 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<UiTheme> {
if let Some(theme) = read_theme_file(self.dirs.theme_file()) {
return Ok(theme);

View file

@ -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<String>,
) -> 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<HashMap<(PluginId, EntrypointId), String>> {
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)
}

View file

@ -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<Mutex<()>>,
@ -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<EntrypointActionData>,
accessories: Vec<SearchResultAccessory>,
search_alias: Option<String>,
}
struct EntrypointActionData {
@ -119,7 +125,7 @@ pub enum SearchIndexItemActionActionType {
}
impl SearchIndex {
pub fn create_index(frontend_api: FrontendApiProxy) -> tantivy::Result<Self> {
pub fn create_index(frontend_api: FrontendApiProxy, settings: Settings) -> tantivy::Result<Self> {
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<String>,
) -> 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::<TantivyDocument>(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<SearchIndexItem>,
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::<TantivyDocument>(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::<TantivyDocument>(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<Vec<(SearchResult, f64)>> {
let get_str_field = |retrieved_doc: &TantivyDocument, field: Field| -> String {
let get_str_field = |retrieved_doc: &TantivyDocument, field: Field| -> Option<String> {
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::<TantivyDocument>(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::<Vec<_>>();
.collect::<anyhow::Result<Vec<_>>>()?;
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),
]))
}