diff --git a/formwork/src/Assets/Asset.php b/formwork/src/Assets/Asset.php new file mode 100644 index 000000000..d935602f3 --- /dev/null +++ b/formwork/src/Assets/Asset.php @@ -0,0 +1,105 @@ +path = FileSystem::normalizePath($path); + $this->uri = Uri::normalize($uri); + + if (!FileSystem::isFile($this->path)) { + throw new AssetNotFoundException(sprintf('Asset file "%s" not found', $this->path)); + } + } + + /** + * Get asset path + */ + public function path(): string + { + return $this->path; + } + + /** + * Get asset URI + */ + public function uri(bool $includeVersion = false): string + { + $uri = $this->uri; + if ($includeVersion) { + $uri .= '?v=' . $this->version(); + } + return $uri; + } + + /** + * Get asset version + */ + public function version(): string + { + return $this->version ??= dechex(FileSystem::lastModifiedTime($this->path)); + } + + /** + * Get asset integrity hash + */ + public function integrityHash(): string + { + return $this->integrityHash ??= 'sha256-' . base64_encode(hash('sha256', FileSystem::read($this->path), true)); + } + + /** + * Get asset MIME type + */ + public function mimeType(): string + { + return $this->mimeType ??= FileSystem::mimeType($this->path); + } + + /** + * Get asset content + */ + public function content(): string + { + return FileSystem::read($this->path); + } + + /** + * Get asset content as base64 encoded string + */ + public function toBase64(): string + { + return 'data:' . $this->mimeType() . ';base64,' . base64_encode($this->content()); + } +} diff --git a/formwork/src/Assets/AssetCollection.php b/formwork/src/Assets/AssetCollection.php new file mode 100644 index 000000000..b7551dc16 --- /dev/null +++ b/formwork/src/Assets/AssetCollection.php @@ -0,0 +1,45 @@ +filter(function (Asset $asset) { + return $asset->mimeType() === 'text/css'; + }); + } + + /** + * Get scripts from the collection + */ + public function scripts(): static + { + return $this->filter(function (Asset $asset) { + return $asset->mimeType() === 'text/javascript'; + }); + } + + /** + * Get images from the collection + */ + public function images(): static + { + return $this->filter(function (Asset $asset) { + return Str::startsWith($asset->mimeType(), 'image/'); + }); + } +} diff --git a/formwork/src/Assets/Assets.php b/formwork/src/Assets/Assets.php index 5a9c64660..ca1f37a10 100644 --- a/formwork/src/Assets/Assets.php +++ b/formwork/src/Assets/Assets.php @@ -3,6 +3,7 @@ namespace Formwork\Assets; use Formwork\Utils\FileSystem; +use Formwork\Utils\Path; use Formwork\Utils\Str; use Formwork\Utils\Uri; @@ -18,50 +19,64 @@ class Assets */ protected string $baseUri; + /** + * Asset collection + */ + private AssetCollection $collection; + public function __construct(string $basePath, string $baseUri) { $this->basePath = FileSystem::normalizePath($basePath); $this->baseUri = Uri::normalize(Str::append($baseUri, '/')); + $this->collection = new AssetCollection(); } /** - * Get asset version, if possible, based on its last modified time - * - * @param string $path Requested asset path + * Add an asset to the collection */ - public function version(string $path): ?string + public function add(string $key): void { - $file = FileSystem::joinPaths($this->basePath, $path); - if (FileSystem::exists($file)) { - return dechex(FileSystem::lastModifiedTime($file)); + if (!$this->collection->has($key)) { + $path = FileSystem::joinPaths($this->basePath, Path::resolve($key, '/', DIRECTORY_SEPARATOR)); + $uri = Path::join([$this->baseUri, Path::resolve($key, '/')]); + $this->collection->set($key, new Asset($path, $uri)); } - return null; } /** - * Get a SHA-256 integrity hash for an asset if possible + * Get an asset from the collection */ - public function integrityHash(string $path): ?string + public function get(string $key): Asset { - $file = FileSystem::joinPaths($this->basePath, $path); - if (FileSystem::exists($file)) { - return 'sha256-' . base64_encode(hash('sha256', FileSystem::read($file), true)); + if (!$this->collection->has($key)) { + $path = FileSystem::joinPaths($this->basePath, Path::resolve($key, '/', DIRECTORY_SEPARATOR)); + $uri = Path::join([$this->baseUri, Path::resolve($key, '/')]); + $this->collection->set($key, new Asset($path, $uri)); } - return null; + return $this->collection->get($key); } /** - * Get asset URI optionally followed by a version query parameter - * - * @param string $path Requested asset path - * @param bool $includeVersion Whether to include asset version + * Get stylesheets from the assets collection */ - public function uri(string $path, bool $includeVersion = false): string + public function stylesheets(): AssetCollection { - $uri = $this->baseUri . trim($path, '/'); - if ($includeVersion && ($version = $this->version($path)) !== null) { - $uri .= '?v=' . $version; - } - return $uri; + return $this->collection->stylesheets(); + } + + /** + * Get scripts from the assets collection + */ + public function scripts(): AssetCollection + { + return $this->collection->scripts(); + } + + /** + * Get images from the assets collection + */ + public function images(): AssetCollection + { + return $this->collection->images(); } } diff --git a/formwork/src/Assets/Exceptions/AssetNotFoundException.php b/formwork/src/Assets/Exceptions/AssetNotFoundException.php new file mode 100644 index 000000000..05fe45fe3 --- /dev/null +++ b/formwork/src/Assets/Exceptions/AssetNotFoundException.php @@ -0,0 +1,7 @@ +assets ?? ($this->assets = new Assets($this->config->get('system.panel.paths.assets'), $this->uri('/assets/'))); + return $this->assets ??= new Assets($this->config->get('system.panel.paths.assets'), $this->uri('/assets/')); } public function colorScheme(): ColorScheme diff --git a/panel/methods.php b/panel/methods.php index 4122abea0..347a59407 100644 --- a/panel/methods.php +++ b/panel/methods.php @@ -1,15 +1,13 @@ $panel->assets(...), 'modals' => $panel->modals(...), - 'icon' => fn(string $icon) => FileSystem::read(FileSystem::joinPaths($config->get('system.panel.paths.assets'), '/icons/svg/', $icon . '.svg')), + 'icon' => fn(string $icon) => $panel->assets()->get('/icons/svg/' . $icon . '.svg')->content(), ]; }; diff --git a/panel/views/errors/error.php b/panel/views/errors/error.php index 562cb4235..bd56527a0 100644 --- a/panel/views/errors/error.php +++ b/panel/views/errors/error.php @@ -5,9 +5,9 @@ <?php if (!empty($title)) : ?><?= $title ?> | <?php endif ?>Formwork - - - + + + @@ -18,7 +18,7 @@ - +

