New Branch Structure

This commit is contained in:
Mark Gregory
2026-07-23 17:23:57 +01:00
commit 55bf5ea995
206 changed files with 23782 additions and 0 deletions
@@ -0,0 +1,20 @@
<?php
namespace Modules\Stylist\Http\Controllers;
use Illuminate\Routing\Controller;
use Modules\Stylist\Services\ThemeAccessibilityValidator;
use Modules\Stylist\Services\ThemeSettings;
use Modules\Stylist\Theme\Stylist;
use Modules\Stylist\Theme\UrlGenerator;
final class StylistController extends Controller
{
public function __invoke(Stylist $stylist, ThemeSettings $settings, ThemeAccessibilityValidator $accessibility, UrlGenerator $urls)
{
return view('stylist::index', [
'theme' => $stylist->current(), 'themes' => $stylist->all(), 'variables' => $stylist->variables(),
'settingsService' => $settings, 'accessibility' => $accessibility, 'urls' => $urls,
]);
}
}
@@ -0,0 +1,64 @@
<?php
namespace Modules\Stylist\Http\Controllers;
use Illuminate\Http\Response;
use Illuminate\Routing\Controller;
use Modules\Stylist\Theme\Loader;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
final class ThemeAssetController extends Controller
{
private const ALLOWED_ROOTS = ['css', 'js', 'images', 'fonts', 'icons', 'assets'];
private const ALLOWED_EXTENSIONS = [
'css', 'js', 'mjs', 'map', 'svg', 'png', 'jpg', 'jpeg', 'gif', 'webp', 'avif',
'ico', 'woff', 'woff2', 'ttf', 'otf', 'eot', 'json', 'xml', 'txt', 'webmanifest',
];
public function __invoke(string $theme, string $path, Loader $loader): BinaryFileResponse|Response
{
abort_if(str_contains($path, "\0"), 404);
$normalised = str_replace('\\', '/', ltrim($path, '/'));
$root = explode('/', $normalised, 2)[0] ?? '';
$extension = strtolower(pathinfo($normalised, PATHINFO_EXTENSION));
abort_unless(in_array($root, self::ALLOWED_ROOTS, true), 404);
abort_unless(in_array($extension, self::ALLOWED_EXTENSIONS, true), 404);
$loadedTheme = $loader->find($theme);
$themeRoot = realpath($loadedTheme->path());
abort_unless($themeRoot !== false, 404);
$asset = realpath($themeRoot.DIRECTORY_SEPARATOR.$normalised);
abort_unless(
$asset !== false
&& str_starts_with($asset, $themeRoot.DIRECTORY_SEPARATOR)
&& is_file($asset),
404
);
$mime = match ($extension) {
'css' => 'text/css; charset=UTF-8',
'js', 'mjs' => 'application/javascript; charset=UTF-8',
'svg' => 'image/svg+xml',
'json', 'map', 'webmanifest' => 'application/json',
'woff' => 'font/woff',
'woff2' => 'font/woff2',
'ttf' => 'font/ttf',
'otf' => 'font/otf',
default => mime_content_type($asset) ?: 'application/octet-stream',
};
return response()->file($asset, [
'Content-Type' => $mime,
'Cache-Control' => app()->isProduction()
? 'public, max-age=31536000, immutable'
: 'no-cache, private',
'X-Content-Type-Options' => 'nosniff',
'Content-Security-Policy' => "default-src 'none'; style-src 'unsafe-inline'",
]);
}
}
@@ -0,0 +1,52 @@
<?php
namespace Modules\Stylist\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Modules\Stylist\Services\ThemeAccessibilityValidator;
use Modules\Stylist\Services\ThemeOptimizer;
use Modules\Stylist\Services\ThemePackageManager;
use Modules\Stylist\Services\ThemeSettings;
use Modules\Stylist\Theme\Loader;
final class ThemeManagementController extends Controller
{
public function settings(Request $request, string $theme, Loader $loader, ThemeSettings $settings)
{
$item = $loader->find($theme);
$settings->save(
$item,
(array) $request->input('settings', []),
(string) $request->input('variant', 'light'),
$request->string('appearance')->toString() ?: null,
$request->string('accent')->toString() ?: null,
);
return back()->with('status', 'Theme settings saved.');
}
public function install(Request $request, ThemePackageManager $packages)
{
$request->validate(['theme' => ['required','file','mimes:zip','max:51200']]);
$theme = $packages->install($request->file('theme'));
return back()->with('status', $theme->name().' installed.');
}
public function uninstall(string $theme, Loader $loader, ThemePackageManager $packages)
{
$item = $loader->find($theme); $packages->uninstall($item);
return back()->with('status', $item->name().' uninstalled.');
}
public function export(string $theme, Loader $loader, ThemePackageManager $packages)
{
$item = $loader->find($theme); $path = $packages->export($item);
return response()->download($path)->deleteFileAfterSend();
}
public function optimize(string $theme, Loader $loader, ThemeOptimizer $optimizer)
{
$item = $loader->find($theme); $result = $optimizer->optimize($item);
return back()->with('status', "Optimised {$result['css']} CSS, {$result['js']} JS and {$result['images']} images.");
}
public function validateTheme(string $theme, Loader $loader, ThemeAccessibilityValidator $validator)
{
return response()->json(['issues' => $validator->validate($loader->find($theme))]);
}
}