Commit 7a276764 authored by Yassine Doghri's avatar Yassine Doghri
Browse files

feat(themes): add ViewThemes library to set views in root themes folder

app, admin, install and authentication views are now located in root themes/ folder
parent 58c88399
......@@ -2,6 +2,9 @@
declare(strict_types=1);
use App\Libraries\View;
use ViewThemes\Theme;
/**
* The goal of this file is to allow developers a location where they can overwrite core procedural functions and
* replace them with their own. This file is loaded during the bootstrap process and is called during the frameworks
......@@ -12,3 +15,32 @@ declare(strict_types=1);
*
* @link: https://codeigniter4.github.io/CodeIgniter4/
*/
if (! function_exists('view')) {
/**
* Grabs the current RendererInterface-compatible class and tells it to render the specified view. Simply provides a
* convenience method that can be used in Controllers, libraries, and routed closures.
*
* NOTE: Does not provide any escaping of the data, so that must all be handled manually by the developer.
*
* @param array<string, mixed> $data
* @param array<string, mixed> $options Unused - reserved for third-party extensions.
*/
function view(string $name, array $data = [], array $options = []): string
{
$path = Theme::path();
/** @var CodeIgniter\View\View $renderer */
$renderer = single_service('renderer', $path);
$saveData = config(View::class)->saveData;
if (array_key_exists('saveData', $options)) {
$saveData = (bool) $options['saveData'];
unset($options['saveData']);
}
return $renderer->setData($data, 'raw')
->render($name, $options, $saveData);
}
}
......@@ -51,6 +51,8 @@ class Autoload extends AutoloadConfig
'Modules\Fediverse' => ROOTPATH . 'modules/Fediverse/',
'Config' => APPPATH . 'Config/',
'ViewComponents' => APPPATH . 'Libraries/ViewComponents/',
'ViewThemes' => APPPATH . 'Libraries/ViewThemes/',
'Themes' => ROOTPATH . 'themes',
];
/**
......
......@@ -9,14 +9,12 @@ use ViewComponents\Config\ViewComponents as ViewComponentsConfig;
class ViewComponents extends ViewComponentsConfig
{
/**
* @var array<string, string>
* @var string[]
*/
public array $lookupModules = [
APP_NAMESPACE => APPPATH,
'Modules\Admin' => ROOTPATH . 'modules/Admin/',
'Modules\Auth' => ROOTPATH . 'modules/Auth/',
'Modules\Analytics' => ROOTPATH . 'modules/Analytics/',
'Modules\Install' => ROOTPATH . 'modules/Install/',
'Modules\Fediverse' => ROOTPATH . 'modules/Fediverse/',
public array $lookupPaths = [
ROOTPATH . 'themes/cp_app/',
ROOTPATH . 'themes/cp_admin/',
ROOTPATH . 'themes/cp_auth/',
ROOTPATH . 'themes/cp_install/',
];
}
......@@ -8,6 +8,7 @@ use CodeIgniter\Controller;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use Psr\Log\LoggerInterface;
use ViewThemes\Theme;
/**
* Class BaseController
......@@ -19,14 +20,6 @@ use Psr\Log\LoggerInterface;
*/
class BaseController extends Controller
{
/**
* An array of helpers to be loaded automatically upon class instantiation. These helpers will be available to all
* other controllers that extend BaseController.
*
* @var string[]
*/
protected $helpers = ['auth', 'svg', 'components', 'misc'];
/**
* Constructor.
*/
......@@ -35,7 +28,11 @@ class BaseController extends Controller
ResponseInterface $response,
LoggerInterface $logger
): void {
$this->helpers = array_merge($this->helpers, ['auth', 'svg', 'components', 'misc']);
// Do Not Edit This Line
parent::initController($request, $response, $logger);
Theme::setTheme('app');
}
}
......@@ -38,6 +38,7 @@ class HomeController extends BaseController
$data = [
'podcasts' => $allPodcasts,
];
return view('home', $data);
}
}
......@@ -110,7 +110,7 @@ class View extends CodeIgniterView
}
$output = service('components')
->setCurrentView($view)
->setCurrentView($this->renderVars['file'])
->render($output);
$this->logPerformance($this->renderVars['start'], microtime(true), $this->renderVars['view']);
......
......@@ -137,26 +137,26 @@ class ComponentRenderer
private function locateView(string $name): string
{
// TODO: Is there a better way to locate components local to current module?
$modulesToDiscover = [];
$lookupModules = $this->config->lookupModules;
$modulesToDiscover = array_filter($lookupModules, function ($namespace): bool {
return str_starts_with($this->currentView, $namespace);
}, ARRAY_FILTER_USE_KEY);
$modulesToDiscover = array_values($modulesToDiscover);
$modulesToDiscover[] = $this->config->defaultLookupPath;
$pathsToDiscover = [];
$lookupPaths = $this->config->lookupPaths;
$pathsToDiscover = array_filter($lookupPaths, function ($path): bool {
return str_starts_with($this->currentView, $path);
});
$pathsToDiscover = array_values($pathsToDiscover);
$pathsToDiscover[] = $this->config->defaultLookupPath;
$namePath = str_replace('.', '/', $name);
foreach ($modulesToDiscover as $basePath) {
foreach ($pathsToDiscover as $basePath) {
// Look for a class component first
$filePath = $basePath . $this->config->classComponentsPath . '/' . $namePath . '.php';
$filePath = $basePath . $this->config->componentsDirectory . '/' . $namePath . '.php';
if (is_file($filePath)) {
return $filePath;
}
$camelCaseName = strtolower(preg_replace('~(?<!^)(?<!\/)[A-Z]~', '_$0', $namePath) ?? '');
$filePath = $basePath . $this->config->viewFileComponentsPath . '/' . $camelCaseName . '.php';
$snakeCaseName = strtolower(preg_replace('~(?<!^)(?<!\/)[A-Z]~', '_$0', $namePath) ?? '');
$filePath = $basePath . $this->config->componentsDirectory . '/' . $snakeCaseName . '.php';
if (is_file($filePath)) {
return $filePath;
......
......@@ -8,17 +8,14 @@ use CodeIgniter\Config\BaseConfig;
class ViewComponents extends BaseConfig
{
public string $classComponentsPath = 'View/Components';
public string $viewFileComponentsPath = 'Views/components';
public string $componentsDirectory = 'Components';
/**
* Modules to look into for local components. Associative array with the module namespace as key and the module path
* as value.
* Paths to look into for local components. Will look for the $componentsDirectory inside.
*
* @var array<string, string>
* @var string[]
*/
public array $lookupModules = [];
public array $lookupPaths = [];
public string $defaultLookupPath = APPPATH;
public string $defaultLookupPath = APPPATH . 'Views/';
}
<?php
declare(strict_types=1);
namespace ViewThemes\Config;
use CodeIgniter\Config\BaseConfig;
class Themes extends BaseConfig
{
public string $themesDirectory = ROOTPATH . 'themes';
public string $manifestFilename = 'manifest.json';
/**
* @var array<string, string>
*/
public array $themes = [
'app' => 'cp_app',
'admin' => 'cp_admin',
'install' => 'cp_install',
'auth' => 'cp_auth',
];
}
<?php
declare(strict_types=1);
namespace ViewThemes;
use ViewThemes\Config\Themes;
/**
* Borrowed and adapted from https://github.com/lonnieezell/Bonfire2
*/
class Theme
{
protected Themes $config;
/**
* @var string
*/
protected static $defaultTheme = 'app';
/**
* @var string
*/
protected static $currentTheme;
/**
* Holds theme info retrieved
*
* @var array<int, array<string, mixed>>
*/
protected static array $info = [];
public function __construct()
{
$this->config = config('Themes');
}
/**
* Sets the active theme.
*/
public static function setTheme(string $theme): void
{
static::$currentTheme = $theme;
}
/**
* Returns the path to the specified theme folder. If no theme is provided, will use the current theme.
*/
public static function path(string $theme = null): string
{
if ($theme === null) {
$theme = static::current();
}
// Ensure we've pulled the theme info
if (static::$info === []) {
static::$info = self::available();
}
foreach (static::$info as $info) {
if ($info['name'] === $theme) {
return $info['path'];
}
}
return '';
}
/**
* Returns the name of the active theme.
*/
public static function current(): string
{
return static::$currentTheme !== null
? static::$currentTheme
: static::$defaultTheme;
}
/**
* Returns an array of all available themes and the paths to their directories.
*
* @return array<int, array<string, mixed>>
*/
public static function available(): array
{
$themes = [];
$config = config('Themes');
foreach ($config->themes as $name => $folder) {
$themes[] = [
'name' => $name,
'path' => $config->themesDirectory . '/' . $folder . '/',
];
}
return $themes;
}
}
......@@ -2,7 +2,7 @@
declare(strict_types=1);
namespace App\View\Components;
namespace App\Views\Components;
use ViewComponents\Component;
......
......@@ -2,7 +2,7 @@
declare(strict_types=1);
namespace App\View\Components\Forms;
namespace App\Views\Components\Forms;
use ViewComponents\Component;
......
......@@ -2,7 +2,7 @@
declare(strict_types=1);
namespace App\View\Components\Forms;
namespace App\Views\Components\Forms;
use ViewComponents\Component;
......
......@@ -2,7 +2,7 @@
declare(strict_types=1);
namespace App\View\Components\Forms;
namespace App\Views\Components\Forms;
use ViewComponents\Component;
......
......@@ -2,7 +2,7 @@
declare(strict_types=1);
namespace App\View\Components\Forms;
namespace App\Views\Components\Forms;
use ViewComponents\Component;
......
......@@ -2,7 +2,7 @@
declare(strict_types=1);
namespace App\View\Components\Forms;
namespace App\Views\Components\Forms;
use ViewComponents\Component;
......
......@@ -2,7 +2,7 @@
declare(strict_types=1);
namespace App\View\Components\Forms;
namespace App\Views\Components\Forms;
use ViewComponents\Component;
......
......@@ -2,7 +2,7 @@
declare(strict_types=1);
namespace App\View\Components;
namespace App\Views\Components;
use Exception;
use ViewComponents\Component;
......
......@@ -8,6 +8,7 @@ use CodeIgniter\Controller;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use Psr\Log\LoggerInterface;
use ViewThemes\Theme;
/**
* Class BaseController
......@@ -20,14 +21,6 @@ use Psr\Log\LoggerInterface;
class BaseController extends Controller
{
/**
* An array of helpers to be loaded automatically upon class instantiation. These helpers will be available to all
* other controllers that extend BaseController.
*
* @var string[]
*/
protected $helpers = ['auth', 'breadcrumb', 'svg', 'components', 'misc'];
/**
* Constructor.
*/
......@@ -36,7 +29,11 @@ class BaseController extends Controller
ResponseInterface $response,
LoggerInterface $logger
): void {
$this->helpers = array_merge($this->helpers, ['auth', 'breadcrumb', 'svg', 'components', 'misc']);
// Do Not Edit This Line
parent::initController($request, $response, $logger);
Theme::setTheme('admin');
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment