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"> </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}