Compare commits

...

2 commits

Author SHA1 Message Date
Jan Böhmer
5b86d6f652 Require full authentication for the system settings, as some of the settings are quite critical
Some checks are pending
Build assets artifact / Build assets artifact (push) Waiting to run
Docker Image Build / build (linux/amd64, amd64, ubuntu-latest) (push) Waiting to run
Docker Image Build / build (linux/arm/v7, armv7, ubuntu-24.04-arm) (push) Waiting to run
Docker Image Build / build (linux/arm64, arm64, ubuntu-24.04-arm) (push) Waiting to run
Docker Image Build / merge (push) Blocked by required conditions
Docker Image Build (FrankenPHP) / build (linux/amd64, amd64, ubuntu-latest) (push) Waiting to run
Docker Image Build (FrankenPHP) / build (linux/arm/v7, armv7, ubuntu-24.04-arm) (push) Waiting to run
Docker Image Build (FrankenPHP) / build (linux/arm64, arm64, ubuntu-24.04-arm) (push) Waiting to run
Docker Image Build (FrankenPHP) / merge (push) Blocked by required conditions
Static analysis / Static analysis (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, mysql) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, mysql) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, mysql) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.5, mysql) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, postgres) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, postgres) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, postgres) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.5, postgres) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, sqlite) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, sqlite) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, sqlite) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.5, sqlite) (push) Waiting to run
2026-04-15 00:04:52 +02:00
DanTrackpaw
58a34e3628
Add custom KiCad autocomplete list settings (#1342)
* Add admin editor for KiCad autocomplete lists

* Add custom KiCad autocomplete list settings

* Ignore the footprints_custom.txt and symbols_custom.txt in git and create them on the fly if needed

Otherwise it breaks the update mechanism

* Added comments

* Include kicad custom files in config backup command

---------

Co-authored-by: Jan Böhmer <mail@jan-boehmer.de>
2026-04-15 00:01:00 +02:00
15 changed files with 666 additions and 3 deletions

7
.gitignore vendored
View file

@ -55,3 +55,10 @@ phpstan.neon
.claude/
CLAUDE.md
.codex
migrations/.codex
docker-data/
scripts/
db/
docker-compose.yaml

View file

@ -67,6 +67,7 @@ You can define this on a per-part basis using the KiCad symbol and KiCad footpri
For example, to configure the values for a BC547 transistor you would put `Transistor_BJT:BC547` in the part's KiCad symbol field to give it the right schematic symbol in Eeschema and `Package_TO_SOT_THT:TO-92` to give it the right footprint in Pcbnew.
If you type in a character, you will get an autocomplete list of all symbols and footprints available in the KiCad standard library. You can also input your own value.
If you want to keep custom suggestions across updates, open the server settings page and use the "Autocomplete settings" page. There you can edit `public/kicad/footprints_custom.txt` and `public/kicad/symbols_custom.txt` and enable the "Use custom autocomplete lists" option to use those files instead of the autogenerated defaults.
### Parts and category visibility

3
public/kicad/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
# They are user generated and should not be tracked by git
footprints_custom.txt
symbols_custom.txt

View file

@ -201,6 +201,10 @@ class BackupCommand extends Command
$config_dir = $this->project_dir.'/config';
$zip->addFile($config_dir.'/parameters.yaml', 'config/parameters.yaml');
$zip->addFile($config_dir.'/banner.md', 'config/banner.md');
//Add kicad custom footprints and symbols files
$zip->addFile($this->project_dir . '/public/kicad/footprints_custom.txt', 'public/kicad/footprints_custom.txt');
$zip->addFile($this->project_dir . '/public/kicad/symbols_custom.txt', 'public/kicad/symbols_custom.txt');
}
protected function backupAttachments(ZipFile $zip, SymfonyStyle $io): void

View file

