mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-06-13 15:14:40 +00:00
feat: add global theme toggle
This commit is contained in:
@@ -1,29 +1,18 @@
|
||||
import type { Plugin } from '../../core/types';
|
||||
import { BasePlugin, BooleanSetting } from '../../core/settings';
|
||||
|
||||
interface NotificationCollectorStorage {
|
||||
lastNotificationCount: number;
|
||||
lastCheckedTime: string;
|
||||
}
|
||||
|
||||
class NotificationCollectorPluginClass extends BasePlugin {
|
||||
@BooleanSetting({
|
||||
default: true,
|
||||
title: "Notification Collector",
|
||||
description: "Uncaps the 9+ limit for notifications, showing the real number.",
|
||||
})
|
||||
enabled!: boolean;
|
||||
}
|
||||
|
||||
// Create an instance to extract settings
|
||||
const settingsInstance = new NotificationCollectorPluginClass();
|
||||
|
||||
const notificationCollectorPlugin: Plugin<typeof settingsInstance.settings, NotificationCollectorStorage> = {
|
||||
const notificationCollectorPlugin: Plugin<{}, NotificationCollectorStorage> = {
|
||||
id: 'notificationCollector',
|
||||
name: 'Notification Collector',
|
||||
description: 'Collects and displays SEQTA notifications',
|
||||
version: '1.0.0',
|
||||
settings: settingsInstance.settings,
|
||||
settings: {},
|
||||
disableToggle: true,
|
||||
|
||||
run: async (api) => {
|
||||
let pollInterval: number | null = null;
|
||||
|
||||
@@ -80,30 +69,21 @@ const notificationCollectorPlugin: Plugin<typeof settingsInstance.settings, Noti
|
||||
pollInterval = null;
|
||||
const alertDiv = document.querySelector(".notifications__bubble___1EkSQ") as HTMLElement;
|
||||
if (alertDiv) {
|
||||
alertDiv.textContent = "9+";
|
||||
if (api.storage.lastNotificationCount > 9) {
|
||||
alertDiv.textContent = "9+";
|
||||
} else {
|
||||
alertDiv.textContent = api.storage.lastNotificationCount.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (api.settings.enabled) {
|
||||
api.seqta.onMount(".notifications__bubble___1EkSQ", (_) => {
|
||||
startPolling();
|
||||
});
|
||||
}
|
||||
|
||||
const enabledCallback = (value: any) => {
|
||||
if (value) {
|
||||
startPolling();
|
||||
} else {
|
||||
stopPolling();
|
||||
}
|
||||
};
|
||||
|
||||
api.settings.onChange('enabled', enabledCallback);
|
||||
api.seqta.onMount(".notifications__bubble___1EkSQ", (_) => {
|
||||
startPolling();
|
||||
});
|
||||
|
||||
return () => {
|
||||
stopPolling();
|
||||
api.settings.offChange('enabled', enabledCallback);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -5,9 +5,9 @@ class TestPluginClass extends BasePlugin {
|
||||
@BooleanSetting({
|
||||
default: true,
|
||||
title: "Test Plugin",
|
||||
description: "A test plugin for BetterSEQTA+",
|
||||
description: "Some random setting",
|
||||
})
|
||||
enabled!: boolean;
|
||||
someSetting!: boolean;
|
||||
}
|
||||
|
||||
const settingsInstance = new TestPluginClass();
|
||||
@@ -22,9 +22,14 @@ const testPlugin: Plugin<typeof settingsInstance.settings> = {
|
||||
run: async (api) => {
|
||||
console.log('Test plugin running');
|
||||
|
||||
api.seqta.onPageChange((page) => {
|
||||
const { unregister } = api.seqta.onPageChange((page) => {
|
||||
console.log('Page changed to', page);
|
||||
});
|
||||
|
||||
return () => {
|
||||
console.log('Test plugin stopped');
|
||||
unregister();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -2,53 +2,32 @@ import { settingsState } from '@/seqta/utils/listeners/SettingsState';
|
||||
import type { Plugin } from '../../core/types';
|
||||
import { convertTo12HourFormat } from '@/seqta/utils/convertTo12HourFormat';
|
||||
import { waitForElm } from '@/seqta/utils/waitForElm';
|
||||
import { BasePlugin, BooleanSetting } from '../../core/settings';
|
||||
|
||||
// Define only the typed settings - no need for redundant interface
|
||||
class TimetablePluginClass extends BasePlugin {
|
||||
@BooleanSetting({
|
||||
default: true,
|
||||
title: "Timetable Enhancer",
|
||||
description: "Adds extra features to the timetable view."
|
||||
})
|
||||
enabled!: boolean;
|
||||
}
|
||||
|
||||
// Create an instance to extract settings
|
||||
const settingsInstance = new TimetablePluginClass();
|
||||
|
||||
const timetablePlugin: Plugin<typeof settingsInstance.settings> = {
|
||||
const timetablePlugin: Plugin<{}, {}> = {
|
||||
id: 'timetable',
|
||||
name: 'Timetable Enhancer',
|
||||
description: 'Adds extra features to the timetable view',
|
||||
version: '1.0.0',
|
||||
settings: settingsInstance.settings,
|
||||
settings: {},
|
||||
disableToggle: true,
|
||||
|
||||
run: async (api) => {
|
||||
if (api.settings.enabled) {
|
||||
api.seqta.onMount('.timetablepage', handleTimetable)
|
||||
}
|
||||
|
||||
const enabledCallback = (value: any) => {
|
||||
if (value) {
|
||||
api.seqta.onMount('.timetablepage', handleTimetable)
|
||||
} else {
|
||||
const timetablePage = document.querySelector('.timetablepage')
|
||||
if (timetablePage) {
|
||||
const zoomControls = document.querySelector('.timetable-zoom-controls')
|
||||
if (zoomControls) zoomControls.remove()
|
||||
|
||||
const hideControls = document.querySelector('.timetable-hide-controls')
|
||||
if (hideControls) hideControls.remove()
|
||||
|
||||
resetTimetableStyles()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
api.settings.onChange('enabled', enabledCallback)
|
||||
|
||||
const { unregister } = api.seqta.onMount('.timetablepage', handleTimetable)
|
||||
|
||||
return () => {
|
||||
api.settings.offChange('enabled', enabledCallback)
|
||||
// Call the unregister function to remove the mount listener
|
||||
unregister();
|
||||
|
||||
const timetablePage = document.querySelector('.timetablepage')
|
||||
if (timetablePage) {
|
||||
const zoomControls = document.querySelector('.timetable-zoom-controls')
|
||||
if (zoomControls) zoomControls.remove()
|
||||
|
||||
const hideControls = document.querySelector('.timetable-hide-controls')
|
||||
if (hideControls) hideControls.remove()
|
||||
|
||||
resetTimetableStyles()
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ import browser from 'webextension-polyfill';
|
||||
function createSEQTAAPI(): SEQTAAPI {
|
||||
return {
|
||||
onMount: (selector, callback) => {
|
||||
eventManager.register(
|
||||
return eventManager.register(
|
||||
`${selector}Added`,
|
||||
{
|
||||
customCheck: (element) => element.matches(selector),
|
||||
@@ -22,11 +22,20 @@ function createSEQTAAPI(): SEQTAAPI {
|
||||
return path.split('/')[0];
|
||||
},
|
||||
onPageChange: (callback) => {
|
||||
window.addEventListener('hashchange', () => {
|
||||
const handler = () => {
|
||||
const page = window.location.hash.split('?page=/')[1] || '';
|
||||
callback(page.split('/')[0]);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
window.addEventListener('hashchange', handler);
|
||||
|
||||
// Return an unregister function
|
||||
return {
|
||||
unregister: () => {
|
||||
window.removeEventListener('hashchange', handler);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
import type { Plugin, PluginSettings } from './types';
|
||||
import { createPluginAPI } from './createAPI';
|
||||
import browser from 'webextension-polyfill';
|
||||
|
||||
interface PluginSettingsStorage {
|
||||
enabled?: boolean;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
interface StorageChange<T = any> {
|
||||
oldValue?: T;
|
||||
newValue?: T;
|
||||
}
|
||||
|
||||
export class PluginManager {
|
||||
private static instance: PluginManager;
|
||||
@@ -9,7 +20,9 @@ export class PluginManager {
|
||||
private cleanupFunctions: Map<string, () => void> = new Map();
|
||||
private listeners: Map<string, Set<(...args: any[]) => void>> = new Map();
|
||||
|
||||
private constructor() {}
|
||||
private constructor() {
|
||||
this.setupPluginStateListener();
|
||||
}
|
||||
|
||||
public static getInstance(): PluginManager {
|
||||
if (!PluginManager.instance) {
|
||||
@@ -66,6 +79,17 @@ export class PluginManager {
|
||||
try {
|
||||
const api = createPluginAPI(plugin);
|
||||
|
||||
// Check if plugin is enabled before starting
|
||||
if (plugin.disableToggle) {
|
||||
const settings = await browser.storage.local.get(`plugin.${pluginId}.settings`);
|
||||
const pluginSettings = settings[`plugin.${pluginId}.settings`] as PluginSettingsStorage | undefined;
|
||||
const enabled = pluginSettings?.enabled ?? true;
|
||||
if (!enabled) {
|
||||
console.info(`Plugin "${pluginId}" is disabled, skipping initialization`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for both settings and storage to be loaded before starting the plugin
|
||||
await Promise.all([
|
||||
(api.settings as any).loaded,
|
||||
@@ -145,9 +169,21 @@ export class PluginManager {
|
||||
}];
|
||||
});
|
||||
|
||||
if (plugin.disableToggle) {
|
||||
settingsEntries.push([
|
||||
'enabled', {
|
||||
id: 'enabled',
|
||||
title: plugin.name,
|
||||
description: plugin.description,
|
||||
type: 'boolean',
|
||||
default: true
|
||||
}
|
||||
])
|
||||
}
|
||||
return {
|
||||
pluginId: id,
|
||||
name: plugin.name,
|
||||
description: plugin.description,
|
||||
settings: Object.fromEntries(settingsEntries)
|
||||
};
|
||||
});
|
||||
@@ -177,4 +213,36 @@ export class PluginManager {
|
||||
listeners.delete(callback);
|
||||
}
|
||||
}
|
||||
|
||||
// Add handler for plugin enable/disable state changes
|
||||
private async handlePluginStateChange(pluginId: string, enabled: boolean): Promise<void> {
|
||||
if (enabled) {
|
||||
await this.startPlugin(pluginId);
|
||||
} else {
|
||||
await this.stopPlugin(pluginId);
|
||||
}
|
||||
}
|
||||
|
||||
// Add listener for plugin settings changes
|
||||
private setupPluginStateListener(): void {
|
||||
browser.storage.onChanged.addListener((changes: { [key: string]: StorageChange }, area: string) => {
|
||||
if (area !== 'local') return;
|
||||
|
||||
for (const [key, change] of Object.entries(changes)) {
|
||||
const match = key.match(/^plugin\.(.+)\.settings$/);
|
||||
if (!match) continue;
|
||||
|
||||
const pluginId = match[1];
|
||||
const plugin = this.plugins.get(pluginId);
|
||||
if (!plugin?.disableToggle) continue;
|
||||
|
||||
const enabled = (change.newValue as PluginSettingsStorage)?.enabled ?? true;
|
||||
const wasEnabled = (change.oldValue as PluginSettingsStorage)?.enabled ?? true;
|
||||
|
||||
if (enabled !== wasEnabled) {
|
||||
this.handlePluginStateChange(pluginId, enabled);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -56,10 +56,10 @@ export type SettingsAPI<T extends PluginSettings> = {
|
||||
}
|
||||
|
||||
export interface SEQTAAPI {
|
||||
onMount: (selector: string, callback: (element: Element) => void) => void;
|
||||
onMount: (selector: string, callback: (element: Element) => void) => { unregister: () => void };
|
||||
getFiber: (selector: string) => ReactFiber;
|
||||
getCurrentPage: () => string;
|
||||
onPageChange: (callback: (page: string) => void) => void;
|
||||
onPageChange: (callback: (page: string) => void) => { unregister: () => void };
|
||||
}
|
||||
|
||||
export interface StorageAPI<T = any> {
|
||||
@@ -101,5 +101,6 @@ export interface Plugin<T extends PluginSettings = PluginSettings, S = any> {
|
||||
description: string;
|
||||
version: string;
|
||||
settings: T;
|
||||
run: (api: PluginAPI<T, S>) => void | Promise<void> | (() => void) | Promise<() => void>;
|
||||
disableToggle?: boolean; // Optional flag to show/hide the plugin's enable/disable toggle in settings
|
||||
run: (api: PluginAPI<T, S>) => void | Promise<void> | (() => void) | Promise<(() => void)>;
|
||||
}
|
||||
Reference in New Issue
Block a user