mirror of
https://github.com/koel/koel
synced 2024-11-10 06:34:14 +00:00
migration: settings screen
This commit is contained in:
parent
43be702dfb
commit
f0f1d975c9
10 changed files with 32 additions and 40 deletions
|
@ -832,7 +832,7 @@ paths:
|
|||
security:
|
||||
- Bearer Token: []
|
||||
/api/settings:
|
||||
post:
|
||||
put:
|
||||
summary: Save the application settings
|
||||
tags:
|
||||
- settings
|
||||
|
|
|
@ -15,8 +15,7 @@ class SettingController extends Controller
|
|||
$this->mediaSyncService = $mediaSyncService;
|
||||
}
|
||||
|
||||
// @TODO: This should be a PUT request
|
||||
public function store(SettingRequest $request)
|
||||
public function update(SettingRequest $request)
|
||||
{
|
||||
Setting::set('media_path', rtrim(trim($request->media_path), '/'));
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import Component from '@/components/screens/settings.vue'
|
||||
import Component from '@/components/screens/SettingsScreen.vue'
|
||||
import { sharedStore, settingStore } from '@/stores'
|
||||
import { alerts } from '@/utils'
|
||||
import { mock } from '@/__tests__/__helpers__'
|
||||
|
|
|
@ -46,7 +46,7 @@ const UserListScreen = defineAsyncComponent(() => import('@/components/screens/u
|
|||
const AlbumArtOverlay = defineAsyncComponent(() => import('@/components/ui/album-art-overlay.vue'))
|
||||
const AlbumScreen = defineAsyncComponent(() => import('@/components/screens/AlbumScreen.vue'))
|
||||
const ArtistScreen = defineAsyncComponent(() => import('@/components/screens/ArtistScreen.vue'))
|
||||
const SettingsScreen = defineAsyncComponent(() => import('@/components/screens/settings.vue'))
|
||||
const SettingsScreen = defineAsyncComponent(() => import('@/components/screens/SettingsScreen.vue'))
|
||||
const ProfileScreen = defineAsyncComponent(() => import('@/components/screens/profile.vue'))
|
||||
const YoutubeScreen = defineAsyncComponent(() => import('@/components/screens/youtube.vue'))
|
||||
const UploadScreen = defineAsyncComponent(() => import('@/components/screens/UploadScreen.vue'))
|
||||
|
|
|
@ -2,22 +2,22 @@
|
|||
<section id="settingsWrapper">
|
||||
<ScreenHeader>Settings</ScreenHeader>
|
||||
|
||||
<form @submit.prevent="confirmThenSave" class="main-scroll-wrap">
|
||||
<form class="main-scroll-wrap" @submit.prevent="confirmThenSave">
|
||||
<div class="form-row">
|
||||
<label for="inputSettingsPath">Media Path</label>
|
||||
|
||||
<p class="help" id="mediaPathHelp">
|
||||
<p id="mediaPathHelp" class="help">
|
||||
The <em>absolute</em> path to the server directory containing your media.
|
||||
Koel will scan this directory for songs and extract any available information.<br>
|
||||
Scanning may take a while, especially if you have a lot of songs, so be patient.
|
||||
</p>
|
||||
|
||||
<input
|
||||
aria-describedby="mediaPathHelp"
|
||||
id="inputSettingsPath"
|
||||
type="text"
|
||||
v-model="state.media_path"
|
||||
v-model="mediaPath"
|
||||
aria-describedby="mediaPathHelp"
|
||||
name="media_path"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
|
||||
|
@ -29,39 +29,40 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, defineAsyncComponent, reactive } from 'vue'
|
||||
import { settingStore, sharedStore } from '@/stores'
|
||||
import { computed, defineAsyncComponent, ref } from 'vue'
|
||||
import { settingStore } from '@/stores'
|
||||
import { alerts, forceReloadWindow, hideOverlay, parseValidationError, showOverlay } from '@/utils'
|
||||
import router from '@/router'
|
||||
|
||||
const ScreenHeader = defineAsyncComponent(() => import('@/components/ui/ScreenHeader.vue'))
|
||||
const Btn = defineAsyncComponent(() => import('@/components/ui/Btn.vue'))
|
||||
|
||||
const state = settingStore.state
|
||||
const sharedState = reactive(sharedStore.state)
|
||||
const mediaPath = ref(settingStore.state.media_path)
|
||||
const originalMediaPath = mediaPath.value
|
||||
|
||||
const shouldWarn = computed(() => {
|
||||
// Warn the user if the media path is not empty and about to change.
|
||||
if (!sharedState.originalMediaPath || !state.media_path) {
|
||||
if (!originalMediaPath || !mediaPath.value) {
|
||||
return false
|
||||
}
|
||||
|
||||
return sharedState.originalMediaPath !== state.media_path.trim()
|
||||
return originalMediaPath !== mediaPath.value.trim()
|
||||
})
|
||||
|
||||
const save = async () => {
|
||||
showOverlay()
|
||||
|
||||
try {
|
||||
await settingStore.update()
|
||||
await settingStore.update({ media_path: mediaPath.value })
|
||||
alerts.success('Settings saved.')
|
||||
// Make sure we're back to home first.
|
||||
router.go('home')
|
||||
forceReloadWindow()
|
||||
} catch (err: any) {
|
||||
hideOverlay()
|
||||
|
||||
const msg = err.response.status === 422 ? parseValidationError(err.response.data)[0] : 'Unknown error.'
|
||||
alerts.error(msg)
|
||||
} finally {
|
||||
hideOverlay()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,7 @@
|
|||
import { reactive } from 'vue'
|
||||
import { http } from '@/services'
|
||||
import { alerts } from '@/utils'
|
||||
import stub from '@/stubs/settings'
|
||||
|
||||
export const settingStore = {
|
||||
stub,
|
||||
|
||||
state: reactive<Settings>({
|
||||
media_path: ''
|
||||
}),
|
||||
|
@ -18,8 +14,8 @@ export const settingStore = {
|
|||
return this.state
|
||||
},
|
||||
|
||||
async update (): Promise<void> {
|
||||
await http.post('settings', this.all)
|
||||
alerts.success('Settings saved.')
|
||||
async update (settings: Settings) {
|
||||
await http.put('settings', settings)
|
||||
Object.assign(this.state, settings)
|
||||
}
|
||||
}
|
||||
|
|
7
resources/assets/js/types.d.ts
vendored
7
resources/assets/js/types.d.ts
vendored
|
@ -282,7 +282,7 @@ interface User {
|
|||
avatar: string
|
||||
}
|
||||
|
||||
interface Settings extends Object {
|
||||
interface Settings {
|
||||
media_path?: string
|
||||
}
|
||||
|
||||
|
@ -306,11 +306,6 @@ interface SongListMeta {
|
|||
declare module 'koel/types/ui' {
|
||||
import { ComponentInternalInstance } from 'vue'
|
||||
|
||||
export type BaseContextMenu = ComponentInternalInstance & {
|
||||
open (y: number, x: number): void
|
||||
close (): void
|
||||
}
|
||||
|
||||
export type BasePlaylistMenu = ComponentInternalInstance & {
|
||||
open (top: number, left: number): void
|
||||
close (): void
|
||||
|
|
|
@ -7,23 +7,23 @@ const encodeEntities = (str: string) => str.replace(/&/g, '&')
|
|||
.replace(/>/g, '>')
|
||||
|
||||
export const alerts = {
|
||||
alert: (msg: string): void => alertify.alert(encodeEntities(msg)),
|
||||
alert: (msg: string) => alertify.alert(encodeEntities(msg)),
|
||||
|
||||
confirm: (msg: string, okFunc: TAnyFunction, cancelFunc?: TAnyFunction): void => {
|
||||
confirm: (msg: string, okFunc: TAnyFunction, cancelFunc?: TAnyFunction) => {
|
||||
alertify.confirm(msg, okFunc, cancelFunc)
|
||||
},
|
||||
|
||||
log: (msg: string, type: logType = 'log', cb?: TAnyFunction): void => {
|
||||
log: (msg: string, type: logType = 'log', cb?: TAnyFunction) => {
|
||||
alertify.logPosition('top right')
|
||||
alertify.closeLogOnClick(true)
|
||||
alertify[type](encodeEntities(msg), cb)
|
||||
},
|
||||
|
||||
success (msg: string, cb?: TAnyFunction): void {
|
||||
success (msg: string, cb?: TAnyFunction) {
|
||||
this.log(msg, 'success', cb)
|
||||
},
|
||||
|
||||
error (msg: string, cb?: TAnyFunction): void {
|
||||
error (msg: string, cb?: TAnyFunction) {
|
||||
this.log(msg, 'error', cb)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@ Route::middleware('auth')->group(static function (): void {
|
|||
|
||||
Route::get('data', [DataController::class, 'index']);
|
||||
|
||||
Route::post('settings', [SettingController::class, 'store']);
|
||||
Route::put('settings', [SettingController::class, 'update']);
|
||||
|
||||
Route::post('{song}/scrobble', [ScrobbleController::class, 'store']);
|
||||
Route::put('songs', [SongController::class, 'update']);
|
||||
|
|
|
@ -5,10 +5,11 @@ namespace Tests\Feature;
|
|||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use App\Services\MediaSyncService;
|
||||
use Mockery\MockInterface;
|
||||
|
||||
class SettingTest extends TestCase
|
||||
{
|
||||
private $mediaSyncService;
|
||||
private MockInterface $mediaSyncService;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
|
@ -22,7 +23,7 @@ class SettingTest extends TestCase
|
|||
$this->mediaSyncService->shouldReceive('sync')->once();
|
||||
|
||||
$user = User::factory()->admin()->create();
|
||||
$this->postAsUser('/api/settings', ['media_path' => __DIR__], $user);
|
||||
$this->putAsUser('/api/settings', ['media_path' => __DIR__], $user);
|
||||
|
||||
self::assertEquals(__DIR__, Setting::get('media_path'));
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue