mirror of
https://github.com/Insality/druid
synced 2025-11-26 19:00:52 +01:00
Asset store WIP
This commit is contained in:
239
druid/editor_scripts/core/asset_store.lua
Normal file
239
druid/editor_scripts/core/asset_store.lua
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
--- Main asset store module for Druid widgets
|
||||||
|
--- Handles fetching widget data, displaying the store interface, and managing installations
|
||||||
|
|
||||||
|
local installer = require("druid.editor_scripts.core.installer")
|
||||||
|
local ui_components = require("druid.editor_scripts.core.ui_components")
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local STORE_URL = "https://insality.github.io/core/druid_widget_store.json"
|
||||||
|
|
||||||
|
|
||||||
|
---Fetch widget data from the remote store
|
||||||
|
---@return table|nil, string|nil - Store data or nil, error message or nil
|
||||||
|
local function fetch_store_data()
|
||||||
|
print("Fetching widget data from:", STORE_URL)
|
||||||
|
|
||||||
|
local response = http.request(STORE_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
|
||||||
|
|
||||||
|
print("Successfully fetched", #response.body.items, "widgets")
|
||||||
|
return response.body, nil
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
---Filter items based on author and tag filters
|
||||||
|
---@param items table - List of widget items
|
||||||
|
---@param author_filter string - Author filter value
|
||||||
|
---@param tag_filter string - Tag filter value
|
||||||
|
---@return table - Filtered list of items
|
||||||
|
local function filter_items(items, author_filter, tag_filter)
|
||||||
|
local filtered = {}
|
||||||
|
|
||||||
|
for _, item in ipairs(items) do
|
||||||
|
local author_match = author_filter == "All Authors" or item.author == author_filter
|
||||||
|
local tag_match = tag_filter == "All Categories" or (item.tags and table.concat(item.tags, ","):find(tag_filter))
|
||||||
|
|
||||||
|
if author_match and tag_match then
|
||||||
|
table.insert(filtered, item)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return filtered
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
---Handle widget installation
|
||||||
|
---@param item table - Widget item to install
|
||||||
|
---@param install_folder string - Installation folder
|
||||||
|
---@param on_success function - Success callback
|
||||||
|
---@param on_error function - Error callback
|
||||||
|
local function handle_install(item, install_folder, on_success, on_error)
|
||||||
|
print("Installing widget:", item.id)
|
||||||
|
|
||||||
|
local success, message = installer.install_widget(item, install_folder)
|
||||||
|
|
||||||
|
if success then
|
||||||
|
print("Installation successful:", message)
|
||||||
|
on_success(message)
|
||||||
|
else
|
||||||
|
print("Installation failed:", message)
|
||||||
|
on_error(message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
---Handle opening API documentation
|
||||||
|
---@param item table - Widget item
|
||||||
|
local function handle_open_api(item)
|
||||||
|
if item.api then
|
||||||
|
print("Opening API documentation:", item.api)
|
||||||
|
editor.browse(item.api)
|
||||||
|
else
|
||||||
|
print("No API documentation available for:", item.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
---Show installation status dialog
|
||||||
|
---@param success boolean - Whether installation was successful
|
||||||
|
---@param message string - Status message
|
||||||
|
local function show_install_status(success, message)
|
||||||
|
local dialog_component = editor.ui.component(function()
|
||||||
|
return editor.ui.dialog({
|
||||||
|
title = success and "Installation Successful" or "Installation Failed",
|
||||||
|
content = editor.ui.vertical({
|
||||||
|
spacing = editor.ui.SPACING.MEDIUM,
|
||||||
|
padding = editor.ui.PADDING.MEDIUM,
|
||||||
|
children = {
|
||||||
|
editor.ui.label({
|
||||||
|
text = message,
|
||||||
|
color = success and editor.ui.COLOR.TEXT or editor.ui.COLOR.ERROR,
|
||||||
|
alignment = editor.ui.ALIGNMENT.LEFT
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
buttons = {
|
||||||
|
editor.ui.dialog_button({
|
||||||
|
text = "OK",
|
||||||
|
default = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
|
||||||
|
editor.ui.show_dialog(dialog_component({}))
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
---Open the asset store dialog
|
||||||
|
function M.open_asset_store()
|
||||||
|
print("Opening Druid Asset Store")
|
||||||
|
|
||||||
|
-- Fetch data synchronously before creating the dialog
|
||||||
|
local store_data, fetch_error = fetch_store_data()
|
||||||
|
local initial_items = {}
|
||||||
|
local initial_loading = false
|
||||||
|
local initial_error = nil
|
||||||
|
|
||||||
|
if store_data then
|
||||||
|
initial_items = store_data.items
|
||||||
|
print("Successfully loaded", #initial_items, "widgets")
|
||||||
|
else
|
||||||
|
initial_error = fetch_error
|
||||||
|
print("Failed to load widgets:", fetch_error)
|
||||||
|
end
|
||||||
|
|
||||||
|
local dialog_component = editor.ui.component(function(props)
|
||||||
|
-- State management
|
||||||
|
local items, set_items = editor.ui.use_state(initial_items)
|
||||||
|
local loading, set_loading = editor.ui.use_state(initial_loading)
|
||||||
|
local error_message, set_error_message = editor.ui.use_state(initial_error)
|
||||||
|
local install_folder, set_install_folder = editor.ui.use_state(editor.prefs.get("druid.asset_install_folder") or installer.get_default_install_folder())
|
||||||
|
local author_filter, set_author_filter = editor.ui.use_state("All Authors")
|
||||||
|
local tag_filter, set_tag_filter = editor.ui.use_state("All Categories")
|
||||||
|
local install_status, set_install_status = editor.ui.use_state("")
|
||||||
|
|
||||||
|
-- Filter items
|
||||||
|
local filtered_items = editor.ui.use_memo(filter_items, items, author_filter, tag_filter)
|
||||||
|
|
||||||
|
-- Installation status check function
|
||||||
|
local function is_widget_installed(item)
|
||||||
|
return installer.is_widget_installed(item, install_folder)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Installation handlers
|
||||||
|
local function on_install(item)
|
||||||
|
handle_install(item, install_folder,
|
||||||
|
function(message)
|
||||||
|
set_install_status("Success: " .. message)
|
||||||
|
show_install_status(true, message)
|
||||||
|
end,
|
||||||
|
function(message)
|
||||||
|
set_install_status("Error: " .. message)
|
||||||
|
show_install_status(false, message)
|
||||||
|
end
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function on_open_api(item)
|
||||||
|
handle_open_api(item)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Build UI content
|
||||||
|
local content_children = {}
|
||||||
|
|
||||||
|
-- Settings section
|
||||||
|
table.insert(content_children, editor.ui.label({
|
||||||
|
text = "Installation Folder: " .. install_folder,
|
||||||
|
color = editor.ui.COLOR.TEXT
|
||||||
|
}))
|
||||||
|
|
||||||
|
-- Filter section (only show if we have items)
|
||||||
|
if #items > 0 then
|
||||||
|
table.insert(content_children, editor.ui.label({
|
||||||
|
text = "Filters: Author: " .. author_filter .. ", Category: " .. tag_filter,
|
||||||
|
color = editor.ui.COLOR.TEXT
|
||||||
|
}))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Main content area
|
||||||
|
if loading then
|
||||||
|
table.insert(content_children, ui_components.create_loading_indicator("Loading widget store..."))
|
||||||
|
elseif error_message then
|
||||||
|
table.insert(content_children, ui_components.create_error_message(error_message))
|
||||||
|
elseif #filtered_items == 0 then
|
||||||
|
table.insert(content_children, editor.ui.label({
|
||||||
|
text = "No widgets found matching the current filters.",
|
||||||
|
color = editor.ui.COLOR.HINT,
|
||||||
|
alignment = editor.ui.ALIGNMENT.CENTER
|
||||||
|
}))
|
||||||
|
else
|
||||||
|
table.insert(content_children, ui_components.create_widget_list(
|
||||||
|
filtered_items, is_widget_installed, on_install, on_open_api
|
||||||
|
))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Install status message
|
||||||
|
if install_status ~= "" then
|
||||||
|
table.insert(content_children, editor.ui.label({
|
||||||
|
text = install_status,
|
||||||
|
color = install_status:find("Success") and editor.ui.COLOR.TEXT or editor.ui.COLOR.ERROR,
|
||||||
|
alignment = editor.ui.ALIGNMENT.CENTER
|
||||||
|
}))
|
||||||
|
end
|
||||||
|
|
||||||
|
return editor.ui.dialog({
|
||||||
|
title = "Druid Asset Store",
|
||||||
|
content = editor.ui.vertical({
|
||||||
|
spacing = editor.ui.SPACING.MEDIUM,
|
||||||
|
padding = editor.ui.PADDING.MEDIUM,
|
||||||
|
children = content_children
|
||||||
|
}),
|
||||||
|
buttons = {
|
||||||
|
editor.ui.dialog_button({
|
||||||
|
text = "Close",
|
||||||
|
cancel = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
|
||||||
|
local result = editor.ui.show_dialog(dialog_component({}))
|
||||||
|
|
||||||
|
-- Save the install folder preference (this will be handled by the state management in the dialog)
|
||||||
|
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
return M
|
||||||
110
druid/editor_scripts/core/installer.lua
Normal file
110
druid/editor_scripts/core/installer.lua
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
--- Module for handling widget installation from zip files
|
||||||
|
--- Downloads zip files and extracts them to the specified folder
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local DEFAULT_INSTALL_FOLDER = "/widget"
|
||||||
|
|
||||||
|
|
||||||
|
---Download a file from URL
|
||||||
|
---@param url string - The URL to download from
|
||||||
|
---@return string|nil, string|nil - Downloaded content or nil, error message or nil
|
||||||
|
local function download_file(url)
|
||||||
|
print("Downloading from:", url)
|
||||||
|
|
||||||
|
-- Try different approaches for downloading binary data
|
||||||
|
local success, response = pcall(function()
|
||||||
|
-- First try without specifying 'as' parameter
|
||||||
|
return http.request(url)
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- If that fails, try with 'as = "string"'
|
||||||
|
if not success or not response or not response.body then
|
||||||
|
print("First attempt failed, trying with as='string'")
|
||||||
|
success, response = pcall(function()
|
||||||
|
return http.request(url, {
|
||||||
|
as = "string"
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
if not success then
|
||||||
|
print("HTTP request failed:", response)
|
||||||
|
return nil, "HTTP request failed: " .. tostring(response)
|
||||||
|
end
|
||||||
|
|
||||||
|
if not response then
|
||||||
|
print("No response received")
|
||||||
|
return nil, "No response received from server"
|
||||||
|
end
|
||||||
|
|
||||||
|
print("Response status:", response.status)
|
||||||
|
print("Response body type:", type(response.body))
|
||||||
|
print("Response body length:", response.body and #response.body or "nil")
|
||||||
|
if response.headers then
|
||||||
|
print("Response headers:", response.headers["content-type"] or "unknown")
|
||||||
|
print("Content length header:", response.headers["content-length"] or "unknown")
|
||||||
|
end
|
||||||
|
|
||||||
|
if response.status ~= 200 then
|
||||||
|
return nil, "Failed to download file. HTTP status: " .. tostring(response.status)
|
||||||
|
end
|
||||||
|
|
||||||
|
if not response.body then
|
||||||
|
return nil, "No content received from server"
|
||||||
|
end
|
||||||
|
|
||||||
|
print("Downloaded", #response.body, "bytes")
|
||||||
|
return response.body, nil
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
---Install a widget from a zip URL
|
||||||
|
---@param item table - Widget item data containing zip_url and id
|
||||||
|
---@param install_folder string - Target folder to install to
|
||||||
|
---@return boolean, string - Success status and message
|
||||||
|
function M.install_widget(item, install_folder)
|
||||||
|
if not item.zip_url or not item.id then
|
||||||
|
return false, "Invalid widget data: missing zip_url or id"
|
||||||
|
end
|
||||||
|
|
||||||
|
print("Installing widget:", item.id)
|
||||||
|
print("Download URL:", item.zip_url)
|
||||||
|
print("Target folder:", install_folder)
|
||||||
|
|
||||||
|
-- Download the zip file
|
||||||
|
local zip_data, download_error = download_file(item.zip_url)
|
||||||
|
if not zip_data then
|
||||||
|
return false, "Failed to download widget: " .. download_error
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Create a simple success message for now
|
||||||
|
local success = true
|
||||||
|
local message = "Widget '" .. item.id .. "' downloaded successfully!"
|
||||||
|
message = message .. "\nDownload URL: " .. item.zip_url
|
||||||
|
message = message .. "\nSize: " .. tostring(#zip_data) .. " bytes"
|
||||||
|
message = message .. "\nTarget folder: " .. install_folder
|
||||||
|
|
||||||
|
print("Successfully downloaded widget:", item.id)
|
||||||
|
return success, message
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
---Check if a widget is already installed
|
||||||
|
---@param item table - Widget item data containing id
|
||||||
|
---@param install_folder string - Install folder to check in
|
||||||
|
---@return boolean - True if widget is already installed
|
||||||
|
function M.is_widget_installed(item, install_folder)
|
||||||
|
-- For now, assume widgets are not installed to avoid path issues
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
---Get default installation folder
|
||||||
|
---@return string - Default installation folder path
|
||||||
|
function M.get_default_install_folder()
|
||||||
|
return DEFAULT_INSTALL_FOLDER
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
return M
|
||||||
316
druid/editor_scripts/core/ui_components.lua
Normal file
316
druid/editor_scripts/core/ui_components.lua
Normal file
@@ -0,0 +1,316 @@
|
|||||||
|
--- Module for reusable UI components in the asset store
|
||||||
|
--- Contains component builders for filters, widget items, and lists
|
||||||
|
|
||||||
|
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
|
||||||
|
---@param on_open_api function - Callback for API docs button
|
||||||
|
---@return userdata - UI component
|
||||||
|
function M.create_widget_item(item, is_installed, on_install, on_open_api)
|
||||||
|
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 on: " .. table.concat(item.depends, ", ")
|
||||||
|
end
|
||||||
|
|
||||||
|
return editor.ui.horizontal({
|
||||||
|
spacing = editor.ui.SPACING.MEDIUM,
|
||||||
|
padding = editor.ui.PADDING.MEDIUM,
|
||||||
|
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 = {
|
||||||
|
-- Title and author
|
||||||
|
editor.ui.horizontal({
|
||||||
|
spacing = editor.ui.SPACING.SMALL,
|
||||||
|
children = {
|
||||||
|
editor.ui.label({
|
||||||
|
text = item.title or item.id,
|
||||||
|
color = editor.ui.COLOR.TEXT
|
||||||
|
}),
|
||||||
|
editor.ui.label({
|
||||||
|
text = "by " .. (item.author or "Unknown"),
|
||||||
|
color = editor.ui.COLOR.HINT
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
-- Version and size
|
||||||
|
editor.ui.label({
|
||||||
|
text = version_text .. " • " .. size_text,
|
||||||
|
color = editor.ui.COLOR.HINT
|
||||||
|
}),
|
||||||
|
|
||||||
|
-- Description
|
||||||
|
editor.ui.label({
|
||||||
|
text = item.description or "No description available",
|
||||||
|
color = editor.ui.COLOR.TEXT
|
||||||
|
}),
|
||||||
|
|
||||||
|
-- Tags
|
||||||
|
tags_text ~= "" and editor.ui.label({
|
||||||
|
text = tags_text,
|
||||||
|
color = editor.ui.COLOR.HINT
|
||||||
|
}) or nil,
|
||||||
|
|
||||||
|
-- Dependencies
|
||||||
|
deps_text ~= "" and editor.ui.label({
|
||||||
|
text = deps_text,
|
||||||
|
color = editor.ui.COLOR.WARNING
|
||||||
|
}) or nil,
|
||||||
|
|
||||||
|
-- Installation status
|
||||||
|
is_installed and editor.ui.label({
|
||||||
|
text = "✓ Already installed",
|
||||||
|
color = editor.ui.COLOR.HINT
|
||||||
|
}) or nil
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
-- Action buttons
|
||||||
|
editor.ui.vertical({
|
||||||
|
spacing = editor.ui.SPACING.SMALL,
|
||||||
|
children = {
|
||||||
|
editor.ui.button({
|
||||||
|
text = is_installed and "Reinstall" or "Install",
|
||||||
|
on_pressed = on_install,
|
||||||
|
enabled = true
|
||||||
|
}),
|
||||||
|
editor.ui.button({
|
||||||
|
text = "API Docs",
|
||||||
|
on_pressed = on_open_api,
|
||||||
|
enabled = item.api ~= nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
---Create a scrollable list of widget items
|
||||||
|
---@param items table - List of widget items to display
|
||||||
|
---@param is_installed_func function - Function to check if widget is installed
|
||||||
|
---@param on_install function - Callback for install button
|
||||||
|
---@param on_open_api function - Callback for API docs button
|
||||||
|
---@return userdata - UI component
|
||||||
|
function M.create_widget_list(items, is_installed_func, on_install, on_open_api)
|
||||||
|
local widget_items = {}
|
||||||
|
|
||||||
|
for _, item in ipairs(items) do
|
||||||
|
local is_installed = is_installed_func and is_installed_func(item) or false
|
||||||
|
|
||||||
|
table.insert(widget_items, M.create_widget_item(item, is_installed,
|
||||||
|
function() on_install(item) end,
|
||||||
|
function() on_open_api(item) end
|
||||||
|
))
|
||||||
|
|
||||||
|
-- Add separator between items (except for the last one)
|
||||||
|
if _ < #items then
|
||||||
|
table.insert(widget_items, editor.ui.label({
|
||||||
|
text = "---",
|
||||||
|
color = editor.ui.COLOR.HINT
|
||||||
|
}))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return editor.ui.vertical({
|
||||||
|
spacing = editor.ui.SPACING.SMALL,
|
||||||
|
children = widget_items
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
---Create a loading indicator
|
||||||
|
---@param message string - Loading message
|
||||||
|
---@return userdata - UI component
|
||||||
|
function M.create_loading_indicator(message)
|
||||||
|
return editor.ui.vertical({
|
||||||
|
spacing = editor.ui.SPACING.MEDIUM,
|
||||||
|
alignment = editor.ui.ALIGNMENT.CENTER,
|
||||||
|
children = {
|
||||||
|
editor.ui.label({
|
||||||
|
text = message or "Loading...",
|
||||||
|
color = editor.ui.COLOR.TEXT,
|
||||||
|
alignment = editor.ui.ALIGNMENT.CENTER
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
---Create an error message display
|
||||||
|
---@param message string - Error message
|
||||||
|
---@return userdata - UI component
|
||||||
|
function M.create_error_message(message)
|
||||||
|
return editor.ui.vertical({
|
||||||
|
spacing = editor.ui.SPACING.MEDIUM,
|
||||||
|
alignment = editor.ui.ALIGNMENT.CENTER,
|
||||||
|
children = {
|
||||||
|
editor.ui.label({
|
||||||
|
text = "Error: " .. message,
|
||||||
|
color = editor.ui.COLOR.ERROR,
|
||||||
|
alignment = editor.ui.ALIGNMENT.CENTER
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
return M
|
||||||
@@ -2,6 +2,7 @@ local assign_layers = require("druid.editor_scripts.assign_layers")
|
|||||||
local create_druid_widget = require("druid.editor_scripts.create_druid_widget")
|
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 M = {}
|
local M = {}
|
||||||
|
|
||||||
@@ -18,6 +19,10 @@ function M.get_prefs_schema()
|
|||||||
["druid.gui_script_template_path"] = editor.prefs.schema.string({
|
["druid.gui_script_template_path"] = editor.prefs.schema.string({
|
||||||
default = DEFAULT_GUI_SCRIPT_TEMPLATE_PATH,
|
default = DEFAULT_GUI_SCRIPT_TEMPLATE_PATH,
|
||||||
scope = editor.prefs.SCOPE.PROJECT
|
scope = editor.prefs.SCOPE.PROJECT
|
||||||
|
}),
|
||||||
|
["druid.asset_install_folder"] = editor.prefs.schema.string({
|
||||||
|
default = "/widget",
|
||||||
|
scope = editor.prefs.SCOPE.PROJECT
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
@@ -65,6 +70,14 @@ function M.get_commands()
|
|||||||
end
|
end
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
label = "[Druid] Asset Store",
|
||||||
|
locations = { "Edit" },
|
||||||
|
run = function()
|
||||||
|
return asset_store.open_asset_store()
|
||||||
|
end
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
label = "[Druid] Settings",
|
label = "[Druid] Settings",
|
||||||
locations = { "Edit" },
|
locations = { "Edit" },
|
||||||
|
|||||||
Reference in New Issue
Block a user