Skip to content

Commit 4b3df86

Browse files
committed
doc: _extensions: allow to filter boards by compatible strings
This adds the ability to filter boards in the catalog by compatible strings. It supports wildcards so e.g. one can quickly find all boards with an "st,lsm*" accelerometer. Signed-off-by: Benjamin Cabé <[email protected]>
1 parent 8f5fe75 commit 4b3df86

File tree

4 files changed

+172
-17
lines changed

4 files changed

+172
-17
lines changed

doc/_extensions/zephyr/domain/static/css/board-catalog.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,10 @@
106106
margin-right: 8px;
107107
}
108108

109+
#compatibles-tags .tag {
110+
font-family: var(--monospace-font-family);
111+
}
112+
109113
.tag:hover {
110114
background-color: #0056b3;
111115
}

doc/_extensions/zephyr/domain/static/js/board-catalog.js

Lines changed: 142 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,34 @@ function populateFormFromURL() {
4141
const features = hashParams.get("features").split(",");
4242
setTimeout(() => {
4343
features.forEach(feature => {
44-
const tagContainer = document.getElementById('tag-container');
45-
const tagInput = document.getElementById('tag-input');
44+
const tagContainer = document.getElementById('hwcaps-tags');
45+
const tagInput = document.getElementById('hwcaps-input');
4646

4747
const tagElement = document.createElement('span');
4848
tagElement.classList.add('tag');
4949
tagElement.textContent = feature;
5050
tagElement.onclick = () => {
51-
const selectedTags = [...document.querySelectorAll('.tag')].map(tag => tag.textContent);
52-
selectedTags.splice(selectedTags.indexOf(feature), 1);
51+
tagElement.remove();
52+
filterBoards();
53+
};
54+
tagContainer.insertBefore(tagElement, tagInput);
55+
});
56+
filterBoards();
57+
}, 0);
58+
}
59+
60+
// Restore compatibles from URL
61+
if (hashParams.has("compatibles")) {
62+
const compatibles = hashParams.get("compatibles").split("|");
63+
setTimeout(() => {
64+
compatibles.forEach(compatible => {
65+
const tagContainer = document.getElementById('compatibles-tags');
66+
const tagInput = document.getElementById('compatibles-input');
67+
68+
const tagElement = document.createElement('span');
69+
tagElement.classList.add('tag');
70+
tagElement.textContent = compatible;
71+
tagElement.onclick = () => {
5372
tagElement.remove();
5473
filterBoards();
5574
};
@@ -83,9 +102,13 @@ function updateURL() {
83102
});
84103

85104
// Add supported features to URL
86-
const selectedTags = [...document.querySelectorAll('.tag')].map(tag => tag.textContent);
105+
const selectedTags = [...document.querySelectorAll('#hwcaps-tags .tag')].map(tag => tag.textContent);
87106
selectedTags.length ? hashParams.set("features", selectedTags.join(",")) : hashParams.delete("features");
88107

108+
// Add compatibles to URL
109+
const selectedCompatibles = [...document.querySelectorAll('#compatibles-tags .tag')].map(tag => tag.textContent);
110+
selectedCompatibles.length ? hashParams.set("compatibles", selectedCompatibles.join("|")) : hashParams.delete("compatibles");
111+
89112
window.history.replaceState({}, "", `#${hashParams.toString()}`);
90113
}
91114

@@ -126,8 +149,8 @@ function fillSocSocSelect(families, series = undefined, selectOnFill = false) {
126149
function setupHWCapabilitiesField() {
127150
let selectedTags = [];
128151

129-
const tagContainer = document.getElementById('tag-container');
130-
const tagInput = document.getElementById('tag-input');
152+
const tagContainer = document.getElementById('hwcaps-tags');
153+
const tagInput = document.getElementById('hwcaps-input');
131154
const datalist = document.getElementById('tag-list');
132155

133156
const tagCounts = Array.from(document.querySelectorAll('.board-card')).reduce((acc, board) => {
@@ -198,6 +221,86 @@ function setupHWCapabilitiesField() {
198221
updateDatalist();
199222
}
200223

224+
function setupCompatiblesField() {
225+
let selectedCompatibles = [];
226+
227+
const tagContainer = document.getElementById('compatibles-tags');
228+
const tagInput = document.getElementById('compatibles-input');
229+
const datalist = document.getElementById('compatibles-list');
230+
231+
// Collect all unique compatibles from boards
232+
const allCompatibles = Array.from(document.querySelectorAll('.board-card')).reduce((acc, board) => {
233+
(board.getAttribute('data-compatibles') || '').split(' ').forEach(compat => {
234+
if (compat && !acc.includes(compat)) {
235+
acc.push(compat);
236+
}
237+
});
238+
return acc;
239+
}, []);
240+
241+
allCompatibles.sort();
242+
243+
function addCompatible(compatible) {
244+
if (selectedCompatibles.includes(compatible) || compatible === "") return;
245+
selectedCompatibles.push(compatible);
246+
247+
const tagElement = document.createElement('span');
248+
tagElement.classList.add('tag');
249+
tagElement.textContent = compatible;
250+
tagElement.onclick = () => removeCompatible(compatible);
251+
tagContainer.insertBefore(tagElement, tagInput);
252+
253+
tagInput.value = '';
254+
updateDatalist();
255+
}
256+
257+
function removeCompatible(compatible) {
258+
selectedCompatibles = selectedCompatibles.filter(c => c !== compatible);
259+
document.querySelectorAll('.tag').forEach(el => {
260+
if (el.textContent === compatible && el.parentElement === tagContainer) {
261+
el.remove();
262+
}
263+
});
264+
updateDatalist();
265+
}
266+
267+
function updateDatalist() {
268+
datalist.innerHTML = '';
269+
const filteredCompatibles = allCompatibles.filter(c => !selectedCompatibles.includes(c));
270+
271+
filteredCompatibles.forEach(compatible => {
272+
const option = document.createElement('option');
273+
option.value = compatible;
274+
datalist.appendChild(option);
275+
});
276+
277+
filterBoards();
278+
}
279+
280+
tagInput.addEventListener('input', () => {
281+
if (allCompatibles.includes(tagInput.value)) {
282+
addCompatible(tagInput.value);
283+
}
284+
});
285+
286+
// Add compatible when pressing the Enter key (supports wildcards)
287+
tagInput.addEventListener('keydown', (e) => {
288+
if (e.key === 'Enter' && tagInput.value) {
289+
addCompatible(tagInput.value);
290+
e.preventDefault();
291+
}
292+
});
293+
294+
// Delete compatible when pressing the Backspace key
295+
tagInput.addEventListener('keydown', (e) => {
296+
if (e.key === 'Backspace' && tagInput.value === '' && selectedCompatibles.length > 0) {
297+
removeCompatible(selectedCompatibles[selectedCompatibles.length - 1]);
298+
}
299+
});
300+
301+
updateDatalist();
302+
}
303+
201304
document.addEventListener("DOMContentLoaded", function () {
202305
const form = document.querySelector(".filter-form");
203306

@@ -218,6 +321,7 @@ document.addEventListener("DOMContentLoaded", function () {
218321
populateFormFromURL();
219322

220323
setupHWCapabilitiesField();
324+
setupCompatiblesField();
221325

222326
socFamilySelect = document.getElementById("family");
223327
socFamilySelect.addEventListener("change", () => {
@@ -272,8 +376,12 @@ function resetForm() {
272376
document.getElementById("show-shields").checked = true;
273377

274378
// Clear supported features
275-
document.querySelectorAll('.tag').forEach(tag => tag.remove());
276-
document.getElementById('tag-input').value = '';
379+
document.querySelectorAll('#hwcaps-tags .tag').forEach(tag => tag.remove());
380+
document.getElementById('hwcaps-input').value = '';
381+
382+
// Clear compatibles
383+
document.querySelectorAll('#compatibles-tags .tag').forEach(tag => tag.remove());
384+
document.getElementById('compatibles-input').value = '';
277385

278386
filterBoards();
279387
}
@@ -289,6 +397,16 @@ function updateBoardCount() {
289397
+ ` ${visibleShields.length} of ${shields.length} shields`;
290398
}
291399

400+
function wildcardMatch(pattern, str) {
401+
// Convert wildcard pattern to regex
402+
// Escape special regex characters except *
403+
const regexPattern = pattern
404+
.replace(/[.+?^${}()|[\]\\]/g, '\\$&')
405+
.replace(/\*/g, '.*');
406+
const regex = new RegExp(`^${regexPattern}$`);
407+
return regex.test(str);
408+
}
409+
292410
function filterBoards() {
293411
const nameInput = document.getElementById("name").value.toLowerCase();
294412
const archSelect = document.getElementById("arch").value;
@@ -297,10 +415,14 @@ function filterBoards() {
297415
const showBoards = document.getElementById("show-boards").checked;
298416
const showShields = document.getElementById("show-shields").checked;
299417

300-
const selectedTags = [...document.querySelectorAll('.tag')].map(tag => tag.textContent);
418+
// Get selected hardware capability tags
419+
const selectedTags = [...document.querySelectorAll('#hwcaps-tags .tag')].map(tag => tag.textContent);
420+
421+
// Get selected compatible tags
422+
const selectedCompatibles = [...document.querySelectorAll('#compatibles-tags .tag')].map(tag => tag.textContent);
301423

302424
const resetFiltersBtn = document.getElementById("reset-filters");
303-
if (nameInput || archSelect || vendorSelect || socSocSelect.selectedOptions.length || selectedTags.length || !showBoards || !showShields) {
425+
if (nameInput || archSelect || vendorSelect || socSocSelect.selectedOptions.length || selectedTags.length || selectedCompatibles.length || !showBoards || !showShields) {
304426
resetFiltersBtn.classList.remove("btn-disabled");
305427
} else {
306428
resetFiltersBtn.classList.add("btn-disabled");
@@ -314,6 +436,7 @@ function filterBoards() {
314436
const boardVendor = board.getAttribute("data-vendor") || "";
315437
const boardSocs = (board.getAttribute("data-socs") || "").split(" ").filter(Boolean);
316438
const boardSupportedFeatures = (board.getAttribute("data-supported-features") || "").split(" ").filter(Boolean);
439+
const boardCompatibles = (board.getAttribute("data-compatibles") || "").split(" ").filter(Boolean);
317440
const isShield = board.classList.contains("shield");
318441

319442
let matches = true;
@@ -323,12 +446,19 @@ function filterBoards() {
323446
if ((isShield && !showShields) || (!isShield && !showBoards)) {
324447
matches = false;
325448
} else {
449+
// Check if board matches all selected compatibles (with wildcard support)
450+
const compatiblesMatch = selectedCompatibles.length === 0 ||
451+
selectedCompatibles.every((pattern) =>
452+
boardCompatibles.some((compatible) => wildcardMatch(pattern, compatible))
453+
);
454+
326455
matches =
327456
!(nameInput && !boardName.includes(nameInput)) &&
328457
!(archSelect && !boardArchs.includes(archSelect)) &&
329458
!(vendorSelect && boardVendor !== vendorSelect) &&
330459
(selectedSocs.length === 0 || selectedSocs.some((soc) => boardSocs.includes(soc))) &&
331-
(selectedTags.length === 0 || selectedTags.every((tag) => boardSupportedFeatures.includes(tag)));
460+
(selectedTags.length === 0 || selectedTags.every((tag) => boardSupportedFeatures.includes(tag))) &&
461+
compatiblesMatch;
332462
}
333463

334464
board.classList.toggle("hidden", !matches);

doc/_extensions/zephyr/domain/templates/board-card.html

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,19 @@
2525
{%- endfor -%}
2626
{%- endfor -%}
2727
{{- feature_types|join(' ') -}}
28-
" tabindex="0">
28+
"
29+
data-compatibles="
30+
{%- set all_compatibles = [] -%}
31+
{%- for target_compatibles in board.compatibles.values() -%}
32+
{%- for compatible in target_compatibles -%}
33+
{%- if compatible not in all_compatibles -%}
34+
{%- set _ = all_compatibles.append(compatible) -%}
35+
{%- endif -%}
36+
{%- endfor -%}
37+
{%- endfor -%}
38+
{{- all_compatibles|join(' ') -}}
39+
"
40+
tabindex="0">
2941
<div class="vendor">{{ vendors[board.vendor] }}</div>
3042
{% if board.image -%}
3143
<img alt="A picture of the {{ board.full_name }} board"

doc/_extensions/zephyr/domain/templates/board-catalog.html

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,11 @@
7676
</div>
7777

7878
<div class="form-group" style="flex-basis: 100%">
79-
<label for="tag-input">Supported Hardware Capabilities</label>
80-
<div class="tag-container" id="tag-container">
81-
<input list="tag-list" class="tag-input" id="tag-input"
79+
<label for="hwcaps-input">Supported Hardware Capabilities</label>
80+
<div class="tag-container" id="hwcaps-tags">
81+
<input list="tag-list" class="tag-input" id="hwcaps-input"
8282
placeholder="{% if hw_features_present -%}
83-
Type a tag...
83+
Type a hardware capability tag...
8484
{%- else -%}
8585
List of supported hardware capabilities is not available
8686
{%- endif %}"
@@ -89,6 +89,15 @@
8989
</div>
9090
</div>
9191

92+
<div class="form-group" style="flex-basis: 100%">
93+
<label for="compatibles-input">Supported devices (compatible strings, supports wildcards)</label>
94+
<div class="tag-container" id="compatibles-tags">
95+
<input list="compatibles-list" class="tag-input" id="compatibles-input"
96+
placeholder="Type a compatible, e.g. 'ti,hdc2080' or wildcard like 'ti,*'">
97+
<datalist id="compatibles-list"></datalist>
98+
</div>
99+
</div>
100+
92101
</form>
93102

94103
<div id="form-options" style="text-align: center; margin-bottom: 20px">

0 commit comments

Comments
 (0)