extensions-app: Split out ExtensionManager

The extension handling is currently intertwined with the UI.

Splitting it out provides a clearer separation, and will allow
us to switch to a model-based UI later.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/3067>
This commit is contained in:
Florian Müllner
2023-12-18 20:20:26 +01:00
committed by Marge Bot
parent 3d070fdc6b
commit f7ded3e509
6 changed files with 384 additions and 206 deletions

View File

@ -3,7 +3,7 @@ import GLib from 'gi://GLib';
import Gio from 'gi://Gio';
import GObject from 'gi://GObject';
import {ExtensionState, ExtensionType, deserializeExtension} from './misc/extensionUtils.js';
import {ExtensionState} from './misc/extensionUtils.js';
export const ExtensionRow = GObject.registerClass({
GTypeName: 'ExtensionRow',
@ -25,40 +25,38 @@ export const ExtensionRow = GObject.registerClass({
this._app = Gio.Application.get_default();
this._extension = extension;
[this._keywords] = GLib.str_tokenize_and_fold(this.name, null);
this._actionGroup = new Gio.SimpleActionGroup();
this.insert_action_group('row', this._actionGroup);
let action;
action = new Gio.SimpleAction({
name: 'show-prefs',
enabled: this.hasPrefs,
enabled: extension.hasPrefs,
});
action.connect('activate', () => {
this._detailsPopover.popdown();
this.get_root().openPrefs(this.uuid);
this.get_root().openPrefs(extension);
});
this._actionGroup.add_action(action);
action = new Gio.SimpleAction({
name: 'show-url',
enabled: this.url !== '',
enabled: extension.url !== '',
});
action.connect('activate', () => {
this._detailsPopover.popdown();
Gio.AppInfo.launch_default_for_uri(
this.url, this.get_display().get_app_launch_context());
extension.url, this.get_display().get_app_launch_context());
});
this._actionGroup.add_action(action);
action = new Gio.SimpleAction({
name: 'uninstall',
enabled: this.type === ExtensionType.PER_USER,
enabled: extension.isUser,
});
action.connect('activate', () => {
this._detailsPopover.popdown();
this.get_root().uninstall(this.uuid);
this.get_root().uninstall(extension);
});
this._actionGroup.add_action(action);
@ -70,97 +68,28 @@ export const ExtensionRow = GObject.registerClass({
const state = action.get_state();
action.change_state(new GLib.Variant('b', !state.get_boolean()));
});
action.connect('change-state', (a, state) => {
const {uuid} = this._extension;
if (state.get_boolean())
this._app.shellProxy.EnableExtensionAsync(this.uuid).catch(console.error);
this._app.extensionManager.enableExtension(uuid);
else
this._app.shellProxy.DisableExtensionAsync(this.uuid).catch(console.error);
this._app.extensionManager.disableExtension(uuid);
});
this._actionGroup.add_action(action);
this.title = this.name;
this.title = extension.name;
const desc = this._extension.metadata.description.split('\n')[0];
this._descriptionLabel.label = desc;
this._descriptionLabel.label = extension.description;
this.connect('destroy', this._onDestroy.bind(this));
this._extensionStateChangedId = this._app.shellProxy.connectSignal(
'ExtensionStateChanged', (p, sender, [uuid, newState]) => {
if (this.uuid !== uuid)
return;
this._extension = deserializeExtension(newState);
this._updateState();
});
this._extensionStateChangedId = this._app.extensionManager.connect(
`extension-changed::${extension.uuid}`, () => this._updateState());
this._updateState();
}
get uuid() {
return this._extension.uuid;
}
get name() {
return this._extension.metadata.name;
}
get hasPrefs() {
return this._extension.hasPrefs;
}
get hasUpdate() {
return this._extension.hasUpdate || false;
}
get hasError() {
const {state} = this._extension;
return state === ExtensionState.OUT_OF_DATE ||
state === ExtensionState.ERROR;
}
get type() {
return this._extension.type;
}
get creator() {
return this._extension.metadata.creator || '';
}
get url() {
return this._extension.metadata.url || '';
}
get version() {
return this._extension.metadata['version-name'] || this._extension.metadata.version || '';
}
get error() {
if (!this.hasError)
return '';
if (this._extension.state === ExtensionState.OUT_OF_DATE) {
const {ShellVersion: shellVersion} = this._app.shellProxy;
return this.version !== ''
? _('The installed version of this extension (%s) is incompatible with the current version of GNOME (%s). The extension has been disabled.').format(this.version, shellVersion)
: _('The installed version of this extension is incompatible with the current version of GNOME (%s). The extension has been disabled.').format(shellVersion);
}
const message = [
_('An error has occurred in this extension. This could cause issues elsewhere in the system. It is recommended to turn the extension off until the error is resolved.'),
];
if (this._extension.error) {
message.push(
// translators: Details for an extension error
_('Error details:'), this._extension.error);
}
return message.join('\n\n');
}
get keywords() {
return this._keywords;
get extension() {
return this._extension;
}
_updateState() {
@ -173,21 +102,17 @@ export const ExtensionRow = GObject.registerClass({
if (!action.enabled)
this._switch.active = state;
this._updatesButton.visible = this.hasUpdate;
this._errorButton.visible = this.hasError;
this._errorLabel.label = this.error;
this._updatesButton.visible = this._extension.hasUpdate;
this._errorButton.visible = this._extension.hasError;
this._errorLabel.label = this._extension.error;
this._versionLabel.label = _('Version %s').format(this.version.toString());
this._versionLabel.visible = this.version !== '';
this._versionLabel.label = _('Version %s').format(this._extension.version);
this._versionLabel.visible = this._extension.version !== '';
}
_onDestroy() {
if (!this._app.shellProxy)
return;
if (this._extensionStateChangedId)
this._app.shellProxy.disconnectSignal(this._extensionStateChangedId);
this._extensionStateChangedId = 0;
this._app.extensionManager.disconnect(this._extensionStateChangedId);
delete this._extensionStateChangedId;
}
});