@ -0,0 +1,88 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2024 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace App\Controller;
use App\Form\Settings\KicadListEditorType;
use App\Settings\MiscSettings\KiCadEDASettings;
use App\Services\EDA\KicadListFileManager;
use Jbtronics\SettingsBundle\Exception\SettingsNotValidException;
use Jbtronics\SettingsBundle\Manager\SettingsManagerInterface;
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
{
public function __construct(
private readonly SettingsManagerInterface $settingsManager,
) {
}
#[Route('/settings/misc/kicad-lists', name: 'settings_kicad_lists')]
public function __invoke(Request $request, KicadListFileManager $fileManager): Response
{
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
$this->denyAccessUnlessGranted('@config.change_system_settings');
/** @var KiCadEDASettings $settings */
$settings = $this->settingsManager->createTemporaryCopy(KiCadEDASettings::class);
$form = $this->createForm(KicadListEditorType::class, [
'useCustomList' => $settings->useCustomList,
'customFootprints' => $fileManager->getCustomFootprintsContent(),
'customSymbols' => $fileManager->getCustomSymbolsContent(),
], [
'default_footprints' => $fileManager->getFootprintsContent(),
'default_symbols' => $fileManager->getSymbolsContent(),
]);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
try {
$fileManager->saveCustom($data['customFootprints'], $data['customSymbols']);
$settings->useCustomList = (bool) $data['useCustomList'];
$this->settingsManager->mergeTemporaryCopy($settings);
$this->settingsManager->save($settings);
$this->addFlash('success', t('settings.flash.saved'));
return $this->redirectToRoute('settings_kicad_lists');
} catch (RuntimeException|SettingsNotValidException $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,
]);
}
}

View file

@ -44,6 +44,7 @@ class SettingsController extends AbstractController
public function systemSettings(Request $request, TagAwareCacheInterface $cache): Response
{
$this->denyAccessUnlessGranted('@config.change_system_settings');
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
//Create a clone of the settings object
$settings = $this->settingsManager->createTemporaryCopy(AppSettings::class);

View file

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace App\Form\Part\EDA;
use App\Form\Type\StaticFileAutocompleteType;
use App\Settings\MiscSettings\KiCadEDASettings;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
@ -39,6 +40,13 @@ class KicadFieldAutocompleteType extends AbstractType
//Do not use a leading slash here! otherwise it will not work under prefixed reverse proxies
public const FOOTPRINT_PATH = 'kicad/footprints.txt';
public const SYMBOL_PATH = 'kicad/symbols.txt';
public const CUSTOM_FOOTPRINT_PATH = 'kicad/footprints_custom.txt';
public const CUSTOM_SYMBOL_PATH = 'kicad/symbols_custom.txt';
public function __construct(
private readonly KiCadEDASettings $kiCadEDASettings,
) {
}
public function configureOptions(OptionsResolver $resolver): void
{
@ -47,8 +55,8 @@ class KicadFieldAutocompleteType extends AbstractType
$resolver->setDefaults([
'file' => fn(Options $options) => match ($options['type']) {
self::TYPE_FOOTPRINT => self::FOOTPRINT_PATH,
self::TYPE_SYMBOL => self::SYMBOL_PATH,
self::TYPE_FOOTPRINT => $this->kiCadEDASettings->useCustomList ? self::CUSTOM_FOOTPRINT_PATH : self::FOOTPRINT_PATH,
self::TYPE_SYMBOL => $this->kiCadEDASettings->useCustomList ? self::CUSTOM_SYMBOL_PATH : self::SYMBOL_PATH,
default => throw new \InvalidArgumentException('Invalid type'),
}
]);
@ -58,4 +66,4 @@ class KicadFieldAutocompleteType extends AbstractType
{
return StaticFileAutocompleteType::class;
}
}
}

View file

