[增添]添加了datasource的setting数据库以及默认值

This commit is contained in:
makotocc0107
2024-08-27 09:57:44 +08:00
parent d111dfaea4
commit 72eb990970
10955 changed files with 978898 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) Dries Vints
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,25 @@
#!/usr/bin/env php
<?php
use BladeUI\Icons\Generation\IconGenerator;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\SingleCommandApplication as Command;
if (file_exists(__DIR__.'/../vendor/autoload.php')) {
require __DIR__.'/../vendor/autoload.php';
} else {
require __DIR__.'/../../../autoload.php';
}
return (new Command)->setCode(function (InputInterface $input, OutputInterface $output) {
$output->writeln("Starting to generate icons...");
IconGenerator::create(
require getcwd().'/config/generation.php'
)->generate();
$output->writeln("Finished generating icons!");
return Command::SUCCESS;
})->run();

View File

@@ -0,0 +1,69 @@
{
"name": "blade-ui-kit/blade-icons",
"description": "A package to easily make use of icons in your Laravel Blade views.",
"keywords": ["Blade", "Icons", "Laravel", "SVG"],
"homepage": "https://github.com/blade-ui-kit/blade-icons",
"license": "MIT",
"support": {
"issues": "https://github.com/blade-ui-kit/blade-icons/issues",
"source": "https://github.com/blade-ui-kit/blade-icons"
},
"authors": [
{
"name": "Dries Vints",
"homepage": "https://driesvints.com"
}
],
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/driesvints"
},
{
"type": "paypal",
"url": "https://www.paypal.com/paypalme/driesvints"
}
],
"require": {
"php": "^7.4|^8.0",
"illuminate/contracts": "^8.0|^9.0|^10.0|^11.0",
"illuminate/filesystem": "^8.0|^9.0|^10.0|^11.0",
"illuminate/support": "^8.0|^9.0|^10.0|^11.0",
"illuminate/view": "^8.0|^9.0|^10.0|^11.0",
"symfony/console": "^5.3|^6.0|^7.0",
"symfony/finder": "^5.3|^6.0|^7.0"
},
"require-dev": {
"mockery/mockery": "^1.5.1",
"orchestra/testbench": "^6.0|^7.0|^8.0|^9.0",
"phpunit/phpunit": "^9.0|^10.5|^11.0"
},
"bin": [
"bin/blade-icons-generate"
],
"autoload": {
"psr-4": {
"BladeUI\\Icons\\": "src"
},
"files": [
"src/helpers.php"
]
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests"
}
},
"extra": {
"laravel": {
"providers": [
"BladeUI\\Icons\\BladeIconsServiceProvider"
]
}
},
"config": {
"sort-packages": true
},
"minimum-stability": "dev",
"prefer-stable": true
}

View File

@@ -0,0 +1,183 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Icons Sets
|--------------------------------------------------------------------------
|
| With this config option you can define a couple of
| default icon sets. Provide a key name for your icon
| set and a combination from the options below.
|
*/
'sets' => [
// 'default' => [
//
// /*
// |-----------------------------------------------------------------
// | Icons Path
// |-----------------------------------------------------------------
// |
// | Provide the relative path from your app root to your SVG icons
// | directory. Icons are loaded recursively so there's no need to
// | list every sub-directory.
// |
// | Relative to the disk root when the disk option is set.
// |
// */
//
// 'path' => 'resources/svg',
//
// /*
// |-----------------------------------------------------------------
// | Filesystem Disk
// |-----------------------------------------------------------------
// |
// | Optionally, provide a specific filesystem disk to read
// | icons from. When defining a disk, the "path" option
// | starts relatively from the disk root.
// |
// */
//
// 'disk' => '',
//
// /*
// |-----------------------------------------------------------------
// | Default Prefix
// |-----------------------------------------------------------------
// |
// | This config option allows you to define a default prefix for
// | your icons. The dash separator will be applied automatically
// | to every icon name. It's required and needs to be unique.
// |
// */
//
// 'prefix' => 'icon',
//
// /*
// |-----------------------------------------------------------------
// | Fallback Icon
// |-----------------------------------------------------------------
// |
// | This config option allows you to define a fallback
// | icon when an icon in this set cannot be found.
// |
// */
//
// 'fallback' => '',
//
// /*
// |-----------------------------------------------------------------
// | Default Set Classes
// |-----------------------------------------------------------------
// |
// | This config option allows you to define some classes which
// | will be applied by default to all icons within this set.
// |
// */
//
// 'class' => '',
//
// /*
// |-----------------------------------------------------------------
// | Default Set Attributes
// |-----------------------------------------------------------------
// |
// | This config option allows you to define some attributes which
// | will be applied by default to all icons within this set.
// |
// */
//
// 'attributes' => [
// // 'width' => 50,
// // 'height' => 50,
// ],
//
// ],
],
/*
|--------------------------------------------------------------------------
| Global Default Classes
|--------------------------------------------------------------------------
|
| This config option allows you to define some classes which
| will be applied by default to all icons.
|
*/
'class' => '',
/*
|--------------------------------------------------------------------------
| Global Default Attributes
|--------------------------------------------------------------------------
|
| This config option allows you to define some attributes which
| will be applied by default to all icons.
|
*/
'attributes' => [
// 'width' => 50,
// 'height' => 50,
],
/*
|--------------------------------------------------------------------------
| Global Fallback Icon
|--------------------------------------------------------------------------
|
| This config option allows you to define a global fallback
| icon when an icon in any set cannot be found. It can
| reference any icon from any configured set.
|
*/
'fallback' => '',
/*
|--------------------------------------------------------------------------
| Components
|--------------------------------------------------------------------------
|
| These config options allow you to define some
| settings related to Blade Components.
|
*/
'components' => [
/*
|----------------------------------------------------------------------
| Disable Components
|----------------------------------------------------------------------
|
| This config option allows you to disable Blade components
| completely. It's useful to avoid performance problems
| when working with large icon libraries.
|
*/
'disabled' => false,
/*
|----------------------------------------------------------------------
| Default Icon Component Name
|----------------------------------------------------------------------
|
| This config option allows you to define the name
| for the default Icon class component.
|
*/
'default' => 'icon',
],
];

View File

@@ -0,0 +1,6 @@
{
"notPath": [
"tests/fixtures/blade-icons.php",
"tests/fixtures/generated-manifest.php"
]
}

View File

@@ -0,0 +1,116 @@
<?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,
]);
}
}
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');
}
}
}

View File

@@ -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();
};
}
}

View File

@@ -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();
};
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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.");
}
}

View File

@@ -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.");
}
}

View File

@@ -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('"', '&quot;', $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;
}
}

View File

@@ -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;
}
}

View File

@@ -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).';',
);
}
}

View File

@@ -0,0 +1,105 @@
<?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
{
// generate a random id for the title element
$titleId = 'svg-inline--title-'.Str::random(10);
// create title element
$titleElement = '<title id="'.$titleId.'">'.$title.'</title>';
// add aria-labelledby attribute to svg element
$this->attributes['aria-labelledby'] = $titleId;
// 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;
}
}

View File

@@ -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);
}
}