This commit is contained in:
rubenwardy 2024-05-16 15:43:48 -07:00 committed by GitHub
commit 87bea0413a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 647 additions and 170 deletions

View File

@ -57,12 +57,10 @@ srifqi:
textures/base/pack/minimap_btn.png textures/base/pack/minimap_btn.png
Zughy: Zughy:
textures/base/pack/cdb_add.png
textures/base/pack/cdb_downloading.png textures/base/pack/cdb_downloading.png
textures/base/pack/cdb_queued.png textures/base/pack/cdb_queued.png
textures/base/pack/cdb_update.png textures/base/pack/cdb_update.png
textures/base/pack/cdb_update_cropped.png textures/base/pack/cdb_update_cropped.png
textures/base/pack/cdb_viewonline.png
textures/base/pack/settings_btn.png textures/base/pack/settings_btn.png
textures/base/pack/settings_info.png textures/base/pack/settings_info.png
textures/base/pack/settings_reset.png textures/base/pack/settings_reset.png
@ -79,7 +77,6 @@ kilbith:
textures/base/pack/progress_bar_bg.png textures/base/pack/progress_bar_bg.png
SmallJoker: SmallJoker:
textures/base/pack/cdb_clear.png
textures/base/pack/server_favorite_delete.png (based on server_favorite.png) textures/base/pack/server_favorite_delete.png (based on server_favorite.png)
DS: DS:

View File

@ -262,6 +262,16 @@ function core.formspec_escape(text)
end end
local hypertext_escapes = {
["\\"] = "\\\\",
["<"] = "\\<",
[">"] = "\\>",
}
function core.hypertext_escape(text)
return text and text:gsub("[\\<>]", hypertext_escapes)
end
function core.wrap_text(text, max_length, as_table) function core.wrap_text(text, max_length, as_table)
local result = {} local result = {}
local line = {} local line = {}

View File

@ -181,6 +181,22 @@ function contentdb.get_package_by_id(id)
end end
function contentdb.get_package_by_info(author, name)
local id = author:lower() .. "/" .. name
local package = contentdb.package_by_id[id]
if package then
return package
end
local name_len = #name
if name_len > 5 and name:sub(name_len - 4) == "_game" then
id = author:lower() .. "/" .. name:sub(1, name_len - 5)
return contentdb.package_by_id[id]
end
return nil
end
local function get_raw_dependencies(package) local function get_raw_dependencies(package)
if package.type ~= "mod" then if package.type ~= "mod" then
return {} return {}
@ -376,8 +392,8 @@ local function fetch_pkgs(params)
local aliases = {} local aliases = {}
for _, package in pairs(packages) do for _, package in pairs(packages) do
local name_len = #package.name
-- This must match what contentdb.update_paths() does! -- This must match what contentdb.update_paths() does!
local name_len = #package.name
package.id = package.author:lower() .. "/" package.id = package.author:lower() .. "/"
if package.type == "game" and name_len > 5 and package.name:sub(name_len - 4) == "_game" then if package.type == "game" and name_len > 5 and package.name:sub(name_len - 4) == "_game" then
package.id = package.id .. package.name:sub(1, name_len - 5) package.id = package.id .. package.name:sub(1, name_len - 5)
@ -542,3 +558,45 @@ function contentdb.filter_packages(query, by_type)
end end
end end
end end
function contentdb.get_full_package_info(package, callback)
assert(package)
local function fetch(params)
local version = core.get_version()
local base_url = core.settings:get("contentdb_url")
local languages
local current_language = core.get_language()
if current_language ~= "" then
languages = { current_language, "en;q=0.8" }
else
languages = { "en" }
end
local url = base_url ..
"/api/packages/" .. params.package.url_part .. "/for-client/?" ..
"protocol_version=" .. core.get_max_supp_proto() ..
"&engine_version=" .. core.urlencode(version.string) ..
"&formspec_version=" .. core.urlencode(7) ..
"&include_images=false"
local http = core.get_http_api()
local response = http.fetch_sync({
url = url,
extra_headers = {
"Accept-Language: " .. table.concat(languages, ", ")
},
})
if not response.succeeded then
return nil
end
return core.parse_json(response.data)
end
if not core.handle_async(fetch, { package = package }, callback) then
core.log("error", "ERROR: async event failed")
callback(nil)
end
end

View File

