schoolbox web extension :)
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor(snippets): `Snippet` class

willow e06bbc5d bedf7243

+126 -135
+7 -8
src/entrypoints/popup/routes/Snippets.svelte
··· 1 1 <script lang="ts"> 2 2 import type { UserSnippet } from "@/utils/storage"; 3 - import { globalSettings, snippets } from "@/utils/storage"; 3 + import { globalSettings } from "@/utils/storage"; 4 + import { snippets } from "@/entrypoints/snippets.content"; 4 5 5 6 import Title from "../components/Title.svelte"; 6 7 import Toggle from "../components/inputs/Toggle.svelte"; ··· 45 46 }} /> 46 47 47 48 <div class="snippets-container w-full"> 48 - {#each Object.entries(snippets) as [id, snippet] (id)} 49 + {#each snippets as snippet (snippet.meta.id)} 49 50 <div class="group my-4 w-full"> 50 51 <Toggle 51 - {id} 52 + id={snippet.meta.id} 52 53 checked={snippet.toggle.state.toggle} 53 - update={(toggled: boolean) => { 54 - snippet.toggle.set({ toggle: toggled }); 55 - }} 56 - text={snippet.name} 57 - description={snippet.description} 54 + update={(toggle) => snippet.toggle.set({ toggle })} 55 + text={snippet.meta.name} 56 + description={snippet.meta.description} 58 57 size="small" /> 59 58 </div> 60 59 {/each}
+9 -9
src/entrypoints/snippets.content.ts
··· 1 1 import { defineContentScript } from "#imports"; 2 2 import { EXCLUDE_MATCHES } from "@/utils/constants"; 3 - import { defineSnippet } from "@/utils/snippet"; 4 - import censor from "./snippets/censor.css?inline"; 5 - import hidePfp from "./snippets/hidePfp/styles.css?inline"; 6 - import hidePwaPrompt from "./snippets/hidePwaPrompt.css?inline"; 7 - import roundedCorners from "./snippets/roundedCorners.css?inline"; 3 + import censor from "./snippets/censor"; 4 + import hidePfp from "./snippets/hidePfp"; 5 + import hidePwaPrompt from "./snippets/hidePwaPrompt"; 6 + import roundedCorners from "./snippets/roundedCorners"; 7 + 8 + export const snippets = [roundedCorners, hidePfp, hidePwaPrompt, censor]; 8 9 9 10 export default defineContentScript({ 10 11 matches: ["<all_urls>"], 11 12 runAt: "document_start", 12 13 excludeMatches: EXCLUDE_MATCHES, 13 14 async main() { 14 - defineSnippet("roundedCorners", roundedCorners); 15 - defineSnippet("hidePfp", hidePfp); 16 - defineSnippet("hidePwaPrompt", hidePwaPrompt); 17 - defineSnippet("censor", censor); 15 + for (const snippet of snippets) { 16 + snippet.init(); 17 + } 18 18 }, 19 19 });
src/entrypoints/snippets/censor.css src/entrypoints/snippets/censor/styles.css
+12
src/entrypoints/snippets/censor/index.ts
··· 1 + import { Snippet } from "@/utils/snippet"; 2 + import styles from "./styles.css?inline"; 3 + 4 + export default new Snippet( 5 + { 6 + id: "censor", 7 + name: "Censor", 8 + description: "Censors all text and images. This is intended for development purposes.", 9 + }, 10 + false, 11 + styles, 12 + );
+12
src/entrypoints/snippets/hidePfp/index.ts
··· 1 + import { Snippet } from "@/utils/snippet"; 2 + import styles from "./styles.css?inline"; 3 + 4 + export default new Snippet( 5 + { 6 + id: "hidePfp", 7 + name: "Hide Profile Picture", 8 + description: "Hide your profile picture across Schoolbox.", 9 + }, 10 + true, 11 + styles, 12 + );
src/entrypoints/snippets/hidePwaPrompt.css src/entrypoints/snippets/hidePwaPrompt/styles.css
+12
src/entrypoints/snippets/hidePwaPrompt/index.ts
··· 1 + import { Snippet } from "@/utils/snippet"; 2 + import styles from "./styles.css?inline"; 3 + 4 + export default new Snippet( 5 + { 6 + id: "hidePwaPrompt", 7 + name: "Hide PWA Prompt", 8 + description: "Hide the prompt in the notifications menu to install Schoolbox as a PWA and enable notifications.", 9 + }, 10 + true, 11 + styles, 12 + );
src/entrypoints/snippets/roundedCorners.css src/entrypoints/snippets/roundedCorners/styles.css
+12
src/entrypoints/snippets/roundedCorners/index.ts
··· 1 + import { Snippet } from "@/utils/snippet"; 2 + import styles from "./styles.css?inline"; 3 + 4 + export default new Snippet( 5 + { 6 + id: "roundedCorners", 7 + name: "Rounded Corners", 8 + description: "Hide your profile picture across Schoolbox.", 9 + }, 10 + true, 11 + styles, 12 + );
+60 -41
src/utils/snippet.ts
··· 1 + import { storage } from "#imports"; 1 2 import { hasChanged, injectInlineStyles, uninjectInlineStyles } from "."; 2 3 import { logger } from "./logger"; 3 - import type { SnippetId } from "./storage"; 4 - import { globalSettings, schoolboxUrls, snippets } from "./storage"; 4 + import type { Toggle } from "./storage"; 5 + import { globalSettings } from "./storage"; 6 + import { StorageState } from "./storage/state.svelte"; 7 + 8 + export class Snippet { 9 + private injected = false; 10 + public toggle: StorageState<Toggle>; 11 + 12 + constructor( 13 + public meta: { 14 + id: string; 15 + name: string; 16 + description: string; 17 + }, 18 + defaultToggle: boolean, 19 + private styleText: string, 20 + ) { 21 + this.meta = meta; 22 + this.styleText = styleText; // TODO: check if can remove this 23 + 24 + // init snippet storage 25 + this.toggle = new StorageState( 26 + storage.defineItem(`local:snippet-${meta.id}`, { 27 + fallback: { toggle: defaultToggle }, 28 + }), 29 + ); 30 + } 31 + 32 + async init() { 33 + logger.info(`init snippet: ${this.meta.name}`); 34 + 35 + if (await this.isEnabled()) this.inject(); 5 36 6 - export async function defineSnippet(snippetId: SnippetId, styleText: string) { 7 - const snippet = await snippets[snippetId].toggle.get(); 8 - const inject = () => { 9 - logger.info(`injecting snippet: ${snippets[snippetId].name}`); 10 - injectInlineStyles(styleText, `snippet-${snippetId}`); 11 - }; 12 - const uninject = () => { 13 - logger.info(`uninjecting snippet: ${snippets[snippetId].name}`); 14 - uninjectInlineStyles(`snippet-${snippetId}`); 15 - }; 37 + // init watchers 38 + globalSettings.watch((newValue, oldValue) => { 39 + if (hasChanged(newValue, oldValue, ["global", "snippets"])) this.reload(); 40 + }); 41 + this.toggle.watch(this.reload.bind(this)); 42 + } 16 43 17 - logger.info(`${snippets[snippetId].name}: ${snippet.toggle ? "enabled" : "disabled"}`); 44 + private inject() { 45 + if (this.injected) return; 46 + logger.info(`injecting snippet: ${this.meta.name}`); 47 + injectInlineStyles(this.styleText, `snippet-${this.meta.id}`); 48 + this.injected = true; 49 + } 18 50 19 - const settings = await globalSettings.get(); 20 - const urls = (await schoolboxUrls.get()).urls; 51 + private uninject() { 52 + if (!this.injected) return; 53 + logger.info(`uninjecting snippet: ${this.meta.name}`); 54 + uninjectInlineStyles(`snippet-${this.meta.id}`); 55 + this.injected = false; 56 + } 21 57 22 - if (snippet && typeof window !== "undefined" && urls.includes(window.location.origin)) { 23 - if (settings.global && settings.snippets && snippet.toggle) { 24 - // inject 25 - inject(); 26 - } 58 + private async reload() { 59 + if (this.injected) this.uninject(); 60 + if (await this.isEnabled()) this.inject(); 27 61 } 28 62 29 - // settings watcher for uninjection/injection 30 - globalSettings.watch(async (newValue, oldValue) => { 31 - if (hasChanged(newValue, oldValue, ["global", "snippets"])) { 32 - const snippet = await snippets[snippetId].toggle.get(); 33 - if (newValue.global && newValue.snippets && snippet.toggle) { 34 - inject(); 35 - } else { 36 - uninject(); 37 - } 38 - } 39 - }); 40 - snippets[snippetId].toggle.watch(async (newValue, oldValue) => { 41 - if (hasChanged(newValue, oldValue, ["toggle"])) { 42 - const settings = await globalSettings.get(); 43 - if (newValue.toggle && settings.global && settings.snippets) { 44 - inject(); 45 - } else { 46 - uninject(); 47 - } 48 - } 49 - }); 63 + private async isEnabled(): Promise<boolean> { 64 + const settings = await globalSettings.get(); 65 + const toggle = await this.toggle.get(); 66 + 67 + return settings.global && settings.snippets && toggle.toggle; 68 + } 50 69 }
+1 -2
src/utils/storage/index.ts
··· 1 1 export * from "./global"; 2 - export * from "./snippets"; 3 - export * from "./types"; 2 + export * from "./types";
-53
src/utils/storage/snippets.ts
··· 1 - import { storage } from "#imports"; 2 - import { StorageState } from "./state.svelte"; 3 - import type * as Types from "./types"; 4 - 5 - export const snippetConfig: Record<string, Types.SnippetConfig> = { 6 - roundedCorners: { 7 - name: "Rounded Corners", 8 - description: "Adds rounded corners to all elements across Schoolbox.", 9 - default: true, 10 - }, 11 - 12 - hidePfp: { 13 - name: "Hide Profile Picture", 14 - description: "Hide your profile picture across Schoolbox.", 15 - default: true, 16 - }, 17 - 18 - hidePwaPrompt: { 19 - name: "Hide PWA Prompt", 20 - description: "Hide the prompt in the notifications menu to install Schoolbox as a PWA and enable notifications.", 21 - default: true, 22 - }, 23 - 24 - censor: { 25 - name: "Censor", 26 - description: "Censors all text and images. This is intended for development purposes.", 27 - default: false, 28 - }, 29 - }; 30 - 31 - export const snippets = buildSnippetsFromConfig(snippetConfig); 32 - 33 - function buildSnippetsFromConfig( 34 - config: Record<string, Types.SnippetConfig>, 35 - ): Record<Types.SnippetId, Types.SnippetData> { 36 - const snippets: Partial<Record<Types.SnippetId, Types.SnippetData>> = {}; 37 - 38 - for (const [snippetId, snippetConfig] of Object.entries(config)) { 39 - const snippet: Types.SnippetData = { 40 - name: snippetConfig.name, 41 - description: snippetConfig.description, 42 - toggle: new StorageState( 43 - storage.defineItem<Types.Toggle>(`local:snippet-${snippetId}`, { 44 - fallback: { toggle: snippetConfig.default }, 45 - }), 46 - ), 47 - }; 48 - 49 - snippets[snippetId as Types.SnippetId] = snippet; 50 - } 51 - 52 - return snippets as Record<Types.SnippetId, Types.SnippetData>; 53 - }
+1 -22
src/utils/storage/types.ts
··· 1 - import type { snippetConfig } from "./snippets"; 2 - import type { StorageState } from "./state.svelte"; 3 - 4 - // Global 1 + // global 5 2 export interface Settings { 6 3 global: boolean; 7 4 plugins: boolean; ··· 46 43 toggle: boolean; 47 44 } 48 45 49 - // common for plugins and snippets 50 - export interface ItemInfo { 51 - name: string; 52 - description: string; 53 - } 54 - 55 - // plugins 56 46 export type Toggle = { toggle: boolean }; 57 47 58 48 export type Slider = { ··· 60 50 min: number; 61 51 max: number; 62 52 }; 63 - 64 - // snippets 65 - export type SnippetId = keyof typeof snippetConfig; 66 - 67 - export type SnippetData = { 68 - toggle: StorageState<Toggle>; 69 - } & ItemInfo; 70 - 71 - export type SnippetConfig = { 72 - default: boolean; 73 - } & ItemInfo;