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/json/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 console.log('Static Settings Data:' , data); 23 const saveBtn = document.getElementById('save')!; 24 25 preferences.getValue() 26 .then(async (preferenceValues) => { 27 values = preferenceValues; 28 29 for (const _ of data as SettingData[]) { 30 const container = await createContainer(_); 31 32 container.getElementsByClassName('toggle-btn')[0].addEventListener('click', async () => { 33 saveBtn.removeAttribute('disabled'); 34 35 const state = !values[_.setting].enabled; 36 values[_.setting].enabled = state; 37 updateState(container, state); 38 }); 39 }; 40 }); 41 42 saveBtn.addEventListener('click', async () => { 43 saveBtn.setAttribute('disabled', 'true'); 44 preferences.setValue(values); 45 }); 46}); 47 48async function createContainer(data: SettingData): Promise<HTMLDivElement> { 49 const noteClasses = { 50 "?": "note", 51 "!": "warning", 52 "-": "secondary" 53 }; 54 55 const state = await getState(data.setting); 56 const container = document.createElement('div'); 57 58 container.classList.add('setting-container'); 59 container.classList.add(state ? 'enabled' : 'disabled'); 60 61 container.innerHTML = ` 62 <span class="indicator">&nbsp;</span> 63 <span class="title"> 64 ${data.name} 65 </span> 66 <div class="setting-buttons"> 67 <button class="btn btn-sm toggle-btn ${ state ? 'btn-danger' : 'btn-success' }">Toggle</button> 68 </div> 69 <br /> 70 <span class="desc">${data.desc}</span> 71 ${(data.notes || []).map((note) => { 72 const noteType = note.charAt(0) as keyof typeof noteClasses; 73 return `<span class="${ noteClasses[noteType] }">* ${ note.slice(1) }</span>`; 74 }).join('')} 75 `; 76 77 document.getElementById('settings')!.appendChild(container); 78 if (data.config) createConfig(container, data); 79 return container; 80} 81 82function createConfig(div: HTMLDivElement, data: SettingData) { 83 for (const subsetting of data.config!) { 84 if (subsetting.type === 'select') { 85 const select = document.createElement('select'); 86 select.classList.add('form-select', 'form-select-sm', 'mb-2'); 87 select.setAttribute('style', 'width: 350px;'); 88 89 for (const [key, value] of Object.entries(subsetting.values!)) { 90 const option = document.createElement('option'); 91 option.value = key; 92 option.innerText = value; 93 select.appendChild(option); 94 }; 95 96 div.appendChild(select); 97 } else if (subsetting.type === 'check') { 98 const check = document.createElement('span'); 99 100 check.classList.add('form-check', 'form-switch'); 101 check.innerHTML = ` 102 <input class="form-check-input" type="checkbox" role="switch" id="${subsetting.subsetting}" /> 103 <label class="form-check-label" for="${subsetting.subsetting}"> 104 ${subsetting.label} 105 </label> 106 `; 107 108 check.getElementsByClassName('form-check-input')[0].addEventListener('change', () => { 109 document.getElementById('save')!.removeAttribute('disabled'); 110 111 const state = !values[data.setting][subsetting.subsetting]; 112 values[data.setting][subsetting.subsetting] = state; 113 }); 114 115 div.appendChild(check); 116 } 117 } 118} 119 120function updateState(div: HTMLDivElement, state: boolean) { 121 const toggleBtn = div.getElementsByClassName('toggle-btn')[0]; 122 123 div.classList.toggle('enabled', state); 124 div.classList.toggle('disabled', !state); 125 126 toggleBtn.classList.toggle('btn-success', !state); 127 toggleBtn.classList.toggle('btn-danger', state); 128} 129 130async function getState(name: string): Promise<boolean | null> { 131 const state = values[name] ?? (defaultPreferences as preferencesSchema)[name]; 132 if (!state) return null; 133 return state.enabled; 134}