diff --git a/assets/controllers/elements/datatables/parts_controller.js b/assets/controllers/elements/datatables/parts_controller.js
index 1fe11a20..c43fa276 100644
--- a/assets/controllers/elements/datatables/parts_controller.js
+++ b/assets/controllers/elements/datatables/parts_controller.js
@@ -45,8 +45,10 @@ export default class extends DatatablesController {
//Hide/Unhide panel with the selection tools
if (count > 0) {
selectPanel.classList.remove('d-none');
+ selectPanel.classList.add('sticky-select-bar');
} else {
selectPanel.classList.add('d-none');
+ selectPanel.classList.remove('sticky-select-bar');
}
//Update selection count text
diff --git a/assets/css/app/images.css b/assets/css/app/images.css
index 214776e7..0212a85b 100644
--- a/assets/css/app/images.css
+++ b/assets/css/app/images.css
@@ -18,8 +18,8 @@
*/
.hoverpic {
- min-width: 10px;
- max-width: 30px;
+ min-width: var(--table-image-preview-min-size, 20px);
+ max-width: var(--table-image-preview-max-size, 35px);
display: block;
margin-left: auto;
margin-right: auto;
@@ -49,7 +49,7 @@
}
.part-table-image {
- max-height: 40px;
+ max-height: calc(1.2*var(--table-image-preview-max-size, 35px)); /** Aspect ratio of maximum 1.2 */
object-fit: contain;
}
diff --git a/assets/css/app/tables.css b/assets/css/app/tables.css
index ae892f50..8d4b200c 100644
--- a/assets/css/app/tables.css
+++ b/assets/css/app/tables.css
@@ -17,6 +17,16 @@
* along with this program. If not, see .
*/
+/****************************************
+ * Action bar
+ ****************************************/
+
+.sticky-select-bar {
+ position: sticky;
+ top: 120px;
+ z-index: 1000; /* Ensure the bar is above other content */
+}
+
/****************************************
* Tables
****************************************/
@@ -109,4 +119,4 @@ Classes for Datatables export
#export-messageTop,
.export-helper{
display: none;
-}
\ No newline at end of file
+}
diff --git a/composer.lock b/composer.lock
index 6de15830..1f67b80f 100644
--- a/composer.lock
+++ b/composer.lock
@@ -7513,16 +7513,16 @@
},
{
"name": "part-db/label-fonts",
- "version": "v1.1.0",
+ "version": "v1.2.0",
"source": {
"type": "git",
"url": "https://github.com/Part-DB/label-fonts.git",
- "reference": "77c84b70ed3bb005df15f30ff835ddec490394b9"
+ "reference": "c85aeb051d6492961a2c59bc291979f15ce60e88"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Part-DB/label-fonts/zipball/77c84b70ed3bb005df15f30ff835ddec490394b9",
- "reference": "77c84b70ed3bb005df15f30ff835ddec490394b9",
+ "url": "https://api.github.com/repos/Part-DB/label-fonts/zipball/c85aeb051d6492961a2c59bc291979f15ce60e88",
+ "reference": "c85aeb051d6492961a2c59bc291979f15ce60e88",
"shasum": ""
},
"type": "library",
@@ -7545,9 +7545,9 @@
],
"support": {
"issues": "https://github.com/Part-DB/label-fonts/issues",
- "source": "https://github.com/Part-DB/label-fonts/tree/v1.1.0"
+ "source": "https://github.com/Part-DB/label-fonts/tree/v1.2.0"
},
- "time": "2024-02-08T21:44:38+00:00"
+ "time": "2025-09-07T15:42:51+00:00"
},
{
"name": "part-db/swap",
@@ -17883,16 +17883,16 @@
},
{
"name": "phpstan/phpstan-doctrine",
- "version": "2.0.4",
+ "version": "2.0.5",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan-doctrine.git",
- "reference": "6271e66ce37545bd2edcddbe6bcbdd3b665ab7b8"
+ "reference": "eeff19808f8ae3a6f7c4e43e388a2848eb2b0865"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/6271e66ce37545bd2edcddbe6bcbdd3b665ab7b8",
- "reference": "6271e66ce37545bd2edcddbe6bcbdd3b665ab7b8",
+ "url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/eeff19808f8ae3a6f7c4e43e388a2848eb2b0865",
+ "reference": "eeff19808f8ae3a6f7c4e43e388a2848eb2b0865",
"shasum": ""
},
"require": {
@@ -17949,9 +17949,9 @@
"description": "Doctrine extensions for PHPStan",
"support": {
"issues": "https://github.com/phpstan/phpstan-doctrine/issues",
- "source": "https://github.com/phpstan/phpstan-doctrine/tree/2.0.4"
+ "source": "https://github.com/phpstan/phpstan-doctrine/tree/2.0.5"
},
- "time": "2025-07-17T11:57:55+00:00"
+ "time": "2025-09-07T11:52:30+00:00"
},
{
"name": "phpstan/phpstan-strict-rules",
@@ -18003,16 +18003,16 @@
},
{
"name": "phpstan/phpstan-symfony",
- "version": "2.0.7",
+ "version": "2.0.8",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan-symfony.git",
- "reference": "392f7ab8f52a0a776977be4e62535358c28e1b15"
+ "reference": "8820c22d785c235f69bb48da3d41e688bc8a1796"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/392f7ab8f52a0a776977be4e62535358c28e1b15",
- "reference": "392f7ab8f52a0a776977be4e62535358c28e1b15",
+ "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/8820c22d785c235f69bb48da3d41e688bc8a1796",
+ "reference": "8820c22d785c235f69bb48da3d41e688bc8a1796",
"shasum": ""
},
"require": {
@@ -18068,9 +18068,9 @@
"description": "Symfony Framework extensions and rules for PHPStan",
"support": {
"issues": "https://github.com/phpstan/phpstan-symfony/issues",
- "source": "https://github.com/phpstan/phpstan-symfony/tree/2.0.7"
+ "source": "https://github.com/phpstan/phpstan-symfony/tree/2.0.8"
},
- "time": "2025-07-22T09:40:57+00:00"
+ "time": "2025-09-07T06:55:50+00:00"
},
{
"name": "phpunit/php-code-coverage",
diff --git a/config/packages/settings.yaml b/config/packages/settings.yaml
index 05e21636..c16d1804 100644
--- a/config/packages/settings.yaml
+++ b/config/packages/settings.yaml
@@ -5,4 +5,11 @@ jbtronics_settings:
default_cacheable: true
orm_storage:
- default_entity_class: App\Entity\SettingsEntry
\ No newline at end of file
+ default_entity_class: App\Entity\SettingsEntry
+
+
+# Disable caching for development environment
+when@dev:
+ jbtronics_settings:
+ cache:
+ default_cacheable: false
diff --git a/src/Controller/AttachmentFileController.php b/src/Controller/AttachmentFileController.php
index 7917e97f..81369e12 100644
--- a/src/Controller/AttachmentFileController.php
+++ b/src/Controller/AttachmentFileController.php
@@ -24,6 +24,7 @@ namespace App\Controller;
use App\DataTables\AttachmentDataTable;
use App\DataTables\Filters\AttachmentFilter;
+use App\DataTables\PartsDataTable;
use App\Entity\Attachments\Attachment;
use App\Form\Filters\AttachmentFilterType;
use App\Services\Attachments\AttachmentManager;
@@ -112,7 +113,7 @@ class AttachmentFileController extends AbstractController
$filterForm->handleRequest($formRequest);
- $table = $dataTableFactory->createFromType(AttachmentDataTable::class, ['filter' => $filter], ['pageLength' => $tableSettings->fullDefaultPageSize])
+ $table = $dataTableFactory->createFromType(AttachmentDataTable::class, ['filter' => $filter], ['pageLength' => $tableSettings->fullDefaultPageSize, 'lengthMenu' => PartsDataTable::LENGTH_MENU])
->handleRequest($request);
if ($table->isCallback()) {
diff --git a/src/Controller/InfoProviderController.php b/src/Controller/InfoProviderController.php
index a6e886e6..dae8213e 100644
--- a/src/Controller/InfoProviderController.php
+++ b/src/Controller/InfoProviderController.php
@@ -30,6 +30,7 @@ use App\Services\InfoProviderSystem\ExistingPartFinder;
use App\Services\InfoProviderSystem\PartInfoRetriever;
use App\Services\InfoProviderSystem\ProviderRegistry;
use App\Settings\AppSettings;
+use App\Settings\InfoProviderSystem\InfoProviderGeneralSettings;
use Doctrine\ORM\EntityManagerInterface;
use Jbtronics\SettingsBundle\Form\SettingsFormFactoryInterface;
use Jbtronics\SettingsBundle\Manager\SettingsManagerInterface;
@@ -113,7 +114,7 @@ class InfoProviderController extends AbstractController
#[Route('/search', name: 'info_providers_search')]
#[Route('/update/{target}', name: 'info_providers_update_part_search')]
- public function search(Request $request, #[MapEntity(id: 'target')] ?Part $update_target, LoggerInterface $exceptionLogger): Response
+ public function search(Request $request, #[MapEntity(id: 'target')] ?Part $update_target, LoggerInterface $exceptionLogger, InfoProviderGeneralSettings $infoProviderSettings): Response
{
$this->denyAccessUnlessGranted('@info_providers.create_parts');
@@ -144,6 +145,23 @@ class InfoProviderController extends AbstractController
}
}
+ //If the providers form is still empty, use our default value from the settings
+ if (count($form->get('providers')->getData() ?? []) === 0) {
+ $default_providers = $infoProviderSettings->defaultSearchProviders;
+ $provider_objects = [];
+ foreach ($default_providers as $provider_key) {
+ try {
+ $tmp = $this->providerRegistry->getProviderByKey($provider_key);
+ if ($tmp->isActive()) {
+ $provider_objects[] = $tmp;
+ }
+ } catch (\InvalidArgumentException $e) {
+ //If the provider is not found, just ignore it
+ }
+ }
+ $form->get('providers')->setData($provider_objects);
+ }
+
if ($form->isSubmitted() && $form->isValid()) {
$keyword = $form->get('keyword')->getData();
$providers = $form->get('providers')->getData();
diff --git a/src/Controller/PartController.php b/src/Controller/PartController.php
index b11a5c90..6708ed4c 100644
--- a/src/Controller/PartController.php
+++ b/src/Controller/PartController.php
@@ -46,6 +46,7 @@ use App\Services\Parameters\ParameterExtractor;
use App\Services\Parts\PartLotWithdrawAddHelper;
use App\Services\Parts\PricedetailHelper;
use App\Services\ProjectSystem\ProjectBuildPartHelper;
+use App\Settings\BehaviorSettings\PartInfoSettings;
use DateTime;
use Doctrine\ORM\EntityManagerInterface;
use Exception;
@@ -69,7 +70,7 @@ class PartController extends AbstractController
protected PartPreviewGenerator $partPreviewGenerator,
private readonly TranslatorInterface $translator,
private readonly AttachmentSubmitHandler $attachmentSubmitHandler, private readonly EntityManagerInterface $em,
- protected EventCommentHelper $commentHelper)
+ protected EventCommentHelper $commentHelper, private readonly PartInfoSettings $partInfoSettings)
{
}
@@ -119,8 +120,8 @@ class PartController extends AbstractController
'pricedetail_helper' => $this->pricedetailHelper,
'pictures' => $this->partPreviewGenerator->getPreviewAttachments($part),
'timeTravel' => $timeTravel_timestamp,
- 'description_params' => $parameterExtractor->extractParameters($part->getDescription()),
- 'comment_params' => $parameterExtractor->extractParameters($part->getComment()),
+ 'description_params' => $this->partInfoSettings->extractParamsFromDescription ? $parameterExtractor->extractParameters($part->getDescription()) : [],
+ 'comment_params' => $this->partInfoSettings->extractParamsFromNotes ? $parameterExtractor->extractParameters($part->getComment()) : [],
'withdraw_add_helper' => $withdrawAddHelper,
]
);
diff --git a/src/Controller/PartListsController.php b/src/Controller/PartListsController.php
index f6836ddc..b2df18c1 100644
--- a/src/Controller/PartListsController.php
+++ b/src/Controller/PartListsController.php
@@ -161,7 +161,9 @@ class PartListsController extends AbstractController
$filterForm->handleRequest($formRequest);
- $table = $this->dataTableFactory->createFromType(PartsDataTable::class, array_merge(['filter' => $filter], $additional_table_vars), ['pageLength' => $this->tableSettings->fullDefaultPageSize])
+ $table = $this->dataTableFactory->createFromType(PartsDataTable::class, array_merge(
+ ['filter' => $filter], $additional_table_vars),
+ ['pageLength' => $this->tableSettings->fullDefaultPageSize, 'lengthMenu' => PartsDataTable::LENGTH_MENU])
->handleRequest($request);
if ($table->isCallback()) {
diff --git a/src/Form/InfoProviderSystem/ProviderSelectType.php b/src/Form/InfoProviderSystem/ProviderSelectType.php
index a9373390..95e10791 100644
--- a/src/Form/InfoProviderSystem/ProviderSelectType.php
+++ b/src/Form/InfoProviderSystem/ProviderSelectType.php
@@ -28,6 +28,7 @@ use App\Services\InfoProviderSystem\Providers\InfoProviderInterface;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\ChoiceList\ChoiceList;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
+use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ProviderSelectType extends AbstractType
@@ -44,13 +45,43 @@ class ProviderSelectType extends AbstractType
public function configureOptions(OptionsResolver $resolver): void
{
- $resolver->setDefaults([
- 'choices' => $this->providerRegistry->getActiveProviders(),
- 'choice_label' => ChoiceList::label($this, static fn (?InfoProviderInterface $choice) => $choice?->getProviderInfo()['name']),
- 'choice_value' => ChoiceList::value($this, static fn(?InfoProviderInterface $choice) => $choice?->getProviderKey()),
+ $providers = $this->providerRegistry->getActiveProviders();
- 'multiple' => true,
- ]);
+ $resolver->setDefault('input', 'object');
+ $resolver->setAllowedTypes('input', 'string');
+ //Either the form returns the provider objects or their keys
+ $resolver->setAllowedValues('input', ['object', 'string']);
+ $resolver->setDefault('multiple', true);
+
+ $resolver->setDefault('choices', function (Options $options) use ($providers) {
+ if ('object' === $options['input']) {
+ return $this->providerRegistry->getActiveProviders();
+ }
+
+ $tmp = [];
+ foreach ($providers as $provider) {
+ $name = $provider->getProviderInfo()['name'];
+ $tmp[$name] = $provider->getProviderKey();
+ }
+
+ return $tmp;
+ });
+
+ //The choice_label and choice_value only needs to be set if we want the objects
+ $resolver->setDefault('choice_label', function (Options $options){
+ if ('object' === $options['input']) {
+ return ChoiceList::label($this, static fn (?InfoProviderInterface $choice) => $choice?->getProviderInfo()['name']);
+ }
+
+ return null;
+ });
+ $resolver->setDefault('choice_value', function (Options $options) {
+ if ('object' === $options['input']) {
+ return ChoiceList::value($this, static fn(?InfoProviderInterface $choice) => $choice?->getProviderKey());
+ }
+
+ return null;
+ });
}
-}
\ No newline at end of file
+}
diff --git a/src/Settings/BehaviorSettings/PartInfoSettings.php b/src/Settings/BehaviorSettings/PartInfoSettings.php
index 4c44b9bb..f017c846 100644
--- a/src/Settings/BehaviorSettings/PartInfoSettings.php
+++ b/src/Settings/BehaviorSettings/PartInfoSettings.php
@@ -40,4 +40,10 @@ class PartInfoSettings
#[SettingsParameter(label: new TM("settings.behavior.part_info.show_part_image_overlay"), description: new TM("settings.behavior.part_info.show_part_image_overlay.help"),
envVar: "bool:SHOW_PART_IMAGE_OVERLAY", envVarMode: EnvVarMode::OVERWRITE)]
public bool $showPartImageOverlay = true;
-}
\ No newline at end of file
+
+ #[SettingsParameter(label: new TM("settings.behavior.part_info.extract_params_from_description"))]
+ public bool $extractParamsFromDescription = true;
+
+ #[SettingsParameter(label: new TM("settings.behavior.part_info.extract_params_from_notes"))]
+ public bool $extractParamsFromNotes = true;
+}
diff --git a/src/Settings/BehaviorSettings/TableSettings.php b/src/Settings/BehaviorSettings/TableSettings.php
index 7b4e7912..b6964876 100644
--- a/src/Settings/BehaviorSettings/TableSettings.php
+++ b/src/Settings/BehaviorSettings/TableSettings.php
@@ -70,6 +70,20 @@ class TableSettings
PartTableColumns::CATEGORY, PartTableColumns::FOOTPRINT, PartTableColumns::MANUFACTURER,
PartTableColumns::LOCATION, PartTableColumns::AMOUNT];
+ #[SettingsParameter(label: new TM("settings.behavior.table.preview_image_min_width"),
+ formOptions: ['attr' => ['min' => 1, 'max' => 100]],
+ envVar: "int:TABLE_IMAGE_PREVIEW_MIN_SIZE", envVarMode: EnvVarMode::OVERWRITE
+ )]
+ #[Assert\Range(min: 1, max: 100)]
+ public int $previewImageMinWidth = 20;
+
+ #[SettingsParameter(label: new TM("settings.behavior.table.preview_image_max_width"),
+ formOptions: ['attr' => ['min' => 1, 'max' => 100]],
+ envVar: "int:TABLE_IMAGE_PREVIEW_MAX_SIZE", envVarMode: EnvVarMode::OVERWRITE
+ )]
+ #[Assert\Range(min: 1, max: 100)]
+ #[Assert\GreaterThanOrEqual(propertyPath: 'previewImageMinWidth')]
+ public int $previewImageMaxWidth = 35;
public static function mapPartsDefaultColumnsEnv(string $columns): array
{
@@ -87,4 +101,4 @@ class TableSettings
return $ret;
}
-}
\ No newline at end of file
+}
diff --git a/src/Settings/InfoProviderSystem/InfoProviderGeneralSettings.php b/src/Settings/InfoProviderSystem/InfoProviderGeneralSettings.php
new file mode 100644
index 00000000..03fff0bf
--- /dev/null
+++ b/src/Settings/InfoProviderSystem/InfoProviderGeneralSettings.php
@@ -0,0 +1,45 @@
+.
+ */
+
+declare(strict_types=1);
+
+
+namespace App\Settings\InfoProviderSystem;
+
+use App\Form\InfoProviderSystem\ProviderSelectType;
+use App\Settings\SettingsIcon;
+use Jbtronics\SettingsBundle\ParameterTypes\ArrayType;
+use Jbtronics\SettingsBundle\ParameterTypes\StringType;
+use Jbtronics\SettingsBundle\Settings\Settings;
+use Jbtronics\SettingsBundle\Settings\SettingsParameter;
+use Symfony\Component\Translation\TranslatableMessage as TM;
+
+#[Settings(label: new TM("settings.ips.general"))]
+#[SettingsIcon("fa-magnifying-glass")]
+class InfoProviderGeneralSettings
+{
+ /**
+ * @var string[]
+ */
+ #[SettingsParameter(type: ArrayType::class, label: new TM("settings.ips.default_providers"),
+ description: new TM("settings.ips.default_providers.help"), options: ['type' => StringType::class],
+ formType: ProviderSelectType::class, formOptions: ['input' => 'string'])]
+ public array $defaultSearchProviders = [];
+}
diff --git a/src/Settings/InfoProviderSystem/InfoProviderSettings.php b/src/Settings/InfoProviderSystem/InfoProviderSettings.php
index 3c7159cb..c223bd88 100644
--- a/src/Settings/InfoProviderSystem/InfoProviderSettings.php
+++ b/src/Settings/InfoProviderSystem/InfoProviderSettings.php
@@ -25,6 +25,7 @@ namespace App\Settings\InfoProviderSystem;
use Jbtronics\SettingsBundle\Settings\EmbeddedSettings;
use Jbtronics\SettingsBundle\Settings\Settings;
+use Jbtronics\SettingsBundle\Settings\SettingsParameter;
use Jbtronics\SettingsBundle\Settings\SettingsTrait;
#[Settings()]
@@ -32,6 +33,9 @@ class InfoProviderSettings
{
use SettingsTrait;
+ #[EmbeddedSettings]
+ public ?InfoProviderGeneralSettings $general = null;
+
#[EmbeddedSettings]
public ?DigikeySettings $digikey = null;
@@ -58,4 +62,4 @@ class InfoProviderSettings
#[EmbeddedSettings]
public ?PollinSettings $pollin = null;
-}
\ No newline at end of file
+}
diff --git a/src/Settings/SystemSettings/CustomizationSettings.php b/src/Settings/SystemSettings/CustomizationSettings.php
index d7e92a51..623e6187 100644
--- a/src/Settings/SystemSettings/CustomizationSettings.php
+++ b/src/Settings/SystemSettings/CustomizationSettings.php
@@ -28,10 +28,13 @@ use App\Form\Type\ThemeChoiceType;
use App\Settings\SettingsIcon;
use App\Validator\Constraints\ValidTheme;
use Jbtronics\SettingsBundle\Metadata\EnvVarMode;
+use Jbtronics\SettingsBundle\ParameterTypes\ArrayType;
+use Jbtronics\SettingsBundle\ParameterTypes\EnumType;
use Jbtronics\SettingsBundle\Settings\Settings;
use Jbtronics\SettingsBundle\Settings\SettingsParameter;
use Jbtronics\SettingsBundle\Settings\SettingsTrait;
use Symfony\Component\Translation\TranslatableMessage as TM;
+use Symfony\Component\Validator\Constraints as Assert;
#[Settings(name: "customization", label: new TM("settings.system.customization"))]
#[SettingsIcon("fa-paint-roller")]
@@ -46,6 +49,13 @@ class CustomizationSettings
)]
public string $instanceName = "Part-DB";
+ #[SettingsParameter(
+ label: new TM("settings.system.customization.theme"),
+ formType: ThemeChoiceType::class, formOptions: ['placeholder' => false]
+ )]
+ #[ValidTheme]
+ public string $theme = 'bootstrap';
+
#[SettingsParameter(
label: new TM("settings.system.customization.banner"),
formType: RichTextEditorType::class, formOptions: ['mode' => 'markdown-full'],
@@ -53,10 +63,22 @@ class CustomizationSettings
)]
public ?string $banner = null;
- #[SettingsParameter(
- label: new TM("settings.system.customization.theme"),
- formType: ThemeChoiceType::class, formOptions: ['placeholder' => false]
+ /**
+ * @var HomepageItems[] The items to show in the sidebar.
+ */
+ #[SettingsParameter(ArrayType::class,
+ label: new TM("settings.behavior.hompepage.items"),
+ description: new TM("settings.behavior.homepage.items.help"),
+ options: ['type' => EnumType::class, 'options' => ['class' => HomepageItems::class]],
+ formType: \Symfony\Component\Form\Extension\Core\Type\EnumType::class,
+ formOptions: ['class' => HomepageItems::class, 'multiple' => true, 'ordered' => true]
)]
- #[ValidTheme]
- public string $theme = 'bootstrap';
+ #[Assert\NotBlank()]
+ #[Assert\Unique()]
+ public array $homepageitems = [HomepageItems::SEARCH, HomepageItems::BANNER, HomepageItems::FIRST_STEPS, HomepageItems::LICENSE, HomepageItems::LAST_ACTIVITY];
+
+ #[SettingsParameter(
+ label: new TM("settings.system.customization.showVersionOnHomepage")
+ )]
+ public bool $showVersionOnHomepage = true;
}
diff --git a/src/Settings/SystemSettings/HomepageItems.php b/src/Settings/SystemSettings/HomepageItems.php
new file mode 100644
index 00000000..7366dfa2
--- /dev/null
+++ b/src/Settings/SystemSettings/HomepageItems.php
@@ -0,0 +1,51 @@
+.
+ */
+
+declare(strict_types=1);
+
+
+namespace App\Settings\SystemSettings;
+
+use Symfony\Contracts\Translation\TranslatableInterface;
+use Symfony\Contracts\Translation\TranslatorInterface;
+
+use function Symfony\Component\Translation\t;
+
+enum HomepageItems: string implements TranslatableInterface
+{
+ case SEARCH = 'search';
+ case BANNER = 'banner';
+ case LICENSE = 'license';
+ case FIRST_STEPS = 'first_steps';
+ case LAST_ACTIVITY = 'last_activity';
+
+ public function trans(TranslatorInterface $translator, ?string $locale = null): string
+ {
+ $key = match($this) {
+ self::SEARCH => 'search.placeholder',
+ self::BANNER => 'settings.system.customization.banner',
+ self::LICENSE => 'homepage.license',
+ self::FIRST_STEPS => 'homepage.first_steps.title',
+ self::LAST_ACTIVITY => 'homepage.last_activity',
+ };
+
+ return $translator->trans($key, locale: $locale);
+ }
+}
diff --git a/templates/base.html.twig b/templates/base.html.twig
index 48e45ab0..bb9844fa 100644
--- a/templates/base.html.twig
+++ b/templates/base.html.twig
@@ -53,6 +53,14 @@
{% endif %}
{{ encore_entry_link_tags('app') }}
+
+ {% set table_settings = settings_instance('table') %}
+
{% endblock %}
{% block javascripts %}
diff --git a/templates/components/datatables.macro.html.twig b/templates/components/datatables.macro.html.twig
index 5ce0f23f..009f815e 100644
--- a/templates/components/datatables.macro.html.twig
+++ b/templates/components/datatables.macro.html.twig
@@ -29,7 +29,7 @@
-
+
{#
#}
@@ -95,4 +95,4 @@
-{% endmacro %}
\ No newline at end of file
+{% endmacro %}
diff --git a/templates/homepage.html.twig b/templates/homepage.html.twig
index 3f820a53..6e7aa360 100644
--- a/templates/homepage.html.twig
+++ b/templates/homepage.html.twig
@@ -4,26 +4,23 @@
{% import "components/search.macro.html.twig" as search %}
{% import "vars.macro.twig" as vars %}
-{% block content %}
-
- {% if is_granted('@system.show_updates') %}
- {{ nv.new_version_alert(new_version_available, new_version, new_version_url) }}
- {% endif %}
-
+{% block item_search %}
{% if is_granted('@parts.read') %}
{{ search.search_form("standalone") }}
-
{% endif %}
+{% endblock %}
-
+{% block item_banner %}
{{ vars.partdb_title() }}
-
- {% trans %}version.caption{% endtrans %}: {{ shivas_app_version }}
- {% if git_branch is not empty or git_commit is not empty %}
- ({{ git_branch ?? '' }}/{{ git_commit ?? '' }})
- {% endif %}
-
+ {% if settings_instance('customization').showVersionOnHomepage %}
+
+ {% trans %}version.caption{% endtrans %}: {{ shivas_app_version }}
+ {% if git_branch is not empty or git_commit is not empty %}
+ ({{ git_branch ?? '' }}/{{ git_commit ?? '' }})
+ {% endif %}
+
+ {% endif %}
{% if banner is not empty %}
@@ -31,9 +28,11 @@
{% endif %}
+{% endblock %}
+{% block item_first_steps %}
{% if show_first_steps %}
-
{% endif %}
+{% endblock %}
-
+{% block item_license %}
+
@@ -68,9 +69,11 @@
{% trans %}homepage.forum.caption{% endtrans %}: {% trans with {'%href%': 'https://github.com/Part-DB/Part-DB-server/discussions'}%}homepage.forum.text{% endtrans %}
+{% endblock %}
+{% block item_last_activity %}
{% if datatable is not null %}
-
+
{% import "components/history_log_macros.html.twig" as log %}
@@ -78,4 +81,23 @@
{% endif %}
-{% endblock %}
\ No newline at end of file
+{% endblock %}
+
+{% block content %}
+
+ {% if is_granted('@system.show_updates') %}
+ {{ nv.new_version_alert(new_version_available, new_version, new_version_url) }}
+ {% endif %}
+
+ {% for item in settings_instance('customization').homepageitems %}
+ {% if block('item_' ~ item.value) is defined %}
+ {{ block('item_' ~ item.value) }}
+
+ {% else %}
+
+ Alert: The homepage item "{{ item.value }}" is not defined!
+
+ {% endif %}
+ {% endfor %}
+
+{% endblock %}
diff --git a/translations/messages.de.xlf b/translations/messages.de.xlf
index b579d908..8515abb8 100644
--- a/translations/messages.de.xlf
+++ b/translations/messages.de.xlf
@@ -13056,5 +13056,389 @@ Bitte beachten Sie, dass Sie sich nicht als deaktivierter Benutzer ausgeben kön
Aus Sicherheitsgründen ausgeblendet
+
+
+ project.bom_import.map_fields
+ Spalten zuordnen
+
+
+
+
+ project.bom_import.map_fields.help
+ Wählen Sie aus, wie CSV Spalten auf BOM Felder gemappt werden
+
+
+
+
+ project.bom_import.delimiter
+ Trennzeichen
+
+
+
+
+ project.bom_import.delimiter.comma
+ Komma (,)
+
+
+
+
+ project.bom_import.delimiter.semicolon
+ Semikolon (;)
+
+
+
+
+ project.bom_import.delimiter.tab
+ Tab
+
+
+
+
+ project.bom_import.field_mapping.title
+ Spaltenzuordnung
+
+
+
+
+ project.bom_import.field_mapping.csv_field
+ CSV Spalte
+
+
+
+
+ project.bom_import.field_mapping.maps_to
+ Mappt auf
+
+
+
+
+ project.bom_import.field_mapping.suggestion
+ Vorschlag
+
+
+
+
+ project.bom_import.field_mapping.priority
+ Priorität
+
+
+
+
+ project.bom_import.field_mapping.priority_help
+ Priorität (kleinere Nummer = höhere Priorität)
+
+
+
+
+ project.bom_import.field_mapping.priority_short
+ P
+
+
+
+
+ project.bom_import.field_mapping.priority_note
+ Prioritätstipp: Niedrigere Zahlen = höhere Priorität. Die Standardpriorität ist 10. Verwenden Sie die Prioritäten 1–9 für die wichtigsten Felder und 10+ für normale Priorität.
+
+
+
+
+ project.bom_import.field_mapping.summary
+ Zusammenfassung der Zuordnung
+
+
+
+
+ project.bom_import.field_mapping.select_to_see_summary
+ Wählen Sie Zuordnungen aus, um eine Zusammenfassung anzuzeigen.
+
+
+
+
+ project.bom_import.field_mapping.no_suggestion
+ Kein Vorschlag
+
+
+
+
+ project.bom_import.preview
+ Vorschau
+
+
+
+
+ project.bom_import.flash.session_expired
+ Die Import-Sitzung ist abgelaufen. Bitte laden Sie Ihre Datei erneut hoch.
+
+
+
+
+ project.bom_import.field_mapping.ignore
+ Ignorieren
+
+
+
+
+ project.bom_import.type.kicad_schematic
+ KiCAD Schaltplaneditor BOM (CSV Datei)
+
+
+
+
+ common.back
+ Zurück
+
+
+
+
+ project.bom_import.validation.errors.required_field_missing
+ Zeile %line%: Das Pflichtfeld „%field%“ fehlt oder ist leer. Bitte stellen Sie sicher, dass dieses Feld zugeordnet ist und Daten enthält.
+
+
+
+
+ project.bom_import.validation.errors.no_valid_designators
+ Zeile %line%: Das Bezeichnungsfeld enthält keine gültigen Komponentenreferenzen. Erwartetes Format: „R1,C2,U3“ oder „R1, C2, U3“.
+
+
+
+
+ project.bom_import.validation.warnings.unusual_designator_format
+ Zeile %line%: Einige Komponentenreferenzen haben möglicherweise ein ungewöhnliches Format: %designators%. Erwartetes Format: „R1“, „C2“, „U3“ usw.
+
+
+
+
+ project.bom_import.validation.errors.duplicate_designators
+ Zeile %line%: Doppelte Komponentenreferenzen gefunden: %designators%. Jede Komponente sollte nur einmal pro Zeile referenziert werden.
+
+
+
+
+ project.bom_import.validation.errors.invalid_quantity
+ Zeile %line%: Die Menge „%quantity%“ ist keine gültige Zahl. Bitte geben Sie einen numerischen Wert ein (z. B. 1, 2,5, 10).
+
+
+
+
+ project.bom_import.validation.errors.quantity_zero_or_negative
+ Zeile %line%: Die Menge muss größer als 0 sein, erhaltene Menge %quantity%.
+
+
+
+
+ project.bom_import.validation.warnings.quantity_unusually_high
+ Zeile %line%: Die Menge %quantity% erscheint ungewöhnlich hoch. Bitte überprüfen Sie, ob dies korrekt ist.
+
+
+
+
+ project.bom_import.validation.warnings.quantity_not_whole_number
+ Zeile %line%: Die Menge %quantity% ist keine ganze Zahl, aber Sie haben %count% Komponentenreferenzen. Dies kann auf eine Nichtübereinstimmung hindeuten.
+
+
+
+
+ project.bom_import.validation.errors.quantity_designator_mismatch
+ Zeile %line%: Diskrepanz zwischen Menge und Komponentenreferenzen. Menge: %quantity%, Referenzen: %count% (%designators%). Diese sollten übereinstimmen. Passen Sie entweder die Menge an oder überprüfen Sie Ihre Komponentenreferenzen.
+
+
+
+
+ project.bom_import.validation.errors.invalid_partdb_id
+ Zeile %line%: Part-DB ID „%id%“ ist keine gültige Zahl. Bitte geben Sie eine numerische ID ein.
+
+
+
+
+ project.bom_import.validation.errors.partdb_id_zero_or_negative
+ Zeile %line%: Die Part-DB ID muss größer als 0 sein, erhaltene ID lautet %id%.
+
+
+
+
+ project.bom_import.validation.warnings.partdb_id_not_found
+ Zeile %line%: Teil-DB-ID %id% nicht in der Datenbank gefunden. Die Komponente wird ohne Verknüpfung mit einem vorhandenen Teil importiert.
+
+
+
+
+ project.bom_import.validation.info.partdb_link_success
+ Zeile %line%: Erfolgreich mit dem Bauteil „%name%“ (ID: %id%) verknüpft.
+
+
+
+
+ project.bom_import.validation.warnings.no_component_name
+ Zeile %line%: Kein Komponentenname/keine Komponentenbezeichnung angegeben (MPN, Bezeichnung oder Wert). Die Komponente wird als „Unbekanntes Bauteil” bezeichnet.
+
+
+
+
+ project.bom_import.validation.warnings.package_name_too_long
+ Zeile %line%: Der Footprintname „%package%“ ist ungewöhnlich lang. Bitte überprüfen Sie, ob er korrekt ist.
+
+
+
+
+ project.bom_import.validation.info.library_prefix_detected
+ Zeile %line%: Das Footprint „%package%“ enthält ein Bibliothekspräfix. Dieses wird beim Import automatisch entfernt.
+
+
+
+
+ project.bom_import.validation.errors.non_numeric_field
+ Zeile %line%: Das Feld „%field%“ enthält den nicht numerischen Wert „%value%“. Bitte geben Sie eine gültige Zahl ein.
+
+
+
+
+ project.bom_import.validation.info.import_summary
+ Importübersicht: %total% Einträge insgesamt, %valid% gültig, %invalid% mit Problemen.
+
+
+
+
+ project.bom_import.validation.errors.summary
+ Es wurden %count% Validierungsfehler gefunden, die behoben werden müssen, bevor der Import fortgesetzt werden kann.
+
+
+
+
+ project.bom_import.validation.warnings.summary
+ Es wurden %count% Warnungen gefunden. Bitte überprüfen Sie diese Probleme, bevor Sie fortfahren.
+
+
+
+
+ project.bom_import.validation.info.all_valid
+ Alle Einträge haben die Validierung erfolgreich bestanden!
+
+
+
+
+ project.bom_import.validation.summary
+ Validierungsübersicht
+
+
+
+
+ project.bom_import.validation.total_entries
+ Gesamtzahl der Einträge
+
+
+
+
+ project.bom_import.validation.valid_entries
+ Gültige Einträge
+
+
+
+
+ project.bom_import.validation.invalid_entries
+ Ungültige Einträge
+
+
+
+
+ project.bom_import.validation.success_rate
+ Erfolgsquote
+
+
+
+
+ project.bom_import.validation.errors.title
+ Validierungsfehler
+
+
+
+
+ project.bom_import.validation.errors.description
+ Die folgenden Fehler müssen behoben werden, bevor der Import fortgesetzt werden kann:
+
+
+
+
+ project.bom_import.validation.warnings.title
+ Validierungswarnungen
+
+
+
+
+ project.bom_import.validation.warnings.description
+ Die folgenden Warnhinweise sollten vor dem Fortfahren gelesen werden:
+
+
+
+
+ project.bom_import.validation.info.title
+ Informationen
+
+
+
+
+ project.bom_import.validation.details.title
+ Detaillierte Validierungsergebnisse
+
+
+
+
+ project.bom_import.validation.details.line
+ Zeile
+
+
+
+
+ project.bom_import.validation.details.status
+ Status
+
+
+
+
+ project.bom_import.validation.details.messages
+ Meldungen
+
+
+
+
+ project.bom_import.validation.details.valid
+ Gültig
+
+
+
+
+ project.bom_import.validation.details.invalid
+ Ungültig
+
+
+
+
+ project.bom_import.validation.all_valid
+ Alle Einträge sind gültig und bereit zum Import!
+
+
+
+
+ project.bom_import.validation.fix_errors
+ Bitte beheben Sie die Validierungsfehler, bevor Sie mit dem Import fortfahren.
+
+
+
+
+ project.bom_import.type.generic_csv
+ Generische CSV-Datei
+
+
+
+
+ label_generator.update_profile
+ Profil mit aktuellen Einstellungen aktualisieren
+
+
+
+
+ label_generator.profile_updated
+ Labelprofil aktualisiert
+
+
diff --git a/translations/messages.en.xlf b/translations/messages.en.xlf
index 41ad8358..88ae764a 100644
--- a/translations/messages.en.xlf
+++ b/translations/messages.en.xlf
@@ -13441,5 +13441,65 @@ Please note, that you can not impersonate a disabled user. If you try you will g
Label profile updated successfully.
+
+
+ settings.behavior.hompepage.items
+ Homepage items
+
+
+
+
+ settings.behavior.homepage.items.help
+
+
+
+
+
+ settings.system.customization.showVersionOnHomepage
+ Show Part-DB version on homepage
+
+
+
+
+ settings.behavior.part_info.extract_params_from_description
+ Extract parameters from part description
+
+
+
+
+ settings.behavior.part_info.extract_params_from_notes
+ Extract parameters from part notes
+
+
+
+
+ settings.ips.default_providers
+ Default search providers
+
+
+
+
+ settings.ips.general
+ General settings
+
+
+
+
+ settings.ips.default_providers.help
+ These providers will be preselected for searches in part providers.
+
+
+
+
+ settings.behavior.table.preview_image_max_width
+ Preview image max width (px)
+
+
+
+
+ settings.behavior.table.preview_image_min_width
+ Preview image min width (px)
+
+