tangled
alpha
login
or
join now
42willow.github.io
/
schooltape
schoolbox web extension :)
0
fork
atom
overview
issues
pulls
pipelines
Compare changes
Choose any two refs to compare.
base:
refactor/storage
refactor/plugins/class
main
feat/plugins/quick-switcher
feat/plugins/change-logo
feat/hot-reload
feat/css-hot-reload
no tags found
compare:
refactor/storage
refactor/plugins/class
main
feat/plugins/quick-switcher
feat/plugins/change-logo
feat/hot-reload
feat/css-hot-reload
no tags found
go
+287
-161
11 changed files
expand all
collapse all
unified
split
src
entrypoints
plugins
changeLogo
Menu.svelte
index.ts
plugins.content.ts
popup
components
inputs
Toggle.svelte
routes
Themes.svelte
start.content.ts
utils
constants.ts
index.ts
plugin.ts
storage
global.ts
types.ts
+34
src/entrypoints/plugins/changeLogo/Menu.svelte
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
···
1
+
<script lang="ts">
2
+
import Toggle from "@/entrypoints/popup/components/inputs/Toggle.svelte";
3
+
import type { Settings } from ".";
4
+
import { logos } from ".";
5
+
6
+
let { settings }: { settings: Settings } = $props();
7
+
</script>
8
+
9
+
<div class="grid grid-cols-3 gap-4">
10
+
{#await logos then logos}
11
+
{#each Object.entries(logos) as [id, logo] (id)}
12
+
<button
13
+
onclick={() => settings.logo.set({ id: id as keyof typeof logos })}
14
+
class:highlight={settings.logo.state.id === id}
15
+
class="flex flex-col rounded-lg border border-(--ctp-accent) p-2">
16
+
<span>{logo.name}</span>
17
+
<div class="flex h-full w-full items-center justify-center">
18
+
<img src={logo.url} alt="Logo" class="mt-2 w-16" />
19
+
</div>
20
+
</button>
21
+
{/each}
22
+
{/await}
23
+
</div>
24
+
25
+
<div class="mt-2">
26
+
<Toggle
27
+
text="Set as tab favicon"
28
+
update={(toggle) => {
29
+
settings.setAsFavicon.set({ toggle });
30
+
}}
31
+
checked={settings.setAsFavicon.state.toggle}
32
+
size="small"
33
+
id="setAsFavicon" />
34
+
</div>
+171
src/entrypoints/plugins/changeLogo/index.ts
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
···
1
+
import { browser } from "#imports";
2
+
import { hasChanged, injectInlineStyles, uninjectInlineStyles } from "@/utils";
3
+
import { logger } from "@/utils/logger";
4
+
import { Plugin } from "@/utils/plugin";
5
+
import type { Toggle } from "@/utils/storage";
6
+
import { globalSettings } from "@/utils/storage";
7
+
import type { StorageState } from "@/utils/storage/state.svelte";
8
+
import { flavors } from "@catppuccin/palette";
9
+
import type { Unwatch } from "wxt/utils/storage";
10
+
import schoolbox from "/schoolbox.svg?raw";
11
+
12
+
const ID = "changeLogo";
13
+
const PLUGIN_ID = `plugin-${ID}`;
14
+
15
+
let originalFavicon: string | null = null;
16
+
let unwatch: Unwatch | null = null;
17
+
18
+
export const logos = buildLogos({
19
+
schooltape: {
20
+
name: "Schooltape",
21
+
url: "schooltape.svg",
22
+
},
23
+
"schooltape-rainbow": {
24
+
name: "ST Rainbow",
25
+
url: "schooltape-ctp.svg",
26
+
},
27
+
"schooltape-legacy": {
28
+
name: "ST Legacy",
29
+
url: "https://schooltape.github.io/schooltape-legacy.svg",
30
+
},
31
+
catppuccin: {
32
+
name: "Catppuccin",
33
+
url: "https://raw.githubusercontent.com/catppuccin/catppuccin/main/assets/logos/exports/1544x1544_circle.png",
34
+
},
35
+
schoolbox: {
36
+
name: "Schoolbox",
37
+
raw: schoolbox,
38
+
},
39
+
});
40
+
41
+
export type Settings = {
42
+
setAsFavicon: StorageState<Toggle>;
43
+
logo: StorageState<{ id: keyof Awaited<typeof logos> }>;
44
+
};
45
+
46
+
interface LogoInfo {
47
+
name: string;
48
+
url: string;
49
+
}
50
+
type ImageSource = { name: string; url: string; raw?: never } | { name: string; url?: never; raw: string };
51
+
52
+
export default new Plugin<Settings>(
53
+
{
54
+
id: ID,
55
+
name: "Change Logo",
56
+
description: "Changes the Schoolbox logo to a logo of your choice.",
57
+
},
58
+
true,
59
+
{
60
+
setAsFavicon: { toggle: false },
61
+
logo: { id: "schooltape-rainbow" },
62
+
},
63
+
async (settings) => {
64
+
await inject(settings);
65
+
66
+
// add watcher to reload logo
67
+
unwatch = globalSettings.watch((newValue, oldValue) => {
68
+
if (hasChanged(newValue, oldValue, ["themeFlavour", "themeAccent"])) {
69
+
uninject();
70
+
inject(settings);
71
+
}
72
+
});
73
+
},
74
+
() => {
75
+
uninject();
76
+
77
+
// remove watcher
78
+
if (unwatch) {
79
+
unwatch();
80
+
unwatch = null;
81
+
}
82
+
},
83
+
[".logo"],
84
+
);
85
+
86
+
async function inject(settings: Settings) {
87
+
const resolvedLogos = await logos;
88
+
const logoId = (await settings.logo.get()).id;
89
+
90
+
injectLogo(resolvedLogos[logoId]);
91
+
92
+
if ((await settings.setAsFavicon.get()).toggle) {
93
+
injectFavicon(resolvedLogos[logoId]);
94
+
}
95
+
}
96
+
97
+
function uninject() {
98
+
uninjectLogo();
99
+
uninjectFavicon();
100
+
}
101
+
102
+
function injectLogo(logo: LogoInfo): void {
103
+
logger.info(`injecting logo: ${logo.name}`);
104
+
105
+
injectInlineStyles(
106
+
`a.logo > img { content: url("${logo.url}"); max-width: 30%; width: 100px; }`,
107
+
`${PLUGIN_ID}-logo`,
108
+
);
109
+
}
110
+
111
+
function uninjectLogo() {
112
+
logger.info("uninjecting logo...");
113
+
114
+
uninjectInlineStyles(`${PLUGIN_ID}-logo`);
115
+
}
116
+
117
+
function injectFavicon(logo: LogoInfo) {
118
+
logger.info(`injecting favicon: ${logo.name}`);
119
+
120
+
let favicon = document.querySelector("link[rel~='icon']") as HTMLLinkElement | null;
121
+
if (!favicon) {
122
+
favicon = document.createElement("link") as HTMLLinkElement;
123
+
favicon.rel = "icon";
124
+
document.head.appendChild(favicon);
125
+
}
126
+
127
+
originalFavicon = favicon?.href;
128
+
favicon.href = logo.url;
129
+
}
130
+
131
+
function uninjectFavicon() {
132
+
logger.info("uninjecting favicon...");
133
+
134
+
const favicon = document.querySelector<HTMLLinkElement>("link[rel~='icon']");
135
+
if (favicon && originalFavicon) {
136
+
favicon.href = originalFavicon;
137
+
originalFavicon = null;
138
+
}
139
+
}
140
+
141
+
async function buildLogos<T extends Record<string, ImageSource>>(logos: T): Promise<Record<keyof T, LogoInfo>> {
142
+
const output: Record<keyof T, LogoInfo> = {} as Record<keyof T, LogoInfo>;
143
+
144
+
for (const [key, value] of Object.entries(logos) as [keyof T, ImageSource][]) {
145
+
let url;
146
+
147
+
if (value.url) {
148
+
if (value.url.startsWith("http")) {
149
+
url = value.url;
150
+
} else {
151
+
// @ts-expect-error unlisted CSS not a PublicPath
152
+
url = browser.runtime.getURL(value.url);
153
+
}
154
+
} else if (value.raw) {
155
+
const settings = await globalSettings.get();
156
+
const flavour = settings.themeFlavour;
157
+
const accent = settings.themeAccent;
158
+
const accentHex = flavors[flavour].colors[accent].hex;
159
+
url = `data:image/svg+xml;utf8,${encodeURIComponent(value.raw.replaceAll("currentColor", accentHex))}`;
160
+
}
161
+
162
+
if (!url) throw new Error("error getting URL for logo");
163
+
164
+
output[key] = {
165
+
name: value.name,
166
+
url,
167
+
};
168
+
}
169
+
170
+
return output;
171
+
}
+11
-1
src/entrypoints/plugins.content.ts
···
1
import { defineContentScript } from "#imports";
2
import { EXCLUDE_MATCHES } from "@/utils/constants";
0
3
import homepageSwitcher from "./plugins/homepageSwitcher";
4
import modernIcons from "./plugins/modernIcons";
5
import progressBar from "./plugins/progressBar";
···
8
import subheader from "./plugins/subheader";
9
import tabTitle from "./plugins/tabTitle";
10
11
-
export const plugins = [subheader, scrollSegments, scrollPeriod, progressBar, modernIcons, tabTitle, homepageSwitcher];
0
0
0
0
0
0
0
0
0
12
13
export type PluginInstance = (typeof plugins)[number];
14
···
1
import { defineContentScript } from "#imports";
2
import { EXCLUDE_MATCHES } from "@/utils/constants";
3
+
import changeLogo from "./plugins/changeLogo";
4
import homepageSwitcher from "./plugins/homepageSwitcher";
5
import modernIcons from "./plugins/modernIcons";
6
import progressBar from "./plugins/progressBar";
···
9
import subheader from "./plugins/subheader";
10
import tabTitle from "./plugins/tabTitle";
11
12
+
export const plugins = [
13
+
subheader,
14
+
scrollSegments,
15
+
scrollPeriod,
16
+
progressBar,
17
+
modernIcons,
18
+
tabTitle,
19
+
changeLogo,
20
+
homepageSwitcher,
21
+
];
22
23
export type PluginInstance = (typeof plugins)[number];
24
+2
-2
src/entrypoints/popup/components/inputs/Toggle.svelte
···
29
</label>
30
31
<div
32
-
class="flex items-center justify-between text-ctp-overlay1 transition-colors duration-500 ease-in-out group-hover:text-ctp-subtext0">
33
<div>{description}</div>
34
-
<div>{@render children?.()}</div>
35
</div>
···
29
</label>
30
31
<div
32
+
class="flex items-center justify-between gap-2 text-ctp-overlay1 transition-colors duration-500 ease-in-out group-hover:text-ctp-subtext0">
33
<div>{description}</div>
34
+
{@render children?.()}
35
</div>
+3
-55
src/entrypoints/popup/routes/Themes.svelte
···
1
<script lang="ts">
2
-
import { browser } from "#imports";
3
-
import type { LogoId } from "@/utils/storage";
4
import { globalSettings } from "@/utils/storage";
5
-
import { LOGO_INFO } from "@/utils/constants";
6
-
import { Palette } from "@lucide/svelte";
7
8
import Title from "../components/Title.svelte";
9
-
import Modal from "../components/Modal.svelte";
10
-
import Button from "../components/inputs/Button.svelte";
11
-
import Toggle from "../components/inputs/Toggle.svelte";
12
13
-
const flavours = ["latte", "frappe", "macchiato", "mocha"];
14
const accents = [
15
"bg-ctp-rosewater",
16
"bg-ctp-flamingo",
···
28
"bg-ctp-lavender",
29
];
30
31
-
const logos = LOGO_INFO;
32
-
let showModal = $state(false);
33
-
34
function cleanAccent(accent: string) {
35
return accent.replace("bg-ctp-", "");
36
}
37
</script>
38
39
-
<Modal bind:showModal>
40
-
{#snippet header()}
41
-
<h2 class="mb-4 text-xl">Choose an icon</h2>
42
-
{/snippet}
43
-
44
-
<div class="grid grid-cols-3 gap-4">
45
-
{#each Object.entries(logos) as [logoId, logo] (logoId)}
46
-
<button
47
-
onclick={() => {
48
-
globalSettings.update({ themeLogo: logoId as LogoId });
49
-
}}
50
-
class:highlight={globalSettings.state.themeLogo === logoId}
51
-
class="flex flex-col rounded-lg border border-(--ctp-accent) p-2">
52
-
<span>{logo.name}</span>
53
-
{#if logo.disable !== true}
54
-
<div class="flex h-full w-full items-center justify-center">
55
-
{#if logo.adaptive}
56
-
<!-- eslint-disable-next-line @typescript-eslint/no-explicit-any -->
57
-
<span class="logo-picker" style="--icon: url({browser.runtime.getURL(logo.url as any)})"></span>
58
-
{:else}
59
-
<img src={logo.url} alt="Logo" class="mt-2 w-16" />
60
-
{/if}
61
-
</div>
62
-
{/if}
63
-
</button>
64
-
{/each}
65
-
</div>
66
-
67
-
<div class="mt-4">
68
-
<Toggle
69
-
update={(toggled) => {
70
-
globalSettings.update({ themeLogoAsFavicon: toggled });
71
-
}}
72
-
checked={globalSettings.state.themeLogoAsFavicon}
73
-
id="setAsFavicon"
74
-
size="small"
75
-
text="Set icon as tab favicon" />
76
-
</div>
77
-
</Modal>
78
-
79
<div id="card">
80
<Title
81
title="Themes"
···
105
aria-label={cleanAccent(accent)}
106
title={cleanAccent(accent)}
107
onclick={() => {
108
-
globalSettings.update({ themeAccent: cleanAccent(accent) });
109
}}></button>
110
{/each}
111
</div>
112
-
113
-
<Button title="Choose icon" id="choose-icon" onclick={() => (showModal = true)}
114
-
><Palette size={22} /> Choose an icon</Button>
115
</div>
···
1
<script lang="ts">
0
0
2
import { globalSettings } from "@/utils/storage";
3
+
import type { Accent, Flavour } from "@/utils/storage";
0
4
5
import Title from "../components/Title.svelte";
0
0
0
6
7
+
const flavours: Flavour[] = ["latte", "frappe", "macchiato", "mocha"];
8
const accents = [
9
"bg-ctp-rosewater",
10
"bg-ctp-flamingo",
···
22
"bg-ctp-lavender",
23
];
24
0
0
0
25
function cleanAccent(accent: string) {
26
return accent.replace("bg-ctp-", "");
27
}
28
</script>
29
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
30
<div id="card">
31
<Title
32
title="Themes"
···
56
aria-label={cleanAccent(accent)}
57
title={cleanAccent(accent)}
58
onclick={() => {
59
+
globalSettings.update({ themeAccent: cleanAccent(accent) as Accent });
60
}}></button>
61
{/each}
62
</div>
0
0
0
63
</div>
+4
-8
src/entrypoints/start.content.ts
···
2
import {
3
hasChanged,
4
injectCatppuccin,
5
-
injectLogo,
6
injectStylesheet,
7
injectUserSnippet,
8
onSchoolboxPage,
···
11
uninjectStylesheet,
12
uninjectUserSnippet,
13
} from "@/utils";
14
-
import { EXCLUDE_MATCHES, LOGO_INFO } from "@/utils/constants";
15
-
import type { LogoId, Settings } from "@/utils/storage";
16
import { globalSettings } from "@/utils/storage";
17
import type { WatchCallback } from "wxt/utils/storage";
18
import cssUrl from "./catppuccin.css?url";
···
26
// if not on Schoolbox page
27
if (!(await onSchoolboxPage())) return;
28
29
-
const updateThemes: WatchCallback<Settings> = async (newValue, oldValue) => {
30
// if global or themes was changed
31
if (hasChanged(newValue, oldValue, ["global", "themes", "themeFlavour", "themeAccent"])) {
32
if (newValue.global && newValue.themes) {
···
39
}
40
};
41
42
-
const updateUserSnippets: WatchCallback<Settings> = async (newValue, oldValue) => {
43
// if global or userSnippets were changed
44
if (hasChanged(newValue, oldValue, ["global", "userSnippets"])) {
45
// uninject removed snippets
···
79
injectThemes();
80
injectCatppuccin();
81
}
82
-
83
-
// inject logo
84
-
injectLogo(LOGO_INFO[settings.themeLogo as LogoId], settings.themeLogoAsFavicon);
85
86
// inject user snippets
87
if (settings.snippets) {
···
2
import {
3
hasChanged,
4
injectCatppuccin,
0
5
injectStylesheet,
6
injectUserSnippet,
7
onSchoolboxPage,
···
10
uninjectStylesheet,
11
uninjectUserSnippet,
12
} from "@/utils";
13
+
import { EXCLUDE_MATCHES } from "@/utils/constants";
14
+
import type { SettingsV2 } from "@/utils/storage";
15
import { globalSettings } from "@/utils/storage";
16
import type { WatchCallback } from "wxt/utils/storage";
17
import cssUrl from "./catppuccin.css?url";
···
25
// if not on Schoolbox page
26
if (!(await onSchoolboxPage())) return;
27
28
+
const updateThemes: WatchCallback<SettingsV2> = async (newValue, oldValue) => {
29
// if global or themes was changed
30
if (hasChanged(newValue, oldValue, ["global", "themes", "themeFlavour", "themeAccent"])) {
31
if (newValue.global && newValue.themes) {
···
38
}
39
};
40
41
+
const updateUserSnippets: WatchCallback<SettingsV2> = async (newValue, oldValue) => {
42
// if global or userSnippets were changed
43
if (hasChanged(newValue, oldValue, ["global", "userSnippets"])) {
44
// uninject removed snippets
···
78
injectThemes();
79
injectCatppuccin();
80
}
0
0
0
81
82
// inject user snippets
83
if (settings.snippets) {
-30
src/utils/constants.ts
···
1
-
import type { LogoId, LogoInfo } from "./storage";
2
-
3
export const EXCLUDE_MATCHES: string[] = ["*://*/learning/quiz/*"];
4
-
export const LOGO_INFO: Record<LogoId, LogoInfo> = {
5
-
default: {
6
-
name: "Default",
7
-
url: "default",
8
-
disable: true,
9
-
},
10
-
catppuccin: {
11
-
name: "Catppuccin",
12
-
url: "https://raw.githubusercontent.com/catppuccin/catppuccin/main/assets/logos/exports/1544x1544_circle.png",
13
-
},
14
-
schoolbox: {
15
-
name: "Schoolbox",
16
-
url: "schoolbox.svg",
17
-
adaptive: true,
18
-
},
19
-
schooltape: {
20
-
name: "Schooltape",
21
-
url: "schooltape.svg",
22
-
},
23
-
"schooltape-rainbow": {
24
-
name: "ST Rainbow",
25
-
url: "schooltape-ctp.svg",
26
-
},
27
-
"schooltape-legacy": {
28
-
name: "ST Legacy",
29
-
url: "https://schooltape.github.io/schooltape-legacy.svg",
30
-
},
31
-
};
···
0
0
1
export const EXCLUDE_MATCHES: string[] = ["*://*/learning/quiz/*"];
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
+2
-47
src/utils/index.ts
···
1
import { browser } from "#imports";
2
import { flavorEntries } from "@catppuccin/palette";
3
import { logger } from "./logger";
4
-
import type { BackgroundMessage, LogoInfo } from "./storage";
5
import { globalSettings, schoolboxUrls } from "./storage";
6
7
export const dataAttr = (id: string) => `[data-schooltape="${id}"]`;
···
20
const style = document.createElement("style");
21
style.textContent = styleText;
22
setDataAttr(style, `inline-${id}`);
23
-
document.head.append(style);
24
-
// logger.info(`injected styles with id ${id}`);
25
}
26
27
export function uninjectInlineStyles(id: string) {
···
52
53
export function uninjectCatppuccin() {
54
uninjectInlineStyles("catppuccin");
55
-
}
56
-
57
-
export function injectLogo(logo: LogoInfo, setAsFavicon: boolean) {
58
-
let url = logo.url;
59
-
if (!url.startsWith("http")) {
60
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
61
-
url = browser.runtime.getURL(url as any);
62
-
}
63
-
logger.info(`injecting logo: ${logo.name}`);
64
-
if (logo.disable) {
65
-
return;
66
-
}
67
-
const style = document.createElement("style");
68
-
style.classList.add("schooltape");
69
-
if (logo.adaptive) {
70
-
style.textContent = `a.logo > img { display: none !important; } a.logo { display: flex; align-items: center; justify-content: center; }`;
71
-
const span = document.createElement("span");
72
-
span.style.mask = `url("${url}") no-repeat center`;
73
-
span.style.maskSize = "100% 100%";
74
-
span.style.backgroundColor = "hsl(var(--ctp-accent))";
75
-
span.style.width = "100%";
76
-
span.style.height = "60px";
77
-
span.style.display = "block";
78
-
window.addEventListener("load", () => {
79
-
document.querySelectorAll("a.logo").forEach((logo) => {
80
-
const clonedSpan = span.cloneNode(true);
81
-
logo.append(clonedSpan);
82
-
});
83
-
});
84
-
} else {
85
-
style.textContent = `a.logo > img { content: url("${url}"); max-width: 30%; width: 100px; }`;
86
-
}
87
-
document.head.appendChild(style);
88
-
89
-
// inject favicon
90
-
if (setAsFavicon) {
91
-
let favicon = document.querySelector("link[rel~='icon']") as HTMLLinkElement | null;
92
-
if (!favicon) {
93
-
favicon = document.createElement("link") as HTMLLinkElement;
94
-
favicon.rel = "icon";
95
-
document.head.appendChild(favicon);
96
-
}
97
-
favicon.href = url;
98
-
}
99
}
100
101
export function injectStylesheet(url: string, id: string) {
···
1
import { browser } from "#imports";
2
import { flavorEntries } from "@catppuccin/palette";
3
import { logger } from "./logger";
4
+
import type { BackgroundMessage } from "./storage";
5
import { globalSettings, schoolboxUrls } from "./storage";
6
7
export const dataAttr = (id: string) => `[data-schooltape="${id}"]`;
···
20
const style = document.createElement("style");
21
style.textContent = styleText;
22
setDataAttr(style, `inline-${id}`);
23
+
document.head.appendChild(style);
0
24
}
25
26
export function uninjectInlineStyles(id: string) {
···
51
52
export function uninjectCatppuccin() {
53
uninjectInlineStyles("catppuccin");
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
54
}
55
56
export function injectStylesheet(url: string, id: string) {
-1
src/utils/plugin.ts
···
9
private injected = false;
10
public toggle: StorageState<Toggle>;
11
public settings!: T;
12
-
public menu: string | undefined;
13
14
constructor(
15
public meta: {
···
9
private injected = false;
10
public toggle: StorageState<Toggle>;
11
public settings!: T;
0
12
13
constructor(
14
public meta: {
+27
-4
src/utils/storage/global.ts
···
1
import { storage } from "#imports";
0
2
import { StorageState } from "./state.svelte";
3
import type * as Types from "./types";
4
5
-
export const globalSettings = new StorageState<Types.Settings>(
6
-
storage.defineItem<Types.Settings>("local:globalSettings", {
0
7
fallback: {
8
global: true,
9
plugins: true,
···
12
13
themeFlavour: "mocha",
14
themeAccent: "mauve",
15
-
themeLogo: "schooltape-rainbow",
16
-
themeLogoAsFavicon: false,
17
18
userSnippets: {},
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
19
},
20
}),
21
);
···
1
import { storage } from "#imports";
2
+
import type { Settings as LogoSettings } from "@/entrypoints/plugins/changeLogo";
3
import { StorageState } from "./state.svelte";
4
import type * as Types from "./types";
5
6
+
export const globalSettings = new StorageState(
7
+
storage.defineItem<Types.SettingsV2>("local:globalSettings", {
8
+
version: 2,
9
fallback: {
10
global: true,
11
plugins: true,
···
14
15
themeFlavour: "mocha",
16
themeAccent: "mauve",
0
0
17
18
userSnippets: {},
19
+
},
20
+
migrations: {
21
+
2: async (settings: Types.SettingsV1) => {
22
+
const { themeLogo, themeLogoAsFavicon, ...rest } = settings;
23
+
24
+
// dynamic import to avoid TDZ error
25
+
const { plugins } = await import("@/entrypoints/plugins.content");
26
+
const changeLogo = plugins.find((plugin) => plugin.meta.id === "changeLogo");
27
+
28
+
if (changeLogo) {
29
+
const s = changeLogo.settings as LogoSettings;
30
+
if (themeLogo !== "default") {
31
+
// update logo
32
+
s.logo.set({ id: themeLogo });
33
+
} else {
34
+
// disable changeLogo
35
+
changeLogo.toggle.set({ toggle: false });
36
+
}
37
+
s.setAsFavicon.set({ toggle: themeLogoAsFavicon });
38
+
}
39
+
40
+
return rest;
41
+
},
42
},
43
}),
44
);
+33
-13
src/utils/storage/types.ts
···
5
| { type: "updateTabUrl"; url: string };
6
7
// global
8
-
export interface Settings {
9
global: boolean;
10
plugins: boolean;
11
themes: boolean;
12
snippets: boolean;
13
14
-
themeFlavour: string;
15
-
themeAccent: string;
16
-
themeLogo: LogoId;
17
themeLogoAsFavicon: boolean;
18
19
userSnippets: Record<string, UserSnippet>;
20
}
21
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
22
export interface UpdatedBadges {
23
icon: boolean;
24
changelog: boolean;
···
30
31
export interface SchoolboxUrls {
32
urls: string[];
33
-
}
34
-
35
-
export type LogoId = "default" | "catppuccin" | "schoolbox" | "schooltape" | "schooltape-rainbow" | "schooltape-legacy";
36
-
37
-
export interface LogoInfo {
38
-
name: string;
39
-
url: string;
40
-
disable?: boolean; // whether the icon should be injected or not
41
-
adaptive?: boolean; // whether the icon should follow the accent colour
42
}
43
44
export interface UserSnippet {
···
5
| { type: "updateTabUrl"; url: string };
6
7
// global
8
+
export interface SettingsV1 {
9
global: boolean;
10
plugins: boolean;
11
themes: boolean;
12
snippets: boolean;
13
14
+
themeFlavour: Flavour;
15
+
themeAccent: Accent;
16
+
themeLogo: "default" | "schooltape" | "schooltape-rainbow" | "schooltape-legacy" | "catppuccin" | "schoolbox";
17
themeLogoAsFavicon: boolean;
18
19
userSnippets: Record<string, UserSnippet>;
20
}
21
22
+
export interface SettingsV2 {
23
+
global: boolean;
24
+
plugins: boolean;
25
+
themes: boolean;
26
+
snippets: boolean;
27
+
28
+
themeFlavour: Flavour;
29
+
themeAccent: Accent;
30
+
31
+
userSnippets: Record<string, UserSnippet>;
32
+
}
33
+
34
+
export type Flavour = "latte" | "frappe" | "macchiato" | "mocha";
35
+
export type Accent =
36
+
| "rosewater"
37
+
| "flamingo"
38
+
| "pink"
39
+
| "mauve"
40
+
| "red"
41
+
| "maroon"
42
+
| "peach"
43
+
| "yellow"
44
+
| "green"
45
+
| "teal"
46
+
| "sky"
47
+
| "sapphire"
48
+
| "blue"
49
+
| "lavender";
50
+
51
export interface UpdatedBadges {
52
icon: boolean;
53
changelog: boolean;
···
59
60
export interface SchoolboxUrls {
61
urls: string[];
0
0
0
0
0
0
0
0
0
62
}
63
64
export interface UserSnippet {