From 386b000be000abe845bdec426c3adac6195e2e50 Mon Sep 17 00:00:00 2001
From: Florian Müllner <fmuellner@gnome.org>
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 <fmuellner@gnome.org>
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é <claudioandre.br@gmail.com>
 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 <fmuellner@gnome.org>
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() {
 }