@ -23,30 +23,29 @@ if not core.get_http_api then
return return
end end
local color_backdrop = "#000c"
local color_text_white = "#fff"
local color_text_blue = "#22e0f6"
local color_text_green = mt_color_green
-- Filter -- Filter
local search_string = "" local search_string = ""
local cur_page = 1 local cur_page = 1
local num_per_page = 5 local filter_type
local filter_type = 1
local filter_types_titles = {
fgettext("All packages"),
fgettext("Games"),
fgettext("Mods"),
fgettext("Texture packs"),
}
-- Automatic package installation -- Automatic package installation
local auto_install_spec = nil local auto_install_spec = nil
local filter_types_type = {
nil, local filter_type_names = {
"game", { "type_all", nil },
"mod", { "type_game", "game" },
"txp", { "type_mod", "mod" },
{ "type_txp", "txp" },
} }
local function install_or_update_package(this, package) function install_or_update_package(parent, package)
local install_parent local install_parent
if package.type == "mod" then if package.type == "mod" then
install_parent = core.get_modpath() install_parent = core.get_modpath()
@ -66,14 +65,14 @@ local function install_or_update_package(this, package)
local has_hard_deps = contentdb.has_hard_deps(package) local has_hard_deps = contentdb.has_hard_deps(package)
if has_hard_deps then if has_hard_deps then
local dlg = create_install_dialog(package) local dlg = create_install_dialog(package)
dlg:set_parent(this) dlg:set_parent(parent)
this:hide() parent:hide()
dlg:show() dlg:show()
elseif has_hard_deps == nil then elseif has_hard_deps == nil then
local dlg = messagebox("error_checking_deps", local dlg = messagebox("error_checking_deps",
fgettext("Error getting dependencies for package")) fgettext("Error getting dependencies for package"))
dlg:set_parent(this) dlg:set_parent(parent)
this:hide() parent:hide()
dlg:show() dlg:show()
else else
contentdb.queue_download(package, package.path and contentdb.REASON_UPDATE or contentdb.REASON_NEW) contentdb.queue_download(package, package.path and contentdb.REASON_UPDATE or contentdb.REASON_NEW)
@ -83,13 +82,13 @@ local function install_or_update_package(this, package)
if package.type == "mod" and #pkgmgr.games == 0 then if package.type == "mod" and #pkgmgr.games == 0 then
local dlg = messagebox("install_game", local dlg = messagebox("install_game",
fgettext("You need to install a game before you can install a mod")) fgettext("You need to install a game before you can install a mod"))
dlg:set_parent(this) dlg:set_parent(parent)
this:hide() parent:hide()
dlg:show() dlg:show()
elseif not package.path and core.is_dir(install_parent .. DIR_DELIM .. package.name) then elseif not package.path and core.is_dir(install_parent .. DIR_DELIM .. package.name) then
local dlg = create_confirm_overwrite(package, on_confirm) local dlg = create_confirm_overwrite(package, on_confirm)
dlg:set_parent(this) dlg:set_parent(parent)
this:hide() parent:hide()
dlg:show() dlg:show()
else else
on_confirm() on_confirm()
@ -154,7 +153,7 @@ end
local function sort_and_filter_pkgs() local function sort_and_filter_pkgs()
contentdb.update_paths() contentdb.update_paths()
contentdb.sort_packages() contentdb.sort_packages()
contentdb.filter_packages(search_string, filter_types_type[filter_type]) contentdb.filter_packages(search_string, filter_type)
local auto_install_pkg = resolve_auto_install_spec() local auto_install_pkg = resolve_auto_install_spec()
if auto_install_pkg then if auto_install_pkg then
@ -185,72 +184,130 @@ local function load()
end end
local function get_info_formspec(text) local function get_info_formspec(size, safezone_left, text)
local H = 9.5
return table.concat({ return table.concat({
"formspec_version[6]", "formspec_version[6]",
"size[15.75,9.5]", "size[", size.x, ",", size.y, "]",
core.settings:get_bool("enable_touch") and "padding[0.01,0.01]" or "position[0.5,0.55]", "padding[-0.01,-0.01]",
"label[4,4.35;", text, "]", "label[", safezone_left + 3.625, ",4.35;", text, "]",
"container[0,", H - 0.8 - 0.375, "]", "container[0,", size.y - 0.8 - 0.375, "]",
"button[0.375,0;5,0.8;back;", fgettext("Back to Main Menu"), "]", "button[", safezone_left, ",0;4,0.8;back;", fgettext("Back"), "]",
"container_end[]", "container_end[]",
}) })
end end
-- Determines how to fit `num_per_page` into `size` space
local function fit_cells(num_per_page, size)
local cell_spacing = 0.25
local desired_size = 4.5
local row_cells = math.min(5, math.floor(size.x / desired_size))
local cell_w, cell_h
-- Fit cells into the available height
while true do
cell_w = (size.x - (row_cells-1)*cell_spacing) / row_cells
cell_h = cell_w * 2 / 3
local required_height = math.ceil(num_per_page / row_cells) * (cell_h + cell_spacing) - cell_spacing
-- Add 0.1 to be more lenient
if required_height <= size.y + 0.1 then
break
end
row_cells = row_cells + 1
end
return cell_spacing, row_cells, cell_w, cell_h
end
local function get_formspec(dlgdata) local function get_formspec(dlgdata)
local safezone_left = PLATFORM == "Android" and 1 or 0.375
local window = core.get_window_info()
local size = { x = window.max_formspec_size.x, y = window.max_formspec_size.y }
if contentdb.loading then if contentdb.loading then
return get_info_formspec(fgettext("Loading...")) return get_info_formspec(size, safezone_left, fgettext("Loading..."))
end end
if contentdb.load_error then if contentdb.load_error then
return get_info_formspec(fgettext("No packages could be retrieved")) return get_info_formspec(size, safezone_left, fgettext("No packages could be retrieved"))
end end
assert(contentdb.load_ok) assert(contentdb.load_ok)
contentdb.update_paths() contentdb.update_paths()
local num_per_page = dlgdata.num_per_page
dlgdata.pagemax = math.max(math.ceil(#contentdb.packages / num_per_page), 1) dlgdata.pagemax = math.max(math.ceil(#contentdb.packages / num_per_page), 1)
if cur_page > dlgdata.pagemax then if cur_page > dlgdata.pagemax then
cur_page = 1 cur_page = 1
end end
local W = 15.75 local W = size.x - safezone_left
local H = 9.5 local H = size.y
local category_x = 0
local number_category_buttons = 4
local max_button_w = (W - 0.375 - 0.25 - 7) / number_category_buttons
local category_button_w = math.min(max_button_w, 3)
local function make_category_button(name, label, selected)
category_x = category_x + 1
local color = selected and mt_color_green or ""
return ("style[%s;bgcolor=%s]button[%f,0;%f,0.8;%s;%s]"):format(name, color,
(category_x - 1) * category_button_w, category_button_w, name, label)
end
local selected_type = filter_type
local search_box_width = W - 0.375 - 0.25 - 2*0.8
- number_category_buttons * category_button_w
local formspec = { local formspec = {
"formspec_version[6]", "formspec_version[7]",
"size[15.75,9.5]", "size[", size.x, ",", size.y, "]",
core.settings:get_bool("enable_touch") and "padding[0.01,0.01]" or "position[0.5,0.55]", "padding[-0.01,-0.01]",
"style[status,downloading,queued;border=false]", "container[", safezone_left, ",0]",
"container[0.375,0.375]", -- Top-left: categories
"field[0,0;7.225,0.8;search_string;;", core.formspec_escape(search_string), "]", "container[0,0.375]",
make_category_button("type_all", fgettext("All"), selected_type == nil),
make_category_button("type_game", fgettext("Games"), selected_type == "game"),
make_category_button("type_mod", fgettext("Mods"), selected_type == "mod"),
make_category_button("type_txp", fgettext("Texture Packs"), selected_type == "txp"),
"container_end[]",
-- Top-right: Search
"container[", W - 0.375 - search_box_width - 0.8*2, ",0.375]",
"field[0,0;", search_box_width, ",0.8;search_string;;", core.formspec_escape(search_string), "]",
"field_enter_after_edit[search_string;true]", "field_enter_after_edit[search_string;true]",
"image_button[7.3,0;0.8,0.8;", core.formspec_escape(defaulttexturedir .. "search.png"), ";search;]", "image_button[", search_box_width, ",0;0.8,0.8;",
"image_button[8.125,0;0.8,0.8;", core.formspec_escape(defaulttexturedir .. "clear.png"), ";clear;]", core.formspec_escape(defaulttexturedir .. "search.png"), ";search;]",
"dropdown[9.175,0;2.7875,0.8;type;", table.concat(filter_types_titles, ","), ";", filter_type, "]", "image_button[", search_box_width + 0.8, ",0;0.8,0.8;",
core.formspec_escape(defaulttexturedir .. "clear.png"), ";clear;]",
"container_end[]", "container_end[]",
-- Page nav buttons -- Bottom strip start
"container[0,", H - 0.8 - 0.375, "]", "container[0,", H - 0.8 - 0.375, "]",
"button[0.375,0;5,0.8;back;", fgettext("Back to Main Menu"), "]", "button[0,0;4,0.8;back;", fgettext("Back to Main Menu"), "]",
"container[", W - 0.375 - 0.8*4 - 2, ",0]", -- Bottom-center: Page nav buttons
"image_button[0,0;0.8,0.8;", core.formspec_escape(defaulttexturedir), "start_icon.png;pstart;]", "container[", (W - 1*4 - 2) / 2, ",0]",
"image_button[0.8,0;0.8,0.8;", core.formspec_escape(defaulttexturedir), "prev_icon.png;pback;]", "image_button[0,0;1,0.8;", core.formspec_escape(defaulttexturedir), "start_icon.png;pstart;]",
"image_button[1,0;1,0.8;", core.formspec_escape(defaulttexturedir), "prev_icon.png;pback;]",
"style[pagenum;border=false]", "style[pagenum;border=false]",
"button[1.6,0;2,0.8;pagenum;", tonumber(cur_page), " / ", tonumber(dlgdata.pagemax), "]", "button[2,0;2,0.8;pagenum;", tonumber(cur_page), " / ", tonumber(dlgdata.pagemax), "]",
"image_button[3.6,0;0.8,0.8;", core.formspec_escape(defaulttexturedir), "next_icon.png;pnext;]", "image_button[4,0;1,0.8;", core.formspec_escape(defaulttexturedir), "next_icon.png;pnext;]",
"image_button[4.4,0;0.8,0.8;", core.formspec_escape(defaulttexturedir), "end_icon.png;pend;]", "image_button[5,0;1,0.8;", core.formspec_escape(defaulttexturedir), "end_icon.png;pend;]",
"container_end[]", "container_end[]", -- page nav end
"container_end[]", -- Bottom-right: updating
"container[", W - 0.375 - 3, ",0]",
"style[status,downloading,queued;border=false]",
} }
if contentdb.number_downloading > 0 then if contentdb.number_downloading > 0 then
formspec[#formspec + 1] = "button[12.5875,0.375;2.7875,0.8;downloading;" formspec[#formspec + 1] = "button[0,0;3,0.8;downloading;"
if #contentdb.download_queue > 0 then if #contentdb.download_queue > 0 then
formspec[#formspec + 1] = fgettext("$1 downloading,\n$2 queued", formspec[#formspec + 1] = fgettext("$1 downloading,\n$2 queued",
contentdb.number_downloading, #contentdb.download_queue) contentdb.number_downloading, #contentdb.download_queue)
@ -269,16 +326,19 @@ local function get_formspec(dlgdata)
end end
if num_avail_updates == 0 then if num_avail_updates == 0 then
formspec[#formspec + 1] = "button[12.5875,0.375;2.7875,0.8;status;" formspec[#formspec + 1] = "button[0,0;3,0.8;status;"
formspec[#formspec + 1] = fgettext("No updates") formspec[#formspec + 1] = fgettext("No updates")
formspec[#formspec + 1] = "]" formspec[#formspec + 1] = "]"
else else
formspec[#formspec + 1] = "button[12.5875,0.375;2.7875,0.8;update_all;" formspec[#formspec + 1] = "button[0,0;3,0.8;update_all;"
formspec[#formspec + 1] = fgettext("Update All [$1]", num_avail_updates) formspec[#formspec + 1] = fgettext("Update All [$1]", num_avail_updates)
formspec[#formspec + 1] = "]" formspec[#formspec + 1] = "]"
end end
end end
formspec[#formspec + 1] = "container_end[]" -- updating end
formspec[#formspec + 1] = "container_end[]" -- bottom strip end
if #contentdb.packages == 0 then if #contentdb.packages == 0 then
formspec[#formspec + 1] = "label[4,4.75;" formspec[#formspec + 1] = "label[4,4.75;"
formspec[#formspec + 1] = fgettext("No results") formspec[#formspec + 1] = fgettext("No results")
@ -290,81 +350,91 @@ local function get_formspec(dlgdata)
formspec[#formspec + 1] = "tooltip[downloading;" .. fgettext("Downloading...") .. tooltip_colors formspec[#formspec + 1] = "tooltip[downloading;" .. fgettext("Downloading...") .. tooltip_colors
formspec[#formspec + 1] = "tooltip[queued;" .. fgettext("Queued") .. tooltip_colors formspec[#formspec + 1] = "tooltip[queued;" .. fgettext("Queued") .. tooltip_colors
formspec[#formspec + 1] = "container[0,1.425]"
local cell_spacing, row_cells, cell_w, cell_h = fit_cells(num_per_page, {
x = W - 0.375,
y = H - 1.425 - 0.25 - 0.8 - 0.375
})
local start_idx = (cur_page - 1) * num_per_page + 1 local start_idx = (cur_page - 1) * num_per_page + 1
for i=start_idx, math.min(#contentdb.packages, start_idx+num_per_page-1) do for i=start_idx, math.min(#contentdb.packages, start_idx+num_per_page-1) do
local package = contentdb.packages[i] local package = contentdb.packages[i]
local container_y = (i - start_idx) * 1.375 + (2*0.375 + 0.8)
formspec[#formspec + 1] = "container[0.375,"
formspec[#formspec + 1] = container_y
formspec[#formspec + 1] = "]"
-- image local textcolor = color_text_white
formspec[#formspec + 1] = "image[0,0;1.5,1;" if package.path then
formspec[#formspec + 1] = core.formspec_escape(get_screenshot(package))
formspec[#formspec + 1] = "]"
-- title
formspec[#formspec + 1] = "label[1.875,0.1;"
formspec[#formspec + 1] = core.formspec_escape(
core.colorize(mt_color_green, package.title) ..
core.colorize("#BFBFBF", " by " .. package.author))
formspec[#formspec + 1] = "]"
-- buttons
local description_width = W - 2.625 - 2 * 0.7 - 2 * 0.15
local second_base = "image_button[-1.55,0;0.7,0.7;" .. core.formspec_escape(defaulttexturedir)
local third_base = "image_button[-2.4,0;0.7,0.7;" .. core.formspec_escape(defaulttexturedir)
formspec[#formspec + 1] = "container["
formspec[#formspec + 1] = W - 0.375*2
formspec[#formspec + 1] = ",0.1]"
if package.downloading then
formspec[#formspec + 1] = "animated_image[-1.7,-0.15;1,1;downloading;"
formspec[#formspec + 1] = core.formspec_escape(defaulttexturedir)
formspec[#formspec + 1] = "cdb_downloading.png;3;400;]"
elseif package.queued then
formspec[#formspec + 1] = second_base
formspec[#formspec + 1] = "cdb_queued.png;queued;]"
elseif not package.path then
local elem_name = "install_" .. i .. ";"
formspec[#formspec + 1] = "style[" .. elem_name .. "bgcolor=#71aa34]"
formspec[#formspec + 1] = second_base .. "cdb_add.png;" .. elem_name .. "]"
formspec[#formspec + 1] = "tooltip[" .. elem_name .. fgettext("Install") .. tooltip_colors
else
if package.installed_release < package.release then if package.installed_release < package.release then
-- The install_ action also handles updating textcolor = color_text_blue
local elem_name = "install_" .. i .. ";" else
formspec[#formspec + 1] = "style[" .. elem_name .. "bgcolor=#28ccdf]" textcolor = color_text_green
formspec[#formspec + 1] = third_base .. "cdb_update.png;" .. elem_name .. "]"
formspec[#formspec + 1] = "tooltip[" .. elem_name .. fgettext("Update") .. tooltip_colors
description_width = description_width - 0.7 - 0.15
end end
end
local elem_name = "uninstall_" .. i .. ";" table.insert_all(formspec, {
formspec[#formspec + 1] = "style[" .. elem_name .. "bgcolor=#a93b3b]" "container[",
formspec[#formspec + 1] = second_base .. "cdb_clear.png;" .. elem_name .. "]" (cell_w + cell_spacing) * ((i - start_idx) % row_cells),
formspec[#formspec + 1] = "tooltip[" .. elem_name .. fgettext("Uninstall") .. tooltip_colors ",",
(cell_h + cell_spacing) * math.floor((i - start_idx) / row_cells),
"]",
-- image,
"image_button[0,0;", cell_w, ",", cell_h, ";",
core.formspec_escape(get_screenshot(package, package.thumbnail, 2)),
";view_", i, ";;;false]",
--"style[title_", i, ";border=false]",
-- The 0.01 here fixes a single line of image pixels appearing below the box
"box[0,", cell_h - 0.5 + 0.01, ";", cell_w, ",0.5;", color_backdrop, "]",
"style_type[button;font_size=*1.1;border=false]",
"button[0.25,", cell_h - 0.5, ";", cell_w - 0.5, ",0.5;title_", i ,";",
core.formspec_escape(core.colorize(textcolor, package.title)), "]",
"style_type[button;font_size=;border=;textcolor=]",
})
if package.featured then
table.insert_all(formspec, {
"tooltip[0,0;0.8,0.8;", fgettext("Featured"), "]",
"image[0.2,0.2;0.4,0.4;", defaulttexturedir, "server_favorite.png]",
})
end end
local web_elem_name = "view_" .. i .. ";" table.insert_all(formspec, {
formspec[#formspec + 1] = "image_button[-0.7,0;0.7,0.7;" .. "container[", cell_w - 0.5,",", cell_h - 0.5, "]",
core.formspec_escape(defaulttexturedir) .. "cdb_viewonline.png;" .. web_elem_name .. "]" })
formspec[#formspec + 1] = "tooltip[" .. web_elem_name ..
fgettext("View more information in a web browser") .. tooltip_colors
formspec[#formspec + 1] = "container_end[]"
-- description if package.downloading then
formspec[#formspec + 1] = "textarea[1.855,0.3;" table.insert_all(formspec, {
formspec[#formspec + 1] = tostring(description_width) "animated_image[0,0;0.5,0.5;downloading;", defaulttexturedir, "cdb_downloading.png;3;400;;]",
formspec[#formspec + 1] = ",0.8;;;" })
formspec[#formspec + 1] = core.formspec_escape(package.short_description) elseif package.queued then
formspec[#formspec + 1] = "]" table.insert_all(formspec, {
"image[0,0;0.5,0.5;", defaulttexturedir, "cdb_queued.png]",
})
elseif package.path then
if package.installed_release < package.release then
table.insert_all(formspec, {
"image[0,0;0.5,0.5;", defaulttexturedir, "cdb_update.png]",
})
else
table.insert_all(formspec, {
"image[0.1,0.1;0.3,0.3;", defaulttexturedir, "checkbox_64.png]",
})
end
end
formspec[#formspec + 1] = "container_end[]" local tooltip = package.short_description
table.insert_all(formspec, {
"container_end[]",
"tooltip[0,0;", cell_w, ",", cell_h, ";", core.formspec_escape(tooltip), "]",
"container_end[]",
})
end end
formspec[#formspec + 1] = "container_end[]"
formspec[#formspec + 1] = "container_end[]"
return table.concat(formspec) return table.concat(formspec)
end end
@ -373,14 +443,14 @@ local function handle_submit(this, fields)
if fields.search or fields.key_enter_field == "search_string" then if fields.search or fields.key_enter_field == "search_string" then
search_string = fields.search_string:trim() search_string = fields.search_string:trim()
cur_page = 1 cur_page = 1
contentdb.filter_packages(search_string, filter_types_type[filter_type]) contentdb.filter_packages(search_string, filter_type)
return true return true
end end
if fields.clear then if fields.clear then
search_string = "" search_string = ""
cur_page = 1 cur_page = 1
contentdb.filter_packages("", filter_types_type[filter_type]) contentdb.filter_packages("", filter_type)
return true return true
end end
@ -416,12 +486,11 @@ local function handle_submit(this, fields)
return true return true
end end
if fields.type then for _, pair in ipairs(filter_type_names) do
local new_type = table.indexof(filter_types_titles, fields.type) if fields[pair[1]] then
if new_type ~= filter_type then filter_type = pair[2]
filter_type = new_type
cur_page = 1 cur_page = 1
contentdb.filter_packages(search_string, filter_types_type[filter_type]) contentdb.filter_packages(search_string, filter_type)
return true return true
end end
end end
@ -437,32 +506,20 @@ local function handle_submit(this, fields)
return true return true
end end
local num_per_page = this.data.num_per_page
local start_idx = (cur_page - 1) * num_per_page + 1 local start_idx = (cur_page - 1) * num_per_page + 1
assert(start_idx ~= nil) assert(start_idx ~= nil)
for i=start_idx, math.min(#contentdb.packages, start_idx+num_per_page-1) do for i=start_idx, math.min(#contentdb.packages, start_idx+num_per_page-1) do
local package = contentdb.packages[i] local package = contentdb.packages[i]
assert(package) assert(package)
if fields["install_" .. i] then if fields["view_" .. i] or fields["title_" .. i] or fields["author_" .. i] then
install_or_update_package(this, package) local dlg = create_package_dialog(package)
return true
end
if fields["uninstall_" .. i] then
local dlg = create_delete_content_dlg(package)
dlg:set_parent(this) dlg:set_parent(this)
this:hide() this:hide()
dlg:show() dlg:show()
return true return true
end end
if fields["view_" .. i] then
local url = ("%s/packages/%s?protocol_version=%d"):format(
core.settings:get("contentdb_url"), package.url_part,
core.get_max_supp_proto())
core.open_url(url)
return true
end
end end
return false return false
@ -494,17 +551,7 @@ end
function create_contentdb_dlg(type, install_spec) function create_contentdb_dlg(type, install_spec)
search_string = "" search_string = ""
cur_page = 1 cur_page = 1
if type then filter_type = type
-- table.indexof does not work on tables that contain `nil`
for i, v in pairs(filter_types_type) do
if v == type then
filter_type = i
break
end
end
else
filter_type = 1
end
-- Keep the old auto_install_spec if the caller doesn't specify one. -- Keep the old auto_install_spec if the caller doesn't specify one.
if install_spec then if install_spec then
@ -513,8 +560,10 @@ function create_contentdb_dlg(type, install_spec)
load() load()
return dialog_create("contentdb", local dlg = dialog_create("contentdb",
get_formspec, get_formspec,
handle_submit, handle_submit,
handle_events) handle_events)
dlg.data.num_per_page = core.settings:get_bool("enable_touch") and 8 or 15
return dlg
end end

View File

@ -0,0 +1,316 @@
--Minetest
--Copyright (C) 2018-24 rubenwardy
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
local function get_info_formspec(size, text)
return table.concat({
"formspec_version[6]",
"size[", size.x, ",", size.y, "]",
"padding[-0.01,-0.01]",
"label[4,4.35;", text, "]",
"container[0,", size.y - 0.8 - 0.375, "]",
"button[0.375,0;2,0.8;back;", fgettext("Back"), "]",
"container_end[]",
})
end
local function get_formspec(data)
local window = core.get_window_info()
local size = { x = window.max_formspec_size.x, y = window.max_formspec_size.y }
if not data.info then
if not data.loading and not data.loading_error then
data.loading = true
contentdb.get_full_package_info(data.package, function(info)
data.loading = false
if info == nil then
data.loading_error = true
ui.update()
return
end
if info.forums then
info.forums = "https://forum.minetest.net/viewtopic.php?t=" .. info.forums
end
if data.package.name == info.name then
data.info = info
ui.update()
end
end)
end
if data.loading_error then
return get_info_formspec(size, fgettext("No packages could be retrieved"))
else
return get_info_formspec(size, fgettext("Loading..."))
end
else
-- Check installation status
contentdb.update_paths()
local info = data.info
local info_line =
fgettext("by $1 — $2 downloads — +$3 / $4 / -$5",
info.author, info.downloads,
info.reviews.positive, info.reviews.neutral, info.reviews.negative)
local bottom_buttons_y = size.y - 0.8 - 0.375
local formspec = {
"formspec_version[7]",
"size[", size.x, ",", size.y, "]",
"padding[-0.01,-0.01]",
"bgcolor[#0000]",
"box[0,0;", size.x, ",", size.y, ";#0000008C]",
"button[0.375,", bottom_buttons_y, ";2,0.8;back;", fgettext("Back"), "]",
"button[", size.x - 3.375, ",", bottom_buttons_y, ";3,0.8;open_contentdb;", fgettext("ContentDB page"), "]",
"style_type[label;font_size=+24;font=bold]",
"label[0.375,0.7;", core.formspec_escape(info.title), "]",
"style_type[label;font_size=;font=]",
"label[0.375,1.4;", core.formspec_escape(info_line), "]",
}
table.insert_all(formspec, {
"container[", size.x - 6.375, ",0.375]"
})
local left_button_rect = "0,0;2.875,1"
local right_button_rect = "3.125,0;2.875,1"
if data.package.downloading then
formspec[#formspec + 1] = "animated_image[5,0;1,1;downloading;"
formspec[#formspec + 1] = core.formspec_escape(defaulttexturedir)
formspec[#formspec + 1] = "cdb_downloading.png;3;400;]"
elseif data.package.queued then
formspec[#formspec + 1] = "style[queued;border=false]"
formspec[#formspec + 1] = "image_button[5,0;1,1;" .. core.formspec_escape(defaulttexturedir)
formspec[#formspec + 1] = "cdb_queued.png;queued;]"
elseif not data.package.path then
formspec[#formspec + 1] = "style[install;bgcolor=green]"
formspec[#formspec + 1] = "button["
formspec[#formspec + 1] = right_button_rect
formspec[#formspec + 1] =";install;"
formspec[#formspec + 1] = fgettext("Install [$1]", info.download_size)
formspec[#formspec + 1] = "]"
else
if data.package.installed_release < data.package.release then
-- The install_ action also handles updating
formspec[#formspec + 1] = "style[install;bgcolor=#28ccdf]"
formspec[#formspec + 1] = "button["
formspec[#formspec + 1] = left_button_rect
formspec[#formspec + 1] = ";install;"
formspec[#formspec + 1] = fgettext("Update")
formspec[#formspec + 1] = "]"
end
formspec[#formspec + 1] = "style[uninstall;bgcolor=#a93b3b]"
formspec[#formspec + 1] = "button["
formspec[#formspec + 1] = right_button_rect
formspec[#formspec + 1] = ";uninstall;"
formspec[#formspec + 1] = fgettext("Uninstall")
formspec[#formspec + 1] = "]"
end
local current_tab = data.current_tab or 1
local tab_titles = {
fgettext("Description"),
fgettext("Information"),
}
local tab_body_height = bottom_buttons_y - 2.8
table.insert_all(formspec, {
"container_end[]",
"tabheader[0.375,2.55;", size.x - 0.375*2, ",0.8;tabs;",
table.concat(tab_titles, ","), ";", current_tab, ";true;true]",
"container[0,2.8]",
})
local safezone_right = PLATFORM == "Android" and 0.375 or 0
if current_tab == 1 then
-- Screenshots and description
local hypertext = "<big><b>" .. core.hypertext_escape(info.short_description) .. "</b></big>\n"
local winfo = core.get_window_info()
local fs_to_px = winfo.size.x / winfo.max_formspec_size.x
for i, ss in ipairs(info.screenshots) do
local path = get_screenshot(data.package, ss.url, 2)
hypertext = hypertext .. "<action name=ss_" .. i .. "><img name=" ..
core.hypertext_escape(path) .. " width=" .. (3 * fs_to_px) ..
" height=" .. (2 * fs_to_px) .. "></action>"
if i ~= #info.screenshots then
hypertext = hypertext .. "<img name=blank.png width=" .. (0.25 * fs_to_px) ..
" height=" .. (2.25 * fs_to_px).. ">"
end
end
hypertext = hypertext .. "\n" .. info.long_description.head
local first = true
local function add_link_button(label, name)
if info[name] then
if not first then
hypertext = hypertext .. " | "
end
hypertext = hypertext .. "<action name=link_" .. name .. ">" .. core.hypertext_escape(label) .. "</action>"
info.long_description.links["link_" .. name] = info[name]
first = false
end
end
add_link_button(fgettext("Donate"), "donate_url")
add_link_button(fgettext("Website"), "website")
add_link_button(fgettext("Source"), "repo")
add_link_button(fgettext("Issue Tracker"), "issue_tracker")
add_link_button(fgettext("Translate"), "translation_url")
add_link_button(fgettext("Forum Topic"), "forums")
hypertext = hypertext .. "\n\n" .. info.long_description.body
hypertext = hypertext:gsub("<img name=blank.png ",
"<img name=\"" .. core.hypertext_escape(defaulttexturedir) .. "blank.png\" ")
table.insert_all(formspec, {
"hypertext[0.375,0;",
size.x - 2*0.375 - safezone_right, ",",
tab_body_height - 0.375,
";desc;", core.formspec_escape(hypertext), "]",
})
elseif current_tab == 2 then
local hypertext = info.info_hypertext.head .. info.info_hypertext.body
table.insert_all(formspec, {
"hypertext[0.375,0;", size.x - 2*0.375 - safezone_right, ",", tab_body_height - 0.375,
";info;", core.formspec_escape(hypertext), "]",
})
else
error("Unknown tab " .. current_tab)
end
formspec[#formspec + 1] = "container_end[]"
return table.concat(formspec)
end
end
local function handle_hypertext_event(this, event, hypertext_object)
if not (event and event:sub(1, 7) == "action:") then
return
end
for i, ss in ipairs(this.data.info.screenshots) do
if event == "action:ss_" .. i then
core.open_url(ss.url)
return true
end
end
-- TODO: escape base_url
local base_url = core.settings:get("contentdb_url")
for key, url in pairs(hypertext_object.links) do
if event == "action:" .. key then
local author, name = url:match("^" .. base_url .. "/?packages/([A-Za-z0-9 _-]+)/([a-z0-9_]+)/?$")
if author and name then
local package2 = contentdb.get_package_by_info(author, name)
if package2 then
local dlg = create_package_dialog(package2)
dlg:set_parent(this)
this:hide()
dlg:show()
return true
end
end
core.open_url_dialog(url)
return true
end
end
end
local function handle_submit(this, fields)
local info = this.data.info
local package = this.data.package
if fields.back then
this:delete()
return true
end
if not info then
return false
end
if fields.open_contentdb then
local url = ("%s/packages/%s/?protocol_version=%d"):format(
core.settings:get("contentdb_url"), package.url_part,
core.get_max_supp_proto())
core.open_url(url)
return true
end
if fields.install then
install_or_update_package(this, package)
return true
end
if fields.uninstall then
local dlg = create_delete_content_dlg(package)
dlg:set_parent(this)
this:hide()
dlg:show()
return true
end
if fields.tabs then
this.data.current_tab = tonumber(fields.tabs)
return true
end
if handle_hypertext_event(this, fields.desc, info.long_description) or
handle_hypertext_event(this, fields.info, info.info_hypertext) then
return true
end
end
function create_package_dialog(package)
assert(package)
local dlg = dialog_create("package_dialog_" .. package.id,
get_formspec,
handle_submit)
local data = dlg.data
data.package = package
data.info = nil
data.loading = false
data.loading_error = nil
data.current_tab = 1
return dlg
end

View File

@ -23,4 +23,5 @@ dofile(path .. DIR_DELIM .. "update_detector.lua")
dofile(path .. DIR_DELIM .. "screenshots.lua") dofile(path .. DIR_DELIM .. "screenshots.lua")
dofile(path .. DIR_DELIM .. "dlg_install.lua") dofile(path .. DIR_DELIM .. "dlg_install.lua")
dofile(path .. DIR_DELIM .. "dlg_overwrite.lua") dofile(path .. DIR_DELIM .. "dlg_overwrite.lua")
dofile(path .. DIR_DELIM .. "dlg_package.lua")
dofile(path .. DIR_DELIM .. "dlg_contentdb.lua") dofile(path .. DIR_DELIM .. "dlg_contentdb.lua")

View File

@ -23,23 +23,43 @@ local screenshot_downloading = {}
local screenshot_downloaded = {} local screenshot_downloaded = {}
local function get_filename(path)
local parts = path:split("/")
return parts[#parts]
end
local function get_file_extension(path) local function get_file_extension(path)
local parts = path:split(".") local parts = path:split(".")
return parts[#parts] return parts[#parts]
end end
function get_screenshot(package) function get_screenshot(package, screenshot_url, level)
if not package.thumbnail then if not screenshot_url then
return defaulttexturedir .. "no_screenshot.png" return defaulttexturedir .. "no_screenshot.png"
elseif screenshot_downloading[package.thumbnail] then end
-- Minetest only supports png and jpg
local ext = get_file_extension(screenshot_url)
if ext ~= "png" and ext ~= "jpg" then
screenshot_url = screenshot_url:sub(0, -#ext - 1) .. "png"
level = level or 4
end
-- Set thumbnail level
if level then
screenshot_url = screenshot_url:gsub("/thumbnails/[0-9]+/", "/thumbnails/" .. level .. "/")
screenshot_url = screenshot_url:gsub("/uploads/", "/thumbnails/" .. level .. "/")
end
if screenshot_downloading[screenshot_url] then
return defaulttexturedir .. "loading_screenshot.png" return defaulttexturedir .. "loading_screenshot.png"
end end
-- Get tmp screenshot path
local ext = get_file_extension(package.thumbnail)
local filepath = screenshot_dir .. DIR_DELIM .. local filepath = screenshot_dir .. DIR_DELIM ..
("%s-%s-%s.%s"):format(package.type, package.author, package.name, ext) ("%s-%s-%s-l%d-%s"):format(package.type, package.author, package.name,
level or 1, get_filename(screenshot_url))
-- Return if already downloaded -- Return if already downloaded
local file = io.open(filepath, "r") local file = io.open(filepath, "r")
@ -49,7 +69,7 @@ function get_screenshot(package)
end end
-- Show error if we've failed to download before -- Show error if we've failed to download before
if screenshot_downloaded[package.thumbnail] then if screenshot_downloaded[screenshot_url] then
return defaulttexturedir .. "error_screenshot.png" return defaulttexturedir .. "error_screenshot.png"
end end
@ -59,16 +79,16 @@ function get_screenshot(package)
return core.download_file(params.url, params.dest) return core.download_file(params.url, params.dest)
end end
local function callback(success) local function callback(success)
screenshot_downloading[package.thumbnail] = nil screenshot_downloading[screenshot_url] = nil
screenshot_downloaded[package.thumbnail] = true screenshot_downloaded[screenshot_url] = true
if not success then if not success then
core.log("warning", "Screenshot download failed for some reason") core.log("warning", "Screenshot download failed for some reason")
end end
ui.update() ui.update()
end end
if core.handle_async(download_screenshot, if core.handle_async(download_screenshot,
{ dest = filepath, url = package.thumbnail }, callback) then { dest = filepath, url = screenshot_url }, callback) then
screenshot_downloading[package.thumbnail] = true screenshot_downloading[screenshot_url] = true
else else
core.log("error", "ERROR: async event failed") core.log("error", "ERROR: async event failed")
return defaulttexturedir .. "error_screenshot.png" return defaulttexturedir .. "error_screenshot.png"

View File

@ -6443,6 +6443,9 @@ Formspec
* `minetest.formspec_escape(string)`: returns a string * `minetest.formspec_escape(string)`: returns a string
* escapes the characters "[", "]", "\", "," and ";", which cannot be used * escapes the characters "[", "]", "\", "," and ";", which cannot be used
in formspecs. in formspecs.
* `minetest.hypertext_escape(string)`: returns a string
* escapes the charecters "\", "<", and ">" to show text in a hypertext element.
* not safe for use with tag attributes.
* `minetest.explode_table_event(string)`: returns a table * `minetest.explode_table_event(string)`: returns a table
* returns e.g. `{type="CHG", row=1, column=2}` * returns e.g. `{type="CHG", row=1, column=2}`
* `type` is one of: * `type` is one of:

View File

@ -47,7 +47,10 @@ Functions
* returns the maximum supported network protocol version * returns the maximum supported network protocol version
* `core.open_url(url)` * `core.open_url(url)`
* opens the URL in a web browser, returns false on failure. * opens the URL in a web browser, returns false on failure.
* Must begin with http:// or https:// * `url` must begin with http:// or https://
* `core.open_url_dialog(url)`
* shows a dialog to allow the user to choose whether to open a URL.
* `url` must begin with http:// or https://
* `core.open_dir(path)` * `core.open_dir(path)`
* opens the path in the system file browser/explorer, returns false on failure. * opens the path in the system file browser/explorer, returns false on failure.
* Must be an existing directory. * Must be an existing directory.

View File

@ -343,7 +343,7 @@ void ClientLauncher::init_guienv(gui::IGUIEnvironment *guienv)
float density = rangelim(g_settings->getFloat("gui_scaling"), 0.5f, 20) * float density = rangelim(g_settings->getFloat("gui_scaling"), 0.5f, 20) *
RenderingEngine::getDisplayDensity(); RenderingEngine::getDisplayDensity();
skin->setSize(gui::EGDS_CHECK_BOX_WIDTH, (s32)(17.0f * density)); skin->setSize(gui::EGDS_CHECK_BOX_WIDTH, (s32)(17.0f * density));
skin->setSize(gui::EGDS_SCROLLBAR_SIZE, (s32)(14.0f * density)); skin->setSize(gui::EGDS_SCROLLBAR_SIZE, (s32)(21.0f * density));
skin->setSize(gui::EGDS_WINDOW_BUTTON_WIDTH, (s32)(15.0f * density)); skin->setSize(gui::EGDS_WINDOW_BUTTON_WIDTH, (s32)(15.0f * density));
if (density > 1.5f) { if (density > 1.5f) {
std::string sprite_path = porting::path_share + "/textures/base/pack/"; std::string sprite_path = porting::path_share + "/textures/base/pack/";

View File

@ -60,7 +60,7 @@ GUITable::GUITable(gui::IGUIEnvironment *env,
m_rowheight = MYMAX(m_rowheight, 1); m_rowheight = MYMAX(m_rowheight, 1);
} }
const s32 s = skin->getSize(gui::EGDS_SCROLLBAR_SIZE) * 1.5f; const s32 s = skin->getSize(gui::EGDS_SCROLLBAR_SIZE);
m_scrollbar = new GUIScrollBar(Environment, this, -1, m_scrollbar = new GUIScrollBar(Environment, this, -1,
core::rect<s32>(RelativeRect.getWidth() - s, core::rect<s32>(RelativeRect.getWidth() - s,
0, 0,

View File

@ -40,6 +40,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "content/mod_configuration.h" #include "content/mod_configuration.h"
#include "threading/mutex_auto_lock.h" #include "threading/mutex_auto_lock.h"
#include "common/c_converter.h" #include "common/c_converter.h"
#include "gui/guiOpenURL.h"
/******************************************************************************/ /******************************************************************************/
std::string ModApiMainMenu::getTextData(lua_State *L, const std::string &name) std::string ModApiMainMenu::getTextData(lua_State *L, const std::string &name)
@ -1038,6 +1039,22 @@ int ModApiMainMenu::l_open_url(lua_State *L)
return 1; return 1;
} }
/******************************************************************************/
int ModApiMainMenu::l_open_url_dialog(lua_State *L)
{
GUIEngine* engine = getGuiEngine(L);
sanity_check(engine != NULL);
std::string url = luaL_checkstring(L, 1);
GUIOpenURLMenu* openURLMenu =
new GUIOpenURLMenu(engine->m_rendering_engine->get_gui_env(),
engine->m_parent, -1, engine->m_menumanager,
engine->m_texture_source.get(), url);
openURLMenu->drop();
return 1;
}
/******************************************************************************/ /******************************************************************************/
int ModApiMainMenu::l_open_dir(lua_State *L) int ModApiMainMenu::l_open_dir(lua_State *L)
{ {
@ -1129,6 +1146,7 @@ void ModApiMainMenu::Initialize(lua_State *L, int top)
API_FCT(get_min_supp_proto); API_FCT(get_min_supp_proto);
API_FCT(get_max_supp_proto); API_FCT(get_max_supp_proto);
API_FCT(open_url); API_FCT(open_url);
API_FCT(open_url_dialog);
API_FCT(open_dir); API_FCT(open_dir);
API_FCT(share_file); API_FCT(share_file);
API_FCT(do_async_callback); API_FCT(do_async_callback);

View File

@ -162,6 +162,8 @@ private:
// other // other
static int l_open_url(lua_State *L); static int l_open_url(lua_State *L);
static int l_open_url_dialog(lua_State *L);
static int l_open_dir(lua_State *L); static int l_open_dir(lua_State *L);
static int l_share_file(lua_State *L); static int l_share_file(lua_State *L);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 147 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 191 B