Fix custom quotes saving issue, fix viewport width
This commit is contained in:
221
extension.js
221
extension.js
@ -10,6 +10,18 @@ import GObject from 'gi://GObject';
|
||||
import { Extension } from 'resource:///org/gnome/shell/extensions/extension.js';
|
||||
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
||||
import * as PanelMenu from 'resource:///org/gnome/shell/ui/panelMenu.js';
|
||||
import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js';
|
||||
|
||||
// Simple hash function for quote IDs
|
||||
function simpleHash(str) {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const char = str.charCodeAt(i);
|
||||
hash = ((hash << 5) - hash) + char;
|
||||
hash = hash & hash; // Convert to 32-bit integer
|
||||
}
|
||||
return Math.abs(hash).toString(16);
|
||||
}
|
||||
|
||||
class QuotesExtension extends Extension {
|
||||
constructor(metadata) {
|
||||
@ -23,11 +35,13 @@ class QuotesExtension extends Extension {
|
||||
this._dataDir = null;
|
||||
this._quotesFile = null;
|
||||
this._settings = null;
|
||||
this._isPaused = false;
|
||||
}
|
||||
|
||||
enable() {
|
||||
this._initializeSettings();
|
||||
this._initializeDataDir();
|
||||
this._processCustomQuotes();
|
||||
this._loadQuotes();
|
||||
this._createIndicator();
|
||||
this._setupTimers();
|
||||
@ -70,6 +84,7 @@ class QuotesExtension extends Extension {
|
||||
this._quotesFileName = this._settings.get_string('quotes-file') || 'quotes.json';
|
||||
this._useCustomQuotes = this._settings.get_boolean('use-custom-quotes');
|
||||
this._customQuotes = this._settings.get_string('custom-quotes') || '[]';
|
||||
this._quoteSources = { synced: [], custom: [] };
|
||||
|
||||
// Listen for settings changes
|
||||
this._settings.connect('changed', (settings, key) => {
|
||||
@ -77,6 +92,7 @@ class QuotesExtension extends Extension {
|
||||
this._updateLabelStyle();
|
||||
} else if (key === 'max-width') {
|
||||
this._maxWidth = this._settings.get_int('max-width') || 60;
|
||||
this._label.set_width(this._maxWidth * 8);
|
||||
this._label.set_text(this._getCurrentQuote());
|
||||
} else if (key === 'quotes-file') {
|
||||
this._quotesFileName = this._settings.get_string('quotes-file') || 'quotes.json';
|
||||
@ -85,6 +101,8 @@ class QuotesExtension extends Extension {
|
||||
} else if (key === 'use-custom-quotes' || key === 'custom-quotes') {
|
||||
this._useCustomQuotes = this._settings.get_boolean('use-custom-quotes');
|
||||
this._customQuotes = this._settings.get_string('custom-quotes') || '[]';
|
||||
this._processCustomQuotes();
|
||||
this._mergeAndSaveQuotes();
|
||||
this._loadQuotes();
|
||||
if (this._label) {
|
||||
this._label.set_text(this._getCurrentQuote());
|
||||
@ -96,34 +114,34 @@ class QuotesExtension extends Extension {
|
||||
_loadQuotes() {
|
||||
this._quotes = [];
|
||||
|
||||
// Use custom quotes if enabled
|
||||
if (this._useCustomQuotes) {
|
||||
try {
|
||||
this._quotes = JSON.parse(this._customQuotes);
|
||||
if (!Array.isArray(this._quotes)) {
|
||||
this._quotes = [];
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to parse custom quotes:', e);
|
||||
this._quotes = [];
|
||||
}
|
||||
} else {
|
||||
// Load from file
|
||||
try {
|
||||
if (GLib.file_test(this._quotesFile, GLib.FileTest.EXISTS)) {
|
||||
const [success, contents] = GLib.file_get_contents(this._quotesFile);
|
||||
if (success) {
|
||||
const decoder = new TextDecoder();
|
||||
this._quotes = JSON.parse(decoder.decode(contents));
|
||||
// Always load from file (contains both synced and custom quotes)
|
||||
try {
|
||||
if (GLib.file_test(this._quotesFile, GLib.FileTest.EXISTS)) {
|
||||
const [success, contents] = GLib.file_get_contents(this._quotesFile);
|
||||
if (success) {
|
||||
const decoder = new TextDecoder();
|
||||
const fileData = JSON.parse(decoder.decode(contents));
|
||||
|
||||
// Handle both old format (array) and new format (object with sources)
|
||||
if (Array.isArray(fileData)) {
|
||||
this._quotes = fileData;
|
||||
} else if (fileData.synced && fileData.custom) {
|
||||
this._quoteSources = fileData;
|
||||
// Combine quotes based on use-custom-quotes setting
|
||||
if (this._useCustomQuotes) {
|
||||
this._quotes = [...this._quoteSources.custom, ...this._quoteSources.synced];
|
||||
} else {
|
||||
this._quotes = [...this._quoteSources.synced, ...this._quoteSources.custom];
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to load quotes:', e);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to load quotes:', e);
|
||||
}
|
||||
|
||||
if (this._quotes.length === 0) {
|
||||
this._quotes = [
|
||||
const defaultQuotes = [
|
||||
{ text: "The only way to do great work is to love what you do.", author: "Steve Jobs" },
|
||||
{ text: "Life is what happens to you while you're busy making other plans.", author: "John Lennon" },
|
||||
{ text: "The future belongs to those who believe in the beauty of their dreams.", author: "Eleanor Roosevelt" },
|
||||
@ -150,18 +168,64 @@ class QuotesExtension extends Extension {
|
||||
{ text: "The only way to make sense out of change is to plunge into it, move with it, and join the dance.", author: "Alan Watts" },
|
||||
{ text: "Yesterday is history, tomorrow is a mystery, today is a gift of God, which is why we call it the present.", author: "Bill Keane" }
|
||||
];
|
||||
|
||||
// Add hashes to default quotes and mark as synced
|
||||
this._quoteSources.synced = defaultQuotes.map(quote => ({
|
||||
...quote,
|
||||
hash: simpleHash(quote.text + quote.author),
|
||||
source: 'default'
|
||||
}));
|
||||
this._quotes = this._quoteSources.synced;
|
||||
this._saveQuotes();
|
||||
}
|
||||
}
|
||||
|
||||
_saveQuotes() {
|
||||
try {
|
||||
const contents = JSON.stringify(this._quotes, null, 2);
|
||||
const contents = JSON.stringify(this._quoteSources, null, 2);
|
||||
GLib.file_set_contents(this._quotesFile, contents);
|
||||
} catch (e) {
|
||||
console.error('Failed to save quotes:', e);
|
||||
}
|
||||
}
|
||||
|
||||
_processCustomQuotes() {
|
||||
try {
|
||||
const customQuotes = JSON.parse(this._customQuotes);
|
||||
if (Array.isArray(customQuotes)) {
|
||||
this._quoteSources.custom = customQuotes.map(quote => ({
|
||||
...quote,
|
||||
hash: simpleHash(quote.text + quote.author),
|
||||
source: 'custom'
|
||||
}));
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to process custom quotes:', e);
|
||||
this._quoteSources.custom = [];
|
||||
}
|
||||
}
|
||||
|
||||
_mergeAndSaveQuotes() {
|
||||
// Remove duplicates based on hash
|
||||
const existingHashes = new Set();
|
||||
this._quoteSources.synced = this._quoteSources.synced.filter(quote => {
|
||||
if (existingHashes.has(quote.hash)) {
|
||||
return false;
|
||||
}
|
||||
existingHashes.add(quote.hash);
|
||||
return true;
|
||||
});
|
||||
|
||||
this._quoteSources.custom = this._quoteSources.custom.filter(quote => {
|
||||
if (existingHashes.has(quote.hash)) {
|
||||
return false;
|
||||
}
|
||||
existingHashes.add(quote.hash);
|
||||
return true;
|
||||
});
|
||||
|
||||
this._saveQuotes();
|
||||
}
|
||||
|
||||
_createIndicator() {
|
||||
this._indicator = new PanelMenu.Button(0.0, 'Quotes', false);
|
||||
@ -173,12 +237,92 @@ class QuotesExtension extends Extension {
|
||||
});
|
||||
|
||||
this._updateLabelStyle();
|
||||
this._label.set_width(this._maxWidth * 8); // Set viewport width based on maxWidth
|
||||
this._label.clutter_text.set_ellipsize(3); // PANGO_ELLIPSIZE_END
|
||||
this._indicator.add_child(this._label);
|
||||
Main.panel.addToStatusArea('quotes', this._indicator, 1, 'left');
|
||||
|
||||
this._indicator.connect('button-press-event', () => {
|
||||
// Create popup menu
|
||||
this._createPopupMenu();
|
||||
|
||||
this._indicator.connect('button-press-event', (actor, event) => {
|
||||
if (event.get_button() === 1) { // Left click
|
||||
this._nextQuote();
|
||||
return Clutter.EVENT_STOP; // Prevent menu from opening
|
||||
}
|
||||
return Clutter.EVENT_PROPAGATE;
|
||||
});
|
||||
}
|
||||
|
||||
_createPopupMenu() {
|
||||
// Toggle pause/resume
|
||||
this._pauseMenuItem = new PopupMenu.PopupMenuItem('Pause Rotation');
|
||||
this._pauseMenuItem.connect('activate', () => {
|
||||
this._togglePause();
|
||||
});
|
||||
this._indicator.menu.addMenuItem(this._pauseMenuItem);
|
||||
|
||||
// Next quote
|
||||
const nextMenuItem = new PopupMenu.PopupMenuItem('Next Quote');
|
||||
nextMenuItem.connect('activate', () => {
|
||||
this._nextQuote();
|
||||
});
|
||||
this._indicator.menu.addMenuItem(nextMenuItem);
|
||||
|
||||
// Separator
|
||||
this._indicator.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||
|
||||
// Delay options
|
||||
const delaySubmenu = new PopupMenu.PopupSubMenuMenuItem('Delay Next Quote');
|
||||
|
||||
const delay30s = new PopupMenu.PopupMenuItem('30 seconds');
|
||||
delay30s.connect('activate', () => this._delayRotation(30));
|
||||
delaySubmenu.menu.addMenuItem(delay30s);
|
||||
|
||||
const delay1m = new PopupMenu.PopupMenuItem('1 minute');
|
||||
delay1m.connect('activate', () => this._delayRotation(60));
|
||||
delaySubmenu.menu.addMenuItem(delay1m);
|
||||
|
||||
const delay5m = new PopupMenu.PopupMenuItem('5 minutes');
|
||||
delay5m.connect('activate', () => this._delayRotation(300));
|
||||
delaySubmenu.menu.addMenuItem(delay5m);
|
||||
|
||||
const delay15m = new PopupMenu.PopupMenuItem('15 minutes');
|
||||
delay15m.connect('activate', () => this._delayRotation(900));
|
||||
delaySubmenu.menu.addMenuItem(delay15m);
|
||||
|
||||
this._indicator.menu.addMenuItem(delaySubmenu);
|
||||
}
|
||||
|
||||
_togglePause() {
|
||||
this._isPaused = !this._isPaused;
|
||||
|
||||
if (this._isPaused) {
|
||||
this._pauseMenuItem.label.text = 'Resume Rotation';
|
||||
if (this._rotationTimer) {
|
||||
GLib.source_remove(this._rotationTimer);
|
||||
this._rotationTimer = null;
|
||||
}
|
||||
} else {
|
||||
this._pauseMenuItem.label.text = 'Pause Rotation';
|
||||
this._setupRotationTimer();
|
||||
}
|
||||
}
|
||||
|
||||
_delayRotation(seconds) {
|
||||
if (this._rotationTimer) {
|
||||
GLib.source_remove(this._rotationTimer);
|
||||
this._rotationTimer = null;
|
||||
}
|
||||
|
||||
this._rotationTimer = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT,
|
||||
seconds, () => {
|
||||
if (!this._isPaused) {
|
||||
this._nextQuote();
|
||||
this._setupRotationTimer();
|
||||
}
|
||||
return GLib.SOURCE_REMOVE;
|
||||
});
|
||||
}
|
||||
|
||||
_getCurrentQuote() {
|
||||
@ -187,10 +331,6 @@ class QuotesExtension extends Extension {
|
||||
const quote = this._quotes[this._currentQuoteIndex];
|
||||
let text = `"${quote.text}" - ${quote.author}`;
|
||||
|
||||
if (text.length > this._maxWidth) {
|
||||
text = text.substring(0, this._maxWidth - 3) + '...';
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
@ -212,12 +352,23 @@ class QuotesExtension extends Extension {
|
||||
}
|
||||
|
||||
_setupTimers() {
|
||||
this._setupRotationTimer();
|
||||
this._setupSyncTimer();
|
||||
}
|
||||
|
||||
_setupRotationTimer() {
|
||||
if (this._isPaused) return;
|
||||
|
||||
this._rotationTimer = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT,
|
||||
this._rotationInterval, () => {
|
||||
this._nextQuote();
|
||||
if (!this._isPaused) {
|
||||
this._nextQuote();
|
||||
}
|
||||
return GLib.SOURCE_CONTINUE;
|
||||
});
|
||||
}
|
||||
|
||||
_setupSyncTimer() {
|
||||
this._syncTimer = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT,
|
||||
this._syncInterval, () => {
|
||||
this._syncQuotes();
|
||||
@ -240,15 +391,17 @@ class QuotesExtension extends Extension {
|
||||
if (data.content && data.author) {
|
||||
const newQuote = {
|
||||
text: data.content,
|
||||
author: data.author
|
||||
author: data.author,
|
||||
hash: simpleHash(data.content + data.author),
|
||||
source: 'api'
|
||||
};
|
||||
|
||||
const exists = this._quotes.some(q =>
|
||||
q.text === newQuote.text && q.author === newQuote.author);
|
||||
const exists = this._quoteSources.synced.some(q => q.hash === newQuote.hash);
|
||||
|
||||
if (!exists) {
|
||||
this._quotes.push(newQuote);
|
||||
this._saveQuotes();
|
||||
this._quoteSources.synced.push(newQuote);
|
||||
this._mergeAndSaveQuotes();
|
||||
this._loadQuotes();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
|
Reference in New Issue
Block a user