Fix provider sync, left mouse event handling
This commit is contained in:
172
extension.js
172
extension.js
@ -36,6 +36,8 @@ class QuotesExtension extends Extension {
|
|||||||
this._quotesFile = null;
|
this._quotesFile = null;
|
||||||
this._settings = null;
|
this._settings = null;
|
||||||
this._isPaused = false;
|
this._isPaused = false;
|
||||||
|
this._preChangeTimer = null;
|
||||||
|
this._isShowingIndicator = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
enable() {
|
enable() {
|
||||||
@ -57,6 +59,10 @@ class QuotesExtension extends Extension {
|
|||||||
GLib.source_remove(this._syncTimer);
|
GLib.source_remove(this._syncTimer);
|
||||||
this._syncTimer = null;
|
this._syncTimer = null;
|
||||||
}
|
}
|
||||||
|
if (this._preChangeTimer) {
|
||||||
|
GLib.source_remove(this._preChangeTimer);
|
||||||
|
this._preChangeTimer = null;
|
||||||
|
}
|
||||||
if (this._indicator) {
|
if (this._indicator) {
|
||||||
this._indicator.destroy();
|
this._indicator.destroy();
|
||||||
this._indicator = null;
|
this._indicator = null;
|
||||||
@ -77,13 +83,15 @@ class QuotesExtension extends Extension {
|
|||||||
this._settings = this.getSettings();
|
this._settings = this.getSettings();
|
||||||
this._rotationInterval = this._settings.get_int('rotation-interval') || 30;
|
this._rotationInterval = this._settings.get_int('rotation-interval') || 30;
|
||||||
this._syncInterval = this._settings.get_int('sync-interval') || 3600;
|
this._syncInterval = this._settings.get_int('sync-interval') || 3600;
|
||||||
this._apiUrl = this._settings.get_string('api-url') || 'https://api.quotable.io/random';
|
this._apiProviders = this._parseApiProviders();
|
||||||
this._fontSize = this._settings.get_int('font-size') || 14;
|
this._fontSize = this._settings.get_int('font-size') || 14;
|
||||||
this._textColor = this._settings.get_string('text-color') || '#ffffff';
|
this._textColor = this._settings.get_string('text-color') || '#ffffff';
|
||||||
this._maxWidth = this._settings.get_int('max-width') || 60;
|
this._maxWidth = this._settings.get_int('max-width') || 60;
|
||||||
this._quotesFileName = this._settings.get_string('quotes-file') || 'quotes.json';
|
this._quotesFileName = this._settings.get_string('quotes-file') || 'quotes.json';
|
||||||
this._useCustomQuotes = this._settings.get_boolean('use-custom-quotes');
|
this._useCustomQuotes = this._settings.get_boolean('use-custom-quotes');
|
||||||
this._customQuotes = this._settings.get_string('custom-quotes') || '[]';
|
this._customQuotes = this._settings.get_string('custom-quotes') || '[]';
|
||||||
|
this._showChangeIndicator = this._settings.get_boolean('show-change-indicator');
|
||||||
|
this._indicatorDuration = this._settings.get_int('indicator-duration') || 5;
|
||||||
this._quoteSources = { synced: [], custom: [] };
|
this._quoteSources = { synced: [], custom: [] };
|
||||||
|
|
||||||
// Listen for settings changes
|
// Listen for settings changes
|
||||||
@ -107,6 +115,12 @@ class QuotesExtension extends Extension {
|
|||||||
if (this._label) {
|
if (this._label) {
|
||||||
this._label.set_text(this._getCurrentQuote());
|
this._label.set_text(this._getCurrentQuote());
|
||||||
}
|
}
|
||||||
|
} else if (key === 'api-providers') {
|
||||||
|
this._apiProviders = this._parseApiProviders();
|
||||||
|
} else if (key === 'show-change-indicator') {
|
||||||
|
this._showChangeIndicator = this._settings.get_boolean('show-change-indicator');
|
||||||
|
} else if (key === 'indicator-duration') {
|
||||||
|
this._indicatorDuration = this._settings.get_int('indicator-duration') || 5;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -245,10 +259,21 @@ class QuotesExtension extends Extension {
|
|||||||
// Create popup menu
|
// Create popup menu
|
||||||
this._createPopupMenu();
|
this._createPopupMenu();
|
||||||
|
|
||||||
this._indicator.connect('button-press-event', (actor, event) => {
|
// Override the reactive property to handle clicks properly
|
||||||
|
this._indicator.reactive = true;
|
||||||
|
this._indicator.can_focus = true;
|
||||||
|
this._indicator.track_hover = true;
|
||||||
|
|
||||||
|
// Connect to event signals using a different approach
|
||||||
|
this._indicator.connect('event', (actor, event) => {
|
||||||
|
if (event.type() === Clutter.EventType.BUTTON_PRESS) {
|
||||||
if (event.get_button() === 1) { // Left click
|
if (event.get_button() === 1) { // Left click
|
||||||
this._nextQuote();
|
this._nextQuote();
|
||||||
return Clutter.EVENT_STOP; // Prevent menu from opening
|
return Clutter.EVENT_STOP;
|
||||||
|
} else if (event.get_button() === 3) { // Right click
|
||||||
|
this._indicator.menu.toggle();
|
||||||
|
return Clutter.EVENT_STOP;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return Clutter.EVENT_PROPAGATE;
|
return Clutter.EVENT_PROPAGATE;
|
||||||
});
|
});
|
||||||
@ -314,11 +339,16 @@ class QuotesExtension extends Extension {
|
|||||||
GLib.source_remove(this._rotationTimer);
|
GLib.source_remove(this._rotationTimer);
|
||||||
this._rotationTimer = null;
|
this._rotationTimer = null;
|
||||||
}
|
}
|
||||||
|
if (this._preChangeTimer) {
|
||||||
|
GLib.source_remove(this._preChangeTimer);
|
||||||
|
this._preChangeTimer = null;
|
||||||
|
}
|
||||||
|
this._hideIndicator();
|
||||||
|
|
||||||
this._rotationTimer = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT,
|
this._rotationTimer = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT,
|
||||||
seconds, () => {
|
seconds, () => {
|
||||||
if (!this._isPaused) {
|
if (!this._isPaused) {
|
||||||
this._nextQuote();
|
this._scheduleQuoteChange();
|
||||||
this._setupRotationTimer();
|
this._setupRotationTimer();
|
||||||
}
|
}
|
||||||
return GLib.SOURCE_REMOVE;
|
return GLib.SOURCE_REMOVE;
|
||||||
@ -362,12 +392,54 @@ class QuotesExtension extends Extension {
|
|||||||
this._rotationTimer = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT,
|
this._rotationTimer = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT,
|
||||||
this._rotationInterval, () => {
|
this._rotationInterval, () => {
|
||||||
if (!this._isPaused) {
|
if (!this._isPaused) {
|
||||||
this._nextQuote();
|
this._scheduleQuoteChange();
|
||||||
}
|
}
|
||||||
return GLib.SOURCE_CONTINUE;
|
return GLib.SOURCE_CONTINUE;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_scheduleQuoteChange() {
|
||||||
|
if (this._preChangeTimer) {
|
||||||
|
GLib.source_remove(this._preChangeTimer);
|
||||||
|
this._preChangeTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._showChangeIndicator) {
|
||||||
|
this._showIndicator();
|
||||||
|
|
||||||
|
this._preChangeTimer = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, this._indicatorDuration, () => {
|
||||||
|
this._hideIndicator();
|
||||||
|
this._nextQuote();
|
||||||
|
this._preChangeTimer = null;
|
||||||
|
return GLib.SOURCE_REMOVE;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this._nextQuote();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_showIndicator() {
|
||||||
|
if (!this._label || this._isShowingIndicator) return;
|
||||||
|
|
||||||
|
this._isShowingIndicator = true;
|
||||||
|
const currentQuote = this._getCurrentQuote();
|
||||||
|
let countdown = this._indicatorDuration;
|
||||||
|
this._label.set_text(`⏳${countdown}${currentQuote}`);
|
||||||
|
|
||||||
|
const countdownTimer = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 1, () => {
|
||||||
|
countdown--;
|
||||||
|
if (this._isShowingIndicator && countdown > 0) {
|
||||||
|
this._label.set_text(`⏳${countdown}${currentQuote}`);
|
||||||
|
return GLib.SOURCE_CONTINUE;
|
||||||
|
}
|
||||||
|
return GLib.SOURCE_REMOVE;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_hideIndicator() {
|
||||||
|
this._isShowingIndicator = false;
|
||||||
|
}
|
||||||
|
|
||||||
_setupSyncTimer() {
|
_setupSyncTimer() {
|
||||||
this._syncTimer = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT,
|
this._syncTimer = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT,
|
||||||
this._syncInterval, () => {
|
this._syncInterval, () => {
|
||||||
@ -376,10 +448,47 @@ class QuotesExtension extends Extension {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_parseApiProviders() {
|
||||||
|
try {
|
||||||
|
const providersString = this._settings.get_string('api-providers') || '[]';
|
||||||
|
return JSON.parse(providersString);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to parse API providers:', e);
|
||||||
|
return [{
|
||||||
|
name: 'quotable',
|
||||||
|
url: 'https://api.quotable.io/random',
|
||||||
|
method: 'GET',
|
||||||
|
mapping: { text: 'content', author: 'author' },
|
||||||
|
enabled: true
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_syncQuotes() {
|
_syncQuotes() {
|
||||||
|
const enabledProviders = this._apiProviders.filter(p => p.enabled);
|
||||||
|
if (enabledProviders.length === 0) {
|
||||||
|
console.log('No enabled API providers');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
enabledProviders.forEach(provider => this._syncFromProvider(provider));
|
||||||
|
}
|
||||||
|
|
||||||
|
_syncFromProvider(provider) {
|
||||||
try {
|
try {
|
||||||
this._session = new Soup.Session();
|
this._session = new Soup.Session();
|
||||||
const message = Soup.Message.new('GET', this._apiUrl);
|
const message = Soup.Message.new(provider.method || 'GET', provider.url);
|
||||||
|
|
||||||
|
if (provider.headers) {
|
||||||
|
Object.entries(provider.headers).forEach(([key, value]) => {
|
||||||
|
message.request_headers.append(key, value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (provider.body && (provider.method === 'POST' || provider.method === 'PUT')) {
|
||||||
|
message.set_request_body_from_bytes('application/json',
|
||||||
|
new GLib.Bytes(JSON.stringify(provider.body)));
|
||||||
|
}
|
||||||
|
|
||||||
this._session.send_and_read_async(message, GLib.PRIORITY_DEFAULT, null, (session, result) => {
|
this._session.send_and_read_async(message, GLib.PRIORITY_DEFAULT, null, (session, result) => {
|
||||||
try {
|
try {
|
||||||
@ -388,29 +497,54 @@ class QuotesExtension extends Extension {
|
|||||||
const response = decoder.decode(bytes.get_data());
|
const response = decoder.decode(bytes.get_data());
|
||||||
const data = JSON.parse(response);
|
const data = JSON.parse(response);
|
||||||
|
|
||||||
if (data.content && data.author) {
|
this._processProviderResponse(data, provider);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Failed to sync from ${provider.name}:`, e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Failed to create sync session for ${provider.name}:`, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_processProviderResponse(data, provider) {
|
||||||
|
const mapping = provider.mapping || { text: 'text', author: 'author' };
|
||||||
|
let quotes = [];
|
||||||
|
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
quotes = data;
|
||||||
|
} else if (provider.responseArray) {
|
||||||
|
quotes = this._getNestedProperty(data, provider.responseArray) || [];
|
||||||
|
} else {
|
||||||
|
quotes = [data];
|
||||||
|
}
|
||||||
|
|
||||||
|
quotes.forEach(item => {
|
||||||
|
const text = this._getNestedProperty(item, mapping.text);
|
||||||
|
const author = this._getNestedProperty(item, mapping.author);
|
||||||
|
|
||||||
|
if (text && author) {
|
||||||
const newQuote = {
|
const newQuote = {
|
||||||
text: data.content,
|
text: text,
|
||||||
author: data.author,
|
author: author,
|
||||||
hash: simpleHash(data.content + data.author),
|
hash: simpleHash(text + author),
|
||||||
source: 'api'
|
source: provider.name
|
||||||
};
|
};
|
||||||
|
|
||||||
const exists = this._quoteSources.synced.some(q => q.hash === newQuote.hash);
|
const exists = this._quoteSources.synced.some(q => q.hash === newQuote.hash);
|
||||||
|
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
this._quoteSources.synced.push(newQuote);
|
this._quoteSources.synced.push(newQuote);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this._mergeAndSaveQuotes();
|
this._mergeAndSaveQuotes();
|
||||||
this._loadQuotes();
|
this._loadQuotes();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} catch (e) {
|
_getNestedProperty(obj, path) {
|
||||||
console.error('Failed to sync quotes:', e);
|
return path.split('.').reduce((current, key) => current?.[key], obj);
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Failed to create sync session:', e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
112
prefs.js
112
prefs.js
@ -51,6 +51,28 @@ export default class QuotesPreferences extends ExtensionPreferences {
|
|||||||
});
|
});
|
||||||
timingGroup.add(syncRow);
|
timingGroup.add(syncRow);
|
||||||
|
|
||||||
|
// Show change indicator
|
||||||
|
const indicatorRow = new Adw.SwitchRow({
|
||||||
|
title: 'Show Change Indicator',
|
||||||
|
subtitle: 'Display countdown before quote changes',
|
||||||
|
active: settings.get_boolean('show-change-indicator'),
|
||||||
|
});
|
||||||
|
timingGroup.add(indicatorRow);
|
||||||
|
|
||||||
|
// Indicator duration
|
||||||
|
const indicatorDurationRow = new Adw.SpinRow({
|
||||||
|
title: 'Indicator Duration',
|
||||||
|
subtitle: 'Countdown duration in seconds',
|
||||||
|
adjustment: new Gtk.Adjustment({
|
||||||
|
lower: 1,
|
||||||
|
upper: 30,
|
||||||
|
step_increment: 1,
|
||||||
|
page_increment: 5,
|
||||||
|
value: settings.get_int('indicator-duration'),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
timingGroup.add(indicatorDurationRow);
|
||||||
|
|
||||||
// API Group
|
// API Group
|
||||||
const apiGroup = new Adw.PreferencesGroup({
|
const apiGroup = new Adw.PreferencesGroup({
|
||||||
title: 'API Settings',
|
title: 'API Settings',
|
||||||
@ -58,12 +80,72 @@ export default class QuotesPreferences extends ExtensionPreferences {
|
|||||||
});
|
});
|
||||||
behaviorPage.add(apiGroup);
|
behaviorPage.add(apiGroup);
|
||||||
|
|
||||||
// API URL
|
// API Providers
|
||||||
const apiRow = new Adw.EntryRow({
|
const apiProvidersRow = new Adw.ExpanderRow({
|
||||||
title: 'API URL',
|
title: 'API Providers Configuration',
|
||||||
text: settings.get_string('api-url'),
|
subtitle: 'Configure multiple quote providers',
|
||||||
});
|
});
|
||||||
apiGroup.add(apiRow);
|
|
||||||
|
const providersTextView = new Gtk.TextView({
|
||||||
|
buffer: new Gtk.TextBuffer(),
|
||||||
|
hexpand: true,
|
||||||
|
vexpand: true,
|
||||||
|
css_classes: ['card'],
|
||||||
|
monospace: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const scrolledProviders = new Gtk.ScrolledWindow({
|
||||||
|
child: providersTextView,
|
||||||
|
hexpand: true,
|
||||||
|
min_content_height: 200,
|
||||||
|
max_content_height: 400,
|
||||||
|
});
|
||||||
|
|
||||||
|
const providersContainer = new Gtk.Box({
|
||||||
|
orientation: Gtk.Orientation.VERTICAL,
|
||||||
|
spacing: 12,
|
||||||
|
margin_top: 12,
|
||||||
|
margin_bottom: 12,
|
||||||
|
margin_start: 12,
|
||||||
|
margin_end: 12,
|
||||||
|
});
|
||||||
|
|
||||||
|
const providersLabel = new Gtk.Label({
|
||||||
|
label: 'Provider Configuration (JSON):',
|
||||||
|
xalign: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
providersContainer.append(providersLabel);
|
||||||
|
providersContainer.append(scrolledProviders);
|
||||||
|
|
||||||
|
const providersPrefsRow = new Adw.PreferencesRow({
|
||||||
|
child: providersContainer,
|
||||||
|
});
|
||||||
|
|
||||||
|
apiProvidersRow.add_row(providersPrefsRow);
|
||||||
|
apiGroup.add(apiProvidersRow);
|
||||||
|
|
||||||
|
// Load current providers
|
||||||
|
const currentProviders = settings.get_string('api-providers');
|
||||||
|
try {
|
||||||
|
const formatted = JSON.stringify(JSON.parse(currentProviders), null, 2);
|
||||||
|
providersTextView.buffer.text = formatted;
|
||||||
|
} catch (e) {
|
||||||
|
providersTextView.buffer.text = currentProviders;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save on focus out
|
||||||
|
const focusController = new Gtk.EventControllerFocus();
|
||||||
|
focusController.connect('leave', () => {
|
||||||
|
const text = providersTextView.buffer.text;
|
||||||
|
try {
|
||||||
|
JSON.parse(text); // Validate JSON
|
||||||
|
settings.set_string('api-providers', text);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Invalid JSON in providers configuration');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
providersTextView.add_controller(focusController);
|
||||||
|
|
||||||
// Appearance Settings Page
|
// Appearance Settings Page
|
||||||
const appearancePage = new Adw.PreferencesPage({
|
const appearancePage = new Adw.PreferencesPage({
|
||||||
@ -400,12 +482,6 @@ export default class QuotesPreferences extends ExtensionPreferences {
|
|||||||
Gio.SettingsBindFlags.DEFAULT
|
Gio.SettingsBindFlags.DEFAULT
|
||||||
);
|
);
|
||||||
|
|
||||||
settings.bind(
|
|
||||||
'api-url',
|
|
||||||
apiRow,
|
|
||||||
'text',
|
|
||||||
Gio.SettingsBindFlags.DEFAULT
|
|
||||||
);
|
|
||||||
|
|
||||||
settings.bind(
|
settings.bind(
|
||||||
'font-size',
|
'font-size',
|
||||||
@ -442,5 +518,19 @@ export default class QuotesPreferences extends ExtensionPreferences {
|
|||||||
Gio.SettingsBindFlags.DEFAULT
|
Gio.SettingsBindFlags.DEFAULT
|
||||||
);
|
);
|
||||||
|
|
||||||
|
settings.bind(
|
||||||
|
'show-change-indicator',
|
||||||
|
indicatorRow,
|
||||||
|
'active',
|
||||||
|
Gio.SettingsBindFlags.DEFAULT
|
||||||
|
);
|
||||||
|
|
||||||
|
settings.bind(
|
||||||
|
'indicator-duration',
|
||||||
|
indicatorDurationRow,
|
||||||
|
'value',
|
||||||
|
Gio.SettingsBindFlags.DEFAULT
|
||||||
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
Binary file not shown.
@ -11,10 +11,10 @@
|
|||||||
<summary>Sync interval</summary>
|
<summary>Sync interval</summary>
|
||||||
<description>Time in seconds between API synchronizations</description>
|
<description>Time in seconds between API synchronizations</description>
|
||||||
</key>
|
</key>
|
||||||
<key name="api-url" type="s">
|
<key name="api-providers" type="s">
|
||||||
<default>'https://api.quotable.io/random'</default>
|
<default>'[{"name":"quotable","url":"https://api.quotable.io/random","method":"GET","mapping":{"text":"content","author":"author"},"enabled":true}]'</default>
|
||||||
<summary>API URL</summary>
|
<summary>API providers</summary>
|
||||||
<description>URL for fetching quotes from remote server</description>
|
<description>JSON array of API provider configurations</description>
|
||||||
</key>
|
</key>
|
||||||
<key name="font-size" type="i">
|
<key name="font-size" type="i">
|
||||||
<default>14</default>
|
<default>14</default>
|
||||||
@ -46,5 +46,15 @@
|
|||||||
<summary>Use custom quotes</summary>
|
<summary>Use custom quotes</summary>
|
||||||
<description>Whether to use custom quotes instead of built-in/synced quotes</description>
|
<description>Whether to use custom quotes instead of built-in/synced quotes</description>
|
||||||
</key>
|
</key>
|
||||||
|
<key name="show-change-indicator" type="b">
|
||||||
|
<default>true</default>
|
||||||
|
<summary>Show change indicator</summary>
|
||||||
|
<description>Whether to show countdown before quote changes</description>
|
||||||
|
</key>
|
||||||
|
<key name="indicator-duration" type="i">
|
||||||
|
<default>5</default>
|
||||||
|
<summary>Indicator duration</summary>
|
||||||
|
<description>Duration in seconds for the change indicator countdown</description>
|
||||||
|
</key>
|
||||||
</schema>
|
</schema>
|
||||||
</schemalist>
|
</schemalist>
|
Reference in New Issue
Block a user