@ -0,0 +1,103 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2024 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace App\Form\Settings;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* Form type for editing the custom KiCad footprints and symbols lists.
*/
final class KicadListEditorType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('useCustomList', CheckboxType::class, [
'label' => 'settings.misc.kicad_eda.use_custom_list',
'help' => 'settings.misc.kicad_eda.use_custom_list.help',
'required' => false,
])
->add('customFootprints', TextareaType::class, [
'label' => 'settings.misc.kicad_eda.editor.custom_footprints',
'help' => 'settings.misc.kicad_eda.editor.footprints.help',
'attr' => [
'rows' => 16,
'spellcheck' => 'false',
'class' => 'font-monospace',
],
])
->add('defaultFootprints', TextareaType::class, [
'label' => 'settings.misc.kicad_eda.editor.default_footprints',
'help' => 'settings.misc.kicad_eda.editor.default_files_help',
'disabled' => true,
'mapped' => false,
'data' => $options['default_footprints'],
'attr' => [
'rows' => 16,
'spellcheck' => 'false',
'class' => 'font-monospace',
'readonly' => 'readonly',
],
])
->add('customSymbols', TextareaType::class, [
'label' => 'settings.misc.kicad_eda.editor.custom_symbols',
'help' => 'settings.misc.kicad_eda.editor.symbols.help',
'attr' => [
'rows' => 16,
'spellcheck' => 'false',
'class' => 'font-monospace',
],
])
->add('defaultSymbols', TextareaType::class, [
'label' => 'settings.misc.kicad_eda.editor.default_symbols',
'help' => 'settings.misc.kicad_eda.editor.default_files_help',
'disabled' => true,
'mapped' => false,
'data' => $options['default_symbols'],
'attr' => [
'rows' => 16,
'spellcheck' => 'false',
'class' => 'font-monospace',
'readonly' => 'readonly',
],
])
->add('save', SubmitType::class, [
'label' => 'save',
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'default_footprints' => '',
'default_symbols' => '',
]);
$resolver->setAllowedTypes('default_footprints', 'string');
$resolver->setAllowedTypes('default_symbols', 'string');
}
}

View file

@ -0,0 +1,158 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2024 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace App\Services\EDA;
use RuntimeException;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
/**
* Manages the KiCad footprints and symbols list files, including reading, writing and ensuring their existence.
*/
final class KicadListFileManager implements CacheWarmerInterface
{
private const FOOTPRINTS_PATH = '/public/kicad/footprints.txt';
private const SYMBOLS_PATH = '/public/kicad/symbols.txt';
private const CUSTOM_FOOTPRINTS_PATH = '/public/kicad/footprints_custom.txt';
private const CUSTOM_SYMBOLS_PATH = '/public/kicad/symbols_custom.txt';
private const CUSTOM_TEMPLATE = <<<'EOT'
# Custom KiCad autocomplete entries. One entry per line.
EOT;
public function __construct(
#[Autowire('%kernel.project_dir%')]
private readonly string $projectDir,
) {
}
public function getFootprintsContent(): string
{
return $this->readFile(self::FOOTPRINTS_PATH);
}
public function getCustomFootprintsContent(): string
{
//Ensure that the custom file exists, so that the UI can always display it without error.
$this->createCustomFileIfNotExists(self::CUSTOM_FOOTPRINTS_PATH);
return $this->readFile(self::CUSTOM_FOOTPRINTS_PATH);
}
public function getSymbolsContent(): string
{
return $this->readFile(self::SYMBOLS_PATH);
}
public function getCustomSymbolsContent(): string
{
//Ensure that the custom file exists, so that the UI can always display it without error.
$this->createCustomFileIfNotExists(self::CUSTOM_SYMBOLS_PATH);
return $this->readFile(self::CUSTOM_SYMBOLS_PATH);
}
public function saveCustom(string $footprints, string $symbols): void
{
$this->writeFile(self::CUSTOM_FOOTPRINTS_PATH, $this->normalizeContent($footprints));
$this->writeFile(self::CUSTOM_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;
}
private function createCustomFileIfNotExists(string $path): void
{
$fullPath = $this->projectDir . $path;
if (!is_file($fullPath)) {
if (file_put_contents($fullPath, self::CUSTOM_TEMPLATE, LOCK_EX) === false) {
throw new RuntimeException(sprintf('Failed to create custom footprints file "%s".', $fullPath));
}
}
}
/**
* Ensures that the custom footprints and symbols files exist, so that the UI can always display them without error.
* @return void
*/
public function createCustomFilesIfNotExist(): void
{
$this->createCustomFileIfNotExists(self::CUSTOM_FOOTPRINTS_PATH);
$this->createCustomFileIfNotExists(self::CUSTOM_SYMBOLS_PATH);
}
public function isOptional(): bool
{
return false;
}
/**
* Ensure that the custom footprints and symbols files exist and generate them on cache warmup, so that the frontend
* can always display them without error, even if the user has not yet visited the settings page.
*/
public function warmUp(string $cacheDir, ?string $buildDir = null): array
{
$this->createCustomFilesIfNotExist();
return [];
}
}

View file

@ -62,4 +62,10 @@ class KiCadEDASettings
)]
public bool $defaultOrderdetailsVisibility = false;
#[SettingsParameter(
label: new TM("settings.misc.kicad_eda.use_custom_list"),
description: new TM("settings.misc.kicad_eda.use_custom_list.help"),
)]
public bool $useCustomList = false;
}

