mirror of
https://github.com/Insality/druid
synced 2025-11-26 10:50:54 +01:00
Up
This commit is contained in:
@@ -2,43 +2,88 @@
|
|||||||
--- Handles fetching widget data, displaying the store interface, and managing installations
|
--- Handles fetching widget data, displaying the store interface, and managing installations
|
||||||
|
|
||||||
local installer = require("druid.editor_scripts.core.installer")
|
local installer = require("druid.editor_scripts.core.installer")
|
||||||
local ui_components = require("druid.editor_scripts.core.ui_components")
|
local internal = require("druid.editor_scripts.core.asset_store.data")
|
||||||
local internal = require("druid.editor_scripts.core.asset_store_internal")
|
local dialog_ui = require("druid.editor_scripts.core.asset_store.ui.dialog")
|
||||||
|
local filters_ui = require("druid.editor_scripts.core.asset_store.ui.filters")
|
||||||
|
local search_ui = require("druid.editor_scripts.core.asset_store.ui.search")
|
||||||
|
local settings_ui = require("druid.editor_scripts.core.asset_store.ui.settings")
|
||||||
|
local widget_list_ui = require("druid.editor_scripts.core.asset_store.ui.widget_list")
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
|
local INFO_RESULT = "asset_store_open_info"
|
||||||
---Build type options array
|
local DEFAULT_INSTALL_PREF_KEY = "druid.asset_install_folder"
|
||||||
---@return table
|
local DEFAULT_INSTALL_FOLDER = "/widget"
|
||||||
local function build_type_options()
|
local DEFAULT_TITLE = "Asset Store"
|
||||||
return {"All", "Installed", "Not Installed"}
|
local DEFAULT_INFO_BUTTON = "Info"
|
||||||
end
|
local DEFAULT_CLOSE_BUTTON = "Close"
|
||||||
|
local DEFAULT_EMPTY_SEARCH_MESSAGE = "No widgets found matching '%s'."
|
||||||
|
local DEFAULT_EMPTY_FILTER_MESSAGE = "No widgets found matching the current filters."
|
||||||
|
local DEFAULT_SEARCH_LABELS = {
|
||||||
|
search_tooltip = "Search for widgets by title, author, or description"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
---Build author options array
|
local function normalize_config(input)
|
||||||
---@param authors table
|
if type(input) == "string" then
|
||||||
---@return table
|
input = { store_url = input }
|
||||||
local function build_author_options(authors)
|
|
||||||
local options = {"All Authors"}
|
|
||||||
for _, author in ipairs(authors) do
|
|
||||||
table.insert(options, author)
|
|
||||||
end
|
end
|
||||||
return options
|
|
||||||
end
|
|
||||||
|
|
||||||
|
assert(type(input) == "table", "asset_store.open expects a string URL or config table")
|
||||||
|
assert(input.store_url, "asset_store.open requires a store_url")
|
||||||
|
|
||||||
---Build tag options array
|
local config = {
|
||||||
---@param tags table
|
store_url = input.store_url,
|
||||||
---@return table
|
info_url = input.info_url,
|
||||||
local function build_tag_options(tags)
|
title = input.title or DEFAULT_TITLE,
|
||||||
local options = {"All Tags"}
|
info_button_label = input.info_button_label or DEFAULT_INFO_BUTTON,
|
||||||
for _, tag in ipairs(tags) do
|
close_button_label = input.close_button_label or DEFAULT_CLOSE_BUTTON,
|
||||||
table.insert(options, tag)
|
empty_search_message = input.empty_search_message or DEFAULT_EMPTY_SEARCH_MESSAGE,
|
||||||
|
empty_filter_message = input.empty_filter_message or DEFAULT_EMPTY_FILTER_MESSAGE,
|
||||||
|
install_prefs_key = input.install_prefs_key,
|
||||||
|
default_install_folder = input.default_install_folder or DEFAULT_INSTALL_FOLDER,
|
||||||
|
labels = input.labels or {},
|
||||||
|
info_action = input.info_action,
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.install_prefs_key == nil then
|
||||||
|
config.install_prefs_key = DEFAULT_INSTALL_PREF_KEY
|
||||||
|
elseif config.install_prefs_key == false then
|
||||||
|
config.install_prefs_key = nil
|
||||||
end
|
end
|
||||||
return options
|
|
||||||
|
config.labels.search = config.labels.search or {}
|
||||||
|
for key, value in pairs(DEFAULT_SEARCH_LABELS) do
|
||||||
|
if config.labels.search[key] == nil then
|
||||||
|
config.labels.search[key] = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return config
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
local function get_initial_install_folder(config)
|
||||||
|
if not config.install_prefs_key then
|
||||||
|
return config.default_install_folder
|
||||||
|
end
|
||||||
|
|
||||||
|
return editor.prefs.get(config.install_prefs_key) or config.default_install_folder
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
local function persist_install_folder(config, folder)
|
||||||
|
if not config.install_prefs_key then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
editor.prefs.set(config.install_prefs_key, folder)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
---Handle widget installation
|
---Handle widget installation
|
||||||
---@param item table - Widget item to install
|
---@param item table - Widget item to install
|
||||||
---@param install_folder string - Installation folder
|
---@param install_folder string - Installation folder
|
||||||
@@ -60,50 +105,38 @@ local function handle_install(item, install_folder, all_items, on_success, on_er
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
---Open the asset store dialog
|
function M.open(config_input)
|
||||||
function M.open_asset_store(store_url)
|
local config = normalize_config(config_input)
|
||||||
print("Opening Druid Asset Store from:", store_url)
|
|
||||||
|
|
||||||
-- Fetch data synchronously before creating the dialog
|
print("Opening " .. config.title .. " from:", config.store_url)
|
||||||
local store_data, fetch_error = internal.download_json(store_url)
|
|
||||||
|
local store_data, fetch_error = internal.download_json(config.store_url)
|
||||||
if not store_data then
|
if not store_data then
|
||||||
print("Failed to load widgets:", fetch_error)
|
print("Failed to load store items:", fetch_error)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
print("Successfully loaded", #store_data.items, "widgets")
|
print("Successfully loaded", #store_data.items, "items")
|
||||||
|
|
||||||
local initial_items = store_data.items
|
local initial_items = store_data.items
|
||||||
|
local initial_install_folder = get_initial_install_folder(config)
|
||||||
|
local filter_overrides = config.labels.filters and { labels = config.labels.filters } or nil
|
||||||
|
|
||||||
local dialog_component = editor.ui.component(function(props)
|
local dialog_component = editor.ui.component(function(props)
|
||||||
-- State management
|
|
||||||
local all_items = editor.ui.use_state(initial_items)
|
local all_items = editor.ui.use_state(initial_items)
|
||||||
local install_folder, set_install_folder = editor.ui.use_state(editor.prefs.get("druid.asset_install_folder") or installer.get_install_folder())
|
local install_folder, set_install_folder = editor.ui.use_state(initial_install_folder)
|
||||||
local search_query, set_search_query = editor.ui.use_state("")
|
local search_query, set_search_query = editor.ui.use_state("")
|
||||||
local filter_type, set_filter_type = editor.ui.use_state("All")
|
local filter_type, set_filter_type = editor.ui.use_state("All")
|
||||||
local filter_author, set_filter_author = editor.ui.use_state("All Authors")
|
local filter_author, set_filter_author = editor.ui.use_state("All Authors")
|
||||||
local filter_tag, set_filter_tag = editor.ui.use_state("All Tags")
|
local filter_tag, set_filter_tag = editor.ui.use_state("All Tags")
|
||||||
local install_status, set_install_status = editor.ui.use_state("")
|
local install_status, set_install_status = editor.ui.use_state("")
|
||||||
|
|
||||||
-- Extract unique authors and tags for dropdown options
|
|
||||||
local authors = editor.ui.use_memo(internal.extract_authors, all_items)
|
local authors = editor.ui.use_memo(internal.extract_authors, all_items)
|
||||||
local tags = editor.ui.use_memo(internal.extract_tags, all_items)
|
local tags = editor.ui.use_memo(internal.extract_tags, all_items)
|
||||||
|
|
||||||
-- Build dropdown options (memoized to avoid recreation on each render)
|
local type_options = editor.ui.use_memo(filters_ui.build_type_options, filter_overrides)
|
||||||
local type_options = editor.ui.use_memo(build_type_options)
|
local author_options = editor.ui.use_memo(filters_ui.build_author_options, authors, filter_overrides)
|
||||||
local author_options = editor.ui.use_memo(build_author_options, authors)
|
local tag_options = editor.ui.use_memo(filters_ui.build_tag_options, tags, filter_overrides)
|
||||||
local tag_options = editor.ui.use_memo(build_tag_options, tags)
|
|
||||||
|
|
||||||
-- Debug output
|
|
||||||
if #type_options > 0 then
|
|
||||||
print("Type options count:", #type_options, "first:", type_options[1])
|
|
||||||
end
|
|
||||||
if #author_options > 0 then
|
|
||||||
print("Author options count:", #author_options, "first:", author_options[1])
|
|
||||||
end
|
|
||||||
if #tag_options > 0 then
|
|
||||||
print("Tag options count:", #tag_options, "first:", tag_options[1])
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Filter items based on all filters
|
|
||||||
local filtered_items = editor.ui.use_memo(
|
local filtered_items = editor.ui.use_memo(
|
||||||
internal.filter_items_by_filters,
|
internal.filter_items_by_filters,
|
||||||
all_items,
|
all_items,
|
||||||
@@ -114,7 +147,6 @@ function M.open_asset_store(store_url)
|
|||||||
install_folder
|
install_folder
|
||||||
)
|
)
|
||||||
|
|
||||||
-- Installation handlers
|
|
||||||
local function on_install(item)
|
local function on_install(item)
|
||||||
handle_install(item, install_folder, all_items,
|
handle_install(item, install_folder, all_items,
|
||||||
function(message)
|
function(message)
|
||||||
@@ -126,116 +158,57 @@ function M.open_asset_store(store_url)
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Build UI content
|
|
||||||
local content_children = {}
|
local content_children = {}
|
||||||
|
|
||||||
-- Settings section
|
table.insert(content_children, settings_ui.create({
|
||||||
table.insert(content_children, editor.ui.horizontal({
|
install_folder = install_folder,
|
||||||
spacing = editor.ui.SPACING.MEDIUM,
|
on_install_folder_changed = function(new_folder)
|
||||||
children = {
|
set_install_folder(new_folder)
|
||||||
editor.ui.label({
|
persist_install_folder(config, new_folder)
|
||||||
spacing = editor.ui.SPACING.MEDIUM,
|
end,
|
||||||
text = "Installation Folder:",
|
labels = config.labels.settings
|
||||||
color = editor.ui.COLOR.TEXT
|
|
||||||
}),
|
|
||||||
|
|
||||||
editor.ui.string_field({
|
|
||||||
value = install_folder,
|
|
||||||
on_value_changed = function(new_folder)
|
|
||||||
set_install_folder(new_folder)
|
|
||||||
editor.prefs.set("druid.asset_install_folder", new_folder)
|
|
||||||
end,
|
|
||||||
title = "Installation Folder:",
|
|
||||||
tooltip = "The folder to install the assets to",
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
-- Filter dropdowns section
|
table.insert(content_children, filters_ui.create({
|
||||||
table.insert(content_children, editor.ui.horizontal({
|
filter_type = filter_type,
|
||||||
spacing = editor.ui.SPACING.MEDIUM,
|
filter_author = filter_author,
|
||||||
children = {
|
filter_tag = filter_tag,
|
||||||
-- Type filter dropdown
|
type_options = type_options,
|
||||||
editor.ui.horizontal({
|
author_options = author_options,
|
||||||
spacing = editor.ui.SPACING.SMALL,
|
tag_options = tag_options,
|
||||||
children = {
|
on_type_change = set_filter_type,
|
||||||
editor.ui.label({
|
on_author_change = set_filter_author,
|
||||||
text = "Type:",
|
on_tag_change = set_filter_tag,
|
||||||
color = editor.ui.COLOR.TEXT
|
labels = config.labels.filters,
|
||||||
}),
|
|
||||||
editor.ui.select_box({
|
|
||||||
value = filter_type,
|
|
||||||
options = type_options,
|
|
||||||
on_value_changed = set_filter_type
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
-- Author filter dropdown
|
|
||||||
editor.ui.horizontal({
|
|
||||||
spacing = editor.ui.SPACING.SMALL,
|
|
||||||
children = {
|
|
||||||
editor.ui.label({
|
|
||||||
text = "Author:",
|
|
||||||
color = editor.ui.COLOR.TEXT
|
|
||||||
}),
|
|
||||||
editor.ui.select_box({
|
|
||||||
value = filter_author,
|
|
||||||
options = author_options,
|
|
||||||
on_value_changed = set_filter_author
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
-- Tag filter dropdown
|
|
||||||
editor.ui.horizontal({
|
|
||||||
spacing = editor.ui.SPACING.SMALL,
|
|
||||||
children = {
|
|
||||||
editor.ui.label({
|
|
||||||
text = "Tag:",
|
|
||||||
color = editor.ui.COLOR.TEXT
|
|
||||||
}),
|
|
||||||
editor.ui.select_box({
|
|
||||||
value = filter_tag,
|
|
||||||
options = tag_options,
|
|
||||||
on_value_changed = set_filter_tag
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
-- Search section
|
table.insert(content_children, search_ui.create({
|
||||||
table.insert(content_children, editor.ui.horizontal({
|
search_query = search_query,
|
||||||
spacing = editor.ui.SPACING.MEDIUM,
|
on_search = set_search_query,
|
||||||
children = {
|
labels = config.labels.search,
|
||||||
editor.ui.label({
|
|
||||||
text = "Search:",
|
|
||||||
color = editor.ui.COLOR.TEXT
|
|
||||||
}),
|
|
||||||
editor.ui.string_field({
|
|
||||||
value = search_query,
|
|
||||||
on_value_changed = set_search_query,
|
|
||||||
title = "Search:",
|
|
||||||
tooltip = "Search for widgets by title, author, or description",
|
|
||||||
grow = true
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
-- Main content area
|
|
||||||
if #filtered_items == 0 then
|
if #filtered_items == 0 then
|
||||||
local message = search_query ~= "" and
|
local message = config.empty_filter_message
|
||||||
"No widgets found matching '" .. search_query .. "'." or
|
if search_query ~= "" then
|
||||||
"No widgets found matching the current filters."
|
message = string.format(config.empty_search_message, search_query)
|
||||||
|
end
|
||||||
table.insert(content_children, editor.ui.label({
|
table.insert(content_children, editor.ui.label({
|
||||||
text = message,
|
text = message,
|
||||||
color = editor.ui.COLOR.HINT,
|
color = editor.ui.COLOR.HINT,
|
||||||
alignment = editor.ui.ALIGNMENT.CENTER
|
alignment = editor.ui.ALIGNMENT.CENTER
|
||||||
}))
|
}))
|
||||||
else
|
else
|
||||||
table.insert(content_children, ui_components.create_widget_list(filtered_items, on_install))
|
table.insert(content_children, widget_list_ui.create(filtered_items, {
|
||||||
|
on_install = on_install,
|
||||||
|
open_url = internal.open_url,
|
||||||
|
is_installed = function(item)
|
||||||
|
return installer.is_widget_installed(item, install_folder)
|
||||||
|
end,
|
||||||
|
labels = config.labels.widget_card,
|
||||||
|
}))
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Install status message
|
|
||||||
if install_status ~= "" then
|
if install_status ~= "" then
|
||||||
table.insert(content_children, editor.ui.label({
|
table.insert(content_children, editor.ui.label({
|
||||||
text = install_status,
|
text = install_status,
|
||||||
@@ -244,31 +217,33 @@ function M.open_asset_store(store_url)
|
|||||||
}))
|
}))
|
||||||
end
|
end
|
||||||
|
|
||||||
return editor.ui.dialog({
|
local buttons = {}
|
||||||
title = "Druid Asset Store",
|
if config.info_url or config.info_action then
|
||||||
content = editor.ui.vertical({
|
table.insert(buttons, editor.ui.dialog_button({
|
||||||
spacing = editor.ui.SPACING.MEDIUM,
|
text = config.info_button_label,
|
||||||
padding = editor.ui.PADDING.SMALL,
|
result = INFO_RESULT,
|
||||||
grow = true,
|
}))
|
||||||
children = content_children
|
end
|
||||||
}),
|
table.insert(buttons, editor.ui.dialog_button({
|
||||||
buttons = {
|
text = config.close_button_label,
|
||||||
editor.ui.dialog_button({
|
cancel = true
|
||||||
text = "Info",
|
}))
|
||||||
result = "info_assets_store",
|
|
||||||
}),
|
return dialog_ui.build({
|
||||||
editor.ui.dialog_button({
|
title = config.title,
|
||||||
text = "Close",
|
children = content_children,
|
||||||
cancel = true
|
buttons = buttons
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
end)
|
end)
|
||||||
|
|
||||||
local result = editor.ui.show_dialog(dialog_component({}))
|
local result = editor.ui.show_dialog(dialog_component({}))
|
||||||
|
|
||||||
if result and result == "info_assets_store" then
|
if result and result == INFO_RESULT then
|
||||||
editor.browse("https://github.com/Insality/core/blob/main/druid_widget_store.md")
|
if config.info_action then
|
||||||
|
config.info_action()
|
||||||
|
elseif config.info_url then
|
||||||
|
internal.open_url(config.info_url)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return {}
|
return {}
|
||||||
|
|||||||
196
druid/editor_scripts/core/asset_store/data.lua
Normal file
196
druid/editor_scripts/core/asset_store/data.lua
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
local installer = require("druid.editor_scripts.core.installer")
|
||||||
|
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
|
||||||
|
local function normalize_query(query)
|
||||||
|
if not query or query == "" then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
return string.lower(query)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function is_unlisted_visible(item, lower_query)
|
||||||
|
if not item.unlisted then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
if not lower_query or not item.id then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
return string.lower(item.id) == lower_query
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function M.download_json(json_url)
|
||||||
|
local response = http.request(json_url, { as = "json" })
|
||||||
|
|
||||||
|
if response.status ~= 200 then
|
||||||
|
return nil, "Failed to fetch store data. HTTP status: " .. response.status
|
||||||
|
end
|
||||||
|
|
||||||
|
if not response.body or not response.body.items then
|
||||||
|
return nil, "Invalid store data format"
|
||||||
|
end
|
||||||
|
|
||||||
|
return response.body, nil
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function M.filter_items(items, query)
|
||||||
|
if query == "" or query == nil then
|
||||||
|
return items
|
||||||
|
end
|
||||||
|
|
||||||
|
local filtered = {}
|
||||||
|
local lower_query = string.lower(query)
|
||||||
|
|
||||||
|
for _, item in ipairs(items) do
|
||||||
|
local matches = false
|
||||||
|
if item.id and string.find(string.lower(item.id), lower_query, 1, true) then
|
||||||
|
matches = true
|
||||||
|
elseif item.title and string.find(string.lower(item.title), lower_query, 1, true) then
|
||||||
|
matches = true
|
||||||
|
elseif item.author and string.find(string.lower(item.author), lower_query, 1, true) then
|
||||||
|
matches = true
|
||||||
|
elseif item.description and string.find(string.lower(item.description), lower_query, 1, true) then
|
||||||
|
matches = true
|
||||||
|
end
|
||||||
|
|
||||||
|
if not matches and item.tags then
|
||||||
|
for _, tag in ipairs(item.tags) do
|
||||||
|
if string.find(string.lower(tag), lower_query, 1, true) then
|
||||||
|
matches = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not matches and item.depends then
|
||||||
|
for _, dep in ipairs(item.depends) do
|
||||||
|
if string.find(string.lower(dep), lower_query, 1, true) then
|
||||||
|
matches = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if matches then
|
||||||
|
table.insert(filtered, item)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return filtered
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function M.extract_authors(items)
|
||||||
|
local authors = {}
|
||||||
|
local author_set = {}
|
||||||
|
|
||||||
|
for _, item in ipairs(items) do
|
||||||
|
if not item.unlisted and item.author and not author_set[item.author] then
|
||||||
|
author_set[item.author] = true
|
||||||
|
table.insert(authors, item.author)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
table.sort(authors)
|
||||||
|
|
||||||
|
return authors
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function M.extract_tags(items)
|
||||||
|
local tags = {}
|
||||||
|
local tag_set = {}
|
||||||
|
|
||||||
|
for _, item in ipairs(items) do
|
||||||
|
if not item.unlisted and item.tags then
|
||||||
|
for _, tag in ipairs(item.tags) do
|
||||||
|
if not tag_set[tag] then
|
||||||
|
tag_set[tag] = true
|
||||||
|
table.insert(tags, tag)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
table.sort(tags)
|
||||||
|
|
||||||
|
return tags
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function M.filter_items_by_filters(items, search_query, filter_type, filter_author, filter_tag, install_folder)
|
||||||
|
local lower_query = normalize_query(search_query)
|
||||||
|
local visible_items = {}
|
||||||
|
|
||||||
|
for _, item in ipairs(items) do
|
||||||
|
if is_unlisted_visible(item, lower_query) then
|
||||||
|
table.insert(visible_items, item)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local filtered = visible_items
|
||||||
|
|
||||||
|
if lower_query then
|
||||||
|
filtered = M.filter_items(filtered, search_query)
|
||||||
|
end
|
||||||
|
|
||||||
|
if filter_type and filter_type ~= "All" then
|
||||||
|
local type_filtered = {}
|
||||||
|
for _, item in ipairs(filtered) do
|
||||||
|
local is_installed = installer.is_widget_installed(item, install_folder)
|
||||||
|
if (filter_type == "Installed" and is_installed) or
|
||||||
|
(filter_type == "Not Installed" and not is_installed) then
|
||||||
|
table.insert(type_filtered, item)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
filtered = type_filtered
|
||||||
|
end
|
||||||
|
|
||||||
|
if filter_author and filter_author ~= "All Authors" then
|
||||||
|
local author_filtered = {}
|
||||||
|
for _, item in ipairs(filtered) do
|
||||||
|
if item.author == filter_author then
|
||||||
|
table.insert(author_filtered, item)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
filtered = author_filtered
|
||||||
|
end
|
||||||
|
|
||||||
|
if filter_tag and filter_tag ~= "All Tags" then
|
||||||
|
local tag_filtered = {}
|
||||||
|
for _, item in ipairs(filtered) do
|
||||||
|
if item.tags then
|
||||||
|
for _, tag in ipairs(item.tags) do
|
||||||
|
if tag == filter_tag then
|
||||||
|
table.insert(tag_filtered, item)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
filtered = tag_filtered
|
||||||
|
end
|
||||||
|
|
||||||
|
return filtered
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function M.open_url(url)
|
||||||
|
if not url then
|
||||||
|
print("No URL available for:", url)
|
||||||
|
end
|
||||||
|
|
||||||
|
editor.browse(url)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
return M
|
||||||
|
|
||||||
19
druid/editor_scripts/core/asset_store/ui/dialog.lua
Normal file
19
druid/editor_scripts/core/asset_store/ui/dialog.lua
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
local M = {}
|
||||||
|
|
||||||
|
|
||||||
|
function M.build(params)
|
||||||
|
return editor.ui.dialog({
|
||||||
|
title = params.title or "Asset Store",
|
||||||
|
content = editor.ui.vertical({
|
||||||
|
spacing = params.spacing or editor.ui.SPACING.MEDIUM,
|
||||||
|
padding = params.padding or editor.ui.PADDING.SMALL,
|
||||||
|
grow = true,
|
||||||
|
children = params.children or {}
|
||||||
|
}),
|
||||||
|
buttons = params.buttons or {}
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
return M
|
||||||
|
|
||||||
119
druid/editor_scripts/core/asset_store/ui/filters.lua
Normal file
119
druid/editor_scripts/core/asset_store/ui/filters.lua
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
local DEFAULT_LABELS = {
|
||||||
|
type_label = "Type:",
|
||||||
|
author_label = "Author:",
|
||||||
|
tag_label = "Tag:",
|
||||||
|
all_types = "All",
|
||||||
|
installed = "Installed",
|
||||||
|
not_installed = "Not Installed",
|
||||||
|
all_authors = "All Authors",
|
||||||
|
all_tags = "All Tags",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
|
||||||
|
local function build_labels(overrides)
|
||||||
|
if not overrides then
|
||||||
|
return DEFAULT_LABELS
|
||||||
|
end
|
||||||
|
|
||||||
|
local labels = {}
|
||||||
|
for key, value in pairs(DEFAULT_LABELS) do
|
||||||
|
labels[key] = overrides[key] or value
|
||||||
|
end
|
||||||
|
|
||||||
|
return labels
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function M.build_type_options(overrides)
|
||||||
|
local labels = build_labels(overrides and overrides.labels)
|
||||||
|
|
||||||
|
return {
|
||||||
|
labels.all_types,
|
||||||
|
labels.installed,
|
||||||
|
labels.not_installed,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function M.build_author_options(authors, overrides)
|
||||||
|
local labels = build_labels(overrides and overrides.labels)
|
||||||
|
local options = {labels.all_authors}
|
||||||
|
|
||||||
|
for _, author in ipairs(authors or {}) do
|
||||||
|
table.insert(options, author)
|
||||||
|
end
|
||||||
|
|
||||||
|
return options
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function M.build_tag_options(tags, overrides)
|
||||||
|
local labels = build_labels(overrides and overrides.labels)
|
||||||
|
local options = {labels.all_tags}
|
||||||
|
|
||||||
|
for _, tag in ipairs(tags or {}) do
|
||||||
|
table.insert(options, tag)
|
||||||
|
end
|
||||||
|
|
||||||
|
return options
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function M.create(params)
|
||||||
|
local labels = build_labels(params and params.labels)
|
||||||
|
|
||||||
|
return editor.ui.horizontal({
|
||||||
|
spacing = editor.ui.SPACING.MEDIUM,
|
||||||
|
children = {
|
||||||
|
editor.ui.horizontal({
|
||||||
|
spacing = editor.ui.SPACING.SMALL,
|
||||||
|
children = {
|
||||||
|
editor.ui.label({
|
||||||
|
text = labels.type_label,
|
||||||
|
color = editor.ui.COLOR.TEXT
|
||||||
|
}),
|
||||||
|
editor.ui.select_box({
|
||||||
|
value = params.filter_type,
|
||||||
|
options = params.type_options,
|
||||||
|
on_value_changed = params.on_type_change
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
editor.ui.horizontal({
|
||||||
|
spacing = editor.ui.SPACING.SMALL,
|
||||||
|
children = {
|
||||||
|
editor.ui.label({
|
||||||
|
text = labels.author_label,
|
||||||
|
color = editor.ui.COLOR.TEXT
|
||||||
|
}),
|
||||||
|
editor.ui.select_box({
|
||||||
|
value = params.filter_author,
|
||||||
|
options = params.author_options,
|
||||||
|
on_value_changed = params.on_author_change
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
editor.ui.horizontal({
|
||||||
|
spacing = editor.ui.SPACING.SMALL,
|
||||||
|
children = {
|
||||||
|
editor.ui.label({
|
||||||
|
text = labels.tag_label,
|
||||||
|
color = editor.ui.COLOR.TEXT
|
||||||
|
}),
|
||||||
|
editor.ui.select_box({
|
||||||
|
value = params.filter_tag,
|
||||||
|
options = params.tag_options,
|
||||||
|
on_value_changed = params.on_tag_change
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
return M
|
||||||
|
|
||||||
49
druid/editor_scripts/core/asset_store/ui/search.lua
Normal file
49
druid/editor_scripts/core/asset_store/ui/search.lua
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
local DEFAULT_LABELS = {
|
||||||
|
search_label = "Search:",
|
||||||
|
search_title = "Search:",
|
||||||
|
search_tooltip = "Search for items",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
|
||||||
|
local function build_labels(overrides)
|
||||||
|
if not overrides then
|
||||||
|
return DEFAULT_LABELS
|
||||||
|
end
|
||||||
|
|
||||||
|
local labels = {}
|
||||||
|
for key, value in pairs(DEFAULT_LABELS) do
|
||||||
|
labels[key] = overrides[key] or value
|
||||||
|
end
|
||||||
|
|
||||||
|
return labels
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function M.create(params)
|
||||||
|
local labels = build_labels(params and params.labels)
|
||||||
|
|
||||||
|
return editor.ui.horizontal({
|
||||||
|
spacing = editor.ui.SPACING.MEDIUM,
|
||||||
|
children = {
|
||||||
|
editor.ui.label({
|
||||||
|
text = labels.search_label,
|
||||||
|
color = editor.ui.COLOR.TEXT
|
||||||
|
}),
|
||||||
|
editor.ui.string_field({
|
||||||
|
value = params.search_query or "",
|
||||||
|
on_value_changed = params.on_search,
|
||||||
|
title = labels.search_title,
|
||||||
|
tooltip = labels.search_tooltip,
|
||||||
|
grow = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
return M
|
||||||
|
|
||||||
49
druid/editor_scripts/core/asset_store/ui/settings.lua
Normal file
49
druid/editor_scripts/core/asset_store/ui/settings.lua
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
local DEFAULT_LABELS = {
|
||||||
|
install_label = "Installation Folder:",
|
||||||
|
install_title = "Installation Folder:",
|
||||||
|
install_tooltip = "The folder to install the assets to",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
|
||||||
|
local function build_labels(overrides)
|
||||||
|
if not overrides then
|
||||||
|
return DEFAULT_LABELS
|
||||||
|
end
|
||||||
|
|
||||||
|
local labels = {}
|
||||||
|
for key, value in pairs(DEFAULT_LABELS) do
|
||||||
|
labels[key] = overrides[key] or value
|
||||||
|
end
|
||||||
|
|
||||||
|
return labels
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function M.create(params)
|
||||||
|
local labels = build_labels(params and params.labels)
|
||||||
|
|
||||||
|
return editor.ui.horizontal({
|
||||||
|
spacing = editor.ui.SPACING.MEDIUM,
|
||||||
|
children = {
|
||||||
|
editor.ui.label({
|
||||||
|
spacing = editor.ui.SPACING.MEDIUM,
|
||||||
|
text = labels.install_label,
|
||||||
|
color = editor.ui.COLOR.TEXT
|
||||||
|
}),
|
||||||
|
editor.ui.string_field({
|
||||||
|
value = params.install_folder,
|
||||||
|
on_value_changed = params.on_install_folder_changed,
|
||||||
|
title = labels.install_title,
|
||||||
|
tooltip = labels.install_tooltip,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
return M
|
||||||
|
|
||||||
167
druid/editor_scripts/core/asset_store/ui/widget_card.lua
Normal file
167
druid/editor_scripts/core/asset_store/ui/widget_card.lua
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
local DEFAULT_LABELS = {
|
||||||
|
install_button = "Install",
|
||||||
|
api_button = "API",
|
||||||
|
example_button = "Example",
|
||||||
|
author_caption = "Author",
|
||||||
|
installed_tag = "✓ Installed",
|
||||||
|
tags_prefix = "Tags: ",
|
||||||
|
depends_prefix = "Depends: ",
|
||||||
|
size_separator = "• ",
|
||||||
|
unknown_size = "Unknown size",
|
||||||
|
unknown_version = "Unknown version",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
|
||||||
|
local function format_size(size_bytes)
|
||||||
|
if not size_bytes then
|
||||||
|
return DEFAULT_LABELS.unknown_size
|
||||||
|
end
|
||||||
|
|
||||||
|
if size_bytes < 1024 then
|
||||||
|
return size_bytes .. " B"
|
||||||
|
elseif size_bytes < 1024 * 1024 then
|
||||||
|
return math.floor(size_bytes / 1024) .. " KB"
|
||||||
|
end
|
||||||
|
|
||||||
|
return math.floor(size_bytes / (1024 * 1024)) .. " MB"
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
local function build_labels(overrides)
|
||||||
|
if not overrides then
|
||||||
|
return DEFAULT_LABELS
|
||||||
|
end
|
||||||
|
|
||||||
|
local labels = {}
|
||||||
|
for key, value in pairs(DEFAULT_LABELS) do
|
||||||
|
labels[key] = overrides[key] or value
|
||||||
|
end
|
||||||
|
|
||||||
|
return labels
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function M.create(item, context)
|
||||||
|
local labels = build_labels(context and context.labels)
|
||||||
|
local open_url = context and context.open_url or function(_) end
|
||||||
|
local on_install = context and context.on_install or function(...) end
|
||||||
|
local is_installed = context and context.is_installed or false
|
||||||
|
|
||||||
|
local size_text = format_size(item.size)
|
||||||
|
local version_text = item.version and ("v" .. item.version) or labels.unknown_version
|
||||||
|
local tags_text = item.tags and #item.tags > 0 and labels.tags_prefix .. table.concat(item.tags, ", ") or ""
|
||||||
|
local deps_text = item.depends and #item.depends > 0 and labels.depends_prefix .. table.concat(item.depends, ", ") or ""
|
||||||
|
|
||||||
|
local widget_details_children = {
|
||||||
|
editor.ui.horizontal({
|
||||||
|
spacing = editor.ui.SPACING.SMALL,
|
||||||
|
children = {
|
||||||
|
editor.ui.label({
|
||||||
|
text = item.title or item.id,
|
||||||
|
color = editor.ui.COLOR.OVERRIDE
|
||||||
|
}),
|
||||||
|
editor.ui.label({
|
||||||
|
text = version_text,
|
||||||
|
color = editor.ui.COLOR.WARNING
|
||||||
|
}),
|
||||||
|
editor.ui.label({
|
||||||
|
text = labels.size_separator .. size_text,
|
||||||
|
color = editor.ui.COLOR.HINT
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
editor.ui.paragraph({
|
||||||
|
text = item.description or "No description available",
|
||||||
|
color = editor.ui.COLOR.TEXT
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if tags_text ~= "" then
|
||||||
|
table.insert(widget_details_children, editor.ui.label({
|
||||||
|
text = tags_text,
|
||||||
|
color = editor.ui.COLOR.HINT
|
||||||
|
}))
|
||||||
|
end
|
||||||
|
|
||||||
|
if deps_text ~= "" then
|
||||||
|
table.insert(widget_details_children, editor.ui.label({
|
||||||
|
text = deps_text,
|
||||||
|
color = editor.ui.COLOR.HINT
|
||||||
|
}))
|
||||||
|
end
|
||||||
|
|
||||||
|
if is_installed then
|
||||||
|
table.insert(widget_details_children, editor.ui.label({
|
||||||
|
text = labels.installed_tag,
|
||||||
|
color = editor.ui.COLOR.WARNING
|
||||||
|
}))
|
||||||
|
end
|
||||||
|
|
||||||
|
local button_children = {
|
||||||
|
editor.ui.button({
|
||||||
|
text = labels.install_button,
|
||||||
|
on_pressed = on_install,
|
||||||
|
enabled = not is_installed
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if item.api then
|
||||||
|
table.insert(button_children, editor.ui.button({
|
||||||
|
text = labels.api_button,
|
||||||
|
on_pressed = function() open_url(item.api) end,
|
||||||
|
enabled = item.api ~= nil
|
||||||
|
}))
|
||||||
|
end
|
||||||
|
|
||||||
|
if item.example_url then
|
||||||
|
table.insert(button_children, editor.ui.button({
|
||||||
|
text = labels.example_button,
|
||||||
|
on_pressed = function() open_url(item.example_url) end,
|
||||||
|
enabled = item.example_url ~= nil
|
||||||
|
}))
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(button_children, editor.ui.horizontal({ grow = true }))
|
||||||
|
|
||||||
|
if item.author_url then
|
||||||
|
table.insert(button_children, editor.ui.label({
|
||||||
|
text = labels.author_caption,
|
||||||
|
color = editor.ui.COLOR.HINT
|
||||||
|
}))
|
||||||
|
table.insert(button_children, editor.ui.button({
|
||||||
|
text = item.author or labels.author_caption,
|
||||||
|
on_pressed = function() open_url(item.author_url) end,
|
||||||
|
enabled = item.author_url ~= nil
|
||||||
|
}))
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(widget_details_children, editor.ui.horizontal({
|
||||||
|
spacing = editor.ui.SPACING.SMALL,
|
||||||
|
children = button_children
|
||||||
|
}))
|
||||||
|
|
||||||
|
return editor.ui.horizontal({
|
||||||
|
spacing = editor.ui.SPACING.NONE,
|
||||||
|
padding = editor.ui.PADDING.SMALL,
|
||||||
|
children = {
|
||||||
|
editor.ui.label({
|
||||||
|
text = "•••",
|
||||||
|
color = editor.ui.COLOR.HINT
|
||||||
|
}),
|
||||||
|
editor.ui.vertical({
|
||||||
|
spacing = editor.ui.SPACING.SMALL,
|
||||||
|
grow = true,
|
||||||
|
children = widget_details_children
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
return M
|
||||||
|
|
||||||
51
druid/editor_scripts/core/asset_store/ui/widget_list.lua
Normal file
51
druid/editor_scripts/core/asset_store/ui/widget_list.lua
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
local widget_card = require("druid.editor_scripts.core.asset_store.ui.widget_card")
|
||||||
|
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
|
||||||
|
local function noop(...)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
local function build_context(overrides)
|
||||||
|
return {
|
||||||
|
on_install = overrides.on_install or noop,
|
||||||
|
open_url = overrides.open_url or noop,
|
||||||
|
labels = overrides.labels,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function M.create(items, overrides)
|
||||||
|
local card_context = build_context(overrides or {})
|
||||||
|
local is_installed = overrides and overrides.is_installed or function(_)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local widget_items = {}
|
||||||
|
for _, item in ipairs(items) do
|
||||||
|
local context = {
|
||||||
|
on_install = function()
|
||||||
|
card_context.on_install(item)
|
||||||
|
end,
|
||||||
|
open_url = card_context.open_url,
|
||||||
|
labels = card_context.labels,
|
||||||
|
is_installed = is_installed(item),
|
||||||
|
}
|
||||||
|
|
||||||
|
table.insert(widget_items, widget_card.create(item, context))
|
||||||
|
end
|
||||||
|
|
||||||
|
return editor.ui.scroll({
|
||||||
|
content = editor.ui.vertical({
|
||||||
|
children = widget_items
|
||||||
|
})
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
return M
|
||||||
|
|
||||||
@@ -1,213 +1,39 @@
|
|||||||
local installer = require("druid.editor_scripts.core.installer")
|
local data = require("druid.editor_scripts.core.asset_store.data")
|
||||||
|
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
---Download a JSON file from a URL
|
|
||||||
---@param json_url string - The URL to download the JSON from
|
|
||||||
---@return table|nil, string|nil - The JSON data or nil, error message or nil
|
|
||||||
function M.download_json(json_url)
|
function M.download_json(json_url)
|
||||||
local response = http.request(json_url, { as = "json" })
|
return data.download_json(json_url)
|
||||||
|
|
||||||
if response.status ~= 200 then
|
|
||||||
return nil, "Failed to fetch store data. HTTP status: " .. response.status
|
|
||||||
end
|
|
||||||
|
|
||||||
if not response.body or not response.body.items then
|
|
||||||
return nil, "Invalid store data format"
|
|
||||||
end
|
|
||||||
|
|
||||||
return response.body, nil
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local function is_unlisted_visible(item, lower_query)
|
|
||||||
if not item.unlisted then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
if not lower_query or lower_query == "" or not item.id then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
return string.lower(item.id) == lower_query
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
---Filter items based on search query
|
|
||||||
---Filter items based on search query
|
|
||||||
---@param items table - List of widget items
|
|
||||||
---@param query string - Search query
|
|
||||||
---@return table - Filtered items
|
|
||||||
function M.filter_items(items, query)
|
function M.filter_items(items, query)
|
||||||
if query == "" then
|
return data.filter_items(items, query)
|
||||||
return items
|
|
||||||
end
|
|
||||||
|
|
||||||
local filtered = {}
|
|
||||||
local lower_query = string.lower(query)
|
|
||||||
|
|
||||||
for _, item in ipairs(items) do
|
|
||||||
-- Search in title, author, description
|
|
||||||
local matches = false
|
|
||||||
if item.id and string.find(string.lower(item.id), lower_query, 1, true) then
|
|
||||||
matches = true
|
|
||||||
elseif item.title and string.find(string.lower(item.title), lower_query, 1, true) then
|
|
||||||
matches = true
|
|
||||||
elseif item.author and string.find(string.lower(item.author), lower_query, 1, true) then
|
|
||||||
matches = true
|
|
||||||
elseif item.description and string.find(string.lower(item.description), lower_query, 1, true) then
|
|
||||||
matches = true
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Search in tags
|
|
||||||
if not matches and item.tags then
|
|
||||||
for _, tag in ipairs(item.tags) do
|
|
||||||
if string.find(string.lower(tag), lower_query, 1, true) then
|
|
||||||
matches = true
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Search in dependencies
|
|
||||||
if not matches and item.depends then
|
|
||||||
for _, dep in ipairs(item.depends) do
|
|
||||||
if string.find(string.lower(dep), lower_query, 1, true) then
|
|
||||||
matches = true
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if matches then
|
|
||||||
table.insert(filtered, item)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return filtered
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
---Extract unique authors from items list
|
|
||||||
---@param items table - List of widget items
|
|
||||||
---@return table - Sorted list of unique authors
|
|
||||||
function M.extract_authors(items)
|
function M.extract_authors(items)
|
||||||
local authors = {}
|
return data.extract_authors(items)
|
||||||
local author_set = {}
|
|
||||||
|
|
||||||
for _, item in ipairs(items) do
|
|
||||||
if not item.unlisted and item.author and not author_set[item.author] then
|
|
||||||
author_set[item.author] = true
|
|
||||||
table.insert(authors, item.author)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
table.sort(authors)
|
|
||||||
return authors
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
---Extract unique tags from items list
|
|
||||||
---@param items table - List of widget items
|
|
||||||
---@return table - Sorted list of unique tags
|
|
||||||
function M.extract_tags(items)
|
function M.extract_tags(items)
|
||||||
local tags = {}
|
return data.extract_tags(items)
|
||||||
local tag_set = {}
|
|
||||||
|
|
||||||
for _, item in ipairs(items) do
|
|
||||||
if not item.unlisted and item.tags then
|
|
||||||
for _, tag in ipairs(item.tags) do
|
|
||||||
if not tag_set[tag] then
|
|
||||||
tag_set[tag] = true
|
|
||||||
table.insert(tags, tag)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
table.sort(tags)
|
|
||||||
return tags
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
---Filter items based on all filters (search, type, author, tag)
|
|
||||||
---@param items table - List of widget items
|
|
||||||
---@param search_query string - Search query
|
|
||||||
---@param filter_type string - Type filter: "All", "Installed", "Not Installed"
|
|
||||||
---@param filter_author string - Author filter: "All Authors" or specific author
|
|
||||||
---@param filter_tag string - Tag filter: "All Tags" or specific tag
|
|
||||||
---@param install_folder string - Installation folder to check installed status
|
|
||||||
---@return table - Filtered items
|
|
||||||
function M.filter_items_by_filters(items, search_query, filter_type, filter_author, filter_tag, install_folder)
|
function M.filter_items_by_filters(items, search_query, filter_type, filter_author, filter_tag, install_folder)
|
||||||
local lower_query = nil
|
return data.filter_items_by_filters(items, search_query, filter_type, filter_author, filter_tag, install_folder)
|
||||||
if search_query and search_query ~= "" then
|
|
||||||
lower_query = string.lower(search_query)
|
|
||||||
end
|
|
||||||
|
|
||||||
local visible_items = {}
|
|
||||||
for _, item in ipairs(items) do
|
|
||||||
if is_unlisted_visible(item, lower_query) then
|
|
||||||
table.insert(visible_items, item)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local filtered = visible_items
|
|
||||||
|
|
||||||
-- Filter by search query
|
|
||||||
if search_query and search_query ~= "" then
|
|
||||||
filtered = M.filter_items(filtered, search_query)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Filter by type (Installed/Not Installed)
|
|
||||||
if filter_type and filter_type ~= "All" then
|
|
||||||
local type_filtered = {}
|
|
||||||
for _, item in ipairs(filtered) do
|
|
||||||
local is_installed = installer.is_widget_installed(item, install_folder)
|
|
||||||
if (filter_type == "Installed" and is_installed) or
|
|
||||||
(filter_type == "Not Installed" and not is_installed) then
|
|
||||||
table.insert(type_filtered, item)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
filtered = type_filtered
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Filter by author
|
|
||||||
if filter_author and filter_author ~= "All Authors" then
|
|
||||||
local author_filtered = {}
|
|
||||||
for _, item in ipairs(filtered) do
|
|
||||||
if item.author == filter_author then
|
|
||||||
table.insert(author_filtered, item)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
filtered = author_filtered
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Filter by tag
|
|
||||||
if filter_tag and filter_tag ~= "All Tags" then
|
|
||||||
local tag_filtered = {}
|
|
||||||
for _, item in ipairs(filtered) do
|
|
||||||
if item.tags then
|
|
||||||
for _, tag in ipairs(item.tags) do
|
|
||||||
if tag == filter_tag then
|
|
||||||
table.insert(tag_filtered, item)
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
filtered = tag_filtered
|
|
||||||
end
|
|
||||||
|
|
||||||
return filtered
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
---Open a URL in the default browser
|
|
||||||
---@param url string - The URL to open
|
|
||||||
function M.open_url(url)
|
function M.open_url(url)
|
||||||
if not url then
|
if not url then
|
||||||
print("No URL available for:", url)
|
print("No URL available for:", url)
|
||||||
end
|
end
|
||||||
|
|
||||||
editor.browse(url)
|
editor.browse(url)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,303 +0,0 @@
|
|||||||
--- Module for reusable UI components in the asset store
|
|
||||||
--- Contains component builders for filters, widget items, and lists
|
|
||||||
|
|
||||||
local internal = require("druid.editor_scripts.core.asset_store_internal")
|
|
||||||
local installer = require("druid.editor_scripts.core.installer")
|
|
||||||
|
|
||||||
local M = {}
|
|
||||||
|
|
||||||
|
|
||||||
---Create a settings section with installation folder input
|
|
||||||
---@param install_path string - Current installation path
|
|
||||||
---@param on_change function - Callback when path changes
|
|
||||||
---@return userdata - UI component
|
|
||||||
function M.create_settings_section(install_path, on_change)
|
|
||||||
return editor.ui.vertical({
|
|
||||||
spacing = editor.ui.SPACING.SMALL,
|
|
||||||
children = {
|
|
||||||
editor.ui.label({
|
|
||||||
text = "Installation Folder:",
|
|
||||||
color = editor.ui.COLOR.TEXT
|
|
||||||
}),
|
|
||||||
editor.ui.label({
|
|
||||||
text = install_path,
|
|
||||||
color = editor.ui.COLOR.TEXT,
|
|
||||||
grow = true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
---Extract unique authors from items list
|
|
||||||
---@param items table - List of widget items
|
|
||||||
---@return table - Sorted list of unique authors
|
|
||||||
local function extract_authors(items)
|
|
||||||
local authors = {}
|
|
||||||
local author_set = {}
|
|
||||||
|
|
||||||
for _, item in ipairs(items) do
|
|
||||||
if item.author and not author_set[item.author] then
|
|
||||||
author_set[item.author] = true
|
|
||||||
table.insert(authors, item.author)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
table.sort(authors)
|
|
||||||
return authors
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
---Extract unique tags from items list
|
|
||||||
---@param items table - List of widget items
|
|
||||||
---@return table - Sorted list of unique tags
|
|
||||||
local function extract_tags(items)
|
|
||||||
local tags = {}
|
|
||||||
local tag_set = {}
|
|
||||||
|
|
||||||
for _, item in ipairs(items) do
|
|
||||||
if item.tags then
|
|
||||||
for _, tag in ipairs(item.tags) do
|
|
||||||
if not tag_set[tag] then
|
|
||||||
tag_set[tag] = true
|
|
||||||
table.insert(tags, tag)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
table.sort(tags)
|
|
||||||
return tags
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
---Create filter section with author and tag dropdowns
|
|
||||||
---@param items table - List of all widget items
|
|
||||||
---@param author_filter string - Current author filter
|
|
||||||
---@param tag_filter string - Current tag filter
|
|
||||||
---@param on_author_change function - Callback for author filter change
|
|
||||||
---@param on_tag_change function - Callback for tag filter change
|
|
||||||
---@return userdata - UI component
|
|
||||||
function M.create_filter_section(items, author_filter, tag_filter, on_author_change, on_tag_change)
|
|
||||||
local authors = extract_authors(items)
|
|
||||||
local tags = extract_tags(items)
|
|
||||||
|
|
||||||
-- Build author options
|
|
||||||
local author_options = {"All Authors"}
|
|
||||||
for _, author in ipairs(authors) do
|
|
||||||
table.insert(author_options, author)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Build tag options
|
|
||||||
local tag_options = {"All Categories"}
|
|
||||||
for _, tag in ipairs(tags) do
|
|
||||||
table.insert(tag_options, tag)
|
|
||||||
end
|
|
||||||
|
|
||||||
return editor.ui.horizontal({
|
|
||||||
spacing = editor.ui.SPACING.MEDIUM,
|
|
||||||
children = {
|
|
||||||
editor.ui.vertical({
|
|
||||||
spacing = editor.ui.SPACING.SMALL,
|
|
||||||
children = {
|
|
||||||
editor.ui.label({
|
|
||||||
text = "Author:",
|
|
||||||
color = editor.ui.COLOR.TEXT
|
|
||||||
}),
|
|
||||||
editor.ui.label({
|
|
||||||
text = author_filter,
|
|
||||||
color = editor.ui.COLOR.TEXT
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
editor.ui.vertical({
|
|
||||||
spacing = editor.ui.SPACING.SMALL,
|
|
||||||
children = {
|
|
||||||
editor.ui.label({
|
|
||||||
text = "Category:",
|
|
||||||
color = editor.ui.COLOR.TEXT
|
|
||||||
}),
|
|
||||||
editor.ui.label({
|
|
||||||
text = tag_filter,
|
|
||||||
color = editor.ui.COLOR.TEXT
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
---Format file size for display
|
|
||||||
---@param size_bytes number - Size in bytes
|
|
||||||
---@return string - Formatted size string
|
|
||||||
local function format_size(size_bytes)
|
|
||||||
if size_bytes < 1024 then
|
|
||||||
return size_bytes .. " B"
|
|
||||||
elseif size_bytes < 1024 * 1024 then
|
|
||||||
return math.floor(size_bytes / 1024) .. " KB"
|
|
||||||
else
|
|
||||||
return math.floor(size_bytes / (1024 * 1024)) .. " MB"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
---Create a widget item card
|
|
||||||
---@param item table - Widget item data
|
|
||||||
---@param is_installed boolean - Whether widget is already installed
|
|
||||||
---@param on_install function - Callback for install button
|
|
||||||
---@return userdata - UI component
|
|
||||||
function M.create_widget_item(item, is_installed, on_install)
|
|
||||||
local size_text = item.size and format_size(item.size) or "Unknown size"
|
|
||||||
local version_text = item.version and "v" .. item.version or "Unknown version"
|
|
||||||
|
|
||||||
-- Create tags display
|
|
||||||
local tags_text = ""
|
|
||||||
if item.tags and #item.tags > 0 then
|
|
||||||
tags_text = "Tags: " .. table.concat(item.tags, ", ")
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Create dependencies display
|
|
||||||
local deps_text = ""
|
|
||||||
if item.depends and #item.depends > 0 then
|
|
||||||
deps_text = "Depends: " .. table.concat(item.depends, ", ")
|
|
||||||
end
|
|
||||||
|
|
||||||
local widget_details_children = {
|
|
||||||
editor.ui.horizontal({
|
|
||||||
spacing = editor.ui.SPACING.SMALL,
|
|
||||||
children = {
|
|
||||||
editor.ui.label({
|
|
||||||
text = item.title or item.id,
|
|
||||||
color = editor.ui.COLOR.OVERRIDE
|
|
||||||
}),
|
|
||||||
editor.ui.label({
|
|
||||||
text = version_text,
|
|
||||||
color = editor.ui.COLOR.WARNING
|
|
||||||
}),
|
|
||||||
editor.ui.label({
|
|
||||||
text = "• " .. size_text,
|
|
||||||
color = editor.ui.COLOR.HINT
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
|
|
||||||
-- Description
|
|
||||||
editor.ui.paragraph({
|
|
||||||
text = item.description or "No description available",
|
|
||||||
color = editor.ui.COLOR.TEXT
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
|
|
||||||
if tags_text ~= "" then
|
|
||||||
table.insert(widget_details_children, editor.ui.label({
|
|
||||||
text = tags_text,
|
|
||||||
color = editor.ui.COLOR.HINT
|
|
||||||
}))
|
|
||||||
end
|
|
||||||
|
|
||||||
if deps_text ~= "" then
|
|
||||||
table.insert(widget_details_children, editor.ui.label({
|
|
||||||
text = deps_text,
|
|
||||||
color = editor.ui.COLOR.HINT
|
|
||||||
}))
|
|
||||||
end
|
|
||||||
|
|
||||||
if is_installed then
|
|
||||||
table.insert(widget_details_children, editor.ui.label({
|
|
||||||
text = "✓ Installed",
|
|
||||||
color = editor.ui.COLOR.WARNING
|
|
||||||
}))
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Create button row at the bottom
|
|
||||||
local button_children = {
|
|
||||||
editor.ui.button({
|
|
||||||
text = "Install",
|
|
||||||
on_pressed = on_install,
|
|
||||||
enabled = not is_installed
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
|
|
||||||
if item.api ~= nil then
|
|
||||||
table.insert(button_children, editor.ui.button({
|
|
||||||
text = "API",
|
|
||||||
on_pressed = function() internal.open_url(item.api) end,
|
|
||||||
enabled = item.api ~= nil
|
|
||||||
}))
|
|
||||||
end
|
|
||||||
|
|
||||||
if item.example_url ~= nil then
|
|
||||||
table.insert(button_children, editor.ui.button({
|
|
||||||
text = "Example",
|
|
||||||
on_pressed = function() internal.open_url(item.example_url) end,
|
|
||||||
enabled = item.example_url ~= nil
|
|
||||||
}))
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Add spacer to push Author button to the right
|
|
||||||
table.insert(button_children, editor.ui.horizontal({ grow = true }))
|
|
||||||
|
|
||||||
if item.author_url ~= nil then
|
|
||||||
table.insert(button_children, editor.ui.label({
|
|
||||||
text = "Author",
|
|
||||||
color = editor.ui.COLOR.HINT
|
|
||||||
}))
|
|
||||||
table.insert(button_children, editor.ui.button({
|
|
||||||
text = item.author or "Author",
|
|
||||||
on_pressed = function() internal.open_url(item.author_url) end,
|
|
||||||
enabled = item.author_url ~= nil
|
|
||||||
}))
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Add button row to widget details
|
|
||||||
table.insert(widget_details_children, editor.ui.horizontal({
|
|
||||||
spacing = editor.ui.SPACING.SMALL,
|
|
||||||
children = button_children
|
|
||||||
}))
|
|
||||||
|
|
||||||
return editor.ui.horizontal({
|
|
||||||
spacing = editor.ui.SPACING.NONE,
|
|
||||||
padding = editor.ui.PADDING.SMALL,
|
|
||||||
children = {
|
|
||||||
-- Widget icon placeholder
|
|
||||||
editor.ui.label({
|
|
||||||
text = "•••",
|
|
||||||
color = editor.ui.COLOR.HINT
|
|
||||||
}),
|
|
||||||
|
|
||||||
-- Widget details
|
|
||||||
editor.ui.vertical({
|
|
||||||
spacing = editor.ui.SPACING.SMALL,
|
|
||||||
grow = true,
|
|
||||||
children = widget_details_children
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
---Create a scrollable list of widget items
|
|
||||||
---@param items table - List of widget items to display
|
|
||||||
---@param on_install function - Callback for install button
|
|
||||||
---@return userdata - UI component
|
|
||||||
function M.create_widget_list(items, on_install)
|
|
||||||
local widget_items = {}
|
|
||||||
local install_folder = editor.prefs.get("druid.asset_install_folder") or installer.get_install_folder()
|
|
||||||
|
|
||||||
for _, item in ipairs(items) do
|
|
||||||
local is_installed = installer.is_widget_installed(item, install_folder)
|
|
||||||
table.insert(widget_items, M.create_widget_item(item, is_installed,
|
|
||||||
function() on_install(item) end
|
|
||||||
))
|
|
||||||
end
|
|
||||||
|
|
||||||
return editor.ui.scroll({
|
|
||||||
content = editor.ui.vertical({
|
|
||||||
children = widget_items
|
|
||||||
})
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
return M
|
|
||||||
@@ -3,6 +3,14 @@ local create_druid_widget = require("druid.editor_scripts.create_druid_widget")
|
|||||||
local create_druid_gui_script = require("druid.editor_scripts.create_druid_gui_script")
|
local create_druid_gui_script = require("druid.editor_scripts.create_druid_gui_script")
|
||||||
local druid_settings = require("druid.editor_scripts.druid_settings")
|
local druid_settings = require("druid.editor_scripts.druid_settings")
|
||||||
local asset_store = require("druid.editor_scripts.core.asset_store")
|
local asset_store = require("druid.editor_scripts.core.asset_store")
|
||||||
|
-- Reuse tip: copy the snippet below into another editor script to open a custom store.
|
||||||
|
-- asset_store.open({
|
||||||
|
-- store_url = "https://example.com/store.json",
|
||||||
|
-- info_url = "https://example.com/docs",
|
||||||
|
-- title = "My Library Store",
|
||||||
|
-- install_prefs_key = "my_lib.asset_install_folder",
|
||||||
|
-- default_install_folder = "/my_widgets",
|
||||||
|
-- })
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
@@ -75,7 +83,13 @@ function M.get_commands()
|
|||||||
label = "[Druid] Asset Store",
|
label = "[Druid] Asset Store",
|
||||||
locations = { "Edit" },
|
locations = { "Edit" },
|
||||||
run = function()
|
run = function()
|
||||||
return asset_store.open_asset_store("https://insality.github.io/core/druid_widget_store.json")
|
return asset_store.open({
|
||||||
|
store_url = "https://insality.github.io/core/druid_widget_store.json",
|
||||||
|
info_url = "https://github.com/Insality/core/blob/main/druid_widget_store.md",
|
||||||
|
title = "Druid Asset Store",
|
||||||
|
install_prefs_key = "druid.asset_install_folder",
|
||||||
|
default_install_folder = DEFAULT_ASSET_INSTALL_FOLDER,
|
||||||
|
})
|
||||||
end
|
end
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user