From 386b000be000abe845bdec426c3adac6195e2e50 Mon Sep 17 00:00:00 2001 From: Florian Müllner Date: Feb 09 2022 22:36:11 +0000 Subject: [PATCH 1/3] cleanup: Document public functions gjs now enforces this in its eslint configuration. Adding type information generally is a good idea, so add appropriate comments to public functions before picking up that configuration change. --- diff --git a/extension.js b/extension.js index d1bf35b..1f34b2e 100644 --- a/extension.js +++ b/extension.js @@ -275,6 +275,7 @@ class Extension { } } +/** */ function init() { return new Extension(); } diff --git a/prefs.js b/prefs.js index a2ce27a..99b862e 100644 --- a/prefs.js +++ b/prefs.js @@ -229,9 +229,11 @@ class BackgroundLogoPrefsWidget extends Gtk.Grid { } }); +/** */ function init() { } +/** */ function buildPrefsWidget() { return new BackgroundLogoPrefsWidget(); } From 2fcaa8a9d06868d1c7a555fa16d1c373155c5e02 Mon Sep 17 00:00:00 2001 From: Florian Müllner Date: Feb 09 2022 22:36:11 +0000 Subject: [PATCH 2/3] lint: Sync configuration with gjs/shell --- diff --git a/lint/eslintrc-gjs.yml b/lint/eslintrc-gjs.yml index 53d5fbd..62fd4a2 100644 --- a/lint/eslintrc-gjs.yml +++ b/lint/eslintrc-gjs.yml @@ -1,7 +1,11 @@ --- +# SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +# SPDX-FileCopyrightText: 2018 Claudio André env: - es6: true + es2021: true extends: 'eslint:recommended' +plugins: + - jsdoc rules: array-bracket-newline: - error @@ -24,7 +28,9 @@ rules: # allow: [^vfunc_, ^on_, _instance_init] comma-dangle: - error - - always-multiline + - arrays: always-multiline + objects: always-multiline + functions: never comma-spacing: - error - before: false @@ -57,6 +63,17 @@ rules: - 'CallExpression[callee.object.name=GObject][callee.property.name=registerClass] > ClassExpression:first-child' # Allow dedenting chained member expressions MemberExpression: 'off' + jsdoc/check-alignment: error + jsdoc/check-param-names: error + jsdoc/check-tag-names: error + jsdoc/check-types: error + jsdoc/implements-on-classes: error + jsdoc/newline-after-description: error + jsdoc/require-jsdoc: error + jsdoc/require-param: error + jsdoc/require-param-description: error + jsdoc/require-param-name: error + jsdoc/require-param-type: error key-spacing: - error - beforeColon: false @@ -68,7 +85,10 @@ rules: linebreak-style: - error - unix - lines-between-class-members: error + lines-between-class-members: + - error + - always + - exceptAfterSingleLine: true max-nested-callbacks: error max-statements-per-line: error new-parens: error @@ -87,6 +107,7 @@ rules: - error - all - conditionalAssign: false + nestedBinaryExpressions: false returnAssign: false no-implicit-coercion: - error @@ -103,17 +124,18 @@ rules: no-octal-escape: error no-proto: error no-prototype-builtins: 'off' + no-restricted-globals: [error, window] no-restricted-properties: - error - object: Lang + property: copyProperties + message: Use Object.assign() + - object: Lang property: bind message: Use arrow notation or Function.prototype.bind() - object: Lang property: Class message: Use ES6 classes - - object: imports - property: mainloop - message: Use GLib main loops and timeouts no-restricted-syntax: - error - selector: >- @@ -129,6 +151,8 @@ rules: BlockStatement[body.length=1] CallExpression[arguments.length=0][callee.object.type="Super"][callee.property.name="_init"] message: _init() that only calls super._init() is unnecessary + - selector: BinaryExpression[operator="instanceof"][right.name="Array"] + message: Use Array.isArray() no-return-assign: error no-return-await: error no-self-compare: error @@ -161,10 +185,14 @@ rules: object-curly-newline: - error - consistent: true + multiline: true object-curly-spacing: error object-shorthand: error operator-assignment: error operator-linebreak: error + padded-blocks: + - error + - never # These may be a bit controversial, we can try them out and enable them later # prefer-const: error # prefer-destructuring: error @@ -205,18 +233,19 @@ rules: template-curly-spacing: error template-tag-spacing: error unicode-bom: error - valid-jsdoc: - - error - - requireReturn: false wrap-iife: - error - inside yield-star-spacing: error yoda: error +settings: + jsdoc: + mode: typescript globals: ARGV: readonly Debugger: readonly GIRepositoryGType: readonly + globalThis: readonly imports: readonly Intl: readonly log: readonly @@ -224,5 +253,12 @@ globals: print: readonly printerr: readonly window: readonly + TextEncoder: readonly + TextDecoder: readonly + console: readonly + setTimeout: readonly + setInterval: readonly + clearTimeout: readonly + clearInterval: readonly parserOptions: - ecmaVersion: 2017 + ecmaVersion: 2022 diff --git a/lint/eslintrc-shell.yml b/lint/eslintrc-shell.yml index 2291532..beb003e 100644 --- a/lint/eslintrc-shell.yml +++ b/lint/eslintrc-shell.yml @@ -3,6 +3,12 @@ rules: - error - properties: never allow: [^vfunc_, ^on_] + consistent-return: error + key-spacing: + - error + - mode: minimum + beforeColon: false + afterColon: true object-curly-spacing: - error - always From 11237e98c874408e147402a2d8d1a11579821e11 Mon Sep 17 00:00:00 2001 From: Florian Müllner Date: Feb 12 2022 02:42:36 +0000 Subject: [PATCH 3/3] prefs: Overhaul preference dialog There is a big push to adopt libadwaita in GNOME 42, including for extensions preferences. So go with the times and give the prefs dialog a bit of a face lift. --- diff --git a/prefs.js b/prefs.js index 99b862e..39e1f96 100644 --- a/prefs.js +++ b/prefs.js @@ -1,5 +1,5 @@ /* exported init, buildPrefsWidget */ -const { Gdk, GdkPixbuf, Gio, GLib, GObject, Gtk } = imports.gi; +const { Adw, Gdk, GdkPixbuf, Gio, GLib, GObject, Gtk } = imports.gi; const ByteArray = imports.byteArray; let GnomeDesktop = null; @@ -15,21 +15,13 @@ const BACKGROUND_SCHEMA = 'org.gnome.desktop.background'; const PREVIEW_WIDTH = 400; -let BackgroundLogoPrefsWidget = GObject.registerClass( -class BackgroundLogoPrefsWidget extends Gtk.Grid { - _init() { - super._init({ - halign: Gtk.Align.CENTER, - margin_top: 24, - margin_bottom: 24, - margin_start: 24, - margin_end: 24, - column_spacing: 12, - row_spacing: 6, - }); +const PreviewGroup = GObject.registerClass( +class PreviewGroup extends Adw.PreferencesGroup { + _init(settings) { + super._init(); - this._settings = ExtensionUtils.getSettings(); - this._settings.connect('changed', (settings, key) => { + this._settings = settings; + this._settings.connect('changed', (s, key) => { if (key === 'logo-file' || key === 'logo-size') this._logo = null; @@ -38,105 +30,16 @@ class BackgroundLogoPrefsWidget extends Gtk.Grid { this._preview = new Gtk.DrawingArea({ halign: Gtk.Align.CENTER, - margin_bottom: 18, + margin_bottom: 12, + margin_top: 12, width_request: PREVIEW_WIDTH, height_request: PREVIEW_WIDTH * 9 / 16, }); - this._preview.set_draw_func(this._drawPreview.bind(this)); - this.attach(this._preview, 0, 0, 2, 1); - - const filter = new Gtk.FileFilter(); - filter.add_pixbuf_formats(); - - this._fileChooser = new Gtk.FileChooserNative({ - title: 'Select an Image', - filter, - modal: true, - }); - this._fileChooser.connect('response', (dlg, response) => { - if (response !== Gtk.ResponseType.ACCEPT) - return; - this._settings.set_string('logo-file', dlg.get_file().get_path()); - }); - - this._logoPicker = new Gtk.Button({ - label: '(None)', - }); - this._logoPicker.connect('clicked', () => { - this._fileChooser.transient_for = this.get_root(); - this._fileChooser.show(); - }); - this._settings.connect('changed::logo-file', - this._updateLogoPicker.bind(this)); - this._updateLogoPicker(); - this._addRow(1, 'Logo image', this._logoPicker); - - let comboBox = new Gtk.ComboBoxText(); - comboBox.append('center', 'Center'); - comboBox.append('bottom-left', 'Bottom left'); - comboBox.append('bottom-center', 'Bottom center'); - comboBox.append('bottom-right', 'Bottom right'); - this._settings.bind('logo-position', - comboBox, 'active-id', - Gio.SettingsBindFlags.DEFAULT); - this._addRow(2, 'Position', comboBox); - - let adjustment = this._createAdjustment('logo-size', 0.25); - let scale = new Gtk.Scale({ adjustment, draw_value: false }); - this._addRow(3, 'Size', scale); - - adjustment = this._createAdjustment('logo-border', 1.0); - scale = new Gtk.Scale({ adjustment, draw_value: false }); - this._addRow(4, 'Border', scale); - - adjustment = this._createAdjustment('logo-opacity', 1.0); - scale = new Gtk.Scale({ adjustment, draw_value: false }); - this._addRow(5, 'Opacity', scale); - - let checkWidget = new Gtk.CheckButton({ - label: 'Show for all backgrounds', - }); - this._settings.bind('logo-always-visible', - checkWidget, 'active', - Gio.SettingsBindFlags.DEFAULT); - this.attach(checkWidget, 1, 6, 1, 1); - } - - _addRow(row, label, widget) { - let margin = 48; - - widget.margin_end = margin; - widget.hexpand = true; - if (!this._sizeGroup) { - this._sizeGroup = new Gtk.SizeGroup({ - mode: Gtk.SizeGroupMode.VERTICAL, - }); - } - this._sizeGroup.add_widget(widget); - - this.attach(new Gtk.Label({ - label, - xalign: 1.0, - margin_start: margin, - }), 0, row, 1, 1); - this.attach(widget, 1, row, 1, 1); - } - - _createAdjustment(key, step) { - let schemaKey = this._settings.settings_schema.get_key(key); - let [type, variant] = schemaKey.get_range().deep_unpack(); - if (type !== 'range') - throw new Error('Invalid key type "%s" for adjustment'.format(type)); - let [lower, upper] = variant.deep_unpack(); - let adj = new Gtk.Adjustment({ - lower, - upper, - step_increment: step, - page_increment: 10 * step, - }); - this._settings.bind(key, adj, 'value', Gio.SettingsBindFlags.DEFAULT); - return adj; + this._preview.set_draw_func(this._drawPreview.bind(this)); + const previewRow = new Adw.PreferencesRow({ activatable: false }); + previewRow.set_child(this._preview); + this.add(previewRow); } _drawPreview(preview, cr, width, height) { @@ -216,10 +119,133 @@ class BackgroundLogoPrefsWidget extends Gtk.Grid { } return [x, y]; } +}); + +const LogoPosition = GObject.registerClass({ + Properties: { + 'name': GObject.ParamSpec.string( + 'name', 'name', 'name', + GObject.ParamFlags.READWRITE, + null), + 'value': GObject.ParamSpec.string( + 'value', 'value', 'value', + GObject.ParamFlags.READWRITE, + null), + }, +}, class LogoPosition extends GObject.Object { + _init(name, value) { + super._init({ name, value }); + } +}); + +const LogoGroup = GObject.registerClass( +class LogoGroup extends Adw.PreferencesGroup { + _init(settings) { + super._init({ title: 'Logo' }); + + this._settings = settings; + + const filter = new Gtk.FileFilter(); + filter.add_pixbuf_formats(); - _updateLogoPicker() { + this._fileChooser = new Gtk.FileChooserNative({ + title: 'Select an Image', + filter, + modal: true, + }); + this._fileChooser.connect('response', (dlg, response) => { + if (response !== Gtk.ResponseType.ACCEPT) + return; + this._settings.set_string('logo-file', dlg.get_file().get_path()); + }); + + this._filenameLabel = new Gtk.Label(); + this._settings.connect('changed::logo-file', + () => this._updateFilenameLabel()); + this._updateFilenameLabel(); + + const filenameRow = new Adw.ActionRow({ + title: 'Filename', + activatable: true, + }); + filenameRow.connect('activated', () => { + this._fileChooser.transient_for = this.get_root(); + this._fileChooser.show(); + }); + filenameRow.add_suffix(this._filenameLabel); + this.add(filenameRow); + + const positionModel = new Gio.ListStore({ item_type: LogoPosition }); + positionModel.append(new LogoPosition('Center', 'center')); + positionModel.append(new LogoPosition('Bottom left', 'bottom-left')); + positionModel.append(new LogoPosition('Bottom center', 'bottom-center')); + positionModel.append(new LogoPosition('Bottom right', 'bottom-right')); + this._positionRow = new Adw.ComboRow({ + title: 'Position', + model: positionModel, + expression: new Gtk.PropertyExpression(LogoPosition, null, 'name'), + }); + this.add(this._positionRow); + + this._positionRow.connect('notify::selected-item', () => { + const { selectedItem } = this._positionRow; + this._settings.set_string('logo-position', selectedItem.value); + }); + this._settings.connect('changed::logo-position', + () => this._updateSelectedPosition()); + this._updateSelectedPosition(); + + this._addScaleRow('Size', 'logo-size', 0.25); + this._addScaleRow('Border', 'logo-border', 1.0); + this._addScaleRow('Opacity', 'logo-opacity', 1.0); + } + + _addScaleRow(title, key, stepSize) { + const adjustment = this._createAdjustment(key, stepSize); + const activatableWidget = new Gtk.Scale({ + adjustment, + draw_value: false, + hexpand: true, + }); + const row = new Adw.ActionRow({ + activatableWidget, + title, + }); + row.add_suffix(activatableWidget); + this.add(row); + } + + _updateSelectedPosition() { + const position = this._settings.get_string('logo-position'); + const { model } = this._positionRow; + for (let i = 0; i < model.get_n_items(); i++) { + const item = model.get_item(i); + if (item.value === position) { + this._positionRow.set_selected(i); + break; + } + } + } + + _createAdjustment(key, step) { + let schemaKey = this._settings.settings_schema.get_key(key); + let [type, variant] = schemaKey.get_range().deep_unpack(); + if (type !== 'range') + throw new Error('Invalid key type "%s" for adjustment'.format(type)); + let [lower, upper] = variant.deep_unpack(); + let adj = new Gtk.Adjustment({ + lower, + upper, + step_increment: step, + page_increment: 10 * step, + }); + this._settings.bind(key, adj, 'value', Gio.SettingsBindFlags.DEFAULT); + return adj; + } + + _updateFilenameLabel() { const filename = this._settings.get_string('logo-file'); - this._logoPicker.label = GLib.basename(filename); + this._filenameLabel.label = GLib.basename(filename); } on_destroy() { @@ -229,6 +255,41 @@ class BackgroundLogoPrefsWidget extends Gtk.Grid { } }); +const OptionsGroup = GObject.registerClass( +class OptionsGroup extends Adw.PreferencesGroup { + _init(settings) { + super._init({ title: 'Options' }); + + this._settings = settings; + const alwaysShowSwitch = new Gtk.Switch({ + valign: Gtk.Align.CENTER, + }); + this._settings.bind('logo-always-visible', + alwaysShowSwitch, 'active', + Gio.SettingsBindFlags.DEFAULT); + + const row = new Adw.ActionRow({ + title: 'Show for all backgrounds', + activatable_widget: alwaysShowSwitch, + }); + row.add_suffix(alwaysShowSwitch); + this.add(row); + } +}); + +const BackgroundLogoPrefsWidget = GObject.registerClass( +class BackgroundLogoPrefsWidget extends Adw.PreferencesPage { + _init() { + super._init(); + + const settings = ExtensionUtils.getSettings(); + + this.add(new PreviewGroup(settings)); + this.add(new LogoGroup(settings)); + this.add(new OptionsGroup(settings)); + } +}); + /** */ function init() { }