doomsday.git

doomsday

~
git clone https://github.com/debuman/doomsday.git
commit cc842929b4b2a9c0bf2ca792da148733d235c8dc
parent 44ada1834f4edf0e3f59554fee949758a3a937df
Author: devastator-x <devastator-x@users.noreply.github.com>
Date: Sat, 7 Feb 2026 13:23:22 +0900

Fix EGO review: refactor prefs.js and add donations

- Remove version from Gtk import
- Replace instance properties with local variables and closures
to allow proper garbage collection after window close
- Add close-request handler to disconnect settings signal
- Add GitHub Sponsors donations field to metadata.json

Diffstat:
Mmetadata.json | 5++++-
Mprefs.js | 452++++++++++++++++++++++++++++++++++++++++---------------------------------------
2 files changed, 231 insertion(+), 226 deletion(-)

diff --git a/metadata.json b/metadata.json
@@ -5,5 +5,8 @@
   "shell-version": ["45", "46", "47"],
   "url": "https://github.com/devastator-x",
   "settings-schema": "org.gnome.shell.extensions.doomsday",
-  "version": 1
+  "version": 1,
+  "donations": {
+    "github": "devastator-x"
+  }
 }
diff --git a/prefs.js b/prefs.js
@@ -1,4 +1,4 @@
-import Gtk from 'gi://Gtk?version=4.0';
+import Gtk from 'gi://Gtk';
 import Adw from 'gi://Adw';
 import Gio from 'gi://Gio';
 import GLib from 'gi://GLib';
