feat(api): added search_submenus prop to menus (#655)

`search_submenus` makes uosc's internal search handler (when no `on_search` callback is defined) look into submenus as well, effectively flattening the menu for the duration of the search.
This commit is contained in:
Tomas Klaen
2023-09-30 17:24:03 +02:00
committed by GitHub
parent 512281ac4b
commit cbfb2a62fb
3 changed files with 37 additions and 12 deletions

View File

@@ -465,6 +465,8 @@ While the menu is open this value will be available in `user-data/uosc/menu/type
`search_debounce` controls how soon the search happens after the last character was entered in milliseconds. Entering new character resets the timer. Defaults to `300`. It can also have a special value `'submit'`, which triggers a search only after `ctrl+enter` was pressed.
`search_submenus` makes uosc's internal search handler (when no `on_search` callback is defined) look into submenus as well, effectively flattening the menu for the duration of the search.
`search_suggestion` fills menu search with initial query string. Useful for example when you want to implement something like subtitle downloader, you'd set it to current file name.
`item.icon` property accepts icon names. You can pick one from here: [Google Material Icons](https://fonts.google.com/icons)\

View File

@@ -1,13 +1,13 @@
local Element = require('elements/Element')
-- Menu data structure accepted by `Menu:open(menu)`.
---@alias MenuData {id?: string; type?: string; title?: string; hint?: string; palette?: boolean; keep_open?: boolean; separator?: boolean; items?: MenuDataItem[]; selected_index?: integer; on_search?: string|string[]|fun(search_text: string), search_debounce?: number|string; search_suggestion?: string}
---@alias MenuData {id?: string; type?: string; title?: string; hint?: string; palette?: boolean; keep_open?: boolean; separator?: boolean; items?: MenuDataItem[]; selected_index?: integer; on_search?: string|string[]|fun(search_text: string); search_debounce?: number|string; search_submenus?: boolean; search_suggestion?: string}
---@alias MenuDataItem MenuDataValue|MenuData
---@alias MenuDataValue {title?: string; hint?: string; icon?: string; value: any; bold?: boolean; italic?: boolean; muted?: boolean; active?: boolean; keep_open?: boolean; separator?: boolean; selectable?: boolean; align?: 'left'|'center'|'right'}
---@alias MenuOptions {mouse_nav?: boolean; on_open?: fun(); on_close?: fun(); on_back?: fun(); on_move_item?: fun(from_index: integer, to_index: integer, submenu_path: integer[]); on_delete_item?: fun(index: integer, submenu_path: integer[])}
-- Internal data structure created from `Menu`.
---@alias MenuStack {id?: string; type?: string; title?: string; hint?: string; palette?: boolean, selected_index?: number; keep_open?: boolean; separator?: boolean; items: MenuStackItem[]; on_search?: string|string[]|fun(search_text: string); search_debounce?: number|string; search_suggestion?: string; parent_menu?: MenuStack; submenu_path: integer[]; active?: boolean; width: number; height: number; top: number; scroll_y: number; scroll_height: number; title_width: number; hint_width: number; max_width: number; is_root?: boolean; fling?: Fling, search?: Search, ass_safe_title?: string}
---@alias MenuStack {id?: string; type?: string; title?: string; hint?: string; palette?: boolean, selected_index?: number; keep_open?: boolean; separator?: boolean; items: MenuStackItem[]; on_search?: string|string[]|fun(search_text: string); search_debounce?: number|string; search_submenus?: boolean; search_suggestion?: string; parent_menu?: MenuStack; submenu_path: integer[]; active?: boolean; width: number; height: number; top: number; scroll_y: number; scroll_height: number; title_width: number; hint_width: number; max_width: number; is_root?: boolean; fling?: Fling, search?: Search, ass_safe_title?: string}
---@alias MenuStackItem MenuStackValue|MenuStack
---@alias MenuStackValue {title?: string; hint?: string; icon?: string; value: any; active?: boolean; bold?: boolean; italic?: boolean; muted?: boolean; keep_open?: boolean; separator?: boolean; selectable?: boolean; align?: 'left'|'center'|'right'; title_width: number; hint_width: number}
---@alias Fling {y: number, distance: number, time: number, easing: fun(x: number), duration: number, update_cursor?: boolean}
@@ -144,7 +144,9 @@ function Menu:update(data)
local new_by_id = {}
local menus_to_serialize = {{new_root, data}}
local old_current_id = self.current and self.current.id
local menu_props_to_copy = {'title', 'hint', 'keep_open', 'palette', 'on_search', 'search_suggestion'}
local menu_props_to_copy = {
'title', 'hint', 'keep_open', 'palette', 'on_search', 'search_submenus', 'search_suggestion'
}
local item_props_to_copy = itable_join(menu_props_to_copy, {
'icon', 'active', 'bold', 'italic', 'muted', 'value', 'separator', 'selectable', 'align'
})
@@ -670,14 +672,7 @@ function Menu:search_internal(menu)
-- Reset menu state to what it was before search
for key, value in pairs(menu.search.source) do menu[key] = value end
else
menu.items = itable_filter(menu.search.source.items, function(item)
if item.selectable == false then return false end
local title = item.title and item.title:lower()
local hint = item.hint and item.hint:lower()
return title and title:find(query, 1, true) or hint and hint:find(query, 1, true) or
title and table.concat(initials(title)):find(query, 1, true) or
hint and table.concat(initials(hint)):find(query, 1, true)
end)
menu.items = search_items(menu.search.source.items, query, menu.search_submenus)
-- Select 1st item in search results
menu.scroll_y = 0
self:select_index(1, menu)
@@ -685,6 +680,34 @@ function Menu:search_internal(menu)
self:update_content_dimensions()
end
---@param items MenuStackItem[]
---@param query string
---@param recursive? boolean
---@param prefix? string
---@return MenuStackItem[]
function search_items(items, query, recursive, prefix)
local result = {}
for _, item in ipairs(items) do
if item.selectable ~= false then
local prefixed_title = prefix and prefix .. ' / ' .. (item.title or '') or item.title
if item.items and recursive then
itable_append(result, search_items(item.items, query, recursive, prefixed_title))
else
local title = item.title and item.title:lower()
local hint = item.hint and item.hint:lower()
if title and title:find(query, 1, true) or hint and hint:find(query, 1, true) or
title and table.concat(initials(title)):find(query, 1, true) or
hint and table.concat(initials(hint)):find(query, 1, true) then
item = table_shallow_copy(item)
item.title = prefixed_title
result[#result + 1] = item
end
end
end
end
return result
end
---@param menu? MenuStack
function Menu:search_submit(menu)
menu = menu or self.current

View File

@@ -23,7 +23,7 @@ end
---@param opts? {submenu?: string; mouse_nav?: boolean; on_close?: string | string[]}
function toggle_menu_with_items(opts)
if Menu:is_open('menu') then Menu:close()
else open_command_menu({type = 'menu', items = config.menu_items}, opts) end
else open_command_menu({type = 'menu', items = config.menu_items, search_submenus = true}, opts) end
end
---@param options {type: string; title: string; list_prop: string; active_prop?: string; serializer: fun(list: any, active: any): MenuDataItem[]; on_select: fun(value: any); on_move_item?: fun(from_index: integer, to_index: integer, submenu_path: integer[]); on_delete_item?: fun(index: integer, submenu_path: integer[])}