multiple schedule suppport kinda
This commit is contained in:
27
package-lock.json
generated
27
package-lock.json
generated
@@ -9,7 +9,7 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/sql.js": "^1.4.4",
|
"@types/sql.js": "^1.4.4",
|
||||||
"chrome-extension-toolkit": "^0.0.37",
|
"chrome-extension-toolkit": "^0.0.48",
|
||||||
"classnames": "^2.3.2",
|
"classnames": "^2.3.2",
|
||||||
"clean-webpack-plugin": "^4.0.0",
|
"clean-webpack-plugin": "^4.0.0",
|
||||||
"highcharts": "^10.3.3",
|
"highcharts": "^10.3.3",
|
||||||
@@ -29,6 +29,7 @@
|
|||||||
"@types/react": "^18.0.26",
|
"@types/react": "^18.0.26",
|
||||||
"@types/react-dom": "^18.0.9",
|
"@types/react-dom": "^18.0.9",
|
||||||
"@types/semver": "^7.3.13",
|
"@types/semver": "^7.3.13",
|
||||||
|
"@types/uuid": "^9.0.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.47.0",
|
"@typescript-eslint/eslint-plugin": "^5.47.0",
|
||||||
"@typescript-eslint/parser": "^5.47.0",
|
"@typescript-eslint/parser": "^5.47.0",
|
||||||
"archiver": "^5.3.1",
|
"archiver": "^5.3.1",
|
||||||
@@ -3294,6 +3295,12 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/uuid": {
|
||||||
|
"version": "9.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.1.tgz",
|
||||||
|
"integrity": "sha512-rFT3ak0/2trgvp4yYZo5iKFEPsET7vKydKF+VRCxlQ9bpheehyAJH89dAkaLEq/j/RZXJIqcgsmPJKUP1Z28HA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@types/ws": {
|
"node_modules/@types/ws": {
|
||||||
"version": "8.5.3",
|
"version": "8.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
|
||||||
@@ -4808,9 +4815,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/chrome-extension-toolkit": {
|
"node_modules/chrome-extension-toolkit": {
|
||||||
"version": "0.0.37",
|
"version": "0.0.48",
|
||||||
"resolved": "https://registry.npmjs.org/chrome-extension-toolkit/-/chrome-extension-toolkit-0.0.37.tgz",
|
"resolved": "https://registry.npmjs.org/chrome-extension-toolkit/-/chrome-extension-toolkit-0.0.48.tgz",
|
||||||
"integrity": "sha512-j8umRVPr6uKx77a191zIUCaQlq4KE2J1+ShXxsW1TEJIPKBxGcOfwH3N3OhDGnoCF/3shAgs6nZSg2uIvZJsfg==",
|
"integrity": "sha512-maShnkzOxMOWcsKaG2dFzpRwabDGBoUrkjHGwEEjYbgnPPmqTYKQAywibUAdGC4gpJL1iJyOJN+sUuha3DsloQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0"
|
"react-dom": "^18.2.0"
|
||||||
@@ -20080,6 +20087,12 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/uuid": {
|
||||||
|
"version": "9.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.1.tgz",
|
||||||
|
"integrity": "sha512-rFT3ak0/2trgvp4yYZo5iKFEPsET7vKydKF+VRCxlQ9bpheehyAJH89dAkaLEq/j/RZXJIqcgsmPJKUP1Z28HA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"@types/ws": {
|
"@types/ws": {
|
||||||
"version": "8.5.3",
|
"version": "8.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
|
||||||
@@ -21236,9 +21249,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chrome-extension-toolkit": {
|
"chrome-extension-toolkit": {
|
||||||
"version": "0.0.37",
|
"version": "0.0.48",
|
||||||
"resolved": "https://registry.npmjs.org/chrome-extension-toolkit/-/chrome-extension-toolkit-0.0.37.tgz",
|
"resolved": "https://registry.npmjs.org/chrome-extension-toolkit/-/chrome-extension-toolkit-0.0.48.tgz",
|
||||||
"integrity": "sha512-j8umRVPr6uKx77a191zIUCaQlq4KE2J1+ShXxsW1TEJIPKBxGcOfwH3N3OhDGnoCF/3shAgs6nZSg2uIvZJsfg==",
|
"integrity": "sha512-maShnkzOxMOWcsKaG2dFzpRwabDGBoUrkjHGwEEjYbgnPPmqTYKQAywibUAdGC4gpJL1iJyOJN+sUuha3DsloQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0"
|
"react-dom": "^18.2.0"
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/sql.js": "^1.4.4",
|
"@types/sql.js": "^1.4.4",
|
||||||
"chrome-extension-toolkit": "^0.0.37",
|
"chrome-extension-toolkit": "^0.0.48",
|
||||||
"classnames": "^2.3.2",
|
"classnames": "^2.3.2",
|
||||||
"clean-webpack-plugin": "^4.0.0",
|
"clean-webpack-plugin": "^4.0.0",
|
||||||
"highcharts": "^10.3.3",
|
"highcharts": "^10.3.3",
|
||||||
@@ -34,6 +34,7 @@
|
|||||||
"@types/react": "^18.0.26",
|
"@types/react": "^18.0.26",
|
||||||
"@types/react-dom": "^18.0.9",
|
"@types/react-dom": "^18.0.9",
|
||||||
"@types/semver": "^7.3.13",
|
"@types/semver": "^7.3.13",
|
||||||
|
"@types/uuid": "^9.0.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.47.0",
|
"@typescript-eslint/eslint-plugin": "^5.47.0",
|
||||||
"@typescript-eslint/parser": "^5.47.0",
|
"@typescript-eslint/parser": "^5.47.0",
|
||||||
"archiver": "^5.3.1",
|
"archiver": "^5.3.1",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import onInstall from './events/onInstall';
|
|||||||
import onNewChromeSession from './events/onNewChromeSession';
|
import onNewChromeSession from './events/onNewChromeSession';
|
||||||
import onServiceWorkerAlive from './events/onServiceWorkerAlive';
|
import onServiceWorkerAlive from './events/onServiceWorkerAlive';
|
||||||
import onUpdate from './events/onUpdate';
|
import onUpdate from './events/onUpdate';
|
||||||
import { SessionStore } from '../shared/storage/SessionStore';
|
import { sessionStore } from '../shared/storage/sessionStore';
|
||||||
import browserActionHandler from './handler/browserActionHandler';
|
import browserActionHandler from './handler/browserActionHandler';
|
||||||
import hotReloadingHandler from './handler/hotReloadingHandler';
|
import hotReloadingHandler from './handler/hotReloadingHandler';
|
||||||
import tabManagementHandler from './handler/tabManagementHandler';
|
import tabManagementHandler from './handler/tabManagementHandler';
|
||||||
@@ -38,9 +38,9 @@ const messageListener = new MessageListener<BACKGROUND_MESSAGES>({
|
|||||||
|
|
||||||
messageListener.listen();
|
messageListener.listen();
|
||||||
|
|
||||||
SessionStore.getChromeSessionId().then(async chromeSessionId => {
|
sessionStore.get('chromeSessionId').then(async chromeSessionId => {
|
||||||
if (!chromeSessionId) {
|
if (!chromeSessionId) {
|
||||||
await SessionStore.setChromeSessionId(generateRandomId(10));
|
await sessionStore.set('chromeSessionId', generateRandomId(10));
|
||||||
onNewChromeSession();
|
onNewChromeSession();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { SECOND } from 'src/shared/util/time';
|
import { extensionStore } from '../../shared/storage/extensionStore';
|
||||||
import { ExtensionStore } from '../../shared/storage/ExtensionStore';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the extension is first installed or synced onto a new machine
|
* Called when the extension is first installed or synced onto a new machine
|
||||||
*/
|
*/
|
||||||
export default async function onInstall() {
|
export default async function onInstall() {
|
||||||
await ExtensionStore.setVersion(chrome.runtime.getManifest().version);
|
await extensionStore.set('version', chrome.runtime.getManifest().version);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import { hotReloadTab } from 'src/background/util/hotReloadTab';
|
import { hotReloadTab } from 'src/background/util/hotReloadTab';
|
||||||
import { ExtensionStore } from '../../shared/storage/ExtensionStore';
|
import { extensionStore } from '../../shared/storage/extensionStore';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the extension is updated (or when the extension is reloaded in development mode)
|
* Called when the extension is updated (or when the extension is reloaded in development mode)
|
||||||
*/
|
*/
|
||||||
export default async function onUpdate() {
|
export default async function onUpdate() {
|
||||||
await Promise.all([
|
await extensionStore.set({
|
||||||
ExtensionStore.setLastUpdate(Date.now()),
|
version: chrome.runtime.getManifest().version,
|
||||||
ExtensionStore.setVersion(chrome.runtime.getManifest().version),
|
lastUpdate: Date.now(),
|
||||||
]);
|
});
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'development') {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
hotReloadTab();
|
hotReloadTab();
|
||||||
|
|||||||
@@ -1,18 +1,17 @@
|
|||||||
import HotReloadingMessages from 'src/shared/messages/HotReloadingMessages';
|
import HotReloadingMessages from 'src/shared/messages/HotReloadingMessages';
|
||||||
import { MessageHandler } from 'chrome-extension-toolkit';
|
import { MessageHandler } from 'chrome-extension-toolkit';
|
||||||
import { DevStore } from 'src/shared/storage/DevStore';
|
import { devStore } from 'src/shared/storage/devStore';
|
||||||
|
|
||||||
const hotReloadingHandler: MessageHandler<HotReloadingMessages> = {
|
const hotReloadingHandler: MessageHandler<HotReloadingMessages> = {
|
||||||
async reloadExtension({ sendResponse }) {
|
async reloadExtension({ sendResponse }) {
|
||||||
const isExtensionReloading = await DevStore.getIsExtensionReloading();
|
const { isExtensionReloading, isTabReloading } = await devStore.get(['isExtensionReloading', 'isTabReloading']);
|
||||||
if (!isExtensionReloading) return sendResponse();
|
if (!isExtensionReloading) return sendResponse();
|
||||||
|
|
||||||
const isTabReloading = await DevStore.getIsExtensionReloading();
|
|
||||||
if (isTabReloading) {
|
if (isTabReloading) {
|
||||||
const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
|
const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
|
||||||
const tabToReload = tabs[0];
|
const tabToReload = tabs[0];
|
||||||
|
|
||||||
await DevStore.setReloadTabId(tabToReload?.id);
|
await devStore.set('reloadTabId', tabToReload?.id);
|
||||||
}
|
}
|
||||||
chrome.runtime.reload();
|
chrome.runtime.reload();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { DevStore } from 'src/shared/storage/DevStore';
|
import { devStore } from 'src/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)
|
* A list of websites that we don't want to reload when the extension reloads (becuase it'd be hella annoying lmao)
|
||||||
@@ -24,9 +24,7 @@ const HOT_RELOADING_WHITELIST = [
|
|||||||
* @returns a promise that resolves when the tab is reloaded
|
* @returns a promise that resolves when the tab is reloaded
|
||||||
*/
|
*/
|
||||||
export async function hotReloadTab(): Promise<void> {
|
export async function hotReloadTab(): Promise<void> {
|
||||||
const { getIsTabReloading, getReloadTabId } = DevStore;
|
const { isTabReloading, reloadTabId } = await devStore.get(['isTabReloading', 'reloadTabId']);
|
||||||
|
|
||||||
const [isTabReloading, reloadTabId] = await Promise.all([getIsTabReloading(), getReloadTabId()]);
|
|
||||||
|
|
||||||
if (!isTabReloading || !reloadTabId) return;
|
if (!isTabReloading || !reloadTabId) return;
|
||||||
|
|
||||||
|
|||||||
@@ -1,24 +1,22 @@
|
|||||||
import { DevStore } from 'src/shared/storage/DevStore';
|
import { devStore } from 'src/shared/storage/devStore';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open the debug tab as the first tab
|
* Open the debug tab as the first tab
|
||||||
*/
|
*/
|
||||||
export async function openDebugTab() {
|
export async function openDebugTab() {
|
||||||
if (process.env.NODE_ENV === 'development') {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
const debugTabId = await DevStore.getDebugTabId();
|
const { debugTabId, wasDebugTabVisible } = await devStore.get(['debugTabId', 'wasDebugTabVisible']);
|
||||||
|
|
||||||
const isAlreadyOpen = await (await chrome.tabs.query({})).some(tab => tab.id === debugTabId);
|
const isAlreadyOpen = await (await chrome.tabs.query({})).some(tab => tab.id === debugTabId);
|
||||||
if (isAlreadyOpen) return;
|
if (isAlreadyOpen) return;
|
||||||
|
|
||||||
const wasVisible = await DevStore.getWasDebugTabVisible();
|
|
||||||
|
|
||||||
const tab = await chrome.tabs.create({
|
const tab = await chrome.tabs.create({
|
||||||
url: chrome.runtime.getURL('debug.html'),
|
url: chrome.runtime.getURL('debug.html'),
|
||||||
active: wasVisible,
|
active: wasDebugTabVisible,
|
||||||
pinned: true,
|
pinned: true,
|
||||||
index: 0,
|
index: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
await DevStore.setDebugTabId(tab.id);
|
await devStore.set('debugTabId', tab.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import './hotReload';
|
import './hotReload';
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { render } from 'react-dom';
|
import { render } from 'react-dom';
|
||||||
import { DevStore } from 'src/shared/storage/DevStore';
|
import { devStore } from 'src/shared/storage/devStore';
|
||||||
|
|
||||||
const manifest = chrome.runtime.getManifest();
|
const manifest = chrome.runtime.getManifest();
|
||||||
|
|
||||||
@@ -69,11 +69,7 @@ function DevDashboard() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onVisibilityChange = () => {
|
const onVisibilityChange = () => {
|
||||||
if (document.visibilityState === 'visible') {
|
devStore.set('wasDebugTabVisible', document.visibilityState === 'visible');
|
||||||
DevStore.setWasDebugTabVisible(true);
|
|
||||||
} else {
|
|
||||||
DevStore.setWasDebugTabVisible(false);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
document.addEventListener('visibilitychange', onVisibilityChange);
|
document.addEventListener('visibilitychange', onVisibilityChange);
|
||||||
return () => {
|
return () => {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ interface IDevStore {
|
|||||||
reloadTabId?: number;
|
reloadTabId?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DevStore = createLocalStore<IDevStore>({
|
export const devStore = createLocalStore<IDevStore>({
|
||||||
debugTabId: undefined,
|
debugTabId: undefined,
|
||||||
isTabReloading: true,
|
isTabReloading: true,
|
||||||
wasDebugTabVisible: false,
|
wasDebugTabVisible: false,
|
||||||
@@ -24,5 +24,4 @@ export const DevStore = createLocalStore<IDevStore>({
|
|||||||
reloadTabId: undefined,
|
reloadTabId: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
debugStore({ devStore });
|
||||||
debugStore({ DevStore });
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { v4 as uuidv4 } from 'uuid';
|
|
||||||
import { createLocalStore, debugStore } from 'chrome-extension-toolkit';
|
import { createLocalStore, debugStore } from 'chrome-extension-toolkit';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -9,31 +8,11 @@ interface IExtensionStore {
|
|||||||
version: string;
|
version: string;
|
||||||
/** When was the last update */
|
/** When was the last update */
|
||||||
lastUpdate: number;
|
lastUpdate: number;
|
||||||
/** A unique identifier generated for the current user in lieu of a userId */
|
|
||||||
deviceId: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Actions {
|
export const extensionStore = createLocalStore<IExtensionStore>({
|
||||||
getDeviceId(): Promise<string>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ExtensionStore = createLocalStore<IExtensionStore, Actions>(
|
|
||||||
{
|
|
||||||
version: chrome.runtime.getManifest().version,
|
version: chrome.runtime.getManifest().version,
|
||||||
lastUpdate: Date.now(),
|
lastUpdate: Date.now(),
|
||||||
deviceId: '',
|
});
|
||||||
},
|
|
||||||
store => ({
|
|
||||||
getDeviceId: async () => {
|
|
||||||
const deviceId = await store.getDeviceId();
|
|
||||||
if (deviceId) {
|
|
||||||
return deviceId;
|
|
||||||
}
|
|
||||||
const newDeviceId = uuidv4();
|
|
||||||
await store.setDeviceId(newDeviceId);
|
|
||||||
return newDeviceId;
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
debugStore({ ExtensionStore });
|
debugStore({ extensionStore });
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ interface IOptionsStore {
|
|||||||
shouldScrollToLoad: boolean;
|
shouldScrollToLoad: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const OptionsStore = createSyncStore<IOptionsStore>({
|
export const optionsStore = createSyncStore<IOptionsStore>({
|
||||||
shouldHighlightConflicts: true,
|
shouldHighlightConflicts: true,
|
||||||
shouldScrollToLoad: true,
|
shouldScrollToLoad: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
debugStore({ OptionsStore });
|
debugStore({ optionsStore });
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ interface ISessionStore {
|
|||||||
chromeSessionId?: string;
|
chromeSessionId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SessionStore = createSessionStore<ISessionStore>({
|
export const sessionStore = createSessionStore<ISessionStore>({
|
||||||
chromeSessionId: undefined,
|
chromeSessionId: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
debugStore({ SessionStore });
|
debugStore({ sessionStore });
|
||||||
|
|||||||
@@ -1,62 +1,15 @@
|
|||||||
import { createLocalStore, debugStore } from 'chrome-extension-toolkit';
|
import { createLocalStore, debugStore } from 'chrome-extension-toolkit';
|
||||||
import { Course } from 'src/shared/types/Course';
|
import { UserSchedule } from 'src/shared/types/UserSchedule';
|
||||||
/**
|
|
||||||
* A store that is used for storing user options
|
|
||||||
*/
|
|
||||||
interface IUserScheduleStore {
|
interface IUserScheduleStore {
|
||||||
current: string;
|
schedules: UserSchedule[];
|
||||||
schedules: {
|
|
||||||
[id: string]: Course[];
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Actions {
|
/**
|
||||||
createSchedule(name: string): Promise<void>;
|
* A store that is used for storing user schedules (and the active schedule)
|
||||||
addCourseToSchedule(name: string, course: Course): Promise<void>;
|
*/
|
||||||
removeCourseFromSchedule(name: string, course: Course): Promise<void>;
|
export const userScheduleStore = createLocalStore<IUserScheduleStore>({
|
||||||
removeSchedule(name: string): Promise<void>;
|
schedules: [],
|
||||||
getSchedule(name: string): Promise<Course[] | undefined>;
|
});
|
||||||
}
|
|
||||||
|
|
||||||
const UserScheduleStore = createLocalStore<IUserScheduleStore, Actions>(
|
debugStore({ userScheduleStore });
|
||||||
{
|
|
||||||
current: 'Schedule 1',
|
|
||||||
schedules: {},
|
|
||||||
},
|
|
||||||
store => ({
|
|
||||||
async createSchedule(name: string) {
|
|
||||||
const schedules = await store.getSchedules();
|
|
||||||
if (!schedules[name]) {
|
|
||||||
schedules[name] = [];
|
|
||||||
await store.setSchedules(schedules as any);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async removeSchedule(name: string) {
|
|
||||||
const schedules = await store.getSchedules();
|
|
||||||
delete schedules[name];
|
|
||||||
await store.setSchedules(schedules);
|
|
||||||
},
|
|
||||||
async getSchedule(name) {
|
|
||||||
const schedules = await store.getSchedules();
|
|
||||||
return schedules[name]?.map(course => new Course(course));
|
|
||||||
},
|
|
||||||
async addCourseToSchedule(name, course) {
|
|
||||||
const schedules = await store.getSchedules();
|
|
||||||
const scheduleToEdit = schedules[name];
|
|
||||||
if (scheduleToEdit) {
|
|
||||||
scheduleToEdit.push(course);
|
|
||||||
await store.setSchedules(schedules);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async removeCourseFromSchedule(name, course) {
|
|
||||||
const schedules = await store.getSchedules();
|
|
||||||
const scheduleToEdit = schedules[name];
|
|
||||||
if (scheduleToEdit) {
|
|
||||||
schedules[name] = scheduleToEdit.filter(c => c.uniqueId !== course.uniqueId);
|
|
||||||
await store.setSchedules(schedules);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
debugStore({ UserScheduleStore });
|
|
||||||
|
|||||||
@@ -45,6 +45,9 @@ export class Course {
|
|||||||
courseName: string;
|
courseName: string;
|
||||||
/** The unique identifier for which department that a course belongs to, i.e. CS, MAL, etc. */
|
/** The unique identifier for which department that a course belongs to, i.e. CS, MAL, etc. */
|
||||||
department: string;
|
department: string;
|
||||||
|
|
||||||
|
/** The number of credits that a course is worth */
|
||||||
|
creditHours: number;
|
||||||
/** Is the course open, closed, waitlisted, or cancelled? */
|
/** Is the course open, closed, waitlisted, or cancelled? */
|
||||||
status: Status;
|
status: Status;
|
||||||
/** all the people that are teaching this course, and some metadata about their names */
|
/** all the people that are teaching this course, and some metadata about their names */
|
||||||
|
|||||||
35
src/shared/types/UserSchedule.ts
Normal file
35
src/shared/types/UserSchedule.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { Serialized } from 'chrome-extension-toolkit';
|
||||||
|
import { Course } from './Course';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a user's schedule that is stored in the extension
|
||||||
|
*/
|
||||||
|
export class UserSchedule {
|
||||||
|
courses: Course[];
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
constructor(schedule: Serialized<UserSchedule>) {
|
||||||
|
this.courses = schedule.courses.map(c => new Course(c));
|
||||||
|
this.id = schedule.id;
|
||||||
|
this.name = schedule.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
containsCourse(course: Course): boolean {
|
||||||
|
return this.courses.some(c => c.uniqueId === course.uniqueId);
|
||||||
|
}
|
||||||
|
|
||||||
|
getCreditHours(): number {
|
||||||
|
return this.courses.reduce((acc, course) => acc + course.creditHours, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
addCourse(course: Course): void {
|
||||||
|
if (!this.containsCourse(course)) {
|
||||||
|
this.courses.push(course);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removeCourse(course: Course): void {
|
||||||
|
this.courses = this.courses.filter(c => c.uniqueId !== course.uniqueId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Course, ScrapedRow } from 'src/shared/types/Course';
|
import { Course, ScrapedRow } from 'src/shared/types/Course';
|
||||||
import { useKeyPress } from '../hooks/useKeyPress';
|
import { useKeyPress } from '../hooks/useKeyPress';
|
||||||
|
import useUserSchedules from '../hooks/useUserSchedules';
|
||||||
import { CourseCatalogScraper } from '../lib/CourseCatalogScraper';
|
import { CourseCatalogScraper } from '../lib/CourseCatalogScraper';
|
||||||
import getCourseTableRows from '../lib/getCourseTableRows';
|
import getCourseTableRows from '../lib/getCourseTableRows';
|
||||||
import { SiteSupport } from '../lib/getSiteSupport';
|
import { SiteSupport } from '../lib/getSiteSupport';
|
||||||
@@ -43,6 +44,9 @@ export default function CourseCatalogMain({ support }: Props) {
|
|||||||
setRows([...rows, ...newRows]);
|
setRows([...rows, ...newRows]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const schedules = useUserSchedules();
|
||||||
|
const [activeSchedule] = schedules;
|
||||||
|
|
||||||
const handleRowButtonClick = (course: Course) => () => {
|
const handleRowButtonClick = (course: Course) => () => {
|
||||||
setSelectedCourse(course);
|
setSelectedCourse(course);
|
||||||
};
|
};
|
||||||
@@ -67,11 +71,18 @@ export default function CourseCatalogMain({ support }: Props) {
|
|||||||
key={row.course.uniqueId}
|
key={row.course.uniqueId}
|
||||||
row={row}
|
row={row}
|
||||||
isSelected={row.course.uniqueId === selectedCourse?.uniqueId}
|
isSelected={row.course.uniqueId === selectedCourse?.uniqueId}
|
||||||
|
isInActiveSchedule={Boolean(activeSchedule?.containsCourse(row.course))}
|
||||||
onClick={handleRowButtonClick(row.course)}
|
onClick={handleRowButtonClick(row.course)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{selectedCourse && <CoursePopup course={selectedCourse} onClose={handleClearSelectedCourse} />}
|
{selectedCourse && (
|
||||||
|
<CoursePopup
|
||||||
|
course={selectedCourse}
|
||||||
|
activeSchedule={activeSchedule}
|
||||||
|
onClose={handleClearSelectedCourse}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<AutoLoad addRows={addRows} />
|
<AutoLoad addRows={addRows} />
|
||||||
</ExtensionRoot>
|
</ExtensionRoot>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { bMessenger } from 'src/shared/messages';
|
import { bMessenger } from 'src/shared/messages';
|
||||||
|
import { userScheduleStore } from 'src/shared/storage/userScheduleStore';
|
||||||
import { Course } from 'src/shared/types/Course';
|
import { Course } from 'src/shared/types/Course';
|
||||||
|
import { UserSchedule } from 'src/shared/types/UserSchedule';
|
||||||
import { Button } from 'src/views/components/common/Button/Button';
|
import { Button } from 'src/views/components/common/Button/Button';
|
||||||
import Card from 'src/views/components/common/Card/Card';
|
import Card from 'src/views/components/common/Card/Card';
|
||||||
import Icon from 'src/views/components/common/Icon/Icon';
|
import Icon from 'src/views/components/common/Icon/Icon';
|
||||||
@@ -8,6 +10,7 @@ import Text from 'src/views/components/common/Text/Text';
|
|||||||
import styles from './CourseButtons.module.scss';
|
import styles from './CourseButtons.module.scss';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
activeSchedule?: UserSchedule;
|
||||||
course: Course;
|
course: Course;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -17,7 +20,7 @@ const { openNewTab } = bMessenger;
|
|||||||
* This component displays the buttons for the course info popup, that allow the user to either
|
* This component displays the buttons for the course info popup, that allow the user to either
|
||||||
* navigate to other pages that are useful for the course, or to do actions on the current course.
|
* navigate to other pages that are useful for the course, or to do actions on the current course.
|
||||||
*/
|
*/
|
||||||
export default function CourseButtons({ course }: Props) {
|
export default function CourseButtons({ course, activeSchedule }: Props) {
|
||||||
const openRateMyProfessorURL = () => {
|
const openRateMyProfessorURL = () => {
|
||||||
const primaryInstructor = course.instructors?.[0];
|
const primaryInstructor = course.instructors?.[0];
|
||||||
if (!primaryInstructor) return;
|
if (!primaryInstructor) return;
|
||||||
@@ -61,6 +64,17 @@ export default function CourseButtons({ course }: Props) {
|
|||||||
openNewTab({ url: url.toString() });
|
openNewTab({ url: url.toString() });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const saveCourse = async () => {
|
||||||
|
const schedules = await userScheduleStore.get('schedules');
|
||||||
|
const active = schedules.find(schedule => schedule.id === activeSchedule?.id);
|
||||||
|
|
||||||
|
if (!active) return;
|
||||||
|
|
||||||
|
active.addCourse(course);
|
||||||
|
|
||||||
|
await userScheduleStore.set('schedules', schedules);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className={styles.container}>
|
<Card className={styles.container}>
|
||||||
<Button
|
<Button
|
||||||
@@ -86,7 +100,7 @@ export default function CourseButtons({ course }: Props) {
|
|||||||
</Text>
|
</Text>
|
||||||
<Icon className={styles.icon} color='white' name='collections_bookmark' size='medium' />
|
<Icon className={styles.icon} color='white' name='collections_bookmark' size='medium' />
|
||||||
</Button>
|
</Button>
|
||||||
<Button type='success' className={styles.button}>
|
<Button disabled={!activeSchedule} onClick={saveCourse} type='success' className={styles.button}>
|
||||||
<Text size='medium' weight='regular' color='white'>
|
<Text size='medium' weight='regular' color='white'>
|
||||||
Save
|
Save
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Course } from 'src/shared/types/Course';
|
import { Course } from 'src/shared/types/Course';
|
||||||
|
import { UserSchedule } from 'src/shared/types/UserSchedule';
|
||||||
import Card from 'src/views/components/common/Card/Card';
|
import Card from 'src/views/components/common/Card/Card';
|
||||||
import Divider from 'src/views/components/common/Divider/Divider';
|
import Divider from 'src/views/components/common/Divider/Divider';
|
||||||
import Icon from 'src/views/components/common/Icon/Icon';
|
import Icon from 'src/views/components/common/Icon/Icon';
|
||||||
@@ -10,6 +11,7 @@ import styles from './CourseHeader.module.scss';
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
course: Course;
|
course: Course;
|
||||||
|
activeSchedule?: UserSchedule;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -17,7 +19,7 @@ type Props = {
|
|||||||
* This component displays the header of the course info popup.
|
* This component displays the header of the course info popup.
|
||||||
* It displays the course name, unique id, instructors, and schedule, all formatted nicely.
|
* It displays the course name, unique id, instructors, and schedule, all formatted nicely.
|
||||||
*/
|
*/
|
||||||
export default function CourseHeader({ course, onClose }: Props) {
|
export default function CourseHeader({ course, activeSchedule, onClose }: Props) {
|
||||||
const getBuildingUrl = (building?: string): string | undefined => {
|
const getBuildingUrl = (building?: string): string | undefined => {
|
||||||
if (!building) return undefined;
|
if (!building) return undefined;
|
||||||
return `https://utdirect.utexas.edu/apps/campus/buildings/nlogon/maps/UTM/${building}/`;
|
return `https://utdirect.utexas.edu/apps/campus/buildings/nlogon/maps/UTM/${building}/`;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Course } from 'src/shared/types/Course';
|
import { Course } from 'src/shared/types/Course';
|
||||||
|
import { UserSchedule } from 'src/shared/types/UserSchedule';
|
||||||
import Popup from '../../common/Popup/Popup';
|
import Popup from '../../common/Popup/Popup';
|
||||||
import CourseDescription from './CourseDescription/CourseDescription';
|
import CourseDescription from './CourseDescription/CourseDescription';
|
||||||
import CourseHeader from './CourseHeader/CourseHeader';
|
import CourseHeader from './CourseHeader/CourseHeader';
|
||||||
@@ -8,16 +9,17 @@ import GradeDistribution from './GradeDistribution/GradeDistribution';
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
course: Course;
|
course: Course;
|
||||||
|
activeSchedule?: UserSchedule;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The popup that appears when the user clicks on a course for more details.
|
* The popup that appears when the user clicks on a course for more details.
|
||||||
*/
|
*/
|
||||||
export default function CoursePopup({ course, onClose }: Props) {
|
export default function CoursePopup({ course, activeSchedule, onClose }: Props) {
|
||||||
return (
|
return (
|
||||||
<Popup className={styles.popup} overlay onClose={onClose}>
|
<Popup className={styles.popup} overlay onClose={onClose}>
|
||||||
<CourseHeader course={course} onClose={onClose} />
|
<CourseHeader course={course} activeSchedule={activeSchedule} onClose={onClose} />
|
||||||
<CourseDescription course={course} />
|
<CourseDescription course={course} />
|
||||||
<GradeDistribution course={course} />
|
<GradeDistribution course={course} />
|
||||||
</Popup>
|
</Popup>
|
||||||
|
|||||||
@@ -137,6 +137,7 @@ export default function GradeDistribution({ course }: Props) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
queryAggregateDistribution(course)
|
queryAggregateDistribution(course)
|
||||||
.then(([distribution, semesters]) => {
|
.then(([distribution, semesters]) => {
|
||||||
|
console.log('.then -> distribution, semesters:', distribution, semesters);
|
||||||
setSemesters(semesters);
|
setSemesters(semesters);
|
||||||
updateChart(distribution);
|
updateChart(distribution);
|
||||||
setStatus(DataStatus.FOUND);
|
setStatus(DataStatus.FOUND);
|
||||||
|
|||||||
@@ -16,23 +16,8 @@ const RECRUIT_FROM_DEPARTMENTS = ['C S', 'ECE', 'MIS', 'CSE', 'EE', 'ITD'];
|
|||||||
export default function RecruitmentBanner() {
|
export default function RecruitmentBanner() {
|
||||||
const [container, setContainer] = useState<HTMLDivElement | null>(null);
|
const [container, setContainer] = useState<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
const shouldShowBanner = (): boolean => {
|
|
||||||
const params = ['fos_fl', 'fos_cn'];
|
|
||||||
let department = '';
|
|
||||||
params.forEach(p => {
|
|
||||||
const param = new URLSearchParams(window.location.search).get(p);
|
|
||||||
if (param) {
|
|
||||||
department = param;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (!department) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return RECRUIT_FROM_DEPARTMENTS.includes(department);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!shouldShowBanner()) {
|
if (!canRecruitFrom()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const container = document.createElement('div');
|
const container = document.createElement('div');
|
||||||
@@ -64,3 +49,18 @@ export default function RecruitmentBanner() {
|
|||||||
container
|
container
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function canRecruitFrom(): boolean {
|
||||||
|
const params = ['fos_fl', 'fos_cn'];
|
||||||
|
let department = '';
|
||||||
|
params.forEach(p => {
|
||||||
|
const param = new URLSearchParams(window.location.search).get(p);
|
||||||
|
if (param) {
|
||||||
|
department = param;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!department) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return RECRUIT_FROM_DEPARTMENTS.includes(department);
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,3 +11,10 @@
|
|||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.inActiveSchedule {
|
||||||
|
* {
|
||||||
|
color: $turtle_pond !important;
|
||||||
|
font-weight: bold !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,13 +9,17 @@ interface Props {
|
|||||||
isSelected: boolean;
|
isSelected: boolean;
|
||||||
row: ScrapedRow;
|
row: ScrapedRow;
|
||||||
onClick: (...args: any[]) => any;
|
onClick: (...args: any[]) => any;
|
||||||
|
/**
|
||||||
|
* Whether the course is in the user' active schedule.
|
||||||
|
*/
|
||||||
|
isInActiveSchedule: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component is injected into each row of the course catalog table.
|
* This component is injected into each row of the course catalog table.
|
||||||
* @returns a react portal to the new td in the column or null if the column has not been created yet.
|
* @returns a react portal to the new td in the column or null if the column has not been created yet.
|
||||||
*/
|
*/
|
||||||
export default function TableRow({ row, isSelected, onClick }: Props): JSX.Element | null {
|
export default function TableRow({ row, isSelected, isInActiveSchedule, onClick }: Props): JSX.Element | null {
|
||||||
const [container, setContainer] = useState<HTMLTableCellElement | null>(null);
|
const [container, setContainer] = useState<HTMLTableCellElement | null>(null);
|
||||||
|
|
||||||
const { element, course } = row;
|
const { element, course } = row;
|
||||||
@@ -35,6 +39,10 @@ export default function TableRow({ row, isSelected, onClick }: Props): JSX.Eleme
|
|||||||
element.classList[isSelected ? 'add' : 'remove'](styles.selectedRow);
|
element.classList[isSelected ? 'add' : 'remove'](styles.selectedRow);
|
||||||
}, [isSelected, element.classList]);
|
}, [isSelected, element.classList]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
element.classList[isInActiveSchedule ? 'add' : 'remove'](styles.inActiveSchedule);
|
||||||
|
}, [isInActiveSchedule, element.classList]);
|
||||||
|
|
||||||
if (!container) {
|
if (!container) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
26
src/views/hooks/useUserSchedules.ts
Normal file
26
src/views/hooks/useUserSchedules.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { Serialized } from 'chrome-extension-toolkit';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { userScheduleStore } from 'src/shared/storage/userScheduleStore';
|
||||||
|
import { UserSchedule } from 'src/shared/types/UserSchedule';
|
||||||
|
|
||||||
|
export default function useUserSchedules(): UserSchedule[] {
|
||||||
|
const [schedules, setSchedules] = useState<UserSchedule[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
function updateSchedules(schedules: Serialized<UserSchedule>[]) {
|
||||||
|
setSchedules(schedules.map(s => new UserSchedule(s)));
|
||||||
|
}
|
||||||
|
|
||||||
|
userScheduleStore.get('schedules').then(updateSchedules);
|
||||||
|
|
||||||
|
const listener = userScheduleStore.listen('schedules', ({ newValue }) => {
|
||||||
|
updateSchedules(newValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
userScheduleStore.removeListener(listener);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return schedules;
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@ if (!support) {
|
|||||||
throw new Error('UT Registration Plus does not support this page, even though it should...');
|
throw new Error('UT Registration Plus does not support this page, even though it should...');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isExtensionPopup()) {
|
if (support === SiteSupport.EXTENSION_POPUP) {
|
||||||
render(<PopupMain />, document.getElementById('root'));
|
render(<PopupMain />, document.getElementById('root'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ export class CourseCatalogScraper {
|
|||||||
number,
|
number,
|
||||||
status,
|
status,
|
||||||
isReserved,
|
isReserved,
|
||||||
|
creditHours: this.getCreditHours(number),
|
||||||
schedule: this.getSchedule(row),
|
schedule: this.getSchedule(row),
|
||||||
registerURL: this.getRegisterURL(row),
|
registerURL: this.getRegisterURL(row),
|
||||||
url: this.getURL(row),
|
url: this.getURL(row),
|
||||||
@@ -112,6 +113,15 @@ export class CourseCatalogScraper {
|
|||||||
return [courseName, department, number];
|
return [courseName, department, number];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets how many credit hours the course is worth
|
||||||
|
* @param number the course number, CS 314H
|
||||||
|
* @return the number of credit hours the course is worth
|
||||||
|
*/
|
||||||
|
getCreditHours(number: string): number {
|
||||||
|
return Number(number.split('')[0]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scrape the Unique ID from the course catalog table row
|
* Scrape the Unique ID from the course catalog table row
|
||||||
* @param row the row of the course catalog table
|
* @param row the row of the course catalog table
|
||||||
|
|||||||
@@ -37,9 +37,17 @@ export async function queryAggregateDistribution(course: Course): Promise<[Distr
|
|||||||
F: row.f,
|
F: row.f,
|
||||||
};
|
};
|
||||||
|
|
||||||
const semesters: Semester[] = row.semesters.split(',').map((sem: string) => {
|
// the db file for some reason has duplicate semesters, so we use a set to remove duplicates
|
||||||
|
const rawSemesters = new Set<string>();
|
||||||
|
row.semesters.split(',').forEach((sem: string) => {
|
||||||
|
rawSemesters.add(sem);
|
||||||
|
});
|
||||||
|
|
||||||
|
const semesters: Semester[] = [];
|
||||||
|
|
||||||
|
rawSemesters.forEach((sem: string) => {
|
||||||
const [season, year] = sem.split(' ');
|
const [season, year] = sem.split(' ');
|
||||||
return { year: parseInt(year, 10), season: season as Semester['season'] };
|
semesters.push({ year: parseInt(year, 10), season: season as Semester['season'] });
|
||||||
});
|
});
|
||||||
|
|
||||||
return [distribution, semesters];
|
return [distribution, semesters];
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { isExtensionPopup } from 'chrome-extension-toolkit';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An enum that represents the different types of pages that we support
|
* An enum that represents the different types of pages that we support
|
||||||
* a given url/page can correspond to many of these enum values
|
* a given url/page can correspond to many of these enum values
|
||||||
@@ -7,6 +9,7 @@ export enum SiteSupport {
|
|||||||
COURSE_CATALOG_DETAILS = 'COURSE_CATALOG_DETAILS',
|
COURSE_CATALOG_DETAILS = 'COURSE_CATALOG_DETAILS',
|
||||||
UT_PLANNER = 'UT_PLANNER',
|
UT_PLANNER = 'UT_PLANNER',
|
||||||
WAITLIST = 'WAITLIST',
|
WAITLIST = 'WAITLIST',
|
||||||
|
EXTENSION_POPUP = 'EXTENSION_POPUP',
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -15,6 +18,9 @@ export enum SiteSupport {
|
|||||||
* @returns a list of page types that the current page is
|
* @returns a list of page types that the current page is
|
||||||
*/
|
*/
|
||||||
export default function getSiteSupport(url: string): SiteSupport | null {
|
export default function getSiteSupport(url: string): SiteSupport | null {
|
||||||
|
if (isExtensionPopup()) {
|
||||||
|
return SiteSupport.EXTENSION_POPUP;
|
||||||
|
}
|
||||||
if (url.includes('utexas.collegescheduler.com')) {
|
if (url.includes('utexas.collegescheduler.com')) {
|
||||||
return SiteSupport.UT_PLANNER;
|
return SiteSupport.UT_PLANNER;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ export function getBuildPlugins(mode: Environment, htmlEntries: EntryId[], manif
|
|||||||
title: `${manifest.short_name} v${manifest.version} ${mode}`,
|
title: `${manifest.short_name} v${manifest.version} ${mode}`,
|
||||||
logo: path.resolve('public', 'icons', 'icon_production_128.png'),
|
logo: path.resolve('public', 'icons', 'icon_production_128.png'),
|
||||||
failureSound: 'Ping',
|
failureSound: 'Ping',
|
||||||
|
successSound: false,
|
||||||
showDuration: true,
|
showDuration: true,
|
||||||
suppressWarning: true,
|
suppressWarning: true,
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user