A rewrite of Poly+, my quality-of-life browser extension for Polytoria. Built entirely fresh using the WXT extension framework, Typescript, and with added better overall code quality.
extension
1import { preferences, defaultPreferences, preferencesSchema } from "@/utils/storage"; 2import data from "@/public/preferences.json"; 3let values: preferencesSchema; 4 5type Tags = "utility" | "social" | "economy" | "development" | "customization"; 6 7type SettingData = { 8 name: string, 9 desc: string, 10 setting: string, 11 notes: Array<string>, 12 config?: Array<{ 13 type: "select" | "check", 14 subsetting: string, 15 label?: string, 16 values?: Record<string, any> 17 }>, 18 tags: Array<Tags> 19}; 20 21export default defineUnlistedScript(() => { 22 for (const src of [ 23 "/css/polytoria.css", 24 "/css/preferences.css" 25 ]) { 26 const css = browser.runtime.getURL(src as any); 27 const link = document.createElement('link'); 28 link.rel = 'stylesheet'; 29 link.href = css; 30 document.head.appendChild(link); 31 }; 32 33 console.log('Static Settings Data:' , data); 34 const saveBtn = document.getElementById('save')!; 35 36 preferences.getPreferences() 37 .then(async (preferenceValues) => { 38 values = preferenceValues; 39 console.log('Loaded preferences: ', preferenceValues); 40 41 for (const _ of data as SettingData[]) { 42 const container = await createContainer(_); 43 44 container.getElementsByClassName('toggle-btn')[0].addEventListener('click', async () => { 45 saveBtn.removeAttribute('disabled'); 46 47 const state = !values[_.setting].enabled; 48 values[_.setting].enabled = state; 49 updateState(container, state); 50 }); 51 }; 52 }); 53 54 saveBtn.addEventListener('click', async () => { 55 saveBtn.setAttribute('disabled', 'true'); 56 preferences.setValue(values); 57 }); 58}); 59 60async function createContainer(data: SettingData): Promise<HTMLDivElement> { 61 const noteClasses = { 62 "?": "note", 63 "!": "warning", 64 "-": "secondary" 65 }; 66 67 const state = await getState(data.setting); 68 const container = document.createElement('div'); 69 70 container.classList.add('setting-container'); 71 container.classList.add(state ? 'enabled' : 'disabled'); 72 73 container.innerHTML = ` 74 <span class="indicator">&nbsp;</span> 75 <span class="title"> 76 ${data.name} 77 </span> 78 <div class="setting-buttons"> 79 <button class="btn btn-sm toggle-btn ${ state ? 'btn-danger' : 'btn-success' }">Toggle</button> 80 </div> 81 <br /> 82 <span class="desc">${data.desc}</span> 83 ${(data.notes || []).map((note) => { 84 const noteType = note.charAt(0) as keyof typeof noteClasses; 85 return `<span class="${ noteClasses[noteType] }">* ${ note.slice(1) }</span>`; 86 }).join('')} 87 `; 88 89 document.getElementById('settings')!.appendChild(container); 90 if (data.config) createConfig(container, data); 91 return container; 92} 93 94function createConfig(div: HTMLDivElement, data: SettingData) { 95 for (const subsetting of data.config!) { 96 if (subsetting.type === 'select') { 97 const select = document.createElement('select'); 98 select.classList.add('form-select', 'form-select-sm', 'mb-2'); 99 select.setAttribute('style', 'width: 350px;'); 100 101 for (const [key, value] of Object.entries(subsetting.values!)) { 102 const option = document.createElement('option'); 103 option.value = key; 104 option.innerText = value; 105 select.appendChild(option); 106 }; 107 108 div.appendChild(select); 109 } else if (subsetting.type === 'check') { 110 const check = document.createElement('span'); 111 112 check.classList.add('form-check', 'form-switch'); 113 check.innerHTML = ` 114 <input class="form-check-input" type="checkbox" role="switch" id="${subsetting.subsetting}" /> 115 <label class="form-check-label" for="${subsetting.subsetting}"> 116 ${subsetting.label} 117 </label> 118 `; 119 120 check.getElementsByClassName('form-check-input')[0].addEventListener('change', () => { 121 document.getElementById('save')!.removeAttribute('disabled'); 122 123 const state = !values[data.setting][subsetting.subsetting]; 124 values[data.setting][subsetting.subsetting] = state; 125 }); 126 127 div.appendChild(check); 128 } 129 } 130} 131 132function updateState(div: HTMLDivElement, state: boolean) { 133 const toggleBtn = div.getElementsByClassName('toggle-btn')[0]; 134 135 div.classList.toggle('enabled', state); 136 div.classList.toggle('disabled', !state); 137 138 toggleBtn.classList.toggle('btn-success', !state); 139 toggleBtn.classList.toggle('btn-danger', state); 140} 141 142async function getState(name: string): Promise<boolean | null> { 143 const state = values[name] ?? (defaultPreferences as preferencesSchema)[name]; 144 if (!state) return null; 145 return state.enabled; 146}