Implement generated command accessories

This commit is contained in:
Exidex 2024-12-24 21:56:38 +01:00
parent 63a54d1d18
commit 7fb6ca4074
No known key found for this signature in database
GPG key ID: 46D8D21671EB48FA
13 changed files with 322 additions and 238 deletions

View file

@ -1,6 +1,7 @@
import { GeneratorProps, showHud } from "@project-gauntlet/api/helpers";
import { Icons } from "@project-gauntlet/api/components";
export default function CommandGenerator({ add, remove: _ }: GeneratorProps): void {
export default function CommandGenerator({ add, remove: _, updateAccessories }: GeneratorProps): void {
add('generated-test-1', {
name: 'Generated Item 1',
fn: () => {
@ -52,6 +53,12 @@ export default function CommandGenerator({ add, remove: _ }: GeneratorProps): vo
name: 'Generated Item 4',
fn: () => {
console.log('generated-test-4')
}
},
})
updateAccessories('generated-test-4', [
{
text: "1 window open"
},
])
}

View file

@ -20,6 +20,7 @@ export default function ListView(): ReactElement {
value={searchText}
onChange={setSearchText}
/>
<List.Item title={"Title"} subtitle={"Subtitle"} onClick={onClick}/>
{
numbers.map(value => {
const title = "Title " + value;

View file

@ -45,9 +45,23 @@ export interface GeneratedCommandAction {
fn: () => void
}
export type GeneratedCommandAccessory = GeneratedCommandTextAccessory | GeneratedCommandIconAccessory;
export interface GeneratedCommandTextAccessory {
text: string
icon?: string
tooltip?: string
}
export interface GeneratedCommandIconAccessory {
icon: string
tooltip?: string
}
export type GeneratorProps = {
add: (id: string, data: GeneratedCommand) => void,
remove: (id: string) => void,
updateAccessories: (id: string, accessories: GeneratedCommandAccessory[] | undefined) => void,
};
export const Clipboard: Clipboard = {

View file

@ -22,11 +22,12 @@ interface GeneratedCommandAction {
type GeneratorProps = {
add: (id: string, data: GeneratedCommand) => void,
remove: (id: string) => void,
updateAccessories: (id: string, accessories: GeneratedCommandAccessory[] | undefined) => void,
};
type Generator = (props: GeneratorProps) => void | (() => (void | Promise<void>)) | Promise<void | (() => (void | Promise<void>))>
type ProcessedGeneratedCommand = { generatorEntrypointId: string, uuid: string, command: GeneratedCommand };
type ProcessedGeneratedCommand = { generatorEntrypointId: string, uuid: string, command: GeneratedCommand, accessories?: GeneratedCommandAccessory[] };
type ProcessedGeneratedCommands = { [lookupEntrypointId: string]: ProcessedGeneratedCommand };
type GeneratorCleanups = { [generatorEntrypointId: string]: () => (void | Promise<void>) };
@ -63,7 +64,8 @@ export async function runCommandGenerators(): Promise<void> {
storedGeneratedCommands[lookupId] = {
generatorEntrypointId: generatorEntrypointId,
uuid: crypto.randomUUID(),
command: data
command: data,
accessories: [],
}
reloadSearchIndex(true)
@ -77,11 +79,24 @@ export async function runCommandGenerators(): Promise<void> {
reloadSearchIndex(true)
}
const updateAccessories = (id: string, accessories: GeneratedCommandAccessory[] | undefined): void => {
op_log_info("command_generator", `Updating accessories for entry '${id}' by command generator entrypoint '${generatorEntrypointId}'`)
const lookupId = generatorEntrypointId + ":" + id;
const generatedCommand = storedGeneratedCommands[lookupId];
if (generatedCommand) {
generatedCommand.accessories = accessories
reloadSearchIndex(true)
} else {
console.error(`Unable to update accessories for entry '${id}' because it doesn't exist`)
}
}
// noinspection ES6MissingAwait
(async () => {
try {
update_loading_bar(generatorEntrypointId, true)
let cleanup = await generator({ add, remove })
let cleanup = await generator({ add, remove, updateAccessories })
update_loading_bar(generatorEntrypointId, false)
if (typeof cleanup === "function") {
generatorCleanups[generatorEntrypointId] = cleanup
@ -108,6 +123,7 @@ export function generatedCommandSearchIndex(): AdditionalSearchItem[] {
id: action.ref,
label: action.label
})),
entrypoint_accessories: value.accessories || []
}))
}

16
js/typings/index.d.ts vendored
View file

@ -37,8 +37,6 @@ type MacOSDesktopSettings13AndPostData = {
icon: ArrayBuffer | undefined,
}
type PromiseRejectCallback = (type: number, promise: Promise<unknown>, reason: any) => void;
type PluginEvent = ViewEvent | NotReactsKeyboardEvent | RunCommand | RunGeneratedCommand | OpenView | CloseView | OpenInlineView | ReloadSearchIndex | RefreshSearchIndex
type RenderLocation = "InlineView" | "View"
@ -112,12 +110,26 @@ type UiWidget = {
type Props = { [key: string]: any };
type PropsWithChildren = { children?: UiWidget[] } & Props;
type GeneratedCommandAccessory = GeneratedCommandTextAccessory | GeneratedCommandIconAccessory;
interface GeneratedCommandTextAccessory {
text: string
icon?: string
tooltip?: string
}
interface GeneratedCommandIconAccessory {
icon: string
tooltip?: string
}
type AdditionalSearchItem = {
entrypoint_name: string,
entrypoint_id: string,
entrypoint_uuid: string,
entrypoint_icon: ArrayBuffer | undefined,
entrypoint_actions: AdditionalSearchItemAction[],
entrypoint_accessories: GeneratedCommandAccessory[],
}
type AdditionalSearchItemAction = {

View file

@ -1599,11 +1599,8 @@ fn view_main(state: &AppModel) -> Element<'_, AppMsg> {
.width(Length::Fill)
.themed(TextInputStyle::MainSearch);
let search_list = search_list(
&state.search_results,
&focused_search_result,
|search_result| AppMsg::RunSearchItemAction(search_result, None),
);
let search_list = search_list(&state.search_results, &focused_search_result)
.map(|search_result| AppMsg::RunSearchItemAction(search_result, None));
let search_list = container(search_list)
.width(Length::Fill)

View file

@ -1,139 +1,122 @@
use iced::{Alignment, Length};
use iced::advanced::image::Handle;
use iced::widget::{column, Component, container, horizontal_space};
use iced::widget::button;
use iced::widget::component;
use iced::widget::row;
use iced::widget::text;
use iced::widget::text::Shaping;
use gauntlet_common::model::SearchResult;
use crate::ui::scroll_handle::ScrollHandle;
use crate::ui::theme::{Element, GauntletComplexTheme, ThemableWidget};
use crate::ui::theme::button::ButtonStyle;
use crate::ui::theme::container::ContainerStyle;
use crate::ui::theme::image::ImageStyle;
use crate::ui::theme::space::ThemeKindSpace;
use crate::ui::theme::text::TextStyle;
use crate::ui::theme::{Element, ThemableWidget};
use crate::ui::widget::{render_icon_accessory, render_text_accessory};
use std::collections::HashMap;
pub struct SearchList<'a, Message> {
on_select: Box<dyn Fn(SearchResult) -> Message>,
focused_search_result: Option<usize>,
search_results: &'a[SearchResult],
}
use gauntlet_common::model::{IconAccessoryWidget, ImageLike, SearchResult, SearchResultAccessory, TextAccessoryWidget};
use iced::advanced::image::Handle;
use iced::widget::button;
use iced::widget::row;
use iced::widget::text;
use iced::widget::text::Shaping;
use iced::widget::{column, container, horizontal_space};
use iced::{Alignment, Length};
pub fn search_list<'a, Message>(
search_results: &'a[SearchResult],
pub fn search_list<'a>(
search_results: &'a [SearchResult],
focused_search_result: &ScrollHandle<SearchResult>,
on_select: impl Fn(SearchResult) -> Message + 'static,
) -> SearchList<'a, Message> {
SearchList::new(search_results, focused_search_result.index, on_select)
}
) -> Element<'a, SearchResult> {
let items: Vec<Element<_>> = search_results
.iter()
.enumerate()
.map(|(index, search_result)| {
let main_text: Element<_> = text(&search_result.entrypoint_name)
.shaping(Shaping::Advanced)
.into();
let main_text: Element<_> = container(main_text)
.themed(ContainerStyle::MainListItemText);
#[derive(Debug, Clone)]
pub struct SelectItemEvent(SearchResult);
let spacer: Element<_> = horizontal_space()
.width(Length::Fill)
.into();
impl<'a, Message> SearchList<'a, Message> {
pub fn new(
search_results: &'a[SearchResult],
focused_search_result: Option<usize>,
on_open_view: impl Fn(SearchResult) -> Message + 'static,
) -> Self {
Self {
search_results,
focused_search_result,
on_select: Box::new(on_open_view),
}
}
}
impl<'a, Message> Component<Message, GauntletComplexTheme> for SearchList<'a, Message> {
type State = ();
type Event = SelectItemEvent;
fn update(
&mut self,
_state: &mut Self::State,
SelectItemEvent(event): SelectItemEvent,
) -> Option<Message> {
Some((self.on_select)(event))
}
fn view(&self, _state: &Self::State) -> Element<SelectItemEvent> {
let items: Vec<Element<_>> = self.search_results
.iter()
.enumerate()
.map(|(index, search_result)| {
let main_text: Element<_> = text(&search_result.entrypoint_name)
.shaping(Shaping::Advanced)
.into();
let main_text: Element<_> = container(main_text)
.themed(ContainerStyle::MainListItemText);
let spacer: Element<_> = horizontal_space()
.width(Length::Fill)
.into();
let sub_text: Element<_> = text(&search_result.plugin_name)
.shaping(Shaping::Advanced)
.themed(TextStyle::MainListItemSubtext);
let sub_text: Element<_> = container(sub_text)
let sub_text: Element<_> = text(&search_result.plugin_name)
.shaping(Shaping::Advanced)
.themed(TextStyle::MainListItemSubtext);
let sub_text: Element<_> = container(sub_text)
.themed(ContainerStyle::MainListItemSubText); // FIXME find a way to set padding based on whether the scroll bar is visible
let mut button_content = vec![];
let mut button_content = vec![];
if let Some(path) = &search_result.entrypoint_icon {
let image: Element<_> = iced::widget::image(Handle::from_path(path))
.themed(ImageStyle::MainListItemIcon);
if let Some(path) = &search_result.entrypoint_icon {
let image: Element<_> = iced::widget::image(Handle::from_path(path))
.themed(ImageStyle::MainListItemIcon);
let image: Element<_> = container(image)
.themed(ContainerStyle::MainListItemIcon);
let image: Element<_> = container(image)
.themed(ContainerStyle::MainListItemIcon);
button_content.push(image);
} else {
let spacer: Element<_> = horizontal_space() // TODO replace with grayed out gauntlet icon
button_content.push(image);
} else {
let spacer: Element<_> = horizontal_space() // TODO replace with grayed out gauntlet icon
.themed(ThemeKindSpace::MainListItemIcon);
let spacer: Element<_> = container(spacer)
.themed(ContainerStyle::MainListItemIcon);
let spacer: Element<_> = container(spacer)
.themed(ContainerStyle::MainListItemIcon);
button_content.push(spacer);
}
button_content.push(main_text);
button_content.push(spacer);
button_content.push(sub_text);
}
let button_content: Element<_> = row(button_content)
.align_y(Alignment::Center)
button_content.push(main_text);
button_content.push(spacer);
if search_result.entrypoint_accessories.len() > 0 {
let accessories: Vec<Element<_>> = search_result.entrypoint_accessories
.iter()
.map(|accessory| {
match accessory {
SearchResultAccessory::TextAccessory { text, icon, tooltip } => {
render_text_accessory(&HashMap::new(), &TextAccessoryWidget {
__id__: 0,
text: text.clone(),
icon: icon.as_ref().map(|icon| ImageLike::Icons(icon.clone())),
tooltip: tooltip.clone(),
})
},
SearchResultAccessory::IconAccessory { icon, tooltip } => {
render_icon_accessory(&HashMap::new(), &IconAccessoryWidget {
__id__: 0,
icon: ImageLike::Icons(icon.clone()),
tooltip: tooltip.clone(),
})
}
}
})
.collect();
let accessories: Element<_> = row(accessories)
.into();
let style = match self.focused_search_result {
None => ButtonStyle::MainListItem,
Some(focused_index) => {
if focused_index == index {
ButtonStyle::MainListItemFocused
} else {
ButtonStyle::MainListItem
}
button_content.push(accessories);
}
button_content.push(sub_text);
let button_content: Element<_> = row(button_content)
.align_y(Alignment::Center)
.into();
let style = match focused_search_result.index {
None => ButtonStyle::MainListItem,
Some(focused_index) => {
if focused_index == index {
ButtonStyle::MainListItemFocused
} else {
ButtonStyle::MainListItem
}
};
}
};
button(button_content)
.width(Length::Fill)
.on_press(SelectItemEvent(search_result.clone()))
.themed(style)
})
.collect();
button(button_content)
.width(Length::Fill)
.on_press(search_result.clone())
.themed(style)
})
.collect();
column(items).into()
}
column(items).into()
}
impl<'a, Message> From<SearchList<'a, Message>> for Element<'a, Message>
where
Message: 'a,
{
fn from(search_list: SearchList<'a, Message>) -> Self {
component(search_list)
}
}

View file

@ -1000,7 +1000,7 @@ impl<'b> ComponentWidgets<'b> {
fn render_image_widget<'a>(&self, widget: &ImageWidget, centered: bool) -> Element<'a, ComponentWidgetEvent> {
// TODO image size, height and width
let content: Element<_> = self.render_image(widget.__id__, &widget.source, None);
let content: Element<_> = render_image(self.images, widget.__id__, &widget.source, None);
let mut content = container(content)
.width(Length::Fill);
@ -1412,7 +1412,7 @@ impl<'b> ComponentWidgets<'b> {
fn render_empty_view_widget<'a>(&self, widget: &EmptyViewWidget) -> Element<'a, ComponentWidgetEvent> {
let image: Option<Element<_>> = widget.image
.as_ref()
.map(|image| self.render_image(widget.__id__, image, Some(TextStyle::EmptyViewSubtitle)));
.map(|image| render_image(self.images, widget.__id__, image, Some(TextStyle::EmptyViewSubtitle)));
let title: Element<_> = text(widget.title.to_string())
.shaping(Shaping::Advanced)
@ -1459,69 +1459,6 @@ impl<'b> ComponentWidgets<'b> {
.themed(TextInputStyle::PluginSearchBar)
}
fn render_icon_accessory<'a>(&self, widget: &IconAccessoryWidget) -> Element<'a, ComponentWidgetEvent> {
let icon = self.render_image(widget.__id__, &widget.icon, Some(TextStyle::IconAccessory));
let content = container(icon)
.align_x(Horizontal::Center)
.align_y(Vertical::Center)
.themed(ContainerStyle::IconAccessory);
match widget.tooltip.as_ref() {
None => content,
Some(tooltip_text) => {
let tooltip_text: Element<_> = text(tooltip_text.to_string())
.shaping(Shaping::Advanced)
.into();
tooltip(content, tooltip_text, Position::Top)
.themed(TooltipStyle::Tooltip)
}
}
}
fn render_text_accessory<'a>(&self, widget: &TextAccessoryWidget) -> Element<'a, ComponentWidgetEvent> {
let icon: Option<Element<_>> = widget.icon
.as_ref()
.map(|icon| self.render_image(widget.__id__, icon, Some(TextStyle::TextAccessory)));
let text_content: Element<_> = text(widget.text.to_string())
.shaping(Shaping::Advanced)
.themed(TextStyle::TextAccessory);
let mut content: Vec<Element<_>> = vec![];
if let Some(icon) = icon {
let icon: Element<_> = container(icon)
.themed(ContainerStyle::TextAccessoryIcon);
content.push(icon)
}
content.push(text_content);
let content: Element<_> = row(content)
.align_y(Alignment::Center)
.into();
let content = container(content)
.align_x(Horizontal::Center)
.align_y(Vertical::Center)
.themed(ContainerStyle::TextAccessory);
match widget.tooltip.as_ref() {
None => content,
Some(tooltip_text) => {
let tooltip_text: Element<_> = text(tooltip_text.to_string())
.shaping(Shaping::Advanced)
.into();
tooltip(content, tooltip_text, Position::Top)
.themed(TooltipStyle::Tooltip)
}
}
}
fn render_list_widget<'a>(
&self,
list_widget: &ListWidget,
@ -1669,7 +1606,7 @@ impl<'b> ComponentWidgets<'b> {
) -> Element<'a, ComponentWidgetEvent> {
let icon: Option<Element<_>> = widget.icon
.as_ref()
.map(|icon| self.render_image(widget.__id__, icon, None));
.map(|icon| render_image(self.images, widget.__id__, icon, None));
let title: Element<_> = text(widget.title.to_string())
.shaping(Shaping::Advanced)
@ -1701,8 +1638,8 @@ impl<'b> ComponentWidgets<'b> {
.iter()
.map(|accessory| {
match accessory {
ListItemAccessories::_0(widget) => self.render_text_accessory(widget),
ListItemAccessories::_1(widget) => self.render_icon_accessory(widget)
ListItemAccessories::_0(widget) => render_text_accessory(self.images, widget),
ListItemAccessories::_1(widget) => render_icon_accessory(self.images, widget)
}
})
.collect();
@ -1894,7 +1831,7 @@ impl<'b> ComponentWidgets<'b> {
let mut sub_content_right = vec![];
if let Some(widget) = &widget.content.accessory {
sub_content_right.push(self.render_icon_accessory(widget));
sub_content_right.push(render_icon_accessory(self.images, widget));
}
let sub_content_left: Element<_> = column(sub_content_left)
@ -2060,37 +1997,6 @@ impl<'b> ComponentWidgets<'b> {
}
}
}
fn render_image<'a>(&self, widget_id: UiWidgetId, image_data: &ImageLike, icon_style: Option<TextStyle>) -> Element<'a, ComponentWidgetEvent> {
match image_data {
ImageLike::ImageSource(_) => {
match self.images.get(&widget_id) {
Some(bytes) => {
image(Handle::from_bytes(bytes.clone()))
.into()
}
None => {
horizontal_space()
.into()
}
}
}
ImageLike::Icons(icon) => {
match icon_style {
None => {
value(icon_to_bootstrap(icon))
.font(BOOTSTRAP_FONT)
.into()
}
Some(icon_style) => {
value(icon_to_bootstrap(icon))
.font(BOOTSTRAP_FONT)
.themed(icon_style)
}
}
}
}
}
}
@ -2620,6 +2526,101 @@ fn render_shortcut<'a, T: 'a>(shortcut: &PhysicalShortcut) -> Element<'a, T> {
.themed(RowStyle::ActionShortcut)
}
fn render_image<'a, T: 'a + Clone>(images: &HashMap<UiWidgetId, Vec<u8>>, widget_id: UiWidgetId, image_data: &ImageLike, icon_style: Option<TextStyle>) -> Element<'a, T> {
match image_data {
ImageLike::ImageSource(_) => {
match images.get(&widget_id) {
Some(bytes) => {
image(Handle::from_bytes(bytes.clone()))
.into()
}
None => {
horizontal_space()
.into()
}
}
}
ImageLike::Icons(icon) => {
match icon_style {
None => {
value(icon_to_bootstrap(icon))
.font(BOOTSTRAP_FONT)
.into()
}
Some(icon_style) => {
value(icon_to_bootstrap(icon))
.font(BOOTSTRAP_FONT)
.themed(icon_style)
}
}
}
}
}
pub fn render_icon_accessory<'a, T: 'a + Clone>(images: &HashMap<UiWidgetId, Vec<u8>>, widget: &IconAccessoryWidget) -> Element<'a, T> {
let icon = render_image(images, widget.__id__, &widget.icon, Some(TextStyle::IconAccessory));
let content = container(icon)
.align_x(Horizontal::Center)
.align_y(Vertical::Center)
.themed(ContainerStyle::IconAccessory);
match widget.tooltip.as_ref() {
None => content,
Some(tooltip_text) => {
let tooltip_text: Element<_> = text(tooltip_text.to_string())
.shaping(Shaping::Advanced)
.into();
tooltip(content, tooltip_text, Position::Top)
.themed(TooltipStyle::Tooltip)
}
}
}
pub fn render_text_accessory<'a, T: 'a + Clone>(images: &HashMap<UiWidgetId, Vec<u8>>, widget: &TextAccessoryWidget) -> Element<'a, T> {
let icon: Option<Element<_>> = widget.icon
.as_ref()
.map(|icon| render_image(images, widget.__id__, icon, Some(TextStyle::TextAccessory)));
let text_content: Element<_> = text(widget.text.to_string())
.shaping(Shaping::Advanced)
.themed(TextStyle::TextAccessory);
let mut content: Vec<Element<_>> = vec![];
if let Some(icon) = icon {
let icon: Element<_> = container(icon)
.themed(ContainerStyle::TextAccessoryIcon);
content.push(icon)
}
content.push(text_content);
let content: Element<_> = row(content)
.align_y(Alignment::Center)
.into();
let content = container(content)
.align_x(Horizontal::Center)
.align_y(Vertical::Center)
.themed(ContainerStyle::TextAccessory);
match widget.tooltip.as_ref() {
None => content,
Some(tooltip_text) => {
let tooltip_text: Element<_> = text(tooltip_text.to_string())
.shaping(Shaping::Advanced)
.into();
tooltip(content, tooltip_text, Position::Top)
.themed(TooltipStyle::Tooltip)
}
}
}
#[derive(Clone, Debug)]
pub enum ComponentWidgetEvent {
LinkClick {

View file

@ -501,7 +501,7 @@ fn component_model_generator() -> Result<(), Box<dyn std::error::Error>> {
for (type_name, shared_type) in shared_types {
match shared_type {
SharedType::Enum { items } => {
output.push_str("#[derive(Debug, Serialize, Deserialize, Encode, Decode)]\n");
output.push_str("#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)]\n");
output.push_str(&format!("pub enum {} {{\n", type_name));
for item in items {

View file

@ -113,6 +113,20 @@ pub struct SearchResult {
pub entrypoint_icon: Option<String>,
pub entrypoint_type: SearchResultEntrypointType,
pub entrypoint_actions: Vec<SearchResultEntrypointAction>,
pub entrypoint_accessories: Vec<SearchResultAccessory>,
}
#[derive(Debug, Clone)]
pub enum SearchResultAccessory {
TextAccessory {
text: String,
icon: Option<Icons>,
tooltip: Option<String>
},
IconAccessory {
icon: Icons,
tooltip: Option<String>
},
}
#[derive(Debug, Clone)]

View file

@ -1,5 +1,5 @@
use crate::JsEvent;
use gauntlet_common::model::{EntrypointId, PluginId, RootWidget};
use gauntlet_common::model::{EntrypointId, Icons, PluginId, RootWidget};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt;
@ -172,6 +172,7 @@ pub struct JsAdditionalSearchItem {
pub entrypoint_uuid: String,
pub entrypoint_icon: Option<Vec<u8>>,
pub entrypoint_actions: Vec<JsAdditionalSearchItemAction>,
pub entrypoint_accessories: Vec<JsAdditionalSearchItemAccessory>,
}
impl fmt::Debug for JsAdditionalSearchItem {
@ -203,6 +204,20 @@ pub enum JsPreferenceUserData {
ListOfNumbers(Vec<f64>),
}
#[derive(Debug, Deserialize, Serialize, Encode, Decode)]
#[serde(untagged)]
pub enum JsAdditionalSearchItemAccessory {
TextAccessory {
text: String,
icon: Option<Icons>,
tooltip: Option<String>
},
IconAccessory {
icon: Icons,
tooltip: Option<String>
},
}
#[derive(Debug, Serialize, Deserialize, Encode, Decode)]
pub struct JsClipboardData {
pub text_data: Option<String>,

View file

@ -26,10 +26,10 @@ use tokio::sync::Mutex;
use tokio::task::spawn_blocking;
use tokio_util::sync::CancellationToken;
use gauntlet_common::dirs::Dirs;
use gauntlet_common::model::{EntrypointId, KeyboardEventOrigin, PhysicalKey, PluginId, RootWidget, SearchResultEntrypointType, UiPropertyValue, UiRenderLocation, UiWidgetId};
use gauntlet_common::model::{EntrypointId, KeyboardEventOrigin, PhysicalKey, PluginId, RootWidget, SearchResultAccessory, SearchResultEntrypointType, UiPropertyValue, UiRenderLocation, UiWidgetId};
use gauntlet_common::rpc::frontend_api::FrontendApi;
use gauntlet_common::settings_env_data_to_string;
use gauntlet_plugin_runtime::{recv_message, send_message, BackendForPluginRuntimeApi, JsAdditionalSearchItem, JsClipboardData, JsInit, JsKeyboardEventOrigin, JsPluginCode, JsPluginPermissions, JsPreferenceUserData, JsEvent, JsUiPropertyValue, JsRequest, JsUiRenderLocation, JsResponse, JsMessage, JsPluginPermissionsFileSystem, JsPluginPermissionsExec, JsPluginPermissionsMainSearchBar, JsMessageSide, JsPluginRuntimeMessage};
use gauntlet_plugin_runtime::{recv_message, send_message, BackendForPluginRuntimeApi, JsAdditionalSearchItem, JsClipboardData, JsInit, JsKeyboardEventOrigin, JsPluginCode, JsPluginPermissions, JsPreferenceUserData, JsEvent, JsUiPropertyValue, JsRequest, JsUiRenderLocation, JsResponse, JsMessage, JsPluginPermissionsFileSystem, JsPluginPermissionsExec, JsPluginPermissionsMainSearchBar, JsMessageSide, JsPluginRuntimeMessage, JsAdditionalSearchItemAccessory};
use crate::model::{IntermediateUiEvent};
use crate::plugins::clipboard::Clipboard;
use crate::plugins::data_db_repository::{db_entrypoint_from_str, DataDbRepository, DbPluginClipboardPermissions, DbPluginEntrypointType, DbPluginPreference, DbPluginPreferenceUserData, DbReadPlugin, DbReadPluginEntrypoint};
@ -725,7 +725,7 @@ impl BackendForPluginRuntimeApi for BackendForPluginRuntimeApiImpl {
shortcuts.insert(id.clone(), entrypoint_shortcuts);
}
let mut plugins_search_items = generated_commands.into_iter()
let mut generated_search_items = generated_commands.into_iter()
.map(|item| {
let entrypoint_icon_path = match item.entrypoint_icon {
None => None,
@ -753,6 +753,19 @@ impl BackendForPluginRuntimeApi for BackendForPluginRuntimeApiImpl {
})
.collect();
let entrypoint_accessories = item.entrypoint_accessories.into_iter()
.map(|accessory| {
match accessory {
JsAdditionalSearchItemAccessory::TextAccessory { text, icon, tooltip } => {
SearchResultAccessory::TextAccessory { text, icon, tooltip }
}
JsAdditionalSearchItemAccessory::IconAccessory { icon, tooltip } => {
SearchResultAccessory::IconAccessory { icon, tooltip }
}
}
})
.collect();
Ok(SearchIndexItem {
entrypoint_type: SearchResultEntrypointType::GeneratedCommand,
entrypoint_id: EntrypointId::from_string(item.entrypoint_id),
@ -760,6 +773,7 @@ impl BackendForPluginRuntimeApi for BackendForPluginRuntimeApiImpl {
entrypoint_icon_path,
entrypoint_frecency,
entrypoint_actions,
entrypoint_accessories,
})
})
.collect::<anyhow::Result<Vec<_>>>()?;
@ -806,6 +820,7 @@ impl BackendForPluginRuntimeApi for BackendForPluginRuntimeApiImpl {
entrypoint_icon_path,
entrypoint_frecency,
entrypoint_actions: vec![],
entrypoint_accessories: vec![],
}))
},
DbPluginEntrypointType::View => {
@ -816,6 +831,7 @@ impl BackendForPluginRuntimeApi for BackendForPluginRuntimeApiImpl {
entrypoint_icon_path,
entrypoint_frecency,
entrypoint_actions: vec![],
entrypoint_accessories: vec![],
}))
},
DbPluginEntrypointType::CommandGenerator | DbPluginEntrypointType::InlineView => {
@ -828,9 +844,9 @@ impl BackendForPluginRuntimeApi for BackendForPluginRuntimeApiImpl {
.flat_map(|item| item)
.collect::<Vec<_>>();
plugins_search_items.append(&mut builtin_search_items);
generated_search_items.append(&mut builtin_search_items);
self.search_index.save_for_plugin(self.plugin_id.clone(), name, plugins_search_items, refresh_search_list)
self.search_index.save_for_plugin(self.plugin_id.clone(), name, generated_search_items, refresh_search_list)
.context("error when updating search index")?;
Ok(())

View file

@ -6,7 +6,7 @@ use tantivy::collector::TopDocs;
use tantivy::query::{AllQuery, BooleanQuery, FuzzyTermQuery, Query, RegexQuery, TermQuery};
use tantivy::schema::*;
use tantivy::tokenizer::TokenizerManager;
use gauntlet_common::model::{EntrypointId, PhysicalShortcut, PluginId, SearchResult, SearchResultEntrypointAction, SearchResultEntrypointType};
use gauntlet_common::model::{EntrypointId, PhysicalShortcut, PluginId, SearchResult, SearchResultAccessory, SearchResultEntrypointAction, SearchResultEntrypointType};
use gauntlet_common::rpc::frontend_api::FrontendApi;
#[derive(Clone)]
@ -29,6 +29,7 @@ struct EntrypointData {
icon_path: Option<String>,
frecency: f64,
actions: Vec<EntrypointActionData>,
accessories: Vec<SearchResultAccessory>,
}
struct EntrypointActionData {
@ -44,6 +45,7 @@ pub struct SearchIndexItem {
pub entrypoint_icon_path: Option<String>,
pub entrypoint_frecency: f64,
pub entrypoint_actions: Vec<SearchIndexItemAction>,
pub entrypoint_accessories: Vec<SearchResultAccessory>,
}
#[derive(Clone, Debug)]
@ -133,20 +135,21 @@ impl SearchIndex {
index_writer.commit()?;
self.index_reader.reload()?;
let data = search_items.iter()
let data = search_items.into_iter()
.map(|item| {
let actions = item.entrypoint_actions.iter()
let actions = item.entrypoint_actions.into_iter()
.map(|action| EntrypointActionData {
label: action.label.clone(),
shortcut: action.shortcut.clone(),
label: action.label,
shortcut: action.shortcut,
})
.collect();
let data = EntrypointData {
entrypoint_type: item.entrypoint_type.clone(),
icon_path: item.entrypoint_icon_path.clone(),
entrypoint_type: item.entrypoint_type,
icon_path: item.entrypoint_icon_path,
frecency: item.entrypoint_frecency,
actions,
accessories: item.entrypoint_accessories,
};
(item.entrypoint_id.clone(), data)
@ -256,6 +259,10 @@ impl SearchIndex {
})
.collect();
let entrypoint_accessories = entrypoint_data.accessories.iter()
.cloned()
.collect();
let result_item = SearchResult {
entrypoint_type: entrypoint_data.entrypoint_type.clone(),
entrypoint_name,
@ -264,6 +271,7 @@ impl SearchIndex {
plugin_name,
plugin_id,
entrypoint_actions,
entrypoint_accessories,
};
(result_item, entrypoint_data.frecency)