mirror of
https://github.com/blender/blender-addons-contrib.git
synced 2025-08-16 16:14:56 +00:00
Extensions: improve extensions UI for URL & filepath dropping
- Add cancel button. - Add repository selector when installing from files. - Make enabling after installing optional. Implement design from #118635.
This commit is contained in:
@ -180,20 +180,13 @@ def extenion_repos_upgrade(*_):
|
||||
def extenion_url_drop(url):
|
||||
from .bl_extension_ui import (
|
||||
extension_drop_url_popover,
|
||||
extension_drop_filepath_popover,
|
||||
extension_drop_file_popover,
|
||||
)
|
||||
|
||||
wm = bpy.context.window_manager
|
||||
if url.startswith(("http://", "https://", "file://")):
|
||||
wm.popover(
|
||||
lambda panel, context: extension_drop_url_popover(panel, context, url),
|
||||
ui_units_x=14,
|
||||
)
|
||||
extension_drop_url_popover(url)
|
||||
else:
|
||||
wm.popover(
|
||||
lambda panel, context: extension_drop_filepath_popover(panel, context, url),
|
||||
ui_units_x=14,
|
||||
)
|
||||
extension_drop_file_popover(url)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@ -411,6 +404,19 @@ def register():
|
||||
default=True,
|
||||
)
|
||||
|
||||
# Use for drag & drop operators.
|
||||
from .bl_extension_ops import rna_prop_repo_enum_local_only_itemf
|
||||
WindowManager.extension_local_repos = EnumProperty(
|
||||
name="Local Repository",
|
||||
description="Local repository",
|
||||
items=rna_prop_repo_enum_local_only_itemf,
|
||||
)
|
||||
WindowManager.extension_enable_on_install = BoolProperty(
|
||||
name="Enable Add-on",
|
||||
description="Enable on install",
|
||||
default=True,
|
||||
)
|
||||
|
||||
from bl_ui.space_userpref import USERPREF_MT_interface_theme_presets
|
||||
USERPREF_MT_interface_theme_presets.append(theme_preset_draw)
|
||||
|
||||
|
@ -50,10 +50,6 @@ from .bl_extension_utils import (
|
||||
RepoLockContext,
|
||||
)
|
||||
|
||||
# Enable extensions when they're installed.
|
||||
USE_ENABLE_ON_INSTALL = True
|
||||
|
||||
|
||||
rna_prop_url = StringProperty(name="URL", subtype='FILE_PATH', options={'HIDDEN'})
|
||||
rna_prop_directory = StringProperty(name="Repo Directory", subtype='FILE_PATH')
|
||||
rna_prop_repo_index = IntProperty(name="Repo Index", default=-1)
|
||||
@ -126,6 +122,13 @@ class CheckSIGINT_Context:
|
||||
# Internal Utilities
|
||||
#
|
||||
|
||||
def wm_close_popup_hack():
|
||||
# Setting the workspace closes popup, we may want an API for this.
|
||||
from bpy import context
|
||||
window = context.window
|
||||
window.workspace = window.workspace
|
||||
|
||||
|
||||
def online_user_agent_from_blender():
|
||||
# NOTE: keep this brief and avoid `platform.platform()` which could identify individual users.
|
||||
# Produces something like this: `Blender/4.2.0 (Linux x86_64; cycle=alpha)` or similar.
|
||||
@ -1095,10 +1098,20 @@ class BlPkgPkgInstallFiles(Operator, _BlPkgCmdMixIn):
|
||||
description="The local repository to install extensions into",
|
||||
)
|
||||
|
||||
enable_on_install: BoolProperty(
|
||||
name="Enable Add-on",
|
||||
description="Enable add-ons after installing",
|
||||
default=True,
|
||||
)
|
||||
|
||||
def exec_command_iter(self, is_modal):
|
||||
from .bl_extension_utils import (
|
||||
pkg_manifest_dict_from_file_or_error,
|
||||
)
|
||||
from .bl_extension_ui import (
|
||||
extension_drop_file_popover_close_as_needed,
|
||||
)
|
||||
extension_drop_file_popover_close_as_needed()
|
||||
|
||||
self._addon_restore = []
|
||||
|
||||
@ -1223,7 +1236,7 @@ class BlPkgPkgInstallFiles(Operator, _BlPkgCmdMixIn):
|
||||
pkg_id_sequence_installed = pkg_id_sequence
|
||||
|
||||
# Install.
|
||||
if USE_ENABLE_ON_INSTALL:
|
||||
if self.enable_on_install:
|
||||
import addon_utils
|
||||
|
||||
# TODO: it would be nice to include this message in the banner.
|
||||
@ -1272,7 +1285,18 @@ class BlPkgPkgInstall(Operator, _BlPkgCmdMixIn):
|
||||
|
||||
pkg_id: rna_prop_pkg_id
|
||||
|
||||
enable_on_install: BoolProperty(
|
||||
name="Enable Add-on",
|
||||
description="Enable add-ons after installing",
|
||||
default=True,
|
||||
)
|
||||
|
||||
def exec_command_iter(self, is_modal):
|
||||
from .bl_extension_ui import (
|
||||
extension_drop_url_popover_close_as_needed,
|
||||
)
|
||||
extension_drop_url_popover_close_as_needed()
|
||||
|
||||
self._addon_restore = []
|
||||
|
||||
directory = _repo_dir_and_index_get(self.repo_index, self.repo_directory, self.report)
|
||||
@ -1352,7 +1376,7 @@ class BlPkgPkgInstall(Operator, _BlPkgCmdMixIn):
|
||||
)
|
||||
else:
|
||||
# Install.
|
||||
if USE_ENABLE_ON_INSTALL:
|
||||
if self.enable_on_install:
|
||||
import addon_utils
|
||||
|
||||
# TODO: it would be nice to include this message in the banner.
|
||||
@ -1678,6 +1702,16 @@ class BlPkgEnableNotInstalled(Operator):
|
||||
return {'CANCELLED'}
|
||||
|
||||
|
||||
class BlPkgPopupCancel(Operator):
|
||||
"""Close the popup"""
|
||||
bl_idname = "bl_pkg.popup_cancel"
|
||||
bl_label = "Cancel"
|
||||
|
||||
def execute(self, context):
|
||||
wm_close_popup_hack()
|
||||
return {'CANCELLED'}
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Register
|
||||
#
|
||||
@ -1709,6 +1743,7 @@ classes = (
|
||||
|
||||
# Dummy, just shows a message.
|
||||
BlPkgEnableNotInstalled,
|
||||
BlPkgPopupCancel,
|
||||
|
||||
# Dummy commands (for testing).
|
||||
BlPkgDummyProgress,
|
||||
|
@ -9,8 +9,10 @@ Written to allow a UI without modifying Blender.
|
||||
|
||||
__all__ = (
|
||||
"display_errors",
|
||||
"extension_drop_filepath_popover",
|
||||
"extension_drop_file_popover",
|
||||
"extension_drop_file_popover_close_as_needed",
|
||||
"extension_drop_url_popover",
|
||||
"extension_drop_url_popover_close_as_needed",
|
||||
"register",
|
||||
"unregister",
|
||||
)
|
||||
@ -113,55 +115,57 @@ def extension_url_find_repo_index_and_pkg_id(url):
|
||||
return -1, "", "", None, None
|
||||
|
||||
|
||||
def extension_drop_url_popover(panel, context, url):
|
||||
layout = panel.layout
|
||||
def wm_error(title, body, *, icon):
|
||||
def fn(panel, context):
|
||||
layout = panel.layout
|
||||
layout.label(text=title, icon=icon)
|
||||
if body:
|
||||
layout.label(text=body)
|
||||
|
||||
wm = bpy.context.window_manager
|
||||
wm.popover(fn, ui_units_x=14)
|
||||
|
||||
|
||||
def extension_drop_url_popover(url):
|
||||
repo_index, repo_name, pkg_id, item_remote, item_local = extension_url_find_repo_index_and_pkg_id(url)
|
||||
|
||||
if repo_index == -1:
|
||||
layout.label(text="Extension: URL not found in remote repositories!", icon='ERROR')
|
||||
layout.label(text=url)
|
||||
wm_error("Extension: URL not found in remote repositories!", url, icon='ERROR')
|
||||
return
|
||||
|
||||
if item_local is not None:
|
||||
layout.label(text="Extension: {:s}".format(pkg_id), icon='INFO')
|
||||
layout.label(text="Already installed!")
|
||||
wm_error("Extension: {:s}".format(pkg_id), "Already installed!", icon='ERROR')
|
||||
return
|
||||
|
||||
layout.label(text="Install Extension")
|
||||
layout.separator(type='LINE')
|
||||
layout.label(text="Do you want to install the following {:s}?".format(item_remote["type"]))
|
||||
|
||||
col = layout.column(align=True)
|
||||
col.label(text="Name: {:s}".format(item_remote["name"]))
|
||||
col.label(text="Repository: {:s}".format(repo_name))
|
||||
col.label(text="Size: {:s}".format(size_as_fmt_string(item_remote["archive_size"], precision=0)))
|
||||
del col
|
||||
|
||||
layout.separator()
|
||||
|
||||
# if item_remote["type"] == "add-on":
|
||||
# # TODO: option to enable.
|
||||
# pass
|
||||
|
||||
row = layout.row()
|
||||
|
||||
props = row.operator("bl_pkg.pkg_install", text="Install & Enable")
|
||||
props.repo_index = repo_index
|
||||
props.pkg_id = pkg_id
|
||||
USERPREF_PT_extensions_bl_pkg_drop_url.drop_variables = repo_index, repo_name, pkg_id, item_remote
|
||||
bpy.ops.wm.call_panel(name="USERPREF_PT_extensions_bl_pkg_drop_url", keep_open=True)
|
||||
|
||||
|
||||
def extension_drop_filepath_popover(panel, context, url):
|
||||
layout = panel.layout
|
||||
def extension_drop_url_popover_close_as_needed():
|
||||
if USERPREF_PT_extensions_bl_pkg_drop_url.drop_variables is None:
|
||||
return
|
||||
USERPREF_PT_extensions_bl_pkg_drop_url.drop_variables = None
|
||||
from .bl_extension_ops import wm_close_popup_hack
|
||||
wm_close_popup_hack()
|
||||
|
||||
# TODO: this UI isn't so nice, ideally the repo can be selected with an OK button.
|
||||
# This is more complicated than it might seem as calling `bpy.ops.*` doesn't forward errors to the window-manager.
|
||||
# The API may need to be extended to better support this use-case.
|
||||
layout.operator_context = 'EXEC_DEFAULT'
|
||||
layout.label(text="Install from Disk")
|
||||
layout.separator(type='LINE')
|
||||
props = layout.operator_menu_enum("bl_pkg.pkg_install_files", "repo", text="Local Repository")
|
||||
props.filepath = url
|
||||
|
||||
def extension_drop_file_popover(url):
|
||||
from .bl_extension_ops import repo_iter_valid_local_only
|
||||
|
||||
if not list(repo_iter_valid_local_only(bpy.context)):
|
||||
wm_error("Error", "No Local Repositories", icon='ERROR')
|
||||
return
|
||||
|
||||
USERPREF_PT_extensions_bl_pkg_drop_file.drop_variables = url
|
||||
bpy.ops.wm.call_panel(name="USERPREF_PT_extensions_bl_pkg_drop_file", keep_open=True)
|
||||
|
||||
|
||||
def extension_drop_file_popover_close_as_needed():
|
||||
if USERPREF_PT_extensions_bl_pkg_drop_file.drop_variables is None:
|
||||
return
|
||||
USERPREF_PT_extensions_bl_pkg_drop_file.drop_variables = None
|
||||
from .bl_extension_ops import wm_close_popup_hack
|
||||
wm_close_popup_hack()
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@ -794,10 +798,101 @@ def extensions_panel_draw(panel, context):
|
||||
)
|
||||
|
||||
|
||||
class USERPREF_PT_extensions_bl_pkg_drop_url(Panel):
|
||||
bl_label = "Drop URL"
|
||||
|
||||
bl_space_type = 'TOPBAR' # dummy.
|
||||
bl_region_type = 'HEADER'
|
||||
bl_ui_units_x = 13
|
||||
|
||||
# WARNING: workaround for not being able to pass arguments to a popup.
|
||||
drop_variables = None
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
repo_index, repo_name, pkg_id, item_remote = USERPREF_PT_extensions_bl_pkg_drop_url.drop_variables
|
||||
|
||||
layout.label(text="Install Extension")
|
||||
layout.separator(type='LINE')
|
||||
layout.label(text="Do you want to install the following {:s}?".format(item_remote["type"]))
|
||||
|
||||
col = layout.column(align=True)
|
||||
col.label(text="Name: {:s}".format(item_remote["name"]))
|
||||
col.label(text="Repository: {:s}".format(repo_name))
|
||||
col.label(text="Size: {:s}".format(size_as_fmt_string(item_remote["archive_size"], precision=0)))
|
||||
del col
|
||||
|
||||
layout.separator()
|
||||
|
||||
if item_remote["type"] == "add-on":
|
||||
wm = context.window_manager
|
||||
layout.prop(wm, "extension_enable_on_install")
|
||||
enable_on_install = wm.extension_enable_on_install
|
||||
else:
|
||||
enable_on_install = False
|
||||
|
||||
layout.separator()
|
||||
|
||||
row = layout.row()
|
||||
|
||||
row.operator("bl_pkg.popup_cancel", text="Cancel")
|
||||
|
||||
props = row.operator("bl_pkg.pkg_install", text="Install")
|
||||
props.repo_index = repo_index
|
||||
props.pkg_id = pkg_id
|
||||
props.enable_on_install = enable_on_install
|
||||
|
||||
|
||||
class USERPREF_PT_extensions_bl_pkg_drop_file(Panel):
|
||||
bl_label = "Drop File"
|
||||
|
||||
bl_space_type = 'TOPBAR' # dummy.
|
||||
bl_region_type = 'HEADER'
|
||||
bl_ui_units_x = 13
|
||||
|
||||
# WARNING: workaround for not being able to pass arguments to a popup.
|
||||
drop_variables = None
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
url = USERPREF_PT_extensions_bl_pkg_drop_file.drop_variables
|
||||
|
||||
# TODO: this UI isn't so nice, ideally the repo can be selected with an OK button.
|
||||
# This is more complicated than it might seem as calling `bpy.ops.*` doesn't forward errors to the window-manager.
|
||||
# The API may need to be extended to better support this use-case.
|
||||
layout.operator_context = 'EXEC_DEFAULT'
|
||||
layout.label(text="Install from Disk")
|
||||
layout.separator(type='LINE')
|
||||
|
||||
wm = context.window_manager
|
||||
layout.label(text="Local Repository")
|
||||
layout.prop(wm, "extension_local_repos", text="")
|
||||
|
||||
# TODO: inspect the ZIP and find if the type is an add-on.
|
||||
if True:
|
||||
wm = context.window_manager
|
||||
layout.prop(wm, "extension_enable_on_install")
|
||||
enable_on_install = wm.extension_enable_on_install
|
||||
|
||||
row = layout.row()
|
||||
|
||||
row.operator("bl_pkg.popup_cancel", text="Cancel")
|
||||
|
||||
props = row.operator("bl_pkg.pkg_install_files", text="Install")
|
||||
props.repo = wm.extension_local_repos
|
||||
props.filepath = url
|
||||
props.enable_on_install = enable_on_install
|
||||
|
||||
|
||||
classes = (
|
||||
# Pop-overs.
|
||||
USERPREF_PT_extensions_bl_pkg_filter,
|
||||
USERPREF_MT_extensions_bl_pkg_settings,
|
||||
|
||||
USERPREF_PT_extensions_bl_pkg_drop_url,
|
||||
USERPREF_PT_extensions_bl_pkg_drop_file,
|
||||
)
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user