🆙 Add cms i using 🆙

This commit is contained in:
Remco
2025-11-25 22:42:56 +01:00
parent 94704e0925
commit d44196149e
35591 changed files with 3601123 additions and 0 deletions
@@ -0,0 +1,207 @@
<?php
namespace Qirolab\Theme\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Validator;
use Qirolab\Theme\Enums\CssFramework;
use Qirolab\Theme\Enums\JsFramework;
use Qirolab\Theme\Presets\Traits\AuthScaffolding;
use Qirolab\Theme\Presets\Traits\PackagesTrait;
use Qirolab\Theme\Presets\Traits\StubTrait;
use Qirolab\Theme\Presets\Vite\VitePresetExport;
class MakeThemeCommand extends Command
{
use AuthScaffolding;
use PackagesTrait;
use StubTrait;
/**
* @var string
*/
public $signature = 'make:theme {theme?}';
/**
* @var string
*/
public $description = 'Create a new theme';
/**
* @var string
*/
public $theme;
/**
* @var string
*/
public $themePath;
/**
* @var string
*/
public $cssFramework;
/**
* @var string
*/
public $jsFramework;
public function handle(): void
{
$this->theme = $this->askTheme();
if (! $this->themeExists($this->theme)) {
$this->cssFramework = $this->askCssFramework();
$this->jsFramework = $this->askJsFramework();
$authScaffolding = $this->askAuthScaffolding();
(new VitePresetExport(
$this->theme,
$this->cssFramework,
$this->jsFramework
))
->export();
$this->exportAuthScaffolding($authScaffolding);
$this->line("<options=bold>Theme Name:</options=bold> {$this->theme}");
$this->line("<options=bold>CSS Framework:</options=bold> {$this->cssFramework}");
$this->line("<options=bold>JS Framework:</options=bold> {$this->jsFramework}");
$this->line("<options=bold>Auth Scaffolding:</options=bold> {$authScaffolding}");
$this->line('');
$this->info("Theme scaffolding installed successfully.\n");
$themePath = $this->relativeThemePath($this->theme);
$scriptDevCmd = ' "dev:'.$this->theme.'": "vite --config '.$themePath.'/vite.config.js",';
$scriptBuildCmd = ' "build:'.$this->theme.'": "vite build --config '.$themePath.'/vite.config.js"';
$this->comment('Add following line in the `<fg=blue>scripts</fg=blue>` section of the `<fg=blue>package.json</fg=blue>` file:');
$this->line('');
$this->line('"scripts": {', 'fg=magenta');
$this->line(' ...', 'fg=magenta');
$this->line('');
$this->line($scriptDevCmd, 'fg=magenta');
$this->line($scriptBuildCmd, 'fg=magenta');
$this->line('}');
$this->line('');
$this->comment('And please run `<fg=blue>npm install && npm run dev:'.$this->theme.'</fg=blue>` to compile your fresh scaffolding.');
}
}
protected function askTheme()
{
$theme = $this->argument('theme');
if (! $theme) {
$theme = $this->askValid(
'Name of your theme',
'theme',
['required']
);
}
return $theme;
}
protected function askCssFramework()
{
$options = [
CssFramework::Bootstrap,
CssFramework::Tailwind,
'Skip',
];
$cssFramework = $this->choice(
'Select CSS Framework',
$options,
$_default = $options[0],
$_maxAttempts = null,
$_allowMultipleSelections = false
);
return $cssFramework;
}
protected function askJsFramework()
{
$options = [
JsFramework::Vue3,
JsFramework::React,
'Skip',
];
$jsFramework = $this->choice(
'Select Javascript Framework',
$options,
$_default = $options[0], // Default value
$_maxAttempts = null,
$_allowMultipleSelections = false
);
return $jsFramework;
}
public function askAuthScaffolding()
{
$options = [
'Views Only',
'Controllers & Views',
'Skip',
];
$authScaffolding = $this->choice(
'Publish Auth Scaffolding',
$options,
$_default = $options[0],
$_maxAttempts = null,
$_allowMultipleSelections = false
);
return $authScaffolding;
}
protected function themeExists(string $theme): bool
{
$directory = config('theme.base_path').DIRECTORY_SEPARATOR.$theme;
if (is_dir($directory)) {
$this->error("`{$theme}` theme already exists.");
return true;
}
return false;
}
protected function askValid(string $question, string $field, array $rules)
{
$value = $this->ask($question);
if ($message = $this->validateInput($rules, $field, $value)) {
$this->error($message);
return $this->askValid($question, $field, $rules);
}
return $value;
}
protected function validateInput($rules, $fieldName, $value): ?string
{
$validator = Validator::make([
$fieldName => $value,
], [
$fieldName => $rules,
]);
return $validator->fails()
? $validator->errors()->first($fieldName)
: null;
}
}
@@ -0,0 +1,9 @@
<?php
namespace Qirolab\Theme\Enums;
class CssFramework
{
const Bootstrap = 'Bootstrap';
const Tailwind = 'Tailwind';
}
@@ -0,0 +1,9 @@
<?php
namespace Qirolab\Theme\Enums;
class JsFramework
{
const Vue3 = 'Vue 3';
const React = 'React';
}
@@ -0,0 +1,13 @@
<?php
namespace Qirolab\Theme\Exceptions;
use Exception;
class ThemeBasePathNotDefined extends Exception
{
public function __construct()
{
parent::__construct('Theme base path is not defined');
}
}
@@ -0,0 +1,24 @@
<?php
namespace Qirolab\Theme\Middleware;
use Closure;
use Illuminate\Http\Request;
use Qirolab\Theme\Theme;
class ThemeMiddleware
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next, string $theme, string $parentTheme = null)
{
Theme::set($theme, $parentTheme);
return $next($request);
}
}
@@ -0,0 +1,184 @@
<?php
namespace Qirolab\Theme\Presets\Traits;
use Qirolab\Theme\Enums\CssFramework;
use Qirolab\Theme\Enums\JsFramework;
use Qirolab\Theme\Theme;
trait AuthScaffolding
{
use HandleFiles;
use StubTrait;
public function themePath($path = '')
{
return Theme::path($path, $this->theme);
}
public function exportAuthScaffolding(string $authScaffolding = 'Views Only'): void
{
if ($authScaffolding == 'Controllers & Views') {
$this->exportControllers()
->exportComponents()
->exportRequests()
->exportViews()
->exportRoutes()
->exportTests();
}
if ($authScaffolding == 'Views Only') {
$this->exportViews();
}
}
public function exportControllers(): self
{
$this->ensureDirectoryExists(app_path('Http/Controllers/Auth'));
$controllers = [
'app/Http/Controllers/Auth/AuthenticatedSessionController.php',
'app/Http/Controllers/Auth/ConfirmablePasswordController.php',
'app/Http/Controllers/Auth/EmailVerificationNotificationController.php',
'app/Http/Controllers/Auth/EmailVerificationPromptController.php',
'app/Http/Controllers/Auth/NewPasswordController.php',
'app/Http/Controllers/Auth/PasswordResetLinkController.php',
'app/Http/Controllers/Auth/RegisteredUserController.php',
'app/Http/Controllers/Auth/VerifyEmailController.php',
];
$this->publishFiles($controllers);
return $this;
}
public function exportComponents(): self
{
$this->ensureDirectoryExists(app_path('View/Components'));
$components = [
'app/View/Components/AppLayout.php',
'app/View/Components/GuestLayout.php',
];
$this->publishFiles($components);
return $this;
}
protected function exportRequests(): self
{
$this->ensureDirectoryExists(app_path('Http/Requests/Auth'));
$files = [
'app/Http/Requests/Auth/LoginRequest.php',
];
$this->publishFiles($files);
return $this;
}
public function exportViews(): self
{
$this->ensureDirectoryExists(Theme::path('views', $this->theme));
$this->copyDirectory(
__DIR__."/../../../stubs/resources/{$this->cssFramework}/views",
Theme::path('views', $this->theme)
);
$themePath = $this->relativeThemePath($this->theme);
$cssPath = $themePath.($this->cssFramework === CssFramework::Bootstrap ? '/sass/app.scss' : '/css/app.css');
$jsPath = $themePath.'/js/app.js';
$viteConfig = "@vite(['".$cssPath."', '".$jsPath."'], '".$this->theme."')";
if ($this->jsFramework === JsFramework::React) {
$viteConfig = '@viteReactRefresh'."\n ".$viteConfig;
}
$this->replaceInFile('%vite%', $viteConfig, Theme::path('views/layouts/app.blade.php', $this->theme));
$this->replaceInFile('%vite%', $viteConfig, Theme::path('views/layouts/guest.blade.php', $this->theme));
return $this;
}
public function exportRoutes(): self
{
$routeFile = 'routes/auth.php';
$overwrite = false;
if (file_exists(base_path($routeFile))) {
$overwrite = $this->confirm(
"<fg=red>{$routeFile} already exists.</fg=red>\n ".
'Do you want to overwrite?',
false
);
}
if (! file_exists(base_path($routeFile)) || $overwrite) {
copy(__DIR__.'/../../../stubs/routes/auth.php', base_path('routes/auth.php'));
$homeRoute = "
Route::get('/dashboard', function () {
return view('dashboard');
})->middleware(['auth'])->name('dashboard');
";
$requireAuth = "require __DIR__.'/auth.php';";
if (! exec('grep '.escapeshellarg($requireAuth).' '.base_path('routes/web.php'))) {
$this->append(
base_path('routes/web.php'),
$homeRoute.$requireAuth
);
}
}
return $this;
}
public function exportTests(): self
{
$this->ensureDirectoryExists(base_path('tests/Feature'));
$testFiles = [
'tests/Feature/AuthenticationTest.php',
'tests/Feature/EmailVerificationTest.php',
'tests/Feature/PasswordConfirmationTest.php',
'tests/Feature/PasswordResetTest.php',
'tests/Feature/RegistrationTest.php',
];
$this->publishFiles($testFiles);
return $this;
}
protected function publishFiles(array $files): void
{
foreach ($files as $file) {
$publishPath = base_path($file);
$overwrite = false;
if (file_exists($publishPath)) {
$overwrite = $this->confirm(
"<fg=red>{$file} already exists.</fg=red>\n ".
'Do you want to overwrite?',
false
);
}
if (! file_exists($publishPath) || $overwrite) {
copy(
__DIR__.'/../../../stubs/'.$file,
$publishPath
);
}
}
}
}
@@ -0,0 +1,48 @@
<?php
namespace Qirolab\Theme\Presets\Traits;
use Illuminate\Filesystem\Filesystem;
trait HandleFiles
{
/**
* Ensure a directory exists.
*
* @param string $path
* @param int $mode
* @param bool $recursive
* @return void
*/
protected function ensureDirectoryExists(string $path, int $mode = 0755, bool $recursive = true)
{
if (! (new Filesystem())->isDirectory($path)) {
(new Filesystem())->makeDirectory($path, $mode, $recursive);
}
}
protected function replaceInFile(string $search, string $replace, string $path): bool
{
return (bool) file_put_contents($path, str_replace($search, $replace, file_get_contents($path)));
}
public function createFile(string $path, string $content = ''): bool
{
return (bool) file_put_contents($path, $content);
}
public function append(string $path, string $data): bool
{
return (bool) file_put_contents($path, $data, FILE_APPEND);
}
public function copyDirectory(string $directory, string $destination, $options = null): bool
{
return (new Filesystem())->copyDirectory($directory, $destination, $options);
}
public function exists(string $path): bool
{
return file_exists($path);
}
}
@@ -0,0 +1,121 @@
<?php
namespace Qirolab\Theme\Presets\Traits;
trait PackagesTrait
{
public function getPackages()
{
if (! file_exists(base_path('package.json'))) {
return;
}
return json_decode(file_get_contents(base_path('package.json')), true);
}
/**
* Update the "package.json" file.
*
* @param bool $dev
*
* @return null|$this
*/
protected function updatePackages($dev = true)
{
if ($packages = $this->getPackages()) {
$configurationKey = $dev ? 'devDependencies' : 'dependencies';
$packages[$configurationKey] = static::updatePackageArray(
array_key_exists($configurationKey, $packages) ? $packages[$configurationKey] : []
);
ksort($packages[$configurationKey]);
file_put_contents(
base_path('package.json'),
json_encode($packages, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) . PHP_EOL
);
return $this;
}
return null;
}
/**
* @param string $package
* @param bool $dev
* @return null|string
*/
public function getPackageVersion($package, $dev = true)
{
if ($packages = $this->getPackages()) {
$configurationKey = $dev ? 'devDependencies' : 'dependencies';
$version = $packages[$configurationKey][$package] ?? null;
return $this->getVersion($version);
}
return null;
}
/**
* @param string $str
* @return null|string
*/
protected function getVersion($str)
{
preg_match("/\s*((?:[0-9]+\.?)+)/i", $str, $matches);
return $matches[1] ?? null;
}
/**
* @param bool $dev
* @return null|string
*/
public function getMixVersion($dev = true)
{
return $this->getPackageVersion('laravel-mix', $dev);
}
/**
* @param bool $dev
* @return null|string
*/
public function getVueVersion($dev = true)
{
return $this->getPackageVersion('vue', $dev);
}
/**
* @param string $actual
* @param string $compare
* @return bool
*/
public function versionLessThan($actual, $compare)
{
return version_compare($actual, $compare, '<');
}
/**
* @param string $actual
* @param string $compare
* @return bool
*/
public function versionGreaterThan($actual, $compare)
{
return version_compare($actual, $compare, '>');
}
/**
* @param string $actual
* @param string $compare
* @return bool
*/
public function versionGreaterOrEqual($actual, $compare)
{
return version_compare($actual, $compare, '>=');
}
}
@@ -0,0 +1,37 @@
<?php
namespace Qirolab\Theme\Presets\Traits;
use Qirolab\Theme\Presets\Vite\VitePresetExport;
use Qirolab\Theme\Theme;
trait PresetTrait
{
use HandleFiles;
use PackagesTrait;
/**
* @var VitePresetExport
*/
public $exporter;
public function __construct(VitePresetExport $exporter)
{
$this->exporter = $exporter;
}
public function getTheme(): string
{
return $this->exporter->getTheme();
}
public function themePath($path = '')
{
return Theme::path($path, $this->getTheme());
}
public function jsPreset()
{
return $this->exporter->jsPreset();
}
}
@@ -0,0 +1,20 @@
<?php
namespace Qirolab\Theme\Presets\Traits;
use Qirolab\Theme\Theme;
trait StubTrait
{
public function stubPath(string $file): string
{
return __DIR__.'/../../../stubs/Presets/'.$file;
}
public function relativeThemePath($theme)
{
$themePath = str_replace(base_path(), '', Theme::path('', $theme));
return ltrim($themePath, DIRECTORY_SEPARATOR);
}
}
@@ -0,0 +1,86 @@
<?php
namespace Qirolab\Theme\Presets\Vite;
use Qirolab\Theme\Presets\Traits\PresetTrait;
use Qirolab\Theme\Presets\Traits\StubTrait;
class BootstrapPreset
{
use PresetTrait;
use StubTrait;
public function export(): void
{
$this->updatePackages()
->exportSass()
->exportJs();
}
/**
* Update the given package array.
*
* @param array $packages
* @return array
*/
protected static function updatePackageArray(array $packages): array
{
return [
'bootstrap' => '^4.6.0',
'jquery' => '^3.5',
'popper.js' => '^1.16',
'sass' => '^1.32.1',
'sass-loader' => '^10.1.1',
] + $packages;
}
/**
* Update the bootstrapping files.
*
* @return $this
*/
protected function exportJs()
{
$this->ensureDirectoryExists($this->themePath('js'));
copy(
$this->stubPath('bootstrap-stubs/js/bootstrap.js'),
$this->themePath('js/bootstrap.js')
);
copy(
$this->stubPath('bootstrap-stubs/js/app.js'),
$this->themePath('js/app.js')
);
return $this;
}
/**
* Update the Sass files for the application.
*
* @return $this
*/
protected function exportSass()
{
$this->ensureDirectoryExists($this->themePath('sass'));
copy(
$this->stubPath('bootstrap-stubs/sass/_variables.scss'),
$this->themePath('sass/_variables.scss')
);
copy(
$this->stubPath('bootstrap-stubs/sass/app.scss'),
$this->themePath('sass/app.scss')
);
return $this;
}
public function updateViteConfig($configData)
{
$configData = str_replace('%app_css_input%', 'sass/app.scss', $configData);
$bootstrap = "'~bootstrap': path.resolve('node_modules/bootstrap'),";
return str_replace('%bootstrap%', $bootstrap, $configData);
}
}
@@ -0,0 +1,89 @@
<?php
namespace Qirolab\Theme\Presets\Vite;
use Qirolab\Theme\Presets\Traits\PresetTrait;
use Qirolab\Theme\Presets\Traits\StubTrait;
class ReactPreset
{
use PresetTrait;
use StubTrait;
public function export(): void
{
$this->updatePackages()
->exportReactComponent()
->exportJs();
}
/**
* Update the given package array.
*
* @param array $packages
* @return array
*/
protected static function updatePackageArray(array $packages): array
{
return [
'@vitejs/plugin-react' => '^1.3.2',
// '@babel/preset-react' => '^7.18.6',
'react' => '^18.2.0',
'react-dom' => '^18.2.0',
] + $packages;
// return [
// '@babel/preset-react' => '^7.0.0',
// 'react' => '^16.2.0',
// 'react-dom' => '^16.2.0',
// ] + Arr::except($packages, ['vue', 'vue-template-compiler']);
}
/**
* Update the bootstrapping files.
*
* @return $this
*/
protected function exportJs()
{
copy($this->stubPath('react-stubs/app.js'), $this->themePath('js/app.js'));
if (! $this->exists($this->themePath('js/bootstrap.js'))) {
copy($this->stubPath('react-stubs/bootstrap.js'), $this->themePath('js/bootstrap.js'));
}
// if (!$this->exists(base_path('.babelrc'))) {
// copy(__DIR__ . '/../../stubs/Presets/react-stubs/.babelrc', base_path('.babelrc'));
// }
return $this;
}
/**
* Update the example component.
*
* @return $this
*/
protected function exportReactComponent()
{
$this->ensureDirectoryExists($this->themePath('js/components'));
copy(
$this->stubPath('react-stubs/Example.jsx'),
$this->themePath('js/components/Example.jsx')
);
return $this;
}
public function updateViteConfig($configData)
{
$reactImport = "import react from '@vitejs/plugin-react';";
$reactConfig = 'react(),';
$configData = str_replace('%react_import%', $reactImport, $configData);
$configData = str_replace('%react_plugin_config%', $reactConfig, $configData);
return $configData;
}
}
@@ -0,0 +1,91 @@
<?php
namespace Qirolab\Theme\Presets\Vite;
use Qirolab\Theme\Presets\Traits\PresetTrait;
use Qirolab\Theme\Presets\Traits\StubTrait;
class TailwindPreset
{
use PresetTrait;
use StubTrait;
public function export(): void
{
$this->updatePackages()
->exportBootstrapping();
}
/**
* Update the given package array.
*
* @param array $packages
* @return array
*/
protected static function updatePackageArray(array $packages): array
{
return [
'@tailwindcss/forms' => '^0.5.7',
'autoprefixer' => '^10.4.19',
'postcss' => '^8.4.38',
'postcss-import' => '^16.1.0',
'tailwindcss' => '^3.4.3',
] + $packages;
}
/**
* Update the bootstrapping files.
*
* @return $this
*/
protected function exportBootstrapping()
{
$this->ensureDirectoryExists($this->themePath('js'));
$this->ensureDirectoryExists($this->themePath('css'));
copy($this->stubPath('tailwind-stubs/tailwind.config.js'), $this->themePath('tailwind.config.js'));
$this->replaceInFile(
'%theme_path%',
$this->relativeThemePath($this->getTheme()),
$this->themePath('tailwind.config.js')
);
if (! $this->exists($this->themePath('js/app.js'))) {
copy(
$this->stubPath('tailwind-stubs/js/app.js'),
$this->themePath('js/app.js')
);
}
if (! $this->exists($this->themePath('js/bootstrap.js'))) {
copy(
$this->stubPath('tailwind-stubs/js/bootstrap.js'),
$this->themePath('js/bootstrap.js')
);
}
copy($this->stubPath('tailwind-stubs/css/app.css'), $this->themePath('css/app.css'));
return $this;
}
public function getViteConfig()
{
return 'css: {
postcss: {
plugins: [
tailwindcss({
config: path.resolve(__dirname, "tailwind.config.js"),
}),
],
},
},';
}
public function updateViteConfig($configData)
{
$configData = str_replace('%app_css_input%', 'css/app.css', $configData);
$configData = str_replace('%tailwind_import%', 'import tailwindcss from "tailwindcss";', $configData);
return str_replace('%css_config%', $this->getViteConfig(), $configData);
}
}
@@ -0,0 +1,115 @@
<?php
namespace Qirolab\Theme\Presets\Vite;
use Qirolab\Theme\Presets\Traits\HandleFiles;
use Qirolab\Theme\Presets\Traits\StubTrait;
use Qirolab\Theme\Theme;
class VitePresetExport
{
use HandleFiles;
use StubTrait;
/**
* @var string
*/
protected $theme;
/**
* @var string
*/
public $cssFramework;
/**
* @var string
*/
public $jsFramework;
public function __construct(string $theme, string $cssFramework, string $jsFramework)
{
$this->theme = $theme;
$this->cssFramework = $cssFramework;
$this->jsFramework = $jsFramework;
$this->ensureDirectoryExists(Theme::path('', $theme));
}
public function getTheme(): string
{
return $this->theme;
}
public function export(): void
{
if ($this->cssPreset()) {
$this->cssPreset()->export();
}
if ($this->jsPreset()) {
$this->jsPreset()->export();
}
$this->exportViteConfig();
}
public function getPreset($preset)
{
$preset = str_replace(' ', '', $preset);
$presetClass = "\\Qirolab\\Theme\\Presets\\Vite\\{$preset}Preset";
if (class_exists($presetClass)) {
return new $presetClass($this);
}
}
public function cssPreset()
{
return $this->getPreset($this->cssFramework);
}
/**
* @return null|object
*/
public function jsPreset()
{
return $this->getPreset($this->jsFramework);
}
public function exportViteConfig()
{
$placeHolders = [
'%app_css_input%',
'%theme_path%',
'%theme_name%',
'%css_config%',
'%tailwind_import%',
'%vue_import%',
'%vue_plugin_config%',
'%react_import%',
'%react_plugin_config%',
'%bootstrap%',
];
$themePath = $this->relativeThemePath($this->theme);
$configData = file_get_contents($this->stubPath('vite.config.js'));
$configData = str_replace('%theme_path%', $themePath.DIRECTORY_SEPARATOR, $configData);
$configData = str_replace('%theme_name%', $this->theme, $configData);
if ($this->cssPreset()) {
$configData = $this->cssPreset()->updateViteConfig($configData);
}
if ($this->jsPreset()) {
$configData = $this->jsPreset()->updateViteConfig($configData);
}
foreach ($placeHolders as $placeHolder) {
$configData = str_replace($placeHolder, '', $configData);
}
$this->createFile(Theme::path('vite.config.js', $this->theme), $configData);
}
}
@@ -0,0 +1,101 @@
<?php
namespace Qirolab\Theme\Presets\Vite;
use Qirolab\Theme\Presets\Traits\PresetTrait;
use Qirolab\Theme\Presets\Traits\StubTrait;
class Vue3Preset
{
use PresetTrait;
use StubTrait;
public function export(): void
{
$this->updatePackages()
->exportVueComponent()
->exportJs();
}
/**
* Update the given package array.
*
* @param array $packages
* @return array
*/
protected static function updatePackageArray(array $packages): array
{
return [
'@vitejs/plugin-vue' => '^5.0.4',
// '@vue/compiler-sfc' => '^3.2.37',
// 'resolve-url-loader' => '^5.0.0',
// 'sass' => '^1.53.0',
// 'sass-loader' => '^13.0.2',
'vue' => '^3.4.27',
// 'vue-loader' => '^17.0.0',
] + $packages;
// return [
// 'resolve-url-loader' => '^2.3.1',
// 'sass' => '^1.20.1',
// 'sass-loader' => '^8.0.0',
// 'vue' => '^2.5.17',
// 'vue-template-compiler' => '^2.6.10',
// ] + Arr::except($packages, [
// '@babel/preset-react',
// 'react',
// 'react-dom',
// ]);
}
/**
* Update the bootstrapping files.
*
* @return $this
*/
protected function exportJs()
{
copy($this->stubPath('vue3-stubs/app.js'), $this->themePath('js/app.js'));
if (! $this->exists($this->themePath('js/bootstrap.js'))) {
copy($this->stubPath('vue3-stubs/bootstrap.js'), $this->themePath('js/bootstrap.js'));
}
return $this;
}
/**
* Update the example component.
*
* @return $this
*/
protected function exportVueComponent()
{
$this->ensureDirectoryExists($this->themePath('js/components'));
copy(
$this->stubPath('vue3-stubs/ExampleComponent.vue'),
$this->themePath('js/components/ExampleComponent.vue')
);
return $this;
}
public function updateViteConfig($configData)
{
$vueImport = "import vue from '@vitejs/plugin-vue';";
$vueConfig = 'vue({
template: {
transformAssetUrls: {
base: null,
includeAbsolute: false,
},
},
}),';
$configData = str_replace('%vue_import%', $vueImport, $configData);
$configData = str_replace('%vue_plugin_config%', $vueConfig, $configData);
return $configData;
}
}
@@ -0,0 +1,52 @@
<?php
namespace Qirolab\Theme\SolutionProviders;
use Facade\IgnitionContracts\BaseSolution;
use Facade\IgnitionContracts\HasSolutionsForThrowable;
use Qirolab\Theme\Theme;
use Throwable;
class ThemeSolutionProvider implements HasSolutionsForThrowable
{
public function canSolve(Throwable $throwable): bool
{
return true;
}
public function getSolutions(Throwable $throwable): array
{
$message = $this->getMessage();
if (app()->runningInConsole() || ! $message) {
return [];
}
return [
BaseSolution::create('Theme')
->setSolutionDescription($message)
->setDocumentationLinks([
'Documentation' => 'https://github.com/qirolab/laravel-themer',
'Video Tutorial' => 'https://www.youtube.com/watch?v=Ty4ZwFTLYXE',
]),
];
}
public function getMessage(): string
{
$message = '';
$activeTheme = Theme::active();
$parentTheme = Theme::parent();
if ($activeTheme) {
$message = "**Active Theme:** `{$activeTheme}` ";
}
if ($parentTheme) {
$message .= "**Parent Theme:** `{$parentTheme}`";
}
return $message;
}
}
@@ -0,0 +1,62 @@
<?php
namespace Qirolab\Theme;
class Theme
{
public static function finder()
{
return app('theme.finder');
}
public static function set(string $theme, string $parentTheme = null): void
{
self::finder()->setActiveTheme($theme, $parentTheme);
}
public static function clear(): void
{
self::finder()->clearThemes();
}
public static function active(): ?string
{
return self::finder()->getActiveTheme();
}
public static function parent(): ?string
{
return self::finder()->getParentTheme();
}
public static function viewPath(string $theme = null): ?string
{
$theme = $theme ?? self::active();
if ($theme) {
return self::finder()->getThemeViewPath($theme);
}
return null;
}
public static function path(string $path = null, string $theme = null): ?string
{
$theme = $theme ?? self::active();
if ($theme) {
return self::finder()->getThemePath($theme, $path);
}
return null;
}
public static function getViewPaths(): array
{
if (self::finder()) {
return self::finder()->getViewFinder()->getPaths();
}
return app('view')->getFinder()->getPaths();
}
}
@@ -0,0 +1,80 @@
<?php
namespace Qirolab\Theme;
use Facade\IgnitionContracts\SolutionProviderRepository;
// use Illuminate\Container\Container;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Support\ServiceProvider;
use Qirolab\Theme\Commands\MakeThemeCommand;
use Qirolab\Theme\SolutionProviders\ThemeSolutionProvider;
class ThemeServiceProvider extends ServiceProvider
{
public function boot(): void
{
if ($this->app->runningInConsole()) {
$this->publishes([
__DIR__.'/../config/theme.php' => config_path('theme.php'),
], 'config');
$this->commands([
MakeThemeCommand::class,
]);
}
}
public function register()
{
$this->mergeConfig();
$this->registerThemeFinder();
$this->registerSolutionProvider();
}
protected function mergeConfig(): void
{
$this->mergeConfigFrom(__DIR__.'/../config/theme.php', 'theme');
}
protected function registerSolutionProvider(): void
{
try {
$solutionProvider = $this->app->make(SolutionProviderRepository::class);
$solutionProvider->registerSolutionProvider(
ThemeSolutionProvider::class
);
} catch (BindingResolutionException $error) {
}
}
protected function registerThemeFinder(): void
{
$this->app->singleton('theme.finder', function ($app) {
$themeFinder = new ThemeViewFinder(
$app['files'],
$app['config']['view.paths']
);
// $themeFinder = new ThemeViewFinder(
// Container::getInstance()->make('files'),
// Container::getInstance()->make('config')['view.paths']
// );
$themeFinder->setHints(
$this->app->make('view')->getFinder()->getHints()
);
return $themeFinder;
});
if (config('theme.active')) {
$this->app->make('theme.finder')->setActiveTheme(config('theme.active'), config('theme.parent'));
}
// If need to replace Laravel's view finder with package's theme.finder
// $this->app->make('view')->setFinder($this->app->make('theme.finder'));
}
}
@@ -0,0 +1,134 @@
<?php
namespace Qirolab\Theme;
use Illuminate\View\FileViewFinder;
use Qirolab\Theme\Exceptions\ThemeBasePathNotDefined;
class ThemeViewFinder extends FileViewFinder
{
/**
* @var null|string
*/
protected $activeTheme;
/**
* @var null|string
*/
protected $parentTheme;
public function getViewFinder()
{
// It should return `theme.finder` if Laravel's view finder is replaced
// with package's finder.
// return app('theme.finder');
return app('view')->getFinder();
}
public function setActiveTheme(string $theme, string $parentTheme = null): void
{
if ($theme) {
$this->clearThemes();
if ($parentTheme) {
$this->registerTheme($parentTheme);
$this->parentTheme = $parentTheme;
}
$this->registerTheme($theme);
$this->activeTheme = $theme;
}
}
public function setHints($hints): void
{
$this->hints = $hints;
}
public function getThemePath(string $theme, string $path = null): string
{
if (! config('theme.base_path')) {
throw new ThemeBasePathNotDefined();
}
return $this->resolvePath(
config('theme.base_path') . DIRECTORY_SEPARATOR . $theme . ($path ? DIRECTORY_SEPARATOR . $path : '')
);
}
public function getThemeViewPath(string $theme = null): string
{
$theme = $theme ?? $this->getActiveTheme();
return $this->getThemePath($theme, 'views');
}
/**
* Get active theme name.
*
* @return null|string
*/
public function getActiveTheme()
{
return $this->activeTheme;
}
/**
* Get parent theme name.
*
* @return null|string
*/
public function getParentTheme()
{
return $this->parentTheme;
}
public function clearThemes(): void
{
$paths = $this->getViewFinder()->getPaths();
if ($this->getActiveTheme()) {
if (($key = array_search($this->getThemeViewPath($this->getActiveTheme()), $paths)) !== false) {
unset($paths[$key]);
}
}
if ($this->getParentTheme()) {
if (($key = array_search($this->getThemeViewPath($this->getParentTheme()), $paths)) !== false) {
unset($paths[$key]);
}
}
$this->activeTheme = null;
$this->parentTheme = null;
$this->getViewFinder()->setPaths($paths);
}
public function registerTheme(string $theme): void
{
// array_unshift($this->paths, $this->getThemeViewPath($theme));
$this->getViewFinder()->prependLocation($this->getThemeViewPath($theme));
$this->registerNameSpacesForTheme($theme);
}
public function registerNameSpacesForTheme(string $theme): void
{
$vendorViewsPath = $this->getThemeViewPath($theme) . DIRECTORY_SEPARATOR . 'vendor';
if (is_dir($vendorViewsPath)) {
$directories = scandir($vendorViewsPath);
foreach ($directories as $namespace) {
if ($namespace != '.' && $namespace != '..') {
$path = $vendorViewsPath . DIRECTORY_SEPARATOR . $namespace;
$this->getViewFinder()->prependNamespace($namespace, $path);
}
}
}
}
}