gnome-shell/js/dbusServices/extensions/extensionPrefsDialog.js
Leleat 0f30bfdd53 dbusServices/extensions: Await prefs.fillPreferencesWindow
There are cases where it makes sense to have fillPreferencesWindow be
async, so await it. One such case is when using .ui files from resources
with (GTK) templates. When extension developers load the resource in
`fillPreferencesWindow` and then import the js file that registers the
classes, there will be an error at the moment that there is no UI
provided. Similarly, make getPreferencesWidget async optionally.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/3348>
2024-05-28 18:43:53 +00:00

164 lines
4.9 KiB
JavaScript

// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
import Adw from 'gi://Adw?version=1';
import Gdk from 'gi://Gdk?version=4.0';
import Gio from 'gi://Gio';
import GLib from 'gi://GLib';
import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk?version=4.0';
import {formatError} from './misc/errorUtils.js';
export const ExtensionPrefsDialog = GObject.registerClass({
GTypeName: 'ExtensionPrefsDialog',
Signals: {
'loaded': {},
},
}, class ExtensionPrefsDialog extends Adw.PreferencesWindow {
_init(extension) {
super._init({
title: extension.metadata.name,
search_enabled: false,
});
this._extension = extension;
this._loadPrefs().catch(e => {
this._showErrorPage(e);
logError(e, 'Failed to open preferences');
}).finally(() => this.emit('loaded'));
}
async _loadPrefs() {
const {dir, path, metadata} = this._extension;
const prefsJs = dir.get_child('prefs.js');
const prefsModule = await import(prefsJs.get_uri());
const prefsObj = new prefsModule.default({...metadata, dir, path});
this._extension.stateObj = prefsObj;
await prefsObj.fillPreferencesWindow(this);
if (!this.visible_page)
throw new Error('Extension did not provide any UI');
}
set titlebar(w) {
this.set_titlebar(w);
}
// eslint-disable-next-line camelcase
set_titlebar() {
// intercept fatal libadwaita error, show error page instead
GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
this._showErrorPage(
new Error('set_titlebar() is not supported for Adw.Window'));
return GLib.SOURCE_REMOVE;
});
}
destroy() {
this._showErrorPage(
new Error('destroy() breaks tracking open dialogs, use close() if you must'));
}
_showErrorPage(e) {
while (this.visible_page)
this.remove(this.visible_page);
this.add(new ExtensionPrefsErrorPage(this._extension, e));
}
});
const ExtensionPrefsErrorPage = GObject.registerClass({
GTypeName: 'ExtensionPrefsErrorPage',
Template: 'resource:///org/gnome/Shell/Extensions/ui/extension-error-page.ui',
InternalChildren: [
'expander',
'expanderArrow',
'revealer',
'errorView',
],
}, class ExtensionPrefsErrorPage extends Adw.PreferencesPage {
static _classInit(klass) {
super._classInit(klass);
klass.install_action('page.copy-error',
null,
self => {
const clipboard = self.get_display().get_clipboard();
clipboard.set(self._errorMarkdown);
});
klass.install_action('page.show-url',
null,
self => Gtk.show_uri(self.get_root(), self._url, Gdk.CURRENT_TIME));
return klass;
}
_init(extension, error) {
super._init();
this._addCustomStylesheet();
this._uuid = extension.uuid;
this._url = extension.metadata.url || '';
this.action_set_enabled('page.show-url', this._url !== '');
this._gesture = new Gtk.GestureClick({
button: 0,
exclusive: true,
});
this._expander.add_controller(this._gesture);
this._gesture.connect('released', (gesture, nPress) => {
if (nPress === 1)
this._revealer.reveal_child = !this._revealer.reveal_child;
});
this._revealer.connect('notify::reveal-child', () => {
this._expanderArrow.icon_name = this._revealer.reveal_child
? 'pan-down-symbolic'
: 'pan-end-symbolic';
this._syncExpandedStyle();
});
this._revealer.connect('notify::child-revealed',
() => this._syncExpandedStyle());
const formattedError = formatError(error);
this._errorView.buffer.text = formattedError;
// markdown for pasting in gitlab issues
let lines = [
`The settings of extension ${this._uuid} had an error:`,
'```',
formattedError.replace(/\n$/, ''), // remove trailing newline
'```',
'',
];
this._errorMarkdown = lines.join('\n');
}
_syncExpandedStyle() {
if (this._revealer.reveal_child)
this._expander.add_css_class('expanded');
else if (!this._revealer.child_revealed)
this._expander.remove_css_class('expanded');
}
_addCustomStylesheet() {
let provider = new Gtk.CssProvider();
let uri = 'resource:///org/gnome/Shell/Extensions/css/application.css';
try {
provider.load_from_file(Gio.File.new_for_uri(uri));
} catch (e) {
logError(e, 'Failed to add application style');
}
Gtk.StyleContext.add_provider_for_display(Gdk.Display.get_default(),
provider,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
}
});