mirror of
https://github.com/project-gauntlet/gauntlet.git
synced 2025-12-23 10:35:53 +00:00
Implement generated command accessories
This commit is contained in:
parent
63a54d1d18
commit
7fb6ca4074
13 changed files with 322 additions and 238 deletions
|
|
@ -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"
|
||||
},
|
||||
])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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
16
js/typings/index.d.ts
vendored
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue