mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-08-28 17:21:05 +00:00
feat: replace sidebar with svelte alternative
This commit is contained in:
@@ -1,87 +0,0 @@
|
||||
import type { Plugin } from "@/plugins/core/types";
|
||||
import MenuitemSVGKey from "@/seqta/content/MenuItemSVGKey.json";
|
||||
import { waitForElm } from "@/seqta/utils/waitForElm";
|
||||
import { isSeqtaEngageExperience } from "@/seqta/utils/isSeqtaEngage";
|
||||
import {
|
||||
ensureAnalyticsMenuOrder,
|
||||
insertMenuItemAfterKey,
|
||||
processMenuItemNode,
|
||||
} from "@/seqta/utils/sidebarMenuIcons";
|
||||
import {
|
||||
ChangeMenuItemPositions,
|
||||
MenuOptionsOpen,
|
||||
} from "@/seqta/utils/Openers/OpenMenuOptions";
|
||||
import { settingsState } from "@/seqta/utils/listeners/SettingsState";
|
||||
import { applyMenuItemVisibility } from "@/seqta/utils/menuItemVisibility";
|
||||
import { loadAnalyticsPage } from "../loadAnalyticsPage";
|
||||
import styles from "../styles.css?inline";
|
||||
|
||||
const ANALYTICS_MENU_ICON = MenuitemSVGKey.analytics;
|
||||
const ANALYTICS_MENU_CLASS = "betterseqta-grade-analytics-item";
|
||||
|
||||
const gradeAnalyticsPlugin: Plugin<{}> = {
|
||||
id: "grade-analytics",
|
||||
name: "Grade Analytics",
|
||||
description:
|
||||
"Adds an analytics page with grade trends, distribution charts, and assessment history",
|
||||
version: "1.0.0",
|
||||
settings: {},
|
||||
disableToggle: true,
|
||||
styles,
|
||||
|
||||
run: async () => {
|
||||
if (isSeqtaEngageExperience()) {
|
||||
return () => {};
|
||||
}
|
||||
|
||||
const menuList = (await waitForElm("#menu > ul, #menu ul", true, 100, 60)) as HTMLElement;
|
||||
|
||||
const analyticsItem = document.createElement("li");
|
||||
analyticsItem.className = "item";
|
||||
analyticsItem.classList.add(ANALYTICS_MENU_CLASS);
|
||||
analyticsItem.id = "analyticsbutton";
|
||||
analyticsItem.dataset.key = "analytics";
|
||||
analyticsItem.dataset.path = "/analytics";
|
||||
analyticsItem.dataset.betterseqta = "true";
|
||||
analyticsItem.innerHTML = `<label>${ANALYTICS_MENU_ICON}<span>Analytics</span></label>`;
|
||||
|
||||
const syncAnalyticsMenu = () => {
|
||||
insertMenuItemAfterKey(menuList, analyticsItem, "courses");
|
||||
ensureAnalyticsMenuOrder();
|
||||
if (settingsState.menuorder.length > 0) {
|
||||
ChangeMenuItemPositions(settingsState.menuorder);
|
||||
}
|
||||
processMenuItemNode(analyticsItem);
|
||||
applyMenuItemVisibility();
|
||||
};
|
||||
|
||||
syncAnalyticsMenu();
|
||||
|
||||
const menuObserver = new MutationObserver(() => {
|
||||
if (MenuOptionsOpen || menuList.contains(analyticsItem)) return;
|
||||
syncAnalyticsMenu();
|
||||
});
|
||||
menuObserver.observe(menuList, { childList: true });
|
||||
|
||||
analyticsItem.addEventListener("click", (e) => {
|
||||
const target = e.target as HTMLElement;
|
||||
if (
|
||||
MenuOptionsOpen ||
|
||||
analyticsItem.classList.contains("draggable") ||
|
||||
target.closest(".onoffswitch, .editmenuoption-container")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
window.history.pushState({}, "", "/#?page=/analytics");
|
||||
void loadAnalyticsPage();
|
||||
});
|
||||
|
||||
return () => {
|
||||
menuObserver.disconnect();
|
||||
analyticsItem.remove();
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export default gradeAnalyticsPlugin;
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
import MenuitemSVGKey from "@/seqta/content/MenuItemSVGKey.json";
|
||||
import { waitForElm } from "@/seqta/utils/waitForElm";
|
||||
import { isSeqtaEngageExperience } from "@/seqta/utils/isSeqtaEngage";
|
||||
import {
|
||||
ensureAnalyticsMenuOrder,
|
||||
insertMenuItemAfterKey,
|
||||
} from "@/seqta/utils/Openers/analyticsMenuOrder";
|
||||
import { processMenuItemNode } from "@/seqta/utils/sidebarMenuIcons";
|
||||
import { ChangeMenuItemPositions } from "@/seqta/utils/Openers/menuOrder";
|
||||
import { isMenuOptionsOpen } from "@/seqta/utils/Openers/menuOptionsState";
|
||||
import { settingsState } from "@/seqta/utils/listeners/SettingsState";
|
||||
import { applyMenuItemVisibility } from "@/seqta/utils/menuItemVisibility";
|
||||
|
||||
const ANALYTICS_MENU_ICON = MenuitemSVGKey.analytics;
|
||||
export const ANALYTICS_MENU_CLASS = "betterseqta-grade-analytics-item";
|
||||
export const ANALYTICS_MENU_KEY = "analytics";
|
||||
|
||||
function getNativeMenuList(): HTMLElement | null {
|
||||
const menu = document.getElementById("menu");
|
||||
if (!menu) return null;
|
||||
return menu.querySelector(
|
||||
":scope > ul:not(#bsplus-sidebar-root)",
|
||||
) as HTMLElement | null;
|
||||
}
|
||||
|
||||
function loadAnalyticsPageLazy() {
|
||||
void import("./loadAnalyticsPage").then((m) => m.loadAnalyticsPage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject the Analytics sidebar row immediately (eager).
|
||||
* The analytics page chunk stays lazy until click / direct navigation.
|
||||
*/
|
||||
export async function injectAnalyticsMenuItem(): Promise<() => void> {
|
||||
if (isSeqtaEngageExperience()) {
|
||||
return () => {};
|
||||
}
|
||||
|
||||
document
|
||||
.querySelectorAll(
|
||||
`#menu .${ANALYTICS_MENU_CLASS}, #menu [data-key="${ANALYTICS_MENU_KEY}"]`,
|
||||
)
|
||||
.forEach((node) => {
|
||||
// Only touch the native list copy — custom sidebar mirrors via sync.
|
||||
if (node.closest("#bsplus-sidebar-root")) return;
|
||||
node.remove();
|
||||
});
|
||||
|
||||
await waitForElm(
|
||||
"#menu > ul:not(#bsplus-sidebar-root), #menu > ul",
|
||||
true,
|
||||
50,
|
||||
120,
|
||||
);
|
||||
|
||||
let menuList = getNativeMenuList();
|
||||
if (!menuList) {
|
||||
menuList = (await waitForElm(
|
||||
"#menu > ul:not(#bsplus-sidebar-root), #menu ul",
|
||||
true,
|
||||
50,
|
||||
120,
|
||||
)) as HTMLElement;
|
||||
}
|
||||
|
||||
const analyticsItem = document.createElement("li");
|
||||
analyticsItem.className = "item";
|
||||
analyticsItem.classList.add(ANALYTICS_MENU_CLASS);
|
||||
analyticsItem.id = "analyticsbutton";
|
||||
analyticsItem.dataset.key = ANALYTICS_MENU_KEY;
|
||||
analyticsItem.dataset.path = "/analytics";
|
||||
analyticsItem.dataset.betterseqta = "true";
|
||||
analyticsItem.innerHTML = `<label>${ANALYTICS_MENU_ICON}<span>Analytics</span></label>`;
|
||||
|
||||
const syncAnalyticsMenu = () => {
|
||||
const list = getNativeMenuList() ?? menuList;
|
||||
if (!list) return;
|
||||
|
||||
if (list.querySelector(':scope > [data-key="courses"]')) {
|
||||
insertMenuItemAfterKey(list, analyticsItem, "courses");
|
||||
} else if (list.querySelector(':scope > [data-key="home"]')) {
|
||||
insertMenuItemAfterKey(list, analyticsItem, "home");
|
||||
} else if (!list.contains(analyticsItem)) {
|
||||
list.insertBefore(analyticsItem, list.firstChild);
|
||||
}
|
||||
|
||||
ensureAnalyticsMenuOrder();
|
||||
if (settingsState.menuorder.length > 0) {
|
||||
ChangeMenuItemPositions(settingsState.menuorder);
|
||||
}
|
||||
processMenuItemNode(analyticsItem);
|
||||
applyMenuItemVisibility();
|
||||
window.dispatchEvent(new CustomEvent("bsplus-native-menu-updated"));
|
||||
};
|
||||
|
||||
syncAnalyticsMenu();
|
||||
|
||||
const menuObserver = new MutationObserver(() => {
|
||||
const list = getNativeMenuList() ?? menuList;
|
||||
if (!list || isMenuOptionsOpen() || list.contains(analyticsItem)) return;
|
||||
syncAnalyticsMenu();
|
||||
});
|
||||
menuObserver.observe(menuList, { childList: true });
|
||||
|
||||
const onClick = (e: MouseEvent) => {
|
||||
const target = e.target as HTMLElement;
|
||||
if (
|
||||
isMenuOptionsOpen() ||
|
||||
analyticsItem.classList.contains("draggable") ||
|
||||
target.closest(".onoffswitch, .editmenuoption-container")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
window.history.pushState({}, "", "/#?page=/analytics");
|
||||
loadAnalyticsPageLazy();
|
||||
};
|
||||
analyticsItem.addEventListener("click", onClick);
|
||||
|
||||
return () => {
|
||||
menuObserver.disconnect();
|
||||
analyticsItem.removeEventListener("click", onClick);
|
||||
analyticsItem.remove();
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { defineLazyPlugin } from "../../core/dynamicLoader";
|
||||
import { defineSettings, numberSetting } from "../../core/settingsHelpers";
|
||||
import { isSeqtaEngageExperience } from "@/seqta/utils/isSeqtaEngage";
|
||||
import { injectAnalyticsMenuItem } from "./injectAnalyticsMenuItem";
|
||||
import styles from "./styles.css?inline";
|
||||
|
||||
const settings = defineSettings({
|
||||
@@ -13,6 +14,10 @@ const settings = defineSettings({
|
||||
}),
|
||||
});
|
||||
|
||||
/**
|
||||
* Shell loads immediately so the sidebar Analytics icon can mount.
|
||||
* The page UI (`loadAnalyticsPage` / charts) stays in a separate lazy chunk.
|
||||
*/
|
||||
const gradeAnalyticsPluginLazy = defineLazyPlugin({
|
||||
id: "grade-analytics",
|
||||
name: "Grade Analytics",
|
||||
@@ -23,16 +28,21 @@ const gradeAnalyticsPluginLazy = defineLazyPlugin({
|
||||
disableToggle: true,
|
||||
defaultEnabled: true,
|
||||
styles,
|
||||
// Kept for API compatibility; menu injection no longer waits on this chunk.
|
||||
loader: () => import("./core/index"),
|
||||
});
|
||||
|
||||
const runGradeAnalytics = gradeAnalyticsPluginLazy.run!;
|
||||
|
||||
gradeAnalyticsPluginLazy.run = async (api) => {
|
||||
gradeAnalyticsPluginLazy.run = async () => {
|
||||
if (isSeqtaEngageExperience()) {
|
||||
return () => {};
|
||||
}
|
||||
return runGradeAnalytics(api);
|
||||
|
||||
// Eager: sidebar icon / native menu row only.
|
||||
const cleanupMenu = await injectAnalyticsMenuItem();
|
||||
|
||||
return () => {
|
||||
cleanupMenu();
|
||||
};
|
||||
};
|
||||
|
||||
export default gradeAnalyticsPluginLazy;
|
||||
|
||||
Reference in New Issue
Block a user