diff --git a/panel/views/layouts/login.php b/panel/views/layouts/login.php index 4dec02647..6ab512f3f 100644 --- a/panel/views/layouts/login.php +++ b/panel/views/layouts/login.php @@ -6,9 +6,9 @@ - - - + + + diff --git a/panel/views/layouts/panel.php b/panel/views/layouts/panel.php index f3a80f880..dd94d3b84 100644 --- a/panel/views/layouts/panel.php +++ b/panel/views/layouts/panel.php @@ -8,9 +8,12 @@ notifications() as $notification) : ?> - - - + + + assets()->add('css/panel.min.css') ?> + assets()->stylesheets() as $stylesheet): ?> + + @@ -29,6 +32,7 @@ modals() as $modal) : ?> insert('modals.modal', ['modal' => $modal]) ?> + assets()->add('js/app.min.js') ?> insert('partials.scripts') ?> diff --git a/panel/views/partials/scripts.php b/panel/views/partials/scripts.php index 4b00fb164..9d04c12b9 100644 --- a/panel/views/partials/scripts.php +++ b/panel/views/partials/scripts.php @@ -1,4 +1,7 @@ - +assets()->scripts() as $script): ?> + + + \ No newline at end of file diff --git a/panel/views/partials/sidebar.php b/panel/views/partials/sidebar.php index c3d7f227e..bccb8e57a 100644 --- a/panel/views/partials/sidebar.php +++ b/panel/views/partials/sidebar.php @@ -1,6 +1,6 @@