diff --git a/assets/controllers/elements/tagsinput_controller.js b/assets/controllers/elements/tagsinput_controller.js index 4454089f..88f1626a 100644 --- a/assets/controllers/elements/tagsinput_controller.js +++ b/assets/controllers/elements/tagsinput_controller.js @@ -8,7 +8,6 @@ export default class extends Controller { _tomSelect; connect() { - let settings = { plugins: { remove_button:{ diff --git a/assets/controllers/pages/parameters_autocomplete_controller.js b/assets/controllers/pages/parameters_autocomplete_controller.js new file mode 100644 index 00000000..2d75bc57 --- /dev/null +++ b/assets/controllers/pages/parameters_autocomplete_controller.js @@ -0,0 +1,95 @@ +import {Controller} from "@hotwired/stimulus"; +import TomSelect from "tom-select"; +import katex from "katex"; +import "katex/dist/katex.css"; + +/* stimulusFetch: 'lazy' */ +export default class extends Controller +{ + static values = { + url: String, + } + + static targets = ["name", "symbol", "unit"] + + onItemAdd(value, item) { + //Retrieve the unit and symbol from the item + const symbol = item.dataset.symbol; + const unit = item.dataset.unit; + + if (this.symbolTarget && symbol !== undefined) { + this.symbolTarget.value = symbol; + } + if (this.unitTarget && unit !== undefined) { + this.unitTarget.value = unit; + } + } + + connect() { + const settings = { + plugins: { + clear_button:{} + }, + persistent: false, + maxItems: 1, + //This a an ugly solution to disable the delimiter parsing of the TomSelect plugin + delimiter: 'VERY_L0NG_D€LIMITER_WHICH_WILL_NEVER_BE_ENCOUNTERED_IN_A_STRING', + createOnBlur: true, + create: true, + searchField: "name", + //labelField: "name", + valueField: "name", + onItemAdd: this.onItemAdd.bind(this), + render: { + option: (data, escape) => { + let tmp = '
' + + '' + escape(data.name) + '
'; + + if (data.symbol) { + tmp += '' + katex.renderToString(data.symbol) + '' + } + if (data.unit) { + tmp += '' + katex.renderToString('[' + data.unit + ']') + '' + } + + + //+ '' + escape(data.unit) + '' + tmp += '
'; + + return tmp; + }, + item: (data, escape) => { + //We use the item to transfert data to the onItemAdd function using data attributes + const element = document.createElement('div'); + element.innerText = data.name; + if(data.unit !== undefined) { + element.dataset.unit = data.unit; + } + if (data.symbol !== undefined) { + element.dataset.symbol = data.symbol; + } + + return element.outerHTML; + } + } + }; + + if(this.urlValue) { + const base_url = this.urlValue; + settings.load = (query, callback) => { + const url = base_url.replace('__QUERY__', encodeURIComponent(query)); + + fetch(url) + .then(response => response.json()) + .then(json => { + //const data = json.map(x => {return {"value": x, "text": x}}); + callback(json); + }).catch(()=>{ + callback(); + }); + } + } + + this._tomSelect = new TomSelect(this.nameTarget, settings); + } +} \ No newline at end of file diff --git a/src/Controller/TypeaheadController.php b/src/Controller/TypeaheadController.php index f69f137e..77edae80 100644 --- a/src/Controller/TypeaheadController.php +++ b/src/Controller/TypeaheadController.php @@ -42,9 +42,22 @@ declare(strict_types=1); namespace App\Controller; +use App\Entity\Parameters\AttachmentTypeParameter; +use App\Entity\Parameters\CategoryParameter; +use App\Entity\Parameters\DeviceParameter; +use App\Entity\Parameters\FootprintParameter; +use App\Entity\Parameters\GroupParameter; +use App\Entity\Parameters\ManufacturerParameter; +use App\Entity\Parameters\MeasurementUnitParameter; +use App\Entity\Parameters\PartParameter; +use App\Entity\Parameters\StorelocationParameter; +use App\Entity\Parameters\SupplierParameter; +use App\Entity\PriceInformations\Currency; +use App\Repository\ParameterRepository; use App\Services\Attachments\AttachmentURLGenerator; use App\Services\Attachments\BuiltinAttachmentsFinder; use App\Services\TagFinder; +use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Asset\Packages; use Symfony\Component\HttpFoundation\JsonResponse; @@ -99,6 +112,58 @@ class TypeaheadController extends AbstractController return new JsonResponse($data, 200, [], true); } + /** + * This functions map the parameter type to the class, so we can access its repository + * @param string $type + * @return class-string + */ + private function typeToParameterClass(string $type): string + { + switch ($type) { + case 'category': + return CategoryParameter::class; + case 'part': + return PartParameter::class; + case 'device': + return DeviceParameter::class; + case 'footprint': + return FootprintParameter::class; + case 'manufacturer': + return ManufacturerParameter::class; + case 'storelocation': + return StorelocationParameter::class; + case 'supplier': + return SupplierParameter::class; + case 'attachment_type': + return AttachmentTypeParameter::class; + case 'group': + return GroupParameter::class; + case 'measurement_unit': + return MeasurementUnitParameter::class; + case 'currency': + return Currency::class; + + default: + throw new \InvalidArgumentException('Invalid parameter type: '.$type); + } + } + + /** + * @Route("/parameters/{type}/search/{query}", name="typeahead_parameters", requirements={"type" = ".+"}) + * @param string $query + * @return JsonResponse + */ + public function parameters(string $type, EntityManagerInterface $entityManager, string $query = ""): JsonResponse + { + $class = $this->typeToParameterClass($type); + /** @var ParameterRepository $repository */ + $repository = $entityManager->getRepository($class); + + $data = $repository->autocompleteParamName($query); + + return new JsonResponse($data); + } + /** * @Route("/tags/search/{query}", name="typeahead_tags", requirements={"query"= ".+"}) */ diff --git a/src/Entity/Parameters/AbstractParameter.php b/src/Entity/Parameters/AbstractParameter.php index ba13f623..b7427985 100644 --- a/src/Entity/Parameters/AbstractParameter.php +++ b/src/Entity/Parameters/AbstractParameter.php @@ -33,7 +33,7 @@ use Symfony\Component\Validator\Constraints as Assert; use function sprintf; /** - * @ORM\Entity() + * @ORM\Entity(repositoryClass="App\Repository\ParameterRepository") * @ORM\Table("parameters") * @ORM\InheritanceType("SINGLE_TABLE") * @ORM\DiscriminatorColumn(name="type", type="smallint") diff --git a/src/Entity/Parameters/AttachmentTypeParameter.php b/src/Entity/Parameters/AttachmentTypeParameter.php index d1ad9094..1a7e4f37 100644 --- a/src/Entity/Parameters/AttachmentTypeParameter.php +++ b/src/Entity/Parameters/AttachmentTypeParameter.php @@ -28,7 +28,7 @@ use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; /** - * @ORM\Entity() + * @ORM\Entity(repositoryClass="App\Repository\ParameterRepository") * @UniqueEntity(fields={"name", "group", "element"}) */ class AttachmentTypeParameter extends AbstractParameter diff --git a/src/Entity/Parameters/CategoryParameter.php b/src/Entity/Parameters/CategoryParameter.php index 86925755..1165f985 100644 --- a/src/Entity/Parameters/CategoryParameter.php +++ b/src/Entity/Parameters/CategoryParameter.php @@ -28,7 +28,7 @@ use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; /** - * @ORM\Entity() + * @ORM\Entity(repositoryClass="App\Repository\ParameterRepository") * @UniqueEntity(fields={"name", "group", "element"}) */ class CategoryParameter extends AbstractParameter diff --git a/src/Entity/Parameters/CurrencyParameter.php b/src/Entity/Parameters/CurrencyParameter.php index 28f3c934..8651ec13 100644 --- a/src/Entity/Parameters/CurrencyParameter.php +++ b/src/Entity/Parameters/CurrencyParameter.php @@ -30,7 +30,7 @@ use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; /** * A attachment attached to a category element. * - * @ORM\Entity() + * @ORM\Entity(repositoryClass="App\Repository\ParameterRepository") * @UniqueEntity(fields={"name", "group", "element"}) */ class CurrencyParameter extends AbstractParameter diff --git a/src/Entity/Parameters/DeviceParameter.php b/src/Entity/Parameters/DeviceParameter.php index 724ad3be..1534c84a 100644 --- a/src/Entity/Parameters/DeviceParameter.php +++ b/src/Entity/Parameters/DeviceParameter.php @@ -28,7 +28,7 @@ use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; /** - * @ORM\Entity() + * @ORM\Entity(repositoryClass="App\Repository\ParameterRepository") * @UniqueEntity(fields={"name", "group", "element"}) */ class DeviceParameter extends AbstractParameter diff --git a/src/Entity/Parameters/FootprintParameter.php b/src/Entity/Parameters/FootprintParameter.php index d92ed8cb..ef354ef1 100644 --- a/src/Entity/Parameters/FootprintParameter.php +++ b/src/Entity/Parameters/FootprintParameter.php @@ -28,7 +28,7 @@ use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; /** - * @ORM\Entity() + * @ORM\Entity(repositoryClass="App\Repository\ParameterRepository") * @UniqueEntity(fields={"name", "group", "element"}) */ class FootprintParameter extends AbstractParameter diff --git a/src/Entity/Parameters/GroupParameter.php b/src/Entity/Parameters/GroupParameter.php index c6b62aa1..361a60cd 100644 --- a/src/Entity/Parameters/GroupParameter.php +++ b/src/Entity/Parameters/GroupParameter.php @@ -28,7 +28,7 @@ use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; /** - * @ORM\Entity() + * @ORM\Entity(repositoryClass="App\Repository\ParameterRepository") * @UniqueEntity(fields={"name", "group", "element"}) */ class GroupParameter extends AbstractParameter diff --git a/src/Entity/Parameters/ManufacturerParameter.php b/src/Entity/Parameters/ManufacturerParameter.php index b06633f0..fbdf8fe6 100644 --- a/src/Entity/Parameters/ManufacturerParameter.php +++ b/src/Entity/Parameters/ManufacturerParameter.php @@ -28,7 +28,7 @@ use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; /** - * @ORM\Entity() + * @ORM\Entity(repositoryClass="App\Repository\ParameterRepository") * @UniqueEntity(fields={"name", "group", "element"}) */ class ManufacturerParameter extends AbstractParameter diff --git a/src/Entity/Parameters/MeasurementUnitParameter.php b/src/Entity/Parameters/MeasurementUnitParameter.php index 91ce5809..52af7a13 100644 --- a/src/Entity/Parameters/MeasurementUnitParameter.php +++ b/src/Entity/Parameters/MeasurementUnitParameter.php @@ -28,7 +28,7 @@ use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; /** - * @ORM\Entity() + * @ORM\Entity(repositoryClass="App\Repository\ParameterRepository") * @UniqueEntity(fields={"name", "group", "element"}) */ class MeasurementUnitParameter extends AbstractParameter diff --git a/src/Entity/Parameters/PartParameter.php b/src/Entity/Parameters/PartParameter.php index d7d08cfa..7f30cd02 100644 --- a/src/Entity/Parameters/PartParameter.php +++ b/src/Entity/Parameters/PartParameter.php @@ -28,7 +28,7 @@ use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; /** - * @ORM\Entity() + * @ORM\Entity(repositoryClass="App\Repository\ParameterRepository") * @UniqueEntity(fields={"name", "group", "element"}) */ class PartParameter extends AbstractParameter diff --git a/src/Entity/Parameters/StorelocationParameter.php b/src/Entity/Parameters/StorelocationParameter.php index 44077d48..76c209a6 100644 --- a/src/Entity/Parameters/StorelocationParameter.php +++ b/src/Entity/Parameters/StorelocationParameter.php @@ -28,7 +28,7 @@ use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; /** - * @ORM\Entity() + * @ORM\Entity(repositoryClass="App\Repository\ParameterRepository") * @UniqueEntity(fields={"name", "group", "element"}) */ class StorelocationParameter extends AbstractParameter diff --git a/src/Entity/Parameters/SupplierParameter.php b/src/Entity/Parameters/SupplierParameter.php index e61137a0..3bc42a56 100644 --- a/src/Entity/Parameters/SupplierParameter.php +++ b/src/Entity/Parameters/SupplierParameter.php @@ -28,7 +28,7 @@ use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; /** - * @ORM\Entity() + * @ORM\Entity(repositoryClass="App\Repository\ParameterRepository") * @UniqueEntity(fields={"name", "group", "element"}) */ class SupplierParameter extends AbstractParameter diff --git a/src/Repository/ParameterRepository.php b/src/Repository/ParameterRepository.php new file mode 100644 index 00000000..36ee2ff8 --- /dev/null +++ b/src/Repository/ParameterRepository.php @@ -0,0 +1,33 @@ +createQueryBuilder('parameter'); + + $qb->distinct() + ->select('parameter.name') + ->addSelect('parameter.symbol') + ->addSelect('parameter.unit') + ->where('parameter.name LIKE :name'); + if ($exact) { + $qb->setParameter('name', $name); + } else { + $qb->setParameter('name', '%'.$name.'%'); + } + + $qb->setMaxResults($max_results); + + return $qb->getQuery()->getArrayResult(); + } +} \ No newline at end of file diff --git a/src/Services/TagFinder.php b/src/Services/TagFinder.php deleted file mode 100644 index 4038bc34..00000000 --- a/src/Services/TagFinder.php +++ /dev/null @@ -1,117 +0,0 @@ -. - */ - -declare(strict_types=1); - -/** - * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). - * - * Copyright (C) 2019 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 General Public License - * as published by the Free Software Foundation; either version 2 - * 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA - */ - -namespace App\Services; - -use App\Entity\Parts\Part; -use Doctrine\ORM\EntityManagerInterface; -use Symfony\Component\OptionsResolver\OptionsResolver; - -use function array_slice; - -/** - * A service related for searching for tags. Mostly useful for autocomplete reasons. - */ -class TagFinder -{ - protected $em; - - public function __construct(EntityManagerInterface $entityManager) - { - $this->em = $entityManager; - } - - /** - * Search tags that begins with the certain keyword. - * - * @param string $keyword The keyword the tag must begin with - * @param array $options Some options specifying the search behavior. See configureOptions for possible options. - * - * @return string[] an array containing the tags that match the given keyword - */ - public function searchTags(string $keyword, array $options = []): array - { - $results = []; - $keyword_regex = '/^'.preg_quote($keyword, '/').'/'; - - $resolver = new OptionsResolver(); - $this->configureOptions($resolver); - - $options = $resolver->resolve($options); - - //If the keyword is too short we will get to much results, which takes too much time... - if (mb_strlen($keyword) < $options['min_keyword_length']) { - return []; - } - - //Build a query to get all - $qb = $this->em->createQueryBuilder(); - - $qb->select('p.tags') - ->from(Part::class, 'p') - ->where('p.tags LIKE ?1') - ->setMaxResults($options['query_limit']) - //->orderBy('RAND()') - ->setParameter(1, '%'.$keyword.'%'); - - $possible_tags = $qb->getQuery()->getArrayResult(); - - //Iterate over each possible tags (which are comma separated) and extract tags which match our keyword - foreach ($possible_tags as $tags) { - $tags = explode(',', $tags['tags']); - $results = array_merge($results, preg_grep($keyword_regex, $tags)); - } - - $results = array_unique($results); - //Limit the returned tag count to specified value. - return array_slice($results, 0, $options['return_limit']); - } - - protected function configureOptions(OptionsResolver $resolver): void - { - $resolver->setDefaults([ - 'query_limit' => 75, - 'return_limit' => 75, - 'min_keyword_length' => 2, - ]); - } -} diff --git a/templates/Parts/edit/edit_form_styles.html.twig b/templates/Parts/edit/edit_form_styles.html.twig index 8fef44b4..003be938 100644 --- a/templates/Parts/edit/edit_form_styles.html.twig +++ b/templates/Parts/edit/edit_form_styles.html.twig @@ -68,13 +68,13 @@ {% block parameter_widget %} {% import 'components/collection_type.macro.html.twig' as collection %} - - {{ form_widget(form.name) }}{{ form_errors(form.name) }} - {{ form_widget(form.symbol) }}{{ form_errors(form.symbol) }} + + {{ form_widget(form.name, {"attr": {"data-pages--parameters-autocomplete-target": "name"}}) }}{{ form_errors(form.name) }} + {{ form_widget(form.symbol, {"attr": {"data-pages--parameters-autocomplete-target": "symbol"}}) }}{{ form_errors(form.symbol) }} {{ form_widget(form.value_min) }}{{ form_errors(form.value_min) }} {{ form_widget(form.value_typical) }}{{ form_errors(form.value_typical) }} {{ form_widget(form.value_max) }}{{ form_errors(form.value_max) }} - {{ form_widget(form.unit) }}{{ form_errors(form.unit) }} + {{ form_widget(form.unit, {"attr": {"data-pages--parameters-autocomplete-target": "unit"}}) }}{{ form_errors(form.unit) }} {{ form_widget(form.value_text) }}{{ form_errors(form.value_text) }} {{ form_widget(form.group) }}{{ form_errors(form.group) }}