mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-06-05 19:24:39 +00:00
feat: queue popups and new engage popup
This commit is contained in:
@@ -3524,6 +3524,26 @@ div.day-empty {
|
||||
font-size: 1em;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
.whatsnewHeader.engageParentsAnnouncementHeader {
|
||||
height: auto;
|
||||
min-height: unset;
|
||||
}
|
||||
.whatsnewHeader.engageParentsAnnouncementHeader h1 {
|
||||
line-height: 1.2;
|
||||
}
|
||||
.whatsnewHeader.engageParentsAnnouncementHeader .engageParentsSubheading {
|
||||
margin-top: 0.35rem;
|
||||
font-size: 1.05rem;
|
||||
font-weight: 600;
|
||||
opacity: 0.92;
|
||||
}
|
||||
.seqtaEngageAccent {
|
||||
color: #ea580c;
|
||||
font-weight: 700;
|
||||
}
|
||||
.dark .seqtaEngageAccent {
|
||||
color: #fb923c;
|
||||
}
|
||||
.whatsnewBackground {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@@ -3652,6 +3672,25 @@ div.day-empty {
|
||||
object-fit: cover;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.whatsnewTextContainer .engageParentsPromoWrap {
|
||||
width: 100%;
|
||||
margin-bottom: 12px;
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
aspect-ratio: 16 / 9;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.28);
|
||||
background: color-mix(in srgb, var(--background-secondary) 88%, var(--text-primary) 12%);
|
||||
}
|
||||
.whatsnewTextContainer .engageParentsPromoWrap .engageParentsPromoImg {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
aspect-ratio: unset;
|
||||
object-fit: contain;
|
||||
object-position: center;
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% {
|
||||
|
||||
+2
-10
@@ -29,8 +29,7 @@ import {
|
||||
updateEngageHomeMenuActive,
|
||||
} from "@/seqta/utils/Loaders/LoadEngageHomePage";
|
||||
import { loadHomePage } from "@/seqta/utils/Loaders/LoadHomePage";
|
||||
import { OpenWhatsNewPopup } from "@/seqta/utils/Openers/OpenWhatsNewPopup";
|
||||
import { showPrivacyNotification } from "@/seqta/utils/Openers/OpenPrivacyNotification";
|
||||
import { runStartupPopupQueue } from "@/seqta/utils/Openers/StartupPopupQueue";
|
||||
|
||||
import { updateTimetableTimes } from "@/seqta/utils/updateTimetableTimes";
|
||||
|
||||
@@ -106,14 +105,7 @@ export async function finishLoad() {
|
||||
console.error("Error during loading cleanup:", err);
|
||||
}
|
||||
|
||||
// Check and show privacy statement notification (before what's new)
|
||||
if (!document.getElementById("privacy-notification")) {
|
||||
await showPrivacyNotification();
|
||||
}
|
||||
|
||||
if (settingsState.justupdated && !document.getElementById("whatsnewbk") && !document.getElementById("privacy-notification")) {
|
||||
OpenWhatsNewPopup();
|
||||
}
|
||||
runStartupPopupQueue();
|
||||
}
|
||||
|
||||
export function GetCSSElement(file: string) {
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 653 KiB |
@@ -0,0 +1,59 @@
|
||||
import stringToHTML from "../stringToHTML";
|
||||
import { settingsState } from "../listeners/SettingsState";
|
||||
import { openPopup } from "./PopupManager";
|
||||
|
||||
/** Same hosting pattern as the privacy statement branding images (avoids page-relative extension URLs on Engage). */
|
||||
const ENGAGE_PROMO_IMG_URL =
|
||||
"https://raw.githubusercontent.com/BetterSEQTA/BetterSEQTA-Plus/main/src/resources/bq%2Bengage.png";
|
||||
|
||||
export function shouldShowEngageParentsAnnouncement(): boolean {
|
||||
return !settingsState.engageParentsAnnouncementShown;
|
||||
}
|
||||
|
||||
/**
|
||||
* One-time announcement that BetterSEQTA Plus works on SEQTA Engage (parents).
|
||||
*/
|
||||
export function showEngageParentsAnnouncement(onDismissed?: () => void) {
|
||||
if (document.getElementById("whatsnewbk")) {
|
||||
onDismissed?.();
|
||||
return;
|
||||
}
|
||||
if (!shouldShowEngageParentsAnnouncement()) {
|
||||
onDismissed?.();
|
||||
return;
|
||||
}
|
||||
|
||||
const header = stringToHTML(
|
||||
/* html */
|
||||
`<div class="whatsnewHeader engageParentsAnnouncementHeader">
|
||||
<h1>BetterSEQTA Plus now supports <span class="seqtaEngageAccent">SEQTA Engage</span></h1>
|
||||
<p class="engageParentsSubheading">Buy your mom a BetterSEQTA Plus</p>
|
||||
</div>`,
|
||||
).firstChild as HTMLElement;
|
||||
|
||||
const text = stringToHTML(/* html */ `
|
||||
<div class="whatsnewTextContainer privacyStatement" style="overflow-y: auto; font-size: 1.2rem; line-height: 1.6;">
|
||||
<div class="engageParentsPromoWrap">
|
||||
<img class="engageParentsPromoImg" src="${ENGAGE_PROMO_IMG_URL}" width="1920" height="1080" alt="BetterSEQTA Plus now supports SEQTA Engage" />
|
||||
</div>
|
||||
<p>
|
||||
<strong class="seqtaEngageAccent">SEQTA Engage</strong> is the portal many parents use for notices, messages, and day-to-day school info.
|
||||
Before anything else: BetterSEQTA Plus now supports <strong class="seqtaEngageAccent">SEQTA Engage</strong>, so parents get the same kinds of improvements you are used to on SEQTA Learn—themes, a clearer home experience, and other Plus polish while browsing Engage.
|
||||
</p>
|
||||
<p>
|
||||
The title is a bit of fun; if the extension saves you time, you can always support development via Open Collective or Ko-fi from the What is New changelog or related links in settings.
|
||||
</p>
|
||||
<p>
|
||||
Close this dialog when you are done. We will not show this announcement again.
|
||||
</p>
|
||||
</div>
|
||||
`).firstChild as HTMLElement;
|
||||
|
||||
settingsState.engageParentsAnnouncementShown = true;
|
||||
|
||||
openPopup({
|
||||
header,
|
||||
content: [text],
|
||||
afterClose: onDismissed,
|
||||
});
|
||||
}
|
||||
@@ -2,12 +2,29 @@ import stringToHTML from "../stringToHTML";
|
||||
import { settingsState } from "../listeners/SettingsState";
|
||||
import { openPopup } from "./PopupManager";
|
||||
|
||||
export function showPrivacyNotification() {
|
||||
const lastUpdated = "2025-12-19";
|
||||
const PRIVACY_STATEMENT_VERSION = "2025-12-19";
|
||||
|
||||
if (document.getElementById("whatsnewbk")) return;
|
||||
if (settingsState.privacyStatementShown) return;
|
||||
if (settingsState.privacyStatementLastUpdated && new Date(settingsState.privacyStatementLastUpdated) > new Date(lastUpdated)) return;
|
||||
export function shouldShowPrivacyNotification(): boolean {
|
||||
if (settingsState.privacyStatementShown) return false;
|
||||
if (
|
||||
settingsState.privacyStatementLastUpdated &&
|
||||
new Date(settingsState.privacyStatementLastUpdated) >
|
||||
new Date(PRIVACY_STATEMENT_VERSION)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function showPrivacyNotification(onDismissed?: () => void) {
|
||||
if (document.getElementById("whatsnewbk")) {
|
||||
onDismissed?.();
|
||||
return;
|
||||
}
|
||||
if (!shouldShowPrivacyNotification()) {
|
||||
onDismissed?.();
|
||||
return;
|
||||
}
|
||||
|
||||
const header = stringToHTML(
|
||||
/* html */
|
||||
@@ -48,5 +65,6 @@ export function showPrivacyNotification() {
|
||||
openPopup({
|
||||
header,
|
||||
content: [text],
|
||||
afterClose: onDismissed,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import browser from "webextension-polyfill";
|
||||
import kofi from "@/resources/kofi.png?base64";
|
||||
import { openPopup } from "./PopupManager";
|
||||
|
||||
export function OpenWhatsNewPopup() {
|
||||
export function OpenWhatsNewPopup(onDismissed?: () => void) {
|
||||
const header = stringToHTML(
|
||||
/* html */
|
||||
`<div class="whatsnewHeader">
|
||||
@@ -362,5 +362,7 @@ export function OpenWhatsNewPopup() {
|
||||
openPopup({
|
||||
header,
|
||||
content: [imageContainer, text, footer],
|
||||
afterClose: onDismissed,
|
||||
clearJustUpdated: true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,6 +4,13 @@ import { animate as motionAnimate, stagger } from "motion";
|
||||
type AnimationTarget = string | Element | Element[] | NodeList | null;
|
||||
|
||||
let isClosing = false;
|
||||
let pendingAfterClose: (() => void) | undefined;
|
||||
|
||||
function invokeAfterClose() {
|
||||
const fn = pendingAfterClose;
|
||||
pendingAfterClose = undefined;
|
||||
fn?.();
|
||||
}
|
||||
|
||||
export async function closePopup() {
|
||||
if (isClosing) return;
|
||||
@@ -16,12 +23,14 @@ export async function closePopup() {
|
||||
|
||||
if (!background || !popup) {
|
||||
isClosing = false;
|
||||
invokeAfterClose();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!settingsState.animations) {
|
||||
background.remove();
|
||||
isClosing = false;
|
||||
invokeAfterClose();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -33,19 +42,28 @@ export async function closePopup() {
|
||||
|
||||
background.remove();
|
||||
isClosing = false;
|
||||
invokeAfterClose();
|
||||
}
|
||||
|
||||
interface OpenPopupOptions {
|
||||
header?: Node | null;
|
||||
content?: (Node | null | undefined)[];
|
||||
animateSelector?: AnimationTarget;
|
||||
/** Called once after this popup is fully closed (including skip-animation path). */
|
||||
afterClose?: () => void;
|
||||
/** When true, clears the post-update flag when this popup opens (What's New only). */
|
||||
clearJustUpdated?: boolean;
|
||||
}
|
||||
|
||||
export function openPopup({
|
||||
header,
|
||||
content = [],
|
||||
animateSelector = ".whatsnewTextContainer *",
|
||||
afterClose,
|
||||
clearJustUpdated = false,
|
||||
}: OpenPopupOptions = {}) {
|
||||
pendingAfterClose = afterClose;
|
||||
|
||||
const background = document.createElement("div");
|
||||
background.id = "whatsnewbk";
|
||||
background.classList.add("whatsnewBackground");
|
||||
@@ -88,7 +106,9 @@ export function openPopup({
|
||||
}
|
||||
}
|
||||
|
||||
delete settingsState.justupdated;
|
||||
if (clearJustUpdated) {
|
||||
delete settingsState.justupdated;
|
||||
}
|
||||
|
||||
background.addEventListener("click", (event) => {
|
||||
if (event.target === background) void closePopup();
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { settingsState } from "../listeners/SettingsState";
|
||||
import { OpenWhatsNewPopup } from "./OpenWhatsNewPopup";
|
||||
import {
|
||||
shouldShowPrivacyNotification,
|
||||
showPrivacyNotification,
|
||||
} from "./OpenPrivacyNotification";
|
||||
import {
|
||||
shouldShowEngageParentsAnnouncement,
|
||||
showEngageParentsAnnouncement,
|
||||
} from "./OpenEngageParentsAnnouncement";
|
||||
|
||||
type QueueStep = (goNext: () => void) => void;
|
||||
|
||||
/**
|
||||
* Runs startup modals in order: What's New (if the extension just updated),
|
||||
* privacy statement (if required), then the SEQTA Engage announcement (once).
|
||||
*/
|
||||
export function runStartupPopupQueue() {
|
||||
const steps: QueueStep[] = [];
|
||||
|
||||
if (settingsState.justupdated) {
|
||||
steps.push((goNext) => OpenWhatsNewPopup(goNext));
|
||||
}
|
||||
|
||||
if (shouldShowPrivacyNotification()) {
|
||||
steps.push((goNext) => showPrivacyNotification(goNext));
|
||||
}
|
||||
|
||||
if (shouldShowEngageParentsAnnouncement()) {
|
||||
steps.push((goNext) => showEngageParentsAnnouncement(goNext));
|
||||
}
|
||||
|
||||
function runNext() {
|
||||
const step = steps.shift();
|
||||
if (step) step(runNext);
|
||||
}
|
||||
|
||||
runNext();
|
||||
}
|
||||
@@ -32,6 +32,8 @@ export interface SettingsState {
|
||||
justupdated?: boolean;
|
||||
privacyStatementShown?: boolean;
|
||||
privacyStatementLastUpdated?: string;
|
||||
/** One-time announcement: SEQTA Engage support for parents (dismissed popup queue). */
|
||||
engageParentsAnnouncementShown?: boolean;
|
||||
timeFormat?: string;
|
||||
animations: boolean;
|
||||
defaultPage: string;
|
||||
|
||||
Reference in New Issue
Block a user