refactor: Replace Webpack with Vite (#53)

This commit is contained in:
Razboy20
2024-01-24 19:40:30 -06:00
committed by GitHub
parent 1629c85818
commit 0560a01a55
112 changed files with 7322 additions and 32180 deletions

View File

@@ -0,0 +1,36 @@
import { BACKGROUND_MESSAGES } from '@shared/messages';
import { MessageListener } from 'chrome-extension-toolkit';
import onInstall from './events/onInstall';
import onServiceWorkerAlive from './events/onServiceWorkerAlive';
import onUpdate from './events/onUpdate';
import browserActionHandler from './handler/browserActionHandler';
import tabManagementHandler from './handler/tabManagementHandler';
import userScheduleHandler from './handler/userScheduleHandler';
onServiceWorkerAlive();
/**
* will be triggered on either install or update
* (will also be triggered on a user's sync'd browsers (on other devices)))
*/
chrome.runtime.onInstalled.addListener(details => {
switch (details.reason) {
case 'install':
onInstall();
break;
case 'update':
onUpdate();
break;
default:
break;
}
});
// initialize the message listener that will listen for messages from the content script
const messageListener = new MessageListener<BACKGROUND_MESSAGES>({
...browserActionHandler,
...tabManagementHandler,
...userScheduleHandler,
});
messageListener.listen();

View File

@@ -0,0 +1,8 @@
import { ExtensionStore } from '@shared/storage/ExtensionStore';
/**
* Called when the extension is first installed or synced onto a new machine
*/
export default async function onInstall() {
await ExtensionStore.set('version', chrome.runtime.getManifest().version);
}

View File

@@ -0,0 +1,9 @@
import { openDebugTab } from '../util/openDebugTab';
/**
* Called whenever the background service worker comes alive
* (usually around 30 seconds to 5 minutes after it was last alive)
*/
export default function onServiceWorkerAlive() {
openDebugTab();
}

View File

@@ -0,0 +1,11 @@
import { ExtensionStore } from '@shared/storage/ExtensionStore';
/**
* Called when the extension is updated (or when the extension is reloaded in development mode)
*/
export default async function onUpdate() {
await ExtensionStore.set({
version: chrome.runtime.getManifest().version,
lastUpdate: Date.now(),
});
}

View File

@@ -0,0 +1,20 @@
import BrowserActionMessages from '@shared/messages/BrowserActionMessages';
import { MessageHandler } from 'chrome-extension-toolkit';
const browserActionHandler: MessageHandler<BrowserActionMessages> = {
disableBrowserAction({ sender, sendResponse }) {
// by setting the popup to an empty string, clicking the browser action will not open the popup.html.
// we can then add an onClickListener to it from the content script
chrome.action.setPopup({ tabId: sender.tab?.id, popup: '' }).then(sendResponse);
},
enableBrowserAction({ sender, sendResponse }) {
chrome.action
.setPopup({
tabId: sender.tab?.id,
popup: 'popup.html',
})
.then(sendResponse);
},
};
export default browserActionHandler;

View File

@@ -0,0 +1,24 @@
import HotReloadingMessages from '@shared/messages/HotReloadingMessages';
import { DevStore } from '@shared/storage/DevStore';
import { MessageHandler } from 'chrome-extension-toolkit';
const hotReloadingHandler: MessageHandler<HotReloadingMessages> = {
async reloadExtension({ sendResponse }) {
const [isExtensionReloading, isTabReloading] = await Promise.all([
DevStore.get('isExtensionReloading'),
DevStore.get('isTabReloading'),
]);
if (!isExtensionReloading) return sendResponse();
if (isTabReloading) {
const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
const tabToReload = tabs[0];
await DevStore.set('reloadTabId', tabToReload?.id);
}
chrome.runtime.reload();
},
};
export default hotReloadingHandler;

View File

@@ -0,0 +1,20 @@
import TabManagementMessages from '@shared/messages/TabManagementMessages';
import { MessageHandler } from 'chrome-extension-toolkit';
import openNewTab from '../util/openNewTab';
const tabManagementHandler: MessageHandler<TabManagementMessages> = {
getTabId({ sendResponse, sender }) {
sendResponse(sender.tab?.id ?? -1);
},
openNewTab({ data, sender, sendResponse }) {
const { url } = data;
const nextIndex = sender.tab?.index ? sender.tab.index + 1 : undefined;
openNewTab(url, nextIndex).then(sendResponse);
},
removeTab({ data, sendResponse }) {
const { tabId } = data;
chrome.tabs.remove(tabId).then(sendResponse);
},
};
export default tabManagementHandler;

View File

@@ -0,0 +1,36 @@
import { UserScheduleMessages } from '@shared/messages/UserScheduleMessages';
import { Course } from '@shared/types/Course';
import { MessageHandler } from 'chrome-extension-toolkit';
import addCourse from '../lib/addCourse';
import clearCourses from '../lib/clearCourses';
import createSchedule from '../lib/createSchedule';
import deleteSchedule from '../lib/deleteSchedule';
import removeCourse from '../lib/removeCourse';
import renameSchedule from '../lib/renameSchedule';
import switchSchedule from '../lib/switchSchedule';
const userScheduleHandler: MessageHandler<UserScheduleMessages> = {
addCourse({ data, sendResponse }) {
addCourse(data.scheduleName, new Course(data.course)).then(sendResponse);
},
removeCourse({ data, sendResponse }) {
removeCourse(data.scheduleName, new Course(data.course)).then(sendResponse);
},
clearCourses({ data, sendResponse }) {
clearCourses(data.scheduleName).then(sendResponse);
},
switchSchedule({ data, sendResponse }) {
switchSchedule(data.scheduleName).then(sendResponse);
},
createSchedule({ data, sendResponse }) {
createSchedule(data.scheduleName).then(sendResponse);
},
deleteSchedule({ data, sendResponse }) {
deleteSchedule(data.scheduleName).then(sendResponse);
},
renameSchedule({ data, sendResponse }) {
renameSchedule(data.scheduleName, data.newName).then(sendResponse);
},
};
export default userScheduleHandler;

View File

@@ -0,0 +1,17 @@
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
import { Course } from '@shared/types/Course';
/**
*
*/
export default async function addCourse(scheduleName: string, course: Course): Promise<void> {
const schedules = await UserScheduleStore.get('schedules');
const activeSchedule = schedules.find(s => s.name === scheduleName);
if (!activeSchedule) {
throw new Error('Schedule not found');
}
activeSchedule.courses.push(course);
await UserScheduleStore.set('schedules', schedules);
}

View File

@@ -0,0 +1,11 @@
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
export default async function clearCourses(scheduleName: string): Promise<void> {
const schedules = await UserScheduleStore.get('schedules');
const schedule = schedules.find(schedule => schedule.name === scheduleName);
if (!schedule) {
throw new Error(`Schedule ${scheduleName} does not exist`);
}
schedule.courses = [];
await UserScheduleStore.set('schedules', schedules);
}

View File

@@ -0,0 +1,21 @@
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
/**
* Creates a new schedule with the given name
* @param scheduleName the name of the schedule to create
* @returns undefined if successful, otherwise an error message
*/
export default async function createSchedule(scheduleName: string): Promise<string | undefined> {
const schedules = await UserScheduleStore.get('schedules');
if (schedules.find(schedule => schedule.name === scheduleName)) {
return `Schedule ${scheduleName} already exists`;
}
schedules.push({
name: scheduleName,
courses: [],
});
await UserScheduleStore.set('schedules', schedules);
return undefined;
}

View File

@@ -0,0 +1,20 @@
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
export default async function deleteSchedule(scheduleName: string): Promise<string | undefined> {
const [schedules, activeIndex] = await Promise.all([
UserScheduleStore.get('schedules'),
UserScheduleStore.get('activeIndex'),
]);
const scheduleIndex = schedules.findIndex(schedule => schedule.name === scheduleName);
if (scheduleIndex === -1) {
return `Schedule ${scheduleName} does not exist`;
}
if (scheduleIndex === activeIndex) {
return 'Cannot delete active schedule';
}
schedules.splice(scheduleIndex, 1);
await UserScheduleStore.set('schedules', schedules);
return undefined;
}

View File

@@ -0,0 +1,17 @@
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
import { Course } from '@shared/types/Course';
/**
*
*/
export default async function removeCourse(scheduleName: string, course: Course): Promise<void> {
const schedules = await UserScheduleStore.get('schedules');
const activeSchedule = schedules.find(s => s.name === scheduleName);
if (!activeSchedule) {
throw new Error('Schedule not found');
}
activeSchedule.courses = activeSchedule.courses.filter(c => c.uniqueId !== course.uniqueId);
await UserScheduleStore.set('schedules', schedules);
}

View File

@@ -0,0 +1,17 @@
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
export default async function renameSchedule(scheduleName: string, newName: string): Promise<string | undefined> {
const schedules = await UserScheduleStore.get('schedules');
const scheduleIndex = schedules.findIndex(schedule => schedule.name === scheduleName);
if (scheduleIndex === -1) {
return `Schedule ${scheduleName} does not exist`;
}
if (schedules.find(schedule => schedule.name === newName)) {
return `Schedule ${newName} already exists`;
}
schedules[scheduleIndex].name = newName;
await UserScheduleStore.set('schedules', schedules);
return undefined;
}

View File

@@ -0,0 +1,12 @@
import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
export default async function switchSchedule(scheduleName: string): Promise<void> {
const schedules = await UserScheduleStore.get('schedules');
const scheduleIndex = schedules.findIndex(schedule => schedule.name === scheduleName);
if (scheduleIndex === -1) {
throw new Error(`Schedule ${scheduleName} does not exist`);
}
await UserScheduleStore.set('activeIndex', scheduleIndex);
}

View File

@@ -0,0 +1,42 @@
import { DevStore } from '@shared/storage/DevStore';
/**
* A list of websites that we don't want to reload when the extension reloads (becuase it'd be hella annoying lmao)
*/
const HOT_RELOADING_WHITELIST = [
'youtube.com',
'twitch.tv',
'github.dev',
'figma.com',
'netflix.com',
'disneyplus.com',
'hbomax.com',
'spotify.com',
'localhost:6006',
'docs.google.com',
'reddit.com',
'gmail.com',
'photopea.com',
'chat.openai.com',
];
/**
* Reloads the tab that was open when the extension was reloaded
* @returns a promise that resolves when the tab is reloaded
*/
export async function hotReloadTab(): Promise<void> {
const [isTabReloading, reloadTabId] = await Promise.all([
DevStore.get('isTabReloading'),
DevStore.get('reloadTabId'),
]);
if (!isTabReloading || !reloadTabId) return;
chrome.tabs.get(reloadTabId, tab => {
if (!tab?.id) return;
if (!tab.url) return;
if (!HOT_RELOADING_WHITELIST.find(url => tab.url?.includes(url))) {
chrome.tabs.reload(tab.id);
}
});
}

View File

@@ -0,0 +1,25 @@
import { DevStore } from '@shared/storage/DevStore';
/**
* Open the debug tab as the first tab
*/
export async function openDebugTab() {
if (process.env.NODE_ENV === 'development') {
const [debugTabId, wasDebugTabVisible] = await Promise.all([
DevStore.get('debugTabId'),
DevStore.get('wasDebugTabVisible'),
]);
const isAlreadyOpen = await (await chrome.tabs.query({})).some(tab => tab.id === debugTabId);
if (isAlreadyOpen) return;
const tab = await chrome.tabs.create({
url: chrome.runtime.getURL('debug.html'),
active: wasDebugTabVisible,
pinned: true,
index: 0,
});
await DevStore.set('debugTabId', tab.id);
}
}

View File

@@ -0,0 +1,10 @@
/**
* This is a helper function that opens a new tab in the current window, and focuses the window
* @param tabIndex - the index of the tab to open the new tab at (optional)
* @returns the tab that was opened
*/
export default async function openNewTab(url: string, tabIndex?: number): Promise<chrome.tabs.Tab> {
const tab = await chrome.tabs.create({ url, index: tabIndex, active: true });
await chrome.windows.update(tab.windowId, { focused: true });
return tab;
}