CI / Lint e auditorias (push) Successful in 1m10s
CI / Trivy (dependências e segredos) (push) Successful in 12s
CI / Imagem Docker (linux/arm64) (push) Successful in 2m39s
Release / Plano (reaproveitar a imagem?) (push) Successful in 6s
Release / ci (push) Skipped
Release / Publica a imagem (linux/arm64) (push) Successful in 18s
Release / Cria a release no Gitea (push) Successful in 5s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
524 lines
30 KiB
React
524 lines
30 KiB
React
"use client";
|
||
|
||
import { useState, useMemo } from "react";
|
||
import { usePathname } from "next/navigation";
|
||
import Header from "@/components/Header";
|
||
import Footer from "@/components/Footer";
|
||
import PageHero from "@/components/PageHero";
|
||
import SectionHead from "@/components/SectionHead";
|
||
import * as componentesPt from "@/lib/componentRegistryData";
|
||
import * as componentesEn from "@/lib/data/en/componentes";
|
||
import * as componentesEs from "@/lib/data/es/componentes";
|
||
import * as componentesFr from "@/lib/data/fr/componentes";
|
||
import * as componentesDe from "@/lib/data/de/componentes";
|
||
import * as componentesZh from "@/lib/data/zh-CN/componentes";
|
||
import {
|
||
ArrowUpRight,
|
||
Box,
|
||
Brain,
|
||
Check,
|
||
CircuitBoard,
|
||
Cloud,
|
||
Copy,
|
||
Cpu,
|
||
Download,
|
||
HelpCircle,
|
||
Home,
|
||
Layers,
|
||
Monitor,
|
||
Plus,
|
||
Search,
|
||
ShieldCheck,
|
||
Terminal,
|
||
Trash2,
|
||
Wrench,
|
||
X,
|
||
} from "lucide-react";
|
||
|
||
const ICONES_CATEGORIA = {
|
||
ui_display: Monitor,
|
||
cloud_conectividade: Cloud,
|
||
audio_voz_ia: Brain,
|
||
smart_home_mesh: Home,
|
||
drivers_perifericos: Cpu,
|
||
bsp_placas: CircuitBoard,
|
||
sistema_utilitarios: Wrench,
|
||
};
|
||
|
||
export default function ComponentRegistryPage() {
|
||
const pathname = usePathname();
|
||
const isEn = pathname?.startsWith("/en");
|
||
const isEs = pathname?.startsWith("/es");
|
||
const isFr = pathname?.startsWith("/fr");
|
||
const isDe = pathname?.startsWith("/de");
|
||
const isZh = pathname?.startsWith("/zh-CN");
|
||
const { CATEGORIAS_COMPONENTES, COMPONENTES_REGISTRY, GUIA_COMPONENT_MANAGER } = isEs ? componentesEs : isEn ? componentesEn : isFr ? componentesFr : isZh ? componentesZh : isDe ? componentesDe : componentesPt;
|
||
|
||
const [busca, setBusca] = useState("");
|
||
const [categoriaAtiva, setCategoriaAtiva] = useState("todos");
|
||
const [chipFiltro, setChipFiltro] = useState("todos");
|
||
const [copiadoId, setCopiadoId] = useState(null);
|
||
const [manifestoCopiado, setManifestoCopiado] = useState(false);
|
||
|
||
// Componentes selecionados no construtor de idf_component.yml
|
||
const [componentesSelecionados, setComponentesSelecionados] = useState([
|
||
"espressif-esp_lvgl_port",
|
||
"espressif-led_indicator"
|
||
]);
|
||
|
||
// Lista de chips únicos para filtro
|
||
const chipsDisponiveis = ["todos", "ESP32", "ESP32-S3", "ESP32-C6", "ESP32-P4", "ESP32-H2", "ESP32-C3"];
|
||
|
||
// Filtragem dos componentes
|
||
const componentesFiltrados = useMemo(() => {
|
||
return COMPONENTES_REGISTRY.filter((comp) => {
|
||
const matchCategoria = categoriaAtiva === "todos" || comp.categoria === categoriaAtiva;
|
||
const matchChip = chipFiltro === "todos" || (comp.chips && comp.chips.includes(chipFiltro));
|
||
const matchBusca =
|
||
busca.trim() === "" ||
|
||
comp.name.toLowerCase().includes(busca.toLowerCase()) ||
|
||
comp.namespace.toLowerCase().includes(busca.toLowerCase()) ||
|
||
comp.titulo.toLowerCase().includes(busca.toLowerCase()) ||
|
||
comp.descricao.toLowerCase().includes(busca.toLowerCase()) ||
|
||
comp.tags.some((t) => t.toLowerCase().includes(busca.toLowerCase()));
|
||
|
||
return matchCategoria && matchChip && matchBusca;
|
||
});
|
||
}, [busca, categoriaAtiva, chipFiltro, COMPONENTES_REGISTRY]);
|
||
|
||
// Manipuladores de cópia
|
||
const copiarComando = (id, comando) => {
|
||
navigator.clipboard.writeText(comando);
|
||
setCopiadoId(id);
|
||
setTimeout(() => setCopiadoId(null), 2000);
|
||
};
|
||
|
||
const toggleSelecaoManifesto = (id) => {
|
||
setComponentesSelecionados((prev) =>
|
||
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
||
);
|
||
};
|
||
|
||
// Geração dinâmica do manifesto YAML
|
||
const manifestoYaml = useMemo(() => {
|
||
const selecionados = COMPONENTES_REGISTRY.filter((c) =>
|
||
componentesSelecionados.includes(c.id)
|
||
);
|
||
|
||
if (selecionados.length === 0) {
|
||
return isEs
|
||
? `# No hay componentes seleccionados.\n# Haga clic en "+ Agregar al manifiesto" en las tarjetas de abajo.`
|
||
: isEn
|
||
? `# No components selected.\n# Click "+ Add to Manifest" on the cards below.`
|
||
: isFr
|
||
? `# Aucun composant sélectionné.\n# Cliquez sur "+ Ajouter au manifeste" sur les cartes ci-dessous.`
|
||
: isZh ? `# 尚未选择组件。\n# 点击下方卡片中的“+ 添加到清单”。` : isDe
|
||
? `# Keine Komponenten ausgewählt.\n# Klicken Sie auf den Karten unten auf "+ Zum Manifest hinzufügen".`
|
||
: `# Nenhum componente selecionado.\n# Clique em "+ Adicionar ao Manifesto" nos cards abaixo.`;
|
||
}
|
||
|
||
let yaml = isEs
|
||
? `# main/idf_component.yml\n# Generado mediante ESPDocs Component Hub (https://espdocs.cienciaembarcada.com.br/es/componentes)\n\ndependencies:\n`
|
||
: isEn
|
||
? `# main/idf_component.yml\n# Generated via ESPDocs Component Hub (https://espdocs.cienciaembarcada.com.br/en/components)\n\ndependencies:\n`
|
||
: isFr
|
||
? `# main/idf_component.yml\n# Généré via ESPDocs Component Hub (https://espdocs.cienciaembarcada.com.br/fr/composants)\n\ndependencies:\n`
|
||
: isZh ? `# main/idf_component.yml\n# 由 ESPDocs Component Hub 生成(https://espdocs.cienciaembarcada.com.br/zh-CN/components)\n\ndependencies:\n` : isDe
|
||
? `# main/idf_component.yml\n# Erstellt über den ESPDocs Component Hub (https://espdocs.cienciaembarcada.com.br/de/komponenten)\n\ndependencies:\n`
|
||
: `# main/idf_component.yml\n# Gerado via ESPDocs Component Hub (https://espdocs.cienciaembarcada.com.br/componentes)\n\ndependencies:\n`;
|
||
selecionados.forEach((c) => {
|
||
yaml += ` ${c.namespace}/${c.name}: "${c.versao}"\n`;
|
||
});
|
||
return yaml;
|
||
}, [componentesSelecionados, COMPONENTES_REGISTRY, isEn, isEs, isFr, isDe, isZh]);
|
||
|
||
const copiarManifesto = () => {
|
||
navigator.clipboard.writeText(manifestoYaml);
|
||
setManifestoCopiado(true);
|
||
setTimeout(() => setManifestoCopiado(false), 2000);
|
||
};
|
||
|
||
const baixarManifesto = () => {
|
||
const blob = new Blob([manifestoYaml], { type: "text/yaml;charset=utf-8" });
|
||
const url = URL.createObjectURL(blob);
|
||
const link = document.createElement("a");
|
||
link.href = url;
|
||
link.download = "idf_component.yml";
|
||
document.body.appendChild(link);
|
||
link.click();
|
||
document.body.removeChild(link);
|
||
URL.revokeObjectURL(url);
|
||
};
|
||
|
||
const limparFiltros = () => {
|
||
setBusca("");
|
||
setCategoriaAtiva("todos");
|
||
setChipFiltro("todos");
|
||
};
|
||
|
||
const totalFiltros = (busca.trim() ? 1 : 0) + (categoriaAtiva !== "todos" ? 1 : 0) + (chipFiltro !== "todos" ? 1 : 0);
|
||
|
||
return (
|
||
<div className="site-canvas min-h-screen">
|
||
<Header />
|
||
|
||
<main id="conteudo">
|
||
<PageHero
|
||
isEn={isEn}
|
||
isEs={isEs}
|
||
isFr={isFr}
|
||
isDe={isDe} isZh={isZh}
|
||
trail={[{ label: isEs ? "Component Registry" : isEn ? "Component Registry" : isFr ? "Component Registry" : isZh ? "Component Registry" : isDe ? "Component Registry" : "Component Registry" }]}
|
||
icon={Box}
|
||
label="ESP-IDF Component Manager"
|
||
title="ESP Component Registry"
|
||
lede={
|
||
isEs
|
||
? "El catálogo oficial de paquetes reutilizables de Espressif para ESP-IDF. Elija controladores, voz y pilas industriales, copie el comando y genere el manifiesto del proyecto."
|
||
: isEn
|
||
? "Official Espressif catalog of reusable packages for ESP-IDF. Choose drivers, voice stacks, and industrial protocols, copy commands, and generate project manifests."
|
||
: isFr
|
||
? "Le catalogue officiel de paquets réutilisables d'Espressif pour ESP-IDF. Choisissez vos pilotes, piles vocales et protocoles industriels, copiez la commande et générez le manifeste du projet."
|
||
: isZh ? "乐鑫面向 ESP-IDF 的官方可复用软件包目录。选择驱动、语音和工业协议栈,复制命令并生成项目清单。" : isDe
|
||
? "Der offizielle Espressif-Katalog für wiederverwendbare Pakete für ESP-IDF. Wählen Sie Treiber, Sprach- und Industrieprotokolle, kopieren Sie den Befehl und erstellen Sie das Projektmanifest."
|
||
: "O catálogo oficial de pacotes reutilizáveis da Espressif para o ESP-IDF. Escolha drivers, voz e pilhas industriais, copie o comando e gere o manifesto do projeto."
|
||
}
|
||
>
|
||
<a href="https://components.espressif.com" target="_blank" rel="noopener noreferrer" className="btn btn-primary">
|
||
{isEs ? "Abrir el registro oficial" : isEn ? "Open official registry" : isFr ? "Ouvrir le registre officiel" : isZh ? "打开官方注册中心" : isDe ? "Offizielles Repository öffnen" : "Abrir o registro oficial"}
|
||
<ArrowUpRight className="h-4 w-4" aria-hidden="true" />
|
||
<span className="sr-only"> {isEs ? "(abre en una nueva pestaña)" : isEn ? "(opens in a new tab)" : isFr ? "(ouvre un nouvel onglet)" : isZh ? "(在新标签页打开)" : isDe ? "(öffnet in neuem Tab)" : "(abre em nova aba)"}</span>
|
||
</a>
|
||
<a href="#manifesto-builder" className="btn btn-ghost">
|
||
{isEs ? "Generar idf_component.yml" : isEn ? "Generate idf_component.yml" : isFr ? "Générer idf_component.yml" : isZh ? "生成 idf_component.yml" : isDe ? "idf_component.yml erstellen" : "Gerar idf_component.yml"}
|
||
</a>
|
||
</PageHero>
|
||
|
||
<section className="shell pt-10 sm:pt-12" aria-label={isEs ? "Búsqueda y filtros" : isEn ? "Search and filters" : isFr ? "Recherche et filtres" : isZh ? "搜索与筛选" : isDe ? "Suche und Filter" : "Busca e filtros"}>
|
||
<div className="panel p-5 sm:p-6">
|
||
<div className="relative">
|
||
<label htmlFor="busca-componente" className="sr-only">
|
||
{isEs ? "Buscar por nombre, namespace o etiqueta" : isEn ? "Search by name, namespace, or tag" : isFr ? "Rechercher par nom, namespace ou tag" : isZh ? "按名称、命名空间或标签搜索" : isDe ? "Nach Name, Namespace oder Tag suchen" : "Buscar por nome, namespace ou tag"}
|
||
</label>
|
||
<Search className="pointer-events-none absolute left-4 top-1/2 h-5 w-5 -translate-y-1/2 text-muted" aria-hidden="true" />
|
||
<input
|
||
id="busca-componente"
|
||
type="search"
|
||
value={busca}
|
||
onChange={(e) => setBusca(e.target.value)}
|
||
placeholder={isEs ? "Buscar por nombre, namespace o etiqueta" : isEn ? "Search by name, namespace, or tag" : isFr ? "Rechercher par nom, namespace ou tag" : isZh ? "按名称、命名空间或标签搜索" : isDe ? "Nach Name, Namespace oder Tag suchen" : "Buscar por nome, namespace ou tag"}
|
||
autoComplete="off"
|
||
className="field pl-12 pr-4 text-base"
|
||
/>
|
||
</div>
|
||
|
||
<div className="mt-5">
|
||
<h2 className="text-sm font-semibold text-ink">{isEs ? "Categoría" : isEn ? "Category" : isFr ? "Catégorie" : isZh ? "类别" : isDe ? "Kategorie" : "Categoria"}</h2>
|
||
<div className="mt-3 flex flex-wrap gap-2">
|
||
{CATEGORIAS_COMPONENTES.map((cat) => {
|
||
const Icone = ICONES_CATEGORIA[cat.id] ?? Layers;
|
||
const contagem = cat.id === "todos" ? COMPONENTES_REGISTRY.length : COMPONENTES_REGISTRY.filter((c) => c.categoria === cat.id).length;
|
||
return (
|
||
<button key={cat.id} type="button" onClick={() => setCategoriaAtiva(cat.id)} aria-pressed={categoriaAtiva === cat.id} className="chip-btn">
|
||
<Icone className="h-4 w-4" aria-hidden="true" />
|
||
{cat.id === "todos" ? (isEs ? "Todos" : isEn ? "All" : isFr ? "Tous" : isZh ? "全部" : isDe ? "Alle" : "Todos") : cat.nome.replace(/\s*\(.*\)/, "").split(",")[0].split(" &")[0]}
|
||
<span className="tabular-nums opacity-80">{contagem}</span>
|
||
</button>
|
||
);
|
||
})}
|
||
</div>
|
||
</div>
|
||
|
||
<div className="mt-5 border-t border-line pt-5">
|
||
<h2 className="text-sm font-semibold text-ink">{isEs ? "Chip compatible" : isEn ? "Compatible chip" : isFr ? "Puce compatible" : isZh ? "兼容芯片" : isDe ? "Kompatibler Chip" : "Chip compatível"}</h2>
|
||
<div className="mt-3 flex flex-wrap gap-2">
|
||
{chipsDisponiveis.map((chip) => (
|
||
<button key={chip} type="button" onClick={() => setChipFiltro(chip)} aria-pressed={chipFiltro === chip} className="chip-btn">
|
||
{chip === "todos" ? (isEs ? "Todos" : isEn ? "All" : isFr ? "Tous" : isZh ? "全部" : isDe ? "Alle" : "Todos") : chip}
|
||
</button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section className="shell section pt-8 sm:pt-10" aria-labelledby="lista-titulo">
|
||
<div className="flex flex-wrap items-center justify-between gap-3">
|
||
<h2 id="lista-titulo" role="status" aria-live="polite" className="text-base font-semibold text-ink">
|
||
{componentesFiltrados.length} {componentesFiltrados.length === 1 ? (isEs ? "componente" : isEn ? "component" : isFr ? "composant" : isZh ? "个组件" : isDe ? "Komponente" : "componente") : (isEs ? "componentes" : isEn ? "components" : isFr ? "composants" : isZh ? "个组件" : isDe ? "Komponenten" : "componentes")}
|
||
</h2>
|
||
<div className="flex flex-wrap items-center gap-x-5 gap-y-1">
|
||
{totalFiltros > 0 && (
|
||
<button type="button" onClick={limparFiltros} className="inline-flex min-h-11 items-center gap-1.5 text-sm font-medium text-accent hover:text-accent-strong">
|
||
<X className="h-4 w-4" aria-hidden="true" />
|
||
{isEs ? "Limpiar filtros" : isEn ? "Clear filters" : isFr ? "Effacer les filtres" : isZh ? "清除筛选" : isDe ? "Filter zurücksetzen" : "Limpar filtros"}
|
||
</button>
|
||
)}
|
||
<a href="#manifesto-builder" className="inline-flex min-h-11 items-center gap-1.5 text-sm font-medium text-accent hover:text-accent-strong">
|
||
{isEs ? "Manifiesto:" : isEn ? "Manifest:" : isFr ? "Manifeste :" : isZh ? "清单:" : isDe ? "Manifest :" : "Manifesto:"} {componentesSelecionados.length} {componentesSelecionados.length === 1 ? (isEs ? "componente" : isEn ? "component" : isFr ? "composant" : isZh ? "个组件" : isDe ? "Komponente" : "componente") : (isEs ? "componentes" : isEn ? "components" : isFr ? "composants" : isZh ? "个组件" : isDe ? "Komponenten" : "componentes")}
|
||
</a>
|
||
</div>
|
||
</div>
|
||
|
||
{componentesFiltrados.length === 0 ? (
|
||
<div className="card mt-6 px-6 py-14 text-center">
|
||
<Box className="mx-auto h-10 w-10 text-muted" aria-hidden="true" />
|
||
<h3 className="mt-4 font-display text-xl font-semibold text-ink">
|
||
{isEs ? "No se encontraron componentes" : isEn ? "No components found" : isFr ? "Aucun composant trouvé" : isZh ? "未找到组件" : isDe ? "Keine Komponenten gefunden" : "Nenhum componente encontrado"}
|
||
</h3>
|
||
<p className="mx-auto mt-2 max-w-md text-sm text-muted">
|
||
{isEs
|
||
? "Busque por un término más general o regrese a todas las categorías."
|
||
: isEn
|
||
? "Search for a more general term or return to all categories."
|
||
: isFr
|
||
? "Recherchez un terme plus général ou revenez à toutes les catégories."
|
||
: isZh ? "请搜索更通用的词,或返回全部类别。" : isDe
|
||
? "Suchen Sie nach einem allgemeineren Begriff oder kehren Sie zu allen Kategorien zurück."
|
||
: "Busque por um termo mais geral ou volte para todas as categorias."}
|
||
</p>
|
||
<button type="button" onClick={limparFiltros} className="btn btn-primary mt-6">
|
||
{isEs ? "Limpiar filtros" : isEn ? "Clear filters" : isFr ? "Effacer les filtres" : isZh ? "清除筛选" : isDe ? "Filter zurücksetzen" : "Limpar filtros"}
|
||
</button>
|
||
</div>
|
||
) : (
|
||
<div className="mt-6 grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||
{componentesFiltrados.map((comp) => {
|
||
const selecionado = componentesSelecionados.includes(comp.id);
|
||
const cliCmd = `idf.py add-dependency "${comp.namespace}/${comp.name}${comp.versao}"`;
|
||
const foiCopiado = copiadoId === comp.id;
|
||
|
||
return (
|
||
<article key={comp.id} className={`card flex flex-col overflow-hidden ${selecionado ? "border-accent ring-2 ring-accent/20" : ""}`}>
|
||
<div className="flex-1 p-5 sm:p-6">
|
||
<div className="flex items-start justify-between gap-3">
|
||
<div className="min-w-0">
|
||
<p className="flex flex-wrap items-center gap-2 text-xs text-muted">
|
||
{comp.namespace}/
|
||
{comp.oficial && (
|
||
<span className="chip chip-accent px-2 py-0">
|
||
<ShieldCheck className="h-3 w-3" aria-hidden="true" />
|
||
{isEs ? "Oficial" : isEn ? "Official" : isFr ? "Officiel" : isZh ? "官方" : isDe ? "Offiziell" : "Oficial"}
|
||
</span>
|
||
)}
|
||
</p>
|
||
<h3 className="mt-1 break-words font-display text-xl font-semibold tracking-[-0.03em] text-ink">{comp.name}</h3>
|
||
<p className="mt-0.5 text-sm font-medium text-accent">{comp.titulo}</p>
|
||
</div>
|
||
<span className="shrink-0 rounded-md border border-line bg-surface-2 px-2 py-1 font-mono text-xs text-ink-soft">{comp.versao}</span>
|
||
</div>
|
||
|
||
<p className="mt-3 line-clamp-3 text-sm leading-relaxed text-muted">{comp.descricao}</p>
|
||
|
||
<ul className="mt-4 flex flex-wrap gap-1.5">
|
||
{comp.tags.map((tag) => (
|
||
<li key={tag} className="chip">{tag}</li>
|
||
))}
|
||
</ul>
|
||
</div>
|
||
|
||
<div className="border-t border-line bg-surface-2/60 p-5 sm:px-6">
|
||
<button
|
||
type="button"
|
||
onClick={() => copiarComando(comp.id, cliCmd)}
|
||
className="code-panel flex w-full min-h-11 items-center justify-between gap-3 rounded-lg px-3 py-2 text-left font-mono text-xs transition-colors hover:border-white/30"
|
||
aria-label={isEs ? `Copiar comando para agregar ${comp.name}` : isEn ? `Copy command to add ${comp.name}` : isFr ? `Copier la commande pour ajouter ${comp.name}` : isZh ? `复制添加 ${comp.name} 的命令` : isDe ? `Befehl zum Hinzufügen von ${comp.name} kopieren` : `Copiar comando para adicionar ${comp.name}`}
|
||
>
|
||
<span className="truncate text-[#e9eef4]">{cliCmd}</span>
|
||
{foiCopiado ? <Check className="h-4 w-4 shrink-0 text-[#5fe0a2]" aria-hidden="true" /> : <Copy className="h-4 w-4 shrink-0 text-[#a6afbd]" aria-hidden="true" />}
|
||
</button>
|
||
<p role="status" className="sr-only">{foiCopiado ? (isEs ? "Comando copiado" : isEn ? "Command copied" : isFr ? "Commande copiée" : isZh ? "命令已复制" : isDe ? "Befehl kopiert" : "Comando copiado") : ""}</p>
|
||
|
||
<p className="mt-3 text-xs text-muted">
|
||
<span className="font-medium text-ink-soft">Chips: </span>
|
||
{comp.chips.join(", ")}
|
||
</p>
|
||
</div>
|
||
|
||
<div className="flex items-center justify-between gap-2 border-t border-line p-4 sm:px-6">
|
||
<button
|
||
type="button"
|
||
onClick={() => toggleSelecaoManifesto(comp.id)}
|
||
aria-pressed={selecionado}
|
||
className={`btn btn-sm ${selecionado ? "btn-primary" : "btn-ghost"}`}
|
||
>
|
||
{selecionado ? <Check className="h-4 w-4" aria-hidden="true" /> : <Plus className="h-4 w-4" aria-hidden="true" />}
|
||
{selecionado ? (isEs ? "En manifiesto" : isEn ? "In manifest" : isFr ? "Dans le manifeste" : isZh ? "已在清单中" : isDe ? "Im Manifest" : "No manifesto") : (isEs ? "Agregar al manifiesto" : isEn ? "Add to manifest" : isFr ? "Ajouter au manifeste" : isZh ? "添加到清单" : isDe ? "Zum Manifest hinzufügen" : "Adicionar ao manifesto")}
|
||
</button>
|
||
<a
|
||
href={comp.urlRegistry}
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
className="btn-icon inline-flex h-11 w-11 items-center justify-center rounded-full text-muted transition-colors hover:bg-surface-2 hover:text-accent"
|
||
aria-label={isEs ? `Abrir ${comp.name} en el registro oficial (nueva pestaña)` : isEn ? `Open ${comp.name} in official registry (new tab)` : isFr ? `Ouvrir ${comp.name} sur le registre officiel (nouvel onglet)` : isZh ? `在官方注册中心打开 ${comp.name}(新标签页)` : isDe ? `${comp.name} in der offiziellen Registry öffnen (neuer Tab)` : `Abrir ${comp.name} no registro oficial (nova aba)`}
|
||
>
|
||
<ArrowUpRight className="h-4 w-4" aria-hidden="true" />
|
||
</a>
|
||
</div>
|
||
</article>
|
||
);
|
||
})}
|
||
</div>
|
||
)}
|
||
</section>
|
||
|
||
<section id="manifesto-builder" className="shell scroll-mt-28 pb-16" aria-labelledby="manifesto-titulo">
|
||
<div className="panel p-6 sm:p-9">
|
||
<div className="flex flex-col justify-between gap-5 md:flex-row md:items-end">
|
||
<div className="max-w-2xl">
|
||
<p className="eyebrow">{isEs ? "Generador de manifiesto" : isEn ? "Manifest generator" : isFr ? "Générateur de manifeste" : isZh ? "清单生成器" : isDe ? "Manifest-Generator" : "Gerador de manifesto"}</p>
|
||
<h2 id="manifesto-titulo" className="section-title mt-3">
|
||
{isEs ? "Su idf_component.yml" : isEn ? "Your idf_component.yml" : isFr ? "Votre idf_component.yml" : isZh ? "你的 idf_component.yml" : isDe ? "Ihre idf_component.yml" : "Seu idf_component.yml"}
|
||
</h2>
|
||
<p className="mt-3 text-sm leading-relaxed text-muted">
|
||
{isEs ? (
|
||
<>Guarde el archivo en la carpeta <code className="rounded bg-surface-2 px-1.5 py-0.5 font-mono">main/</code> del proyecto y ESP-IDF descargará todas las bibliotecas durante la compilación.</>
|
||
) : isEn ? (
|
||
<>Save this file in your project's <code className="rounded bg-surface-2 px-1.5 py-0.5 font-mono">main/</code> folder and ESP-IDF will download all dependencies during build.</>
|
||
) : isFr ? (
|
||
<>Enregistrez le fichier dans le dossier <code className="rounded bg-surface-2 px-1.5 py-0.5 font-mono">main/</code> du projet et ESP-IDF téléchargera toutes les bibliothèques lors de la compilation.</>
|
||
) : isZh ? ( <>将文件保存到项目的 <code className="rounded bg-surface-2 px-1.5 py-0.5 font-mono">main/</code> 文件夹中,ESP-IDF 会在编译时下载所有库。</> ) : isDe ? (
|
||
<>Speichern Sie die Datei im Ordner <code className="rounded bg-surface-2 px-1.5 py-0.5 font-mono">main/</code> Ihres Projekts, und ESP-IDF lädt beim Build alle Abhängigkeiten herunter.</>
|
||
) : (
|
||
<>Salve o arquivo na pasta <code className="rounded bg-surface-2 px-1.5 py-0.5 font-mono">main/</code> do projeto e o ESP-IDF baixa todas as bibliotecas na compilação.</>
|
||
)}
|
||
</p>
|
||
</div>
|
||
<div className="flex flex-wrap items-center gap-2">
|
||
<button type="button" onClick={copiarManifesto} className="btn btn-primary btn-sm">
|
||
{manifestoCopiado ? <Check className="h-4 w-4" aria-hidden="true" /> : <Copy className="h-4 w-4" aria-hidden="true" />}
|
||
<span role="status">{manifestoCopiado ? (isEs ? "Copiado" : isEn ? "Copied" : isFr ? "Copié" : isZh ? "已复制" : isDe ? "Kopiert" : "Copiado") : (isEs ? "Copiar YAML" : isEn ? "Copy YAML" : isFr ? "Copier le YAML" : isZh ? "复制 YAML" : isDe ? "YAML kopieren" : "Copiar YAML")}</span>
|
||
</button>
|
||
<button type="button" onClick={baixarManifesto} className="btn btn-ghost btn-sm">
|
||
<Download className="h-4 w-4" aria-hidden="true" />
|
||
{isEs ? "Descargar .yml" : isEn ? "Download .yml" : isFr ? "Télécharger .yml" : isZh ? "下载 .yml" : isDe ? ".yml herunterladen" : "Baixar .yml"}
|
||
</button>
|
||
{componentesSelecionados.length > 0 && (
|
||
<button type="button" onClick={() => setComponentesSelecionados([])} className="btn btn-ghost btn-sm">
|
||
<Trash2 className="h-4 w-4" aria-hidden="true" />
|
||
{isEs ? "Limpiar" : isEn ? "Clear" : isFr ? "Effacer" : isZh ? "清除" : isDe ? "Leeren" : "Limpar"}
|
||
</button>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
<div className="code-panel mt-6">
|
||
<pre tabIndex={0} aria-label={isEs ? "Contenido de idf_component.yml" : isEn ? "Content of idf_component.yml" : isFr ? "Contenu de idf_component.yml" : isZh ? "idf_component.yml 的内容" : isDe ? "Inhalt von idf_component.yml" : "Conteúdo do idf_component.yml"} className="!text-sm"><code className="text-[#e9eef4]">{manifestoYaml}</code></pre>
|
||
</div>
|
||
|
||
{componentesSelecionados.length > 0 && (
|
||
<ul className="mt-5 flex flex-wrap items-center gap-2" aria-label={isEs ? "Componentes incluidos" : isEn ? "Included components" : isFr ? "Composants inclus" : isZh ? "已包含的组件" : isDe ? "Enthaltene Komponenten" : "Componentes incluídos"}>
|
||
{componentesSelecionados.map((id) => {
|
||
const c = COMPONENTES_REGISTRY.find((item) => item.id === id);
|
||
if (!c) return null;
|
||
return (
|
||
<li key={id} className="chip chip-accent gap-1 pr-1">
|
||
{c.name}
|
||
<button
|
||
type="button"
|
||
onClick={() => toggleSelecaoManifesto(id)}
|
||
className="-my-2 grid h-11 w-11 place-items-center rounded-full hover:bg-accent/15"
|
||
aria-label={isEs ? `Eliminar ${c.name} del manifiesto` : isEn ? `Remove ${c.name} from manifest` : isFr ? `Retirer ${c.name} du manifeste` : isZh ? `从清单中移除 ${c.name}` : isDe ? `${c.name} aus dem Manifest entfernen` : `Remover ${c.name} do manifesto`}
|
||
>
|
||
<X className="h-3.5 w-3.5" aria-hidden="true" />
|
||
</button>
|
||
</li>
|
||
);
|
||
})}
|
||
</ul>
|
||
)}
|
||
</div>
|
||
</section>
|
||
|
||
<section className="shell section border-t border-line pt-14" aria-labelledby="guia-titulo">
|
||
<SectionHead id="guia-titulo" eyebrow={isEs ? "Guía rápida" : isEn ? "Quick guide" : isFr ? "Guide rapide" : isZh ? "快速指南" : isDe ? "Kurzanleitung" : "Guia rápido"} title={GUIA_COMPONENT_MANAGER.titulo} lede={GUIA_COMPONENT_MANAGER.resumo} />
|
||
|
||
<ul className="mt-8 grid gap-3 md:grid-cols-2 lg:grid-cols-4">
|
||
{GUIA_COMPONENT_MANAGER.vantagens.map((v) => (
|
||
<li key={v.titulo} className="card p-5">
|
||
<h3 className="font-semibold text-ink">{v.titulo}</h3>
|
||
<p className="mt-2 text-sm leading-relaxed text-muted">{v.descricao}</p>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
|
||
<h3 className="mt-12 flex items-center gap-2 font-display text-xl font-semibold tracking-[-0.03em] text-ink">
|
||
<Terminal className="h-5 w-5 text-accent" aria-hidden="true" />
|
||
{isEs ? "Comandos esenciales" : isEn ? "Essential commands" : isFr ? "Commandes essentielles" : isZh ? "基本命令" : isDe ? "Wichtige Befehle" : "Comandos essenciais"}
|
||
</h3>
|
||
<ul className="mt-4 space-y-3">
|
||
{GUIA_COMPONENT_MANAGER.comandosCli.map((cmd, i) => (
|
||
<li key={cmd.exemplo} className="code-panel flex flex-col justify-between gap-3 p-4 md:flex-row md:items-center">
|
||
<div className="min-w-0">
|
||
<code className="block break-all font-mono text-sm text-[#5eead4]">$ {cmd.exemplo}</code>
|
||
<p className="mt-1 text-sm text-[#a6afbd]">{cmd.explicacao}</p>
|
||
</div>
|
||
<button
|
||
type="button"
|
||
onClick={() => copiarComando(`cmd-${i}`, cmd.exemplo)}
|
||
className="inline-flex min-h-11 shrink-0 items-center gap-1.5 self-start rounded-full border border-white/15 px-4 text-sm font-medium text-[#e9eef4] transition-colors hover:bg-white/10 md:self-auto"
|
||
>
|
||
{copiadoId === `cmd-${i}` ? <Check className="h-4 w-4 text-[#5fe0a2]" aria-hidden="true" /> : <Copy className="h-4 w-4" aria-hidden="true" />}
|
||
{copiadoId === `cmd-${i}` ? (isEs ? "Copiado" : isEn ? "Copied" : isFr ? "Copié" : isZh ? "已复制" : isDe ? "Kopiert" : "Copiado") : (isEs ? "Copiar" : isEn ? "Copy" : isFr ? "Copier" : isZh ? "复制" : isDe ? "Kopieren" : "Copiar")}
|
||
</button>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
|
||
<div className="callout mt-8">
|
||
<h3 className="flex items-center gap-2 font-semibold text-ink">
|
||
<HelpCircle className="h-4 w-4 text-accent" aria-hidden="true" />
|
||
{isEs ? "Versionamiento semántico en ESP-IDF" : isEn ? "Semantic versioning in ESP-IDF" : isFr ? "Versionnement sémantique dans ESP-IDF" : isZh ? "ESP-IDF 中的语义化版本" : isDe ? "Semantische Versionierung in ESP-IDF" : "Versionamento semântico no ESP-IDF"}
|
||
</h3>
|
||
<ul className="mt-2 list-disc space-y-1.5 pl-5">
|
||
{isEs ? (
|
||
<>
|
||
<li><code>^1.2.0</code>: acepta actualizaciones menores y correcciones (1.2.1, 1.3.0), pero bloquea cambios importantes de API (2.0.0). <strong>Recomendado.</strong></li>
|
||
<li><code>~1.2.0</code>: acepta solo correcciones de errores (1.2.1, 1.2.5).</li>
|
||
<li><code>1.2.0</code>: bloquea en esta versión exacta.</li>
|
||
<li><code>*</code>: acepta cualquier versión. Evite en producción.</li>
|
||
</>
|
||
) : isEn ? (
|
||
<>
|
||
<li><code>^1.2.0</code>: accepts minor updates and bug fixes (1.2.1, 1.3.0), but blocks breaking API changes (2.0.0). <strong>Recommended.</strong></li>
|
||
<li><code>~1.2.0</code>: accepts patch releases only (1.2.1, 1.2.5).</li>
|
||
<li><code>1.2.0</code>: locks to this exact version.</li>
|
||
<li><code>*</code>: accepts any version. Avoid in production.</li>
|
||
</>
|
||
) : isFr ? (
|
||
<>
|
||
<li><code>^1.2.0</code>: accepte les mises à jour mineures et les correctifs (1.2.1, 1.3.0), mais bloque les changements majeurs d'API (2.0.0). <strong>Recommandé.</strong></li>
|
||
<li><code>~1.2.0</code>: accepte uniquement les correctifs de bugs (1.2.1, 1.2.5).</li>
|
||
<li><code>1.2.0</code>: verrouille sur cette version exacte.</li>
|
||
<li><code>*</code>: accepte n'importe quelle version. À éviter en production.</li>
|
||
</>
|
||
) : isZh ? ( <>
|
||
<li><code>^1.2.0</code>:接受次版本更新和修复(1.2.1、1.3.0),但会阻止重大 API 变更(2.0.0)。<strong>推荐。</strong></li>
|
||
<li><code>~1.2.0</code>:只接受缺陷修复(1.2.1、1.2.5)。</li>
|
||
<li><code>1.2.0</code>:锁定到精确版本。</li>
|
||
<li><code>*</code>:接受任意版本。生产环境请避免。</li>
|
||
</> ) : isDe ? (
|
||
<>
|
||
<li><code>^1.2.0</code>: akzeptiert Minor-Updates und Bugfixes (1.2.1, 1.3.0), blockiert aber Breaking Changes (2.0.0). <strong>Empfohlen.</strong></li>
|
||
<li><code>~1.2.0</code>: akzeptiert nur Patch-Releases (1.2.1, 1.2.5).</li>
|
||
<li><code>1.2.0</code>: bindet fest an diese Version.</li>
|
||
<li><code>*</code>: akzeptiert jede Version. In Produktion vermeiden.</li>
|
||
</>
|
||
) : (
|
||
<>
|
||
<li><code>^1.2.0</code>: aceita atualizações menores e correções (1.2.1, 1.3.0), mas bloqueia mudanças grandes de API (2.0.0). <strong>Recomendado.</strong></li>
|
||
<li><code>~1.2.0</code>: aceita só correções de bugs (1.2.1, 1.2.5).</li>
|
||
<li><code>1.2.0</code>: trava na versão exata.</li>
|
||
<li><code>*</code>: aceita qualquer versão. Evite em produção.</li>
|
||
</>
|
||
)}
|
||
</ul>
|
||
</div>
|
||
</section>
|
||
</main>
|
||
|
||
<Footer />
|
||
</div>
|
||
);
|
||
}
|