diff --git a/app/Http/Controllers/AdminController.php b/app/Http/Controllers/AdminController.php index b45f8547..d1c02182 100644 --- a/app/Http/Controllers/AdminController.php +++ b/app/Http/Controllers/AdminController.php @@ -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'), + ], ]); } diff --git a/app/Http/View/Composers/HeadComposer.php b/app/Http/View/Composers/HeadComposer.php index 592daebb..24ce7ed5 100644 --- a/app/Http/View/Composers/HeadComposer.php +++ b/app/Http/View/Composers/HeadComposer.php @@ -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) diff --git a/app/Http/View/Composers/SideMenuComposer.php b/app/Http/View/Composers/SideMenuComposer.php index 64beae71..c0d18d23 100644 --- a/app/Http/View/Composers/SideMenuComposer.php +++ b/app/Http/View/Composers/SideMenuComposer.php @@ -53,6 +53,7 @@ class SideMenuComposer $classes = []; if ($isActive) { + $item['active'] = true; $classes[] = 'active menu-open'; } diff --git a/app/Providers/ViewServiceProvider.php b/app/Providers/ViewServiceProvider.php index db98060c..722a0e16 100644 --- a/app/Providers/ViewServiceProvider.php +++ b/app/Providers/ViewServiceProvider.php @@ -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); diff --git a/config/options.php b/config/options.php index 37df3610..8e15189e 100644 --- a/config/options.php +++ b/config/options.php @@ -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', ]; diff --git a/package.json b/package.json index aa8c02c7..1c8af8e3 100644 --- a/package.json +++ b/package.json @@ -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 }, diff --git a/resources/assets/src/styles/admin.styl b/resources/assets/src/styles/admin.styl index 23671ddf..7cf89878 100644 --- a/resources/assets/src/styles/admin.styl +++ b/resources/assets/src/styles/admin.styl @@ -5,3 +5,8 @@ #chart width 100% height 400px + +.btn-color + width 40px + height 20px + cursor pointer diff --git a/resources/assets/src/styles/home.styl b/resources/assets/src/styles/home.styl index 407da67e..7384d3af 100644 --- a/resources/assets/src/styles/home.styl +++ b/resources/assets/src/styles/home.styl @@ -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% diff --git a/resources/assets/src/views/admin/Customization.ts b/resources/assets/src/views/admin/Customization.ts index 577b46c5..10ad81ec 100644 --- a/resources/assets/src/views/admin/Customization.ts +++ b/resources/assets/src/views/admin/Customization.ts @@ -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('.wrapper > nav') +const picker = document.querySelector('#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('.main-sidebar') +const darkPicker = document + .querySelector('#sidebar-dark-picker') +const lightPicker = document + .querySelector('#sidebar-light-picker') +/* istanbul ignore next */ +if (sidebar && darkPicker && lightPicker) { + registerSidebarPicker( + sidebar, + { dark: darkPicker, light: lightPicker }, + blessing.extra.sidebar || 'dark-primary', + ) +} diff --git a/resources/assets/tests/views/admin/Customization.test.ts b/resources/assets/tests/views/admin/Customization.test.ts index e001392c..44aa4a59 100644 --- a/resources/assets/tests/views/admin/Customization.test.ts +++ b/resources/assets/tests/views/admin/Customization.test.ts @@ -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 = ` + + + ` + const cyan = picker.querySelector('[value=cyan]')! + const orange = picker.querySelector('[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 = ` + ` + const darkCyan = darkPicker.querySelector('[value="dark-cyan"]')! + + const lightPicker = document.createElement('div') + lightPicker.innerHTML = ` + ` + const lightCyan = lightPicker.querySelector('[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') }) diff --git a/resources/lang/en/admin.yml b/resources/lang/en/admin.yml index 755be9b8..e6ca40fd 100644 --- a/resources/lang/en/admin.yml +++ b/resources/lang/en/admin.yml @@ -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 diff --git a/resources/misc/changelogs/en/5.0.0.md b/resources/misc/changelogs/en/5.0.0.md index e05a1bdf..5fdfef73 100644 --- a/resources/misc/changelogs/en/5.0.0.md +++ b/resources/misc/changelogs/en/5.0.0.md @@ -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 diff --git a/resources/misc/changelogs/zh_CN/5.0.0.md b/resources/misc/changelogs/zh_CN/5.0.0.md index 285d6d06..abdf91b8 100644 --- a/resources/misc/changelogs/zh_CN/5.0.0.md +++ b/resources/misc/changelogs/zh_CN/5.0.0.md @@ -10,6 +10,7 @@ - 新增「运行状态」页面 - 支持自定义 UI 文本 - 西班牙语支持(感谢 [@poopingpenis](https://github.com/poopingpenis)) +- 全新的站点配色设置 ## 调整 diff --git a/resources/views/admin/customize.twig b/resources/views/admin/customize.twig index e73cbb1b..aa366a7c 100644 --- a/resources/views/admin/customize.twig +++ b/resources/views/admin/customize.twig @@ -12,45 +12,46 @@ {{ trans('admin.customize.change-color.title') }} -
- - - {% for color in colors %} - - - - - - - - +
+
-
{{ trans("admin.customize.colors.#{color}") }} - -
{{ trans("admin.customize.colors.#{color}-light") }} - -
+
+ + +