support toggling dark mode

This commit is contained in:
Pig Fang 2021-06-06 18:07:08 +08:00
parent 9c54427b08
commit 073da66623
No known key found for this signature in database
GPG Key ID: A8198F548DADA9E2
10 changed files with 95 additions and 0 deletions

View File

@ -347,4 +347,14 @@ class UserController extends Controller
return json(trans('skinlib.non-existent'), 1);
}
}
public function toggleDarkMode()
{
/** @var User */
$user = auth()->user();
$user->is_dark_mode = !$user->is_dark_mode;
$user->save();
return response()->noContent();
}
}

View File

@ -69,6 +69,12 @@ class ViewServiceProvider extends ServiceProvider
View::composer('shared.foot', Composers\FootComposer::class);
View::composer('shared.dark-mode', function ($view) {
$view->with([
'dark_mode' => (bool) optional(auth()->user())->is_dark_mode,
]);
});
View::composer('assets.*', function ($view) {
$view->with('cdn_base', option('cdn_address', ''));
});

View File

@ -0,0 +1,27 @@
import React, { useState } from 'react'
import * as fetch from '@/scripts/net'
interface Props {
initMode: boolean
}
const DarkModeButton: React.FC<Props> = ({ initMode }) => {
const [darkMode, setDarkMode] = useState(initMode)
const icon = darkMode ? 'moon' : 'sun'
const handleClick = async () => {
setDarkMode((value) => !value)
await fetch.put('/user/dark-mode')
document.body.classList.toggle('dark-mode')
}
return (
<a className="nav-link" href="#" role="button" onClick={handleClick}>
<i className={`fas fa-${icon}`}></i>
</a>
)
}
export default DarkModeButton

View File

@ -6,6 +6,7 @@ import './event'
import './notification'
import './emailVerification'
import './logout'
import './darkMode'
window.addEventListener('load', () => {
$('[data-toggle="tooltip"]').tooltip()

View File

@ -0,0 +1,9 @@
import * as React from 'react'
import * as ReactDOM from 'react-dom'
import DarkModeButton from '@/components/DarkModeButton'
const el = document.querySelector('#toggle-dark-mode')
if (el) {
const initMode = document.body.classList.contains('dark-mode')
ReactDOM.render(<DarkModeButton initMode={initMode} />, el)
}

View File

@ -0,0 +1,22 @@
import React from 'react'
import { render, fireEvent, waitFor } from '@testing-library/react'
import * as fetch from '@/scripts/net'
import DarkModeButton from '@/components/DarkModeButton'
jest.mock('@/scripts/net')
test('click to toggle', async () => {
const { getByRole } = render(<DarkModeButton initMode={false} />)
const button = getByRole('button')
fireEvent.click(button)
await waitFor(() => expect(fetch.put).toBeCalledWith('/user/dark-mode'))
})
test('default is dark', async () => {
const { getByRole } = render(<DarkModeButton initMode={true} />)
const button = getByRole('button')
fireEvent.click(button)
await waitFor(() => expect(fetch.put).toBeCalledWith('/user/dark-mode'))
})

View File

@ -0,0 +1,7 @@
{% if auth_check() %}
<li class="nav-item" id="toggle-dark-mode">
<a class="nav-link" href="#" role="button">
<i class="fas fa-{{ dark_mode ? 'moon' : 'sun' }}"></i>
</a>
</li>
{% endif %}

View File

@ -8,6 +8,7 @@
</ul>
<ul class="navbar-nav ml-auto">
{{ include('shared.dark-mode') }}
{{ include('shared.notifications') }}
{{ include('shared.languages') }}
{{ include('shared.user-menu') }}

View File

@ -62,6 +62,8 @@ Route::prefix('user')
Route::post('email-verification', 'UserController@sendVerificationEmail');
Route::put('dark-mode', 'UserController@toggleDarkMode');
Route::prefix('player')
->name('player.')
->middleware('verified')

View File

@ -597,4 +597,14 @@ class UserControllerTest extends TestCase
->postJson('/user/profile/avatar', ['tid' => $steve->tid])
->assertJson(['code' => 1, 'message' => 'rejected']);
}
public function testToggleDarkMode()
{
$user = User::factory()->create();
$this->actingAs($user)->putJson('/user/dark-mode')->assertNoContent();
$this->assertTrue($user->fresh()->is_dark_mode);
$this->putJson('/user/dark-mode')->assertNoContent();
$this->assertFalse($user->fresh()->is_dark_mode);
}
}