mirror of
				https://github.com/luanti-org/luanti.git
				synced 2025-11-04 09:15:29 +01:00 
			
		
		
		
	ContentDB redesign: Add package dialog
Co-authored-by: Gregor Parzefall <gregor.parzefall@posteo.de>
This commit is contained in:
		@@ -235,6 +235,16 @@ function core.formspec_escape(text)
 | 
			
		||||
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)
 | 
			
		||||
	local result = {}
 | 
			
		||||
	local line = {}
 | 
			
		||||
 
 | 
			
		||||
@@ -182,6 +182,23 @@ function contentdb.get_package_by_id(id)
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function contentdb.calculate_package_id(type, author, name)
 | 
			
		||||
	local id = author:lower() .. "/"
 | 
			
		||||
	if (type == nil or type == "game") and #name > 5 and name:sub(#name - 4) == "_game" then
 | 
			
		||||
		id = id .. name:sub(1, #name - 5)
 | 
			
		||||
	else
 | 
			
		||||
		id = id .. name
 | 
			
		||||
	end
 | 
			
		||||
	return id
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function contentdb.get_package_by_info(author, name)
 | 
			
		||||
	local id = contentdb.calculate_package_id(nil, author, name)
 | 
			
		||||
	return contentdb.package_by_id[id]
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
-- Create a coroutine from `fn` and provide results to `callback` when complete (dead).
 | 
			
		||||
-- Returns a resumer function.
 | 
			
		||||
local function make_callback_coroutine(fn, callback)
 | 
			
		||||
@@ -415,15 +432,7 @@ local function fetch_pkgs(params)
 | 
			
		||||
	local aliases = {}
 | 
			
		||||
 | 
			
		||||
	for _, package in pairs(packages) do
 | 
			
		||||
		local name_len = #package.name
 | 
			
		||||
		-- This must match what contentdb.update_paths() does!
 | 
			
		||||
		package.id = package.author:lower() .. "/"
 | 
			
		||||
		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)
 | 
			
		||||
		else
 | 
			
		||||
			package.id = package.id .. package.name
 | 
			
		||||
		end
 | 
			
		||||
 | 
			
		||||
		package.id = params.calculate_package_id(package.type, package.author, package.name)
 | 
			
		||||
		package.url_part = core.urlencode(package.author) .. "/" .. core.urlencode(package.name)
 | 
			
		||||
 | 
			
		||||
		if package.aliases then
 | 
			
		||||
@@ -443,7 +452,7 @@ end
 | 
			
		||||
 | 
			
		||||
