Merge master into settings-bundle

This commit is contained in:
Jan Böhmer 2025-07-06 19:05:22 +02:00
commit 9812671a89
13 changed files with 364 additions and 276 deletions

View file

@ -47,6 +47,7 @@
PassEnv PROVIDER_REICHELT_ENABLED PROVIDER_REICHELT_CURRENCY PROVIDER_REICHELT_COUNTRY PROVIDER_REICHELT_LANGUAGE PROVIDER_REICHELT_INCLUDE_VAT PassEnv PROVIDER_REICHELT_ENABLED PROVIDER_REICHELT_CURRENCY PROVIDER_REICHELT_COUNTRY PROVIDER_REICHELT_LANGUAGE PROVIDER_REICHELT_INCLUDE_VAT
PassEnv PROVIDER_POLLIN_ENABLED PassEnv PROVIDER_POLLIN_ENABLED
PassEnv EDA_KICAD_CATEGORY_DEPTH PassEnv EDA_KICAD_CATEGORY_DEPTH
PassEnv SHOW_PART_IMAGE_OVERLAY
# For most configuration files from conf-available/, which are # For most configuration files from conf-available/, which are
# enabled or disabled at a global level, it is possible to # enabled or disabled at a global level, it is possible to

3
.env
View file

@ -164,6 +164,9 @@ FIXER_API_KEY=CHANGEME
# When this is empty the content of config/banner.md is used as banner # When this is empty the content of config/banner.md is used as banner
BANNER="" BANNER=""
# Enable the part image overlay which shows name and filename of the picture
SHOW_PART_IMAGE_OVERLAY=1
APP_ENV=prod APP_ENV=prod
APP_SECRET=a03498528f5a5fc089273ec9ae5b2849 APP_SECRET=a03498528f5a5fc089273ec9ae5b2849

View file