View file

@ -0,0 +1,28 @@
{% extends "main_card.html.twig" %}
{% block title %}{% trans %}settings.misc.kicad_eda.editor.title{% endtrans %}{% endblock %}
{% block card_title %}<i class="fa-solid fa-pen-to-square fa-fw"></i> {% trans %}settings.misc.kicad_eda.editor.title{% endtrans %}{% endblock %}
{% block card_content %}
<p class="text-muted">
{% trans %}settings.misc.kicad_eda.editor.description{% endtrans %}
</p>
{{ form_start(form) }}
{{ form_row(form.useCustomList) }}
<div class="row g-3">
<div class="col-12 col-xl-6">
{{ form_row(form.customFootprints) }}
{{ form_row(form.customSymbols) }}
</div>
<div class="col-12 col-xl-6">
{{ form_row(form.defaultFootprints) }}
{{ form_row(form.defaultSymbols) }}
</div>
</div>
{{ form_row(form.save) }}
{{ form_end(form) }}
{% endblock %}

View file

@ -49,6 +49,15 @@
</div>
</div>
{{ form_widget(section_widget) }}
{% if section_widget.vars.name == 'kicadEDA' %}
<div class="row">
<div class="{{ offset_label }} col mt-2 ps-2">
<a href="{{ path('settings_kicad_lists') }}" class="btn btn-outline-secondary btn-sm">
<i class="fa-solid fa-pen-to-square fa-fw"></i> {% trans %}settings.misc.kicad_eda.editor.link{% endtrans %}
</a>
</div>
</div>
{% endif %}
</fieldset>
{% if not loop.last %}
<hr class="mx-0 mb-2 mt-2">

View file

@ -60,6 +60,7 @@ final class ApplicationAvailabilityFunctionalTest extends WebTestCase
//User related things
yield ['/user/settings'];
yield ['/user/info'];
yield ['/settings/misc/kicad-lists'];
//Login/logout
yield ['/login'];

View file

