From 3d070fdc6b05a80bfe21dc808ce4a81815c0ee10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Tue, 19 Dec 2023 15:14:19 +0100 Subject: [PATCH] extensions-app: Move classes to their own files Using a single source file is getting unwieldy, so start splitting it up. Part-of: --- po/POTFILES.in | 3 +- subprojects/extensions-app/js/extensionRow.js | 193 +++++++ .../extensions-app/js/extensionsWindow.js | 293 +++++++++++ subprojects/extensions-app/js/main.js | 480 +----------------- .../org.gnome.Extensions.src.gresource.xml.in | 2 + 5 files changed, 491 insertions(+), 480 deletions(-) create mode 100644 subprojects/extensions-app/js/extensionRow.js create mode 100644 subprojects/extensions-app/js/extensionsWindow.js diff --git a/po/POTFILES.in b/po/POTFILES.in index 1d9e56948..f7c05eefc 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -92,7 +92,8 @@ src/shell-util.c src/st/st-icon-theme.c subprojects/extensions-app/data/metainfo/org.gnome.Extensions.metainfo.xml.in subprojects/extensions-app/data/org.gnome.Extensions.desktop.in.in -subprojects/extensions-app/js/main.js +subprojects/extensions-app/js/extensionRow.js +subprojects/extensions-app/js/extensionsWindow.js subprojects/extensions-app/data/ui/extension-row.ui subprojects/extensions-app/data/ui/extensions-window.ui subprojects/extensions-app/data/ui/help-overlay.ui diff --git a/subprojects/extensions-app/js/extensionRow.js b/subprojects/extensions-app/js/extensionRow.js new file mode 100644 index 000000000..23bed79d2 --- /dev/null +++ b/subprojects/extensions-app/js/extensionRow.js @@ -0,0 +1,193 @@ +import Adw from 'gi://Adw?version=1'; +import GLib from 'gi://GLib'; +import Gio from 'gi://Gio'; +import GObject from 'gi://GObject'; + +import {ExtensionState, ExtensionType, deserializeExtension} from './misc/extensionUtils.js'; + +export const ExtensionRow = GObject.registerClass({ + GTypeName: 'ExtensionRow', + Template: 'resource:///org/gnome/Extensions/ui/extension-row.ui', + InternalChildren: [ + 'detailsPopover', + 'descriptionLabel', + 'versionLabel', + 'errorLabel', + 'errorButton', + 'updatesButton', + 'switch', + 'actionsBox', + ], +}, class ExtensionRow extends Adw.ActionRow { + _init(extension) { + super._init(); + + 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, + }); + action.connect('activate', () => { + this._detailsPopover.popdown(); + this.get_root().openPrefs(this.uuid); + }); + this._actionGroup.add_action(action); + + action = new Gio.SimpleAction({ + name: 'show-url', + enabled: this.url !== '', + }); + action.connect('activate', () => { + this._detailsPopover.popdown(); + Gio.AppInfo.launch_default_for_uri( + this.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, + }); + action.connect('activate', () => { + this._detailsPopover.popdown(); + this.get_root().uninstall(this.uuid); + }); + this._actionGroup.add_action(action); + + action = new Gio.SimpleAction({ + name: 'enabled', + state: new GLib.Variant('b', false), + }); + action.connect('activate', () => { + const state = action.get_state(); + action.change_state(new GLib.Variant('b', !state.get_boolean())); + }); + + action.connect('change-state', (a, state) => { + if (state.get_boolean()) + this._app.shellProxy.EnableExtensionAsync(this.uuid).catch(console.error); + else + this._app.shellProxy.DisableExtensionAsync(this.uuid).catch(console.error); + }); + this._actionGroup.add_action(action); + + this.title = this.name; + + const desc = this._extension.metadata.description.split('\n')[0]; + this._descriptionLabel.label = desc; + + 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._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; + } + + _updateState() { + const state = this._extension.state === ExtensionState.ENABLED; + + const action = this._actionGroup.lookup_action('enabled'); + action.set_state(new GLib.Variant('b', state)); + action.enabled = this._extension.canChange; + + if (!action.enabled) + this._switch.active = state; + + this._updatesButton.visible = this.hasUpdate; + this._errorButton.visible = this.hasError; + this._errorLabel.label = this.error; + + this._versionLabel.label = _('Version %s').format(this.version.toString()); + this._versionLabel.visible = this.version !== ''; + } + + _onDestroy() { + if (!this._app.shellProxy) + return; + + if (this._extensionStateChangedId) + this._app.shellProxy.disconnectSignal(this._extensionStateChangedId); + this._extensionStateChangedId = 0; + } +}); + diff --git a/subprojects/extensions-app/js/extensionsWindow.js b/subprojects/extensions-app/js/extensionsWindow.js new file mode 100644 index 000000000..66671a68f --- /dev/null +++ b/subprojects/extensions-app/js/extensionsWindow.js @@ -0,0 +1,293 @@ +import Adw from 'gi://Adw?version=1'; +import GLib from 'gi://GLib'; +import Gio from 'gi://Gio'; +import GObject from 'gi://GObject'; +import Gtk from 'gi://Gtk?version=4.0'; +import Shew from 'gi://Shew'; + +const Package = imports.package; +import * as Gettext from 'gettext'; + +import * as Config from './misc/config.js'; +import {ExtensionRow} from './extensionRow.js'; +import {ExtensionState, ExtensionType, deserializeExtension} from './misc/extensionUtils.js'; + +Gio._promisify(Gio.DBusConnection.prototype, 'call'); +Gio._promisify(Shew.WindowExporter.prototype, 'export'); + +export const ExtensionsWindow = GObject.registerClass({ + GTypeName: 'ExtensionsWindow', + Template: 'resource:///org/gnome/Extensions/ui/extensions-window.ui', + InternalChildren: [ + 'userGroup', + 'userList', + 'systemGroup', + 'systemList', + 'mainStack', + 'searchBar', + 'searchButton', + 'searchEntry', + 'updatesBanner', + ], +}, class ExtensionsWindow extends Adw.ApplicationWindow { + _init(params) { + super._init(params); + + if (Config.PROFILE === 'development') + this.add_css_class('devel'); + + this._updatesCheckId = 0; + + this._exporter = new Shew.WindowExporter({window: this}); + this._exportedHandle = ''; + + this.add_action_entries( + [{ + name: 'show-about', + activate: () => this._showAbout(), + }, { + name: 'logout', + activate: () => this._logout(), + }, { + name: 'user-extensions-enabled', + state: 'false', + change_state: (a, state) => { + this._shellProxy.UserExtensionsEnabled = state.get_boolean(); + }, + }]); + + this._searchTerms = []; + this._searchEntry.connect('search-changed', () => { + const {text} = this._searchEntry; + if (text === '') + this._searchTerms = []; + else + [this._searchTerms] = GLib.str_tokenize_and_fold(text, null); + + this._userList.invalidate_filter(); + this._systemList.invalidate_filter(); + }); + + this._userList.set_sort_func(this._sortList.bind(this)); + this._userList.set_filter_func(this._filterList.bind(this)); + this._userList.set_placeholder(new Gtk.Label({ + label: _('No Matches'), + margin_start: 12, + margin_end: 12, + margin_top: 12, + margin_bottom: 12, + })); + this._userList.connect('row-activated', (_list, row) => row.activate()); + + this._systemList.set_sort_func(this._sortList.bind(this)); + this._systemList.set_filter_func(this._filterList.bind(this)); + this._systemList.set_placeholder(new Gtk.Label({ + label: _('No Matches'), + margin_start: 12, + margin_end: 12, + margin_top: 12, + margin_bottom: 12, + })); + this._systemList.connect('row-activated', (_list, row) => row.activate()); + + this._shellProxy.connectSignal('ExtensionStateChanged', + this._onExtensionStateChanged.bind(this)); + + this._shellProxy.connect('g-properties-changed', + this._onUserExtensionsEnabledChanged.bind(this)); + this._onUserExtensionsEnabledChanged(); + + this._scanExtensions(); + } + + get _shellProxy() { + return this.application.shellProxy; + } + + uninstall(uuid) { + const row = this._findExtensionRow(uuid); + + const dialog = new Gtk.MessageDialog({ + transient_for: this, + modal: true, + text: _('Remove “%s”?').format(row.name), + secondary_text: _('If you remove the extension, you need to return to download it if you want to enable it again'), + }); + + dialog.add_button(_('_Cancel'), Gtk.ResponseType.CANCEL); + dialog.add_button(_('_Remove'), Gtk.ResponseType.ACCEPT) + .get_style_context().add_class('destructive-action'); + + dialog.connect('response', (dlg, response) => { + if (response === Gtk.ResponseType.ACCEPT) + this._shellProxy.UninstallExtensionAsync(uuid).catch(console.error); + dialog.destroy(); + }); + dialog.present(); + } + + async openPrefs(uuid) { + if (!this._exportedHandle) { + try { + this._exportedHandle = await this._exporter.export(); + } catch (e) { + console.warn(`Failed to export window: ${e.message}`); + } + } + + this._shellProxy.OpenExtensionPrefsAsync(uuid, + this._exportedHandle, + {modal: new GLib.Variant('b', true)}).catch(console.error); + } + + _showAbout() { + const aboutWindow = new Adw.AboutWindow({ + developers: [ + 'Florian Müllner ', + 'Jasper St. Pierre ', + 'Didier Roche ', + 'Romain Vigier ', + ], + designers: [ + 'Allan Day ', + 'Tobias Bernard ', + ], + translator_credits: _('translator-credits'), + application_name: _('Extensions'), + license_type: Gtk.License.GPL_2_0, + application_icon: Package.name, + version: Package.version, + developer_name: _('The GNOME Project'), + website: 'https://apps.gnome.org/app/org.gnome.Extensions/', + issue_url: 'https://gitlab.gnome.org/GNOME/gnome-shell/issues/new', + + transient_for: this, + }); + aboutWindow.present(); + } + + _logout() { + this.application.get_dbus_connection().call( + 'org.gnome.SessionManager', + '/org/gnome/SessionManager', + 'org.gnome.SessionManager', + 'Logout', + new GLib.Variant('(u)', [0]), + null, + Gio.DBusCallFlags.NONE, + -1, + null); + } + + _sortList(row1, row2) { + return row1.name.localeCompare(row2.name); + } + + _filterList(row) { + return this._searchTerms.every( + t => row.keywords.some(k => k.startsWith(t))); + } + + _findExtensionRow(uuid) { + return [ + ...this._userList, + ...this._systemList, + ].find(c => c.uuid === uuid); + } + + _onUserExtensionsEnabledChanged() { + const action = this.lookup_action('user-extensions-enabled'); + action.set_state( + new GLib.Variant('b', this._shellProxy.UserExtensionsEnabled)); + } + + _onExtensionStateChanged(proxy, senderName, [uuid, newState]) { + const extension = deserializeExtension(newState); + let row = this._findExtensionRow(uuid); + + this._queueUpdatesCheck(); + + // the extension's type changed; remove the corresponding row + // and reset the variable to null so that we create a new row + // below and add it to the appropriate list + if (row && row.type !== extension.type) { + row.get_parent().remove(row); + row = null; + } + + if (row) { + if (extension.state === ExtensionState.UNINSTALLED) + row.get_parent().remove(row); + } else { + this._addExtensionRow(extension); + } + + this._syncListVisibility(); + } + + async _scanExtensions() { + try { + const [extensionsMap] = await this._shellProxy.ListExtensionsAsync(); + + for (let uuid in extensionsMap) { + const extension = deserializeExtension(extensionsMap[uuid]); + this._addExtensionRow(extension); + } + this._extensionsLoaded(); + } catch (e) { + if (e instanceof Gio.DBusError) { + console.log(`Failed to connect to shell proxy: ${e}`); + this._mainStack.visible_child_name = 'noshell'; + } else { + throw e; + } + } + } + + _addExtensionRow(extension) { + const row = new ExtensionRow(extension); + + if (row.type === ExtensionType.PER_USER) + this._userList.append(row); + else + this._systemList.append(row); + } + + _queueUpdatesCheck() { + if (this._updatesCheckId) + return; + + this._updatesCheckId = GLib.timeout_add_seconds( + GLib.PRIORITY_DEFAULT, 1, () => { + this._checkUpdates(); + + this._updatesCheckId = 0; + return GLib.SOURCE_REMOVE; + }); + } + + _syncListVisibility() { + this._userGroup.visible = [...this._userList].length > 1; + this._systemGroup.visible = [...this._systemList].length > 1; + + if (this._userGroup.visible || this._systemGroup.visible) + this._mainStack.visible_child_name = 'main'; + else + this._mainStack.visible_child_name = 'placeholder'; + } + + _checkUpdates() { + const nUpdates = [...this._userList].filter(c => c.hasUpdate).length; + + this._updatesBanner.title = Gettext.ngettext( + '%d extension will be updated on next login.', + '%d extensions will be updated on next login.', + nUpdates).format(nUpdates); + this._updatesBanner.revealed = nUpdates > 0; + } + + _extensionsLoaded() { + this._syncListVisibility(); + this._checkUpdates(); + } +}); diff --git a/subprojects/extensions-app/js/main.js b/subprojects/extensions-app/js/main.js index 696f29b57..47f0d061a 100644 --- a/subprojects/extensions-app/js/main.js +++ b/subprojects/extensions-app/js/main.js @@ -2,26 +2,17 @@ import Adw from 'gi://Adw?version=1'; import GLib from 'gi://GLib'; import Gio from 'gi://Gio'; import GObject from 'gi://GObject'; -import Gtk from 'gi://Gtk?version=4.0'; -import Shew from 'gi://Shew'; import {setConsoleLogDomain} from 'console'; -import * as Gettext from 'gettext'; const Package = imports.package; Package.initFormat(); -import * as Config from './misc/config.js'; -import * as ExtensionUtils from './misc/extensionUtils.js'; - -import {ExtensionState, ExtensionType} from './misc/extensionUtils.js'; +import {ExtensionsWindow} from './extensionsWindow.js'; const GnomeShellIface = loadInterfaceXML('org.gnome.Shell.Extensions'); const GnomeShellProxy = Gio.DBusProxy.makeProxyWrapper(GnomeShellIface); -Gio._promisify(Gio.DBusConnection.prototype, 'call'); -Gio._promisify(Shew.WindowExporter.prototype, 'export'); - function loadInterfaceXML(iface) { const uri = `resource:///org/gnome/Extensions/dbus-interfaces/${iface}.xml`; const f = Gio.File.new_for_uri(uri); @@ -36,11 +27,6 @@ function loadInterfaceXML(iface) { return null; } -function toggleState(action) { - let state = action.get_state(); - action.change_state(new GLib.Variant('b', !state.get_boolean())); -} - var Application = GObject.registerClass( class Application extends Adw.Application { _init() { @@ -77,470 +63,6 @@ class Application extends Adw.Application { } }); -var ExtensionsWindow = GObject.registerClass({ - GTypeName: 'ExtensionsWindow', - Template: 'resource:///org/gnome/Extensions/ui/extensions-window.ui', - InternalChildren: [ - 'userGroup', - 'userList', - 'systemGroup', - 'systemList', - 'mainStack', - 'searchBar', - 'searchButton', - 'searchEntry', - 'updatesBanner', - ], -}, class ExtensionsWindow extends Adw.ApplicationWindow { - _init(params) { - super._init(params); - - if (Config.PROFILE === 'development') - this.add_css_class('devel'); - - this._updatesCheckId = 0; - - this._exporter = new Shew.WindowExporter({window: this}); - this._exportedHandle = ''; - - this.add_action_entries( - [{ - name: 'show-about', - activate: () => this._showAbout(), - }, { - name: 'logout', - activate: () => this._logout(), - }, { - name: 'user-extensions-enabled', - state: 'false', - change_state: (a, state) => { - this._shellProxy.UserExtensionsEnabled = state.get_boolean(); - }, - }]); - - this._searchTerms = []; - this._searchEntry.connect('search-changed', () => { - const {text} = this._searchEntry; - if (text === '') - this._searchTerms = []; - else - [this._searchTerms] = GLib.str_tokenize_and_fold(text, null); - - this._userList.invalidate_filter(); - this._systemList.invalidate_filter(); - }); - - this._userList.set_sort_func(this._sortList.bind(this)); - this._userList.set_filter_func(this._filterList.bind(this)); - this._userList.set_placeholder(new Gtk.Label({ - label: _('No Matches'), - margin_start: 12, - margin_end: 12, - margin_top: 12, - margin_bottom: 12, - })); - this._userList.connect('row-activated', (_list, row) => row.activate()); - - this._systemList.set_sort_func(this._sortList.bind(this)); - this._systemList.set_filter_func(this._filterList.bind(this)); - this._systemList.set_placeholder(new Gtk.Label({ - label: _('No Matches'), - margin_start: 12, - margin_end: 12, - margin_top: 12, - margin_bottom: 12, - })); - this._systemList.connect('row-activated', (_list, row) => row.activate()); - - this._shellProxy.connectSignal('ExtensionStateChanged', - this._onExtensionStateChanged.bind(this)); - - this._shellProxy.connect('g-properties-changed', - this._onUserExtensionsEnabledChanged.bind(this)); - this._onUserExtensionsEnabledChanged(); - - this._scanExtensions(); - } - - get _shellProxy() { - return this.application.shellProxy; - } - - uninstall(uuid) { - let row = this._findExtensionRow(uuid); - - let dialog = new Gtk.MessageDialog({ - transient_for: this, - modal: true, - text: _('Remove “%s”?').format(row.name), - secondary_text: _('If you remove the extension, you need to return to download it if you want to enable it again'), - }); - - dialog.add_button(_('_Cancel'), Gtk.ResponseType.CANCEL); - dialog.add_button(_('_Remove'), Gtk.ResponseType.ACCEPT) - .get_style_context().add_class('destructive-action'); - - dialog.connect('response', (dlg, response) => { - if (response === Gtk.ResponseType.ACCEPT) - this._shellProxy.UninstallExtensionAsync(uuid).catch(console.error); - dialog.destroy(); - }); - dialog.present(); - } - - async openPrefs(uuid) { - if (!this._exportedHandle) { - try { - this._exportedHandle = await this._exporter.export(); - } catch (e) { - console.warn(`Failed to export window: ${e.message}`); - } - } - - this._shellProxy.OpenExtensionPrefsAsync(uuid, - this._exportedHandle, - {modal: new GLib.Variant('b', true)}).catch(console.error); - } - - _showAbout() { - let aboutWindow = new Adw.AboutWindow({ - developers: [ - 'Florian Müllner ', - 'Jasper St. Pierre ', - 'Didier Roche ', - 'Romain Vigier ', - ], - designers: [ - 'Allan Day ', - 'Tobias Bernard ', - ], - translator_credits: _('translator-credits'), - application_name: _('Extensions'), - license_type: Gtk.License.GPL_2_0, - application_icon: Package.name, - version: Package.version, - developer_name: _('The GNOME Project'), - website: 'https://apps.gnome.org/app/org.gnome.Extensions/', - issue_url: 'https://gitlab.gnome.org/GNOME/gnome-shell/issues/new', - - transient_for: this, - }); - aboutWindow.present(); - } - - _logout() { - this.application.get_dbus_connection().call( - 'org.gnome.SessionManager', - '/org/gnome/SessionManager', - 'org.gnome.SessionManager', - 'Logout', - new GLib.Variant('(u)', [0]), - null, - Gio.DBusCallFlags.NONE, - -1, - null); - } - - _sortList(row1, row2) { - return row1.name.localeCompare(row2.name); - } - - _filterList(row) { - return this._searchTerms.every( - t => row.keywords.some(k => k.startsWith(t))); - } - - _findExtensionRow(uuid) { - return [ - ...this._userList, - ...this._systemList, - ].find(c => c.uuid === uuid); - } - - _onUserExtensionsEnabledChanged() { - let action = this.lookup_action('user-extensions-enabled'); - action.set_state( - new GLib.Variant('b', this._shellProxy.UserExtensionsEnabled)); - } - - _onExtensionStateChanged(proxy, senderName, [uuid, newState]) { - let extension = ExtensionUtils.deserializeExtension(newState); - let row = this._findExtensionRow(uuid); - - this._queueUpdatesCheck(); - - // the extension's type changed; remove the corresponding row - // and reset the variable to null so that we create a new row - // below and add it to the appropriate list - if (row && row.type !== extension.type) { - row.get_parent().remove(row); - row = null; - } - - if (row) { - if (extension.state === ExtensionState.UNINSTALLED) - row.get_parent().remove(row); - } else { - this._addExtensionRow(extension); - } - - this._syncListVisibility(); - } - - async _scanExtensions() { - try { - const [extensionsMap] = await this._shellProxy.ListExtensionsAsync(); - - for (let uuid in extensionsMap) { - let extension = ExtensionUtils.deserializeExtension(extensionsMap[uuid]); - this._addExtensionRow(extension); - } - this._extensionsLoaded(); - } catch (e) { - if (e instanceof Gio.DBusError) { - console.log(`Failed to connect to shell proxy: ${e}`); - this._mainStack.visible_child_name = 'noshell'; - } else { - throw e; - } - } - } - - _addExtensionRow(extension) { - let row = new ExtensionRow(extension); - - if (row.type === ExtensionType.PER_USER) - this._userList.append(row); - else - this._systemList.append(row); - } - - _queueUpdatesCheck() { - if (this._updatesCheckId) - return; - - this._updatesCheckId = GLib.timeout_add_seconds( - GLib.PRIORITY_DEFAULT, 1, () => { - this._checkUpdates(); - - this._updatesCheckId = 0; - return GLib.SOURCE_REMOVE; - }); - } - - _syncListVisibility() { - this._userGroup.visible = [...this._userList].length > 1; - this._systemGroup.visible = [...this._systemList].length > 1; - - if (this._userGroup.visible || this._systemGroup.visible) - this._mainStack.visible_child_name = 'main'; - else - this._mainStack.visible_child_name = 'placeholder'; - } - - _checkUpdates() { - let nUpdates = [...this._userList].filter(c => c.hasUpdate).length; - - this._updatesBanner.title = Gettext.ngettext( - '%d extension will be updated on next login.', - '%d extensions will be updated on next login.', - nUpdates).format(nUpdates); - this._updatesBanner.revealed = nUpdates > 0; - } - - _extensionsLoaded() { - this._syncListVisibility(); - this._checkUpdates(); - } -}); - -var ExtensionRow = GObject.registerClass({ - GTypeName: 'ExtensionRow', - Template: 'resource:///org/gnome/Extensions/ui/extension-row.ui', - InternalChildren: [ - 'detailsPopover', - 'descriptionLabel', - 'versionLabel', - 'errorLabel', - 'errorButton', - 'updatesButton', - 'switch', - 'actionsBox', - ], -}, class ExtensionRow extends Adw.ActionRow { - _init(extension) { - super._init(); - - this._app = Gio.Application.get_default(); - this._extension = extension; - this._prefsModule = null; - - [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, - }); - action.connect('activate', () => { - this._detailsPopover.popdown(); - this.get_root().openPrefs(this.uuid); - }); - this._actionGroup.add_action(action); - - action = new Gio.SimpleAction({ - name: 'show-url', - enabled: this.url !== '', - }); - action.connect('activate', () => { - this._detailsPopover.popdown(); - Gio.AppInfo.launch_default_for_uri( - this.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, - }); - action.connect('activate', () => { - this._detailsPopover.popdown(); - this.get_root().uninstall(this.uuid); - }); - this._actionGroup.add_action(action); - - action = new Gio.SimpleAction({ - name: 'enabled', - state: new GLib.Variant('b', false), - }); - action.connect('activate', toggleState); - action.connect('change-state', (a, state) => { - if (state.get_boolean()) - this._app.shellProxy.EnableExtensionAsync(this.uuid).catch(console.error); - else - this._app.shellProxy.DisableExtensionAsync(this.uuid).catch(console.error); - }); - this._actionGroup.add_action(action); - - this.title = this.name; - - const desc = this._extension.metadata.description.split('\n')[0]; - this._descriptionLabel.label = desc; - - 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 = ExtensionUtils.deserializeExtension(newState); - 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; - } - - _updateState() { - let state = this._extension.state === ExtensionState.ENABLED; - - let action = this._actionGroup.lookup('enabled'); - action.set_state(new GLib.Variant('b', state)); - action.enabled = this._canToggle(); - - if (!action.enabled) - this._switch.active = state; - - this._updatesButton.visible = this.hasUpdate; - this._errorButton.visible = this.hasError; - this._errorLabel.label = this.error; - - this._versionLabel.label = _('Version %s').format(this.version.toString()); - this._versionLabel.visible = this.version !== ''; - } - - _onDestroy() { - if (!this._app.shellProxy) - return; - - if (this._extensionStateChangedId) - this._app.shellProxy.disconnectSignal(this._extensionStateChangedId); - this._extensionStateChangedId = 0; - } - - _canToggle() { - return this._extension.canChange; - } -}); - /** * Main entrypoint for the app * diff --git a/subprojects/extensions-app/js/org.gnome.Extensions.src.gresource.xml.in b/subprojects/extensions-app/js/org.gnome.Extensions.src.gresource.xml.in index 330ede100..eb7759aec 100644 --- a/subprojects/extensions-app/js/org.gnome.Extensions.src.gresource.xml.in +++ b/subprojects/extensions-app/js/org.gnome.Extensions.src.gresource.xml.in @@ -1,6 +1,8 @@ + extensionRow.js + extensionsWindow.js main.js misc/config.js