@ -33,7 +33,10 @@ export default class extends Controller {
{ {
let value = ""; let value = "";
if (this.unitValue) { if (this.unitValue) {
value = "\\mathrm{" + this.inputTarget.value + "}"; //Escape percentage signs
value = this.inputTarget.value.replace(/%/g, '\\%');
value = "\\mathrm{" + value + "}";
} else { } else {
value = this.inputTarget.value; value = this.inputTarget.value;
} }

View file

@ -85,7 +85,9 @@ export default class extends Controller
tmp += '<span>' + katex.renderToString(data.symbol) + '</span>' tmp += '<span>' + katex.renderToString(data.symbol) + '</span>'
} }
if (data.unit) { if (data.unit) {
tmp += '<span class="ms-2">' + katex.renderToString('[' + data.unit + ']') + '</span>' let unit = data.unit.replace(/%/g, '\\%');
unit = "\\mathrm{" + unit + "}";
tmp += '<span class="ms-2">' + katex.renderToString('[' + unit + ']') + '</span>'
} }

535
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -17,6 +17,7 @@ twig:
available_themes: '%partdb.available_themes%' available_themes: '%partdb.available_themes%'
saml_enabled: '%partdb.saml.enabled%' saml_enabled: '%partdb.saml.enabled%'
part_preview_generator: '@App\Services\Attachments\PartPreviewGenerator' part_preview_generator: '@App\Services\Attachments\PartPreviewGenerator'
img_overlay: '%partdb.show_part_image_overlay%'
when@test: when@test:
twig: twig:

View file

@ -48,6 +48,7 @@ parameters:
# Miscellaneous # Miscellaneous
###################################################################################################################### ######################################################################################################################
partdb.demo_mode: '%env(bool:DEMO_MODE)%' # If set to true, all potentially dangerous things are disabled (like changing passwords of the own user) partdb.demo_mode: '%env(bool:DEMO_MODE)%' # If set to true, all potentially dangerous things are disabled (like changing passwords of the own user)
partdb.show_part_image_overlay: '%env(bool:SHOW_PART_IMAGE_OVERLAY)%' # If set to false, the filename overlay of the part image will be disabled
# Set the themes from which the user can choose from in the settings. # Set the themes from which the user can choose from in the settings.
# Themes commented here by default, are not really usable, because of display problems. Enable them at your own risk! # Themes commented here by default, are not really usable, because of display problems. Enable them at your own risk!

View file

@ -95,6 +95,8 @@ bundled with Part-DB. Set `DATABASE_MYSQL_SSL_VERIFY_CERT` if you want to accept
particularly for securing and protecting various aspects of your application. It's a secret key that is used for particularly for securing and protecting various aspects of your application. It's a secret key that is used for
cryptographic operations and security measures (session management, CSRF protection, etc..). Therefore this cryptographic operations and security measures (session management, CSRF protection, etc..). Therefore this
value should be handled as confidential data and not shared publicly. value should be handled as confidential data and not shared publicly.
* `SHOW_PART_IMAGE_OVERLAY`: Set to 0 to disable the part image overlay, which appears if you hover over an image in the
part image gallery
### E-Mail settings ### E-Mail settings

View file

@ -217,7 +217,7 @@ abstract class AbstractParameter extends AbstractNamedDBElement implements Uniqu
$str = ''; $str = '';
$bracket_opened = false; $bracket_opened = false;
if ($this->value_typical) { if ($this->value_typical !== null) {
$str .= $this->getValueTypicalWithUnit($latex_formatted); $str .= $this->getValueTypicalWithUnit($latex_formatted);
if ($this->value_min || $this->value_max) { if ($this->value_min || $this->value_max) {
$bracket_opened = true; $bracket_opened = true;
@ -225,11 +225,11 @@ abstract class AbstractParameter extends AbstractNamedDBElement implements Uniqu
} }
} }
if ($this->value_max && $this->value_min) { if ($this->value_max !== null && $this->value_min !== null) {
$str .= $this->getValueMinWithUnit($latex_formatted).' ... '.$this->getValueMaxWithUnit($latex_formatted); $str .= $this->getValueMinWithUnit($latex_formatted).' ... '.$this->getValueMaxWithUnit($latex_formatted);
} elseif ($this->value_max) { } elseif ($this->value_max !== null) {
$str .= 'max. '.$this->getValueMaxWithUnit($latex_formatted); $str .= 'max. '.$this->getValueMaxWithUnit($latex_formatted);
} elseif ($this->value_min) { } elseif ($this->value_min !== null) {
$str .= 'min. '.$this->getValueMinWithUnit($latex_formatted); $str .= 'min. '.$this->getValueMinWithUnit($latex_formatted);
} }
@ -449,7 +449,10 @@ abstract class AbstractParameter extends AbstractNamedDBElement implements Uniqu
if (!$with_latex) { if (!$with_latex) {
$unit = $this->unit; $unit = $this->unit;
} else { } else {
$unit = '$\mathrm{'.$this->unit.'}$'; //Escape the percentage sign for convenience (as latex uses it as comment and it is often used in units)
$escaped = preg_replace('/\\\\?%/', "\\\\%", $this->unit);
$unit = '$\mathrm{'.$escaped.'}$';
} }
return $str.' '.$unit; return $str.' '.$unit;

View file

@ -241,6 +241,49 @@ class KiCadHelper
$result["fields"]["Part-DB IPN"] = $this->createField($part->getIpn()); $result["fields"]["Part-DB IPN"] = $this->createField($part->getIpn());
} }
// Add supplier information from orderdetails (include obsolete orderdetails)
if ($part->getOrderdetails(false)->count() > 0) {
$supplierCounts = [];
foreach ($part->getOrderdetails(false) as $orderdetail) {
if ($orderdetail->getSupplier() !== null && $orderdetail->getSupplierPartNr() !== '') {
$supplierName = $orderdetail->getSupplier()->getName();
$supplierName .= " SPN"; // Append "SPN" to the supplier name to indicate Supplier Part Number
if (!isset($supplierCounts[$supplierName])) {
$supplierCounts[$supplierName] = 0;
}
$supplierCounts[$supplierName]++;
// Create field name with sequential number if more than one from same supplier (e.g. "Mouser", "Mouser 2", etc.)
$fieldName = $supplierCounts[$supplierName] > 1
? $supplierName . ' ' . $supplierCounts[$supplierName]
: $supplierName;
$result["fields"][$fieldName] = $this->createField($orderdetail->getSupplierPartNr());
}
}
}
//Add fields for KiCost:
if ($part->getManufacturer() !== null) {
$result["fields"]["manf"] = $this->createField($part->getManufacturer()->getName());
}
if ($part->getManufacturerProductNumber() !== "") {
$result['fields']['manf#'] = $this->createField($part->getManufacturerProductNumber());
}
//For each supplier, add a field with the supplier name and the supplier part number for KiCost
if ($part->getOrderdetails(false)->count() > 0) {
foreach ($part->getOrderdetails(false) as $orderdetail) {
if ($orderdetail->getSupplier() !== null && $orderdetail->getSupplierPartNr() !== '') {
$fieldName = mb_strtolower($orderdetail->getSupplier()->getName()) . '#';
$result["fields"][$fieldName] = $this->createField($orderdetail->getSupplierPartNr());
}
}
}
return $result; return $result;
} }

