schoolbox web extension :)
0
fork

Configure Feed

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

refactor(plugins): manage config in plugin modules

* #315

willow eca19710 765c4dcb

+149 -68
+5 -5
src/entrypoints/plugins.content.ts
··· 11 11 import tabTitle from "./plugins/tabTitle"; 12 12 13 13 export const plugins: Plugin<any>[] = [ 14 - homepageSwitcher, 15 - modernIcons, 16 - progressBar, 17 - scrollPeriod, 18 - scrollSegments, 19 14 subheader, 15 + scrollSegments, 16 + scrollPeriod, 17 + progressBar, 18 + modernIcons, 20 19 tabTitle, 20 + homepageSwitcher, 21 21 ]; 22 22 23 23 export default defineContentScript({
+5 -3
src/entrypoints/plugins/homepageSwitcher.ts src/entrypoints/plugins/homepageSwitcher/index.ts
··· 2 2 import { Plugin } from "@/utils/plugin"; 3 3 import type { Toggle } from "@/utils/storage"; 4 4 import type { StorageState } from "@/utils/storage/state.svelte"; 5 + import menu from "./Menu.svelte?url"; 5 6 6 7 let logos: HTMLAnchorElement[] | null = null; 7 8 let controller: AbortController | null = null; 8 9 9 - type Settings = { 10 + export type Settings = { 10 11 closeCurrentTab: StorageState<Toggle>; 11 12 }; 12 13 ··· 16 17 name: "Homepage Switcher", 17 18 description: "The logo will switch to existing Schoolbox homepage when available.", 18 19 }, 20 + false, 19 21 { 20 - toggle: true, 21 - settings: { 22 + config: { 22 23 closeCurrentTab: { 23 24 toggle: false, 24 25 }, 25 26 }, 27 + menu, 26 28 }, 27 29 async (settings) => { 28 30 if (logos !== null) return;
+16
src/entrypoints/plugins/homepageSwitcher/Menu.svelte
··· 1 + <script lang="ts"> 2 + import Toggle from "@/entrypoints/popup/components/inputs/Toggle.svelte"; 3 + import type { Settings } from "."; 4 + 5 + let { settings }: { settings: Settings } = $props(); 6 + </script> 7 + 8 + <Toggle 9 + text="Close current tab" 10 + description="When switching to another tab, close the current one." 11 + size="small" 12 + id="closeCurrentTab" 13 + checked={settings.closeCurrentTab.state.toggle} 14 + update={async (toggle) => { 15 + settings.closeCurrentTab.set({ toggle }); 16 + }} />
+16
src/entrypoints/plugins/modernIcons/Menu.svelte
··· 1 + <script lang="ts"> 2 + import Toggle from "@/entrypoints/popup/components/inputs/Toggle.svelte"; 3 + import type { Settings } from "."; 4 + 5 + let { settings }: { settings: Settings } = $props(); 6 + </script> 7 + 8 + <Toggle 9 + text="Filled icons" 10 + description="Whether the icons should be filled or outlined." 11 + size="small" 12 + id="filled" 13 + checked={settings.filled.state.toggle} 14 + update={async (toggle) => { 15 + settings.filled.set({ toggle }); 16 + }} />
+5 -3
src/entrypoints/plugins/modernIcons/index.ts
··· 10 10 import styleText from "./styles.css?inline"; 11 11 import type { Toggle } from "@/utils/storage"; 12 12 import type { StorageState } from "@/utils/storage/state.svelte"; 13 + import menu from "./Menu.svelte?url"; 13 14 14 15 const ID = "modernIcons"; 15 16 const PLUGIN_ID = `plugin-${ID}`; 16 17 17 - type Settings = { 18 + export type Settings = { 18 19 filled: StorageState<Toggle>; 19 20 }; 20 21 ··· 24 25 name: "Modern Icons", 25 26 description: "Modernise the icons across Schoolbox.", 26 27 }, 28 + true, 27 29 { 28 - toggle: true, 29 - settings: { 30 + config: { 30 31 filled: { toggle: true }, 31 32 }, 33 + menu, 32 34 }, 33 35 34 36 async (settings) => {
+2 -3
src/entrypoints/plugins/progressBar/index.ts
··· 13 13 name: "Progress Bar", 14 14 description: "Displays a progress bar below the timetable to show the time of the day.", 15 15 }, 16 - { 17 - toggle: true, 18 - }, 16 + true, 17 + null, 19 18 () => { 20 19 if (window.location.pathname === "/" && document.querySelector(".timetable")) { 21 20 const periodList = getListOfPeriods();
+5 -3
src/entrypoints/plugins/scrollPeriod.ts src/entrypoints/plugins/scrollPeriod/index.ts
··· 2 2 import { Plugin } from "@/utils/plugin"; 3 3 import type { Slider, Toggle } from "@/utils/storage"; 4 4 import type { StorageState } from "@/utils/storage/state.svelte"; 5 + import menu from "./Menu.svelte?url"; 5 6 6 7 let interval: NodeJS.Timeout | null = null; 7 8 let controller: AbortController | null = null; 8 9 9 - type Settings = { 10 + export type Settings = { 10 11 resetCooldownOnMouseMove: StorageState<Toggle>; 11 12 cooldownDuration: StorageState<Slider>; 12 13 }; ··· 17 18 name: "Scroll Period", 18 19 description: "Scrolls to the current period on the timetable.", 19 20 }, 21 + true, 20 22 { 21 - toggle: true, 22 - settings: { 23 + config: { 23 24 resetCooldownOnMouseMove: { toggle: true }, 24 25 cooldownDuration: { min: 1, max: 60, value: 10 }, 25 26 }, 27 + menu, 26 28 }, 27 29 async (settings) => { 28 30 const timetable = document.querySelector("[data-timetable-container] div.scrollable");
+23
src/entrypoints/plugins/scrollPeriod/Menu.svelte
··· 1 + <script lang="ts"> 2 + import Toggle from "@/entrypoints/popup/components/inputs/Toggle.svelte"; 3 + import Slider from "@/entrypoints/popup/components/inputs/Slider.svelte"; 4 + import type { Settings } from "."; 5 + 6 + let { settings }: { settings: Settings } = $props(); 7 + </script> 8 + 9 + <Toggle 10 + text="Reset on mouse move" 11 + description="Whether to reset the scrolling cooldown when you move your mouse." 12 + size="small" 13 + id="resetCooldownOnMouseMove" 14 + checked={settings.resetCooldownOnMouseMove.state.toggle} 15 + update={async (toggle) => { 16 + settings.resetCooldownOnMouseMove.set({ toggle }); 17 + }} /> 18 + 19 + <Slider 20 + name="Cooldown duration" 21 + id="cooldownDuration" 22 + update={(value) => settings.cooldownDuration.update({ value })} 23 + {...settings.cooldownDuration.state} />
+2 -3
src/entrypoints/plugins/scrollSegments/index.ts
··· 11 11 name: "Scroll Segments", 12 12 description: "Segments the Schoolbox page into scrollable sections.", 13 13 }, 14 - { 15 - toggle: true, 16 - }, 14 + true, 15 + null, 17 16 () => { 18 17 const footerCopy = document.querySelector(dataAttr(PLUGIN_ID)); 19 18 if (footerCopy) return;
+15
src/entrypoints/plugins/subheader/Menu.svelte
··· 1 + <script lang="ts"> 2 + import Toggle from "@/entrypoints/popup/components/inputs/Toggle.svelte"; 3 + import type { Settings } from "."; 4 + 5 + let { settings }: { settings: Settings } = $props(); 6 + </script> 7 + 8 + <Toggle 9 + text="Open links in new tab" 10 + size="small" 11 + id="openInNewTab" 12 + checked={settings.openInNewTab.state.toggle} 13 + update={async (toggle) => { 14 + settings.openInNewTab.set({ toggle }); 15 + }} />
+5 -3
src/entrypoints/plugins/subheader/index.ts
··· 4 4 import { dataAttr, injectInlineStyles, setDataAttr, uninjectInlineStyles } from "@/utils"; 5 5 import type { StorageState } from "@/utils/storage/state.svelte"; 6 6 import type { Toggle } from "@/utils/storage"; 7 + import menu from "./Menu.svelte?url"; 7 8 8 9 const ID = "subheader"; 9 10 const PLUGIN_ID = `plugin-${ID}`; ··· 12 13 let oldChildren: ChildNode[] = []; 13 14 let subheader: HTMLHeadingElement | null = null; 14 15 15 - type Settings = { 16 + export type Settings = { 16 17 openInNewTab: StorageState<Toggle>; 17 18 }; 18 19 ··· 22 23 name: "Subheader Revamp", 23 24 description: "Adds a clock and current period info to the subheader.", 24 25 }, 26 + true, 25 27 { 26 - toggle: true, 27 - settings: { 28 + config: { 28 29 openInNewTab: { toggle: true }, 29 30 }, 31 + menu, 30 32 }, 31 33 async (settings) => { 32 34 const openInNewTab = await settings.openInNewTab.get();
+5 -3
src/entrypoints/plugins/tabTitle.ts src/entrypoints/plugins/tabTitle/index.ts
··· 1 1 import { Plugin } from "@/utils/plugin"; 2 2 import type { Toggle } from "@/utils/storage"; 3 3 import type { StorageState } from "@/utils/storage/state.svelte"; 4 + import menu from "./Menu.svelte?url"; 4 5 5 6 const ID = "tabTitle"; 6 7 let originalTitle: string | null = null; 7 8 8 - type Settings = { 9 + export type Settings = { 9 10 showSubjectPrefix: StorageState<Toggle>; 10 11 }; 11 12 ··· 15 16 name: "Better Tab Titles", 16 17 description: "Improves the tab titles for easier navigation.", 17 18 }, 19 + true, 18 20 { 19 - toggle: true, 20 - settings: { 21 + config: { 21 22 showSubjectPrefix: { toggle: true }, 22 23 }, 24 + menu, 23 25 }, 24 26 async (settings) => { 25 27 // if already injected, abort
+16
src/entrypoints/plugins/tabTitle/Menu.svelte
··· 1 + <script lang="ts"> 2 + import Toggle from "@/entrypoints/popup/components/inputs/Toggle.svelte"; 3 + import type { Settings } from "."; 4 + 5 + let { settings }: { settings: Settings } = $props(); 6 + </script> 7 + 8 + <Toggle 9 + text="Show subject prefix" 10 + description="e.g. 'ENG - VCE English 1 & 2' becomes 'VCE English 1 & 2'" 11 + size="small" 12 + id="showSubjectPrefix" 13 + checked={settings.showSubjectPrefix.state.toggle} 14 + update={async (toggle) => { 15 + settings.showSubjectPrefix.set({ toggle }); 16 + }} />
+18 -34
src/entrypoints/popup/routes/Plugins.svelte
··· 1 1 <script lang="ts"> 2 2 import { globalSettings } from "@/utils/storage"; 3 + import { Plugin } from "@/utils/plugin"; 3 4 import { Settings } from "@lucide/svelte"; 4 5 import Title from "../components/Title.svelte"; 5 6 import Button from "../components/inputs/Button.svelte"; 6 7 import Modal from "../components/Modal.svelte"; 7 8 import Toggle from "../components/inputs/Toggle.svelte"; 8 - import Slider from "../components/inputs/Slider.svelte"; 9 9 import { plugins } from "@/entrypoints/plugins.content"; 10 + import { onMount } from "svelte"; 10 11 11 12 let showModal = $state(false); 12 - // let selectedPluginId: PluginId | undefined = $state(); 13 - // let selectedPlugin = $derived.by(() => { 14 - // if (selectedPluginId !== undefined) { 15 - // return plugins[selectedPluginId]; 16 - // } 17 - // }); 13 + let components: Record<string, any> = $state({}); 14 + let selectedPlugin: Plugin | undefined = $state(); 15 + let Menu = $derived(selectedPlugin ? components[selectedPlugin.meta.id] : undefined); 16 + 17 + onMount(async () => { 18 + for (const plugin of plugins) { 19 + if (!plugin.menu) continue; 20 + components[plugin.meta.id] = (await import(/* @vite-ignore */ plugin.menu)).default; 21 + } 22 + }); 18 23 </script> 19 24 20 25 <div id="card"> ··· 37 42 plugin.toggle.set({ toggle: toggled }); 38 43 }} 39 44 size="small"> 40 - {#if plugin.settings !== undefined} 45 + {#if plugin.settings} 41 46 <Button 42 47 title={plugin.meta.name + " Settings"} 43 48 id={plugin.meta.id} 44 49 onclick={() => { 45 - // selectedPluginId = id as PluginId; TODO 50 + selectedPlugin = plugin; 46 51 showModal = true; 47 52 }}><Settings size={22} /></Button> 48 53 {/if} ··· 52 57 </div> 53 58 </div> 54 59 55 - <!-- 56 60 {#if selectedPlugin} 57 61 <Modal bind:showModal> 58 62 {#snippet header()} 59 - <h2 class="mb-4 text-xl">{selectedPlugin.name}</h2> 63 + {#if selectedPlugin} 64 + <h2 class="mb-4 text-xl">{selectedPlugin.meta.name}</h2> 65 + {/if} 60 66 {/snippet} 61 - {#if selectedPlugin.settings !== undefined} 62 - {#each Object.entries(selectedPlugin.settings) as [id, setting] (id)} 63 - {#if setting.type === "toggle"} 64 - <Toggle 65 - text={setting.name} 66 - description={setting.description} 67 - size="small" 68 - checked={setting.state.state.toggle} 69 - update={async (toggled) => { 70 - setting.state.set({ toggle: toggled }); 71 - }} 72 - {id} /> 73 - {:else if setting.type === "slider"} 74 - <Slider 75 - {id} 76 - update={(newValue) => { 77 - setting.state.update({ value: newValue }); 78 - }} 79 - {...setting.state.state} /> 80 - {/if} 81 - {/each} 82 - {/if} 67 + <Menu settings={selectedPlugin.settings} /> 83 68 </Modal> 84 69 {/if} 85 - -->
+11 -8
src/utils/plugin.ts
··· 9 9 private injected = false; 10 10 public toggle: StorageState<Toggle>; 11 11 public settings!: T; 12 + public menu: string | undefined; 12 13 13 14 constructor( 14 15 public meta: { ··· 16 17 name: string; 17 18 description: string; 18 19 }, 19 - defaultConfig: { 20 - toggle: boolean; 21 - settings?: Record<string, object>; 22 - }, 20 + defaultToggle: boolean, 21 + settings: { 22 + config: Record<string, object>; 23 + menu: string; 24 + } | null, 23 25 private injectCallback: (settings: T) => Promise<void> | void, 24 26 private uninjectCallback: (settings: T) => Promise<void> | void, 25 27 private elementsToWaitFor: string[] = [], ··· 28 30 this.elementsToWaitFor = elementsToWaitFor; 29 31 this.injectCallback = injectCallback; 30 32 this.uninjectCallback = uninjectCallback; 33 + if (settings && settings.menu) this.menu = settings.menu; 31 34 32 35 // init plugin storage 33 36 this.toggle = new StorageState( 34 37 storage.defineItem(`local:plugin-${meta.id}`, { 35 - fallback: { toggle: defaultConfig.toggle }, 38 + fallback: { toggle: defaultToggle }, 36 39 }), 37 40 ); 38 - if (defaultConfig.settings) { 41 + if (settings && settings.config) { 39 42 this.settings = Object.fromEntries( 40 - Object.entries(defaultConfig.settings).map(([key, value]) => [ 43 + Object.entries(settings.config).map(([key, value]) => [ 41 44 key, 42 45 new StorageState( 43 46 storage.defineItem(`local:plugin-${meta.id}-${key}`, { ··· 82 85 this.toggle.watch(this.reload.bind(this)); 83 86 if (this.settings) { 84 87 for (const setting of Object.values(this.settings)) { 85 - setting.watch(this.reload); 88 + setting.watch(this.reload.bind(this)); 86 89 } 87 90 } 88 91 }