@ -0,0 +1,162 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2024 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace App\Tests\Controller;
use App\Entity\UserSystem\User;
use App\Settings\MiscSettings\KiCadEDASettings;
use Jbtronics\SettingsBundle\Manager\SettingsManagerInterface;
use PHPUnit\Framework\Attributes\Group;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
#[Group('slow')]
#[Group('DB')]
final class KicadListEditorControllerTest extends WebTestCase
{
private string $footprintsPath;
private string $symbolsPath;
private string $customFootprintsPath;
private string $customSymbolsPath;
private string $originalFootprints;
private string $originalSymbols;
private string $originalCustomFootprints;
private string $originalCustomSymbols;
private bool $originalUseCustomList;
protected function setUp(): void
{
parent::setUp();
$projectDir = dirname(__DIR__, 2);
$this->footprintsPath = $projectDir . '/public/kicad/footprints.txt';
$this->symbolsPath = $projectDir . '/public/kicad/symbols.txt';
$this->customFootprintsPath = $projectDir . '/public/kicad/footprints_custom.txt';
$this->customSymbolsPath = $projectDir . '/public/kicad/symbols_custom.txt';
$this->originalFootprints = (string) file_get_contents($this->footprintsPath);
$this->originalSymbols = (string) file_get_contents($this->symbolsPath);
$this->originalCustomFootprints = is_file($this->customFootprintsPath) ? (string) file_get_contents($this->customFootprintsPath) : '';
$this->originalCustomSymbols = is_file($this->customSymbolsPath) ? (string) file_get_contents($this->customSymbolsPath) : '';
static::bootKernel();
/** @var SettingsManagerInterface $settingsManager */
$settingsManager = static::getContainer()->get(SettingsManagerInterface::class);
/** @var KiCadEDASettings $settings */
$settings = $settingsManager->get(KiCadEDASettings::class);
$this->originalUseCustomList = $settings->useCustomList;
static::ensureKernelShutdown();
}
protected function tearDown(): void
{
file_put_contents($this->footprintsPath, $this->originalFootprints);
file_put_contents($this->symbolsPath, $this->originalSymbols);
file_put_contents($this->customFootprintsPath, $this->originalCustomFootprints);
file_put_contents($this->customSymbolsPath, $this->originalCustomSymbols);
static::bootKernel();
/** @var SettingsManagerInterface $settingsManager */
$settingsManager = static::getContainer()->get(SettingsManagerInterface::class);
/** @var KiCadEDASettings $settings */
$settings = $settingsManager->get(KiCadEDASettings::class);
$settings->useCustomList = $this->originalUseCustomList;
$settingsManager->save($settings);
static::ensureKernelShutdown();
parent::tearDown();
}
public function testEditorRequiresAuthentication(): void
{
$client = static::createClient();
$client->request('GET', '/en/settings/misc/kicad-lists');
$this->assertResponseStatusCodeSame(401);
}
public function testEditorAccessibleByAdmin(): void
{
$client = static::createClient();
$this->loginAsUser($client, 'admin');
$client->request('GET', '/en/settings/misc/kicad-lists');
$this->assertResponseIsSuccessful();
$this->assertSelectorExists('form[name="kicad_list_editor"]');
}
public function testEditorShowsDefaultAndCustomFiles(): void
{
$client = static::createClient();
$this->loginAsUser($client, 'admin');
file_put_contents($this->footprintsPath, "DefaultFootprint\n");
file_put_contents($this->symbolsPath, "DefaultSymbol\n");
file_put_contents($this->customFootprintsPath, "CustomFootprint\n");
file_put_contents($this->customSymbolsPath, "CustomSymbol\n");
$crawler = $client->request('GET', '/en/settings/misc/kicad-lists');
$this->assertSame("CustomFootprint\n", $crawler->filter('#kicad_list_editor_customFootprints')->getNode(0)->nodeValue);
$this->assertSame("CustomSymbol\n", $crawler->filter('#kicad_list_editor_customSymbols')->getNode(0)->nodeValue);
$this->assertSame("DefaultFootprint\n", $crawler->filter('#kicad_list_editor_defaultFootprints')->getNode(0)->nodeValue);
$this->assertSame("DefaultSymbol\n", $crawler->filter('#kicad_list_editor_defaultSymbols')->getNode(0)->nodeValue);
}
public function testEditorSavesCustomFilesAndSetting(): void
{
$client = static::createClient();
$this->loginAsUser($client, 'admin');
$crawler = $client->request('GET', '/en/settings/misc/kicad-lists');
$form = $crawler->filter('form[name="kicad_list_editor"]')->form();
$form['kicad_list_editor[customFootprints]'] = "Package_DIP:DIP-8_W7.62mm\n";
$form['kicad_list_editor[customSymbols]'] = "Device:R\n";
$form['kicad_list_editor[useCustomList]']->tick();
$client->submit($form);
$this->assertResponseRedirects('/en/settings/misc/kicad-lists');
$this->assertSame("Package_DIP:DIP-8_W7.62mm\n", (string) file_get_contents($this->customFootprintsPath));
$this->assertSame("Device:R\n", (string) file_get_contents($this->customSymbolsPath));
$this->assertSame($this->originalFootprints, (string) file_get_contents($this->footprintsPath));
$this->assertSame($this->originalSymbols, (string) file_get_contents($this->symbolsPath));
/** @var SettingsManagerInterface $settingsManager */
$settingsManager = $client->getContainer()->get(SettingsManagerInterface::class);
/** @var KiCadEDASettings $settings */
$settings = $settingsManager->reload(KiCadEDASettings::class);
$this->assertTrue($settings->useCustomList);
}
private function loginAsUser($client, string $username): void
{
$entityManager = $client->getContainer()->get('doctrine')->getManager();
$userRepository = $entityManager->getRepository(User::class);
$user = $userRepository->findOneBy(['name' => $username]);
if (!$user) {
$this->markTestSkipped(sprintf('User "%s" not found in fixtures', $username));
}
$client->loginUser($user);
}
}

View file

