mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2026-08-04 07:31:40 +00:00
Move options from Settings to localStorage
This commit is contained in:
parent
3702c079b2
commit
2a1c985715
7 changed files with 63 additions and 112 deletions
|
|
@ -37,7 +37,6 @@ use App\Form\Filters\PartFilterType;
|
|||
use App\Services\Parts\PartsTableActionHandler;
|
||||
use App\Services\Trees\NodesListBuilder;
|
||||
use App\Settings\BehaviorSettings\SidebarSettings;
|
||||
use App\Settings\BehaviorSettings\SearchSettings;
|
||||
use App\Settings\BehaviorSettings\TableSettings;
|
||||
use Doctrine\DBAL\Exception\DriverException;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
|
@ -60,7 +59,6 @@ class PartListsController extends AbstractController
|
|||
private readonly TranslatorInterface $translator,
|
||||
private readonly TableSettings $tableSettings,
|
||||
private readonly SidebarSettings $sidebarSettings,
|
||||
private readonly SearchSettings $searchSettings,
|
||||
)
|
||||
{
|
||||
}
|
||||
|
|
@ -317,7 +315,7 @@ class PartListsController extends AbstractController
|
|||
|
||||
private function searchRequestToFilter(Request $request): PartSearchFilter
|
||||
{
|
||||
$filter = new PartSearchFilter($request->query->get('keyword', ''), $this->searchSettings);
|
||||
$filter = new PartSearchFilter($request->query->get('keyword', ''));
|
||||
|
||||
//As an unchecked checkbox is not set in the query, the default value for all bools have to be false (which is the default argument value)!
|
||||
$filter->setName($request->query->getBoolean('name'));
|
||||
|
|
@ -336,6 +334,8 @@ class PartListsController extends AbstractController
|
|||
|
||||
|
||||
$filter->setRegex($request->query->getBoolean('regex'));
|
||||
$filter->setExtensive($request->query->getBoolean('extensive'));
|
||||
$filter->setWildcard($request->query->getBoolean('wildcard'));
|
||||
|
||||
return $filter;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,13 +26,18 @@ use Doctrine\Common\Collections\ArrayCollection;
|
|||
use Doctrine\ORM\QueryBuilder;
|
||||
use Doctrine\ORM\Query\Parameter;
|
||||
use Doctrine\DBAL\ParameterType;
|
||||
use App\Settings\BehaviorSettings\SearchSettings;
|
||||
|
||||
class PartSearchFilter implements FilterInterface
|
||||
{
|
||||
|
||||
/** @var boolean Whether to use regex for searching */
|
||||
protected bool $regex = false;
|
||||
|
||||
/** @var boolean Whether to use extensive matching for searching */
|
||||
protected bool $extensive = false;
|
||||
|
||||
/** @var boolean Whether to use wildcards for searching */
|
||||
protected bool $wildcard = false;
|
||||
|
||||
/** @var bool Use name field for searching */
|
||||
protected bool $name = true;
|
||||
|
|
@ -78,9 +83,7 @@ class PartSearchFilter implements FilterInterface
|
|||
|
||||
public function __construct(
|
||||
/** @var string The string to query for */
|
||||
protected string $keyword,
|
||||
/** @var SearchSettings The settings that control how the search operates */
|
||||
private readonly SearchSettings $searchSettings,
|
||||
protected string $keyword
|
||||
) {
|
||||
|
||||
}
|
||||
|
|
@ -138,11 +141,11 @@ class PartSearchFilter implements FilterInterface
|
|||
$search_dbId = $is_numeric && (bool)$this->dbId;
|
||||
|
||||
$tokens = [];
|
||||
if ($this->searchSettings->enableAdvancedSearch) {
|
||||
if ($this->extensive) {
|
||||
//Transform keyword and trim excess spaces
|
||||
$this->keyword = trim(str_replace('+', ' ', $this->keyword));
|
||||
//Split keyword on spaces, but limit token count (default is 3)
|
||||
$tokens = explode(' ', $this->keyword, $this->searchSettings->searchTokenLimit);
|
||||
//Split keyword on spaces, but limit token count to 5
|
||||
$tokens = explode(' ', $this->keyword, 5);
|
||||
//Throw away array elements which are null or have zero length
|
||||
$tokens = array_filter($tokens, fn($x) => (strlen($x) > 0));
|
||||
}
|
||||
|
|
@ -176,7 +179,7 @@ class PartSearchFilter implements FilterInterface
|
|||
//Add a new expression and parameter set to the query for each token
|
||||
foreach ($tokens as $i => $token) {
|
||||
//Conditionally escape % and _ characters
|
||||
if ($this->searchSettings->escapeSQLWildcards)
|
||||
if (!$this->wildcard)
|
||||
$token = str_replace(['%', '_'], ['\%', '\_'], $token);
|
||||
|
||||
//Convert the fields to search to a list of expressions
|
||||
|
|
@ -240,6 +243,30 @@ class PartSearchFilter implements FilterInterface
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function isExtensive(): bool
|
||||
{
|
||||
return $this->extensive;
|
||||
}
|
||||
|
||||
public function setExtensive(bool $extensive): PartSearchFilter
|
||||
{
|
||||
$this->extensive = $extensive;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function isWildcard(): bool
|
||||
{
|
||||
return $this->wildcard;
|
||||
}
|
||||
|
||||
public function setWildcard(bool $wildcard): PartSearchFilter
|
||||
{
|
||||
$this->wildcard = $wildcard;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function isName(): bool
|
||||
{
|
||||
return $this->name;
|
||||
|
|
|
|||
|
|
@ -44,7 +44,4 @@ class BehaviorSettings
|
|||
|
||||
#[EmbeddedSettings]
|
||||
public ?KeybindingsSettings $keybindings = null;
|
||||
|
||||
#[EmbeddedSettings]
|
||||
public ?SearchSettings $search = null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,74 +0,0 @@
|
|||
<?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\BehaviorSettings;
|
||||
|
||||
use App\Settings\SettingsIcon;
|
||||
use Jbtronics\SettingsBundle\Metadata\EnvVarMode;
|
||||
use Jbtronics\SettingsBundle\Settings\Settings;
|
||||
use Jbtronics\SettingsBundle\Settings\SettingsParameter;
|
||||
use Symfony\Component\Translation\TranslatableMessage as TM;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
#[Settings(name: "search", label: new TM("settings.behavior.search"))]
|
||||
#[SettingsIcon('fa-magnifying-glass')]
|
||||
class SearchSettings
|
||||
{
|
||||
/**
|
||||
* Whether to enable advanced search
|
||||
* @var bool
|
||||
*/
|
||||
#[SettingsParameter(
|
||||
label: new TM("settings.behavior.search.enable_advanced_search"),
|
||||
description: new TM("settings.behavior.search.enable_advanced_search.help"),
|
||||
envVar: "bool:ENABLE_ADVANCED_SEARCH",
|
||||
envVarMode: EnvVarMode::OVERWRITE
|
||||
)]
|
||||
public bool $enableAdvancedSearch = false;
|
||||
|
||||
/**
|
||||
* Defines the maximum number of tokens the keyword can be split into
|
||||
* @var int
|
||||
*/
|
||||
#[SettingsParameter(
|
||||
label: new TM("settings.behavior.search.token_limit"),
|
||||
description: new TM("settings.behavior.search.token_limit.help"),
|
||||
envVar: "int:SEARCH_TOKEN_LIMIT",
|
||||
envVarMode: EnvVarMode::OVERWRITE,
|
||||
formOptions: ['attr' => ['min' => 2, 'max' => 5]],
|
||||
)]
|
||||
#[Assert\Range(min: 2, max: 5)]
|
||||
public int $searchTokenLimit = 3;
|
||||
|
||||
/**
|
||||
* Whether to escape sql wildcards
|
||||
* @var bool
|
||||
*/
|
||||
#[SettingsParameter(
|
||||
label: new TM("settings.behavior.search.escape_sql_wildcards"),
|
||||
description: new TM("settings.behavior.search.escape_sql_wildcards.help"),
|
||||
envVar: "bool:ESCAPE_SQL_WILDCARDS",
|
||||
envVarMode: EnvVarMode::OVERWRITE
|
||||
)]
|
||||
public bool $escapeSQLWildcards = true;
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
{% macro settings_drodown(show_label_instead_icon = true) %}
|
||||
{% macro settings_dropdown(show_label_instead_icon = true) %}
|
||||
|
||||
<div class="dropdown">
|
||||
<button class="btn dropdown-toggle my-2" type="button" id="navbar-search-options" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" data-bs-auto-close="true">
|
||||
|
|
@ -70,6 +70,14 @@
|
|||
<input type="checkbox" class="form-check-input" id="regex" name="regex" value="1" {{ stimulus_controller('elements/localStorage_checkbox') }}>
|
||||
<label for="regex" class="form-check-label justify-content-start">{% trans %}search.regexmatching{% endtrans %}</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="extensive" name="extensive" value="0" {{ stimulus_controller('elements/localStorage_checkbox') }}>
|
||||
<label for="extensive" class="form-check-label justify-content-start">{% trans %}search.extensivematching{% endtrans %}</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="wildcard" name="wildcard" value="0" {{ stimulus_controller('elements/localStorage_checkbox') }}>
|
||||
<label for="wildcard" class="form-check-label justify-content-start">{% trans %}search.permitwildcards{% endtrans %}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -85,7 +93,7 @@
|
|||
|
||||
{# Show the options left in navbar #}
|
||||
{% if is_navbar %}
|
||||
{{ _self.settings_drodown(is_navbar) }}
|
||||
{{ _self.settings_dropdown(is_navbar) }}
|
||||
{% endif %}
|
||||
|
||||
<div {{ stimulus_controller('elements/part_search') }}
|
||||
|
|
@ -103,7 +111,7 @@
|
|||
|
||||
{# And right in the standalone mode #}
|
||||
{% if not is_navbar %}
|
||||
{{ _self.settings_drodown(is_navbar) }}
|
||||
{{ _self.settings_dropdown(is_navbar) }}
|
||||
{% endif %}
|
||||
</form>
|
||||
{% endmacro %}
|
||||
|
|
|
|||
|
|
@ -69,6 +69,14 @@
|
|||
<input type="checkbox" class="form-check-input" disabled {% if searchFilter.regex %}checked{% endif %}>
|
||||
<label for="regex" class="form-check-label justify-content-start">{% trans %}search.regexmatching{% endtrans %}</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" disabled {% if searchFilter.extensive %}checked{% endif %}>
|
||||
<label for="extensive" class="form-check-label justify-content-start">{% trans %}search.extensivematching{% endtrans %}</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" disabled {% if searchFilter.extensive %}checked{% endif %}>
|
||||
<label for="wildcard" class="form-check-label justify-content-start">{% trans %}search.permitwildcards{% endtrans %}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@
|
|||
namespace App\Tests\DataTables\Filters;
|
||||
|
||||
use App\DataTables\Filters\PartSearchFilter;
|
||||
use App\Settings\BehaviorSettings\SearchSettings;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\DBAL\ParameterType;
|
||||
use Doctrine\ORM\Query\Expr;
|
||||
|
|
@ -34,22 +33,10 @@ use PHPUnit\Framework\TestCase;
|
|||
|
||||
final class PartSearchFilterTest extends TestCase
|
||||
{
|
||||
private function makeSearchSettings(
|
||||
bool $enableAdvancedSearch = false,
|
||||
int $searchTokenLimit = 3,
|
||||
bool $escapeSQLWildcards = true,
|
||||
): SearchSettings {
|
||||
$settings = $this->createMock(SearchSettings::class);
|
||||
$settings->enableAdvancedSearch = $enableAdvancedSearch;
|
||||
$settings->searchTokenLimit = $searchTokenLimit;
|
||||
$settings->escapeSQLWildcards = $escapeSQLWildcards;
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
public function testApplyEnforcesNoResultsWhenKeywordEmpty(): void
|
||||
{
|
||||
$filter = new PartSearchFilter('', $this->makeSearchSettings());
|
||||
$filter = new PartSearchFilter('');
|
||||
|
||||
$qb = $this->createMock(QueryBuilder::class);
|
||||
$qb->expects($this->once())
|
||||
|
|
@ -63,7 +50,7 @@ final class PartSearchFilterTest extends TestCase
|
|||
|
||||
public function testApplyEnforcesNoResultsWhenNothingToSearchForAndNoExactIdSearch(): void
|
||||
{
|
||||
$filter = (new PartSearchFilter('foo', $this->makeSearchSettings()))
|
||||
$filter = (new PartSearchFilter('foo'))
|
||||
->setName(false)
|
||||
->setCategory(false)
|
||||
->setDescription(false)
|
||||
|
|
@ -90,7 +77,7 @@ final class PartSearchFilterTest extends TestCase
|
|||
|
||||
public function testApplyUsesRegexExpressionAndRawParameterWhenRegexEnabled(): void
|
||||
{
|
||||
$filter = (new PartSearchFilter('foo.*bar', $this->makeSearchSettings()))
|
||||
$filter = (new PartSearchFilter('foo.*bar'))
|
||||
->setRegex(true);
|
||||
|
||||
$expr = $this->createStub(Expr::class);
|
||||
|
|
@ -123,9 +110,7 @@ final class PartSearchFilterTest extends TestCase
|
|||
|
||||
public function testApplyEscapesSqlWildcardsAndWrapsLikeParameterWhenRegexDisabled(): void
|
||||
{
|
||||
$filter = (new PartSearchFilter('10%_off', $this->makeSearchSettings(escapeSQLWildcards: true)))
|
||||
->setRegex(false);
|
||||
|
||||
$filter = (new PartSearchFilter('10%_off'));
|
||||
$expr = $this->createMock(Expr::class);
|
||||
$expr->method('orX')->willReturn(new Orx());
|
||||
|
||||
|
|
@ -157,7 +142,7 @@ final class PartSearchFilterTest extends TestCase
|
|||
|
||||
public function testApplyAddsExactIdExpressionWhenDbIdSearchEnabledAndKeywordNumeric(): void
|
||||
{
|
||||
$filter = (new PartSearchFilter('123', $this->makeSearchSettings()))
|
||||
$filter = (new PartSearchFilter('123'))
|
||||
->setDbId(true);
|
||||
|
||||
$expr = $this->createMock(Expr::class);
|
||||
|
|
@ -209,7 +194,7 @@ final class PartSearchFilterTest extends TestCase
|
|||
|
||||
public function testApplyDoesNotAddExactIdExpressionWhenKeywordNotNumeric(): void
|
||||
{
|
||||
$filter = (new PartSearchFilter('123abc', $this->makeSearchSettings()))
|
||||
$filter = (new PartSearchFilter('123abc'))
|
||||
->setDbId(true);
|
||||
|
||||
$expr = $this->createMock(Expr::class);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue