schoolbox web extension :)
0
fork

Configure Feed

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

refactor(storage): use StorageState methods

willow a068d122 e0a16d05

+79 -78
+3 -5
src/entrypoints/background.ts
··· 46 46 }); 47 47 48 48 // update icon when toggle or update is changed 49 - globalSettings.storage.watch(() => { 50 - updateIcon(); 51 - }); 49 + globalSettings.watch(updateIcon); 52 50 53 51 // listen for messages 54 52 interface Message { ··· 138 136 if (new Date().getMonth() === 5) { 139 137 iconSuffix += "-ctp"; 140 138 } 141 - if ((await globalSettings.storage.getValue()).global === false) { 139 + if ((await globalSettings.get()).global === false) { 142 140 iconSuffix += "-disabled"; 143 141 } 144 - if ((await updated.storage.getValue()).icon === true) { 142 + if ((await updated.get()).icon === true) { 145 143 iconSuffix += "-badge"; 146 144 } 147 145
+4 -4
src/entrypoints/end.content.ts
··· 8 8 runAt: "document_end", 9 9 excludeMatches: EXCLUDE_MATCHES, 10 10 async main() { 11 - const settings = await globalSettings.storage.getValue(); 12 - const urls = (await schoolboxUrls.storage.getValue()).urls; 11 + const settings = await globalSettings.get(); 12 + const urls = (await schoolboxUrls.get()).urls; 13 13 14 - logger.info((await schoolboxUrls.storage.getValue()).urls); 14 + logger.info(urls); 15 15 16 16 if (!settings.global) return; 17 17 ··· 21 21 if (!urls.includes(window.location.origin)) { 22 22 logger.info(`URL ${window.location.origin} not in storage, adding...`); 23 23 urls.push(window.location.origin); 24 - await schoolboxUrls.storage.setValue({ urls }); 24 + await schoolboxUrls.set({ urls }); 25 25 // TODO: hot reload 26 26 window.location.reload(); 27 27 }
+1 -1
src/entrypoints/popup/App.svelte
··· 26 26 let accentRgb = $derived(getAccentRgb(globalSettings.state.themeAccent, globalSettings.state.themeFlavour)); 27 27 28 28 onMount(() => { 29 - updated.set({ icon: false }); 29 + updated.update({ icon: false }); 30 30 browser.runtime.sendMessage({ updateIcon: true }); 31 31 }); 32 32 </script>
+5 -5
src/entrypoints/popup/components/Footer.svelte
··· 18 18 <footer class="mt-4 flex min-w-full justify-around p-4"> 19 19 <span class="relative inline-flex"> 20 20 <Button 21 - onclick={async () => { 22 - await updated.set({ changelog: false }); 21 + onclick={() => { 22 + updated.update({ changelog: false }); 23 23 24 24 browser.tabs.create({ 25 25 url: `https://github.com/schooltape/schooltape/releases/tag/v${version}`, ··· 32 32 <!-- show ripple badge if the extension has been updated (unread release notes) --> 33 33 {#if updated.state.changelog} 34 34 <span class="absolute top-0 right-0 -mt-1 -mr-1 flex size-3"> 35 - <span class="absolute inline-flex h-full w-full animate-ping rounded-full bg-ctp-blue opacity-75"></span> 36 - <span class="relative inline-flex size-3 rounded-full bg-ctp-blue"></span> 35 + <span class="bg-ctp-blue absolute inline-flex h-full w-full animate-ping rounded-full opacity-75"></span> 36 + <span class="bg-ctp-blue relative inline-flex size-3 rounded-full"></span> 37 37 </span> 38 38 {/if} 39 39 </Button> ··· 52 52 onclick={() => { 53 53 window.open("https://discord.gg/rZxtGJ98BE", "_blank"); 54 54 }} 55 - ><svg class="h-[22px] fill-ctp-text" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" 55 + ><svg class="fill-ctp-text h-[22px]" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" 56 56 ><title>Discord</title><path 57 57 d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z" /></svg 58 58 ></Button>
+2 -2
src/entrypoints/popup/routes/Home.svelte
··· 14 14 <button 15 15 class="bg-(--ctp-accent) hover:opacity-75 {globalSettings.state.global ? '' : 'opacity-60'}" 16 16 id="toggle" 17 - onclick={() => { 18 - globalSettings.set({ global: !globalSettings.get().global }); 17 + onclick={async () => { 18 + globalSettings.update({ global: !(await globalSettings.get()).global }); 19 19 }} 20 20 >{globalSettings.state.global ? "enabled" : "disabled"} 21 21 </button>
+3 -3
src/entrypoints/popup/routes/Plugins.svelte
··· 22 22 title="Plugins" 23 23 checked={globalSettings.state.plugins} 24 24 update={(toggled: boolean) => { 25 - globalSettings.set({ plugins: toggled }); 25 + globalSettings.update({ plugins: toggled }); 26 26 }} /> 27 27 28 28 <div class="plugins-container"> ··· 72 72 {:else if setting.type === "slider"} 73 73 <Slider 74 74 {id} 75 - update={async (newValue) => { 76 - setting.state.set({ value: newValue }); 75 + update={(newValue) => { 76 + setting.state.update({ value: newValue }); 77 77 }} 78 78 {...setting.state.state} /> 79 79 {/if}
+10 -10
src/entrypoints/popup/routes/Snippets.svelte
··· 24 24 let sections = snippetURL.split("/"); 25 25 let key = sections[sections.length - 1].split(".")[0]; 26 26 27 - let settings = await globalSettings.storage.getValue(); 27 + let settings = await globalSettings.get(); 28 28 settings.userSnippets[key] = { 29 29 author: sections[3], 30 30 name: getMatch(data, /\/\*\s*name:\s*(.*?)\s*\*\//) || key, ··· 32 32 url: snippetURL, 33 33 toggle: true, 34 34 }; 35 - await globalSettings.storage.setValue(settings); 35 + await globalSettings.set(settings); 36 36 } 37 37 </script> 38 38 ··· 41 41 title="Snippets" 42 42 checked={globalSettings.state.snippets} 43 43 update={(toggled: boolean) => { 44 - globalSettings.set({ snippets: toggled }); 44 + globalSettings.update({ snippets: toggled }); 45 45 }} /> 46 46 47 47 <div class="snippets-container w-full"> ··· 60 60 {/each} 61 61 </div> 62 62 <div class="w-full"> 63 - <h3 class="my-4 text-ctp-text">User Snippets</h3> 64 - <p class="mb-4 text-ctp-overlay2"> 63 + <h3 class="text-ctp-text my-4">User Snippets</h3> 64 + <p class="text-ctp-overlay2 mb-4"> 65 65 To learn how to make your own snippets, please read the 66 66 <a 67 67 class="text-ctp-blue hover:underline" ··· 80 80 {id} 81 81 checked={snippet.toggle} 82 82 update={async (toggled: boolean) => { 83 - let settings = await globalSettings.storage.getValue(); 83 + let settings = await globalSettings.get(); 84 84 settings.userSnippets[id].toggle = toggled; 85 - await globalSettings.storage.setValue(settings); 85 + await globalSettings.set(settings); 86 86 }} 87 87 text={snippet.name} 88 88 description={snippet.description} ··· 90 90 <button 91 91 class="xsmall hover:bg-ctp-red hover:text-ctp-mantle" 92 92 onclick={async () => { 93 - let settings = await globalSettings.storage.getValue(); 93 + let settings = await globalSettings.get(); 94 94 delete settings.userSnippets[id]; 95 - await globalSettings.storage.setValue(settings); 95 + await globalSettings.set(settings); 96 96 }}>Remove</button> 97 97 <a href={snippet.url} target="_blank" 98 - ><button class="xsmall hover:bg-(--ctp-accent) hover:text-ctp-mantle">Gist</button></a> 98 + ><button class="xsmall hover:text-ctp-mantle hover:bg-(--ctp-accent)">Gist</button></a> 99 99 </div> 100 100 {/each} 101 101 </div>
+6 -6
src/entrypoints/popup/routes/Themes.svelte
··· 45 45 {#each Object.entries(logos) as [logoId, logo] (logoId)} 46 46 <button 47 47 onclick={() => { 48 - globalSettings.set({ themeLogo: logoId as LogoId }); 48 + globalSettings.update({ themeLogo: logoId as LogoId }); 49 49 }} 50 50 class:highlight={globalSettings.state.themeLogo === logoId} 51 51 class="flex flex-col rounded-lg border border-(--ctp-accent) p-2"> ··· 67 67 <div class="mt-4"> 68 68 <Toggle 69 69 update={(toggled) => { 70 - globalSettings.set({ themeLogoAsFavicon: toggled }); 70 + globalSettings.update({ themeLogoAsFavicon: toggled }); 71 71 }} 72 72 checked={globalSettings.state.themeLogoAsFavicon} 73 73 id="setAsFavicon" ··· 81 81 title="Themes" 82 82 checked={globalSettings.state.themes} 83 83 update={(toggled: boolean) => { 84 - globalSettings.set({ themes: toggled }); 84 + globalSettings.update({ themes: toggled }); 85 85 }} /> 86 86 87 - <div id="flavours" class="my-6 flex rounded-xl py-2 text-ctp-text"> 87 + <div id="flavours" class="text-ctp-text my-6 flex rounded-xl py-2"> 88 88 {#each flavours as flavour (flavour)} 89 89 <button 90 90 class:active={globalSettings.state.themeFlavour === flavour} ··· 92 92 class:navbutton-right={flavour === "mocha"} 93 93 class:navbutton-center={flavour === "macchiato" || flavour === "frappe"} 94 94 onclick={() => { 95 - globalSettings.set({ themeFlavour: flavour }); 95 + globalSettings.update({ themeFlavour: flavour }); 96 96 }}>{flavour}</button> 97 97 {/each} 98 98 </div> ··· 105 105 aria-label={cleanAccent(accent)} 106 106 title={cleanAccent(accent)} 107 107 onclick={() => { 108 - globalSettings.set({ themeAccent: cleanAccent(accent) }); 108 + globalSettings.update({ themeAccent: cleanAccent(accent) }); 109 109 }}></button> 110 110 {/each} 111 111 </div>
+4 -4
src/entrypoints/start.content.ts
··· 21 21 runAt: "document_start", 22 22 excludeMatches: EXCLUDE_MATCHES, 23 23 async main() { 24 - const settings = await globalSettings.storage.getValue(); 25 - const urls = (await schoolboxUrls.storage.getValue()).urls; 24 + const settings = await globalSettings.get(); 25 + const urls = (await schoolboxUrls.get()).urls; 26 26 27 27 const updateThemes: WatchCallback<Settings> = (newValue, oldValue) => { 28 28 // if global or themes was changed ··· 55 55 const uninjectThemes = () => uninjectStylesheet("themes"); 56 56 57 57 // storage listeners for hot reload 58 - globalSettings.storage.watch((newValue, oldValue) => { 58 + globalSettings.watch((newValue, oldValue) => { 59 59 updateThemes(newValue, oldValue); 60 60 updateUserSnippets(newValue, oldValue); 61 61 }); ··· 72 72 73 73 // inject user snippets 74 74 if (settings.snippets) { 75 - const userSnippets = globalSettings.get().userSnippets; 75 + const userSnippets = (await globalSettings.get()).userSnippets; 76 76 for (const [id, snippet] of Object.entries(userSnippets)) { 77 77 if (snippet.toggle) { 78 78 injectUserSnippet(id);
+4 -4
src/utils/index.ts
··· 24 24 if (style) document.head.removeChild(style); 25 25 } 26 26 27 - export function injectCatppuccin() { 28 - const settings = globalSettings.get(); 27 + export async function injectCatppuccin() { 28 + const settings = await globalSettings.get(); 29 29 const flavour = settings.themeFlavour; 30 30 const accent = settings.themeAccent; 31 31 ··· 113 113 if (link) document.head.removeChild(link); 114 114 } 115 115 116 - export function injectUserSnippet(id: string) { 116 + export async function injectUserSnippet(id: string) { 117 117 logger.info(`injecting user snippet with id ${id}`); 118 118 119 - const userSnippets = globalSettings.get().userSnippets; 119 + const userSnippets = (await globalSettings.get()).userSnippets; 120 120 const snippet = userSnippets[id]; 121 121 122 122 if (!snippet) {
+14 -16
src/utils/plugin.ts
··· 15 15 }) => Promise<void> | void, 16 16 elementsToWaitFor: string[] = [], 17 17 ) { 18 - const plugin = await plugins[pluginId].toggle.storage.getValue(); 18 + const plugin = await plugins[pluginId].toggle.get(); 19 19 let injected = false; 20 20 21 21 logger.info(`${plugins[pluginId].name}: ${plugin.toggle ? "enabled" : "disabled"}`); 22 22 23 - const settings = await globalSettings.storage.getValue(); 24 - const urls = (await schoolboxUrls.storage.getValue()).urls; 23 + const settings = await globalSettings.get(); 24 + const urls = (await schoolboxUrls.get()).urls; 25 25 26 26 if (plugin && typeof window !== "undefined" && urls.includes(window.location.origin)) { 27 27 const allElementsPresent = () => elementsToWaitFor.every((selector) => document.querySelector(selector) !== null); 28 28 29 - const inject = () => { 29 + const inject = async () => { 30 30 if (injected) return; 31 31 if (!allElementsPresent) return; 32 32 logger.info(`injecting plugin: ${plugins[pluginId].name}`); 33 - injectCallback(getSettingsValues(plugins[pluginId]?.settings)); 33 + injectCallback(await getSettingsValues(plugins[pluginId]?.settings)); 34 34 injected = true; 35 35 }; 36 36 37 - const uninject = () => { 37 + const uninject = async () => { 38 38 if (!injected) return; 39 39 logger.info(`uninjecting plugin: ${plugins[pluginId].name}`); 40 - uninjectCallback(getSettingsValues(plugins[pluginId]?.settings)); 40 + uninjectCallback(await getSettingsValues(plugins[pluginId]?.settings)); 41 41 injected = false; 42 42 }; 43 43 44 44 const initWatchers = () => { 45 45 // add watchers for injecting plugin 46 - globalSettings.storage.watch((newValue, oldValue) => { 46 + globalSettings.watch((newValue, oldValue) => { 47 47 if (hasChanged(newValue, oldValue, ["global", "plugins"])) { 48 48 if (newValue.global && newValue.plugins && plugin.toggle) { 49 49 inject(); ··· 52 52 } 53 53 } 54 54 }); 55 - plugins[pluginId].toggle.storage.watch((newValue) => { 55 + plugins[pluginId].toggle.watch((newValue) => { 56 56 if (newValue.toggle) { 57 57 inject(); 58 58 } else { ··· 63 63 // reload plugin if settings have been updated 64 64 if (plugins[pluginId].settings) { 65 65 for (const setting of Object.values(plugins[pluginId].settings)) { 66 - setting.state.storage.watch(() => { 66 + setting.state.watch(async () => { 67 67 uninject(); 68 - if (plugins[pluginId].toggle.get().toggle) inject(); 68 + if ((await plugins[pluginId].toggle.get()).toggle) inject(); 69 69 }); 70 70 } 71 71 } ··· 107 107 } 108 108 } 109 109 110 - function getSettingsValues(settings?: Record<string, PluginSetting>) { 110 + async function getSettingsValues(settings?: Record<string, PluginSetting>) { 111 111 if (!settings) return undefined; 112 112 113 113 const result: { ··· 116 116 } = { toggle: {}, slider: {} }; 117 117 for (const [key, setting] of Object.entries(settings)) { 118 118 if (setting.type === "toggle") { 119 - const value = setting.state.get(); 120 - result.toggle[key] = value.toggle; 119 + result.toggle[key] = (await setting.state.get()).toggle; 121 120 } else if (setting.type === "slider") { 122 - const value = setting.state.get(); 123 - result.slider[key] = value; 121 + result.slider[key] = await setting.state.get(); 124 122 } 125 123 } 126 124 return result;
+5 -5
src/utils/snippet.ts
··· 4 4 import { globalSettings, schoolboxUrls, snippets } from "./storage"; 5 5 6 6 export async function defineSnippet(snippetId: SnippetId, styleText: string) { 7 - const snippet = await snippets[snippetId].toggle.storage.getValue(); 7 + const snippet = await snippets[snippetId].toggle.get(); 8 8 const inject = () => { 9 9 logger.info(`injecting snippet: ${snippets[snippetId].name}`); 10 10 injectInlineStyles(styleText, `snippet-${snippetId}`); ··· 16 16 17 17 logger.info(`${snippets[snippetId].name}: ${snippet.toggle ? "enabled" : "disabled"}`); 18 18 19 - const settings = await globalSettings.storage.getValue(); 20 - const urls = (await schoolboxUrls.storage.getValue()).urls; 19 + const settings = await globalSettings.get(); 20 + const urls = (await schoolboxUrls.get()).urls; 21 21 22 22 if (snippet && typeof window !== "undefined" && urls.includes(window.location.origin)) { 23 23 if (settings.global && settings.snippets && snippet.toggle) { ··· 27 27 } 28 28 29 29 // settings watcher for uninjection/injection 30 - globalSettings.storage.watch((newValue, oldValue) => { 30 + globalSettings.watch((newValue, oldValue) => { 31 31 if (hasChanged(newValue, oldValue, ["global", "snippets"])) { 32 32 if (newValue.global && newValue.snippets && snippet.toggle) { 33 33 inject(); ··· 36 36 } 37 37 } 38 38 }); 39 - snippets[snippetId].toggle.storage.watch((newValue, oldValue) => { 39 + snippets[snippetId].toggle.watch((newValue, oldValue) => { 40 40 if (hasChanged(newValue, oldValue, ["toggle"])) { 41 41 if (newValue.toggle && settings.global && settings.snippets) { 42 42 inject();
+18 -13
src/utils/storage/state.svelte.ts
··· 1 1 import type { WxtStorageItem } from "#imports"; 2 + import { WatchCallback } from "wxt/utils/storage"; 2 3 3 4 export class StorageState<T> { 4 5 public state; 6 + private storage; 5 7 6 - constructor(public storage: WxtStorageItem<T, {}>) { 8 + constructor(storage: WxtStorageItem<T, {}>) { 7 9 this.storage = storage; 8 10 this.state = $state(this.storage.fallback); 9 11 10 - this.storage.getValue().then(this.update); 11 - this.storage.watch((newState) => this.update(newState)); 12 + this.storage.getValue().then(this.updateState); 13 + this.storage.watch((newState) => this.updateState(newState)); 12 14 } 13 15 14 - private update = (newState: T | null) => { 16 + private updateState = (newState: T | null) => { 15 17 this.state = newState ?? this.storage.fallback; 16 18 }; 17 19 18 - async set(updates: Partial<T>) { 19 - const newState = { 20 - ...(await this.storage.getValue()), 21 - ...updates, 22 - }; 20 + watch = (cb: WatchCallback<T>) => this.storage.watch(cb); 21 + 22 + get() { 23 + return this.storage.getValue(); 24 + } 23 25 24 - await this.storage.setValue(newState); 26 + set(newValue: T) { 27 + return this.storage.setValue(newValue); 25 28 } 26 29 27 - get() { 28 - this.storage.getValue().then(this.update); 29 - return $state.snapshot(this.state) as T; 30 + async update(updates: Partial<T>) { 31 + this.set({ 32 + ...(await this.get()), 33 + ...updates, 34 + }); 30 35 } 31 36 }