diff --git a/docs/.vuepress/components/BoardEnvironmentTable.vue b/docs/.vuepress/components/BoardEnvironmentTable.vue
index af5e4fe..4bb205c 100644
--- a/docs/.vuepress/components/BoardEnvironmentTable.vue
+++ b/docs/.vuepress/components/BoardEnvironmentTable.vue
@@ -12,15 +12,75 @@
{{ error }}
-
+
+
+
+
+
+
+
+
+
+
+
+
MCU:
+
+
+
+
+
+
Gateway:
+
+
+
+
+
+
+
+ Showing {{ filteredBoards.length }} of {{ boards.length }} boards
+
+
+
+
+
+
+
No boards match your filters.
+
+
+
+
-
![]()
Popular
+
![]()
@@ -98,6 +158,7 @@
+
@@ -121,7 +182,17 @@ export default {
loading: false,
error: null,
expandedModules: {},
- expandedLibraries: {}
+ expandedLibraries: {},
+ searchQuery: '',
+ activeMcuFilter: null,
+ activeGatewayFilter: null,
+ popularEnvironments: [
+ 'esp32dev-ble',
+ 'theengs-bridge-v11',
+ 'lilygo-rtl_433',
+ 'lilygo-rtl_433-fsk',
+ 'esp32dev-ir'
+ ]
};
},
computed: {
@@ -130,9 +201,89 @@ export default {
},
resolvedSelectorUrl() {
return this.buildUrl(this.selectorPath);
+ },
+ mcuFilters() {
+ var families = {};
+ this.boards.forEach(function(b) {
+ var family = this.getMcuFamily(b.microcontroller);
+ if (family) families[family] = true;
+ }.bind(this));
+ return Object.keys(families).sort();
+ },
+ gatewayFilters() {
+ var gateways = {
+ BT: 'BLE',
+ RF: 'RF',
+ IR: 'IR',
+ LORA: 'LoRa',
+ RTL_433: 'RTL_433',
+ Pilight: 'Pilight'
+ };
+ var result = [];
+ var self = this;
+ Object.keys(gateways).forEach(function(key) {
+ var hasBoards = self.boards.some(function(b) {
+ return Array.isArray(b.modules) && b.modules.some(function(m) {
+ return m.toLowerCase().indexOf(key.toLowerCase()) !== -1;
+ });
+ });
+ if (hasBoards) {
+ result.push({ key: key, label: gateways[key] });
+ }
+ });
+ return result;
+ },
+ hasActiveFilters() {
+ return this.searchQuery || this.activeMcuFilter || this.activeGatewayFilter;
+ },
+ filteredBoards() {
+ var self = this;
+ return this.boards.filter(function(board) {
+ if (self.searchQuery) {
+ var q = self.searchQuery.toLowerCase();
+ var haystack = [
+ board.environment,
+ board.description || '',
+ board.microcontroller || '',
+ (board.modules || []).join(' ')
+ ].join(' ').toLowerCase();
+ if (haystack.indexOf(q) === -1) return false;
+ }
+ if (self.activeMcuFilter) {
+ var family = self.getMcuFamily(board.microcontroller);
+ if (family !== self.activeMcuFilter) return false;
+ }
+ if (self.activeGatewayFilter) {
+ var key = self.activeGatewayFilter.toLowerCase();
+ if (!Array.isArray(board.modules) || !board.modules.some(function(m) {
+ return m.toLowerCase().indexOf(key) !== -1;
+ })) return false;
+ }
+ return true;
+ });
}
},
methods: {
+ getMcuFamily(mcu) {
+ if (!mcu) return null;
+ var m = mcu.toLowerCase();
+ if (m.indexOf('esp32-s3') !== -1 || m.indexOf('esp32s3') !== -1 || m.indexOf('atoms3') !== -1 || m.indexOf('lilygo-t3-s3') !== -1) return 'ESP32-S3';
+ if (m.indexOf('esp32-c3') !== -1 || m.indexOf('esp32c3') !== -1 || m.indexOf('lolin_c3') !== -1 || m.indexOf('airm2m') !== -1) return 'ESP32-C3';
+ if (m.indexOf('esp32') !== -1 || m.indexOf('m5st') !== -1 || m.indexOf('heltec') !== -1 || m.indexOf('ttgo') !== -1 || m.indexOf('lolin32') !== -1 || m.indexOf('pico32') !== -1 || m.indexOf('tinypico') !== -1 || m.indexOf('feather') !== -1) return 'ESP32';
+ if (m.indexOf('esp8') !== -1 || m.indexOf('nodemcu') !== -1) return 'ESP8266';
+ return 'Other';
+ },
+ toggleMcuFilter(mcu) {
+ this.activeMcuFilter = this.activeMcuFilter === mcu ? null : mcu;
+ },
+ toggleGatewayFilter(gw) {
+ this.activeGatewayFilter = this.activeGatewayFilter === gw ? null : gw;
+ },
+ clearFilters() {
+ this.searchQuery = '';
+ this.activeMcuFilter = null;
+ this.activeGatewayFilter = null;
+ },
buildUrl(path) {
const base = this.$site?.base || '/';
const cleanBase = base.endsWith('/') ? base.slice(0, -1) : base;
@@ -151,9 +302,16 @@ export default {
if (!Array.isArray(data)) {
throw new Error('boards-info.json must be an array');
}
+ var self = this;
this.boards = data
.filter(board => board && typeof board.environment === 'string')
- .sort((a, b) => a.environment.localeCompare(b.environment));
+ .sort(function(a, b) {
+ var aPopular = self.popularEnvironments.indexOf(a.environment) !== -1;
+ var bPopular = self.popularEnvironments.indexOf(b.environment) !== -1;
+ if (aPopular && !bPopular) return -1;
+ if (!aPopular && bPopular) return 1;
+ return a.environment.localeCompare(b.environment);
+ });
} catch (err) {
console.error('Failed to load boards-info:', err);
this.error = err.message || 'Unable to load board information';
@@ -196,10 +354,154 @@ export default {
margin: 2rem 0;
}
+/* Search and Filter Bar */
+.filter-bar {
+ margin-bottom: 1.5rem;
+ display: flex;
+ flex-direction: column;
+ gap: 0.75rem;
+}
+
+.search-box {
+ position: relative;
+ display: flex;
+ align-items: center;
+}
+
+.search-icon {
+ position: absolute;
+ left: 12px;
+ color: var(--text-color-secondary, #666);
+ pointer-events: none;
+}
+
+.search-input {
+ width: 100%;
+ padding: 0.6rem 2.2rem 0.6rem 2.4rem;
+ border: 1px solid var(--border-color, #eaecef);
+ border-radius: 8px;
+ font-size: 0.9rem;
+ background: #ffffff;
+ color: var(--text-color, #2c3e50);
+ transition: border-color 0.2s;
+}
+
+.search-input:focus {
+ outline: none;
+ border-color: var(--accent-color, #3eaf7c);
+ box-shadow: 0 0 0 3px rgba(62, 175, 124, 0.15);
+}
+
+.search-input::placeholder {
+ color: var(--text-color-secondary, #999);
+}
+
+.search-clear {
+ position: absolute;
+ right: 8px;
+ background: none;
+ border: none;
+ cursor: pointer;
+ padding: 4px;
+ color: var(--text-color-secondary, #666);
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+}
+
+.search-clear:hover {
+ background: rgba(0, 0, 0, 0.05);
+}
+
+.filter-groups {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 1rem;
+}
+
+.filter-group {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.filter-group__label {
+ font-size: 0.8rem;
+ font-weight: 600;
+ color: var(--text-color-secondary, #666);
+ white-space: nowrap;
+}
+
+.filter-chips {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.4rem;
+}
+
+.filter-chip {
+ background: var(--code-bg-color, #f6f8fa);
+ color: var(--text-color, #2c3e50);
+ border: 1px solid var(--border-color, #eaecef);
+ padding: 0.25rem 0.7rem;
+ border-radius: 16px;
+ font-size: 0.78rem;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 0.2s;
+}
+
+.filter-chip:hover {
+ border-color: var(--accent-color, #3eaf7c);
+ color: var(--accent-color, #3eaf7c);
+}
+
+.filter-chip--active {
+ background: var(--accent-color, #3eaf7c);
+ color: #ffffff;
+ border-color: var(--accent-color, #3eaf7c);
+}
+
+.filter-chip--active:hover {
+ color: #ffffff;
+ opacity: 0.9;
+}
+
+.filter-status {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+ font-size: 0.85rem;
+ color: var(--text-color-secondary, #666);
+}
+
+.filter-clear {
+ background: none;
+ border: none;
+ color: var(--accent-color, #3eaf7c);
+ cursor: pointer;
+ font-size: 0.85rem;
+ font-weight: 500;
+ padding: 0;
+}
+
+.filter-clear:hover {
+ text-decoration: underline;
+}
+
+.no-results {
+ text-align: center;
+ padding: 3rem 1rem;
+ color: var(--text-color-secondary, #666);
+}
+
+.no-results p {
+ margin-bottom: 1rem;
+}
+
/* Grid Layout */
.boards-grid {
display: grid;
- grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
+ grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
gap: 1.5rem;
margin-top: 1.5rem;
}
@@ -232,6 +534,21 @@ export default {
justify-content: center;
min-height: 120px;
border-bottom: 1px solid var(--border-color, #eaecef);
+ position: relative;
+}
+
+.popular-badge {
+ position: absolute;
+ top: 8px;
+ right: 8px;
+ background: #ff9800;
+ color: #ffffff;
+ font-size: 0.65rem;
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ padding: 0.2rem 0.5rem;
+ border-radius: 4px;
}
.board-card__image img {
@@ -374,6 +691,15 @@ export default {
/* Responsive */
@media (max-width: 768px) {
+ .filter-groups {
+ flex-direction: column;
+ gap: 0.5rem;
+ }
+
+ .filter-group {
+ flex-wrap: wrap;
+ }
+
.boards-grid {
grid-template-columns: 1fr;
gap: 1rem;