fix(plugins): pass pluginApi as initial property to prevent binding warnings. Eliminate the need for fallback chains in plugins code.

hot reload
This commit is contained in:
Lemmy
2026-03-28 16:58:55 -04:00
parent ba424526b5
commit decb65ae95
5 changed files with 96 additions and 46 deletions
+33 -16
View File
@@ -61,24 +61,52 @@ Item {
enabled: BarWidgetRegistry.isPluginWidget(root.widgetId)
function onPluginWidgetRegistryUpdated() {
// Force the loader to reload by toggling active
if (BarWidgetRegistry.hasWidget(root.widgetId)) {
root.reloadCounter++;
// Plugin widgets use setSource, so also trigger reload directly
if (root._isPlugin && loader.active)
root._loadPluginWidget();
Logger.d("BarWidgetLoader", "Plugin widget registry updated, reloading:", root.widgetId);
}
}
}
readonly property bool _isPlugin: BarWidgetRegistry.isPluginWidget(widgetId)
function _loadPluginWidget() {
var comp = BarWidgetRegistry.getWidget(root.widgetId);
if (!comp)
return;
var pluginId = root.widgetId.replace("plugin:", "");
var api = PluginService.getPluginAPI(pluginId);
loader.setSource(comp.url, api ? {
"pluginApi": api
} : {});
}
Loader {
id: loader
anchors.fill: parent
asynchronous: true
active: root.checkWidgetExists() && (root.reloadCounter >= 0)
sourceComponent: {
// Depend on reloadCounter to force re-fetch of component
var _ = root.reloadCounter;
return root.checkWidgetExists() ? BarWidgetRegistry.getWidget(root.widgetId) : null;
// Core widgets use sourceComponent; plugin widgets use setSource()
// so pluginApi is available from the first binding evaluation.
// Binding is set imperatively to avoid sourceComponent interfering with setSource.
Component.onCompleted: {
if (root._isPlugin) {
root._loadPluginWidget();
} else {
sourceComponent = Qt.binding(function () {
var _ = root.reloadCounter;
return root.checkWidgetExists() ? BarWidgetRegistry.getWidget(root.widgetId) : null;
});
}
}
onActiveChanged: {
if (active && root._isPlugin)
root._loadPluginWidget();
}
// Unregister when the loaded item is destroyed (Loader deactivated or sourceComponent changed)
@@ -119,17 +147,6 @@ Item {
item.screen = widgetScreen;
}
// Inject plugin API for plugin widgets
// The API is fully populated (settings/translations already loaded) by PluginService
if (BarWidgetRegistry.isPluginWidget(widgetId)) {
var pluginId = widgetId.replace("plugin:", "");
var api = PluginService.getPluginAPI(pluginId);
if (api && item.hasOwnProperty("pluginApi")) {
item.pluginApi = api;
Logger.d("BarWidgetLoader", "Injected plugin API for", widgetId);
}
}
// Unregister any previous registration before registering the new instance
root._unregister();
+30 -14
View File
@@ -288,12 +288,37 @@ Variants {
required property var modelData
required property int index
property var _maskRegion: null
readonly property bool _isPlugin: DesktopWidgetRegistry.isPluginWidget(modelData.id)
sourceComponent: {
// Access registeredWidgets and pluginReloadCounter to create reactive binding
var _ = root.pluginReloadCounter;
var widgets = root.registeredWidgets;
return widgets[modelData.id] || null;
// Core widgets use sourceComponent; plugin widgets use setSource()
// so pluginApi is available from the first binding evaluation.
// Binding is set imperatively to avoid sourceComponent interfering with setSource.
Component.onCompleted: {
if (_isPlugin) {
_loadPluginWidget();
} else {
sourceComponent = Qt.binding(function () {
var _ = root.pluginReloadCounter;
var widgets = root.registeredWidgets;
return widgets[modelData.id] || null;
});
}
}
onActiveChanged: {
if (active && _isPlugin)
_loadPluginWidget();
}
function _loadPluginWidget() {
var comp = root.registeredWidgets[modelData.id];
if (!comp)
return;
var pluginId = modelData.id.replace("plugin:", "");
var api = PluginService.getPluginAPI(pluginId);
setSource(comp.url, api ? {
"pluginApi": api
} : {});
}
onLoaded: {
@@ -309,15 +334,6 @@ Variants {
var newRegions = window._maskRegions.slice();
newRegions.push(_maskRegion);
window._maskRegions = newRegions;
// Inject plugin API for plugin widgets
if (DesktopWidgetRegistry.isPluginWidget(modelData.id)) {
var pluginId = modelData.id.replace("plugin:", "");
var api = PluginService.getPluginAPI(pluginId);
if (api && item.hasOwnProperty("pluginApi")) {
item.pluginApi = api;
}
}
}
}
@@ -22,11 +22,35 @@ Item {
return (item && item.visible) ? item[prop] : 0;
}
readonly property bool _isPlugin: ControlCenterWidgetRegistry.isPluginWidget(widgetId)
function _loadPluginWidget() {
var comp = ControlCenterWidgetRegistry.getWidget(widgetId);
if (!comp)
return;
var pluginId = widgetId.substring(7); // Remove "plugin:" prefix
var api = PluginService.getPluginAPI(pluginId);
loader.setSource(comp.url, api ? {
"pluginApi": api
} : {});
}
Loader {
id: loader
anchors.fill: parent
asynchronous: false
sourceComponent: ControlCenterWidgetRegistry.getWidget(widgetId)
// Core widgets use sourceComponent; plugin widgets use setSource()
// so pluginApi is available from the first binding evaluation.
Component.onCompleted: {
if (root._isPlugin) {
root._loadPluginWidget();
} else {
sourceComponent = Qt.binding(function () {
return ControlCenterWidgetRegistry.getWidget(widgetId);
});
}
}
onLoaded: {
if (!item)
@@ -44,12 +68,6 @@ Item {
item.screen = widgetScreen;
}
// Pass pluginApi for plugin widgets
if (ControlCenterWidgetRegistry.isPluginWidget(widgetId) && item.hasOwnProperty("pluginApi")) {
var pluginId = widgetId.substring(7); // Remove "plugin:" prefix
item.pluginApi = PluginService.getPluginAPI(pluginId);
}
// Call custom onLoaded if it exists
if (item.hasOwnProperty("onLoaded")) {
item.onLoaded();
+5 -7
View File
@@ -164,16 +164,14 @@ SmartPanel {
// Get plugin API
var api = PluginService.getPluginAPI(pluginId);
// Activate loader and set component simultaneously
// Use setSource with initial properties so pluginApi is available
// from the first binding evaluation (before onLoaded)
root.contentLoader.active = true;
root.contentLoader.sourceComponent = component;
root.contentLoader.setSource(component.url, api ? {
"pluginApi": api
} : {});
// Immediately inject API (before any bindings evaluate)
if (root.contentLoader.item) {
if (root.contentLoader.item.hasOwnProperty("pluginApi")) {
root.contentLoader.item.pluginApi = api;
}
root.pluginInstance = root.contentLoader.item;
root.currentPluginId = pluginId;
+3 -2
View File
@@ -1024,8 +1024,9 @@ Singleton {
// Set current language (can't use binding in Qt.createQmlObject string)
api.currentLanguage = I18n.langCode;
// Set pre-loaded settings and translations (available immediately!)
api.pluginSettings = settings || {};
// Merge manifest defaults with loaded settings (user settings take priority)
var defaults = (manifest.metadata && manifest.metadata.defaultSettings) || {};
api.pluginSettings = Object.assign({}, defaults, settings || {});
api.pluginTranslations = translations || {};
api.pluginFallbackTranslations = fallbackTranslations || {};