Compare commits

..

12 commits

Author SHA1 Message Date
Jan Böhmer
c2cbbee0df Ensure that part table action bar dont overlap our navbar dropdowns
Some checks are pending
Build assets artifact / Build assets artifact (push) Waiting to run
Docker Image Build / docker (push) Waiting to run
Docker Image Build (FrankenPHP) / docker (push) Waiting to run
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.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.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
2025-09-07 21:59:30 +02:00
Jan Böhmer
e81c8470be Made part table action bar sticky floating
Related to PR #997
2025-09-07 21:52:04 +02:00
Jan Böhmer
ecd2abe00e Made image size of preview images in tables configurable and slightly bigger by default
This makes PR #984 and #623 obsolete
2025-09-07 21:21:08 +02:00
Jan Böhmer
0d1ae030be Allow to select default info providers for search
This fixes issue #556
2025-09-07 20:42:33 +02:00
Jan Böhmer
1f669a9c53 Readded option to show all elements in a table 2025-09-07 20:04:48 +02:00
Jan Böhmer
8ff2fc5a82 Allow to disable the extraction of parameters out of part description and notes
Fixes issue #747
2025-09-07 19:55:58 +02:00
Jan Böhmer
c7ec8adc31 Disable settings caching in debug mode
Otherwise we run into errors, if a settings get changed
2025-09-07 19:44:32 +02:00
Jan Böhmer
cee6d355e8 Allow to hide the version number on homepage 2025-09-07 19:43:23 +02:00
Jan Böhmer
4b00697f02 Allow to customize which items get shown on the homepage and in which order
This fixes issue #470 and #894
2025-09-07 19:27:02 +02:00
Jan Böhmer
617ae03b48 Merge remote-tracking branch 'origin/master' 2025-09-07 17:56:30 +02:00
Jan Böhmer
71629a696c Use updated gnu unifont 2025-09-07 17:55:55 +02:00
Jan Böhmer
14cc0b9e9a
New translations messages.en.xlf (German) (#1028) 2025-09-07 17:53:12 +02:00
21 changed files with 752 additions and 64 deletions

View file

@ -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

View file

@ -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;
}

View file

