v1.0.0 initial release
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BladeUI\Icons;
|
||||
|
||||
use BladeUI\Icons\Components\Icon;
|
||||
use Illuminate\Contracts\Filesystem\Factory as FilesystemFactory;
|
||||
use Illuminate\Contracts\View\Factory as ViewFactory;
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
final class BladeIconsServiceProvider extends ServiceProvider
|
||||
{
|
||||
public function register(): void
|
||||
{
|
||||
$this->registerConfig();
|
||||
$this->registerFactory();
|
||||
$this->registerManifest();
|
||||
}
|
||||
|
||||
public function boot(): void
|
||||
{
|
||||
$this->bootCommands();
|
||||
$this->bootDirectives();
|
||||
$this->bootIconComponent();
|
||||
$this->bootPublishing();
|
||||
}
|
||||
|
||||
private function registerConfig(): void
|
||||
{
|
||||
$this->mergeConfigFrom(__DIR__.'/../config/blade-icons.php', 'blade-icons');
|
||||
}
|
||||
|
||||
private function registerFactory(): void
|
||||
{
|
||||
$this->app->singleton(Factory::class, function (Application $app) {
|
||||
$config = $app->make('config')->get('blade-icons', []);
|
||||
|
||||
$factory = new Factory(
|
||||
new Filesystem,
|
||||
$app->make(IconsManifest::class),
|
||||
$app->make(FilesystemFactory::class),
|
||||
$config,
|
||||
);
|
||||
|
||||
foreach ($config['sets'] ?? [] as $set => $options) {
|
||||
if (! isset($options['disk']) || ! $options['disk']) {
|
||||
$paths = $options['paths'] ?? $options['path'] ?? [];
|
||||
|
||||
$options['paths'] = array_map(
|
||||
fn ($path) => $app->basePath($path),
|
||||
(array) $paths,
|
||||
);
|
||||
}
|
||||
|
||||
$factory->add($set, $options);
|
||||
}
|
||||
|
||||
return $factory;
|
||||
});
|
||||
|
||||
$this->callAfterResolving(ViewFactory::class, function ($view, Application $app) {
|
||||
$app->make(Factory::class)->registerComponents();
|
||||
});
|
||||
}
|
||||
|
||||
private function registerManifest(): void
|
||||
{
|
||||
$this->app->singleton(IconsManifest::class, function (Application $app) {
|
||||
return new IconsManifest(
|
||||
new Filesystem,
|
||||
$this->manifestPath(),
|
||||
$app->make(FilesystemFactory::class),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private function manifestPath(): string
|
||||
{
|
||||
return $this->app->bootstrapPath('cache/blade-icons.php');
|
||||
}
|
||||
|
||||
private function bootCommands(): void
|
||||
{
|
||||
if ($this->app->runningInConsole()) {
|
||||
$this->commands([
|
||||
Console\CacheCommand::class,
|
||||
Console\ClearCommand::class,
|
||||
]);
|
||||
|
||||
if (method_exists($this, 'optimizes')) {
|
||||
$this->optimizes(
|
||||
'icons:cache',
|
||||
'icons:clear',
|
||||
'blade-icons'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function bootDirectives(): void
|
||||
{
|
||||
Blade::directive('svg', fn ($expression) => "<?php echo e(svg($expression)); ?>");
|
||||
}
|
||||
|
||||
private function bootIconComponent(): void
|
||||
{
|
||||
if ($name = config('blade-icons.components.default')) {
|
||||
Blade::component($name, Icon::class);
|
||||
}
|
||||
}
|
||||
|
||||
private function bootPublishing(): void
|
||||
{
|
||||
if ($this->app->runningInConsole()) {
|
||||
$this->publishes([
|
||||
__DIR__.'/../config/blade-icons.php' => $this->app->configPath('blade-icons.php'),
|
||||
], 'blade-icons');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BladeUI\Icons\Components;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
final class Icon extends Component
|
||||
{
|
||||
public string $name;
|
||||
|
||||
public function __construct(string $name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
public function render(): Closure
|
||||
{
|
||||
return function (array $data) {
|
||||
$attributes = $data['attributes']->getIterator()->getArrayCopy();
|
||||
|
||||
$class = $attributes['class'] ?? '';
|
||||
|
||||
unset($attributes['class']);
|
||||
|
||||
return svg($this->name, $class, $attributes)->toHtml();
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BladeUI\Icons\Components;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
final class Svg extends Component
|
||||
{
|
||||
public function render(): Closure
|
||||
{
|
||||
return function (array $data) {
|
||||
$attributes = $data['attributes']->getIterator()->getArrayCopy();
|
||||
|
||||
$class = $attributes['class'] ?? '';
|
||||
|
||||
unset($attributes['class']);
|
||||
|
||||
return svg($this->componentName, $class, $attributes)->toHtml();
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BladeUI\Icons\Concerns;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
trait RendersAttributes
|
||||
{
|
||||
private array $attributes;
|
||||
|
||||
public function attributes(): array
|
||||
{
|
||||
return $this->attributes;
|
||||
}
|
||||
|
||||
private function renderAttributes(): string
|
||||
{
|
||||
if (count($this->attributes) == 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return ' '.collect($this->attributes)->map(function (string $value, $attribute) {
|
||||
if (is_int($attribute)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return sprintf('%s="%s"', $attribute, $value);
|
||||
})->implode(' ');
|
||||
}
|
||||
|
||||
public function __call(string $method, array $arguments): self
|
||||
{
|
||||
if (count($arguments) === 0) {
|
||||
$this->attributes[] = Str::snake($method, '-');
|
||||
} else {
|
||||
$this->attributes[Str::snake($method, '-')] = $arguments[0];
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BladeUI\Icons\Console;
|
||||
|
||||
use BladeUI\Icons\Factory;
|
||||
use BladeUI\Icons\IconsManifest;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
final class CacheCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'icons:cache';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Discover icon sets and generate a manifest file';
|
||||
|
||||
public function handle(Factory $factory, IconsManifest $manifest): int
|
||||
{
|
||||
$manifest->write($factory->all());
|
||||
|
||||
$this->info('Blade icons manifest file generated successfully!');
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BladeUI\Icons\Console;
|
||||
|
||||
use BladeUI\Icons\IconsManifest;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
final class ClearCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'icons:clear';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Remove the blade icons manifest file';
|
||||
|
||||
public function handle(IconsManifest $manifest): int
|
||||
{
|
||||
$manifest->delete();
|
||||
|
||||
$this->info('Blade icons manifest file cleared!');
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BladeUI\Icons\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
final class CannotRegisterIconSet extends Exception
|
||||
{
|
||||
public static function pathsNotDefined(string $set): self
|
||||
{
|
||||
return new self("The options for the \"$set\" set don't have any paths defined.");
|
||||
}
|
||||
|
||||
public static function nonExistingPath(string $set, string $path): self
|
||||
{
|
||||
return new self("The [$path] path for the \"$set\" set does not exist.");
|
||||
}
|
||||
|
||||
public static function prefixNotDefined(string $set): self
|
||||
{
|
||||
return new self("The options for the \"$set\" set don't have a prefix defined.");
|
||||
}
|
||||
|
||||
public static function prefixNotUnique(string $set, string $collidingSet): self
|
||||
{
|
||||
return new self("The prefix for the \"$set\" collides with the one from the \"$collidingSet\" set.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BladeUI\Icons\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
final class SvgNotFound extends Exception
|
||||
{
|
||||
public static function missing(string $set, string $name): self
|
||||
{
|
||||
return new self("Svg by name \"$name\" from set \"$set\" not found.");
|
||||
}
|
||||
}
|
||||
+257
@@ -0,0 +1,257 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BladeUI\Icons;
|
||||
|
||||
use BladeUI\Icons\Components\Svg as SvgComponent;
|
||||
use BladeUI\Icons\Exceptions\CannotRegisterIconSet;
|
||||
use BladeUI\Icons\Exceptions\SvgNotFound;
|
||||
use Illuminate\Contracts\Filesystem\Factory as FilesystemFactory;
|
||||
use Illuminate\Contracts\Filesystem\FileNotFoundException;
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
final class Factory
|
||||
{
|
||||
private Filesystem $filesystem;
|
||||
|
||||
private IconsManifest $manifest;
|
||||
|
||||
private ?FilesystemFactory $disks;
|
||||
|
||||
private array $config;
|
||||
|
||||
private array $sets = [];
|
||||
|
||||
private array $cache = [];
|
||||
|
||||
public function __construct(
|
||||
Filesystem $filesystem,
|
||||
IconsManifest $manifest,
|
||||
?FilesystemFactory $disks = null,
|
||||
array $config = []
|
||||
) {
|
||||
$this->filesystem = $filesystem;
|
||||
$this->manifest = $manifest;
|
||||
$this->disks = $disks;
|
||||
$this->config = $config;
|
||||
|
||||
$this->config['class'] = $config['class'] ?? '';
|
||||
$this->config['attributes'] = (array) ($config['attributes'] ?? []);
|
||||
$this->config['fallback'] = $config['fallback'] ?? '';
|
||||
$this->config['components'] = [
|
||||
'disabled' => $config['components']['disabled'] ?? false,
|
||||
'default' => $config['components']['default'] ?? 'icon',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal This method is only meant for internal purposes and does not fall under the package's BC promise.
|
||||
*/
|
||||
public function all(): array
|
||||
{
|
||||
return $this->sets;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws CannotRegisterIconSet
|
||||
*/
|
||||
public function add(string $set, array $options): self
|
||||
{
|
||||
if (! isset($options['prefix'])) {
|
||||
throw CannotRegisterIconSet::prefixNotDefined($set);
|
||||
}
|
||||
|
||||
if ($collidingSet = $this->getSetByPrefix($options['prefix'])) {
|
||||
throw CannotRegisterIconSet::prefixNotUnique($set, $collidingSet);
|
||||
}
|
||||
|
||||
$paths = (array) ($options['paths'] ?? $options['path'] ?? []);
|
||||
|
||||
$options['paths'] = array_filter(array_map(
|
||||
fn ($path) => $path !== '/' ? rtrim($path, '/') : $path,
|
||||
$paths,
|
||||
));
|
||||
|
||||
if (empty($options['paths'])) {
|
||||
throw CannotRegisterIconSet::pathsNotDefined($set);
|
||||
}
|
||||
|
||||
unset($options['path']);
|
||||
|
||||
$filesystem = $this->filesystem($options['disk'] ?? null);
|
||||
|
||||
foreach ($options['paths'] as $path) {
|
||||
if ($path !== '/' && $filesystem->missing($path)) {
|
||||
throw CannotRegisterIconSet::nonExistingPath($set, $path);
|
||||
}
|
||||
}
|
||||
|
||||
$this->sets[$set] = $options;
|
||||
|
||||
$this->cache = [];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function registerComponents(): void
|
||||
{
|
||||
if ($this->config['components']['disabled']) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->manifest->getManifest($this->sets) as $set => $paths) {
|
||||
foreach ($paths as $icons) {
|
||||
foreach ($icons as $icon) {
|
||||
Blade::component(
|
||||
SvgComponent::class,
|
||||
$icon,
|
||||
$this->sets[$set]['prefix'] ?? '',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SvgNotFound
|
||||
*/
|
||||
public function svg(string $name, $class = '', array $attributes = []): Svg
|
||||
{
|
||||
[$set, $name] = $this->splitSetAndName($name);
|
||||
|
||||
try {
|
||||
return new Svg(
|
||||
$name,
|
||||
$this->contents($set, $name),
|
||||
$this->formatAttributes($set, $class, $attributes),
|
||||
);
|
||||
} catch (SvgNotFound $exception) {
|
||||
if (isset($this->sets[$set]['fallback']) && $this->sets[$set]['fallback'] !== '') {
|
||||
$name = $this->sets[$set]['fallback'];
|
||||
|
||||
try {
|
||||
return new Svg(
|
||||
$name,
|
||||
$this->contents($set, $name),
|
||||
$this->formatAttributes($set, $class, $attributes),
|
||||
);
|
||||
} catch (SvgNotFound $exception) {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->config['fallback']) {
|
||||
return $this->svg($this->config['fallback'], $class, $attributes);
|
||||
}
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SvgNotFound
|
||||
*/
|
||||
private function contents(string $set, string $name): string
|
||||
{
|
||||
if (isset($this->cache[$set][$name])) {
|
||||
return $this->cache[$set][$name];
|
||||
}
|
||||
|
||||
if (isset($this->sets[$set])) {
|
||||
foreach ($this->sets[$set]['paths'] as $path) {
|
||||
try {
|
||||
return $this->cache[$set][$name] = $this->getSvgFromPath(
|
||||
$name,
|
||||
$path,
|
||||
$this->sets[$set]['disk'] ?? null,
|
||||
);
|
||||
} catch (FileNotFoundException $exception) {
|
||||
//
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw SvgNotFound::missing($set, $name);
|
||||
}
|
||||
|
||||
private function getSvgFromPath(string $name, string $path, ?string $disk = null): string
|
||||
{
|
||||
$contents = trim($this->filesystem($disk)->get(sprintf(
|
||||
'%s/%s.svg',
|
||||
rtrim($path),
|
||||
str_replace('.', '/', $name),
|
||||
)));
|
||||
|
||||
return $this->cleanSvgContents($contents);
|
||||
}
|
||||
|
||||
private function cleanSvgContents(string $contents): string
|
||||
{
|
||||
return trim(preg_replace('/^(<\?xml.+?\?>)/', '', $contents));
|
||||
}
|
||||
|
||||
private function splitSetAndName(string $name): array
|
||||
{
|
||||
$prefix = Str::before($name, '-');
|
||||
|
||||
$set = $this->getSetByPrefix($prefix);
|
||||
|
||||
$name = $set ? Str::after($name, '-') : $name;
|
||||
|
||||
return [$set ?? 'default', $name];
|
||||
}
|
||||
|
||||
private function getSetByPrefix(string $prefix): ?string
|
||||
{
|
||||
return collect($this->sets)->where('prefix', $prefix)->keys()->first();
|
||||
}
|
||||
|
||||
private function formatAttributes(string $set, $class = '', array $attributes = []): array
|
||||
{
|
||||
if (is_string($class)) {
|
||||
if ($class = $this->buildClass($set, $class)) {
|
||||
$attributes['class'] = $attributes['class'] ?? $class;
|
||||
}
|
||||
} elseif (is_array($class)) {
|
||||
$attributes = $class;
|
||||
|
||||
if (! isset($attributes['class']) && $class = $this->buildClass($set, '')) {
|
||||
$attributes['class'] = $class;
|
||||
}
|
||||
}
|
||||
|
||||
$attributes = array_merge(
|
||||
$this->config['attributes'],
|
||||
(array) ($this->sets[$set]['attributes'] ?? []),
|
||||
$attributes,
|
||||
);
|
||||
|
||||
foreach ($attributes as $key => $value) {
|
||||
if (is_string($value)) {
|
||||
$attributes[$key] = str_replace('"', '"', $value);
|
||||
}
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
private function buildClass(string $set, string $class): string
|
||||
{
|
||||
return trim(sprintf(
|
||||
'%s %s',
|
||||
trim(sprintf('%s %s', $this->config['class'], $this->sets[$set]['class'] ?? '')),
|
||||
$class,
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Contracts\Filesystem\Filesystem|Filesystem
|
||||
*/
|
||||
private function filesystem(?string $disk = null)
|
||||
{
|
||||
return $this->disks && $disk ? $this->disks->disk($disk) : $this->filesystem;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BladeUI\Icons\Generation;
|
||||
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Stringable;
|
||||
use Symfony\Component\Finder\SplFileInfo;
|
||||
|
||||
final class IconGenerator
|
||||
{
|
||||
private Filesystem $filesystem;
|
||||
|
||||
private array $sets;
|
||||
|
||||
public function __construct(array $sets)
|
||||
{
|
||||
$this->filesystem = new Filesystem;
|
||||
$this->sets = $sets;
|
||||
}
|
||||
|
||||
public static function create(array $config): self
|
||||
{
|
||||
return new self($config);
|
||||
}
|
||||
|
||||
public function generate(): void
|
||||
{
|
||||
foreach ($this->sets as $set) {
|
||||
$destination = $this->getDestinationDirectory($set);
|
||||
$files = array_filter(
|
||||
$this->filesystem->files($set['source']),
|
||||
fn (SplFileInfo $value) => str_ends_with($value->getFilename(), '.svg')
|
||||
);
|
||||
|
||||
foreach ($files as $file) {
|
||||
$filename = Str::of($file->getFilename());
|
||||
$filename = $this->applyPrefixes($set, $filename);
|
||||
$filename = $this->applySuffixes($set, $filename);
|
||||
$pathname = $destination.$filename;
|
||||
|
||||
$this->filesystem->copy($file->getRealPath(), $pathname);
|
||||
|
||||
if (is_callable($set['after'] ?? null)) {
|
||||
$set['after']($pathname, $set, $file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function getDestinationDirectory(array $set): string
|
||||
{
|
||||
$destination = Str::finish($set['destination'], DIRECTORY_SEPARATOR);
|
||||
|
||||
if (! Arr::get($set, 'safe', false)) {
|
||||
$this->filesystem->deleteDirectory($destination);
|
||||
}
|
||||
|
||||
$this->filesystem->ensureDirectoryExists($destination);
|
||||
|
||||
return $destination;
|
||||
}
|
||||
|
||||
private function applyPrefixes($set, Stringable $filename): Stringable
|
||||
{
|
||||
if ($set['input-prefix'] ?? false) {
|
||||
$filename = $filename->after($set['input-prefix']);
|
||||
}
|
||||
|
||||
if ($set['output-prefix'] ?? false) {
|
||||
$filename = $filename->prepend($set['output-prefix']);
|
||||
}
|
||||
|
||||
return $filename;
|
||||
}
|
||||
|
||||
private function applySuffixes($set, Stringable $filename): Stringable
|
||||
{
|
||||
if ($set['input-suffix'] ?? false) {
|
||||
$filename = $filename->replace($set['input-suffix'].'.svg', '.svg');
|
||||
}
|
||||
|
||||
if ($set['output-suffix'] ?? false) {
|
||||
$filename = $filename->replace('.svg', $set['output-suffix'].'.svg');
|
||||
}
|
||||
|
||||
return $filename;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BladeUI\Icons;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Contracts\Filesystem\Factory as FilesystemFactory;
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Finder\SplFileInfo;
|
||||
|
||||
final class IconsManifest
|
||||
{
|
||||
private Filesystem $filesystem;
|
||||
|
||||
private string $manifestPath;
|
||||
|
||||
private ?FilesystemFactory $disks;
|
||||
|
||||
private ?array $manifest = null;
|
||||
|
||||
public function __construct(Filesystem $filesystem, string $manifestPath, ?FilesystemFactory $disks = null)
|
||||
{
|
||||
$this->filesystem = $filesystem;
|
||||
$this->manifestPath = $manifestPath;
|
||||
$this->disks = $disks;
|
||||
}
|
||||
|
||||
private function build(array $sets): array
|
||||
{
|
||||
$compiled = [];
|
||||
|
||||
foreach ($sets as $name => $set) {
|
||||
$icons = [];
|
||||
|
||||
foreach ($set['paths'] as $path) {
|
||||
$icons[$path] = [];
|
||||
|
||||
foreach ($this->filesystem($set['disk'] ?? null)->allFiles($path) as $file) {
|
||||
if ($file instanceof SplFileInfo) {
|
||||
if ($file->getExtension() !== 'svg') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$icons[$path][] = $this->format($file->getPathName(), $path);
|
||||
} else {
|
||||
if (! Str::endsWith($file, '.svg')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$icons[$path][] = $this->format($file, $path);
|
||||
}
|
||||
}
|
||||
|
||||
$icons[$path] = array_unique($icons[$path]);
|
||||
}
|
||||
|
||||
$compiled[$name] = array_filter($icons);
|
||||
}
|
||||
|
||||
return $compiled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Contracts\Filesystem\Filesystem|Filesystem
|
||||
*/
|
||||
private function filesystem(?string $disk = null)
|
||||
{
|
||||
return $this->disks && $disk ? $this->disks->disk($disk) : $this->filesystem;
|
||||
}
|
||||
|
||||
public function delete(): bool
|
||||
{
|
||||
return $this->filesystem->delete($this->manifestPath);
|
||||
}
|
||||
|
||||
private function format(string $pathname, string $path): string
|
||||
{
|
||||
return (string) Str::of($pathname)
|
||||
->after($path.DIRECTORY_SEPARATOR)
|
||||
->replace(DIRECTORY_SEPARATOR, '.')
|
||||
->basename('.svg');
|
||||
}
|
||||
|
||||
public function getManifest(array $sets): array
|
||||
{
|
||||
if (! is_null($this->manifest)) {
|
||||
return $this->manifest;
|
||||
}
|
||||
|
||||
if (! $this->filesystem->exists($this->manifestPath)) {
|
||||
return $this->manifest = $this->build($sets);
|
||||
}
|
||||
|
||||
return $this->manifest = $this->filesystem->getRequire($this->manifestPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function write(array $sets): void
|
||||
{
|
||||
if (! is_writable($dirname = dirname($this->manifestPath))) {
|
||||
throw new Exception("The {$dirname} directory must be present and writable.");
|
||||
}
|
||||
|
||||
$this->filesystem->replace(
|
||||
$this->manifestPath,
|
||||
'<?php return '.var_export($this->build($sets), true).';',
|
||||
);
|
||||
}
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BladeUI\Icons;
|
||||
|
||||
use BladeUI\Icons\Concerns\RendersAttributes;
|
||||
use Illuminate\Contracts\Support\Htmlable;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
final class Svg implements Htmlable
|
||||
{
|
||||
use RendersAttributes;
|
||||
|
||||
private string $name;
|
||||
|
||||
private string $contents;
|
||||
|
||||
public function __construct(string $name, string $contents, array $attributes = [])
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->contents = $this->deferContent($contents, $attributes['defer'] ?? false);
|
||||
|
||||
unset($attributes['defer']);
|
||||
|
||||
$this->attributes = $attributes;
|
||||
}
|
||||
|
||||
public function name(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function contents(): string
|
||||
{
|
||||
return $this->contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method adds a title element and an aria-labelledby attribute to the SVG.
|
||||
* To comply with accessibility standards, SVGs should have a title element.
|
||||
* Check accessibility patterns for icons: https://www.deque.com/blog/creating-accessible-svgs/
|
||||
*/
|
||||
public function addTitle(string $title): string
|
||||
{
|
||||
// create title element
|
||||
$titleElement = '<title>'.$title.'</title>';
|
||||
|
||||
// add role attribute to svg element
|
||||
$this->attributes['role'] = 'img';
|
||||
|
||||
// add title element to svg
|
||||
return preg_replace('/<svg[^>]*>/', "$0$titleElement", $this->contents);
|
||||
}
|
||||
|
||||
public function toHtml(): string
|
||||
{
|
||||
// Check if the title attribute is set and add a title element to the SVG
|
||||
if (array_key_exists('title', $this->attributes)) {
|
||||
$this->contents = $this->addTitle($this->attributes['title']);
|
||||
}
|
||||
|
||||
return str_replace(
|
||||
'<svg',
|
||||
sprintf('<svg%s', $this->renderAttributes()),
|
||||
$this->contents,
|
||||
);
|
||||
}
|
||||
|
||||
protected function deferContent(string $contents, $defer = false): string
|
||||
{
|
||||
if ($defer === false) {
|
||||
return $contents;
|
||||
}
|
||||
|
||||
$svgContent = Str::of($contents)
|
||||
->replaceMatches('/<svg[^>]*>/', '')
|
||||
->replaceMatches('/<\/svg>/', '')
|
||||
->__toString();
|
||||
|
||||
// Force Unix line endings for hash.
|
||||
$hashContent = str_replace(PHP_EOL, "\n", $svgContent);
|
||||
$hash = 'icon-'.(is_string($defer) ? $defer : md5($hashContent));
|
||||
|
||||
$contents = str_replace($svgContent, strtr('<use href=":href"></use>', [':href' => '#'.$hash]), $contents).PHP_EOL;
|
||||
$svgContent = ltrim($svgContent, PHP_EOL);
|
||||
$contents .= <<<BLADE
|
||||
@once("{$hash}")
|
||||
@push("bladeicons")
|
||||
<g id="{$hash}">
|
||||
{$svgContent}
|
||||
</g>
|
||||
@endpush
|
||||
@endonce
|
||||
BLADE;
|
||||
|
||||
return $contents;
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use BladeUI\Icons\Factory;
|
||||
use BladeUI\Icons\Svg;
|
||||
|
||||
if (! function_exists('svg')) {
|
||||
function svg(string $name, $class = '', array $attributes = []): Svg
|
||||
{
|
||||
return app(Factory::class)->svg($name, $class, $attributes);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user