View file

@ -179,10 +179,7 @@ class TreeViewGenerator
} }
if (($mode === 'list_parts_root' || $mode === 'devices') && $this->rootNodeEnabled) { if (($mode === 'list_parts_root' || $mode === 'devices') && $this->rootNodeEnabled) {
//We show the root node as a link to the list of all parts $root_node = new TreeViewNode($this->entityClassToRootNodeString($class), $this->entityClassToRootNodeHref($class), $generic);
$show_all_parts_url = $this->router->generate('parts_show_all');
$root_node = new TreeViewNode($this->entityClassToRootNodeString($class), $show_all_parts_url, $generic);
$root_node->setExpanded($this->rootNodeExpandedByDefault); $root_node->setExpanded($this->rootNodeExpandedByDefault);
$root_node->setIcon($this->entityClassToRootNodeIcon($class)); $root_node->setIcon($this->entityClassToRootNodeIcon($class));
@ -192,6 +189,27 @@ class TreeViewGenerator
return array_merge($head, $generic); return array_merge($head, $generic);
} }
protected function entityClassToRootNodeHref(string $class): ?string
{
//If the root node should redirect to the new entity page, we return the URL for the new entity.
if ($this->sidebarSettings->rootNodeRedirectsToNewEntity) {
return match ($class) {
Category::class => $this->router->generate('category_new'),
StorageLocation::class => $this->router->generate('store_location_new'),
Footprint::class => $this->router->generate('footprint_new'),
Manufacturer::class => $this->router->generate('manufacturer_new'),
Supplier::class => $this->router->generate('supplier_new'),
Project::class => $this->router->generate('project_new'),
default => null,
};
}
return match ($class) {
Project::class => $this->router->generate('project_new'),
default => $this->router->generate('parts_show_all')
};
}
protected function entityClassToRootNodeString(string $class): string protected function entityClassToRootNodeString(string $class): string
{ {
return match ($class) { return match ($class) {

View file

@ -67,4 +67,6 @@ class SidebarSettings
*/ */
#[SettingsParameter(label: new TM("settings.behavior.sidebar.rootNodeExpanded"))] #[SettingsParameter(label: new TM("settings.behavior.sidebar.rootNodeExpanded"))]
public bool $rootNodeExpanded = true; public bool $rootNodeExpanded = true;
public bool $rootNodeRedirectsToNewEntity = false;
} }

View file

@ -13,6 +13,7 @@
<div class="carousel-item {% if loop.first %}active{% endif %}"> <div class="carousel-item {% if loop.first %}active{% endif %}">
<a href="{{ entity_url(pic, 'file_view') }}" data-turbo="false" target="_blank" rel="noopener"> <a href="{{ entity_url(pic, 'file_view') }}" data-turbo="false" target="_blank" rel="noopener">
<img class="d-block w-100 img-fluid img-thumbnail bg-light part-info-image" src="{{ entity_url(pic, 'file_view') }}" alt=""> <img class="d-block w-100 img-fluid img-thumbnail bg-light part-info-image" src="{{ entity_url(pic, 'file_view') }}" alt="">
{% if img_overlay %}
<div class="mask"></div> <div class="mask"></div>
<div class="carousel-caption-hover"> <div class="carousel-caption-hover">
<div class="carousel-caption text-white"> <div class="carousel-caption text-white">
@ -21,6 +22,7 @@
<div>{{ entity_type_label(pic.element) }}</div> <div>{{ entity_type_label(pic.element) }}</div>
</div> </div>
</div> </div>
{% endif %}
</a> </a>
</div> </div>
{% endfor %} {% endfor %}