refactor: Replace Webpack with Vite (#53)
This commit is contained in:
36
src/pages/background/background.ts
Normal file
36
src/pages/background/background.ts
Normal 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();
|
||||
8
src/pages/background/events/onInstall.ts
Normal file
8
src/pages/background/events/onInstall.ts
Normal 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);
|
||||
}
|
||||
9
src/pages/background/events/onServiceWorkerAlive.ts
Normal file
9
src/pages/background/events/onServiceWorkerAlive.ts
Normal 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();
|
||||
}
|
||||
11
src/pages/background/events/onUpdate.ts
Normal file
11
src/pages/background/events/onUpdate.ts
Normal 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(),
|
||||
});
|
||||
}
|
||||
20
src/pages/background/handler/browserActionHandler.ts
Normal file
20
src/pages/background/handler/browserActionHandler.ts
Normal 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;
|
||||
24
src/pages/background/handler/hotReloadingHandler.ts
Normal file
24
src/pages/background/handler/hotReloadingHandler.ts
Normal 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;
|
||||
20
src/pages/background/handler/tabManagementHandler.ts
Normal file
20
src/pages/background/handler/tabManagementHandler.ts
Normal 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;
|
||||
36
src/pages/background/handler/userScheduleHandler.ts
Normal file
36
src/pages/background/handler/userScheduleHandler.ts
Normal 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;
|
||||
17
src/pages/background/lib/addCourse.ts
Normal file
17
src/pages/background/lib/addCourse.ts
Normal 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);
|
||||
}
|
||||
11
src/pages/background/lib/clearCourses.ts
Normal file
11
src/pages/background/lib/clearCourses.ts
Normal 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);
|
||||
}
|
||||
21
src/pages/background/lib/createSchedule.ts
Normal file
21
src/pages/background/lib/createSchedule.ts
Normal 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;
|
||||
}
|
||||
20
src/pages/background/lib/deleteSchedule.ts
Normal file
20
src/pages/background/lib/deleteSchedule.ts
Normal 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;
|
||||
}
|
||||
17
src/pages/background/lib/removeCourse.ts
Normal file
17
src/pages/background/lib/removeCourse.ts
Normal 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);
|
||||
}
|
||||
17
src/pages/background/lib/renameSchedule.ts
Normal file
17
src/pages/background/lib/renameSchedule.ts
Normal 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;
|
||||
}
|
||||
12
src/pages/background/lib/switchSchedule.ts
Normal file
12
src/pages/background/lib/switchSchedule.ts
Normal 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);
|
||||
}
|
||||
42
src/pages/background/util/hotReloadTab.ts
Normal file
42
src/pages/background/util/hotReloadTab.ts
Normal 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
25
src/pages/background/util/openDebugTab.ts
Normal file
25
src/pages/background/util/openDebugTab.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
10
src/pages/background/util/openNewTab.ts
Normal file
10
src/pages/background/util/openNewTab.ts
Normal 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;
|
||||
}
|
||||
10
src/pages/calendar/CalendarMain.tsx
Normal file
10
src/pages/calendar/CalendarMain.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from 'react';
|
||||
import ExtensionRoot from '@views/components/common/ExtensionRoot/ExtensionRoot';
|
||||
|
||||
export default function CalendarMain() {
|
||||
return (
|
||||
<ExtensionRoot>
|
||||
<div>Calendar Placeholder</div>
|
||||
</ExtensionRoot>
|
||||
);
|
||||
}
|
||||
18
src/pages/calendar/index.html
Normal file
18
src/pages/calendar/index.html
Normal file
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<title>Calendar</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
|
||||
<script src="./index.tsx" type="module"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
5
src/pages/calendar/index.tsx
Normal file
5
src/pages/calendar/index.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import React from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import CalendarMain from './CalendarMain';
|
||||
|
||||
createRoot(document.getElementById('root')).render(<CalendarMain />);
|
||||
18
src/pages/content/index.tsx
Normal file
18
src/pages/content/index.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import CourseCatalogMain from '@views/components/CourseCatalogMain';
|
||||
import getSiteSupport, { SiteSupport } from '@views/lib/getSiteSupport';
|
||||
|
||||
const support = getSiteSupport(window.location.href);
|
||||
|
||||
if (support === SiteSupport.COURSE_CATALOG_DETAILS || support === SiteSupport.COURSE_CATALOG_LIST) {
|
||||
const container = document.createElement('div');
|
||||
container.id = 'extension-root';
|
||||
document.body.appendChild(container);
|
||||
|
||||
createRoot(container).render(
|
||||
<React.StrictMode>
|
||||
<CourseCatalogMain support={support} />
|
||||
</React.StrictMode>
|
||||
);
|
||||
}
|
||||
13
src/pages/debug/App.tsx
Normal file
13
src/pages/debug/App.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import React from 'react';
|
||||
import ExtensionRoot from '@views/components/common/ExtensionRoot/ExtensionRoot';
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export default function App() {
|
||||
return (
|
||||
<ExtensionRoot>
|
||||
<div>hello how are you doing today.</div>
|
||||
</ExtensionRoot>
|
||||
);
|
||||
}
|
||||
18
src/pages/debug/index.html
Normal file
18
src/pages/debug/index.html
Normal file
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<title>Debug</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
|
||||
<script src="./index.tsx" type="module"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
1
src/pages/debug/index.tsx
Normal file
1
src/pages/debug/index.tsx
Normal file
@@ -0,0 +1 @@
|
||||
import 'src/debug';
|
||||
13
src/pages/options/App.tsx
Normal file
13
src/pages/options/App.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import React from 'react';
|
||||
import ExtensionRoot from '@views/components/common/ExtensionRoot/ExtensionRoot';
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export default function App() {
|
||||
return (
|
||||
<ExtensionRoot>
|
||||
<div>hello how are you doing today.</div>
|
||||
</ExtensionRoot>
|
||||
);
|
||||
}
|
||||
18
src/pages/options/index.html
Normal file
18
src/pages/options/index.html
Normal file
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<title>Popup</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
|
||||
<script src="./index.tsx" type="module"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
5
src/pages/options/index.tsx
Normal file
5
src/pages/options/index.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import React from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import App from './App';
|
||||
|
||||
createRoot(document.getElementById('root')).render(<App />);
|
||||
0
src/pages/panel/index.tsx
Normal file
0
src/pages/panel/index.tsx
Normal file
18
src/pages/popup/index.html
Normal file
18
src/pages/popup/index.html
Normal file
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<title>Popup</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
|
||||
<script src="./index.tsx" type="module"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
5
src/pages/popup/index.tsx
Normal file
5
src/pages/popup/index.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import React from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import PopupMain from '../../views/components/PopupMain';
|
||||
|
||||
createRoot(document.getElementById('root')).render(<PopupMain />);
|
||||
Reference in New Issue
Block a user