Files
ESPDocs/lib/boardSuggestions.js
T
Lucas RayanandClaude Sonnet 5.5 1f84f1f661
Release / Plano (reaproveitar a imagem?) (push) Successful in 12s
CI / Lint e auditorias (push) Successful in 1m39s
CI / Trivy (dependências e segredos) (push) Successful in 13s
CI / Imagem Docker (linux/arm64) (push) Successful in 2m35s
Release / ci (push) Successful in 4m30s
Release / Publica a imagem (linux/arm64) (push) Successful in 19s
Release / Cria a release no Gitea (push) Skipped
Release / Deploy no Coolify (push) Successful in 22s
feat(placas): faixa de placas sugeridas nas séries, comparação e seletor
Sorteia uma placa dos chips da página, com preferência pelas de loja oficial, e
troca só pelas setas, com transição suave. Usa o link de afiliado do idioma
(rel=sponsored) e some quando o chip não tem placa. Vai nas páginas de série, na
comparação e nos dois modos de resultado do seletor.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 18:31:01 -03:00

79 lines
5.2 KiB
JavaScript

// Lógica da faixa de placas sugeridas (components/PlacasSugeridas.jsx): quais placas entram,
// em que ordem sorteada e o texto curto de motivo. Sem React, para ficar fácil de testar.
import { linkForLocale } from "@/lib/boardLinks";
// Placa de loja oficial entra no sorteio com este peso; as demais valem 1.
export const PESO_OFICIAL = 3;
export function boardsForChips(boards, chips, locale) {
const alvo = new Set(chips);
return boards.filter((board) => alvo.has(board["Série"]) && linkForLocale(board, locale));
}
// Sorteio ponderado sem reposição (Efraimidis-Spirakis): quanto maior o peso, maior a chance
// de ficar no começo, e todas as placas aparecem uma vez na ordem final.
export function weightedOrder(boards, random = Math.random) {
return boards
.map((board) => {
const peso = board["Loja Oficial"]?.trim() === "Sim" ? PESO_OFICIAL : 1;
return { board, chave: Math.pow(random(), 1 / peso) };
})
.sort((a, b) => b.chave - a.chave)
.map(({ board }) => board);
}
export function imageOf(board) {
const src = board.Imagem?.match(/<img[^>]*\ssrc="([^"]+)"/i)?.[1];
if (!src) return null;
return src.startsWith("//") ? `https:${src}` : src;
}
// Recursos que viram o texto de motivo, do mais característico para o mais comum.
const FEATURES = [
{ columns: ["Display Touch"], key: "touch" },
{ columns: ["Display"], key: "display" },
{ columns: ["Câmera"], key: "camera" },
{ columns: ["LoRa"], key: "lora" },
{ columns: ["GPS"], key: "gps" },
{ columns: ["GSM (3G/4G)"], key: "gsm" },
{ columns: ["Conector Ethernet"], key: "ethernet" },
{ columns: ["RS485"], key: "rs485" },
{ columns: ["Radiofrequência"], key: "rf" },
{ columns: ["Infravermelho"], key: "ir" },
{ columns: ["Bateria Interna"], key: "battery" },
{ columns: ["Conector p/ Bateria"], key: "batteryConnector" },
{ columns: ["Conector p/ Painel Solar"], key: "solar" },
{ columns: ["Wi-Fi 5GHz"], key: "wifi5" },
{ columns: ["Suporte a Matter"], key: "matter" },
];
const LABELS = {
pt: { touch: "tela touch", display: "tela", camera: "câmera", lora: "LoRa", gps: "GPS", gsm: "4G/GSM", ethernet: "Ethernet", rs485: "RS485", rf: "rádio sub-GHz", ir: "infravermelho", battery: "bateria", batteryConnector: "conector de bateria", solar: "entrada solar", wifi5: "Wi-Fi 5 GHz", matter: "Matter", optional: "opcional", basic: "placa de desenvolvimento essencial" },
en: { touch: "touchscreen", display: "display", camera: "camera", lora: "LoRa", gps: "GPS", gsm: "4G/GSM", ethernet: "Ethernet", rs485: "RS485", rf: "sub-GHz radio", ir: "infrared", battery: "battery", batteryConnector: "battery connector", solar: "solar input", wifi5: "5 GHz Wi-Fi", matter: "Matter", optional: "optional", basic: "essential development board" },
es: { touch: "pantalla táctil", display: "pantalla", camera: "cámara", lora: "LoRa", gps: "GPS", gsm: "4G/GSM", ethernet: "Ethernet", rs485: "RS485", rf: "radio sub-GHz", ir: "infrarrojos", battery: "batería", batteryConnector: "conector de batería", solar: "entrada solar", wifi5: "Wi-Fi de 5 GHz", matter: "Matter", optional: "opcional", basic: "placa de desarrollo esencial" },
fr: { touch: "écran tactile", display: "écran", camera: "caméra", lora: "LoRa", gps: "GPS", gsm: "4G/GSM", ethernet: "Ethernet", rs485: "RS485", rf: "radio sub-GHz", ir: "infrarouge", battery: "batterie", batteryConnector: "connecteur de batterie", solar: "entrée solaire", wifi5: "Wi-Fi 5 GHz", matter: "Matter", optional: "optionnel", basic: "carte de développement essentielle" },
de: { touch: "Touchscreen", display: "Display", camera: "Kamera", lora: "LoRa", gps: "GPS", gsm: "4G/GSM", ethernet: "Ethernet", rs485: "RS485", rf: "Sub-GHz-Funk", ir: "Infrarot", battery: "Akku", batteryConnector: "Akkuanschluss", solar: "Solareingang", wifi5: "5-GHz-WLAN", matter: "Matter", optional: "optional", basic: "Entwicklungsboard für den Einstieg" },
"zh-CN": { touch: "触摸屏", display: "显示屏", camera: "摄像头", lora: "LoRa", gps: "GPS", gsm: "4G/GSM", ethernet: "以太网", rs485: "RS485", rf: "Sub-GHz 无线电", ir: "红外", battery: "内置电池", batteryConnector: "电池接口", solar: "太阳能输入", wifi5: "5 GHz Wi-Fi", matter: "Matter", optional: "可选", basic: "基础开发板" },
};
// "tela touch, câmera e LoRa": até três recursos, do mais característico para o mais comum.
// Quando há touch, a tela comum não entra à parte (já está implícita).
export function reasonFor(board, locale) {
const rotulos = LABELS[locale] || LABELS.pt;
const temTouch = ["Sim", "Opcional"].includes(board["Display Touch"]?.trim());
const itens = [];
for (const { columns, key } of FEATURES) {
if (key === "display" && temTouch) continue;
const valor = board[columns[0]]?.trim();
if (valor !== "Sim" && valor !== "Opcional") continue;
const texto = valor === "Opcional" ? `${rotulos[key]} (${rotulos.optional})` : rotulos[key];
itens.push(texto);
if (itens.length === 3) break;
}
if (itens.length === 0) return rotulos.basic;
if (locale === "zh-CN") return itens.join("、");
if (itens.length === 1) return itens[0];
const conector = { pt: "e", en: "and", es: "y", fr: "et", de: "und" }[locale] || "e";
return `${itens.slice(0, -1).join(", ")} ${conector} ${itens[itens.length - 1]}`;
}