@ -10029,6 +10029,90 @@ Please note, that you can not impersonate a disabled user. If you try you will g
<target>When enabled, the datasheet field in KiCad will link to the actual PDF file (if found). When disabled, it will link to the Part-DB page instead. The Part-DB page link is always available as a separate "Part-DB URL" field.</target>
</segment>
</unit>
<unit id="e2e7mR1" name="settings.misc.kicad_eda.editor.title">
<segment state="translated">
<source>settings.misc.kicad_eda.editor.title</source>
<target>KiCad autocomplete lists</target>
</segment>
</unit>
<unit id="qjv1VVx" name="settings.misc.kicad_eda.editor.link">
<segment state="translated">
<source>settings.misc.kicad_eda.editor.link</source>
<target>Autocomplete settings</target>
</segment>
</unit>
<unit id="f0qkcqg" name="settings.misc.kicad_eda.editor.description">
<segment state="translated">
<source>settings.misc.kicad_eda.editor.description</source>
<target>Configure whether KiCad autocomplete uses the autogenerated default lists or your custom override files. The custom files are editable here, while the default files are shown read-only for reference.</target>
</segment>
</unit>
<unit id="AS3yDlb" name="settings.misc.kicad_eda.editor.footprints">
<segment state="translated">
<source>settings.misc.kicad_eda.editor.footprints</source>
<target>Footprints list</target>
</segment>
</unit>
<unit id="Jj_YR7n" name="settings.misc.kicad_eda.editor.footprints.help">
<segment state="translated">
<source>settings.misc.kicad_eda.editor.footprints.help</source>
<target>One entry per line. Used as autocomplete suggestions for KiCad footprint fields.</target>
</segment>
</unit>
<unit id="ELd3KQK" name="settings.misc.kicad_eda.editor.symbols">
<segment state="translated">
<source>settings.misc.kicad_eda.editor.symbols</source>
<target>Symbols list</target>
</segment>
</unit>
<unit id="A9TOJgM" name="settings.misc.kicad_eda.editor.symbols.help">
<segment state="translated">
<source>settings.misc.kicad_eda.editor.symbols.help</source>
<target>One entry per line. Used as autocomplete suggestions for KiCad symbol fields.</target>
</segment>
</unit>
<unit id="tWYlL0u" name="settings.misc.kicad_eda.use_custom_list">
<segment state="translated">
<source>settings.misc.kicad_eda.use_custom_list</source>
<target>Use custom autocomplete lists</target>
</segment>
</unit>
<unit id="v0LK7n6" name="settings.misc.kicad_eda.use_custom_list.help">
<segment state="translated">
<source>settings.misc.kicad_eda.use_custom_list.help</source>
<target>When enabled, KiCad autocomplete uses public/kicad/footprints_custom.txt and public/kicad/symbols_custom.txt instead of the autogenerated default files.</target>
</segment>
</unit>
<unit id="Yl_fqfV" name="settings.misc.kicad_eda.editor.custom_footprints">
<segment state="translated">
<source>settings.misc.kicad_eda.editor.custom_footprints</source>
<target>Custom footprints list</target>
</segment>
</unit>
<unit id="GuD2JcQ" name="settings.misc.kicad_eda.editor.custom_symbols">
<segment state="translated">
<source>settings.misc.kicad_eda.editor.custom_symbols</source>
<target>Custom symbols list</target>
</segment>
</unit>
<unit id="k6m9b5F" name="settings.misc.kicad_eda.editor.default_footprints">
<segment state="translated">
<source>settings.misc.kicad_eda.editor.default_footprints</source>
<target>Default footprints list</target>
</segment>
</unit>
<unit id="bKkF8mM" name="settings.misc.kicad_eda.editor.default_symbols">
<segment state="translated">
<source>settings.misc.kicad_eda.editor.default_symbols</source>
<target>Default symbols list</target>
</segment>
</unit>
<unit id="mIj_i4E" name="settings.misc.kicad_eda.editor.default_files_help">
<segment state="translated">
<source>settings.misc.kicad_eda.editor.default_files_help</source>
<target>Autogenerated file shown for reference only. Changes must be made in the custom list.</target>
</segment>
</unit>
<unit id="VwvmcWE" name="settings.behavior.sidebar">
<segment state="translated">
<source>settings.behavior.sidebar</source>