Added the theme selector
This commit is contained in:
parent
48160088f2
commit
c324c80969
|
|
@ -7323,6 +7323,9 @@ body {
|
|||
.layout > .layout-row > .layout-cell.min-size {
|
||||
width: 0;
|
||||
}
|
||||
.layout > .layout-row > .layout-cell.min-height {
|
||||
height: 0;
|
||||
}
|
||||
.layout > .layout-row > .layout-cell.center {
|
||||
text-align: center;
|
||||
}
|
||||
|
|
@ -7363,6 +7366,9 @@ body {
|
|||
.layout > .layout-row > .layout-cell.min-size {
|
||||
width: 0;
|
||||
}
|
||||
.layout > .layout-row > .layout-cell.min-height {
|
||||
height: 0;
|
||||
}
|
||||
.layout > .layout-row > .layout-cell.center {
|
||||
text-align: center;
|
||||
}
|
||||
|
|
@ -7411,6 +7417,9 @@ body {
|
|||
.layout > .layout-cell.min-size {
|
||||
width: 0;
|
||||
}
|
||||
.layout > .layout-cell.min-height {
|
||||
height: 0;
|
||||
}
|
||||
.layout > .layout-cell.center {
|
||||
text-align: center;
|
||||
}
|
||||
|
|
@ -11521,6 +11530,9 @@ ul.status-list li span.status.info {
|
|||
.control-breadcrumb + .content-tabs {
|
||||
margin-top: -20px;
|
||||
}
|
||||
.control-breadcrumb.no-bottom-margin {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
body.slim-container .control-breadcrumb {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
|
|
|
|||
|
|
@ -43,6 +43,10 @@
|
|||
+ .content-tabs {
|
||||
margin-top: -20px;
|
||||
}
|
||||
|
||||
&.no-bottom-margin {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
body.slim-container {
|
||||
|
|
|
|||
|
|
@ -68,6 +68,10 @@ body {
|
|||
width: 0;
|
||||
}
|
||||
|
||||
&.min-height {
|
||||
height: 0;
|
||||
}
|
||||
|
||||
&.center {
|
||||
text-align: center;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ use BackendMenu;
|
|||
use BackendAuth;
|
||||
use Backend\Classes\WidgetManager;
|
||||
use October\Rain\Support\ModuleServiceProvider;
|
||||
use System\Classes\SettingsManager;
|
||||
|
||||
class ServiceProvider extends ModuleServiceProvider
|
||||
{
|
||||
|
|
@ -89,6 +90,7 @@ class ServiceProvider extends ModuleServiceProvider
|
|||
'cms.manage_pages' => ['label' => 'Manage pages', 'tab' => 'Cms'],
|
||||
'cms.manage_layouts' => ['label' => 'Manage layouts', 'tab' => 'Cms'],
|
||||
'cms.manage_partials' => ['label' => 'Manage partials', 'tab' => 'Cms'],
|
||||
'cms.manage_themes' => ['label' => 'Manage themes', 'tab' => 'Cms']
|
||||
]);
|
||||
});
|
||||
|
||||
|
|
@ -99,6 +101,21 @@ class ServiceProvider extends ModuleServiceProvider
|
|||
$manager->registerFormWidget('Cms\FormWidgets\Components');
|
||||
});
|
||||
|
||||
/*
|
||||
* Register settings
|
||||
*/
|
||||
SettingsManager::instance()->registerCallback(function($manager){
|
||||
$manager->registerSettingItems('October.Cms', [
|
||||
'theme' => [
|
||||
'label' => 'cms::lang.theme.settings_menu',
|
||||
'description' => 'cms::lang.theme.settings_menu_description',
|
||||
'category' => 'CMS',
|
||||
'icon' => 'icon-picture-o',
|
||||
'url' => Backend::URL('cms/theme'),
|
||||
'sort' => 200
|
||||
]
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -0,0 +1,112 @@
|
|||
.theme-selector-layout .layout-cell {
|
||||
padding: 24px;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.theme-selector-layout .theme-thumbnail {
|
||||
width: 288px;
|
||||
background: #ecf0f1;
|
||||
border-bottom: 1px solid #e3e7e9;
|
||||
}
|
||||
.theme-selector-layout .theme-thumbnail img {
|
||||
opacity: 0.6;
|
||||
filter: alpha(opacity=60);
|
||||
width: 240px;
|
||||
}
|
||||
.theme-selector-layout .theme-description {
|
||||
border-bottom: 1px solid #f2f3f4;
|
||||
}
|
||||
.theme-selector-layout .theme-description h3,
|
||||
.theme-selector-layout .theme-description p {
|
||||
opacity: 0.6;
|
||||
filter: alpha(opacity=60);
|
||||
}
|
||||
.theme-selector-layout .theme-description h3 {
|
||||
margin: 0 0 25px 0;
|
||||
font-size: 28px;
|
||||
color: #2b3e50;
|
||||
display: inline-block;
|
||||
}
|
||||
.theme-selector-layout .theme-description p.author {
|
||||
font-size: 13px;
|
||||
display: inline-block;
|
||||
color: #808c8d;
|
||||
}
|
||||
.theme-selector-layout .theme-description p.description {
|
||||
color: #2b3e50;
|
||||
font-size: 14px;
|
||||
line-height: 180%;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.theme-selector-layout .theme-description .controls button i.icon-star {
|
||||
margin-right: 5px;
|
||||
color: #f1a84e;
|
||||
font-size: 16px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.theme-selector-layout .layout-row.active .theme-thumbnail {
|
||||
background: #bdc3c7;
|
||||
border-bottom-color: #bdc3c7;
|
||||
}
|
||||
.theme-selector-layout .layout-row.active .thumbnail-container {
|
||||
position: relative;
|
||||
}
|
||||
.theme-selector-layout .layout-row.active .thumbnail-container:after {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-top: 14px solid transparent;
|
||||
border-bottom: 14px solid transparent;
|
||||
border-left: 15px solid #bdc3c7;
|
||||
position: absolute;
|
||||
right: -35px;
|
||||
top: 50%;
|
||||
margin-top: -14px;
|
||||
}
|
||||
.theme-selector-layout .layout-row.active .theme-description h3,
|
||||
.theme-selector-layout .layout-row:hover .theme-description h3,
|
||||
.theme-selector-layout .layout-row.active .theme-description p,
|
||||
.theme-selector-layout .layout-row:hover .theme-description p {
|
||||
opacity: 1;
|
||||
filter: alpha(opacity=100);
|
||||
}
|
||||
.theme-selector-layout .layout-row.active .theme-thumbnail img,
|
||||
.theme-selector-layout .layout-row:hover .theme-thumbnail img {
|
||||
opacity: 1;
|
||||
filter: alpha(opacity=100);
|
||||
}
|
||||
.theme-selector-layout .layout-row.last .theme-description,
|
||||
.theme-selector-layout .layout-row.last .theme-thumbnail {
|
||||
border-bottom: none!important;
|
||||
}
|
||||
.theme-selector-layout .find-more-themes {
|
||||
background: #ecf0f1;
|
||||
color: #2b3e50;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
padding: 20px;
|
||||
-webkit-border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.theme-selector-layout .find-more-themes:hover {
|
||||
background: #1795f1;
|
||||
color: white;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.theme-selector-layout .layout-cell,
|
||||
.theme-selector-layout .layout-row {
|
||||
display: block!important;
|
||||
width: auto!important;
|
||||
height: auto!important;
|
||||
}
|
||||
.theme-selector-layout .theme-thumbnail img {
|
||||
width: 100%;
|
||||
}
|
||||
.theme-selector-layout .layout-row.links .theme-thumbnail {
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
|
|
@ -0,0 +1,139 @@
|
|||
@import "../../../backend/assets/less/core/boot.less";
|
||||
|
||||
.theme-selector-layout {
|
||||
.layout-cell {
|
||||
padding: 24px;
|
||||
.box-sizing(border-box);
|
||||
}
|
||||
|
||||
.theme-thumbnail {
|
||||
width: 288px;
|
||||
background: #ecf0f1;
|
||||
border-bottom: 1px solid #e3e7e9;
|
||||
|
||||
img {
|
||||
.opacity(0.6);
|
||||
width: 240px;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-description {
|
||||
border-bottom: 1px solid #f2f3f4;
|
||||
|
||||
h3, p {
|
||||
.opacity(0.6);
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 0 0 25px 0;
|
||||
font-size: 28px;
|
||||
color: #2b3e50;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
p.author {
|
||||
font-size: 13px;
|
||||
display: inline-block;
|
||||
color: #808c8d;
|
||||
}
|
||||
|
||||
p.description {
|
||||
color: #2b3e50;
|
||||
font-size: 14px;
|
||||
line-height: 180%;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.controls {
|
||||
button i.icon-star {
|
||||
margin-right: 5px;
|
||||
color: #f1a84e;
|
||||
font-size: 16px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.layout-row.active {
|
||||
.theme-thumbnail {
|
||||
background: #bdc3c7;
|
||||
border-bottom-color: #bdc3c7;
|
||||
|
||||
}
|
||||
|
||||
.thumbnail-container {
|
||||
position: relative;
|
||||
|
||||
&:after {
|
||||
.triangle(right, 15px, 28px, #bdc3c7);
|
||||
position: absolute;
|
||||
right: -35px;
|
||||
top: 50%;
|
||||
margin-top: -14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.layout-row {
|
||||
&.active, &:hover {
|
||||
.theme-description {
|
||||
h3, p {
|
||||
.opacity(1);
|
||||
}
|
||||
}
|
||||
|
||||
.theme-thumbnail {
|
||||
img {
|
||||
.opacity(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.last {
|
||||
.theme-description, .theme-thumbnail {
|
||||
border-bottom: none!important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.find-more-themes {
|
||||
background: #ecf0f1;
|
||||
color: #2b3e50;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
padding: 20px;
|
||||
.border-radius(4px);
|
||||
|
||||
&:hover {
|
||||
background: @link-color;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Screen specific
|
||||
//
|
||||
|
||||
@media (max-width: @screen-sm) {
|
||||
.theme-selector-layout {
|
||||
.layout-cell, .layout-row {
|
||||
display: block!important;
|
||||
width: auto!important;
|
||||
height: auto!important;
|
||||
}
|
||||
|
||||
.theme-thumbnail {
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.layout-row.links {
|
||||
.theme-thumbnail {
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,12 @@ use File;
|
|||
use Lang;
|
||||
use Event;
|
||||
use Config;
|
||||
use URL;
|
||||
use Cache;
|
||||
use DB;
|
||||
use DirectoryIterator;
|
||||
use System\Classes\SystemException;
|
||||
use October\Rain\Support\Yaml;
|
||||
|
||||
/**
|
||||
* This class represents the CMS theme.
|
||||
|
|
@ -21,6 +26,11 @@ class Theme
|
|||
*/
|
||||
protected $dirName;
|
||||
|
||||
/**
|
||||
* @var mixed Keeps the cached configuration file values.
|
||||
*/
|
||||
protected $configCache = null;
|
||||
|
||||
/**
|
||||
* Loads the theme.
|
||||
*/
|
||||
|
|
@ -84,7 +94,16 @@ class Theme
|
|||
*/
|
||||
public static function getActiveTheme()
|
||||
{
|
||||
$activeTheme = Config::get('cms.activeTheme');
|
||||
$configKey = 'cms.activeTheme';
|
||||
|
||||
$activeTheme = Config::get($configKey);
|
||||
|
||||
$dbResult = DB::table('system_settings')
|
||||
->where('item', '=', $configKey)
|
||||
->remember(1440, $configKey)
|
||||
->pluck('value');
|
||||
if ($dbResult !== null)
|
||||
$activeTheme = $dbResult;
|
||||
|
||||
$apiResult = Event::fire('cms.activeTheme', [], true);
|
||||
if ($apiResult !== null)
|
||||
|
|
@ -101,6 +120,20 @@ class Theme
|
|||
return $theme;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the active theme.
|
||||
* The active theme code is stored in the database and overrides the configuration cms.activeTheme parameter.
|
||||
* @param string $code Specifies the active theme code.
|
||||
*/
|
||||
public static function setActiveTheme($code)
|
||||
{
|
||||
$configKey = 'cms.activeTheme';
|
||||
|
||||
DB::table('system_settings')->where('item', '=', $configKey)->delete();
|
||||
DB::table('system_settings')->insert(['item'=>$configKey, 'value'=>$code]);
|
||||
Cache::forget($configKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the edit theme.
|
||||
* By default the edit theme is loaded from the cms.editTheme parameter,
|
||||
|
|
@ -130,4 +163,75 @@ class Theme
|
|||
|
||||
return $theme;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all themes.
|
||||
* @return array Returns an array of the Theme objects.
|
||||
*/
|
||||
public static function all()
|
||||
{
|
||||
$path = base_path().Config::get('cms.themesDir');
|
||||
|
||||
$it = new DirectoryIterator($path);
|
||||
$result = [];
|
||||
foreach ($it as $fileinfo) {
|
||||
if ($fileinfo->isDot() ||(substr($fileinfo->getFilename(), 0, 1) == '.'))
|
||||
continue;
|
||||
|
||||
$theme = new self();
|
||||
$theme->load($fileinfo->getFilename());
|
||||
|
||||
$result[] = $theme;
|
||||
|
||||
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the theme.yaml file and returns the theme configuration values.
|
||||
* @return array Returns the parsed configuration file values.
|
||||
*/
|
||||
public function getConfig()
|
||||
{
|
||||
if ($this->configCache !== null)
|
||||
return $this->configCache;
|
||||
|
||||
$path = $this->getPath().'/theme.yaml';
|
||||
if (!File::exists($path))
|
||||
return $this->configCache = [];
|
||||
|
||||
return $this->configCache = Yaml::parseFile($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a value from the theme configuration file by its name.
|
||||
* @param string $name Specifies the configuration parameter name.
|
||||
* @param mixed $default Specifies the default value to return in case if the parameter doesn't exist in the configuration file.
|
||||
* @return mixed Returns the parameter value or a default value
|
||||
*/
|
||||
public function getConfigValue($name, $default = null)
|
||||
{
|
||||
$config = $this->getConfig();
|
||||
if (isset($config[$name]))
|
||||
return $config[$name];
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the theme preview image URL.
|
||||
* If the image file doesn't exist returns the placeholder image URL.
|
||||
* @return string Returns the image URL.
|
||||
*/
|
||||
public function getPreviewImageUrl()
|
||||
{
|
||||
$previewPath = '/assets/images/theme-preview.png';
|
||||
$path = $this->getPath().$previewPath;
|
||||
if (!File::exists($path))
|
||||
return URL::asset('modules/cms/assets/images/default-theme-preview.png');
|
||||
|
||||
return URL::asset('themes/'.$this->getDirName().$previewPath);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
<?php namespace Cms\Controllers;
|
||||
|
||||
use Lang;
|
||||
use Config;
|
||||
use BackendMenu;
|
||||
use Input;
|
||||
use Backend\Classes\Controller;
|
||||
use Cms\Classes\Theme as CmsTheme;
|
||||
|
||||
/**
|
||||
* Theme selector controller
|
||||
*
|
||||
* @package october\backend
|
||||
* @author Alexey Bobkov, Samuel Georges
|
||||
*
|
||||
*/
|
||||
class Theme extends Controller
|
||||
{
|
||||
public $requiredPermissions = ['cms.manage_themes'];
|
||||
|
||||
public $bodyClass = 'slim-container';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->addJs('/modules/cms/assets/js/themeselector/themeselector.js', 'core');
|
||||
$this->addCss('/modules/cms/assets/css/october.theme-selector.css', 'core');
|
||||
|
||||
$this->pageTitle = Lang::get('cms::lang.theme.settings_menu');
|
||||
BackendMenu::setContext('October.System', 'system', 'settings');
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function index_onSetActiveTheme()
|
||||
{
|
||||
CmsTheme::setActiveTheme(Input::get('theme'));
|
||||
|
||||
return [
|
||||
'#theme-list' => $this->makePartial('theme_list')
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
$themes = Cms\Classes\Theme::all();
|
||||
$activeTheme = Cms\Classes\Theme::getActiveTheme();
|
||||
$cnt = count($themes);
|
||||
|
||||
foreach ($themes as $index=>$theme):
|
||||
$isLast = $index == $cnt-1;
|
||||
$isActive = $activeTheme->getDirName() == $theme->getDirName();
|
||||
$author = $theme->getConfigValue('author');
|
||||
?>
|
||||
<div class="layout-row <?= $isLast ? 'last' : null ?> min-size <?= $isActive ? 'active' : null ?>">
|
||||
<div class="layout-cell min-height theme-thumbnail">
|
||||
<div class="thumbnail-container"><img src="<?= $theme->getPreviewImageUrl() ?>"/></div>
|
||||
</div>
|
||||
<div class="layout-cell min-height theme-description">
|
||||
<h3><?= e($theme->getConfigValue('name', $theme->getDirName())) ?></h3>
|
||||
<?php if (strlen($author)): ?>
|
||||
<p class="author">by <a href="<?= e($theme->getConfigValue('author-url', '#')) ?>"><?= e($author) ?></a></p>
|
||||
<?php endif ?>
|
||||
<p class="description"><?= e($theme->getConfigValue('description', 'The theme description is not provided.')) ?></p>
|
||||
<div class="controls">
|
||||
<?php if ($isActive): ?>
|
||||
<button
|
||||
type="submit"
|
||||
disabled
|
||||
class="btn btn-disabled">
|
||||
<i class="icon-star"></i>
|
||||
<?= e(trans('cms::lang.theme.active_btn')) ?>
|
||||
</button>
|
||||
<?php else: ?>
|
||||
<button
|
||||
type="submit"
|
||||
data-request="onSetActiveTheme"
|
||||
data-request-data="theme: '<?= e($theme->getDirName()) ?>'"
|
||||
data-stripe-load-indicator
|
||||
class="btn btn-primary">
|
||||
<?= e(trans('cms::lang.theme.activate_btn')) ?>
|
||||
</button>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach ?>
|
||||
<div class="layout-row links">
|
||||
<div class="layout-cell theme-thumbnail">
|
||||
|
||||
</div>
|
||||
<div class="layout-cell theme-description">
|
||||
<a class="find-more-themes" href="http://octobercms.com/themes"><?= e(trans('cms::lang.theme.find_more_themes')) ?></a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<?= Block::put('body') ?>
|
||||
<div class="layout">
|
||||
<div class="layout-row min-size">
|
||||
<div class="control-breadcrumb no-bottom-margin">
|
||||
<ul>
|
||||
<li><a href="<?= Backend::url('system/settings') ?>"><?= e(trans('system::lang.settings.menu_label')) ?></a></li>
|
||||
<li><?= e($this->pageTitle) ?></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layout-row">
|
||||
<?= Form::open(['onsubmit'=>'return false']) ?>
|
||||
<div class="layout theme-selector-layout" id="theme-list">
|
||||
<?= $this->makePartial('theme_list') ?>
|
||||
</div>
|
||||
<?= Form::close() ?>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<?= Block::endPut() ?>
|
||||
|
|
@ -20,7 +20,12 @@ return [
|
|||
'not_set' => "The edit theme is not set.",
|
||||
'not_found' => "The edit theme is not found.",
|
||||
'not_match' => "The object you're trying to access doesn't belong to the theme being edited. Please reload the page."
|
||||
]
|
||||
],
|
||||
'settings_menu' => 'Front-end theme',
|
||||
'settings_menu_description' => 'Preview the list of installed themes and select an active theme.',
|
||||
'find_more_themes' => 'Find more themes on OctoberCMS Theme Marketplace.',
|
||||
'activate_btn' => 'Activate',
|
||||
'active_btn' => 'Activate',
|
||||
],
|
||||
'page' => [
|
||||
'not_found' => [
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
|
|
@ -0,0 +1,4 @@
|
|||
name: Demo
|
||||
description: Demo OctoberCMS theme. Demonstrates the basic concepts of the front-end theming: layouts, pages, partials, components, content blocks, AJAX framework.
|
||||
author: OctoberCMS
|
||||
author-url: 'http://octobercms.com'
|
||||
Loading…
Reference in New Issue