function contentdb.fetch_pkgs(callback)
 | 
			
		||||
	contentdb.loading = true
 | 
			
		||||
	core.handle_async(fetch_pkgs, nil, function(result)
 | 
			
		||||
	core.handle_async(fetch_pkgs, { calculate_package_id = contentdb.calculate_package_id  }, function(result)
 | 
			
		||||
		if result then
 | 
			
		||||
			contentdb.load_ok = true
 | 
			
		||||
			contentdb.load_error = false
 | 
			
		||||
@@ -581,3 +590,54 @@ function contentdb.filter_packages(query, by_type)
 | 
			
		||||
		end
 | 
			
		||||
	end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function contentdb.get_full_package_info(package, callback)
 | 
			
		||||
	assert(package)
 | 
			
		||||
	if package.full_info then
 | 
			
		||||
		callback(package.full_info)
 | 
			
		||||
		return
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	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.urlencode(core.get_max_supp_proto()) ..
 | 
			
		||||
				"&engine_version=" .. core.urlencode(version.string) ..
 | 
			
		||||
				"&formspec_version=" .. core.urlencode(core.get_formspec_version()) ..
 | 
			
		||||
				"&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
 | 
			
		||||
 | 
			
		||||
	local function my_callback(value)
 | 
			
		||||
		package.full_info = value
 | 
			
		||||
		callback(value)
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	if not core.handle_async(fetch, { package = package }, my_callback) then
 | 
			
		||||
		core.log("error", "ERROR: async event failed")
 | 
			
		||||
		callback(nil)
 | 
			
		||||
	end
 | 
			
		||||
end
 | 
			
		||||
 
 | 
			
		||||
@@ -46,48 +46,6 @@ local filter_types_type = {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
local function install_or_update_package(this, package)
 | 
			
		||||
	local install_parent
 | 
			
		||||
	if package.type == "mod" then
 | 
			
		||||
		install_parent = core.get_modpath()
 | 
			
		||||
	elseif package.type == "game" then
 | 
			
		||||
		install_parent = core.get_gamepath()
 | 
			
		||||
	elseif package.type == "txp" then
 | 
			
		||||
		install_parent = core.get_texturepath()
 | 
			
		||||
	else
 | 
			
		||||
		error("Unknown package type: " .. package.type)
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	if package.queued or package.downloading then
 | 
			
		||||
		return
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	local function on_confirm()
 | 
			
		||||
		local dlg = create_install_dialog(package)
 | 
			
		||||
		dlg:set_parent(this)
 | 
			
		||||
		this:hide()
 | 
			
		||||
		dlg:show()
 | 
			
		||||
 | 
			
		||||
		dlg:load_deps()
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	if package.type == "mod" and #pkgmgr.games == 0 then
 | 
			
		||||
		local dlg = messagebox("install_game",
 | 
			
		||||
			fgettext("You need to install a game before you can install a mod"))
 | 
			
		||||
		dlg:set_parent(this)
 | 
			
		||||
		this:hide()
 | 
			
		||||
		dlg:show()
 | 
			
		||||
	elseif not package.path and core.is_dir(install_parent .. DIR_DELIM .. package.name) then
 | 
			
		||||
		local dlg = create_confirm_overwrite(package, on_confirm)
 | 
			
		||||
		dlg:set_parent(this)
 | 
			
		||||
		this:hide()
 | 
			
		||||
		dlg:show()
 | 
			
		||||
	else
 | 
			
		||||
		on_confirm()
 | 
			
		||||
	end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
-- Resolves the package specification stored in auto_install_spec into an actual package.
 | 
			
		||||
-- May only be called after the package list has been loaded successfully.
 | 
			
		||||
local function resolve_auto_install_spec()
 | 
			
		||||
@@ -291,7 +249,7 @@ local function get_formspec(dlgdata)
 | 
			
		||||
 | 
			
		||||
		-- image
 | 
			
		||||
		formspec[#formspec + 1] = "image[0,0;1.5,1;"
 | 
			
		||||
		formspec[#formspec + 1] = core.formspec_escape(get_screenshot(package))
 | 
			
		||||
		formspec[#formspec + 1] = core.formspec_escape(get_screenshot(package, package.thumbnail, 1))
 | 
			
		||||
		formspec[#formspec + 1] = "]"
 | 
			
		||||
 | 
			
		||||
		-- title
 | 
			
		||||
@@ -301,52 +259,17 @@ local function get_formspec(dlgdata)
 | 
			
		||||
				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
 | 
			
		||||
				-- The install_ action also handles updating
 | 
			
		||||
				local elem_name = "install_" .. i .. ";"
 | 
			
		||||
				formspec[#formspec + 1] = "style[" .. elem_name .. "bgcolor=#28ccdf]"
 | 
			
		||||
				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
 | 
			
		||||
 | 
			
		||||
			local elem_name = "uninstall_" .. i .. ";"
 | 
			
		||||
			formspec[#formspec + 1] = "style[" .. elem_name .. "bgcolor=#a93b3b]"
 | 
			
		||||
			formspec[#formspec + 1] = second_base .. "cdb_clear.png;" .. elem_name .. "]"
 | 
			
		||||
			formspec[#formspec + 1] = "tooltip[" .. elem_name .. fgettext("Uninstall") .. tooltip_colors
 | 
			
		||||
		end
 | 
			
		||||
 | 
			
		||||
		local web_elem_name = "view_" .. i .. ";"
 | 
			
		||||
		formspec[#formspec + 1] = "image_button[-0.7,0;0.7,0.7;" ..
 | 
			
		||||
			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[]"
 | 
			
		||||
		-- button
 | 
			
		||||
		formspec[#formspec + 1] = "button["
 | 
			
		||||
		formspec[#formspec + 1] = W-0.375*2-2
 | 
			
		||||
		formspec[#formspec + 1] = ",0.1;2,0.7;view_"
 | 
			
		||||
		formspec[#formspec + 1] = i
 | 
			
		||||
		formspec[#formspec + 1] = ";"
 | 
			
		||||
		formspec[#formspec + 1] = fgettext("View")
 | 
			
		||||
		formspec[#formspec + 1] = "]"
 | 
			
		||||
 | 
			
		||||
		-- description
 | 
			
		||||
		local description_width = W - 2.625 - 2 * 0.7 - 2 * 0.15
 | 
			
		||||
		formspec[#formspec + 1] = "textarea[1.855,0.3;"
 | 
			
		||||
		formspec[#formspec + 1] = tostring(description_width)
 | 
			
		||||
		formspec[#formspec + 1] = ",0.8;;;"
 | 
			
		||||
@@ -434,26 +357,13 @@ local function handle_submit(this, fields)
 | 
			
		||||
		local package = contentdb.packages[i]
 | 
			
		||||
		assert(package)
 | 
			
		||||
 | 
			
		||||
		if fields["install_" .. i] then
 | 
			
		||||
			install_or_update_package(this, package)
 | 
			
		||||
			return true
 | 
			
		||||
		end
 | 
			
		||||
 | 
			
		||||
		if fields["uninstall_" .. i] then
 | 
			
		||||
			local dlg = create_delete_content_dlg(package)
 | 
			
		||||
		if fields["view_" .. i] then
 | 
			
		||||
			local dlg = create_package_dialog(package)
 | 
			
		||||
			dlg:set_parent(this)
 | 
			
		||||
			this:hide()
 | 
			
		||||
			dlg:show()
 | 
			
		||||
			return true
 | 
			
		||||
		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
 | 
			
		||||
 | 
			
		||||
	return false
 | 
			
		||||
 
 | 
			
		||||
@@ -244,3 +244,45 @@ function create_install_dialog(package)
 | 
			
		||||
 | 
			
		||||
	return dlg
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function install_or_update_package(parent, package)
 | 
			
		||||
	local install_parent
 | 
			
		||||
	if package.type == "mod" then
 | 
			
		||||
		install_parent = core.get_modpath()
 | 
			
		||||
	elseif package.type == "game" then
 | 
			
		||||
		install_parent = core.get_gamepath()
 | 
			
		||||
	elseif package.type == "txp" then
 | 
			
		||||
		install_parent = core.get_texturepath()
 | 
			
		||||
	else
 | 
			
		||||
		error("Unknown package type: " .. package.type)
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	if package.queued or package.downloading then
 | 
			
		||||
		return
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	local function on_confirm()
 | 
			
		||||
		local dlg = create_install_dialog(package)
 | 
			
		||||
		dlg:set_parent(parent)
 | 
			
		||||
		parent:hide()
 | 
			
		||||
		dlg:show()
 | 
			
		||||
 | 
			
		||||
		dlg:load_deps()
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	if package.type == "mod" and #pkgmgr.games == 0 then
 | 
			
		||||
		local dlg = messagebox("install_game",
 | 
			
		||||
				fgettext("You need to install a game before you can install a mod"))
 | 
			
		||||
		dlg:set_parent(parent)
 | 
			
		||||
		parent:hide()
 | 
			
		||||
		dlg:show()
 | 
			
		||||
	elseif not package.path and core.is_dir(install_parent .. DIR_DELIM .. package.name) then
 | 
			
		||||
		local dlg = create_confirm_overwrite(package, on_confirm)
 | 
			
		||||
		dlg:set_parent(parent)
 | 
			
		||||
		parent:hide()
 | 
			
		||||
		dlg:show()
 | 
			
		||||
	else
 | 
			
		||||
		on_confirm()
 | 
			
		||||
	end
 | 
			
		||||
end
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										325
									
								
								builtin/mainmenu/content/dlg_package.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										325
									
								
								builtin/mainmenu/content/dlg_package.lua
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,325 @@
 | 
			
		||||
--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, padding, text)
 | 
			
		||||
	return table.concat({
 | 
			
		||||
		"formspec_version[6]",
 | 
			
		||||
		"size[", size.x, ",", size.y, "]",
 | 
			
		||||
		"padding[0,0]",
 | 
			
		||||
		"bgcolor[;true]",
 | 
			
		||||
 | 
			
		||||
		"label[4,4.35;", text, "]",
 | 
			
		||||
		"container[", padding.x, ",", size.y - 0.8 - padding.y, "]",
 | 
			
		||||
		"button[0,0;2,0.8;back;", fgettext("Back"), "]",
 | 
			
		||||
		"container_end[]",
 | 
			
		||||
	})
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
local function get_formspec(data)
 | 
			
		||||
	-- Padding is increased on Android to account for notches
 | 
			
		||||
	-- TODO: use Android API to determine size of cut outs
 | 
			
		||||
	local window_padding = { x = PLATFORM == "Android" and 1 or 0.5, y = PLATFORM == "Android" and 0.25 or 0.5 }
 | 
			
		||||
	local window = core.get_window_info()
 | 
			
		||||
	local size = { x = window.max_formspec_size.x, y = window.max_formspec_size.y }
 | 
			
		||||
	size.x = math.min(size.x, 20)
 | 
			
		||||
	local W = size.x - window_padding.x * 2
 | 
			
		||||
	local H = size.y - window_padding.y * 2
 | 
			
		||||
 | 
			
		||||
	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
 | 
			
		||||
 | 
			
		||||
				assert(data.package.name == info.name)
 | 
			
		||||
				data.info = info
 | 
			
		||||
				ui.update()
 | 
			
		||||
			end)
 | 
			
		||||
		end
 | 
			
		||||
 | 
			
		||||
		-- get_full_package_info can return cached info immediately, so
 | 
			
		||||
		-- check to see if that happened
 | 
			
		||||
		if not data.info then
 | 
			
		||||
			if data.loading_error then
 | 
			
		||||
				return get_info_formspec(size, window_padding, fgettext("No packages could be retrieved"))
 | 
			
		||||
			end
 | 
			
		||||
			return get_info_formspec(size, window_padding, fgettext("Loading..."))
 | 
			
		||||
		end
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	-- 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 = H - 0.8
 | 
			
		||||
 | 
			
		||||
	local formspec = {
 | 
			
		||||
		"formspec_version[7]",
 | 
			
		||||
		"size[", size.x, ",",  size.y, "]",
 | 
			
		||||
		"padding[0,0]",
 | 
			
		||||
		"bgcolor[;true]",
 | 
			
		||||
 | 
			
		||||
		"container[", window_padding.x, ",", window_padding.y, "]",
 | 
			
		||||
 | 
			
		||||
		"button[0,", bottom_buttons_y, ";2,0.8;back;", fgettext("Back"), "]",
 | 
			
		||||
		"button[", W - 3, ",", bottom_buttons_y, ";3,0.8;open_contentdb;", fgettext("ContentDB page"), "]",
 | 
			
		||||
 | 
			
		||||
		"style_type[label;font_size=+24;font=bold]",
 | 
			
		||||
		"label[0,0.4;", core.formspec_escape(info.title), "]",
 | 
			
		||||
		"style_type[label;font_size=;font=]",
 | 
			
		||||
 | 
			
		||||
		"label[0,1.2;", core.formspec_escape(info_line), "]",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	table.insert_all(formspec, {
 | 
			
		||||
		"container[", W - 6, ",0]"
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	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[]",
 | 
			
		||||
 | 
			
		||||
		"box[0,2.55;", W, ",", tab_body_height, ";#ffffff11]",
 | 
			
		||||
 | 
			
		||||
		"tabheader[0,2.55;", W, ",0.8;tabs;",
 | 
			
		||||
		table.concat(tab_titles, ","), ";", current_tab, ";true;true]",
 | 
			
		||||
 | 
			
		||||
		"container[0,2.8]",
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	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,0;", W, ",", 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,0;", W, ",", tab_body_height - 0.375,
 | 
			
		||||
			";info;", core.formspec_escape(hypertext), "]",
 | 
			
		||||
		})
 | 
			
		||||
	else
 | 
			
		||||
		error("Unknown tab " .. current_tab)
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	formspec[#formspec + 1] = "container_end[]"
 | 
			
		||||
	formspec[#formspec + 1] = "container_end[]"
 | 
			
		||||
 | 
			
		||||
	return table.concat(formspec)
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
	local base_url = core.settings:get("contentdb_url"):gsub("(%W)", "%%%1")
 | 
			
		||||
	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
 | 
			
		||||
@@ -23,4 +23,5 @@ dofile(path .. DIR_DELIM .. "update_detector.lua")
 | 
			
		||||
dofile(path .. DIR_DELIM .. "screenshots.lua")
 | 
			
		||||
dofile(path .. DIR_DELIM .. "dlg_install.lua")
 | 
			
		||||
dofile(path .. DIR_DELIM .. "dlg_overwrite.lua")
 | 
			
		||||
dofile(path .. DIR_DELIM .. "dlg_package.lua")
 | 
			
		||||
dofile(path .. DIR_DELIM .. "dlg_contentdb.lua")
 | 
			
		||||
 
 | 
			
		||||
@@ -23,23 +23,40 @@ local screenshot_downloading = {}
 | 
			
		||||
local screenshot_downloaded = {}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
local function get_filename(path)
 | 
			
		||||
	local parts = path:split("/")
 | 
			
		||||
	return parts[#parts]
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
local function get_file_extension(path)
 | 
			
		||||
	local parts = path:split(".")
 | 
			
		||||
	return parts[#parts]
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function get_screenshot(package)
 | 
			
		||||
	if not package.thumbnail then
 | 
			
		||||
function get_screenshot(package, screenshot_url, level)
 | 
			
		||||
	if not screenshot_url then
 | 
			
		||||
		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"
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	-- Set thumbnail level
 | 
			
		||||
	screenshot_url = screenshot_url:gsub("/thumbnails/[0-9]+/", "/thumbnails/" .. level .. "/")
 | 
			
		||||
	screenshot_url = screenshot_url:gsub("/uploads/", "/thumbnails/" .. level .. "/")
 | 
			
		||||
 | 
			
		||||
	if screenshot_downloading[screenshot_url] then
 | 
			
		||||
		return defaulttexturedir .. "loading_screenshot.png"
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	-- Get tmp screenshot path
 | 
			
		||||
	local ext = get_file_extension(package.thumbnail)
 | 
			
		||||
	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, get_filename(screenshot_url))
 | 
			
		||||
 | 
			
		||||
	-- Return if already downloaded
 | 
			
		||||
	local file = io.open(filepath, "r")
 | 
			
		||||
@@ -49,7 +66,7 @@ function get_screenshot(package)
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	-- 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"
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
@@ -59,16 +76,16 @@ function get_screenshot(package)
 | 
			
		||||
		return core.download_file(params.url, params.dest)
 | 
			
		||||
	end
 | 
			
		||||
	local function callback(success)
 | 
			
		||||
		screenshot_downloading[package.thumbnail] = nil
 | 
			
		||||
		screenshot_downloaded[package.thumbnail] = true
 | 
			
		||||
		screenshot_downloading[screenshot_url] = nil
 | 
			
		||||
		screenshot_downloaded[screenshot_url] = true
 | 
			
		||||
		if not success then
 | 
			
		||||
			core.log("warning", "Screenshot download failed for some reason")
 | 
			
		||||
		end
 | 
			
		||||
		ui.update()
 | 
			
		||||
	end
 | 
			
		||||
	if core.handle_async(download_screenshot,
 | 
			
		||||
			{ dest = filepath, url = package.thumbnail }, callback) then
 | 
			
		||||
		screenshot_downloading[package.thumbnail] = true
 | 
			
		||||
			{ dest = filepath, url = screenshot_url }, callback) then
 | 
			
		||||
		screenshot_downloading[screenshot_url] = true
 | 
			
		||||
	else
 | 
			
		||||
		core.log("error", "ERROR: async event failed")
 | 
			
		||||
		return defaulttexturedir .. "error_screenshot.png"
 | 
			
		||||
 
 | 
			
		||||
@@ -19,12 +19,7 @@
 | 
			
		||||
local function prepare_credits(dest, source)
 | 
			
		||||
	local string = table.concat(source, "\n") .. "\n"
 | 
			
		||||
 | 
			
		||||
	local hypertext_escapes = {
 | 
			
		||||
		["\\"] = "\\\\",
 | 
			
		||||
		["<"] = "\\<",
 | 
			
		||||
		[">"] = "\\>",
 | 
			
		||||
	}
 | 
			
		||||
	string = string:gsub("[\\<>]", hypertext_escapes)
 | 
			
		||||
	string = core.hypertext_escape(string)
 | 
			
		||||
	string = string:gsub("%[.-%]", "<gray>%1</gray>")
 | 
			
		||||
 | 
			
		||||
	table.insert(dest, string)
 | 
			
		||||
 
 | 
			
		||||
@@ -6581,6 +6581,9 @@ Formspec
 | 
			
		||||
* `minetest.formspec_escape(string)`: returns a string
 | 
			
		||||
    * escapes the characters "[", "]", "\", "," and ";", which cannot be used
 | 
			
		||||
      in formspecs.
 | 
			
		||||
* `minetest.hypertext_escape(string)`: returns a string
 | 
			
		||||
    * escapes the characters "\", "<", and ">" to show text in a hypertext element.
 | 
			
		||||
    * not safe for use with tag attributes.
 | 
			
		||||
* `minetest.explode_table_event(string)`: returns a table
 | 
			
		||||
    * returns e.g. `{type="CHG", row=1, column=2}`
 | 
			
		||||
    * `type` is one of:
 | 
			
		||||
 
 | 
			
		||||
@@ -57,7 +57,10 @@ Functions
 | 
			
		||||
  * returns the maximum supported network protocol version
 | 
			
		||||
* `core.open_url(url)`
 | 
			
		||||
  * 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)`
 | 
			
		||||
  * opens the path in the system file browser/explorer, returns false on failure.
 | 
			
		||||
  * Must be an existing directory.
 | 
			
		||||
@@ -65,6 +68,8 @@ Functions
 | 
			
		||||
  * Android only. Shares file using the share popup
 | 
			
		||||
* `core.get_version()` (possible in async calls)
 | 
			
		||||
  * returns current core version
 | 
			
		||||
* `core.get_formspec_version()`
 | 
			
		||||
  * returns maximum supported formspec version
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -41,6 +41,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 | 
			
		||||
#include "content/mod_configuration.h"
 | 
			
		||||
#include "threading/mutex_auto_lock.h"
 | 
			
		||||
#include "common/c_converter.h"
 | 
			
		||||
#include "gui/guiOpenURL.h"
 | 
			
		||||
 | 
			
		||||
/******************************************************************************/
 | 
			
		||||
std::string ModApiMainMenu::getTextData(lua_State *L, const std::string &name)
 | 
			
		||||
@@ -1038,6 +1039,13 @@ int ModApiMainMenu::l_get_max_supp_proto(lua_State *L)
 | 
			
		||||
	return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/******************************************************************************/
 | 
			
		||||
int ModApiMainMenu::l_get_formspec_version(lua_State  *L)
 | 
			
		||||
{
 | 
			
		||||
	lua_pushinteger(L, FORMSPEC_API_VERSION);
 | 
			
		||||
	return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/******************************************************************************/
 | 
			
		||||
int ModApiMainMenu::l_open_url(lua_State *L)
 | 
			
		||||
{
 | 
			
		||||
@@ -1046,6 +1054,22 @@ int ModApiMainMenu::l_open_url(lua_State *L)
 | 
			
		||||
	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)
 | 
			
		||||
{
 | 
			
		||||
@@ -1136,7 +1160,9 @@ void ModApiMainMenu::Initialize(lua_State *L, int top)
 | 
			
		||||
	API_FCT(get_active_irrlicht_device);
 | 
			
		||||
	API_FCT(get_min_supp_proto);
 | 
			
		||||
	API_FCT(get_max_supp_proto);
 | 
			
		||||
	API_FCT(get_formspec_version);
 | 
			
		||||
	API_FCT(open_url);
 | 
			
		||||
	API_FCT(open_url_dialog);
 | 
			
		||||
	API_FCT(open_dir);
 | 
			
		||||
	API_FCT(share_file);
 | 
			
		||||
	API_FCT(do_async_callback);
 | 
			
		||||
@@ -1166,6 +1192,7 @@ void ModApiMainMenu::InitializeAsync(lua_State *L, int top)
 | 
			
		||||
	API_FCT(download_file);
 | 
			
		||||
	API_FCT(get_min_supp_proto);
 | 
			
		||||
	API_FCT(get_max_supp_proto);
 | 
			
		||||
	API_FCT(get_formspec_version);
 | 
			
		||||
	API_FCT(get_language);
 | 
			
		||||
	API_FCT(gettext);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -159,9 +159,13 @@ private:
 | 
			
		||||
 | 
			
		||||
	static int l_get_max_supp_proto(lua_State *L);
 | 
			
		||||
 | 
			
		||||
	static int l_get_formspec_version(lua_State  *L);
 | 
			
		||||
 | 
			
		||||
	// other
 | 
			
		||||
	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_share_file(lua_State *L);
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user