-
Notifications
You must be signed in to change notification settings - Fork 0
/
state.js
172 lines (159 loc) · 5.39 KB
/
state.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
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @ts-check
'use strict';
/**
* @fileoverview
* Methods for manipulating the state and the DOM of the page
*/
/** @type {HTMLFormElement} Form containing options and filters */
const form = document.getElementById('options');
/** Utilities for working with the DOM */
const dom = {
/**
* Create a document fragment from the given nodes
* @param {Iterable<Node>} nodes
* @returns {DocumentFragment}
*/
createFragment(nodes) {
const fragment = document.createDocumentFragment();
for (const node of nodes) fragment.appendChild(node);
return fragment;
},
/**
* Removes all the existing children of `parent` and inserts
* `newChild` in their place
* @param {Node} parent
* @param {Node | null} newChild
*/
replace(parent, newChild) {
while (parent.firstChild) parent.removeChild(parent.firstChild);
if (newChild != null) parent.appendChild(newChild);
},
};
/** Build utilities for working with the state. */
function _initState() {
/**
* State is represented in the query string and
* can be manipulated by this object. Keys in the query match with
* input names.
*/
let _filterParams = new URLSearchParams(location.search.slice(1));
const state = Object.freeze({
/**
* Returns a string from the current query string state.
* Can optionally restrict valid values for the query.
* Values not present in the query will return null, or the default
* value if supplied.
* @param {string} key
* @param {object} [options]
* @param {string} [options.default] Default to use if key is not present
* in the state
* @param {Set<string>} [options.valid] If provided, values must be in this
* set to be returned. Invalid values will return null or `defaultValue`.
* @returns {string | null}
*/
get(key, options = {}) {
const [val = null] = state.getAll(key, {
default: options.default ? [options.default] : null,
valid: options.valid,
});
return val;
},
/**
* Returns all string values for a key from the current query string state.
* Can optionally provide default values used if there are no values.
* @param {string} key
* @param {object} [options]
* @param {string[]} [options.default] Default to use if key is not present
* in the state.
* @param {Set<string>} [options.valid] If provided, values must be in this
* set to be returned. Invalid values will be omitted.
* @returns {string[]}
*/
getAll(key, options = {}) {
let vals = _filterParams.getAll(key);
if (options.valid != null) {
vals = vals.filter(val => options.valid.has(val));
}
if (options.default != null && vals.length === 0) {
vals = options.default;
}
return vals;
},
/**
* Checks if a key is present in the query string state.
* @param {string} key
* @returns {boolean}
*/
has(key) {
return _filterParams.has(key);
},
});
// Update form inputs to reflect the state from URL.
for (const element of form.elements) {
if (element.name) {
const input = /** @type {HTMLInputElement} */ (element);
const values = _filterParams.getAll(input.name);
const [value] = values;
if (value) {
switch (input.type) {
case 'checkbox':
input.checked = values.includes(input.value);
break;
case 'radio':
input.checked = value === input.value;
break;
default:
input.value = value;
break;
}
}
}
}
// Update the state when the form changes.
form.addEventListener('change', event => {
_filterParams = new URLSearchParams(new FormData(event.currentTarget));
history.replaceState(null, null, '?' + _filterParams.toString());
});
return state;
}
function _startListeners() {
const _SHOW_OPTIONS_STORAGE_KEY = 'show-options';
/**
* The settings dialog on the side can be toggled on and off by elements with
* a 'toggle-options' class.
*/
function _toggleOptions() {
const openedOptions = document.body.classList.toggle('show-options');
localStorage.setItem(_SHOW_OPTIONS_STORAGE_KEY, openedOptions.toString());
}
for (const button of document.getElementsByClassName('toggle-options')) {
button.addEventListener('click', _toggleOptions);
}
if (localStorage.getItem(_SHOW_OPTIONS_STORAGE_KEY) === 'true') {
document.body.classList.add('show-options');
}
/**
* Disable some fields when method_count is set
*/
function setMethodCountModeUI() {
const sizeHeader = document.getElementById('size-header');
const typesFilterContainer = document.getElementById('types-filter');
if (form.method_count.checked) {
sizeHeader.textContent = 'Methods';
form.byteunit.setAttribute('disabled', '');
typesFilterContainer.setAttribute('disabled', '');
} else {
sizeHeader.textContent = sizeHeader.dataset.value;
form.byteunit.removeAttribute('disabled', '');
typesFilterContainer.removeAttribute('disabled');
}
}
setMethodCountModeUI();
form.method_count.addEventListener('change', setMethodCountModeUI);
}
/** Utilities for working with the state */
const state = _initState();
_startListeners();