@@ -6,6 +6,8 @@ import {ExtensionPreferences} from 'resource:///org/gnome/Shell/Extensions/js/ex
 
 export default class DoomsdayPreferences extends ExtensionPreferences {
     fillPreferencesWindow(window) {
+        const settings = this.getSettings();
+
         // Create a preferences page
         const page = new Adw.PreferencesPage({
             title: 'General',
@@ -21,7 +23,6 @@ export default class DoomsdayPreferences extends ExtensionPreferences {
         page.add(panelGroup);
 
         // Panel position selector
-        this._settings = this.getSettings();
         const positionRow = new Adw.ComboRow({
             title: 'Panel Position',
             subtitle: 'Choose where the extension appears in the top panel',
@@ -34,14 +35,14 @@ export default class DoomsdayPreferences extends ExtensionPreferences {
         positionRow.set_model(positionModel);
 
         // Set initial value
-        const currentPosition = this._settings.get_string('panel-position');
+        const currentPosition = settings.get_string('panel-position');
         const positions = ['left', 'center', 'right'];
         positionRow.set_selected(positions.indexOf(currentPosition));
 
         // Connect to changes
         positionRow.connect('notify::selected', () => {
             const newPosition = positions[positionRow.get_selected()];
-            this._settings.set_string('panel-position', newPosition);
+            settings.set_string('panel-position', newPosition);
         });
 
         panelGroup.add(positionRow);
@@ -60,12 +61,12 @@ export default class DoomsdayPreferences extends ExtensionPreferences {
         }));
 
         // Set initial value
-        const currentIndex = this._settings.get_int('panel-index');
+        const currentIndex = settings.get_int('panel-index');
         indexRow.set_value(currentIndex);
 
         // Connect to changes
         indexRow.connect('changed', () => {
-            this._settings.set_int('panel-index', indexRow.get_value());
+            settings.set_int('panel-index', indexRow.get_value());
         });
 
         panelGroup.add(indexRow);
@@ -84,252 +85,253 @@ export default class DoomsdayPreferences extends ExtensionPreferences {
         });
         group.add(listBox);
 
-        // Load and display events
-        this._listBox = listBox;
-        this._radioGroup = null;
-        this._loadEvents();
-
-        // Add button to create new event
-        const addButton = new Gtk.Button({
-            label: 'Add D-Day Event',
-            css_classes: ['suggested-action'],
-            margin_top: 12,
-        });
-        addButton.connect('clicked', () => this._onAddEvent(window));
-        group.add(addButton);
+        let radioGroup = null;
 
-        // Listen for settings changes
-        this._settingsChangedId = this._settings.connect('changed::dday-events',
-            () => this._loadEvents());
+        // Local function: create event row
+        const createEventRow = (event, isSelected) => {
+            const row = new Adw.ActionRow({
+                title: event.name,
+                subtitle: event.date,
+            });
 
-        // Create About page
-        this._addAboutPage(window);
-    }
+            // Add radio button for selection
+            const radioButton = new Gtk.CheckButton({
+                active: isSelected,
+                valign: Gtk.Align.CENTER,
+            });
+            radioButton.connect('toggled', () => {
+                if (radioButton.get_active()) {
+                    settings.set_string('selected-event-id', event.id);
+                }
+            });
+            row.add_prefix(radioButton);
 
-    _loadEvents() {
-        // Clear existing rows
-        let child = this._listBox.get_first_child();
-        while (child) {
-            const next = child.get_next_sibling();
-            this._listBox.remove(child);
-            child = next;
-        }
+            // Make all radio buttons part of same group
+            if (!radioGroup) {
+                radioGroup = radioButton;
+            } else {
+                radioButton.set_group(radioGroup);
+            }
 
-        // Reset radio group
-        this._radioGroup = null;
+            // Add edit button
+            const editButton = new Gtk.Button({
+                icon_name: 'document-edit-symbolic',
+                label: 'Edit',
+                valign: Gtk.Align.CENTER,
+                css_classes: ['flat'],
+                tooltip_text: 'Edit this event',
+            });
+            editButton.connect('clicked', () => showEventDialog(window, event));
+            row.add_suffix(editButton);
+
+            // Add delete button
+            const deleteButton = new Gtk.Button({
+                icon_name: 'user-trash-symbolic',
+                label: 'Delete',
+                valign: Gtk.Align.CENTER,
+                css_classes: ['flat', 'destructive-action'],
+                tooltip_text: 'Delete this event',
+            });
+            deleteButton.connect('clicked', () => deleteEvent(event.id));
+            row.add_suffix(deleteButton);
+
+            return row;
+        };
+
+        // Local function: load events
+        const loadEvents = () => {
+            // Clear existing rows
+            let child = listBox.get_first_child();
+            while (child) {
+                const next = child.get_next_sibling();
+                listBox.remove(child);
+                child = next;
+            }
 
-        // Load events from settings
-        const eventsJson = this._settings.get_string('dday-events');
-        const selectedId = this._settings.get_string('selected-event-id');
+            // Reset radio group
+            radioGroup = null;
 
-        try {
-            const events = JSON.parse(eventsJson);
+            // Load events from settings
+            const eventsJson = settings.get_string('dday-events');
+            const selectedId = settings.get_string('selected-event-id');
 
-            events.forEach(event => {
-                const row = this._createEventRow(event, event.id === selectedId);
-                this._listBox.append(row);
-            });
+            try {
+                const events = JSON.parse(eventsJson);
 
-            // Show placeholder if no events
-            if (events.length === 0) {
-                const row = new Adw.ActionRow({
-                    title: 'No D-Day events yet',
-                    subtitle: 'Click "Add D-Day Event" to create your first countdown',
+                events.forEach(event => {
+                    const row = createEventRow(event, event.id === selectedId);
+                    listBox.append(row);
                 });
-                this._listBox.append(row);
-            }
-
-        } catch (e) {
-            console.error('Doomsday: Failed to load events:', e);
-        }
-    }
 
-    _createEventRow(event, isSelected) {
-        const row = new Adw.ActionRow({
-            title: event.name,
-            subtitle: event.date,
-        });
+                // Show placeholder if no events
+                if (events.length === 0) {
+                    const row = new Adw.ActionRow({
+                        title: 'No D-Day events yet',
+                        subtitle: 'Click "Add D-Day Event" to create your first countdown',
+                    });
+                    listBox.append(row);
+                }
 
-        // Add radio button for selection
-        const radioButton = new Gtk.CheckButton({
-            active: isSelected,
-            valign: Gtk.Align.CENTER,
-        });
-        radioButton.connect('toggled', () => {
-            if (radioButton.get_active()) {
-                this._settings.set_string('selected-event-id', event.id);
+            } catch (e) {
+                console.error('Doomsday: Failed to load events:', e);
             }
-        });
-        row.add_prefix(radioButton);
-
-        // Make all radio buttons part of same group
-        if (!this._radioGroup) {
-            this._radioGroup = radioButton;
-        } else {
-            radioButton.set_group(this._radioGroup);
-        }
-
-        // Add edit button
-        const editButton = new Gtk.Button({
-            icon_name: 'document-edit-symbolic',
-            label: 'Edit',
-            valign: Gtk.Align.CENTER,
-            css_classes: ['flat'],
-            tooltip_text: 'Edit this event',
-        });
-        editButton.connect('clicked', () => this._onEditEvent(event));
-        row.add_suffix(editButton);
-
-        // Add delete button
-        const deleteButton = new Gtk.Button({
-            icon_name: 'user-trash-symbolic',
-            label: 'Delete',
-            valign: Gtk.Align.CENTER,
-            css_classes: ['flat', 'destructive-action'],
-            tooltip_text: 'Delete this event',
-        });
-        deleteButton.connect('clicked', () => this._onDeleteEvent(event.id));
-        row.add_suffix(deleteButton);
+        };
 
-        return row;
-    }
+        // Local function: save event
+        const saveEvent = (eventId, name, date) => {
+            const eventsJson = settings.get_string('dday-events');
+            let events = [];
 
-    _onAddEvent(window) {
-        this._showEventDialog(window, null);
-    }
+            try {
+                events = JSON.parse(eventsJson);
+            } catch (e) {
+                console.error('Doomsday: Failed to parse events:', e);
+            }
 
-    _onEditEvent(event) {
-        const window = this._listBox.get_root();
-        this._showEventDialog(window, event);
-    }
+            if (eventId) {
+                // Update existing event
+                const index = events.findIndex(e => e.id === eventId);
+                if (index !== -1) {
+                    events[index] = { id: eventId, name, date };
+                }
+            } else {
+                // Create new event with UUID
+                const newId = GLib.uuid_string_random();
+                events.push({ id: newId, name, date });
+
+                // Auto-select if it's the first event
+                if (events.length === 1) {
+                    settings.set_string('selected-event-id', newId);
+                }
+            }
 
-    _showEventDialog(window, existingEvent) {
-        const dialog = new Adw.MessageDialog({
-            transient_for: window,
-            modal: true,
-            heading: existingEvent ? 'Edit D-Day Event' : 'New D-Day Event',
-        });
+            // Save back to settings
+            settings.set_string('dday-events', JSON.stringify(events));
+        };
 
-        // Create form content
-        const box = new Gtk.Box({
-            orientation: Gtk.Orientation.VERTICAL,
-            spacing: 12,
-            margin_top: 12,
-            margin_bottom: 12,
-            margin_start: 12,
-            margin_end: 12,
-        });
+        // Local function: delete event
+        const deleteEvent = (eventId) => {
+            const eventsJson = settings.get_string('dday-events');
+            let events = [];
 
-        // Event name entry
-        const nameEntry = new Adw.EntryRow({
-            title: 'Event Name',
-        });
-        if (existingEvent) {
-            nameEntry.set_text(existingEvent.name);
-        }
-        box.append(nameEntry);
-
-        // Date picker (using entry for simplicity)
-        const dateEntry = new Adw.EntryRow({
-            title: 'Date (YYYY-MM-DD)',
-        });
-        if (existingEvent) {
-            dateEntry.set_text(existingEvent.date);
-        } else {
-            // Default to today
-            const today = new Date();
-            const dateStr = today.toISOString().split('T')[0];
-            dateEntry.set_text(dateStr);
-        }
-        box.append(dateEntry);
-
-        dialog.set_extra_child(box);
-
-        // Add buttons
-        dialog.add_response('cancel', 'Cancel');
-        dialog.add_response('save', 'Save');
-        dialog.set_response_appearance('save', Adw.ResponseAppearance.SUGGESTED);
-
-        dialog.connect('response', (dialog, response) => {
-            if (response === 'save') {
-                const name = nameEntry.get_text().trim();
-                const date = dateEntry.get_text().trim();
-
-                // Validate inputs
-                if (!name || !date) {
-                    console.error('Doomsday: Name and date are required');
-                    return;
-                }
+            try {
+                events = JSON.parse(eventsJson);
+            } catch (e) {
+                console.error('Doomsday: Failed to parse events:', e);
+                return;
+            }
 
-                // Validate date format
-                if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) {
-                    console.error('Doomsday: Invalid date format');
-                    return;
-                }
+            // Remove event
+            events = events.filter(e => e.id !== eventId);
 
-                // Save event
-                this._saveEvent(existingEvent?.id, name, date);
+            // If deleted event was selected, clear selection or select first
+            const selectedId = settings.get_string('selected-event-id');
+            if (selectedId === eventId) {
+                const newSelectedId = events.length > 0 ? events[0].id : '';
+                settings.set_string('selected-event-id', newSelectedId);
             }
-            dialog.close();
-        });
 
-        dialog.present();
-    }
+            // Save back to settings
+            settings.set_string('dday-events', JSON.stringify(events));
+        };
+
+        // Local function: show event dialog
+        const showEventDialog = (parentWindow, existingEvent) => {
+            const dialog = new Adw.MessageDialog({
+                transient_for: parentWindow,
+                modal: true,
+                heading: existingEvent ? 'Edit D-Day Event' : 'New D-Day Event',
+            });
 
-    _saveEvent(eventId, name, date) {
-        const eventsJson = this._settings.get_string('dday-events');
-        let events = [];
-
-        try {
-            events = JSON.parse(eventsJson);
-        } catch (e) {
-            console.error('Doomsday: Failed to parse events:', e);
-        }
-
-        if (eventId) {
-            // Update existing event
-            const index = events.findIndex(e => e.id === eventId);
-            if (index !== -1) {
-                events[index] = { id: eventId, name, date };
+            // Create form content
+            const box = new Gtk.Box({
+                orientation: Gtk.Orientation.VERTICAL,
+                spacing: 12,
+                margin_top: 12,
+                margin_bottom: 12,
+                margin_start: 12,
+                margin_end: 12,
+            });
+
+            // Event name entry
+            const nameEntry = new Adw.EntryRow({
+                title: 'Event Name',
+            });
+            if (existingEvent) {
+                nameEntry.set_text(existingEvent.name);
             }
-        } else {
-            // Create new event with UUID
-            const newId = GLib.uuid_string_random();
-            events.push({ id: newId, name, date });
-
-            // Auto-select if it's the first event
-            if (events.length === 1) {
-                this._settings.set_string('selected-event-id', newId);
+            box.append(nameEntry);
+
+            // Date picker (using entry for simplicity)
+            const dateEntry = new Adw.EntryRow({
+                title: 'Date (YYYY-MM-DD)',
+            });
+            if (existingEvent) {
+                dateEntry.set_text(existingEvent.date);
+            } else {
+                // Default to today
+                const today = new Date();
+                const dateStr = today.toISOString().split('T')[0];
+                dateEntry.set_text(dateStr);
             }
-        }
+            box.append(dateEntry);
+
+            dialog.set_extra_child(box);
+
+            // Add buttons
+            dialog.add_response('cancel', 'Cancel');
+            dialog.add_response('save', 'Save');
+            dialog.set_response_appearance('save', Adw.ResponseAppearance.SUGGESTED);
+
+            dialog.connect('response', (dlg, response) => {
+                if (response === 'save') {
+                    const name = nameEntry.get_text().trim();
+                    const date = dateEntry.get_text().trim();
+
+                    // Validate inputs
+                    if (!name || !date) {
+                        console.error('Doomsday: Name and date are required');
+                        return;
+                    }
+
+                    // Validate date format
+                    if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) {
+                        console.error('Doomsday: Invalid date format');
+                        return;
+                    }
+
+                    // Save event
+                    saveEvent(existingEvent?.id, name, date);
+                }
+                dlg.close();
+            });
 
-        // Save back to settings
-        this._settings.set_string('dday-events', JSON.stringify(events));
-    }
+            dialog.present();
+        };
+
+        // Load and display events
+        loadEvents();
 
-    _onDeleteEvent(eventId) {
-        const eventsJson = this._settings.get_string('dday-events');
-        let events = [];
-
-        try {
-            events = JSON.parse(eventsJson);
-        } catch (e) {
-            console.error('Doomsday: Failed to parse events:', e);
-            return;
-        }
-
-        // Remove event
-        events = events.filter(e => e.id !== eventId);
-
-        // If deleted event was selected, clear selection or select first
-        const selectedId = this._settings.get_string('selected-event-id');
-        if (selectedId === eventId) {
-            const newSelectedId = events.length > 0 ? events[0].id : '';
-            this._settings.set_string('selected-event-id', newSelectedId);
-        }
-
-        // Save back to settings
-        this._settings.set_string('dday-events', JSON.stringify(events));
+        // Add button to create new event
+        const addButton = new Gtk.Button({
+            label: 'Add D-Day Event',
+            css_classes: ['suggested-action'],
+            margin_top: 12,
+        });
+        addButton.connect('clicked', () => showEventDialog(window, null));
+        group.add(addButton);
+
+        // Listen for settings changes
+        const settingsChangedId = settings.connect('changed::dday-events',
+            () => loadEvents());
+
+        // Clean up settings signal on window close
+        window.connect('close-request', () => {
+            settings.disconnect(settingsChangedId);
+        });
+
+        // Create About page
+        this._addAboutPage(window);
     }
 
     _addAboutPage(window) {