Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[UX-Icons] Allow Reuse #1800

Open
Jibbarth opened this issue Apr 29, 2024 · 4 comments
Open

[UX-Icons] Allow Reuse #1800

Jibbarth opened this issue Apr 29, 2024 · 4 comments
Labels
Feature New Feature Icons

Comments

@Jibbarth
Copy link
Contributor

Jibbarth commented Apr 29, 2024

When an SVG has been added once in a page, it can be reused with the <use xlink:href=#id> later in the same page to avoid duplication of svg.

It could be great if ux_icon would have a allowReuse parameters to transform icon to use a previously added.

For example :

    {{ ux_icon('fa6-brands:symfony', {}, true)}}
    {{ ux_icon('fa6-brands:symfony', {'style':'color:red'}, true)}}

Would produce following html

<div class="example-wrapper">
    <svg viewBox="0 0 512 512" fill="currentColor" aria-hidden="true" id="fa6-brands--symfony"><path fill="currentColor" d="M256 8C119 8 8 119 8 256s111 248 248 248s248-111 248-248S393 8 256 8m133.74 143.54c-11.47.41-19.4-6.45-19.77-16.87c-.27-9.18 6.68-13.44 6.53-18.85c-.23-6.55-10.16-6.82-12.87-6.67c-39.78 1.29-48.59 57-58.89 113.85c21.43 3.15 36.65-.72 45.14-6.22c12-7.75-3.34-15.72-1.42-24.56c4-18.16 32.55-19 32 5.3c-.36 17.86-25.92 41.81-77.6 35.7c-10.76 59.52-18.35 115-58.2 161.72c-29 34.46-58.4 39.82-71.58 40.26c-24.65.85-41-12.31-41.58-29.84c-.56-17 14.45-26.26 24.31-26.59c21.89-.75 30.12 25.67 14.88 34c-12.09 9.71.11 12.61 2.05 12.55c10.42-.36 17.34-5.51 22.18-9c24-20 33.24-54.86 45.35-118.35c8.19-49.66 17-78 18.23-82c-16.93-12.75-27.08-28.55-49.85-34.72c-15.61-4.23-25.12-.63-31.81 7.83c-7.92 10-5.29 23 2.37 30.7l12.63 14c15.51 17.93 24 31.87 20.8 50.62c-5.06 29.93-40.72 52.9-82.88 39.94c-36-11.11-42.7-36.56-38.38-50.62c7.51-24.15 42.36-11.72 34.62 13.6c-2.79 8.6-4.92 8.68-6.28 13.07c-4.56 14.77 41.85 28.4 51-1.39c4.47-14.52-5.3-21.71-22.25-39.85c-28.47-31.75-16-65.49 2.95-79.67C204.23 140.13 251.94 197 262 205.29c37.17-109 100.53-105.46 102.43-105.53c25.16-.81 44.19 10.59 44.83 28.65c.25 7.69-4.17 22.59-19.52 23.13"></path></svg>
    <svg viewBox="0 0 512 512" fill="currentColor" aria-hidden="true" style="color:red"><use xlink:href="#fa6-brands--symfony"></use></svg>
</div>

image

I was able to achieve this by adding following line in IconRenderer

final class IconRenderer
{
+    private array $renderedIcons = [];

    public function renderIcon(string $name, array $attributes = [], bool $allowReuse = false): string
    {
        $icon = $this->registry->get($name)
            ->withAttributes($this->getIconAttributes($name, $attributes));
        
+        if ($allowReuse) {
+            $id = $icon::nameToId($name);
+            if (array_key_exists($id, $this->renderedIcons)) {
+                $icon = new Icon('<use xlink:href="#'.$id.'"/>', $icon->getAttributes());
+            } else {
+                $icon = $icon->withAttributes(['id' => $id]);
+                $this->renderedIcons[$id] = true; 
+            }
+        }

        return $icon->toHtml();
    }

For really complexe svg reused multiple time, it could save the size of the generated page.

WDYT ? If you think such feature would be nice, I could provide a PR.

@smnandre
Copy link
Collaborator

It is something we have in mind (@kbond even prototyped something as first), but this is much more complex than it seems

Webpages are very often not rendered in a linear / complete manner (think cache, ESI, components, duplicate ids, ...)

@smnandre smnandre added Feature New Feature Icons labels Apr 29, 2024
@kbond
Copy link
Member

kbond commented Apr 30, 2024

Yes, this is the deferred idea I originally had in ux-icons. Blade icons has a similar feature.

Simon and I are planning some refactoring so I'd like to hold off on this until this is done. We'll keep it in mind though as I think it's required.

@Jibbarth
Copy link
Contributor Author

Jibbarth commented May 4, 2024

For the record, I was able to implement it on my personal project by adding a decorator on top of the IconRegistry, instead of the IconRenderer.

I share it below if anyone need this, but remember it may not work in all cases as mentionned by @smnandre, and we shouldn't decorate internal services ⚠️

View code
<?php

declare(strict_types=1);

namespace App\Icon;

use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\UX\Icons\Icon;
use Symfony\UX\Icons\IconRegistryInterface;

#[AsDecorator('.ux_icons.icon_registry')]
final class IconAlreadyUsedRegistry implements IconRegistryInterface
{
    /**
     * @var array<string, string>
     */
    private array $alreadyUsed = [];

    public function __construct(
        private RequestStack $requestStack,
        private IconRegistryInterface $decorated,
    ) {}

    public function get(string $name): Icon
    {
        $icon = $this->decorated->get($name);

        $id = $icon::nameToId($name);
        // Generate an unique Id for each icon by Request
        // Needed as static-site does not restart kernel on each request,
        // and icon are considered as already used on new pages
        $request = $this->requestStack->getCurrentRequest();
        if (null === $request) {
            return $icon;
        }
        $id = md5(sprintf('%s-%s', $id, spl_object_id($request)));
        if (!\array_key_exists($id, $this->alreadyUsed)) {
            $this->alreadyUsed[$id] = $id;

            return $icon->withAttributes(['id' => $id]);
        }

        // Return a reusable icon, take only viewBox attribute to avoid rendering issues
        return new Icon('<use xlink:href="#' . $id . '"/>', [$icon->getAttributes()['viewBox']]);
    }
}

Fully implemented here

@smnandre
Copy link
Collaborator

smnandre commented May 5, 2024

We are working on the configuration of icons (default attributes, prefix aliases). Once it's done we will be able to implement this feature (step by step) :)


I think the separation between the symbols (what's gonna be reused) and the rendered icons (using those symbols) should be stronger... Or we may have troubles with default attributes, runtime render ones, etc.

So the final HTML should probably be more like

 <svg style="display: none;" hidden>
    <symbol id="icon:lucide:heart" viewBox="0 0 512 512"> ... </symbol>
</svg>

<svg viewBox="0 0 512 512"><use href="#icon:lucide:heart"></use></svg>
<svg viewBox="0 0 512 512" style="color:red"><use href="#icon:lucide:heart"></use></svg>

Two comments here:

  • href must be employed there, as xlink:href is deprecated
  • i'm using custom syntax for id here, that would be to decide, but we must avoid id collisions (with another render, but also with any userland ones... so there is some thinking to do and non-used syntaxes/characters may be a way out)

The other complexity lies in those two questions:

Can we ensure a symbol has been renderered in the DOM before we render an icon using it
Can we ensure a symbol has not been rendered when we render it

I'm not saying we must find a single silver-bullet answer.
And i'm convinved there will be some choices about the DX / performances / predictability to me made there.
But they are the one to keep in mind :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature New Feature Icons
Projects
None yet
Development

No branches or pull requests

3 participants