mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-06-06 03:34:40 +00:00
Compare commits
79 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a151e7a07e | |||
| 5b590512ee | |||
| 3ff8ef144a | |||
| d9abed1c5d | |||
| 82a789bbec | |||
| ce6538f850 | |||
| 979ae7149f | |||
| 6e71437fe8 | |||
| 940ecf8714 | |||
| e0cc2e0fdf | |||
| 5a19ef92e8 | |||
| 0a3781e9c2 | |||
| a2e39c9d84 | |||
| 520abbb5c3 | |||
| d0a11da15f | |||
| fd5802f9a3 | |||
| 380d829d19 | |||
| 702528fb0c | |||
| 2c077bc755 | |||
| fd86e57442 | |||
| 60ce18280e | |||
| 668dbfd78b | |||
| 810aa17f15 | |||
| b64558e50a | |||
| 9b969bd708 | |||
| 1945f7c592 | |||
| 3e26d9af3c | |||
| 3c8d7e246b | |||
| 2e56518330 | |||
| e67f3110e0 | |||
| a67f4d2e25 | |||
| d6025140fd | |||
| 88e9ddf29c | |||
| 11adc4f933 | |||
| 15691e8d94 | |||
| 754b8d0589 | |||
| 1d634d0da1 | |||
| 7136de90be | |||
| 466628479e | |||
| 9c08d0bac2 | |||
| 6c5320007f | |||
| 4734a443b4 | |||
| 7c38e1dc29 | |||
| f3f90ef2a8 | |||
| 9bcc94aa8a | |||
| ff2431f269 | |||
| b442194bc5 | |||
| b59c0eae25 | |||
| e895ce9f6b | |||
| 7192f41535 | |||
| f1b707ab25 | |||
| 7f47cb8183 | |||
| 7f5d138bc9 | |||
| cef0f29640 | |||
| 157343dda9 | |||
| 7705c0a3cd | |||
| 7def7b190c | |||
| c294fb7369 | |||
| 0dbbef0eb1 | |||
| c3c747d996 | |||
| cdc8062275 | |||
| 1857b5ff01 | |||
| 700e3ebb48 | |||
| 16b9610301 | |||
| 7d11e203a6 | |||
| 530f07e640 | |||
| 08586781ce | |||
| 3ca5a49769 | |||
| 886c79b3ee | |||
| 30aa39142d | |||
| 4188ef0d67 | |||
| ad9a013b00 | |||
| cd1f954cc7 | |||
| 6ef6c986dc | |||
| f2e28175a0 | |||
| 3ddcb204ef | |||
| 766f0e6d3f | |||
| f1fcba58ef | |||
| dba2d13bb3 |
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a target="_blank" href="https://chrome.google.com/webstore/detail/betterseqta%20/afdgaoaclhkhemfkkkonemoapeinchel"><img src="https://user-images.githubusercontent.com/95666457/149519713-159d7ef7-2c21-4034-a616-f037ff46d9a4.png" alt="ChromeDownload" width="250"></a>
|
<a target="_blank" href="https://chrome.google.com/webstore/detail/betterseqta%20/afdgaoaclhkhemfkkkonemoapeinchel"><img src="https://user-images.githubusercontent.com/95666457/149519713-159d7ef7-2c21-4034-a616-f037ff46d9a4.png" alt="ChromeDownload" width="250"></a>
|
||||||
<a target="_blank" href="https://discord.gg/YzmbnCDkat"><img src="https://github.com/SethBurkart123/EvenBetterSEQTA/assets/108050083/23055730-b16e-44c0-9bef-221d8545af92" width="240" style="border-radius:10%;" /></a>
|
<a target="_blank" href="https://discord.gg/YzmbnCDkat"><img src="https://github.com/BetterSEQTA/BetterSEQTA-Plus/assets/108050083/23055730-b16e-44c0-9bef-221d8545af92" width="240" style="border-radius:10%;" /></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
+12
-11
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "betterseqtaplus",
|
"name": "betterseqtaplus",
|
||||||
"version": "3.4.10",
|
"version": "3.4.13",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Enhance SEQTA Learn's usability and aesthetics! A fork of BetterSEQTA to continue development add add heaps more features!",
|
"description": "Enhance SEQTA Learn's usability and aesthetics! A fork of BetterSEQTA to continue development add add heaps more features!",
|
||||||
"browserslist": "> 0.5%, last 2 versions, not dead",
|
"browserslist": "> 0.5%, last 2 versions, not dead",
|
||||||
@@ -35,19 +35,19 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/plugin-transform-runtime": "^7.26.9",
|
"@babel/plugin-transform-runtime": "^7.26.9",
|
||||||
"@babel/runtime": "^7.26.9",
|
"@babel/runtime": "^7.26.9",
|
||||||
"@bedframe/cli": "^0.0.91",
|
"@bedframe/cli": "^0.0.95",
|
||||||
"@crxjs/vite-plugin": "2.1.0",
|
"@crxjs/vite-plugin": "^2.2.0",
|
||||||
"@types/mime-types": "^2.1.4",
|
"@types/mime-types": "^3.0.1",
|
||||||
"@types/react": "^19.0.10",
|
"@types/react": "^19.0.10",
|
||||||
"@types/react-dom": "^19.0.4",
|
"@types/react-dom": "^19.0.4",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^10.0.0",
|
||||||
"dependency-cruiser": "^16.10.0",
|
"dependency-cruiser": "^17.0.1",
|
||||||
"eslint": "9.22.0",
|
"eslint": "^9.33.0",
|
||||||
"glob": "^11.0.1",
|
"glob": "^11.0.1",
|
||||||
"mime-types": "^2.1.35",
|
"mime-types": "^3.0.1",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.5.3",
|
||||||
"process": "^0.11.10",
|
"process": "^0.11.10",
|
||||||
"publish-browser-extension": "^3.0.0",
|
"publish-browser-extension": "^3.0.1",
|
||||||
"sass": "^1.85.1",
|
"sass": "^1.85.1",
|
||||||
"sass-loader": "^16.0.5",
|
"sass-loader": "^16.0.5",
|
||||||
"semver": "^7.7.1",
|
"semver": "^7.7.1",
|
||||||
@@ -55,6 +55,7 @@
|
|||||||
"url": "^0.11.4"
|
"url": "^0.11.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@bedframe/core": "^0.0.46",
|
||||||
"@codemirror/autocomplete": "^6.18.6",
|
"@codemirror/autocomplete": "^6.18.6",
|
||||||
"@codemirror/commands": "^6.8.0",
|
"@codemirror/commands": "^6.8.0",
|
||||||
"@codemirror/lang-css": "^6.3.1",
|
"@codemirror/lang-css": "^6.3.1",
|
||||||
@@ -65,10 +66,10 @@
|
|||||||
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||||
"@tailwindcss/forms": "^0.5.10",
|
"@tailwindcss/forms": "^0.5.10",
|
||||||
"@tsconfig/svelte": "^5.0.4",
|
"@tsconfig/svelte": "^5.0.4",
|
||||||
"@types/chrome": "^0.0.308",
|
"@types/chrome": "^0.1.4",
|
||||||
"@types/color": "^4.2.0",
|
"@types/color": "^4.2.0",
|
||||||
"@types/lodash": "^4.17.16",
|
"@types/lodash": "^4.17.16",
|
||||||
"@types/node": "^22.13.10",
|
"@types/node": "^24.3.0",
|
||||||
"@types/sortablejs": "^1.15.8",
|
"@types/sortablejs": "^1.15.8",
|
||||||
"@types/uuid": "^10.0.0",
|
"@types/uuid": "^10.0.0",
|
||||||
"@types/webextension-polyfill": "^0.12.3",
|
"@types/webextension-polyfill": "^0.12.3",
|
||||||
|
|||||||
+1
-1
@@ -108,7 +108,7 @@ function getDefaultValues(): SettingsState {
|
|||||||
originalSelectedColor: "",
|
originalSelectedColor: "",
|
||||||
DarkMode: true,
|
DarkMode: true,
|
||||||
animations: !isLowEndDevice,
|
animations: !isLowEndDevice,
|
||||||
assessmentsAverage: true,
|
assessmentsAverage: false,
|
||||||
defaultPage: "home",
|
defaultPage: "home",
|
||||||
shortcuts: [
|
shortcuts: [
|
||||||
{
|
{
|
||||||
|
|||||||
+173
-92
@@ -38,11 +38,27 @@ body,
|
|||||||
html {
|
html {
|
||||||
font-family: Rubik, sans-serif !important;
|
font-family: Rubik, sans-serif !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Ensure native select dropdowns are readable on Windows */
|
||||||
|
select option {
|
||||||
|
background-color: #ffffff !important;
|
||||||
|
color: #111827 !important;
|
||||||
|
}
|
||||||
|
.dark select option {
|
||||||
|
background-color: #1f2937 !important;
|
||||||
|
color: #ffffff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Consistent rounded corners for selects */
|
||||||
|
select {
|
||||||
|
border-radius: 8px !important;
|
||||||
|
}
|
||||||
#container {
|
#container {
|
||||||
transition: 200ms;
|
transition: 200ms;
|
||||||
background: var(--auto-background) !important;
|
background: var(--auto-background) !important;
|
||||||
}
|
}
|
||||||
* {
|
:root * {
|
||||||
|
font-family: Rubik, sans-serif !important;
|
||||||
--theme-fg-parts: white;
|
--theme-fg-parts: white;
|
||||||
}
|
}
|
||||||
.extension-editor {
|
.extension-editor {
|
||||||
@@ -143,6 +159,16 @@ html {
|
|||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
#main {
|
||||||
|
> .timetablepage {
|
||||||
|
> .quickbar {
|
||||||
|
.gutter {
|
||||||
|
border-bottom-left-radius: 15px;
|
||||||
|
border-bottom-right-radius: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.forums {
|
.forums {
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
}
|
}
|
||||||
@@ -379,6 +405,18 @@ ul.magicDelete > li.deleting {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Allow long course/assessment names in the sidebar to wrap and break safely */
|
||||||
|
#menu li > label,
|
||||||
|
#menu section > label {
|
||||||
|
white-space: normal;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
word-break: break-word;
|
||||||
|
text-transform: none;
|
||||||
|
font-size: 16px;
|
||||||
|
hyphens: auto;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
#menu {
|
#menu {
|
||||||
width: 270px;
|
width: 270px;
|
||||||
z-index: 19;
|
z-index: 19;
|
||||||
@@ -451,11 +489,6 @@ ul.magicDelete > li.deleting {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#menu li > label,
|
|
||||||
#menu section > label {
|
|
||||||
text-transform: none;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
#userActions {
|
#userActions {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -791,8 +824,8 @@ div > ol:has(.uiFileHandlerWrapper) {
|
|||||||
[aria-labelledby="lixycoxs-tab-1"] [minlength="0"] {
|
[aria-labelledby="lixycoxs-tab-1"] [minlength="0"] {
|
||||||
min-height: 128px !important;
|
min-height: 128px !important;
|
||||||
}
|
}
|
||||||
.student #menu > ul::before {
|
body.student #menu > ul::before {
|
||||||
background-image: var(--betterseqta-logo);
|
background-image: var(--betterseqta-logo) !important;
|
||||||
position: -webkit-sticky;
|
position: -webkit-sticky;
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
@@ -801,6 +834,18 @@ div > ol:has(.uiFileHandlerWrapper) {
|
|||||||
box-shadow: 0px 0px 4px 2px rgba(0, 0, 0, 0.2);
|
box-shadow: 0px 0px 4px 2px rgba(0, 0, 0, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html.transparencyEffects [class*="BasicPanel__BasicPanel___q92_U"] > ol > li {
|
||||||
|
border-bottom: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.transparencyEffects
|
||||||
|
[class*="BasicPanel__BasicPanel___q92_U"]
|
||||||
|
> ol
|
||||||
|
> li
|
||||||
|
+ li {
|
||||||
|
border-top: 1px solid var(--theme-offset-bg);
|
||||||
|
}
|
||||||
|
|
||||||
.assessmentsWrapper .message {
|
.assessmentsWrapper .message {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -956,7 +1001,7 @@ div > ol:has(.uiFileHandlerWrapper) {
|
|||||||
top: 72px;
|
top: 72px;
|
||||||
left: 0px;
|
left: 0px;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
|
|
||||||
@media (min-width: 1401px) {
|
@media (min-width: 1401px) {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 402px;
|
left: 402px;
|
||||||
@@ -999,8 +1044,8 @@ div > ol:has(.uiFileHandlerWrapper) {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
#title {
|
#title {
|
||||||
background: var(--background-primary);
|
background: var(--background-primary) !important;
|
||||||
color: var(--text-primary);
|
color: var(--text-primary) !important;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding-right: 56px !important;
|
padding-right: 56px !important;
|
||||||
@@ -1111,7 +1156,7 @@ div > ol:has(.uiFileHandlerWrapper) {
|
|||||||
height: 15em;
|
height: 15em;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-auto-flow: column;
|
grid-auto-flow: column;
|
||||||
grid-auto-columns: minmax(130px, 1fr);
|
grid-auto-columns: minmax(142px, 1fr);
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
|
|
||||||
@@ -1320,7 +1365,17 @@ div > ol:has(.uiFileHandlerWrapper) {
|
|||||||
font-size: 20px !important;
|
font-size: 20px !important;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
min-height: 46px;
|
min-height: 46px;
|
||||||
height: 36%;
|
/* Let the title expand naturally but clamp to 2 lines to avoid overlap */
|
||||||
|
height: auto;
|
||||||
|
line-height: 1.2;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
word-break: break-word;
|
||||||
|
hyphens: auto;
|
||||||
}
|
}
|
||||||
.day h3 {
|
.day h3 {
|
||||||
padding: 0px 5px;
|
padding: 0px 5px;
|
||||||
@@ -1653,7 +1708,9 @@ iframe.userHTML {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.programmeNavigator {
|
.programmeNavigator {
|
||||||
box-shadow: 0 0 40px 0px rgba(0,0,0,0.05);
|
box-shadow: 0 0 40px 0px rgba(0, 0, 0, 0.05);
|
||||||
|
overflow-y: scroll;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
.navigator {
|
.navigator {
|
||||||
padding: 6px !important;
|
padding: 6px !important;
|
||||||
@@ -1710,7 +1767,7 @@ iframe.userHTML {
|
|||||||
&.selected {
|
&.selected {
|
||||||
background: transparent !important;
|
background: transparent !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -1722,7 +1779,9 @@ iframe.userHTML {
|
|||||||
background: var(--auto-background);
|
background: var(--auto-background);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
scale: 0.95;
|
scale: 0.95;
|
||||||
transition: opacity 0.2s ease-out, scale 0.1s ease-out;
|
transition:
|
||||||
|
opacity 0.2s ease-out,
|
||||||
|
scale 0.1s ease-out;
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
@@ -1736,13 +1795,12 @@ iframe.userHTML {
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
scale: 1;
|
scale: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.pane {
|
.pane {
|
||||||
.content:has(.programmeNavigator) {
|
.content:has(.programmeNavigator) {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1760,7 +1818,9 @@ iframe.userHTML {
|
|||||||
.dark .programmeNavigator .navigator {
|
.dark .programmeNavigator .navigator {
|
||||||
.search {
|
.search {
|
||||||
background: var(--background-secondary) !important;
|
background: var(--background-secondary) !important;
|
||||||
box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.1), inset 0px 0px 15px 0px rgba(0, 0, 0, 0.1) !important;
|
box-shadow:
|
||||||
|
0px 0px 10px 0px rgba(0, 0, 0, 0.1),
|
||||||
|
inset 0px 0px 15px 0px rgba(0, 0, 0, 0.1) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.dark #main > .course > .content > h1 {
|
.dark #main > .course > .content > h1 {
|
||||||
@@ -1989,7 +2049,7 @@ div.entry.new {
|
|||||||
|
|
||||||
div.liveEntry {
|
div.liveEntry {
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.dailycalMarker {
|
div.dailycalMarker {
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
@@ -2140,24 +2200,23 @@ div.bar.flat {
|
|||||||
background: var(--background-secondary) !important;
|
background: var(--background-secondary) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashlet-motd {
|
.dashlet-motd {
|
||||||
padding: 7px !important;
|
padding: 7px !important;
|
||||||
.message {
|
.message {
|
||||||
font-size: 24px !important;
|
font-size: 24px !important;
|
||||||
border-radius: 12px !important;
|
border-radius: 12px !important;
|
||||||
border: none !important;
|
border: none !important;
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
color: #fff !important;
|
padding: 16px !important;
|
||||||
padding: 16px !important;
|
margin: 0 !important;
|
||||||
margin: 0 !important;
|
height: 100% !important;
|
||||||
height: 100% !important;
|
max-height: none !important;
|
||||||
max-height: none !important;
|
display: flex !important;
|
||||||
display: flex !important;
|
align-items: flex-start !important;
|
||||||
align-items: flex-start !important;
|
overflow: hidden !important;
|
||||||
overflow: hidden !important;
|
color: var(--text-primary) !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.cke_toolbox > .cke_toolbar > .cke_toolgroup > .cke_button {
|
.cke_toolbox > .cke_toolbar > .cke_toolgroup > .cke_button {
|
||||||
background: var(--background-secondary) !important;
|
background: var(--background-secondary) !important;
|
||||||
@@ -3300,6 +3359,22 @@ div.day-empty {
|
|||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
transform-origin: center center;
|
transform-origin: center center;
|
||||||
}
|
}
|
||||||
|
.whatsnewTextContainer.privacyStatement p {
|
||||||
|
margin-bottom: 1.5ex;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.whatsnewTextContainer.privacyStatement a {
|
||||||
|
background: rgba(184, 184, 184, 0.1);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
margin-left: 0.25rem;
|
||||||
|
padding: 2px 4px;
|
||||||
|
}
|
||||||
|
.dark .whatsnewTextContainer.privacyStatement a {
|
||||||
|
background: rgba(7, 7, 7, 0.1);
|
||||||
|
}
|
||||||
.whatsnewHeader {
|
.whatsnewHeader {
|
||||||
margin: 20px;
|
margin: 20px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -3334,6 +3409,7 @@ div.day-empty {
|
|||||||
.whatsnewImg {
|
.whatsnewImg {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
width: 90%;
|
width: 90%;
|
||||||
|
aspect-ratio: 16 / 10;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.3);
|
box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.3);
|
||||||
}
|
}
|
||||||
@@ -3360,7 +3436,6 @@ div.day-empty {
|
|||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: #9a3412;
|
color: #9a3412;
|
||||||
background-color: #ffedd569;
|
|
||||||
border-radius: 9999px;
|
border-radius: 9999px;
|
||||||
border: 1px solid rgba(253, 186, 140, 0.3);
|
border: 1px solid rgba(253, 186, 140, 0.3);
|
||||||
background-color: #ffedd5;
|
background-color: #ffedd5;
|
||||||
@@ -3711,14 +3786,19 @@ div.day-empty {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.notice-unified-content {
|
.notice-unified-content {
|
||||||
h1, h2, h3, h4, h5, h6 {
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
font-weight: inherit !important;
|
font-weight: inherit !important;
|
||||||
color: inherit !important;
|
color: inherit !important;
|
||||||
text-shadow: none !important;
|
text-shadow: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notice-header {
|
.notice-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -3727,16 +3807,16 @@ div.day-empty {
|
|||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notice-content-title {
|
.notice-content-title {
|
||||||
font-size: 20px !important; // Nice middle ground - not too big, not too small
|
font-size: 20px !important; // Nice middle ground - not too big, not too small
|
||||||
font-weight: 600 !important;
|
font-weight: 600 !important;
|
||||||
color: var(--text-primary) !important;
|
color: var(--text-primary) !important;
|
||||||
margin: 0 0 12px 0 !important;
|
margin: 0 0 12px !important;
|
||||||
line-height: 1.3 !important;
|
line-height: 1.3 !important;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notice-content-body {
|
.notice-content-body {
|
||||||
font-size: 14px !important;
|
font-size: 14px !important;
|
||||||
color: var(--text-secondary) !important;
|
color: var(--text-secondary) !important;
|
||||||
@@ -3748,72 +3828,73 @@ div.day-empty {
|
|||||||
min-width: 600px; // Ensure tables have consistent width for layout
|
min-width: 600px; // Ensure tables have consistent width for layout
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The ONLY difference between states is clipping!
|
// The ONLY difference between states is clipping!
|
||||||
&.notice-card-state {
|
&.notice-card-state {
|
||||||
.notice-content-body {
|
.notice-content-body {
|
||||||
// Clip to show only 2 lines but keep full layout
|
// Clip to show only 2 lines but keep full layout
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
max-height: 3em; // ~2 lines worth of height
|
max-height: 3em; // ~2 lines worth of height
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.notice-modal-state {
|
&.notice-modal-state {
|
||||||
.notice-close-btn {
|
.notice-close-btn {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notice-content-body {
|
.notice-content-body {
|
||||||
// Show full content with scrolling
|
// Show full content with scrolling
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
// Custom scrollbar for long content
|
// Custom scrollbar for long content
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
width: 6px;
|
width: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::-webkit-scrollbar-track {
|
&::-webkit-scrollbar-track {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::-webkit-scrollbar-thumb {
|
&::-webkit-scrollbar-thumb {
|
||||||
background: rgba(255, 255, 255, 0.2);
|
background: rgba(255, 255, 255, 0.2);
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: rgba(255, 255, 255, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Style content elements nicely
|
&::-webkit-scrollbar-thumb:hover {
|
||||||
p {
|
background: rgba(255, 255, 255, 0.3);
|
||||||
margin-bottom: 12px;
|
}
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
// Style content elements nicely
|
||||||
color: var(--theme-primary);
|
p {
|
||||||
text-decoration: none;
|
margin-bottom: 12px;
|
||||||
|
|
||||||
&:hover {
|
&:last-child {
|
||||||
text-decoration: underline;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ul, ol {
|
a {
|
||||||
margin: 12px 0;
|
color: var(--theme-primary);
|
||||||
padding-left: 20px;
|
text-decoration: none;
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
&:hover {
|
||||||
margin-bottom: 4px;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
ul,
|
||||||
|
ol {
|
||||||
|
margin: 12px 0;
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.notice-header {
|
.notice-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -3843,7 +3924,6 @@ button.notice-close-btn {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 18px;
|
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
transition: all 0.2s ease !important;
|
transition: all 0.2s ease !important;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
@@ -3901,33 +3981,33 @@ button.notice-close-btn {
|
|||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
margin: 16px 20px 20px 20px;
|
margin: 16px 20px 20px;
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notice-modal-body {
|
.notice-modal-body {
|
||||||
padding: 0 20px 20px 20px;
|
padding: 0 20px 20px;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
// Custom scrollbar
|
// Custom scrollbar
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
width: 6px;
|
width: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::-webkit-scrollbar-track {
|
&::-webkit-scrollbar-track {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::-webkit-scrollbar-thumb {
|
&::-webkit-scrollbar-thumb {
|
||||||
background: rgba(255, 255, 255, 0.2);
|
background: rgba(255, 255, 255, 0.2);
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::-webkit-scrollbar-thumb:hover {
|
&::-webkit-scrollbar-thumb:hover {
|
||||||
background: rgba(255, 255, 255, 0.3);
|
background: rgba(255, 255, 255, 0.3);
|
||||||
}
|
}
|
||||||
@@ -3935,7 +4015,7 @@ button.notice-close-btn {
|
|||||||
// Style content elements
|
// Style content elements
|
||||||
p {
|
p {
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
@@ -3950,7 +4030,8 @@ button.notice-close-btn {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ul, ol {
|
ul,
|
||||||
|
ol {
|
||||||
margin: 12px 0;
|
margin: 12px 0;
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
}
|
}
|
||||||
@@ -3964,7 +4045,7 @@ button.notice-close-btn {
|
|||||||
.dark {
|
.dark {
|
||||||
.notice-card {
|
.notice-card {
|
||||||
border-color: rgba(255, 255, 255, 0.05);
|
border-color: rgba(255, 255, 255, 0.05);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border-color: rgba(255, 255, 255, 0.1);
|
border-color: rgba(255, 255, 255, 0.1);
|
||||||
}
|
}
|
||||||
@@ -3987,17 +4068,17 @@ button.notice-close-btn {
|
|||||||
|
|
||||||
.notice-modal-title {
|
.notice-modal-title {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
margin: 12px 16px 16px 16px;
|
margin: 12px 16px 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notice-modal-body {
|
.notice-modal-body {
|
||||||
padding: 0 16px 16px 16px;
|
padding: 0 16px 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notice-card {
|
.notice-card {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notice-preview {
|
.notice-preview {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"last_updated": "2024-06-15T12:00:00Z",
|
||||||
|
"whatsnew_html": "<div class=\"whatsnewTextContainer\" style=\"overflow-y: auto; font-size: 1.3rem; line-height: 1.6;\"><p>It has come to our attention that several schools have expressed concerns about BetterSEQTA+. This is very disheartening, so we have decided to release a statement on the situation.</p><p>To view our privacy policy, please click the <strong>shield icon</strong> in the settings menu, or <a href=\"https://betterseqta.org/privacy\" target=\"_blank\" rel=\"noopener noreferrer\" id=\"privacy-link\" style=\"color: inherit; text-decoration: underline; cursor: pointer; white-space: nowrap;\">click here</a>.</p><p style=\"font-weight: bold; margin-top: 15px;\">We never collect any information from you, and aim to provide the best features possible.</p></div>"
|
||||||
|
}
|
||||||
@@ -2,6 +2,16 @@ div:has(> #rbgcp-wrapper) {
|
|||||||
background: transparent !important;
|
background: transparent !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#rbgcp-inputs-wrap {
|
||||||
|
padding-top: 4px !important;
|
||||||
|
margin-bottom: -8px;
|
||||||
|
|
||||||
|
#rbgcp-hex-input,
|
||||||
|
#rbgcp-input {
|
||||||
|
height: 28px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
#rbgcp-wrapper {
|
#rbgcp-wrapper {
|
||||||
div[style="padding-top: 11px; position: relative;"] div {
|
div[style="padding-top: 11px; position: relative;"] div {
|
||||||
|
|||||||
@@ -108,7 +108,6 @@ export default function Picker({
|
|||||||
<ColorPicker
|
<ColorPicker
|
||||||
disableDarkMode={true}
|
disableDarkMode={true}
|
||||||
presets={presets}
|
presets={presets}
|
||||||
hideInputs={customOnChange ? false : true}
|
|
||||||
value={customThemeColor ?? ""}
|
value={customThemeColor ?? ""}
|
||||||
onChange={(color: string) => {
|
onChange={(color: string) => {
|
||||||
if (customOnChange) {
|
if (customOnChange) {
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { fade } from 'svelte/transition';
|
||||||
|
import { animate } from 'motion';
|
||||||
|
|
||||||
|
let { onConfirm, onCancel, title, message } = $props<{
|
||||||
|
onConfirm: () => void;
|
||||||
|
onCancel: () => void;
|
||||||
|
title: string;
|
||||||
|
message: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
let modalElement: HTMLElement;
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (modalElement) {
|
||||||
|
animate(
|
||||||
|
modalElement,
|
||||||
|
{ scale: [0.9, 1], opacity: [0, 1] },
|
||||||
|
{
|
||||||
|
type: 'spring',
|
||||||
|
stiffness: 300,
|
||||||
|
damping: 25
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="flex fixed inset-0 z-[10000] justify-center items-center bg-black/50"
|
||||||
|
style="position: fixed; top: 0; left: 0; right: 0; bottom: 0;"
|
||||||
|
onclick={(e) => {
|
||||||
|
if (e.target === e.currentTarget) onCancel();
|
||||||
|
}}
|
||||||
|
onkeydown={(e) => {
|
||||||
|
if (e.key === 'Escape') onCancel();
|
||||||
|
}}
|
||||||
|
role="button"
|
||||||
|
tabindex="-1"
|
||||||
|
transition:fade={{ duration: 150 }}
|
||||||
|
>
|
||||||
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
|
<div
|
||||||
|
bind:this={modalElement}
|
||||||
|
class="p-4 mx-4 w-full max-w-md bg-white rounded-2xl shadow-2xl dark:bg-zinc-800"
|
||||||
|
onclick={(e) => e.stopPropagation()}
|
||||||
|
onkeydown={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<h2 class="mb-3 text-xl font-bold text-gray-900 dark:text-white">
|
||||||
|
{title}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div class="mb-6 text-lg text-gray-700 whitespace-pre-line dark:text-gray-300">
|
||||||
|
{message}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex gap-3 justify-end">
|
||||||
|
<button
|
||||||
|
onclick={onCancel}
|
||||||
|
class="px-4 py-2 text-sm font-medium text-gray-700 bg-gray-100 rounded-lg transition-colors hover:bg-gray-200 dark:bg-zinc-700 dark:text-gray-200 dark:hover:bg-zinc-600"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onclick={onConfirm}
|
||||||
|
class="px-4 py-2 text-sm font-medium text-white bg-green-600 rounded-lg shadow-inner transition-colors hover:bg-green-700 dark:bg-green-500 dark:hover:bg-green-600"
|
||||||
|
>
|
||||||
|
Enable
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
@@ -8,12 +8,12 @@
|
|||||||
let select: HTMLSelectElement;
|
let select: HTMLSelectElement;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="border dark:bg-[#38373D]/50 bg-[#DDDDDD]/50 border-[#DDDDDD]/30 dark:border-[#38373D]/30 shadow-2xl rounded-lg w-full overflow-clip">
|
<div class="border dark:bg-[#38373D]/50 bg-[#DDDDDD]/50 border-[#DDDDDD]/30 dark:border-[#38373D]/30 shadow-2xl rounded-xl w-full overflow-clip">
|
||||||
<select
|
<select
|
||||||
bind:this={select}
|
bind:this={select}
|
||||||
value={state}
|
value={state}
|
||||||
onchange={() => onChange(select.value)}
|
onchange={() => onChange(select.value)}
|
||||||
class="px-4 py-1 text-[0.75rem] dark:text-white w-full border-none bg-transparent focus:ring-0 focus:bg-white/20 dark:focus:bg-black/10"
|
class="px-4 py-2 pr-9 text-[0.875rem] font-medium text-black dark:text-white w-full border-none bg-white/80 dark:bg-zinc-800/70 hover:bg-white/90 dark:hover:bg-zinc-800/80 focus:bg-white/90 dark:focus:bg-zinc-800/80 focus:ring-0 rounded-md appearance-none transition-colors"
|
||||||
>
|
>
|
||||||
{#each options as option}
|
{#each options as option}
|
||||||
<option value={option.value}>
|
<option value={option.value}>
|
||||||
@@ -22,3 +22,19 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* Make native dropdown list readable on Windows */
|
||||||
|
select option {
|
||||||
|
background-color: #ffffff;
|
||||||
|
color: #111827; /* zinc-900 */
|
||||||
|
}
|
||||||
|
:global(.dark) select option {
|
||||||
|
background-color: #1f2937; /* zinc-800 */
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.dark) div::after {
|
||||||
|
color: rgba(255, 255, 255, 0.6);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -11,13 +11,16 @@
|
|||||||
|
|
||||||
import { closeExtensionPopup } from "@/seqta/utils/Closers/closeExtensionPopup";
|
import { closeExtensionPopup } from "@/seqta/utils/Closers/closeExtensionPopup";
|
||||||
import { OpenAboutPage } from "@/seqta/utils/Openers/OpenAboutPage";
|
import { OpenAboutPage } from "@/seqta/utils/Openers/OpenAboutPage";
|
||||||
import { OpenWhatsNewPopup } from "@/seqta/utils/Whatsnew";
|
import { OpenWhatsNewPopup } from "@/seqta/utils/Openers/OpenWhatsNewPopup";
|
||||||
import { OpenMinecraftServerPopup } from "@/seqta/utils/AboutMinecraftServer";
|
//import { OpenMinecraftServerPopup } from "@/seqta/utils/Openers/OpenMinecraftServerPopup";
|
||||||
|
|
||||||
import ColourPicker from "../components/ColourPicker.svelte";
|
import ColourPicker from "../components/ColourPicker.svelte";
|
||||||
|
import DisclaimerModal from "../components/DisclaimerModal.svelte";
|
||||||
import { settingsPopup } from "../hooks/SettingsPopup";
|
import { settingsPopup } from "../hooks/SettingsPopup";
|
||||||
|
|
||||||
let devModeSequence = "";
|
let devModeSequence = "";
|
||||||
|
let showDisclaimerModal = $state(false);
|
||||||
|
let disclaimerCallbacks = $state<{ onConfirm: () => void, onCancel: () => void } | null>(null);
|
||||||
|
|
||||||
const handleDevModeToggle = () => {
|
const handleDevModeToggle = () => {
|
||||||
const handleKeyDown = (event: KeyboardEvent) => {
|
const handleKeyDown = (event: KeyboardEvent) => {
|
||||||
@@ -50,14 +53,24 @@
|
|||||||
closeExtensionPopup();
|
closeExtensionPopup();
|
||||||
};
|
};
|
||||||
|
|
||||||
const openMinecraftServer = () => {
|
/* const openMinecraftServer = () => {
|
||||||
OpenMinecraftServerPopup();
|
OpenMinecraftServerPopup();
|
||||||
closeExtensionPopup();
|
closeExtensionPopup();
|
||||||
|
}; */
|
||||||
|
|
||||||
|
const openPrivacyStatement = () => {
|
||||||
|
window.open("https://betterseqta.org/privacy", "_blank");
|
||||||
|
closeExtensionPopup();
|
||||||
};
|
};
|
||||||
|
|
||||||
let { standalone } = $props<{ standalone?: boolean }>();
|
let { standalone } = $props<{ standalone?: boolean }>();
|
||||||
let showColourPicker = $state<boolean>(false);
|
let showColourPicker = $state<boolean>(false);
|
||||||
|
|
||||||
|
const showDisclaimer = (onConfirm: () => void, onCancel: () => void) => {
|
||||||
|
disclaimerCallbacks = { onConfirm, onCancel };
|
||||||
|
showDisclaimerModal = true;
|
||||||
|
};
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
settingsPopup.addListener(() => {
|
settingsPopup.addListener(() => {
|
||||||
showColourPicker = false;
|
showColourPicker = false;
|
||||||
@@ -101,25 +114,34 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{#if !standalone}
|
{#if !standalone}
|
||||||
<button
|
<div class="flex absolute top-1 right-1 gap-1 items-center">
|
||||||
onclick={openAbout}
|
<button
|
||||||
class="absolute top-1 right-[62px] w-8 h-8 text-lg rounded-xl font-IconFamily bg-zinc-100 dark:bg-zinc-700"
|
onclick={openAbout}
|
||||||
>
|
class="flex justify-center items-center w-8 h-8 text-lg rounded-xl font-IconFamily bg-zinc-100 dark:bg-zinc-700"
|
||||||
{"\ueb73"}
|
>
|
||||||
</button>
|
{"\ueb73"}
|
||||||
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onclick={openChangelog}
|
onclick={openChangelog}
|
||||||
class="absolute top-1 right-10 w-8 h-8 text-lg rounded-xl font-IconFamily bg-zinc-100 dark:bg-zinc-700"
|
class="flex justify-center items-center w-8 h-8 text-lg rounded-xl font-IconFamily bg-zinc-100 dark:bg-zinc-700"
|
||||||
>
|
>
|
||||||
{"\ue929"}
|
{"\ue929"}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onclick={openMinecraftServer}
|
onclick={openPrivacyStatement}
|
||||||
class="absolute top-1 right-1 w-8 h-8 bg-zinc-100 dark:bg-zinc-700 rounded-xl p-1"
|
class="flex justify-center items-center w-8 h-8 text-lg rounded-xl font-IconFamily bg-zinc-100 dark:bg-zinc-700"
|
||||||
aria-label="Open Minecraft Server"
|
aria-label="Privacy Statement"
|
||||||
>
|
>
|
||||||
|
{"\uecba"}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- <button
|
||||||
|
onclick={openMinecraftServer}
|
||||||
|
class="flex justify-center items-center p-1 w-8 h-8 rounded-xl bg-zinc-100 dark:bg-zinc-700"
|
||||||
|
aria-label="Open Minecraft Server"
|
||||||
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 64 70"
|
viewBox="0 0 64 70"
|
||||||
@@ -247,7 +269,8 @@
|
|||||||
transform="translate(18,10)"
|
transform="translate(18,10)"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button> -->
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -256,7 +279,7 @@
|
|||||||
{
|
{
|
||||||
title: "Settings",
|
title: "Settings",
|
||||||
Content: Settings,
|
Content: Settings,
|
||||||
props: { showColourPicker: openColourPicker },
|
props: { showColourPicker: openColourPicker, showDisclaimer },
|
||||||
},
|
},
|
||||||
{ title: "Shortcuts", Content: Shortcuts },
|
{ title: "Shortcuts", Content: Shortcuts },
|
||||||
{ title: "Themes", Content: Theme },
|
{ title: "Themes", Content: Theme },
|
||||||
@@ -272,3 +295,27 @@
|
|||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{#if showDisclaimerModal && disclaimerCallbacks}
|
||||||
|
<DisclaimerModal
|
||||||
|
title="Assessment Averages Disclaimer"
|
||||||
|
message="This feature calculates a simple average of your assessment grades. It does not take into account:
|
||||||
|
• Assessment weightings
|
||||||
|
• Different grading scales
|
||||||
|
• Other factors used in official reports
|
||||||
|
|
||||||
|
The displayed average may be inaccurate compared to your actual marks found in reports.
|
||||||
|
|
||||||
|
Do you want to enable this feature?"
|
||||||
|
onConfirm={() => {
|
||||||
|
disclaimerCallbacks?.onConfirm();
|
||||||
|
showDisclaimerModal = false;
|
||||||
|
disclaimerCallbacks = null;
|
||||||
|
}}
|
||||||
|
onCancel={() => {
|
||||||
|
disclaimerCallbacks?.onCancel();
|
||||||
|
showDisclaimerModal = false;
|
||||||
|
disclaimerCallbacks = null;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
import type { SettingsList } from "@/interface/types/SettingsProps"
|
import type { SettingsList } from "@/interface/types/SettingsProps"
|
||||||
import { settingsState } from "@/seqta/utils/listeners/SettingsState.ts"
|
import { settingsState } from "@/seqta/utils/listeners/SettingsState.ts"
|
||||||
import PickerSwatch from "@/interface/components/PickerSwatch.svelte"
|
import PickerSwatch from "@/interface/components/PickerSwatch.svelte"
|
||||||
|
import { showPrivacyNotification } from "@/seqta/utils/Openers/OpenPrivacyNotification"
|
||||||
|
import { closeExtensionPopup } from "@/seqta/utils/Closers/closeExtensionPopup"
|
||||||
|
|
||||||
import { getAllPluginSettings } from "@/plugins"
|
import { getAllPluginSettings } from "@/plugins"
|
||||||
import type { BooleanSetting, StringSetting, NumberSetting, SelectSetting, ButtonSetting, HotkeySetting, ComponentSetting } from "@/plugins/core/types"
|
import type { BooleanSetting, StringSetting, NumberSetting, SelectSetting, ButtonSetting, HotkeySetting, ComponentSetting } from "@/plugins/core/types"
|
||||||
@@ -90,7 +92,10 @@
|
|||||||
loadPluginSettings();
|
loadPluginSettings();
|
||||||
})
|
})
|
||||||
|
|
||||||
const { showColourPicker } = $props<{ showColourPicker: () => void }>();
|
const { showColourPicker, showDisclaimer } = $props<{
|
||||||
|
showColourPicker: () => void;
|
||||||
|
showDisclaimer: (onConfirm: () => void, onCancel: () => void) => void;
|
||||||
|
}>();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#snippet Setting({ title, description, Component, props }: SettingsList) }
|
{#snippet Setting({ title, description, Component, props }: SettingsList) }
|
||||||
@@ -183,18 +188,19 @@
|
|||||||
props: {
|
props: {
|
||||||
state: $settingsState.newsSource,
|
state: $settingsState.newsSource,
|
||||||
onChange: (value: string) => settingsState.newsSource = value,
|
onChange: (value: string) => settingsState.newsSource = value,
|
||||||
options: [
|
options: [
|
||||||
{ value: "australia", label: "Australia" },
|
{ value: "australia", label: "Australia" },
|
||||||
{ value: "usa", label: "USA" },
|
{ value: "usa", label: "USA" },
|
||||||
{ value: "taiwan", label: "Taiwan" },
|
{ value: "uk", label: "UK" },
|
||||||
{ value: "hong_kong", label: "Hong Kong" },
|
{ value: "taiwan", label: "Taiwan" },
|
||||||
{ value: "panama", label: "Panama" },
|
{ value: "hong_kong", label: "Hong Kong" },
|
||||||
{ value: "canada", label: "Canada" },
|
{ value: "panama", label: "Panama" },
|
||||||
{ value: "singapore", label: "Singapore" },
|
{ value: "canada", label: "Canada" },
|
||||||
{ value: "uk", label: "UK" },
|
{ value: "singapore", label: "Singapore" },
|
||||||
{ value: "japan", label: "Japan" },
|
{ value: "japan", label: "Japan" },
|
||||||
{ value: "netherlands", label: "Netherlands" }
|
{ value: "netherlands", label: "Netherlands" }
|
||||||
]
|
]
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
] as option}
|
] as option}
|
||||||
@@ -221,7 +227,20 @@
|
|||||||
<div>
|
<div>
|
||||||
<Switch
|
<Switch
|
||||||
state={pluginSettingsValues[plugin.pluginId]?.enabled ?? true}
|
state={pluginSettingsValues[plugin.pluginId]?.enabled ?? true}
|
||||||
onChange={(value) => updatePluginSetting(plugin.pluginId, 'enabled', value)}
|
onChange={async (value) => {
|
||||||
|
if (plugin.pluginId === 'assessments-average' && value === true) {
|
||||||
|
showDisclaimer(
|
||||||
|
async () => {
|
||||||
|
await updatePluginSetting(plugin.pluginId, 'enabled', true);
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
// Do nothing on cancel
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await updatePluginSetting(plugin.pluginId, 'enabled', value);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -339,6 +358,25 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex justify-between items-center px-4 py-3">
|
||||||
|
<div class="pr-4">
|
||||||
|
<h2 class="text-sm font-bold">Show Privacy Notification</h2>
|
||||||
|
<p class="text-xs">Show the privacy notification popup on next page load</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
onClick={async () => {
|
||||||
|
settingsState.privacyStatementShown = false;
|
||||||
|
settingsState.privacyStatementLastUpdated = undefined;
|
||||||
|
closeExtensionPopup();
|
||||||
|
// Small delay to ensure popup is closed before showing notification
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
|
await showPrivacyNotification();
|
||||||
|
}}
|
||||||
|
text="Show Now"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -23,13 +23,19 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const switchChange = (shortcut: any) => {
|
const switchChange = (shortcut: any) => {
|
||||||
const value = $settingsState.shortcuts.find(s => s.name === shortcut);
|
const idx = $settingsState.shortcuts.findIndex(s => s.name === shortcut);
|
||||||
if (value) {
|
if (idx !== -1) {
|
||||||
value.enabled = !value.enabled;
|
// Create a new array with the toggled value to ensure reactivity
|
||||||
settingsState.shortcuts = settingsState.shortcuts;
|
const updated = settingsState.shortcuts.map(s =>
|
||||||
|
s.name === shortcut ? { ...s, enabled: !s.enabled } : s
|
||||||
|
);
|
||||||
|
settingsState.shortcuts = updated;
|
||||||
} else {
|
} else {
|
||||||
settingsState.shortcuts = [...settingsState.shortcuts, { name: shortcut, enabled: true }];
|
settingsState.shortcuts = [
|
||||||
|
...settingsState.shortcuts,
|
||||||
|
{ name: shortcut, enabled: true }
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,16 +202,6 @@
|
|||||||
</MotionDiv>
|
</MotionDiv>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#each Object.entries(Shortcuts) as shortcut}
|
|
||||||
<div class="flex justify-between items-center px-4 py-3">
|
|
||||||
<div class="pr-4">
|
|
||||||
<!-- Use DisplayName if it exists, otherwise use the key (shortcut[0]) as a fallback -->
|
|
||||||
<h2 class="text-sm">{shortcut[1].DisplayName || shortcut[0]}</h2>
|
|
||||||
</div>
|
|
||||||
<Switch state={$settingsState.shortcuts.find(s => s.name === shortcut[0])?.enabled ?? false} onChange={() => switchChange(shortcut[0])} />
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
|
|
||||||
<!-- Custom Shortcuts Section -->
|
<!-- Custom Shortcuts Section -->
|
||||||
{#each $settingsState.customshortcuts as shortcut, index}
|
{#each $settingsState.customshortcuts as shortcut, index}
|
||||||
<div class="flex justify-between items-center px-4 py-3">
|
<div class="flex justify-between items-center px-4 py-3">
|
||||||
@@ -217,6 +213,16 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
|
{#each Object.entries(Shortcuts) as shortcut}
|
||||||
|
<div class="flex justify-between items-center px-4 py-3">
|
||||||
|
<div class="pr-4">
|
||||||
|
<!-- Use DisplayName if it exists, otherwise use the key (shortcut[0]) as a fallback -->
|
||||||
|
<h2 class="text-sm">{shortcut[1].DisplayName || shortcut[0]}</h2>
|
||||||
|
</div>
|
||||||
|
<Switch state={$settingsState.shortcuts.find(s => s.name === shortcut[0])?.enabled ?? false} onChange={() => switchChange(shortcut[0])} />
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
{:else}
|
{:else}
|
||||||
<div class="p-4 text-center">
|
<div class="p-4 text-center">
|
||||||
Loading shortcuts...
|
Loading shortcuts...
|
||||||
|
|||||||
@@ -21,13 +21,16 @@
|
|||||||
<div class="relative w-full">
|
<div class="relative w-full">
|
||||||
<button
|
<button
|
||||||
onclick={() => editMode = !editMode}
|
onclick={() => editMode = !editMode}
|
||||||
class="absolute top-0 right-0 z-10 w-8 h-8 text-lg rounded-xl font-IconFamily bg-zinc-100 dark:bg-zinc-700">{editMode ? '\ue9e4' : '\uec38'}</button>
|
class="absolute top-0 right-0 z-10 px-2 h-8 text-lg rounded-xl bg-zinc-100 dark:bg-zinc-700">
|
||||||
|
<span class="mr-2">{editMode ? 'Done' : 'Edit'}</span>
|
||||||
|
<span class="font-IconFamily">{editMode ? '\ue9e4' : '\uec38'}</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
<BackgroundSelector isEditMode={editMode} bind:selectedBackground={selectedBackground} bind:selectNoBackground={selectNoBackground} />
|
<BackgroundSelector isEditMode={editMode} bind:selectedBackground={selectedBackground} bind:selectNoBackground={selectNoBackground} />
|
||||||
<ThemeSelector isEditMode={editMode} />
|
<ThemeSelector isEditMode={editMode} />
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex items-center justify-center w-full h-full">
|
<div class="flex justify-center items-center w-full h-full">
|
||||||
<div class="text-lg">
|
<div class="text-lg">
|
||||||
Open SEQTA and use the embedded settings to access theme settings. 🫠
|
Open SEQTA and use the embedded settings to access theme settings. 🫠
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ const updatedFirefoxManifest = {
|
|||||||
},
|
},
|
||||||
browser_specific_settings: {
|
browser_specific_settings: {
|
||||||
gecko: {
|
gecko: {
|
||||||
id: pkg.author.email,
|
id: "betterseqta@betterseqta.com",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import localforage from 'localforage'
|
||||||
|
import { onMount } from 'svelte'
|
||||||
|
|
||||||
|
let fileInput = $state<HTMLInputElement | undefined>(undefined)
|
||||||
|
let dragging = $state(false)
|
||||||
|
let filename = $state<string | undefined>(undefined)
|
||||||
|
let durationText = $state<string | undefined>(undefined)
|
||||||
|
|
||||||
|
const store = localforage.createInstance({
|
||||||
|
name: 'background-music-store',
|
||||||
|
storeName: 'music',
|
||||||
|
})
|
||||||
|
|
||||||
|
async function loadExisting() {
|
||||||
|
const name = await store.getItem<string>('audio-name')
|
||||||
|
filename = name ?? undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => { loadExisting() })
|
||||||
|
|
||||||
|
function triggerSelect() { fileInput?.click() }
|
||||||
|
|
||||||
|
async function handleFiles(files: FileList | null) {
|
||||||
|
const file = files?.[0]
|
||||||
|
if (!file) return
|
||||||
|
// Accept WAV and MP3 files
|
||||||
|
const isSupported = file.type === 'audio/wav' || file.type === 'audio/mpeg' ||
|
||||||
|
file.name.toLowerCase().endsWith('.wav') || file.name.toLowerCase().endsWith('.mp3')
|
||||||
|
if (!isSupported) {
|
||||||
|
alert('Please select a .wav or .mp3 audio file')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await store.setItem('audio-blob', file)
|
||||||
|
await store.setItem('audio-name', file.name)
|
||||||
|
filename = file.name
|
||||||
|
|
||||||
|
// Probe duration
|
||||||
|
try {
|
||||||
|
const url = URL.createObjectURL(file)
|
||||||
|
const audio = new Audio(url)
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
audio.onloadedmetadata = () => resolve()
|
||||||
|
audio.onerror = () => reject()
|
||||||
|
})
|
||||||
|
if (!isNaN(audio.duration) && audio.duration !== Infinity) {
|
||||||
|
const minutes = Math.floor(audio.duration / 60)
|
||||||
|
const seconds = Math.round(audio.duration % 60)
|
||||||
|
durationText = `${minutes}:${seconds.toString().padStart(2, '0')}`
|
||||||
|
} else {
|
||||||
|
durationText = undefined
|
||||||
|
}
|
||||||
|
URL.revokeObjectURL(url)
|
||||||
|
} catch {
|
||||||
|
durationText = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
window.dispatchEvent(new Event('betterseqta-background-music-updated'))
|
||||||
|
}
|
||||||
|
|
||||||
|
function onFileChange() { handleFiles(fileInput?.files || null) }
|
||||||
|
|
||||||
|
function onDrop(event: DragEvent) {
|
||||||
|
event.preventDefault()
|
||||||
|
dragging = false
|
||||||
|
handleFiles(event.dataTransfer?.files || null)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function removeAudio() {
|
||||||
|
await store.removeItem('audio-blob')
|
||||||
|
await store.removeItem('audio-name')
|
||||||
|
filename = undefined
|
||||||
|
durationText = undefined
|
||||||
|
window.dispatchEvent(new Event('betterseqta-background-music-stop'))
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="relative cursor-pointer select-none"
|
||||||
|
onclick={() => triggerSelect()}
|
||||||
|
ondragover={(e) => { e.stopPropagation(); dragging = true }}
|
||||||
|
ondragleave={() => dragging = false}
|
||||||
|
ondrop={onDrop}
|
||||||
|
onkeydown={(e) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
e.preventDefault()
|
||||||
|
triggerSelect()
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<div class="flex gap-3 items-center">
|
||||||
|
{#if filename}
|
||||||
|
<div class="flex items-center px-3 py-1 rounded-lg bg-zinc-200 dark:bg-zinc-800">
|
||||||
|
<div class="text-xs text-zinc-600 dark:text-zinc-300">
|
||||||
|
{filename}
|
||||||
|
<p>{durationText}</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="flex justify-center items-center m-1 text-lg dark:text-white size-7"
|
||||||
|
onclick={(e) => { e.stopPropagation(); removeAudio() }}
|
||||||
|
aria-label="Remove audio"
|
||||||
|
>×</button>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="flex gap-2 items-center px-3 py-1 text-xs rounded-lg border border-dashed transition border-zinc-300 dark:border-zinc-600 text-zinc-500 dark:text-zinc-400 hover:text-zinc-700 dark:hover:text-zinc-300 text-nowrap">
|
||||||
|
<span class="text-lg font-IconFamily">{'\ued47'}</span>
|
||||||
|
<span>Upload audio</span>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<input type="file" accept="audio/wav,audio/mpeg" class="hidden" bind:this={fileInput} onchange={onFileChange} />
|
||||||
|
{#if dragging}
|
||||||
|
<div class="absolute inset-0 rounded-lg bg-zinc-200/40 dark:bg-zinc-700/40"></div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,187 @@
|
|||||||
|
import type { Plugin } from "@/plugins/core/types";
|
||||||
|
import { componentSetting, defineSettings, numberSetting, booleanSetting } from "@/plugins/core/settingsHelpers";
|
||||||
|
import styles from "./styles.css?inline";
|
||||||
|
import BackgroundMusicSetting from "./BackgroundMusicSetting.svelte";
|
||||||
|
import localforage from "localforage";
|
||||||
|
|
||||||
|
const settings = defineSettings({
|
||||||
|
uploader: componentSetting({
|
||||||
|
title: "Background Music",
|
||||||
|
description: "Upload a .wav or .mp3 audio file to play in the background.",
|
||||||
|
component: BackgroundMusicSetting,
|
||||||
|
}),
|
||||||
|
volume: numberSetting({
|
||||||
|
title: "Volume",
|
||||||
|
description: "Set background music volume",
|
||||||
|
default: 0.5,
|
||||||
|
min: 0,
|
||||||
|
max: 1,
|
||||||
|
step: 0.05,
|
||||||
|
}),
|
||||||
|
pauseOnHidden: booleanSetting({
|
||||||
|
title: "Pause when tab hidden",
|
||||||
|
description: "Pause music when switching to another tab or minimizing the browser",
|
||||||
|
default: true,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const store = localforage.createInstance({
|
||||||
|
name: "background-music-store",
|
||||||
|
storeName: "music",
|
||||||
|
});
|
||||||
|
|
||||||
|
let currentAudio: HTMLAudioElement | null = null;
|
||||||
|
let currentObjectUrl: string | null = null;
|
||||||
|
let cleanupRegistered = false;
|
||||||
|
let pendingGestureCancel: (() => void) | null = null;
|
||||||
|
let visibilityResumeTimeout: number | null = null;
|
||||||
|
|
||||||
|
async function loadAudioBlob(): Promise<Blob | null> {
|
||||||
|
const blob = await store.getItem<Blob>("audio-blob");
|
||||||
|
return blob && blob instanceof Blob ? blob : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopAndCleanupAudio(): void {
|
||||||
|
if (currentAudio) {
|
||||||
|
currentAudio.pause();
|
||||||
|
currentAudio.src = "";
|
||||||
|
currentAudio.remove();
|
||||||
|
currentAudio = null;
|
||||||
|
}
|
||||||
|
if (currentObjectUrl) {
|
||||||
|
URL.revokeObjectURL(currentObjectUrl);
|
||||||
|
currentObjectUrl = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureGestureStart(handler: () => void): () => void {
|
||||||
|
const eventTypes = ["pointerdown", "keydown", "touchstart"]; // broad user gesture coverage
|
||||||
|
const listener = () => {
|
||||||
|
handler();
|
||||||
|
for (const type of eventTypes) {
|
||||||
|
window.removeEventListener(type, listener);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
for (const type of eventTypes) {
|
||||||
|
window.addEventListener(type, listener, { once: true, passive: true });
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
for (const type of eventTypes) {
|
||||||
|
window.removeEventListener(type, listener);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function startPlayback(volume: number): Promise<void> {
|
||||||
|
const blob = await loadAudioBlob();
|
||||||
|
if (!blob) return;
|
||||||
|
|
||||||
|
stopAndCleanupAudio();
|
||||||
|
|
||||||
|
currentObjectUrl = URL.createObjectURL(blob);
|
||||||
|
const audio = new Audio(currentObjectUrl);
|
||||||
|
audio.loop = true;
|
||||||
|
audio.volume = Math.max(0, Math.min(1, volume));
|
||||||
|
audio.preload = "auto";
|
||||||
|
audio.crossOrigin = "anonymous";
|
||||||
|
audio.style.display = "none";
|
||||||
|
document.body.appendChild(audio);
|
||||||
|
currentAudio = audio;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Attempt immediate play; may be blocked until gesture
|
||||||
|
await audio.play();
|
||||||
|
} catch {
|
||||||
|
// Ignore; will be started after gesture if enabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const backgroundMusicPlugin: Plugin<typeof settings> = {
|
||||||
|
id: "background-music",
|
||||||
|
name: "Background Music",
|
||||||
|
description: "Play your own music in the background while SEQTA is open.",
|
||||||
|
version: "1.0.0",
|
||||||
|
settings,
|
||||||
|
styles,
|
||||||
|
disableToggle: true,
|
||||||
|
defaultEnabled: false,
|
||||||
|
|
||||||
|
run: async (api) => {
|
||||||
|
await api.storage.loaded;
|
||||||
|
|
||||||
|
// react to specific setting changes
|
||||||
|
api.settings.onChange("volume" as any, (value: any) => {
|
||||||
|
const vol = (typeof value === "number" ? value : 0.5) as number;
|
||||||
|
if (currentAudio) currentAudio.volume = Math.max(0, Math.min(1, vol));
|
||||||
|
});
|
||||||
|
|
||||||
|
api.settings.onChange("pauseOnHidden" as any, (value: any) => {
|
||||||
|
const pauseOnHidden = (typeof value === "boolean" ? value : true) as boolean;
|
||||||
|
// If the setting is disabled and audio is currently paused due to tab being hidden, resume it
|
||||||
|
if (!pauseOnHidden && currentAudio && currentAudio.paused && document.visibilityState === "hidden") {
|
||||||
|
currentAudio.play().catch(() => {});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Note: Stop button/event removed by user; no stop handling needed
|
||||||
|
|
||||||
|
// Start if we have audio and autoplay is enabled
|
||||||
|
const tryStart = async () => {
|
||||||
|
const vol = (api.settings as any).volume ?? 0.5;
|
||||||
|
await startPlayback(vol);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Always arm gesture start and attempt immediate start
|
||||||
|
const cancel = ensureGestureStart(() => { tryStart(); });
|
||||||
|
cleanupRegistered = true;
|
||||||
|
(window as any).__betterseqta_bg_music_cancel__ = cancel;
|
||||||
|
tryStart();
|
||||||
|
|
||||||
|
// Pause on tab hide, resume on show with a small delay (if enabled)
|
||||||
|
const visHandler = () => {
|
||||||
|
if (!currentAudio) return;
|
||||||
|
const pauseOnHidden = (api.settings as any).pauseOnHidden ?? true;
|
||||||
|
if (!pauseOnHidden) return;
|
||||||
|
|
||||||
|
if (document.visibilityState === "hidden") {
|
||||||
|
if (visibilityResumeTimeout !== null) {
|
||||||
|
clearTimeout(visibilityResumeTimeout);
|
||||||
|
visibilityResumeTimeout = null;
|
||||||
|
}
|
||||||
|
currentAudio.pause();
|
||||||
|
} else if (document.visibilityState === "visible") {
|
||||||
|
if (visibilityResumeTimeout !== null) {
|
||||||
|
clearTimeout(visibilityResumeTimeout);
|
||||||
|
}
|
||||||
|
visibilityResumeTimeout = window.setTimeout(() => {
|
||||||
|
visibilityResumeTimeout = null;
|
||||||
|
currentAudio?.play().catch(() => {});
|
||||||
|
}, 200);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.addEventListener("visibilitychange", visHandler);
|
||||||
|
|
||||||
|
// Allow uploads to trigger refresh
|
||||||
|
const uploadedHandler = () => {
|
||||||
|
const vol = (api.settings as any).volume ?? 0.5;
|
||||||
|
startPlayback(vol);
|
||||||
|
};
|
||||||
|
window.addEventListener("betterseqta-background-music-updated", uploadedHandler);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("visibilitychange", visHandler);
|
||||||
|
window.removeEventListener("betterseqta-background-music-updated", uploadedHandler);
|
||||||
|
if (cleanupRegistered && (window as any).__betterseqta_bg_music_cancel__) {
|
||||||
|
(window as any).__betterseqta_bg_music_cancel__();
|
||||||
|
(window as any).__betterseqta_bg_music_cancel__ = undefined;
|
||||||
|
}
|
||||||
|
if (pendingGestureCancel) { pendingGestureCancel(); pendingGestureCancel = null; }
|
||||||
|
if (visibilityResumeTimeout !== null) { clearTimeout(visibilityResumeTimeout); visibilityResumeTimeout = null; }
|
||||||
|
stopAndCleanupAudio();
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default backgroundMusicPlugin;
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
.background-music-hidden{display:none}
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ const notificationCollectorPlugin: Plugin<{}, NotificationCollectorStorage> = {
|
|||||||
"[class*='notifications__bubble___']",
|
"[class*='notifications__bubble___']",
|
||||||
) as HTMLElement;
|
) as HTMLElement;
|
||||||
|
|
||||||
if (api.storage.lastNotificationCount !== 0) {
|
if (alertDiv && api.storage.lastNotificationCount !== 0) {
|
||||||
alertDiv.textContent = api.storage.lastNotificationCount.toString();
|
alertDiv.textContent = api.storage.lastNotificationCount.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ const notificationCollectorPlugin: Plugin<{}, NotificationCollectorStorage> = {
|
|||||||
const notificationCount = data.payload.notifications.length;
|
const notificationCount = data.payload.notifications.length;
|
||||||
api.storage.lastNotificationCount = notificationCount;
|
api.storage.lastNotificationCount = notificationCount;
|
||||||
api.storage.lastCheckedTime = new Date().toISOString();
|
api.storage.lastCheckedTime = new Date().toISOString();
|
||||||
|
|
||||||
// Reset error count on success
|
// Reset error count on success
|
||||||
api.storage.consecutiveErrors = 0;
|
api.storage.consecutiveErrors = 0;
|
||||||
|
|
||||||
@@ -74,31 +74,36 @@ const notificationCollectorPlugin: Plugin<{}, NotificationCollectorStorage> = {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[BetterSEQTA+] Error fetching notifications:", error);
|
console.error("[BetterSEQTA+] Error fetching notifications:", error);
|
||||||
api.storage.consecutiveErrors = (api.storage.consecutiveErrors || 0) + 1;
|
api.storage.consecutiveErrors =
|
||||||
|
(api.storage.consecutiveErrors || 0) + 1;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getNextInterval = () => {
|
const getNextInterval = () => {
|
||||||
// Exponential backoff on errors, max 5 minutes
|
// Exponential backoff on errors, max 5 minutes
|
||||||
const errorMultiplier = Math.min(Math.pow(2, api.storage.consecutiveErrors || 0), 10);
|
const errorMultiplier = Math.min(
|
||||||
|
Math.pow(2, api.storage.consecutiveErrors || 0),
|
||||||
|
10,
|
||||||
|
);
|
||||||
return Math.min(baseInterval * errorMultiplier, maxInterval);
|
return Math.min(baseInterval * errorMultiplier, maxInterval);
|
||||||
};
|
};
|
||||||
|
|
||||||
const startPolling = () => {
|
const startPolling = () => {
|
||||||
if (pollInterval) return; // Already polling
|
if (pollInterval) return; // Already polling
|
||||||
checkNotifications();
|
checkNotifications();
|
||||||
|
|
||||||
const scheduleNext = () => {
|
const scheduleNext = () => {
|
||||||
const interval = getNextInterval();
|
const interval = getNextInterval();
|
||||||
pollInterval = window.setTimeout(() => {
|
pollInterval = window.setTimeout(() => {
|
||||||
checkNotifications().then(() => {
|
checkNotifications().then(() => {
|
||||||
if (pollInterval) { // Only continue if not stopped
|
if (pollInterval) {
|
||||||
|
// Only continue if not stopped
|
||||||
scheduleNext();
|
scheduleNext();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, interval);
|
}, interval);
|
||||||
};
|
};
|
||||||
|
|
||||||
scheduleNext();
|
scheduleNext();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -124,14 +129,16 @@ const notificationCollectorPlugin: Plugin<{}, NotificationCollectorStorage> = {
|
|||||||
isVisible = !document.hidden;
|
isVisible = !document.hidden;
|
||||||
if (isVisible && !pollInterval) {
|
if (isVisible && !pollInterval) {
|
||||||
// Resume polling when tab becomes visible
|
// Resume polling when tab becomes visible
|
||||||
const alertDiv = document.querySelector("[class*='notifications__bubble___']");
|
const alertDiv = document.querySelector(
|
||||||
|
"[class*='notifications__bubble___']",
|
||||||
|
);
|
||||||
if (alertDiv) {
|
if (alertDiv) {
|
||||||
startPolling();
|
startPolling();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener('visibilitychange', handleVisibilityChange);
|
document.addEventListener("visibilitychange", handleVisibilityChange);
|
||||||
|
|
||||||
api.seqta.onMount("[class*='notifications__bubble___']", (_) => {
|
api.seqta.onMount("[class*='notifications__bubble___']", (_) => {
|
||||||
startPolling();
|
startPolling();
|
||||||
@@ -139,7 +146,7 @@ const notificationCollectorPlugin: Plugin<{}, NotificationCollectorStorage> = {
|
|||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
stopPolling();
|
stopPolling();
|
||||||
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,12 +8,16 @@
|
|||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
z-index: 4;
|
z-index: 4;
|
||||||
box-shadow: 0 0 0 3px #000000;
|
box-shadow: 0 0 0 3px #000000;
|
||||||
|
transition: box-shadow 0.05s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .userInfoImg {
|
.dark .userInfoImg {
|
||||||
box-shadow: 0 0 0 3px #ffffff;
|
box-shadow: 0 0 0 3px #ffffff;
|
||||||
|
transition: box-shadow 0.05s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.userInfosvgdiv {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
filter: invert(0) !important;
|
.userInfoImg {
|
||||||
}
|
transition: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -147,14 +147,21 @@ export class ThemeManager {
|
|||||||
public async initialize(): Promise<void> {
|
public async initialize(): Promise<void> {
|
||||||
console.debug("[ThemeManager] Starting initialization");
|
console.debug("[ThemeManager] Starting initialization");
|
||||||
try {
|
try {
|
||||||
// Check if theme creator was open during reload
|
const neumorphicThemeId = "9a9786d1-b5fc-4a91-8c7a-f8bf7f7679ad";
|
||||||
|
const migrationCSS = "#title {\nbackground: transparent !important;\n}";
|
||||||
|
|
||||||
|
const theme = (await localforage.getItem(neumorphicThemeId)) as CustomTheme | null;
|
||||||
|
if (theme && theme.CustomCSS && !theme.CustomCSS.includes("#title {\nbackground: transparent !important;\n}")) {
|
||||||
|
theme.CustomCSS = theme.CustomCSS + "\n" + migrationCSS;
|
||||||
|
await localforage.setItem(neumorphicThemeId, theme);
|
||||||
|
}
|
||||||
|
|
||||||
const themeCreatorOpen = localStorage.getItem("themeCreatorOpen");
|
const themeCreatorOpen = localStorage.getItem("themeCreatorOpen");
|
||||||
if (themeCreatorOpen === "true") {
|
if (themeCreatorOpen === "true") {
|
||||||
console.debug(
|
console.debug(
|
||||||
"[ThemeManager] Theme creator was open, clearing preview state",
|
"[ThemeManager] Theme creator was open, clearing preview state",
|
||||||
);
|
);
|
||||||
this.clearPreview();
|
this.clearPreview();
|
||||||
// Clean up the flag
|
|
||||||
localStorage.removeItem("themeCreatorOpen");
|
localStorage.removeItem("themeCreatorOpen");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import animatedBackgroundPlugin from "./built-in/animatedBackground";
|
|||||||
import assessmentsAveragePlugin from "./built-in/assessmentsAverage";
|
import assessmentsAveragePlugin from "./built-in/assessmentsAverage";
|
||||||
import profilePicturePlugin from "./built-in/profilePicture";
|
import profilePicturePlugin from "./built-in/profilePicture";
|
||||||
import assessmentsOverviewPlugin from "./built-in/assessmentsOverview";
|
import assessmentsOverviewPlugin from "./built-in/assessmentsOverview";
|
||||||
|
import backgroundMusicPlugin from "./built-in/backgroundMusic";
|
||||||
//import testPlugin from './built-in/test';
|
//import testPlugin from './built-in/test';
|
||||||
|
|
||||||
// Heavy plugins (lazy-loaded only when enabled)
|
// Heavy plugins (lazy-loaded only when enabled)
|
||||||
@@ -24,6 +25,7 @@ pluginManager.registerPlugin(notificationCollectorPlugin);
|
|||||||
pluginManager.registerPlugin(timetablePlugin);
|
pluginManager.registerPlugin(timetablePlugin);
|
||||||
pluginManager.registerPlugin(profilePicturePlugin);
|
pluginManager.registerPlugin(profilePicturePlugin);
|
||||||
pluginManager.registerPlugin(assessmentsOverviewPlugin);
|
pluginManager.registerPlugin(assessmentsOverviewPlugin);
|
||||||
|
pluginManager.registerPlugin(backgroundMusicPlugin);
|
||||||
//pluginManager.registerPlugin(testPlugin);
|
//pluginManager.registerPlugin(testPlugin);
|
||||||
|
|
||||||
// Register heavy plugins with lazy loading
|
// Register heavy plugins with lazy loading
|
||||||
|
|||||||
@@ -23,12 +23,10 @@ import { updateAllColors } from "@/seqta/ui/colors/Manager";
|
|||||||
import loading from "@/seqta/ui/Loading";
|
import loading from "@/seqta/ui/Loading";
|
||||||
import { SendNewsPage } from "@/seqta/utils/SendNewsPage";
|
import { SendNewsPage } from "@/seqta/utils/SendNewsPage";
|
||||||
import { loadHomePage } from "@/seqta/utils/Loaders/LoadHomePage";
|
import { loadHomePage } from "@/seqta/utils/Loaders/LoadHomePage";
|
||||||
import { OpenWhatsNewPopup } from "@/seqta/utils/Whatsnew";
|
import { OpenWhatsNewPopup } from "@/seqta/utils/Openers/OpenWhatsNewPopup";
|
||||||
//import { OpenMinecraftServerPopup } from "@/seqta/utils/AboutMinecraftServer";
|
import { showPrivacyNotification } from "@/seqta/utils/Openers/OpenPrivacyNotification";
|
||||||
|
|
||||||
import {
|
import { updateTimetableTimes } from "@/seqta/utils/updateTimetableTimes";
|
||||||
updateTimetableTimes,
|
|
||||||
} from "@/seqta/utils/updateTimetableTimes";
|
|
||||||
|
|
||||||
// JSON content
|
// JSON content
|
||||||
import MenuitemSVGKey from "@/seqta/content/MenuItemSVGKey.json";
|
import MenuitemSVGKey from "@/seqta/content/MenuItemSVGKey.json";
|
||||||
@@ -96,7 +94,12 @@ export async function finishLoad() {
|
|||||||
console.error("Error during loading cleanup:", err);
|
console.error("Error during loading cleanup:", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settingsState.justupdated && !document.getElementById("whatsnewbk")) {
|
// 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();
|
OpenWhatsNewPopup();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -173,28 +173,35 @@ async function updateStudentInfo(students: any) {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
let houseelement1 = document.getElementsByClassName("userInfohouse")[0];
|
const houseelement = document.getElementsByClassName("userInfohouse")[0] as HTMLElement;
|
||||||
const houseelement = houseelement1 as HTMLElement;
|
|
||||||
|
// Fallback to N/A
|
||||||
|
let text = 'N/A';
|
||||||
|
const student = students[index] ?? {};
|
||||||
|
|
||||||
|
// If student has a house, prefer to show year + house. If no year, only show house.
|
||||||
|
if (student.house) {
|
||||||
|
text = `${student.year ?? ""}${student.house}`;
|
||||||
|
|
||||||
|
// If house_colour exists, compute colour
|
||||||
|
if (student.house_colour) {
|
||||||
|
houseelement.style.background = student.house_colour;
|
||||||
|
|
||||||
if (students[index]?.house) {
|
|
||||||
if (students[index]?.house_colour) {
|
|
||||||
houseelement.style.background = students[index].house_colour;
|
|
||||||
try {
|
try {
|
||||||
let colorresult = GetThresholdOfColor(students[index]?.house_colour);
|
const colorresult = GetThresholdOfColor(student.house_colour);
|
||||||
houseelement.style.color =
|
houseelement.style.color =
|
||||||
colorresult && colorresult > 300 ? "black" : "white";
|
colorresult && colorresult > 300 ? "black" : "white";
|
||||||
houseelement.innerText = students[index].year + students[index].house;
|
|
||||||
} catch (error) {
|
} catch (err) {
|
||||||
houseelement.innerText = students[index].house;
|
// Colour calculation failed, no text colour set
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else if (student.year) {
|
||||||
try {
|
// No house, only year will be shown
|
||||||
houseelement.innerText = students[index].year;
|
text = student.year;
|
||||||
} catch (err) {
|
|
||||||
houseelement.innerText = "N/A";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
houseelement.innerText = text;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createNewsButton(fragment: DocumentFragment, menu: HTMLElement) {
|
function createNewsButton(fragment: DocumentFragment, menu: HTMLElement) {
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ export function addShortcuts(shortcuts: any) {
|
|||||||
|
|
||||||
function createNewShortcut(link: any, icon: any, viewBox: any, title: any) {
|
function createNewShortcut(link: any, icon: any, viewBox: any, title: any) {
|
||||||
// Creates the stucture and element information for each seperate shortcut
|
// Creates the stucture and element information for each seperate shortcut
|
||||||
|
const container = document.getElementById("shortcuts");
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
let shortcut = document.createElement("a");
|
let shortcut = document.createElement("a");
|
||||||
shortcut.setAttribute("href", link);
|
shortcut.setAttribute("href", link);
|
||||||
shortcut.setAttribute("target", "_blank");
|
shortcut.setAttribute("target", "_blank");
|
||||||
@@ -42,5 +45,5 @@ function createNewShortcut(link: any, icon: any, viewBox: any, title: any) {
|
|||||||
shortcutdiv.append(text);
|
shortcutdiv.append(text);
|
||||||
shortcut.append(shortcutdiv);
|
shortcut.append(shortcutdiv);
|
||||||
|
|
||||||
document.getElementById("shortcuts")!.appendChild(shortcut);
|
container.appendChild(shortcut);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ import stringToHTML from "../stringToHTML";
|
|||||||
|
|
||||||
export function CreateCustomShortcutDiv(element: any) {
|
export function CreateCustomShortcutDiv(element: any) {
|
||||||
// Creates the stucture and element information for each seperate shortcut
|
// Creates the stucture and element information for each seperate shortcut
|
||||||
|
const container = document.getElementById("shortcuts");
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
var shortcut = document.createElement("a");
|
var shortcut = document.createElement("a");
|
||||||
shortcut.setAttribute("href", element.url);
|
shortcut.setAttribute("href", element.url);
|
||||||
shortcut.setAttribute("target", "_blank");
|
shortcut.setAttribute("target", "_blank");
|
||||||
@@ -45,5 +48,5 @@ export function CreateCustomShortcutDiv(element: any) {
|
|||||||
shortcutdiv.append(text);
|
shortcutdiv.append(text);
|
||||||
shortcut.append(shortcutdiv);
|
shortcut.append(shortcutdiv);
|
||||||
|
|
||||||
document.getElementById("shortcuts")!.append(shortcut);
|
container.append(shortcut);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
import links from "@/seqta/content/links.json";
|
|
||||||
|
|
||||||
export function RemoveShortcutDiv(elements: any) {
|
|
||||||
if (elements.length === 0) return;
|
|
||||||
|
|
||||||
elements.forEach((element: any) => {
|
|
||||||
const shortcuts = document.querySelectorAll(".shortcut");
|
|
||||||
shortcuts.forEach((shortcut) => {
|
|
||||||
const anchorElement = shortcut.parentElement; // the <a> element is the parent
|
|
||||||
const textElement = shortcut.querySelector("p"); // <p> is a direct child of .shortcut
|
|
||||||
const title = textElement ? textElement.textContent : "";
|
|
||||||
|
|
||||||
const elementName = links[element.name as keyof typeof links]?.DisplayName || element.name;
|
|
||||||
|
|
||||||
let shouldRemove = title === elementName;
|
|
||||||
|
|
||||||
// Check href only if element.url exists
|
|
||||||
if (element.url) {
|
|
||||||
shouldRemove =
|
|
||||||
shouldRemove && anchorElement!.getAttribute("href") === element.url;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shouldRemove) {
|
|
||||||
anchorElement!.remove();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -4,12 +4,11 @@ import LogoLight from "@/resources/icons/betterseqta-light-icon.png";
|
|||||||
import assessmentsicon from "@/seqta/icons/assessmentsIcon";
|
import assessmentsicon from "@/seqta/icons/assessmentsIcon";
|
||||||
import coursesicon from "@/seqta/icons/coursesIcon";
|
import coursesicon from "@/seqta/icons/coursesIcon";
|
||||||
import { GetThresholdOfColor } from "@/seqta/ui/colors/getThresholdColour";
|
import { GetThresholdOfColor } from "@/seqta/ui/colors/getThresholdColour";
|
||||||
import { addShortcuts } from "../Adders/AddShortcuts";
|
|
||||||
import { convertTo12HourFormat } from "../convertTo12HourFormat";
|
import { convertTo12HourFormat } from "../convertTo12HourFormat";
|
||||||
import { delay } from "../delay";
|
import { delay } from "../delay";
|
||||||
import { settingsState } from "../listeners/SettingsState";
|
import { settingsState } from "../listeners/SettingsState";
|
||||||
import stringToHTML from "../stringToHTML";
|
import stringToHTML from "../stringToHTML";
|
||||||
import { CreateCustomShortcutDiv } from "@/seqta/utils/CreateEnable/CreateCustomShortcutDiv";
|
import { renderShortcuts } from "@/seqta/utils/Render/renderShortcuts";
|
||||||
import { CreateElement } from "@/seqta/utils/CreateEnable/CreateElement";
|
import { CreateElement } from "@/seqta/utils/CreateEnable/CreateElement";
|
||||||
import { FilterUpcomingAssessments } from "@/seqta/utils/FilterUpcomingAssessments";
|
import { FilterUpcomingAssessments } from "@/seqta/utils/FilterUpcomingAssessments";
|
||||||
import { getMockNotices } from "@/seqta/ui/dev/hideSensitiveContent";
|
import { getMockNotices } from "@/seqta/ui/dev/hideSensitiveContent";
|
||||||
@@ -100,12 +99,7 @@ export async function loadHomePage() {
|
|||||||
|
|
||||||
const cleanup = setupTimetableListeners();
|
const cleanup = setupTimetableListeners();
|
||||||
|
|
||||||
try {
|
renderShortcuts();
|
||||||
addShortcuts(settingsState.shortcuts);
|
|
||||||
} catch (err: any) {
|
|
||||||
console.error("[BetterSEQTA+] Error adding shortcuts:", err.message || err);
|
|
||||||
}
|
|
||||||
AddCustomShortcutsToPage();
|
|
||||||
|
|
||||||
const date = new Date();
|
const date = new Date();
|
||||||
const TodayFormatted = formatDate(date);
|
const TodayFormatted = formatDate(date);
|
||||||
@@ -367,15 +361,6 @@ function comparedate(obj1: any, obj2: any) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function AddCustomShortcutsToPage() {
|
|
||||||
let customshortcuts: any = settingsState.customshortcuts;
|
|
||||||
if (customshortcuts.length > 0) {
|
|
||||||
for (let i = 0; i < customshortcuts.length; i++) {
|
|
||||||
const element = customshortcuts[i];
|
|
||||||
CreateCustomShortcutDiv(element);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function processNotices(response: any, labelArray: string[]) {
|
function processNotices(response: any, labelArray: string[]) {
|
||||||
const NoticeContainer = document.getElementById("notice-container");
|
const NoticeContainer = document.getElementById("notice-container");
|
||||||
@@ -1117,6 +1102,14 @@ async function CreateUpcomingSection(assessments: any, activeSubjects: any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
FilterUpcomingAssessments(settingsState.subjectfilters);
|
FilterUpcomingAssessments(settingsState.subjectfilters);
|
||||||
|
|
||||||
|
if (assessments.length === 0) {
|
||||||
|
upcomingitemcontainer!.innerHTML = `
|
||||||
|
<div class="day-empty">
|
||||||
|
<img src="${browser.runtime.getURL(LogoLight)}" />
|
||||||
|
<p>No assessments available.</p>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createAssessmentDateDiv(date: string, value: any, datecase?: any) {
|
function createAssessmentDateDiv(date: string, value: any, datecase?: any) {
|
||||||
|
|||||||
@@ -1,25 +1,17 @@
|
|||||||
import stringToHTML from "../stringToHTML";
|
import stringToHTML from "../stringToHTML";
|
||||||
import { settingsState } from "../listeners/SettingsState";
|
import { settingsState } from "../listeners/SettingsState";
|
||||||
import { animate, stagger } from "motion";
|
import { openPopup } from "./PopupManager";
|
||||||
import { DeleteWhatsNew } from "../Whatsnew";
|
|
||||||
|
|
||||||
export function OpenAboutPage() {
|
export function OpenAboutPage() {
|
||||||
const background = document.createElement("div");
|
const header = stringToHTML(
|
||||||
background.id = "whatsnewbk";
|
|
||||||
background.classList.add("whatsnewBackground");
|
|
||||||
|
|
||||||
const container = document.createElement("div");
|
|
||||||
container.classList.add("whatsnewContainer");
|
|
||||||
|
|
||||||
var header: any = stringToHTML(
|
|
||||||
/* html */
|
/* html */
|
||||||
`<div class="whatsnewHeader">
|
`<div class="whatsnewHeader">
|
||||||
<h1>About</h1>
|
<h1>About</h1>
|
||||||
<p>About the extension</p>
|
<p>About the extension</p>
|
||||||
</div>`,
|
</div>`,
|
||||||
).firstChild;
|
).firstChild as HTMLElement;
|
||||||
|
|
||||||
let text = stringToHTML(/* html */ `
|
const text = stringToHTML(/* html */ `
|
||||||
<div class="whatsnewTextContainer" style="overflow-y: hidden;">
|
<div class="whatsnewTextContainer" style="overflow-y: hidden;">
|
||||||
<img src="${settingsState.DarkMode ? "https://raw.githubusercontent.com/BetterSEQTA/BetterSEQTA-Plus/main/src/resources/branding/dark.jpg" : "https://raw.githubusercontent.com/BetterSEQTA/BetterSEQTA-Plus/main/src/resources/branding/light.jpg"}" class="aboutImg" />
|
<img src="${settingsState.DarkMode ? "https://raw.githubusercontent.com/BetterSEQTA/BetterSEQTA-Plus/main/src/resources/branding/dark.jpg" : "https://raw.githubusercontent.com/BetterSEQTA/BetterSEQTA-Plus/main/src/resources/branding/light.jpg"}" class="aboutImg" />
|
||||||
<p>BetterSEQTA+ is a fork of BetterSEQTA (originally developed by Nulkem), which was discontinued. BetterSEQTA+ continued development of BetterSEQTA, while incorporating a plethora of features. </p>
|
<p>BetterSEQTA+ is a fork of BetterSEQTA (originally developed by Nulkem), which was discontinued. BetterSEQTA+ continued development of BetterSEQTA, while incorporating a plethora of features. </p>
|
||||||
@@ -37,9 +29,9 @@ export function OpenAboutPage() {
|
|||||||
style="width: 100%; max-width: 500px; height: auto; object-fit: contain; display: block; margin: -110px auto 0;">
|
style="width: 100%; max-width: 500px; height: auto; object-fit: contain; display: block; margin: -110px auto 0;">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`).firstChild;
|
`).firstChild as HTMLElement;
|
||||||
|
|
||||||
let footer = stringToHTML(/* html */ `
|
const footer = stringToHTML(/* html */ `
|
||||||
<div class="whatsnewFooter">
|
<div class="whatsnewFooter">
|
||||||
<div>
|
<div>
|
||||||
Resources and Feedback:
|
Resources and Feedback:
|
||||||
@@ -67,56 +59,10 @@ export function OpenAboutPage() {
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`).firstChild;
|
`).firstChild as HTMLElement;
|
||||||
|
|
||||||
let exitbutton = document.createElement("div");
|
openPopup({
|
||||||
exitbutton.id = "whatsnewclosebutton";
|
header,
|
||||||
|
content: [text, footer],
|
||||||
container.append(header);
|
|
||||||
container.append(text as ChildNode);
|
|
||||||
container.append(footer as ChildNode);
|
|
||||||
container.append(exitbutton);
|
|
||||||
|
|
||||||
background.append(container);
|
|
||||||
|
|
||||||
document.getElementById("container")!.append(background);
|
|
||||||
|
|
||||||
let bkelement = document.getElementById("whatsnewbk");
|
|
||||||
let popup = document.getElementsByClassName("whatsnewContainer")[0];
|
|
||||||
|
|
||||||
if (settingsState.animations) {
|
|
||||||
animate(
|
|
||||||
[popup, bkelement as HTMLElement],
|
|
||||||
{ scale: [0, 1] },
|
|
||||||
{
|
|
||||||
type: "spring",
|
|
||||||
stiffness: 220,
|
|
||||||
damping: 18,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
animate(
|
|
||||||
".whatsnewTextContainer *",
|
|
||||||
{ opacity: [0, 1], y: [10, 0] },
|
|
||||||
{
|
|
||||||
delay: stagger(0.05, { startDelay: 0.1 }),
|
|
||||||
duration: 0.5,
|
|
||||||
ease: [0.22, 0.03, 0.26, 1],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
delete settingsState.justupdated;
|
|
||||||
|
|
||||||
bkelement!.addEventListener("click", function (event) {
|
|
||||||
// Check if the click event originated from the element itself and not any of its children
|
|
||||||
if (event.target === bkelement) {
|
|
||||||
DeleteWhatsNew();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var closeelement = document.getElementById("whatsnewclosebutton");
|
|
||||||
closeelement!.addEventListener("click", function () {
|
|
||||||
DeleteWhatsNew();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-98
@@ -1,24 +1,5 @@
|
|||||||
import { settingsState } from "./listeners/SettingsState";
|
import stringToHTML from "../stringToHTML";
|
||||||
import { animate, stagger } from "motion";
|
import { openPopup } from "./PopupManager";
|
||||||
import stringToHTML from "./stringToHTML";
|
|
||||||
|
|
||||||
export async function DeleteWhatsNew() {
|
|
||||||
const bkelement = document.getElementById("whatsnewbk");
|
|
||||||
const popup = document.querySelector(".whatsnewContainer") as HTMLElement;
|
|
||||||
|
|
||||||
if (!settingsState.animations) {
|
|
||||||
bkelement?.remove();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
animate(
|
|
||||||
[popup, bkelement!],
|
|
||||||
{ opacity: [1, 0], scale: [1, 0] },
|
|
||||||
{ ease: [0.22, 0.03, 0.26, 1] },
|
|
||||||
).then(() => {
|
|
||||||
bkelement?.remove();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function OpenMinecraftServerPopup() {
|
export function OpenMinecraftServerPopup() {
|
||||||
if (!document.querySelector('link[href*="minecraftia"]')) {
|
if (!document.querySelector('link[href*="minecraftia"]')) {
|
||||||
@@ -28,45 +9,36 @@ export function OpenMinecraftServerPopup() {
|
|||||||
document.head.appendChild(fontLink);
|
document.head.appendChild(fontLink);
|
||||||
}
|
}
|
||||||
|
|
||||||
const background = document.createElement("div");
|
const header = stringToHTML(
|
||||||
background.id = "whatsnewbk";
|
|
||||||
background.classList.add("whatsnewBackground");
|
|
||||||
|
|
||||||
const container = document.createElement("div");
|
|
||||||
container.classList.add("whatsnewContainer");
|
|
||||||
|
|
||||||
var header: any = stringToHTML(
|
|
||||||
/* html */
|
/* html */
|
||||||
`<div class="whatsnewHeader">
|
`<div class="whatsnewHeader">
|
||||||
<h1>Minecraft Server</h1>
|
<h1>Minecraft Server</h1>
|
||||||
<p>The official BetterSEQTA+ Minecraft Server</p>
|
<p>The official BetterSEQTA+ Minecraft Server</p>
|
||||||
</div>`,
|
</div>`,
|
||||||
).firstChild;
|
).firstChild as HTMLElement;
|
||||||
|
|
||||||
let imagecont = document.createElement("div");
|
const imageContainer = document.createElement("div");
|
||||||
imagecont.classList.add("whatsnewImgContainer");
|
imageContainer.classList.add("whatsnewImgContainer");
|
||||||
|
|
||||||
let video = document.createElement("video");
|
const video = document.createElement("video");
|
||||||
video.style.aspectRatio = "16/9";
|
video.style.aspectRatio = "16/9";
|
||||||
video.style.background = "black";
|
video.style.background = "black";
|
||||||
let source = document.createElement("source");
|
|
||||||
|
|
||||||
|
const source = document.createElement("source");
|
||||||
source.setAttribute(
|
source.setAttribute(
|
||||||
"src",
|
"src",
|
||||||
"https://raw.githubusercontent.com/BetterSEQTA/BetterSEQTA-Plus/main/src/resources/server-video.mp4",
|
"https://raw.githubusercontent.com/BetterSEQTA/BetterSEQTA-Plus/main/src/resources/server-video.mp4",
|
||||||
);
|
);
|
||||||
|
|
||||||
video.autoplay = true;
|
video.autoplay = true;
|
||||||
video.muted = true;
|
video.muted = true;
|
||||||
video.loop = true;
|
video.loop = true;
|
||||||
video.appendChild(source);
|
video.appendChild(source);
|
||||||
video.classList.add("whatsnewImg");
|
video.classList.add("whatsnewImg");
|
||||||
imagecont.appendChild(video);
|
imageContainer.appendChild(video);
|
||||||
|
|
||||||
let textcontainer = document.createElement("div");
|
const text = stringToHTML(/* html */ `
|
||||||
textcontainer.classList.add("whatsnewTextContainer");
|
<div class="whatsnewTextContainer" style="height: 50%; overflow-y: hidden;">
|
||||||
|
|
||||||
let text = stringToHTML(/* html */ `
|
|
||||||
<div class="whatsnewTextContainer" style="height: 50%; overflow-y: scroll;">
|
|
||||||
<h1>Join our community in Minecraft!</h1>
|
<h1>Join our community in Minecraft!</h1>
|
||||||
<p style="margin-left: 0;">Join the official BetterSEQTA+ Minecraft Server community now!</p>
|
<p style="margin-left: 0;">Join the official BetterSEQTA+ Minecraft Server community now!</p>
|
||||||
|
|
||||||
@@ -92,8 +64,7 @@ export function OpenMinecraftServerPopup() {
|
|||||||
-1px -1px 0 #000,
|
-1px -1px 0 #000,
|
||||||
1px -1px 0 #000,
|
1px -1px 0 #000,
|
||||||
-1px 1px 0 #000,
|
-1px 1px 0 #000,
|
||||||
1px 1px 0 #000;
|
1px 1px 0 #000;">
|
||||||
">
|
|
||||||
mc.betterseqta.org
|
mc.betterseqta.org
|
||||||
</p>
|
</p>
|
||||||
<p style="
|
<p style="
|
||||||
@@ -107,14 +78,13 @@ export function OpenMinecraftServerPopup() {
|
|||||||
-1px -1px 0 #000,
|
-1px -1px 0 #000,
|
||||||
1px -1px 0 #000,
|
1px -1px 0 #000,
|
||||||
-1px 1px 0 #000,
|
-1px 1px 0 #000,
|
||||||
1px 1px 0 #000;
|
1px 1px 0 #000;">
|
||||||
">
|
|
||||||
Version: 1.21.4
|
Version: 1.21.4
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
`).firstChild;
|
`).firstChild as HTMLElement;
|
||||||
|
|
||||||
let footer = stringToHTML(/* html */ `
|
const footer = stringToHTML(/* html */ `
|
||||||
<div class="whatsnewFooter">
|
<div class="whatsnewFooter">
|
||||||
<div>
|
<div>
|
||||||
Resources and Feedback:
|
Resources and Feedback:
|
||||||
@@ -144,59 +114,10 @@ export function OpenMinecraftServerPopup() {
|
|||||||
<div>
|
<div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`).firstChild;
|
`).firstChild as HTMLElement;
|
||||||
|
|
||||||
let exitbutton = document.createElement("div");
|
openPopup({
|
||||||
exitbutton.id = "whatsnewclosebutton";
|
|
||||||
|
|
||||||
container.append(
|
|
||||||
header,
|
header,
|
||||||
imagecont,
|
content: [imageContainer, text, footer],
|
||||||
text as HTMLElement,
|
|
||||||
footer as HTMLElement,
|
|
||||||
exitbutton,
|
|
||||||
);
|
|
||||||
|
|
||||||
background.append(container);
|
|
||||||
|
|
||||||
document.getElementById("container")!.append(background);
|
|
||||||
|
|
||||||
let bkelement = document.getElementById("whatsnewbk");
|
|
||||||
let popup = document.getElementsByClassName("whatsnewContainer")[0];
|
|
||||||
|
|
||||||
if (settingsState.animations) {
|
|
||||||
animate(
|
|
||||||
[popup, bkelement as HTMLElement],
|
|
||||||
{ scale: [0, 1] },
|
|
||||||
{
|
|
||||||
type: "spring",
|
|
||||||
stiffness: 220,
|
|
||||||
damping: 18,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
animate(
|
|
||||||
".whatsnewTextContainer *",
|
|
||||||
{ opacity: [0, 1], y: [10, 0] },
|
|
||||||
{
|
|
||||||
delay: stagger(0.05, { startDelay: 0.1 }),
|
|
||||||
duration: 0.5,
|
|
||||||
ease: [0.22, 0.03, 0.26, 1],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
delete settingsState.justupdated;
|
|
||||||
|
|
||||||
bkelement!.addEventListener("click", function (event) {
|
|
||||||
// Check if the click event originated from the element itself and not any of its children
|
|
||||||
if (event.target === bkelement) {
|
|
||||||
DeleteWhatsNew();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var closeelement = document.getElementById("whatsnewclosebutton");
|
|
||||||
closeelement!.addEventListener("click", function () {
|
|
||||||
DeleteWhatsNew();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import stringToHTML from "../stringToHTML";
|
||||||
|
import { settingsState } from "../listeners/SettingsState";
|
||||||
|
import { openPopup } from "./PopupManager";
|
||||||
|
|
||||||
|
export function showPrivacyNotification() {
|
||||||
|
const lastUpdated = "2025-12-19";
|
||||||
|
|
||||||
|
if (document.getElementById("whatsnewbk")) return;
|
||||||
|
if (settingsState.privacyStatementShown) return;
|
||||||
|
if (settingsState.privacyStatementLastUpdated && new Date(settingsState.privacyStatementLastUpdated) > new Date(lastUpdated)) return;
|
||||||
|
|
||||||
|
const header = stringToHTML(
|
||||||
|
/* html */
|
||||||
|
`<div class="whatsnewHeader">
|
||||||
|
<h1>Privacy Statement</h1>
|
||||||
|
<p>Important Information</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;">
|
||||||
|
<img style="aspect-ratio: 16/5.8;" src="${settingsState.DarkMode ? "https://raw.githubusercontent.com/BetterSEQTA/BetterSEQTA-Plus/main/src/resources/branding/dark.jpg" : "https://raw.githubusercontent.com/BetterSEQTA/BetterSEQTA-Plus/main/src/resources/branding/light.jpg"}" class="aboutImg" />
|
||||||
|
<p>
|
||||||
|
<strong>Addressing Recent Concerns About BetterSEQTA+</strong><br>
|
||||||
|
We appreciate the feedback we've received from several schools regarding BetterSEQTA+. Transparency and trust are core to our mission, and we want to address these concerns directly.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Our Commitment to Privacy:</strong><br>
|
||||||
|
<span style="display: block; margin-left: 1em;">
|
||||||
|
• We do not collect, store, or share any personal information<br>
|
||||||
|
• All data processing happens locally on your device<br>
|
||||||
|
• Our code is open source and available for review
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>What We're Doing:</strong><br>
|
||||||
|
We're willing to actively work with school administrators to ensure BetterSEQTA+ meets both student needs and institutional requirements. If your school has specific concerns, we encourage them to contact us at <a href="mailto:betterseqta.plus@gmail.com" style="color: inherit; text-decoration: underline;">betterseqta.plus@gmail.com</a> or via github at <a href="https://github.com/BetterSEQTA/BetterSEQTA-Plus" target="_blank" rel="noopener noreferrer" style="color: inherit; text-decoration: underline;">github.com/BetterSEQTA/BetterSEQTA-Plus</a>.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
For complete details about our privacy practices, visit our <a href="https://betterseqta.org/privacy" target="_blank" rel="noopener noreferrer" style="color: inherit; text-decoration: underline;">privacy policy</a> or click the shield icon in settings.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
`).firstChild as HTMLElement;
|
||||||
|
|
||||||
|
settingsState.privacyStatementLastUpdated = "2025-12-20";
|
||||||
|
settingsState.privacyStatementShown = true;
|
||||||
|
|
||||||
|
openPopup({
|
||||||
|
header,
|
||||||
|
content: [text],
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import stringToHTML from "../stringToHTML";
|
||||||
|
import { openPopup } from "./PopupManager";
|
||||||
|
|
||||||
|
export function OpenPrivacyStatement() {
|
||||||
|
const header = stringToHTML(
|
||||||
|
/* html */
|
||||||
|
`<div class="whatsnewHeader">
|
||||||
|
<h1>Privacy Statement</h1>
|
||||||
|
<p>Our commitment to your privacy</p>
|
||||||
|
</div>`,
|
||||||
|
).firstChild as HTMLElement;
|
||||||
|
|
||||||
|
const text = stringToHTML(/* html */ `
|
||||||
|
<div class="whatsnewTextContainer" style="overflow-y: auto; max-height: 60vh;">
|
||||||
|
<h2 style="margin-top: 0;">Privacy Policy</h2>
|
||||||
|
<p>At BetterSEQTA+, we take your privacy seriously. We want to be completely transparent about how we handle your data.</p>
|
||||||
|
|
||||||
|
<h3>Data Collection</h3>
|
||||||
|
<p><strong>We never collect any information from you.</strong> BetterSEQTA+ is designed to work entirely on your device. All processing happens locally in your browser, and we do not send any data to external servers.</p>
|
||||||
|
|
||||||
|
<h3>What We Don't Do</h3>
|
||||||
|
<ul style="text-align: left; margin: 10px 0;">
|
||||||
|
<li>We do not track your browsing activity</li>
|
||||||
|
<li>We do not collect personal information</li>
|
||||||
|
<li>We do not store your SEQTA credentials</li>
|
||||||
|
<li>We do not send data to third-party services</li>
|
||||||
|
<li>We do not use analytics or tracking cookies</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Local Storage</h3>
|
||||||
|
<p>BetterSEQTA+ uses your browser's local storage to save your preferences and settings. This data remains on your device and is never transmitted anywhere. You can clear this data at any time through your browser's settings.</p>
|
||||||
|
|
||||||
|
<h3>Open Source</h3>
|
||||||
|
<p>BetterSEQTA+ is an open-source project. You can review our code on <a href="https://github.com/BetterSEQTA/BetterSEQTA-Plus" target="_blank" style="color: inherit; text-decoration: underline;">GitHub</a> to verify our privacy practices. We believe in transparency and encourage you to inspect the code yourself.</p>
|
||||||
|
|
||||||
|
<h3>Our Commitment</h3>
|
||||||
|
<p>We are committed to providing the best features possible while respecting your privacy. We understand that schools and students have concerns about data privacy, and we want to assure you that BetterSEQTA+ is designed with privacy as a core principle.</p>
|
||||||
|
|
||||||
|
<p style="margin-top: 20px; font-weight: bold;">If you have any questions or concerns about our privacy practices, please reach out to us through our <a href="https://github.com/BetterSEQTA/BetterSEQTA-Plus" target="_blank" style="color: inherit; text-decoration: underline;">GitHub repository</a>.</p>
|
||||||
|
</div>
|
||||||
|
`).firstChild as HTMLElement;
|
||||||
|
|
||||||
|
openPopup({
|
||||||
|
header,
|
||||||
|
content: [text],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,48 +1,22 @@
|
|||||||
import { settingsState } from "./listeners/SettingsState";
|
import stringToHTML from "../stringToHTML";
|
||||||
import { animate, stagger } from "motion";
|
|
||||||
import stringToHTML from "./stringToHTML";
|
|
||||||
import browser from "webextension-polyfill";
|
import browser from "webextension-polyfill";
|
||||||
import kofi from "@/resources/kofi.png?base64";
|
import kofi from "@/resources/kofi.png?base64";
|
||||||
|
import { openPopup } from "./PopupManager";
|
||||||
export async function DeleteWhatsNew() {
|
|
||||||
const bkelement = document.getElementById("whatsnewbk");
|
|
||||||
const popup = document.getElementsByClassName("whatsnewContainer")[0];
|
|
||||||
|
|
||||||
if (!settingsState.animations) {
|
|
||||||
bkelement?.remove();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
animate(
|
|
||||||
[popup, bkelement!],
|
|
||||||
{ opacity: [1, 0], scale: [1, 0] },
|
|
||||||
{ ease: [0.22, 0.03, 0.26, 1] },
|
|
||||||
).then(() => {
|
|
||||||
bkelement?.remove();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function OpenWhatsNewPopup() {
|
export function OpenWhatsNewPopup() {
|
||||||
const background = document.createElement("div");
|
const header = stringToHTML(
|
||||||
background.id = "whatsnewbk";
|
|
||||||
background.classList.add("whatsnewBackground");
|
|
||||||
|
|
||||||
const container = document.createElement("div");
|
|
||||||
container.classList.add("whatsnewContainer");
|
|
||||||
|
|
||||||
var header: any = stringToHTML(
|
|
||||||
/* html */
|
/* html */
|
||||||
`<div class="whatsnewHeader">
|
`<div class="whatsnewHeader">
|
||||||
<h1>What's New</h1>
|
<h1>What's New</h1>
|
||||||
<p>BetterSEQTA+ V${browser.runtime.getManifest().version}</p>
|
<p>BetterSEQTA+ V${browser.runtime.getManifest().version}</p>
|
||||||
</div>`,
|
</div>`,
|
||||||
).firstChild;
|
).firstChild as HTMLElement;
|
||||||
|
|
||||||
let imagecont = document.createElement("div");
|
const imageContainer = document.createElement("div");
|
||||||
imagecont.classList.add("whatsnewImgContainer");
|
imageContainer.classList.add("whatsnewImgContainer");
|
||||||
|
|
||||||
let video = document.createElement("video");
|
const video = document.createElement("video");
|
||||||
let source = document.createElement("source");
|
const source = document.createElement("source");
|
||||||
|
|
||||||
source.setAttribute(
|
source.setAttribute(
|
||||||
"src",
|
"src",
|
||||||
@@ -53,19 +27,32 @@ export function OpenWhatsNewPopup() {
|
|||||||
video.loop = true;
|
video.loop = true;
|
||||||
video.appendChild(source);
|
video.appendChild(source);
|
||||||
video.classList.add("whatsnewImg");
|
video.classList.add("whatsnewImg");
|
||||||
imagecont.appendChild(video);
|
imageContainer.appendChild(video);
|
||||||
|
|
||||||
/* let whatsnewimg = document.createElement("img");
|
const text = stringToHTML(/* html */ `
|
||||||
//whatsnewimg.src = "https://raw.githubusercontent.com/BetterSEQTA/BetterSEQTA-Plus/main/src/resources/update-image.webp";
|
<div class="whatsnewTextContainer" style="height: 50%;overflow-y: auto;">
|
||||||
whatsnewimg.src = browser.runtime.getURL('../../resources/update-image.webp');
|
|
||||||
whatsnewimg.classList.add("whatsnewImg");
|
|
||||||
imagecont.appendChild(whatsnewimg); */
|
|
||||||
|
|
||||||
let textcontainer = document.createElement("div");
|
<h1>3.4.13 - Bug Fixes & Styling Improvements</h1>
|
||||||
textcontainer.classList.add("whatsnewTextContainer");
|
<li>Fixed house/year box hard failing when house_colour does not exist</li>
|
||||||
|
<li>Fixed message of the day being unreadable in light mode</li>
|
||||||
|
<li>Fixed global font styling issues due to SEQTA updates</li>
|
||||||
|
<li>Fixed styling issues with title bar and other elements</li>
|
||||||
|
<li>Other minor bug fixes and improvements</li>
|
||||||
|
|
||||||
let text = stringToHTML(/* html */ `
|
<h1>3.4.12 - Privacy Updates & Bug Fixes</h1>
|
||||||
<div class="whatsnewTextContainer" style="height: 50%;overflow-y: scroll;">
|
<li>Added privacy statement</li>
|
||||||
|
<li>Added disclaimer modal to assessment averages switch</li>
|
||||||
|
<li>Improved popup management system</li>
|
||||||
|
<li>Other minor bug fixes and improvements</li>
|
||||||
|
|
||||||
|
<h1>3.4.11 - New Features & Bug Fixes</h1>
|
||||||
|
<li>Added Background Music plugin</li>
|
||||||
|
<li>Added empty state for assessments on homepage</li>
|
||||||
|
<li>Added Colour Picker hex/rgba controls</li>
|
||||||
|
<li>Fixed custom shortcuts positioning (moved above regular shortcuts)</li>
|
||||||
|
<li>Fixed Go to popup not scrolling properly</li>
|
||||||
|
<li>Made theme edit mode more plain</li>
|
||||||
|
<li>Other minor bug fixes and improvements</li>
|
||||||
|
|
||||||
<h1>3.4.10 - Minor bug fixes</h1>
|
<h1>3.4.10 - Minor bug fixes</h1>
|
||||||
<li>Fixed UI file styling incorrectly applying to documents</li>
|
<li>Fixed UI file styling incorrectly applying to documents</li>
|
||||||
@@ -124,7 +111,7 @@ export function OpenWhatsNewPopup() {
|
|||||||
<li>Fixed discord icon colour in light mode</li>
|
<li>Fixed discord icon colour in light mode</li>
|
||||||
<li>Fixed subject averages not showing up with letter grades</li>
|
<li>Fixed subject averages not showing up with letter grades</li>
|
||||||
<li>Tweaked compose UI</li>
|
<li>Tweaked compose UI</li>
|
||||||
|
|
||||||
<h1>3.4.4 - Bug Fixes and Improvements</h1>
|
<h1>3.4.4 - Bug Fixes and Improvements</h1>
|
||||||
<li>Added vertical zoom to the timetable</li>
|
<li>Added vertical zoom to the timetable</li>
|
||||||
<li>Fixed theme importing failing when images were included</li>
|
<li>Fixed theme importing failing when images were included</li>
|
||||||
@@ -138,15 +125,15 @@ export function OpenWhatsNewPopup() {
|
|||||||
<li>Fixed theme application in the creator</li>
|
<li>Fixed theme application in the creator</li>
|
||||||
<li>Performance improvements</li>
|
<li>Performance improvements</li>
|
||||||
<li>Other minor bug fixes</li>
|
<li>Other minor bug fixes</li>
|
||||||
|
|
||||||
<h1>3.4.3 - Minor Bug Fixes</h1>
|
<h1>3.4.3 - Minor Bug Fixes</h1>
|
||||||
<li>Fixed a bug where timetable colours couldn't be changed</li>
|
<li>Fixed a bug where timetable colours couldn't be changed</li>
|
||||||
<li>Other minor bug fixes</li>
|
<li>Other minor bug fixes</li>
|
||||||
|
|
||||||
<h1>3.4.2 - Minor Bug Fixes</h1>
|
<h1>3.4.2 - Minor Bug Fixes</h1>
|
||||||
<li>Fixed a bug where Assessment Average wasn't enabled by default</li>
|
<li>Fixed a bug where Assessment Average wasn't enabled by default</li>
|
||||||
<li>Fixed floating menus would sometimes be placed behind other elements</li>
|
<li>Fixed floating menus would sometimes be placed behind other elements</li>
|
||||||
|
|
||||||
<h1>3.4.1 - Bug Fixes and Performance Improvements</h1>
|
<h1>3.4.1 - Bug Fixes and Performance Improvements</h1>
|
||||||
<li>Added a new "Subject Average" section to the assessments page</li>
|
<li>Added a new "Subject Average" section to the assessments page</li>
|
||||||
<li>Fixed a bug where animations wouldn't play correctly</li>
|
<li>Fixed a bug where animations wouldn't play correctly</li>
|
||||||
@@ -155,7 +142,7 @@ export function OpenWhatsNewPopup() {
|
|||||||
<li>Improved animation performance</li>
|
<li>Improved animation performance</li>
|
||||||
<li>Better Animations!</li>
|
<li>Better Animations!</li>
|
||||||
<li>Minor style tweaks</li>
|
<li>Minor style tweaks</li>
|
||||||
|
|
||||||
<h1>3.4.0 - Major Performance Update</h1>
|
<h1>3.4.0 - Major Performance Update</h1>
|
||||||
<li>Completely rebuilt the extension popup using Svelte for dramatically improved performance</li>
|
<li>Completely rebuilt the extension popup using Svelte for dramatically improved performance</li>
|
||||||
<li>Added a brand new background store with search functionality and downloadable backgrounds</li>
|
<li>Added a brand new background store with search functionality and downloadable backgrounds</li>
|
||||||
@@ -164,10 +151,10 @@ export function OpenWhatsNewPopup() {
|
|||||||
<li>Smoother animations and improved scrolling</li>
|
<li>Smoother animations and improved scrolling</li>
|
||||||
<li>Fixed Firefox compatibility issues</li>
|
<li>Fixed Firefox compatibility issues</li>
|
||||||
<li>Other minor bug fixes and under the hood improvements</li>
|
<li>Other minor bug fixes and under the hood improvements</li>
|
||||||
|
|
||||||
<h1>3.3.1 - Hot Fix</h1>
|
<h1>3.3.1 - Hot Fix</h1>
|
||||||
<li>Fixed assessments not loading when no notices are available</li>
|
<li>Fixed assessments not loading when no notices are available</li>
|
||||||
|
|
||||||
<h1>3.3.0 - Overhauled Theming System</h1>
|
<h1>3.3.0 - Overhauled Theming System</h1>
|
||||||
<li>Added a theme store!</li>
|
<li>Added a theme store!</li>
|
||||||
<li>Added the new theme creator!</li>
|
<li>Added the new theme creator!</li>
|
||||||
@@ -181,12 +168,12 @@ export function OpenWhatsNewPopup() {
|
|||||||
<li>Made animations toggle apply to settings</li>
|
<li>Made animations toggle apply to settings</li>
|
||||||
<li>Small styling improvements</li>
|
<li>Small styling improvements</li>
|
||||||
<li>Other minor bug fixes</li>
|
<li>Other minor bug fixes</li>
|
||||||
|
|
||||||
|
|
||||||
<h1>3.2.7 - Minor Improvements</h1>
|
<h1>3.2.7 - Minor Improvements</h1>
|
||||||
<li>Improved performance!</li>
|
<li>Improved performance!</li>
|
||||||
<li>Fixed a bug where the icon wasn't showing up</li>
|
<li>Fixed a bug where the icon wasn't showing up</li>
|
||||||
|
|
||||||
<h1>3.2.6 - Bug fixes and performance improvements</h1>
|
<h1>3.2.6 - Bug fixes and performance improvements</h1>
|
||||||
<li>Improved contrast for notifications</li>
|
<li>Improved contrast for notifications</li>
|
||||||
<li>Added 12-hour time format toggle</li>
|
<li>Added 12-hour time format toggle</li>
|
||||||
@@ -200,7 +187,7 @@ export function OpenWhatsNewPopup() {
|
|||||||
<li>Enabled spellcheck inside of direct messages</li>
|
<li>Enabled spellcheck inside of direct messages</li>
|
||||||
<li>Fixed timetable dates being misaligned</li>
|
<li>Fixed timetable dates being misaligned</li>
|
||||||
<li>Other minor bug fixes and under the hood improvements</li>
|
<li>Other minor bug fixes and under the hood improvements</li>
|
||||||
|
|
||||||
<h1>3.2.5 - More Bug Fixes</h1>
|
<h1>3.2.5 - More Bug Fixes</h1>
|
||||||
<li>New direct message scroll animations</li>
|
<li>New direct message scroll animations</li>
|
||||||
<li>Added error message for brave browser shields breaking backgrounds</li>
|
<li>Added error message for brave browser shields breaking backgrounds</li>
|
||||||
@@ -209,7 +196,7 @@ export function OpenWhatsNewPopup() {
|
|||||||
<li>Made settings panel auto size to height of screen</li>
|
<li>Made settings panel auto size to height of screen</li>
|
||||||
<li>Fixed timetable dates not visible</li>
|
<li>Fixed timetable dates not visible</li>
|
||||||
<li>Other minor bug fixes</li>
|
<li>Other minor bug fixes</li>
|
||||||
|
|
||||||
<h1>3.2.4 - Bug Fixes</h1>
|
<h1>3.2.4 - Bug Fixes</h1>
|
||||||
<li>Added an open changelog button to settings</li>
|
<li>Added an open changelog button to settings</li>
|
||||||
<li>Fixed a memory overflow bug with Education Perfect</li>
|
<li>Fixed a memory overflow bug with Education Perfect</li>
|
||||||
@@ -217,74 +204,74 @@ export function OpenWhatsNewPopup() {
|
|||||||
<li>Fixed news feed not loading</li>
|
<li>Fixed news feed not loading</li>
|
||||||
<li>Fixed home items duplicating</li>
|
<li>Fixed home items duplicating</li>
|
||||||
<li>Fixed Upcoming assessments not showing</li>
|
<li>Fixed Upcoming assessments not showing</li>
|
||||||
|
|
||||||
<h1>3.2.2 - Minor Improvements</h1>
|
<h1>3.2.2 - Minor Improvements</h1>
|
||||||
<li>Added Settings open-close animation</li>
|
<li>Added Settings open-close animation</li>
|
||||||
<li>Minor Bug Fixes</li>
|
<li>Minor Bug Fixes</li>
|
||||||
|
|
||||||
<h1>3.2.0 - Custom Themes</h1>
|
<h1>3.2.0 - Custom Themes</h1>
|
||||||
<li>Added transparency (blur) effects</li>
|
<li>Added transparency (blur) effects</li>
|
||||||
<li>Added custom themes</li>
|
<li>Added custom themes</li>
|
||||||
<li>Added colour picker history</li>
|
<li>Added colour picker history</li>
|
||||||
<li>Heaps of bug fixes</li>
|
<li>Heaps of bug fixes</li>
|
||||||
|
|
||||||
<h1>3.1.3 - Custom Backgrounds</h1>
|
<h1>3.1.3 - Custom Backgrounds</h1>
|
||||||
<li>Added custom backgrounds with support for images and videos</li>
|
<li>Added custom backgrounds with support for images and videos</li>
|
||||||
<li>Overhauled topbar</li>
|
<li>Overhauled topbar</li>
|
||||||
<li>New animated hamburger icon</li>
|
<li>New animated hamburger icon</li>
|
||||||
<li>Minor bug fixes</li>
|
<li>Minor bug fixes</li>
|
||||||
|
|
||||||
<h1>3.1.2 - New settings menu!</h1>
|
<h1>3.1.2 - New settings menu!</h1>
|
||||||
<li>Overhauled the settings menu</li>
|
<li>Overhauled the settings menu</li>
|
||||||
<li>Added custom gradients</li>
|
<li>Added custom gradients</li>
|
||||||
<li>Added HEAPS of animations</li>
|
<li>Added HEAPS of animations</li>
|
||||||
<li>Fixed a bug where shortcuts don't show up</li>
|
<li>Fixed a bug where shortcuts don't show up</li>
|
||||||
<li>Other minor bugs fixed</li>
|
<li>Other minor bugs fixed</li>
|
||||||
|
|
||||||
<h1>3.1.1 - Minor Bug fixes</h1>
|
<h1>3.1.1 - Minor Bug fixes</h1>
|
||||||
<li>Fixed assessments overlapping</li>
|
<li>Fixed assessments overlapping</li>
|
||||||
<li>Fixed houses not displaying if they aren't a specific color</li>
|
<li>Fixed houses not displaying if they aren't a specific color</li>
|
||||||
<li>Fixed Chrome Webstore Link</li>
|
<li>Fixed Chrome Webstore Link</li>
|
||||||
|
|
||||||
<h1>3.1.0 - Design Improvements</h1>
|
<h1>3.1.0 - Design Improvements</h1>
|
||||||
<li>Minor UI improvements</li>
|
<li>Minor UI improvements</li>
|
||||||
<li>Added Animation Speed Slider</li>
|
<li>Added Animation Speed Slider</li>
|
||||||
<li>Animation now enables and disables without reloading SEQTA</li>
|
<li>Animation now enables and disables without reloading SEQTA</li>
|
||||||
<li>Changed logo</li>
|
<li>Changed logo</li>
|
||||||
|
|
||||||
<h1>3.0.0 - BetterSEQTA+ *Complete Overhaul*</h1>
|
<h1>3.0.0 - BetterSEQTA+ *Complete Overhaul*</h1>
|
||||||
<li>Redesigned appearance</li>
|
<li>Redesigned appearance</li>
|
||||||
<li>Upgraded to manifest V3 (longer support)</li>
|
<li>Upgraded to manifest V3 (longer support)</li>
|
||||||
<li>Fixed transitional glitches</li>
|
<li>Fixed transitional glitches</li>
|
||||||
<li>Under the hood improvements</li>
|
<li>Under the hood improvements</li>
|
||||||
<li>Fixed News Feed</li>
|
<li>Fixed News Feed</li>
|
||||||
|
|
||||||
<h1>2.0.7 - Added support to other domains + Minor bug fixes</h1>
|
<h1>2.0.7 - Added support to other domains + Minor bug fixes</h1>
|
||||||
<li>Fixed BetterSEQTA+ not loading on some pages</li>
|
<li>Fixed BetterSEQTA+ not loading on some pages</li>
|
||||||
<li>Fixed text colour of notices being unreadable</li>
|
<li>Fixed text colour of notices being unreadable</li>
|
||||||
<li>Fixed pages not reloading when saving changes</li>
|
<li>Fixed pages not reloading when saving changes</li>
|
||||||
|
|
||||||
<h1>2.0.2 - Minor bug fixes</h1>
|
<h1>2.0.2 - Minor bug fixes</h1>
|
||||||
<li>Fixed indicator for current lesson</li>
|
<li>Fixed indicator for current lesson</li>
|
||||||
<li>Fixed text colour for DM messages list in Light mode</li>
|
<li>Fixed text colour for DM messages list in Light mode</li>
|
||||||
<li>Fixed user info text colour</li>
|
<li>Fixed user info text colour</li>
|
||||||
|
|
||||||
<h1>Sleek New Layout</h1>
|
<h1>Sleek New Layout</h1>
|
||||||
<li>Updated with a new font and presentation, BetterSEQTA+ has never looked better.</li>
|
<li>Updated with a new font and presentation, BetterSEQTA+ has never looked better.</li>
|
||||||
|
|
||||||
<h1>New Updated Sidebar</h1>
|
<h1>New Updated Sidebar</h1>
|
||||||
<li>Condensed appearance with new updated icons.</li>
|
<li>Condensed appearance with new updated icons.</li>
|
||||||
|
|
||||||
<h1>Independent Light Mode and Dark Mode</h1>
|
<h1>Independent Light Mode and Dark Mode</h1>
|
||||||
<li>Dark mode and Light mode are now available to pick alongside your chosen Theme Colour. Your Theme Colour will now become an accent colour for the page.
|
<li>Dark mode and Light mode are now available to pick alongside your chosen Theme Colour. Your Theme Colour will now become an accent colour for the page.
|
||||||
Light/Dark mode can be toggled with the new button, found in the top-right of the menu bar.</li>
|
Light/Dark mode can be toggled with the new button, found in the top-right of the menu bar.</li>
|
||||||
|
|
||||||
<h1>Create Custom Shortcuts</h1>
|
<h1>Create Custom Shortcuts</h1>
|
||||||
<li>Found in the BetterSEQTA+ Settings menu, custom shortcuts can now be created with a name and URL of your choice.</li>
|
<li>Found in the BetterSEQTA+ Settings menu, custom shortcuts can now be created with a name and URL of your choice.</li>
|
||||||
</div>
|
</div>
|
||||||
`).firstChild;
|
`).firstChild as HTMLElement;
|
||||||
|
|
||||||
let footer = stringToHTML(/* html */ `
|
const footer = stringToHTML(/* html */ `
|
||||||
<div class="whatsnewFooter">
|
<div class="whatsnewFooter">
|
||||||
<div>
|
<div>
|
||||||
Resources and Feedback:
|
Resources and Feedback:
|
||||||
@@ -312,63 +299,15 @@ export function OpenWhatsNewPopup() {
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<a href="https://ko-fi.com/sethburkart" target="_blank" style="background: none !important; margin:0;margin-left:6px; padding:0; display: flex; align-items: center;">
|
<a href="https://ko-fi.com/sethburkart" target="_blank" style="background: none !important; margin:0;margin-left:6px;padding:0; display: flex; align-items: center;">
|
||||||
<img height="25" style="border:0px; height:25px; margin-right: -6px;" src="${kofi}" border="0" alt="Buy Me a Coffee at ko-fi.com" />
|
<img height="25" style="border:0px; height:25px; margin-right: -6px;" src="${kofi}" border="0" alt="Buy Me a Coffee at ko-fi.com" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`).firstChild;
|
`).firstChild as HTMLElement;
|
||||||
|
|
||||||
let exitbutton = document.createElement("div");
|
openPopup({
|
||||||
exitbutton.id = "whatsnewclosebutton";
|
header,
|
||||||
|
content: [imageContainer, text, footer],
|
||||||
container.append(header);
|
|
||||||
container.append(imagecont);
|
|
||||||
container.append(textcontainer);
|
|
||||||
container.append(text as ChildNode);
|
|
||||||
container.append(footer as ChildNode);
|
|
||||||
container.append(exitbutton);
|
|
||||||
|
|
||||||
background.append(container);
|
|
||||||
|
|
||||||
document.getElementById("container")!.append(background);
|
|
||||||
|
|
||||||
let bkelement = document.getElementById("whatsnewbk");
|
|
||||||
let popup = document.getElementsByClassName("whatsnewContainer")[0];
|
|
||||||
|
|
||||||
if (settingsState.animations) {
|
|
||||||
animate(
|
|
||||||
[popup, bkelement as HTMLElement],
|
|
||||||
{ scale: [0, 1] },
|
|
||||||
{
|
|
||||||
type: "spring",
|
|
||||||
stiffness: 220,
|
|
||||||
damping: 18,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
animate(
|
|
||||||
".whatsnewTextContainer *",
|
|
||||||
{ opacity: [0, 1], y: [10, 0] },
|
|
||||||
{
|
|
||||||
delay: stagger(0.05, { startDelay: 0.1 }),
|
|
||||||
duration: 0.5,
|
|
||||||
ease: [0.22, 0.03, 0.26, 1],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
delete settingsState.justupdated;
|
|
||||||
|
|
||||||
bkelement!.addEventListener("click", function (event) {
|
|
||||||
// Check if the click event originated from the element itself and not any of its children
|
|
||||||
if (event.target === bkelement) {
|
|
||||||
DeleteWhatsNew();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var closeelement = document.getElementById("whatsnewclosebutton");
|
|
||||||
closeelement!.addEventListener("click", function () {
|
|
||||||
DeleteWhatsNew();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
import { settingsState } from "../listeners/SettingsState";
|
||||||
|
import { animate as motionAnimate, stagger } from "motion";
|
||||||
|
|
||||||
|
type AnimationTarget = string | Element | Element[] | NodeList | null;
|
||||||
|
|
||||||
|
let isClosing = false;
|
||||||
|
|
||||||
|
export async function closePopup() {
|
||||||
|
if (isClosing) return;
|
||||||
|
isClosing = true;
|
||||||
|
|
||||||
|
const background = document.getElementById("whatsnewbk");
|
||||||
|
const popup = document.getElementsByClassName("whatsnewContainer")[0] as
|
||||||
|
| HTMLElement
|
||||||
|
| undefined;
|
||||||
|
|
||||||
|
if (!background || !popup) {
|
||||||
|
isClosing = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!settingsState.animations) {
|
||||||
|
background.remove();
|
||||||
|
isClosing = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await (motionAnimate as any)(
|
||||||
|
[popup, background],
|
||||||
|
{ opacity: [1, 0], scale: [1, 0.95] },
|
||||||
|
{ duration: 0.25, easing: [0.22, 0.03, 0.26, 1] },
|
||||||
|
);
|
||||||
|
|
||||||
|
background.remove();
|
||||||
|
isClosing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OpenPopupOptions {
|
||||||
|
header?: Node | null;
|
||||||
|
content?: (Node | null | undefined)[];
|
||||||
|
animateSelector?: AnimationTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function openPopup({
|
||||||
|
header,
|
||||||
|
content = [],
|
||||||
|
animateSelector = ".whatsnewTextContainer *",
|
||||||
|
}: OpenPopupOptions = {}) {
|
||||||
|
const background = document.createElement("div");
|
||||||
|
background.id = "whatsnewbk";
|
||||||
|
background.classList.add("whatsnewBackground");
|
||||||
|
|
||||||
|
const container = document.createElement("div");
|
||||||
|
container.classList.add("whatsnewContainer");
|
||||||
|
|
||||||
|
if (header) container.append(header);
|
||||||
|
for (const node of content) if (node) container.append(node);
|
||||||
|
|
||||||
|
const closeButton = document.createElement("div");
|
||||||
|
closeButton.id = "whatsnewclosebutton";
|
||||||
|
container.append(closeButton);
|
||||||
|
|
||||||
|
background.append(container);
|
||||||
|
document.getElementById("container")!.append(background);
|
||||||
|
|
||||||
|
if (settingsState.animations) {
|
||||||
|
(motionAnimate as any)(
|
||||||
|
[container, background],
|
||||||
|
{ scale: [0, 1] },
|
||||||
|
{ type: "spring", stiffness: 220, damping: 18 },
|
||||||
|
);
|
||||||
|
|
||||||
|
if (animateSelector) {
|
||||||
|
const targets =
|
||||||
|
typeof animateSelector === "string"
|
||||||
|
? document.querySelectorAll(animateSelector)
|
||||||
|
: animateSelector;
|
||||||
|
|
||||||
|
(motionAnimate as any)(
|
||||||
|
targets!,
|
||||||
|
{ opacity: [0, 1], y: [10, 0] },
|
||||||
|
{
|
||||||
|
delay: stagger(0.05, { startDelay: 0.1 }),
|
||||||
|
duration: 0.5,
|
||||||
|
easing: [0.22, 0.03, 0.26, 1],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delete settingsState.justupdated;
|
||||||
|
|
||||||
|
background.addEventListener("click", (event) => {
|
||||||
|
if (event.target === background) void closePopup();
|
||||||
|
});
|
||||||
|
|
||||||
|
closeButton.addEventListener("click", () => void closePopup());
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { settingsState } from "@/seqta/utils/listeners/SettingsState";
|
||||||
|
import { addShortcuts } from "@/seqta/utils/Adders/AddShortcuts";
|
||||||
|
import { CreateCustomShortcutDiv } from "@/seqta/utils/CreateEnable/CreateCustomShortcutDiv";
|
||||||
|
|
||||||
|
export function renderShortcuts() {
|
||||||
|
const container = document.getElementById("shortcuts");
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
container.innerHTML = "";
|
||||||
|
|
||||||
|
try {
|
||||||
|
addShortcuts(settingsState.shortcuts || []);
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error("[BetterSEQTA+] Error adding built-in shortcuts:", err?.message || err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const custom = settingsState.customshortcuts || [];
|
||||||
|
for (const element of custom) {
|
||||||
|
try {
|
||||||
|
CreateCustomShortcutDiv(element);
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error("[BetterSEQTA+] Error adding custom shortcut:", element?.name, err?.message || err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -18,12 +18,27 @@ export async function SendNewsPage() {
|
|||||||
const main = document.getElementById("main");
|
const main = document.getElementById("main");
|
||||||
main!.innerHTML = "";
|
main!.innerHTML = "";
|
||||||
|
|
||||||
|
const displayCountry = (() => {
|
||||||
|
switch (settingsState.newsSource?.toLowerCase()) {
|
||||||
|
case "usa": return "the USA";
|
||||||
|
case "uk": return "the UK";
|
||||||
|
case "netherlands": return "the Netherlands";
|
||||||
|
default:
|
||||||
|
return settingsState.newsSource
|
||||||
|
? settingsState.newsSource
|
||||||
|
.split("_")
|
||||||
|
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
||||||
|
.join(" ")
|
||||||
|
: "Australia";
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
const html = stringToHTML(/* html */ `
|
const html = stringToHTML(/* html */ `
|
||||||
<div class="home-root">
|
<div class="home-root">
|
||||||
<div class="home-container" id="news-container">
|
<div class="home-container" id="news-container">
|
||||||
<h1 class="border">Latest Headlines in ${settingsState.newsSource ? settingsState.newsSource.charAt(0).toUpperCase() + settingsState.newsSource.slice(1) : "Australia"}</h1>
|
<h1 class="border">Latest Headlines in ${displayCountry}</h1>
|
||||||
</div>
|
</div>
|
||||||
</div>`);
|
</div>`);
|
||||||
|
|
||||||
main!.append(html.firstChild!);
|
main!.append(html.firstChild!);
|
||||||
|
|
||||||
|
|||||||
@@ -29,19 +29,11 @@ class StorageManager {
|
|||||||
},
|
},
|
||||||
set: (target, prop: keyof SettingsState, value) => {
|
set: (target, prop: keyof SettingsState, value) => {
|
||||||
const oldValue = target.data[prop];
|
const oldValue = target.data[prop];
|
||||||
|
|
||||||
// Only save if the value actually changed
|
// Only save if the reference actually changed
|
||||||
if (oldValue !== value) {
|
if (oldValue !== value) {
|
||||||
Reflect.set(target.data, prop, value);
|
Reflect.set(target.data, prop, value);
|
||||||
target.saveToStorage();
|
target.saveToStorage();
|
||||||
|
|
||||||
// Notify listeners immediately for responsiveness
|
|
||||||
const listeners = target.listeners.get(prop as string);
|
|
||||||
if (listeners) {
|
|
||||||
for (const listener of listeners) {
|
|
||||||
listener(value, oldValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { settingsState } from "./SettingsState";
|
import { settingsState } from "./SettingsState";
|
||||||
import { updateAllColors } from "@/seqta/ui/colors/Manager";
|
import { updateAllColors } from "@/seqta/ui/colors/Manager";
|
||||||
|
|
||||||
import { addShortcuts } from "@/seqta/utils/Adders/AddShortcuts";
|
// Shortcuts rendering
|
||||||
import { CreateCustomShortcutDiv } from "@/seqta/utils/CreateEnable/CreateCustomShortcutDiv";
|
import { renderShortcuts } from "@/seqta/utils/Render/renderShortcuts";
|
||||||
import { FilterUpcomingAssessments } from "@/seqta/utils/FilterUpcomingAssessments";
|
import { FilterUpcomingAssessments } from "@/seqta/utils/FilterUpcomingAssessments";
|
||||||
import { RemoveShortcutDiv } from "@/seqta/utils/DisableRemove/RemoveShortcutDiv";
|
|
||||||
|
|
||||||
import browser from "webextension-polyfill";
|
import browser from "webextension-polyfill";
|
||||||
import type { CustomShortcut } from "@/types/storage";
|
import type { CustomShortcut } from "@/types/storage";
|
||||||
@@ -47,21 +46,7 @@ export class StorageChangeHandler {
|
|||||||
oldValue: CustomShortcut[],
|
oldValue: CustomShortcut[],
|
||||||
) {
|
) {
|
||||||
if (!newValue || !oldValue) return;
|
if (!newValue || !oldValue) return;
|
||||||
|
renderShortcuts();
|
||||||
if (newValue.length > oldValue.length) {
|
|
||||||
// New shortcut added - add the last one
|
|
||||||
CreateCustomShortcutDiv(newValue[oldValue.length]);
|
|
||||||
} else if (newValue.length < oldValue.length) {
|
|
||||||
// Shortcut removed - find which one was removed
|
|
||||||
const newSet = new Set(newValue.map(item => JSON.stringify(item)));
|
|
||||||
const removedElement = oldValue.find(
|
|
||||||
(oldItem) => !newSet.has(JSON.stringify(oldItem))
|
|
||||||
);
|
|
||||||
|
|
||||||
if (removedElement) {
|
|
||||||
RemoveShortcutDiv([removedElement]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleShortcutsChange(
|
private handleShortcutsChange(
|
||||||
@@ -69,34 +54,7 @@ export class StorageChangeHandler {
|
|||||||
oldValue: { enabled: boolean; name: string }[],
|
oldValue: { enabled: boolean; name: string }[],
|
||||||
) {
|
) {
|
||||||
if (!newValue || !oldValue) return;
|
if (!newValue || !oldValue) return;
|
||||||
|
renderShortcuts();
|
||||||
// Create map for faster lookup
|
|
||||||
const oldMap = new Map(oldValue.map(item => [item.name, item.enabled]));
|
|
||||||
|
|
||||||
const addedShortcuts: { enabled: boolean; name: string }[] = [];
|
|
||||||
const removedShortcuts: { enabled: boolean; name: string }[] = [];
|
|
||||||
|
|
||||||
// Check for changes in shortcuts
|
|
||||||
for (const newItem of newValue) {
|
|
||||||
const oldEnabled = oldMap.get(newItem.name);
|
|
||||||
|
|
||||||
// Newly enabled shortcuts
|
|
||||||
if (newItem.enabled && (oldEnabled === undefined || !oldEnabled)) {
|
|
||||||
addedShortcuts.push(newItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Newly disabled shortcuts
|
|
||||||
if (!newItem.enabled && oldEnabled === true) {
|
|
||||||
removedShortcuts.push(newItem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (addedShortcuts.length > 0) {
|
|
||||||
addShortcuts(addedShortcuts);
|
|
||||||
}
|
|
||||||
if (removedShortcuts.length > 0) {
|
|
||||||
RemoveShortcutDiv(removedShortcuts);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleTransparencyEffectsChange(newValue: boolean) {
|
private handleTransparencyEffectsChange(newValue: boolean) {
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ export interface SettingsState {
|
|||||||
subjectfilters: Record<string, any>;
|
subjectfilters: Record<string, any>;
|
||||||
transparencyEffects: boolean;
|
transparencyEffects: boolean;
|
||||||
justupdated?: boolean;
|
justupdated?: boolean;
|
||||||
|
privacyStatementShown?: boolean;
|
||||||
|
privacyStatementLastUpdated?: string;
|
||||||
timeFormat?: string;
|
timeFormat?: string;
|
||||||
animations: boolean;
|
animations: boolean;
|
||||||
defaultPage: string;
|
defaultPage: string;
|
||||||
|
|||||||
@@ -84,6 +84,10 @@ export default defineConfig(({ command }) => ({
|
|||||||
settings: join(__dirname, "src", "interface", "index.html"),
|
settings: join(__dirname, "src", "interface", "index.html"),
|
||||||
pageState: join(__dirname, "src", "pageState.js"),
|
pageState: join(__dirname, "src", "pageState.js"),
|
||||||
},
|
},
|
||||||
|
onwarn(warning, warn) {
|
||||||
|
if (warning.code === "FILE_NAME_CONFLICT") return;
|
||||||
|
warn(warning);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|||||||
Reference in New Issue
Block a user