Add new color settings

This commit is contained in:
Pig Fang 2019-12-04 16:45:09 +08:00
parent 8aec8e5028
commit ed9d856c43
22 changed files with 258 additions and 104 deletions

View File

@ -118,7 +118,7 @@ class AdminController extends Controller
return redirect('/admin');
}
public function customize(Request $request, \App\Services\Webpack $webpack)
public function customize(Request $request)
{
$homepage = Option::form('homepage', OptionForm::AUTO_DETECT, function ($form) {
$form->text('home_pic_url')->hint();
@ -150,18 +150,40 @@ class AdminController extends Controller
$form->textarea('custom_js', 'JavaScript')->rows(6);
})->addMessage()->handle();
if ($request->isMethod('post') && $request->input('action') == 'color') {
$color = $this->validate($request, ['color' => 'required'])['color'];
option(['color_scheme' => $color]);
if ($request->isMethod('post') && $request->input('action') === 'color') {
$navbar = $request->input('navbar');
if ($navbar) {
option(['navbar_color' => $navbar]);
}
$sidebar = $request->input('sidebar');
if ($sidebar) {
option(['sidebar_color' => $sidebar]);
}
}
return view('admin.customize', [
'colors' => ['blue', 'yellow', 'green', 'purple', 'red', 'black'],
'skins_css' => $webpack->url('skins/_all-skins.min.css'),
'colors' => [
'navbar' => [
'primary', 'secondary', 'success', 'danger', 'indigo',
'purple', 'pink', 'teal', 'cyan', 'dark', 'gray',
'fuchsia', 'maroon', 'olive', 'navy',
'lime', 'light', 'warning', 'white', 'orange',
],
'sidebar' => [
'primary', 'warning', 'info', 'danger', 'success', 'indigo',
'navy', 'purple', 'fuchsia', 'pink', 'maroon', 'orange',
'lime', 'teal', 'olive',
],
],
'forms' => [
'homepage' => $homepage,
'custom_js_css' => $customJsCss,
],
'extra' => [
'navbar' => option('navbar_color'),
'sidebar' => option('sidebar_color'),
],
]);
}

View File

@ -41,15 +41,25 @@ class HeadComposer
public function applyThemeColor(View $view)
{
$colors = [
'blue' => '#3c8dbc',
'yellow' => '#f39c12',
'green' => '#00a65a',
'purple' => '#605ca8',
'red' => '#dd4b39',
'black' => '#ffffff',
'primary' => '#007bff',
'secondary' => '#6c757d',
'success' => '#28a745',
'warning' => '#ffc107',
'danger' => '#dc3545',
'navy' => '#001f3f',
'olive' => '#3d9970',
'lime' => '#01ff70',
'fuchsia' => '#f012be',
'maroon' => '#d81b60',
'indigo' => '#6610f2',
'purple' => '#6f42c1',
'pink' => '#e83e8c',
'orange' => '#fd7e14',
'teal' => '#20c997',
'cyan' => '#17a2b8',
'gray' => '#6c757d',
];
preg_match('/skin-(\w+)?(?:-light)?/', option('color_scheme'), $matches);
$view->with('theme_color', Arr::get($colors, $matches[1]));
$view->with('theme_color', Arr::get($colors, option('navbar_color')));
}
public function seo(View $view)

View File

@ -53,6 +53,7 @@ class SideMenuComposer
$classes = [];
if ($isActive) {
$item['active'] = true;
$classes[] = 'active menu-open';
}

View File

@ -12,9 +12,12 @@ class ViewServiceProvider extends ServiceProvider
public function boot(Webpack $webpack)
{
View::composer(['home', '*.base', 'shared.header'], function ($view) {
$lightColors = ['light', 'warning', 'white', 'orange'];
$color = option('navbar_color');
$view->with([
'site_name' => option_localized('site_name'),
'color_scheme' => option('color_scheme'),
'navbar_color' => $color,
'color_mode' => in_array($color, $lightColors) ? 'light' : 'dark',
]);
});
@ -32,6 +35,10 @@ class ViewServiceProvider extends ServiceProvider
View::composer('shared.user-menu', Composers\UserMenuComposer::class);
View::composer('shared.sidebar', function ($view) {
$view->with('sidebar_color', option('sidebar_color'));
});
View::composer('shared.side-menu', Composers\SideMenuComposer::class);
View::composer('shared.user-panel', Composers\UserPanelComposer::class);

View File

@ -11,7 +11,6 @@ return [
'ip_get_method' => '0',
'api_type' => 'false',
'announcement' => 'Welcome to Blessing Skin {version}!',
'color_scheme' => 'skin-blue',
'home_pic_url' => './app/bg.png',
'custom_css' => '',
'custom_js' => '',
@ -58,4 +57,6 @@ return [
'content_policy' => '',
'transparent_navbar' => 'false',
'status_code_for_private' => '403',
'navbar_color' => 'cyan',
'sidebar_color' => 'dark-maroon',
];

View File

@ -23,6 +23,7 @@
"admin-lte": "^3.0.1",
"echarts": "^4.5.0",
"jquery": "^3.4.1",
"rxjs": "^6.5.3",
"skinview3d": "^1.1.0",
"vue": "^2.6.10",
"vue-good-table": "^2.18.1",
@ -89,7 +90,7 @@
"printWidth": 80,
"semi": false,
"singleQuote": true,
"trailingComma": "es5",
"trailingComma": "all",
"arrowParens": "avoid",
"tabWidth": 2
},

View File

@ -5,3 +5,8 @@
#chart
width 100%
height 400px
.btn-color
width 40px
height 20px
cursor pointer

View File

@ -10,25 +10,16 @@ body
.navbar-brand
font-family Minecraft, 'Segoe UI', 'Microsoft Yahei', 'Microsoft Jhenghei', sans-serif
border-right 0
.navbar
transition color 0.25s ease-in-out, border-color 0.25s ease-in-out, background-color 0.25s ease-in-out
.transparent
.navbar-brand, .navbar-header > a
.navbar-brand
color #5e5e5e
&.navbar
background-color rgba(255, 255, 255, 0.2)
.navbar-nav li a
border 0
color #5e5e5e
&:hover
background rgba(0, 0, 0, 0.2)
color #f6f6f6
.splash
width 80%
height 50%

View File

@ -1,16 +1,83 @@
function handler(event: Event): void {
document.body.className =
document.body.className.replace(
/skin-\w+(?:-light)?/,
// eslint-disable-next-line no-extra-parens
(event.target as HTMLInputElement).value,
/* eslint-disable object-curly-newline */
import { fromEvent, merge, of, partition } from 'rxjs'
import { filter, map, pairwise } from 'rxjs/operators'
export function registerNavbarPicker(
navbar: HTMLElement,
picker: HTMLDivElement,
init: string,
): void {
const color$ = fromEvent(picker, 'click')
.pipe(
map(event => (event.target as HTMLElement)),
filter(
(element): element is HTMLInputElement => element.tagName === 'INPUT',
),
map(element => element.value),
)
merge(of(init), color$)
.pipe(pairwise())
.subscribe(([previous, current]) => {
navbar.classList.replace(`navbar-${previous}`, `navbar-${current}`)
})
const [light$, dark$] = partition(
color$,
color => ['light', 'warning', 'white', 'orange', 'lime'].includes(color),
)
light$.subscribe(() => {
// DO NOT use `classList.replace`.
navbar.classList.remove('navbar-dark')
navbar.classList.add('navbar-light')
})
dark$.subscribe(() => {
// DO NOT use `classList.replace`.
navbar.classList.remove('navbar-light')
navbar.classList.add('navbar-dark')
})
}
const table = document.querySelector('#change-color')
const navbar = document.querySelector<HTMLElement>('.wrapper > nav')
const picker = document.querySelector<HTMLDivElement>('#navbar-color-picker')
/* istanbul ignore next */
if (table) {
table.addEventListener('change', handler)
if (navbar && picker) {
registerNavbarPicker(navbar, picker, blessing.extra.navbar || 'white')
}
export default handler
export function registerSidebarPicker(
sidebar: HTMLElement,
{ dark, light }: { dark: HTMLDivElement, light: HTMLDivElement },
init: string,
): void {
const color$ = merge(
fromEvent(dark, 'click'),
fromEvent(light, 'click'),
).pipe(
map(event => (event.target as HTMLElement)),
filter(
(element): element is HTMLInputElement => element.tagName === 'INPUT',
),
map(element => element.value),
)
merge(of(init), color$)
.pipe(pairwise())
.subscribe(([previous, current]) => {
sidebar.classList.replace(`sidebar-${previous}`, `sidebar-${current}`)
})
}
const sidebar = document.querySelector<HTMLElement>('.main-sidebar')
const darkPicker = document
.querySelector<HTMLDivElement>('#sidebar-dark-picker')
const lightPicker = document
.querySelector<HTMLDivElement>('#sidebar-light-picker')
/* istanbul ignore next */
if (sidebar && darkPicker && lightPicker) {
registerSidebarPicker(
sidebar,
{ dark: darkPicker, light: lightPicker },
blessing.extra.sidebar || 'dark-primary',
)
}

View File

@ -1,10 +1,52 @@
import handler from '@/views/admin/Customization'
import {
registerNavbarPicker,
registerSidebarPicker,
} from '@/views/admin/Customization'
test('preview color', () => {
document.body.classList.add('skin-blue')
const target = document.createElement('input')
target.value = 'skin-purple'
handler({ target } as any as Event)
test('preview navbar color', () => {
const nav = document.createElement('nav')
nav.className = 'navbar-primary navbar-dark'
const picker = document.createElement('div')
picker.innerHTML = `
<label><input type="radio" name="navbar" value="cyan"></label>
<label><input type="radio" name="navbar" value="orange"></label>
`
const cyan = picker.querySelector<HTMLInputElement>('[value=cyan]')!
const orange = picker.querySelector<HTMLInputElement>('[value=orange]')!
expect(document.body.classList.contains('skin-purple')).toBeTrue()
registerNavbarPicker(nav, picker, 'primary')
cyan.click()
expect(nav.className).toContain('navbar-cyan')
expect(nav.className).toContain('navbar-dark')
expect(nav.className).not.toContain('navbar-light')
orange.click()
expect(nav.className).toContain('navbar-orange')
expect(nav.className).not.toContain('navbar-cyan')
expect(nav.className).toContain('navbar-light')
expect(nav.className).not.toContain('navbar-dark')
})
test('preview sidebar color', () => {
const sidebar = document.createElement('aside')
sidebar.className = 'sidebar-dark-primary'
const darkPicker = document.createElement('div')
darkPicker.innerHTML = `
<label><input type="radio" name="sidebar" value="dark-cyan"></label>`
const darkCyan = darkPicker.querySelector<HTMLInputElement>('[value="dark-cyan"]')!
const lightPicker = document.createElement('div')
lightPicker.innerHTML = `
<label><input type="radio" name="sidebar" value="light-cyan"></label>`
const lightCyan = lightPicker.querySelector<HTMLInputElement>('[value="light-cyan"]')!
registerSidebarPicker(
sidebar,
{ dark: darkPicker, light: lightPicker },
'dark-primary',
)
darkCyan.click()
expect(sidebar.className).toContain('sidebar-dark-cyan')
lightCyan.click()
expect(sidebar.className).toContain('sidebar-light-cyan')
})

View File

@ -71,18 +71,10 @@ customize:
success: Theme color updated.
colors:
blue: Blue (Default)
blue-light: Blue Light
yellow: Yellow
yellow-light: Yellow Light
green: Green
green-light: Green Light
purple: Purple
purple-light: Purple Light
red: Red
red-light: Red Light
black: Black
black-light: Black Light
navbar: Top Navigation Bar
sidebar:
dark: Sidebar (Dark)
light: Sidebar (Light)
i18n:
add: Add New Language Line

View File

@ -10,6 +10,7 @@
- Added "Status" page.
- Added support of customizing UI text.
- Spanish support (Greatly thanks [@poopingpenis](https://github.com/poopingpenis))
- Brand new website theme color settings.
## Tweaked

View File

@ -10,6 +10,7 @@
- 新增「运行状态」页面
- 支持自定义 UI 文本
- 西班牙语支持(感谢 [@poopingpenis](https://github.com/poopingpenis)
- 全新的站点配色设置
## 调整

View File

@ -12,45 +12,46 @@
{{ trans('admin.customize.change-color.title') }}
</h3>
</div>
<div class="card-body p-0">
<table class="table table-striped bring-up nth-2-center" id="change-color">
<tbody>
{% for color in colors %}
<tr>
<td>{{ trans("admin.customize.colors.#{color}") }}</td>
<td>
<label>
<input
type="radio"
name="color"
value="skin-{{ color }}"
style="display: none;"
>
<span class="btn bg-{{ color }} btn-xs">
<i class="far fa-eye"></i>
</span>
</label>
</td>
</tr>
<tr>
<td>{{ trans("admin.customize.colors.#{color}-light") }}</td>
<td>
<label>
<input
type="radio"
name="color"
value="skin-{{ color }}-light"
style="display: none;"
>
<span class="btn bg-{{ color }} btn-xs">
<i class="far fa-eye"></i>
</span>
</label>
</td>
</tr>
<div class="card-body">
<div id="navbar-color-picker">
<p>{{ trans('admin.customize.colors.navbar') }}</p>
<div>
{% for color in colors.navbar %}
<label>
<input type="radio" class="d-none" name="navbar" value="{{ color }}">
<div class="btn-color bg-{{ color }} display-inline-block
rounded-pill mr-2 mb-1 elevation-2"
></div>
</label>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div id="sidebar-dark-picker">
<p>{{ trans('admin.customize.colors.sidebar.dark') }}</p>
<div>
{% for color in colors.sidebar %}
<label>
<input type="radio" class="d-none" name="sidebar" value="dark-{{ color }}">
<div class="btn-color bg-{{ color }} display-inline-block
rounded-pill mr-2 mb-1 elevation-2"
></div>
</label>
{% endfor %}
</div>
</div>
<div id="sidebar-light-picker">
<p>{{ trans('admin.customize.colors.sidebar.light') }}</p>
<div>
{% for color in colors.sidebar %}
<label>
<input type="radio" class="d-none" name="sidebar" value="light-{{ color }}">
<div class="btn-color bg-{{ color }} display-inline-block
rounded-pill mr-2 mb-1 elevation-2"
></div>
</label>
{% endfor %}
</div>
</div>
</div>
<div class="card-footer">
<input
@ -70,5 +71,7 @@
{% endblock %}
{% block before_foot %}
<link rel="stylesheet" href="{{ skins_css }}">
<script>
blessing.extra = {{ extra|json_encode|raw }}
</script>
{% endblock %}

View File

@ -20,7 +20,7 @@
<div id="fixed-bg" style="background-image: url('{{ home_pic_url }}')"></div>
{% endif %}
<nav class="navbar navbar-expand fixed-top navbar-light navbar-white
<nav class="navbar navbar-expand fixed-top navbar-{{ navbar_color }} navbar-{{ color_mode }}
ml-0 {{ transparent_navbar ? 'transparent' }}"
>
<div class="container">

View File

@ -1,4 +1,4 @@
<nav class="main-header navbar navbar-expand navbar-white navbar-light">
<nav class="main-header navbar navbar-expand navbar-{{ navbar_color }} navbar-{{ color_mode }}">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" data-widget="pushmenu" href="#">

View File

@ -1,6 +1,6 @@
<li class="nav-item {{ item.classes|join(' ') }}">
{% if item.children %}
<a class="nav-link" href="#">
<a class="nav-link {{ item.active ? 'active' }}" href="#">
<i class="nav-icon fas {{ item.icon }}"></i>
<p>{{ trans(item.title) }}</p>
<i class="right fas fa-angle-left"></i>
@ -13,7 +13,7 @@
{% else %}
<a
href="{{ url(item.link) }}"
class="nav-link"
class="nav-link {{ item.active ? 'active' }}"
{% if attribute(item, 'new-tab') %}
target="_blank"
rel="noopener noreferrer"

View File

@ -1,4 +1,4 @@
<aside class="main-sidebar sidebar-dark-primary elevation-3">
<aside class="main-sidebar sidebar-{{ sidebar_color }} elevation-3">
<a href="{{ url('/') }}" class="brand-link text-center">
<span class="brand-text font-weight-light">{{ site_name }}</span>
</a>

View File

@ -7,7 +7,7 @@
<body class="hold-transition layout-top-nav">
<div class="wrapper">
<nav class="main-header navbar navbar-expand navbar-light navbar-white ml-0">
<nav class="main-header navbar navbar-expand navbar-{{ navbar_color }} navbar-{{ color_mode }} ml-0">
<div class="container">
<div class="navbar-header">
<a href="{{ url('/') }}" class="navbar-brand">{{ site_name }}</a>

View File

@ -27,9 +27,11 @@ class AdminFormsTest extends BrowserKitTestCase
{
// Change color
$this->visit('/admin/customize')
->select('skin-purple', 'color')
->select('orange', 'navbar')
->select('light-fuchsia', 'sidebar')
->press('submit_color');
$this->assertEquals('skin-purple', option('color_scheme'));
$this->assertEquals('orange', option('navbar_color'));
$this->assertEquals('light-fuchsia', option('sidebar_color'));
$this->visit('/admin/customize')
->type('url', 'home_pic_url')

View File

@ -31,7 +31,8 @@ class SideMenuComposerTest extends TestCase
$crawler = new Crawler($this->get('/user/oauth/manage')->getContent());
$this->assertCount(1, $crawler->filter('aside .nav-treeview'));
$this->assertCount(2, $crawler->filter('aside .active'));
$this->assertCount(2, $crawler->filter('aside .nav-item.active'));
$this->assertCount(2, $crawler->filter('aside .nav-link.active'));
}
public function testCollectPluginConfigs()

View File

@ -8413,6 +8413,13 @@ rxjs@^6.4.0:
dependencies:
tslib "^1.9.0"
rxjs@^6.5.3:
version "6.5.3"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.3.tgz#510e26317f4db91a7eb1de77d9dd9ba0a4899a3a"
integrity sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA==
dependencies:
tslib "^1.9.0"
safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.2"
resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"