Skip to content

Commit e3ec8a6

Browse files
kartbencfriedt
authored andcommitted
doc: _extensions: allow to filter boards by compatible strings in catalog
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 67c01cb commit e3ec8a6

File tree

4 files changed

+150
-3
lines changed

4 files changed

+150
-3
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: 119 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,19 @@ function populateFormFromURL() {
5656
filterBoards();
5757
}, 0);
5858
}
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 = () => {
5972
tagElement.remove();
6073
filterBoards();
6174
};
@@ -92,6 +105,10 @@ function updateURL() {
92105
const selectedHWTags = [...document.querySelectorAll('#hwcaps-tags .tag')].map(tag => tag.textContent);
93106
selectedHWTags.length ? hashParams.set("features", selectedHWTags.join(",")) : hashParams.delete("features");
94107

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+
95112
window.history.replaceState({}, "", `#${hashParams.toString()}`);
96113
}
97114

@@ -204,6 +221,80 @@ function setupHWCapabilitiesField() {
204221
updateDatalist();
205222
}
206223

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+
tagInput.addEventListener('keydown', (e) => {
287+
if (e.key === 'Enter' && tagInput.value) {
288+
addCompatible(tagInput.value);
289+
e.preventDefault();
290+
} else if (e.key === 'Backspace' && tagInput.value === '' && selectedCompatibles.length > 0) {
291+
removeCompatible(selectedCompatibles[selectedCompatibles.length - 1]);
292+
}
293+
});
294+
295+
updateDatalist();
296+
}
297+
207298
document.addEventListener("DOMContentLoaded", function () {
208299
const form = document.querySelector(".filter-form");
209300

@@ -224,6 +315,7 @@ document.addEventListener("DOMContentLoaded", function () {
224315
populateFormFromURL();
225316

226317
setupHWCapabilitiesField();
318+
setupCompatiblesField();
227319

228320
socFamilySelect = document.getElementById("family");
229321
socFamilySelect.addEventListener("change", () => {
@@ -281,6 +373,10 @@ function resetForm() {
281373
document.querySelectorAll('#hwcaps-tags .tag').forEach(tag => tag.remove());
282374
document.getElementById('hwcaps-input').value = '';
283375

376+
// Clear compatibles
377+
document.querySelectorAll('#compatibles-tags .tag').forEach(tag => tag.remove());
378+
document.getElementById('compatibles-input').value = '';
379+
284380
filterBoards();
285381
}
286382

@@ -295,6 +391,16 @@ function updateBoardCount() {
295391
+ ` ${visibleShields.length} of ${shields.length} shields`;
296392
}
297393

394+
function wildcardMatch(pattern, str) {
395+
// Convert wildcard pattern to regex
396+
// Escape special regex characters except *
397+
const regexPattern = pattern
398+
.replace(/[.+?^${}()|[\]\\]/g, '\\$&')
399+
.replace(/\*/g, '.*');
400+
const regex = new RegExp(`^${regexPattern}$`, "i");
401+
return regex.test(str);
402+
}
403+
298404
function filterBoards() {
299405
const nameInput = document.getElementById("name").value.toLowerCase();
300406
const archSelect = document.getElementById("arch").value;
@@ -306,8 +412,11 @@ function filterBoards() {
306412
// Get selected hardware capability tags
307413
const selectedHWTags = [...document.querySelectorAll('#hwcaps-tags .tag')].map(tag => tag.textContent);
308414

415+
// Get selected compatible tags
416+
const selectedCompatibles = [...document.querySelectorAll('#compatibles-tags .tag')].map(tag => tag.textContent);
417+
309418
const resetFiltersBtn = document.getElementById("reset-filters");
310-
if (nameInput || archSelect || vendorSelect || socSocSelect.selectedOptions.length || selectedHWTags.length || !showBoards || !showShields) {
419+
if (nameInput || archSelect || vendorSelect || socSocSelect.selectedOptions.length || selectedHWTags.length || selectedCompatibles.length || !showBoards || !showShields) {
311420
resetFiltersBtn.classList.remove("btn-disabled");
312421
} else {
313422
resetFiltersBtn.classList.add("btn-disabled");
@@ -321,6 +430,7 @@ function filterBoards() {
321430
const boardVendor = board.getAttribute("data-vendor") || "";
322431
const boardSocs = (board.getAttribute("data-socs") || "").split(" ").filter(Boolean);
323432
const boardSupportedFeatures = (board.getAttribute("data-supported-features") || "").split(" ").filter(Boolean);
433+
const boardCompatibles = (board.getAttribute("data-compatibles") || "").split(" ").filter(Boolean);
324434
const isShield = board.classList.contains("shield");
325435

326436
let matches = true;
@@ -330,12 +440,19 @@ function filterBoards() {
330440
if ((isShield && !showShields) || (!isShield && !showBoards)) {
331441
matches = false;
332442
} else {
443+
// Check if board matches all selected compatibles (with wildcard support)
444+
const compatiblesMatch = selectedCompatibles.length === 0 ||
445+
selectedCompatibles.every((pattern) =>
446+
boardCompatibles.some((compatible) => wildcardMatch(pattern, compatible))
447+
);
448+
333449
matches =
334450
!(nameInput && !boardName.includes(nameInput)) &&
335451
!(archSelect && !boardArchs.includes(archSelect)) &&
336452
!(vendorSelect && boardVendor !== vendorSelect) &&
337453
(selectedSocs.length === 0 || selectedSocs.some((soc) => boardSocs.includes(soc))) &&
338-
(selectedHWTags.length === 0 || selectedHWTags.every((tag) => boardSupportedFeatures.includes(tag)));
454+
(selectedHWTags.length === 0 || selectedHWTags.every((tag) => boardSupportedFeatures.includes(tag))) &&
455+
compatiblesMatch;
339456
}
340457

341458
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: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,20 @@
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="{% if hw_features_present -%}
97+
Type a compatible, e.g. &quot;ti,hdc2080&quot; or wildcard like &quot;bosch,bmp*&quot;
98+
{%- else -%}
99+
List of supported devices is not available
100+
{%- endif %}"
101+
{% if not hw_features_present %}disabled{% endif %}>
102+
<datalist id="compatibles-list"></datalist>
103+
</div>
104+
</div>
105+
92106
</form>
93107

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

0 commit comments

Comments
 (0)