-
Notifications
You must be signed in to change notification settings - Fork 0
/
user-preferences.js
183 lines (171 loc) · 6.81 KB
/
user-preferences.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
/**
* @template T Preference value type
* @typedef {object} Preference
* @property {T} default The default value of the preference if none is set
* @property {T} current The current value of the preference
* @property {function(T[], T): ?T} findBestOption A method that chooses the option closest to the user preference given the available options
* @property {function(T): boolean} allowSavePref Whether the given value is allowed to be stored
*/
/**
* @typedef {object} UserPreferences
* @property {Object<string, Preference>} prefs
*/
/**
* Class for saving, loading and applying user preferences.
* @class
* @returns {UserPreferences} The newly created instance
*/
function UserPreferences() {
this.prefs = {
architecture: {
default: "aarch64",
current: null,
// Find the closest match for the available options that matches this preference's value
findBestOption: (options, preferredValue) => {
console.log("findBestOption: ", options, preferredValue)
if (options.indexOf(preferredValue) >= 0) {
// Check if our preferred architecture is available
return preferredValue;
} else if (options.indexOf("noarch") >= 0) {
// Otherwise, noarch has preference
return "noarch";
} else if (options.length > 0) {
// Just pick the first I suppose
return options[0];
}
return undefined;
},
allowSavePref: function(value) {
// noarch is not a valid preferred architecture :p.
// Packages have either multiple architecture or a "noarch". To prevent users from
// accidentally setting their preferred architecture to "noarch" when visiting a package
// that is architecture independent.
return value !== "noarch";
}
}
}
}
/**
* Loads all preferences from browser local storage or populates them with default values
*/
UserPreferences.prototype.loadPrefs = function() {
for (const pref in this.prefs) {
if (this.prefs.hasOwnProperty(pref)) {
this.prefs[pref].current = localStorage.getItem("pref_" + pref) ?? this.prefs[pref].default;
}
}
}
/**
* Gets a preference by name
* @template T Preference value type
* @param {string} prefName The name of the preference to get
* @param {T[]} options Optionally, a set of options to choose from. The best option according to the current preference will
* be picked
* @returns The chosen preference. If the options parameter is supplied, it will be one of the supplied options, else it
* will be the preferred option.
*/
UserPreferences.prototype.getPref = function(prefName, options=[]) {
if (options.length > 0) {
return this.prefs[prefName].findBestOption(options, this.getPref(prefName))
} else {
return this.prefs[prefName].current ?? this.prefs[prefName].default;
}
}
/**
* Stores a preference. Does not update the UI
* @template T Preference value type
* @param {string} prefName The name of the preference to save
* @param {T} prefValue The value of the preference to save
*/
UserPreferences.prototype.setPref = function(prefName, prefValue) {
this.prefs[prefName].current = prefValue;
if ("allowSavePref" in this.prefs[prefName]) {
if (this.prefs[prefName].allowSavePref(prefValue)) {
window.localStorage.setItem("pref_" + prefName, prefValue);
}
} else {
window.localStorage.setItem("pref_" + prefName, prefValue);
}
}
/**
* Applies the preference with the given value, without saving it (see `UserPreference.setPref` for saving)
*
* This currently does two things:
*
* 1. Sets a `${prefName}--${prefValue} class on the body element and removes old ones
* 2. Shows and hides elements with the `data-relevant-${prefName}` attribute matching or not matching the value respectively
*
* @template T Preference value type
* @param {string} prefName Name of the preference to save
* @param {T} prefValue Value of the preference to save
*/
UserPreferences.prototype.applyPreference = function(prefName, prefValue) {
let classesToRemove = [];
for (const clazz of document.body.classList) {
if (clazz.startsWith(prefName + "--")) {
classesToRemove.push(clazz);
}
}
if (classesToRemove.length > 0) {
document.body.classList.remove(classesToRemove);
}
document.body.classList.add(prefName + "--" + prefValue);
if (prefValue) {
// Show and hide elements with data-relevant-${prefName} values matching/not matching the current prefValue respectively
let showElements = document.querySelectorAll(`*[data-relevant-${prefName}=${prefValue}]`);
for (const el of showElements) {
el.style.display = null;
}
}
let hideElements = [];
if (prefValue) {
hideElements = document.querySelectorAll( `*[data-relevant-${prefName}]:not([data-relevant-${prefName}=${prefValue}])`);
} else {
hideElements = document.querySelectorAll( `*[data-relevant-${prefName}]`);
}
for (const el of hideElements) {
el.style.display = "none";
}
}
/**
* Adds "change" listeners to input elements which control user preferences to update them
*/
UserPreferences.prototype.addInputListeners = function() {
let self = this;
for (const el of document.querySelectorAll("*[data-pref-id]")) {
const prefName = el.dataset.prefId;
if (el instanceof HTMLSelectElement) {
el.addEventListener("change", (e) => {
const prefValue = e.target.selectedOptions[0].value;
self.setPref(prefName, prefValue);
self.applyPreference(prefName, prefValue)
});
}
}
}
/**
* Update the values of all input elements that control user preferences with the current value
*/
UserPreferences.prototype.updatePreferenceInputs = function() {
// Find all elements with data-pref-id set, and update the values to match the user's preference
for (const el of document.querySelectorAll("*[data-pref-id]")) {
let pref = el.dataset.prefId
if (pref in this.prefs) {
// <select> … </select> element
if (el instanceof HTMLSelectElement) {
let options = [];
for (const option of el.options) {
options.push(option.value);
}
el.value = this.getPref(pref, options);
this.applyPreference(pref, el.value);
}
}
}
}
window.user_prefs = new UserPreferences();
user_prefs.loadPrefs();
document.addEventListener("DOMContentLoaded", () => {
user_prefs.addInputListeners();
user_prefs.updatePreferenceInputs();
})