diff --git a/src/Controller/KicadListEditorController.php b/src/Controller/KicadListEditorController.php new file mode 100644 index 00000000..ea9289db --- /dev/null +++ b/src/Controller/KicadListEditorController.php @@ -0,0 +1,70 @@ +. + */ + +declare(strict_types=1); + +namespace App\Controller; + +use App\Form\Settings\KicadListEditorType; +use App\Services\EDA\KicadListFileManager; +use RuntimeException; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Routing\Attribute\Route; + +use function Symfony\Component\Translation\t; + +final class KicadListEditorController extends AbstractController +{ + #[Route('/settings/misc/kicad-lists', name: 'settings_kicad_lists')] + public function __invoke(Request $request, KicadListFileManager $fileManager): Response + { + $this->denyAccessUnlessGranted('@config.change_system_settings'); + + $form = $this->createForm(KicadListEditorType::class, [ + 'footprints' => $fileManager->getFootprintsContent(), + 'symbols' => $fileManager->getSymbolsContent(), + ]); + + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $data = $form->getData(); + + try { + $fileManager->save($data['footprints'], $data['symbols']); + $this->addFlash('success', t('settings.flash.saved')); + + return $this->redirectToRoute('settings_kicad_lists'); + } catch (RuntimeException $exception) { + $this->addFlash('error', $exception->getMessage()); + } + } + + if ($form->isSubmitted() && !$form->isValid()) { + $this->addFlash('error', t('settings.flash.invalid')); + } + + return $this->render('settings/kicad_list_editor.html.twig', [ + 'form' => $form, + ]); + } +} diff --git a/src/Form/Settings/KicadListEditorType.php b/src/Form/Settings/KicadListEditorType.php new file mode 100644 index 00000000..3c933aee --- /dev/null +++ b/src/Form/Settings/KicadListEditorType.php @@ -0,0 +1,57 @@ +. + */ + +declare(strict_types=1); + +namespace App\Form\Settings; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\SubmitType; +use Symfony\Component\Form\Extension\Core\Type\TextareaType; +use Symfony\Component\Form\FormBuilderInterface; + +final class KicadListEditorType extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder + ->add('footprints', TextareaType::class, [ + 'label' => 'settings.misc.kicad_eda.editor.footprints', + 'help' => 'settings.misc.kicad_eda.editor.footprints.help', + 'attr' => [ + 'rows' => 16, + 'spellcheck' => 'false', + 'class' => 'font-monospace', + ], + ]) + ->add('symbols', TextareaType::class, [ + 'label' => 'settings.misc.kicad_eda.editor.symbols', + 'help' => 'settings.misc.kicad_eda.editor.symbols.help', + 'attr' => [ + 'rows' => 16, + 'spellcheck' => 'false', + 'class' => 'font-monospace', + ], + ]) + ->add('save', SubmitType::class, [ + 'label' => 'save', + ]); + } +} diff --git a/src/Services/EDA/KicadListFileManager.php b/src/Services/EDA/KicadListFileManager.php new file mode 100644 index 00000000..50922510 --- /dev/null +++ b/src/Services/EDA/KicadListFileManager.php @@ -0,0 +1,96 @@ +. + */ + +declare(strict_types=1); + +namespace App\Services\EDA; + +use RuntimeException; +use Symfony\Component\DependencyInjection\Attribute\Autowire; + +final class KicadListFileManager +{ + private const FOOTPRINTS_PATH = '/public/kicad/footprints.txt'; + private const SYMBOLS_PATH = '/public/kicad/symbols.txt'; + + public function __construct( + #[Autowire('%kernel.project_dir%')] + private readonly string $projectDir, + ) { + } + + public function getFootprintsContent(): string + { + return $this->readFile(self::FOOTPRINTS_PATH); + } + + public function getSymbolsContent(): string + { + return $this->readFile(self::SYMBOLS_PATH); + } + + public function save(string $footprints, string $symbols): void + { + $this->writeFile(self::FOOTPRINTS_PATH, $this->normalizeContent($footprints)); + $this->writeFile(self::SYMBOLS_PATH, $this->normalizeContent($symbols)); + } + + private function readFile(string $path): string + { + $fullPath = $this->projectDir . $path; + + if (!is_file($fullPath)) { + return ''; + } + + $content = file_get_contents($fullPath); + if ($content === false) { + throw new RuntimeException(sprintf('Failed to read KiCad list file "%s".', $fullPath)); + } + + return $content; + } + + private function writeFile(string $path, string $content): void + { + $fullPath = $this->projectDir . $path; + $tmpPath = $fullPath . '.tmp'; + + if (file_put_contents($tmpPath, $content, LOCK_EX) === false) { + throw new RuntimeException(sprintf('Failed to write KiCad list file "%s".', $fullPath)); + } + + if (!rename($tmpPath, $fullPath)) { + @unlink($tmpPath); + throw new RuntimeException(sprintf('Failed to replace KiCad list file "%s".', $fullPath)); + } + } + + private function normalizeContent(string $content): string + { + $normalized = str_replace(["\r\n", "\r"], "\n", $content); + + if ($normalized !== '' && !str_ends_with($normalized, "\n")) { + $normalized .= "\n"; + } + + return $normalized; + } +} diff --git a/templates/settings/kicad_list_editor.html.twig b/templates/settings/kicad_list_editor.html.twig new file mode 100644 index 00000000..d83c31ed --- /dev/null +++ b/templates/settings/kicad_list_editor.html.twig @@ -0,0 +1,17 @@ +{% extends "main_card.html.twig" %} + +{% block title %}{% trans %}settings.misc.kicad_eda.editor.title{% endtrans %}{% endblock %} + +{% block card_title %} {% trans %}settings.misc.kicad_eda.editor.title{% endtrans %}{% endblock %} + +{% block card_content %} +
+ {% trans %}settings.misc.kicad_eda.editor.description{% endtrans %} +
+ + {{ form_start(form) }} + {{ form_row(form.footprints) }} + {{ form_row(form.symbols) }} + {{ form_row(form.save) }} + {{ form_end(form) }} +{% endblock %} diff --git a/templates/settings/settings.html.twig b/templates/settings/settings.html.twig index a2c01085..325118d6 100644 --- a/templates/settings/settings.html.twig +++ b/templates/settings/settings.html.twig @@ -49,6 +49,15 @@ {{ form_widget(section_widget) }} + {% if section_widget.vars.name == 'kicadEDA' %} + + {% endif %} {% if not loop.last %}