@ -17,6 +17,16 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/****************************************
* 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;
}
}

36
composer.lock generated
View file

@ -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",

View file

@ -5,4 +5,11 @@ jbtronics_settings:
default_cacheable: true
orm_storage:
default_entity_class: App\Entity\SettingsEntry
default_entity_class: App\Entity\SettingsEntry
# Disable caching for development environment
when@dev:
jbtronics_settings:
cache:
default_cacheable: false

View file

@ -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()) {

View file

@ -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();

View file

@ -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,
]
);

View file

@ -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()) {

View file

@ -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;
});
}
}
}

View file

@ -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;
}
#[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;
}

View file

@ -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;
}
}
}

View file

@ -0,0 +1,45 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2025 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\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 = [];
}

View file

@ -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;
}
}

View file

@ -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;
}

View file

@ -0,0 +1,51 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2025 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\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);
}
}

View file

@ -53,6 +53,14 @@
{% endif %}
{{ encore_entry_link_tags('app') }}
{% set table_settings = settings_instance('table') %}
<style nonce="{{ csp_nonce('style') }}">
:root {
--table-image-preview-min-size: {{ table_settings.previewImageMinWidth }}px;
--table-image-preview-max-size: {{ table_settings.previewImageMaxWidth }}px;
}
</style>
{% endblock %}
{% block javascripts %}

View file

@ -29,7 +29,7 @@
<input type="hidden" name="ids" {{ stimulus_target('elements/datatables/parts', 'selectIDs') }} value="">
<div class="d-none mb-2" {{ stimulus_target('elements/datatables/parts', 'selectPanel') }}>
<div class="d-none mb-2 bg-body-tertiary shadow-sm border border-secondary rounded mx-2 p-2" {{ stimulus_target('elements/datatables/parts', 'selectPanel') }}>
{# <span id="select_count"></span> #}
<div class="input-group">
@ -95,4 +95,4 @@
</div>
</form>
{% endmacro %}
{% endmacro %}

View file

@ -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") }}
<div class="mb-2"></div>
{% endif %}
{% endblock %}
{% block item_banner %}
<div class="rounded p-4 bg-body-secondary">
<h1 class="display-3">{{ vars.partdb_title() }}</h1>
<h4>
{% trans %}version.caption{% endtrans %}: {{ shivas_app_version }}
{% if git_branch is not empty or git_commit is not empty %}
({{ git_branch ?? '' }}/{{ git_commit ?? '' }})
{% endif %}
</h4>
{% if settings_instance('customization').showVersionOnHomepage %}
<h4>
{% trans %}version.caption{% endtrans %}: {{ shivas_app_version }}
{% if git_branch is not empty or git_commit is not empty %}
({{ git_branch ?? '' }}/{{ git_commit ?? '' }})
{% endif %}
</h4>
{% endif %}
{% if banner is not empty %}
<hr>
<div class="latex" data-controller="common--latex">
@ -31,9 +28,11 @@
</div>
{% endif %}
</div>
{% endblock %}
{% block item_first_steps %}
{% if show_first_steps %}
<div class="card border-info mt-3">
<div class="card border-info">
<div class="card-header bg-info ">
<h4><i class="fa fa-circle-play fa-fw " aria-hidden="true"></i> {% trans %}homepage.first_steps.title{% endtrans %}</h4>
</div>
@ -51,8 +50,10 @@
</div>
</div>
{% endif %}
{% endblock %}
<div class="card border-primary mt-3">
{% block item_license %}
<div class="card border-primary">
<div class="card-header bg-primary text-white">
<h4><i class="fa fa-book fa-fw" aria-hidden="true"></i> {% trans %}homepage.license{% endtrans %}</h4>
</div>
@ -68,9 +69,11 @@
<strong><i class="fas fa-comments fa-fw"></i> {% trans %}homepage.forum.caption{% endtrans %}:</strong> {% trans with {'%href%': 'https://github.com/Part-DB/Part-DB-server/discussions'}%}homepage.forum.text{% endtrans %}<br>
</div>
</div>
{% endblock %}
{% block item_last_activity %}
{% if datatable is not null %}
<div class="card mt-3">
<div class="card">
<div class="card-header"><i class="fas fa-fw fa-history"></i> {% trans %}homepage.last_activity{% endtrans %}</div>
<div class="card-body">
{% import "components/history_log_macros.html.twig" as log %}
@ -78,4 +81,23 @@
</div>
</div>
{% endif %}
{% endblock %}
{% 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) }}
<div class="mb-2"></div>
{% else %}
<div class="alert alert-warning mt-3" role="alert">
Alert: The homepage item "{{ item.value }}" is not defined!
</div>
{% endif %}
{% endfor %}
{% endblock %}

View file

@ -13056,5 +13056,389 @@ Bitte beachten Sie, dass Sie sich nicht als deaktivierter Benutzer ausgeben kön
<target>Aus Sicherheitsgründen ausgeblendet</target>
</segment>
</unit>
<unit id="J716Oh4" name="project.bom_import.map_fields">
<segment state="translated">
<source>project.bom_import.map_fields</source>
<target>Spalten zuordnen</target>
</segment>
</unit>
<unit id="wvT5Xvn" name="project.bom_import.map_fields.help">
<segment state="translated">
<source>project.bom_import.map_fields.help</source>
<target>Wählen Sie aus, wie CSV Spalten auf BOM Felder gemappt werden</target>
</segment>
</unit>
<unit id="nh7uLPe" name="project.bom_import.delimiter">
<segment state="translated">
<source>project.bom_import.delimiter</source>
<target>Trennzeichen</target>
</segment>
</unit>
<unit id="MSlSeE4" name="project.bom_import.delimiter.comma">
<segment state="translated">
<source>project.bom_import.delimiter.comma</source>
<target>Komma (,)</target>
</segment>
</unit>
<unit id="UdDSB0h" name="project.bom_import.delimiter.semicolon">
<segment state="translated">
<source>project.bom_import.delimiter.semicolon</source>
<target>Semikolon (;)</target>
</segment>
</unit>
<unit id="Wpa4Gmf" name="project.bom_import.delimiter.tab">
<segment state="translated">
<source>project.bom_import.delimiter.tab</source>
<target>Tab</target>
</segment>
</unit>
<unit id="tjrG_vU" name="project.bom_import.field_mapping.title">
<segment state="translated">
<source>project.bom_import.field_mapping.title</source>
<target>Spaltenzuordnung</target>
</segment>
</unit>
<unit id="nWu8B7R" name="project.bom_import.field_mapping.csv_field">
<segment state="translated">
<source>project.bom_import.field_mapping.csv_field</source>
<target>CSV Spalte</target>
</segment>
</unit>
<unit id="D0KlFqm" name="project.bom_import.field_mapping.maps_to">
<segment state="translated">
<source>project.bom_import.field_mapping.maps_to</source>
<target>Mappt auf</target>
</segment>
</unit>
<unit id="rhUpmxC" name="project.bom_import.field_mapping.suggestion">
<segment state="translated">
<source>project.bom_import.field_mapping.suggestion</source>
<target>Vorschlag</target>
</segment>
</unit>
<unit id="TM0mgKr" name="project.bom_import.field_mapping.priority">
<segment state="translated">
<source>project.bom_import.field_mapping.priority</source>
<target>Priorität</target>
</segment>
</unit>
<unit id="xY8NSRr" name="project.bom_import.field_mapping.priority_help">
<segment state="translated">
<source>project.bom_import.field_mapping.priority_help</source>
<target>Priorität (kleinere Nummer = höhere Priorität)</target>
</segment>
</unit>
<unit id="CuXg8WU" name="project.bom_import.field_mapping.priority_short">
<segment state="translated">
<source>project.bom_import.field_mapping.priority_short</source>
<target>P</target>
</segment>
</unit>
<unit id="jOHOShN" name="project.bom_import.field_mapping.priority_note">
<segment state="translated">
<source>project.bom_import.field_mapping.priority_note</source>
<target>Prioritätstipp: Niedrigere Zahlen = höhere Priorität. Die Standardpriorität ist 10. Verwenden Sie die Prioritäten 19 für die wichtigsten Felder und 10+ für normale Priorität.</target>
</segment>
</unit>
<unit id="pvmv3OT" name="project.bom_import.field_mapping.summary">
<segment state="translated">
<source>project.bom_import.field_mapping.summary</source>
<target>Zusammenfassung der Zuordnung</target>
</segment>
</unit>
<unit id="VjLf1Pf" name="project.bom_import.field_mapping.select_to_see_summary">
<segment state="translated">
<source>project.bom_import.field_mapping.select_to_see_summary</source>
<target>Wählen Sie Zuordnungen aus, um eine Zusammenfassung anzuzeigen.</target>
</segment>
</unit>
<unit id="JOPny6T" name="project.bom_import.field_mapping.no_suggestion">
<segment state="translated">
<source>project.bom_import.field_mapping.no_suggestion</source>
<target>Kein Vorschlag</target>
</segment>
</unit>
<unit id="1LWEtqL" name="project.bom_import.preview">
<segment state="translated">
<source>project.bom_import.preview</source>
<target>Vorschau</target>
</segment>
</unit>
<unit id="1CJA0aY" name="project.bom_import.flash.session_expired">
<segment state="translated">
<source>project.bom_import.flash.session_expired</source>
<target>Die Import-Sitzung ist abgelaufen. Bitte laden Sie Ihre Datei erneut hoch.</target>
</segment>
</unit>
<unit id="BLN7XRN" name="project.bom_import.field_mapping.ignore">
<segment state="translated">
<source>project.bom_import.field_mapping.ignore</source>
<target>Ignorieren</target>
</segment>
</unit>
<unit id="yiIB3yV" name="project.bom_import.type.kicad_schematic">
<segment state="translated">
<source>project.bom_import.type.kicad_schematic</source>
<target>KiCAD Schaltplaneditor BOM (CSV Datei)</target>
</segment>
</unit>
<unit id="ltE6xPP" name="common.back">
<segment state="translated">
<source>common.back</source>
<target>Zurück</target>
</segment>
</unit>
<unit id="HCTqjZO" name="project.bom_import.validation.errors.required_field_missing">
<segment state="translated">
<source>project.bom_import.validation.errors.required_field_missing</source>
<target>Zeile %line%: Das Pflichtfeld „%field%“ fehlt oder ist leer. Bitte stellen Sie sicher, dass dieses Feld zugeordnet ist und Daten enthält.</target>
</segment>
</unit>
<unit id="_ua5YM7" name="project.bom_import.validation.errors.no_valid_designators">
<segment state="translated">
<source>project.bom_import.validation.errors.no_valid_designators</source>
<target>Zeile %line%: Das Bezeichnungsfeld enthält keine gültigen Komponentenreferenzen. Erwartetes Format: „R1,C2,U3“ oder „R1, C2, U3“.</target>
</segment>
</unit>
<unit id="xpkq3rW" name="project.bom_import.validation.warnings.unusual_designator_format">
<segment state="translated">
<source>project.bom_import.validation.warnings.unusual_designator_format</source>
<target>Zeile %line%: Einige Komponentenreferenzen haben möglicherweise ein ungewöhnliches Format: %designators%. Erwartetes Format: „R1“, „C2“, „U3“ usw.</target>
</segment>
</unit>
<unit id="M54Ud5d" name="project.bom_import.validation.errors.duplicate_designators">
<segment state="translated">
<source>project.bom_import.validation.errors.duplicate_designators</source>
<target>Zeile %line%: Doppelte Komponentenreferenzen gefunden: %designators%. Jede Komponente sollte nur einmal pro Zeile referenziert werden.</target>
</segment>
</unit>
<unit id="ZULFZh2" name="project.bom_import.validation.errors.invalid_quantity">
<segment state="translated">
<source>project.bom_import.validation.errors.invalid_quantity</source>
<target>Zeile %line%: Die Menge „%quantity%“ ist keine gültige Zahl. Bitte geben Sie einen numerischen Wert ein (z. B. 1, 2,5, 10).</target>
</segment>
</unit>
<unit id="3WbRgsl" name="project.bom_import.validation.errors.quantity_zero_or_negative">
<segment state="translated">
<source>project.bom_import.validation.errors.quantity_zero_or_negative</source>
<target>Zeile %line%: Die Menge muss größer als 0 sein, erhaltene Menge %quantity%.</target>
</segment>
</unit>
<unit id="_nWGsSU" name="project.bom_import.validation.warnings.quantity_unusually_high">
<segment state="translated">
<source>project.bom_import.validation.warnings.quantity_unusually_high</source>
<target>Zeile %line%: Die Menge %quantity% erscheint ungewöhnlich hoch. Bitte überprüfen Sie, ob dies korrekt ist.</target>
</segment>
</unit>
<unit id="iJWw39f" name="project.bom_import.validation.warnings.quantity_not_whole_number">
<segment state="translated">
<source>project.bom_import.validation.warnings.quantity_not_whole_number</source>
<target>Zeile %line%: Die Menge %quantity% ist keine ganze Zahl, aber Sie haben %count% Komponentenreferenzen. Dies kann auf eine Nichtübereinstimmung hindeuten.</target>
</segment>
</unit>
<unit id="Zffmgvv" name="project.bom_import.validation.errors.quantity_designator_mismatch">
<segment state="translated">
<source>project.bom_import.validation.errors.quantity_designator_mismatch</source>
<target>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.</target>
</segment>
</unit>
<unit id="JqHpDIo" name="project.bom_import.validation.errors.invalid_partdb_id">
<segment state="translated">
<source>project.bom_import.validation.errors.invalid_partdb_id</source>
<target>Zeile %line%: Part-DB ID „%id%“ ist keine gültige Zahl. Bitte geben Sie eine numerische ID ein.</target>
</segment>
</unit>
<unit id="Mr7W6r0" name="project.bom_import.validation.errors.partdb_id_zero_or_negative">
<segment state="translated">
<source>project.bom_import.validation.errors.partdb_id_zero_or_negative</source>
<target>Zeile %line%: Die Part-DB ID muss größer als 0 sein, erhaltene ID lautet %id%.</target>
</segment>
</unit>
<unit id="gRH6IeR" name="project.bom_import.validation.warnings.partdb_id_not_found">
<segment state="translated">
<source>project.bom_import.validation.warnings.partdb_id_not_found</source>
<target>Zeile %line%: Teil-DB-ID %id% nicht in der Datenbank gefunden. Die Komponente wird ohne Verknüpfung mit einem vorhandenen Teil importiert.</target>
</segment>
</unit>
<unit id="eMfaBYo" name="project.bom_import.validation.info.partdb_link_success">
<segment state="translated">
<source>project.bom_import.validation.info.partdb_link_success</source>
<target>Zeile %line%: Erfolgreich mit dem Bauteil „%name%“ (ID: %id%) verknüpft.</target>
</segment>
</unit>
<unit id="b_m9p.f" name="project.bom_import.validation.warnings.no_component_name">
<segment state="translated">
<source>project.bom_import.validation.warnings.no_component_name</source>
<target>Zeile %line%: Kein Komponentenname/keine Komponentenbezeichnung angegeben (MPN, Bezeichnung oder Wert). Die Komponente wird als „Unbekanntes Bauteil” bezeichnet.</target>
</segment>
</unit>
<unit id=".LAGdMe" name="project.bom_import.validation.warnings.package_name_too_long">
<segment state="translated">
<source>project.bom_import.validation.warnings.package_name_too_long</source>
<target>Zeile %line%: Der Footprintname „%package%“ ist ungewöhnlich lang. Bitte überprüfen Sie, ob er korrekt ist.</target>
</segment>
</unit>
<unit id="wFF49kl" name="project.bom_import.validation.info.library_prefix_detected">
<segment state="translated">
<source>project.bom_import.validation.info.library_prefix_detected</source>
<target>Zeile %line%: Das Footprint „%package%“ enthält ein Bibliothekspräfix. Dieses wird beim Import automatisch entfernt.</target>
</segment>
</unit>
<unit id="hEO2sxC" name="project.bom_import.validation.errors.non_numeric_field">
<segment state="translated">
<source>project.bom_import.validation.errors.non_numeric_field</source>
<target>Zeile %line%: Das Feld „%field%“ enthält den nicht numerischen Wert „%value%“. Bitte geben Sie eine gültige Zahl ein.</target>
</segment>
</unit>
<unit id="Ear_sUX" name="project.bom_import.validation.info.import_summary">
<segment state="translated">
<source>project.bom_import.validation.info.import_summary</source>
<target>Importübersicht: %total% Einträge insgesamt, %valid% gültig, %invalid% mit Problemen.</target>
</segment>
</unit>
<unit id="SqJZ97A" name="project.bom_import.validation.errors.summary">
<segment state="translated">
<source>project.bom_import.validation.errors.summary</source>
<target>Es wurden %count% Validierungsfehler gefunden, die behoben werden müssen, bevor der Import fortgesetzt werden kann.</target>
</segment>
</unit>
<unit id="s7dzFnp" name="project.bom_import.validation.warnings.summary">
<segment state="translated">
<source>project.bom_import.validation.warnings.summary</source>
<target>Es wurden %count% Warnungen gefunden. Bitte überprüfen Sie diese Probleme, bevor Sie fortfahren.</target>
</segment>
</unit>
<unit id="2KaAHmS" name="project.bom_import.validation.info.all_valid">
<segment state="translated">
<source>project.bom_import.validation.info.all_valid</source>
<target>Alle Einträge haben die Validierung erfolgreich bestanden!</target>
</segment>
</unit>
<unit id="Lyb3BjK" name="project.bom_import.validation.summary">
<segment state="translated">
<source>project.bom_import.validation.summary</source>
<target>Validierungsübersicht</target>
</segment>
</unit>
<unit id="DUM8GoE" name="project.bom_import.validation.total_entries">
<segment state="translated">
<source>project.bom_import.validation.total_entries</source>
<target>Gesamtzahl der Einträge</target>
</segment>
</unit>
<unit id="AtLGGU2" name="project.bom_import.validation.valid_entries">
<segment state="translated">
<source>project.bom_import.validation.valid_entries</source>
<target>Gültige Einträge</target>
</segment>
</unit>
<unit id="mgdn5v3" name="project.bom_import.validation.invalid_entries">
<segment state="translated">
<source>project.bom_import.validation.invalid_entries</source>
<target>Ungültige Einträge</target>
</segment>
</unit>
<unit id="qmJ5BfF" name="project.bom_import.validation.success_rate">
<segment state="translated">
<source>project.bom_import.validation.success_rate</source>
<target>Erfolgsquote</target>
</segment>
</unit>
<unit id="QyDsrFV" name="project.bom_import.validation.errors.title">
<segment state="translated">
<source>project.bom_import.validation.errors.title</source>
<target>Validierungsfehler</target>
</segment>
</unit>
<unit id="d.Mvu0a" name="project.bom_import.validation.errors.description">
<segment state="translated">
<source>project.bom_import.validation.errors.description</source>
<target>Die folgenden Fehler müssen behoben werden, bevor der Import fortgesetzt werden kann:</target>
</segment>
</unit>
<unit id="MGFfKr." name="project.bom_import.validation.warnings.title">
<segment state="translated">
<source>project.bom_import.validation.warnings.title</source>
<target>Validierungswarnungen</target>
</segment>
</unit>
<unit id="tt4VxY6" name="project.bom_import.validation.warnings.description">
<segment state="translated">
<source>project.bom_import.validation.warnings.description</source>
<target>Die folgenden Warnhinweise sollten vor dem Fortfahren gelesen werden:</target>
</segment>
</unit>
<unit id="YuAdYeh" name="project.bom_import.validation.info.title">
<segment state="translated">
<source>project.bom_import.validation.info.title</source>
<target>Informationen</target>
</segment>
</unit>
<unit id="1pUfi7Q" name="project.bom_import.validation.details.title">
<segment state="translated">
<source>project.bom_import.validation.details.title</source>
<target>Detaillierte Validierungsergebnisse</target>
</segment>
</unit>
<unit id="4Vv0Xns" name="project.bom_import.validation.details.line">
<segment state="translated">
<source>project.bom_import.validation.details.line</source>
<target>Zeile</target>
</segment>
</unit>
<unit id="RbvD2zF" name="project.bom_import.validation.details.status">
<segment state="translated">
<source>project.bom_import.validation.details.status</source>
<target>Status</target>
</segment>
</unit>
<unit id="iGtKkH_" name="project.bom_import.validation.details.messages">
<segment state="translated">
<source>project.bom_import.validation.details.messages</source>
<target>Meldungen</target>
</segment>
</unit>
<unit id="IgdgZd8" name="project.bom_import.validation.details.valid">
<segment state="translated">
<source>project.bom_import.validation.details.valid</source>
<target>Gültig</target>
</segment>
</unit>
<unit id="lWQEtkq" name="project.bom_import.validation.details.invalid">
<segment state="translated">
<source>project.bom_import.validation.details.invalid</source>
<target>Ungültig</target>
</segment>
</unit>
<unit id="tPn6Ind" name="project.bom_import.validation.all_valid">
<segment state="translated">
<source>project.bom_import.validation.all_valid</source>
<target>Alle Einträge sind gültig und bereit zum Import!</target>
</segment>
</unit>
<unit id="fzoGCaf" name="project.bom_import.validation.fix_errors">
<segment state="translated">
<source>project.bom_import.validation.fix_errors</source>
<target>Bitte beheben Sie die Validierungsfehler, bevor Sie mit dem Import fortfahren.</target>
</segment>
</unit>
<unit id="nc9MqUc" name="project.bom_import.type.generic_csv">
<segment state="translated">
<source>project.bom_import.type.generic_csv</source>
<target>Generische CSV-Datei</target>
</segment>
</unit>
<unit id=".N35Pvs" name="label_generator.update_profile">
<segment state="translated">
<source>label_generator.update_profile</source>
<target>Profil mit aktuellen Einstellungen aktualisieren</target>
</segment>
</unit>
<unit id="ulTo6Aa" name="label_generator.profile_updated">
<segment state="translated">
<source>label_generator.profile_updated</source>
<target>Labelprofil aktualisiert</target>
</segment>
</unit>
</file>
</xliff>

View file

@ -13441,5 +13441,65 @@ Please note, that you can not impersonate a disabled user. If you try you will g
<target>Label profile updated successfully.</target>
</segment>
</unit>
<unit id="7lgFa7I" name="settings.behavior.hompepage.items">
<segment>
<source>settings.behavior.hompepage.items</source>
<target>Homepage items</target>
</segment>
</unit>
<unit id="FsrRdkp" name="settings.behavior.homepage.items.help">
<segment>
<source>settings.behavior.homepage.items.help</source>
<target><![CDATA[The items to show at the homepage. Order can be changed via drag & drop.]]></target>
</segment>
</unit>
<unit id="CYw3_pS" name="settings.system.customization.showVersionOnHomepage">
<segment>
<source>settings.system.customization.showVersionOnHomepage</source>
<target>Show Part-DB version on homepage</target>
</segment>
</unit>
<unit id="GLYhV9m" name="settings.behavior.part_info.extract_params_from_description">
<segment>
<source>settings.behavior.part_info.extract_params_from_description</source>
<target>Extract parameters from part description</target>
</segment>
</unit>
<unit id="aYOedkN" name="settings.behavior.part_info.extract_params_from_notes">
<segment>
<source>settings.behavior.part_info.extract_params_from_notes</source>
<target>Extract parameters from part notes</target>
</segment>
</unit>
<unit id="nCH2MW6" name="settings.ips.default_providers">
<segment>
<source>settings.ips.default_providers</source>
<target>Default search providers</target>
</segment>
</unit>
<unit id="TLNoCLT" name="settings.ips.general">
<segment>
<source>settings.ips.general</source>
<target>General settings</target>
</segment>
</unit>
<unit id="IDs2sXK" name="settings.ips.default_providers.help">
<segment>
<source>settings.ips.default_providers.help</source>
<target>These providers will be preselected for searches in part providers.</target>
</segment>
</unit>
<unit id="dv6eslZ" name="settings.behavior.table.preview_image_max_width">
<segment>
<source>settings.behavior.table.preview_image_max_width</source>
<target>Preview image max width (px)</target>
</segment>
</unit>
<unit id="5bOoqEL" name="settings.behavior.table.preview_image_min_width">
<segment>
<source>settings.behavior.table.preview_image_min_width</source>
<target>Preview image min width (px)</target>
</segment>
</unit>
</file>
</xliff>