mirror of
https://github.com/koel/koel
synced 2024-11-10 06:34:14 +00:00
Better error handling for settings saving
This commit is contained in:
parent
73b5c89a85
commit
3aa7cb5ec4
11 changed files with 131 additions and 25 deletions
|
@ -22,7 +22,7 @@ class SettingRequest extends Request
|
|||
public function rules()
|
||||
{
|
||||
return [
|
||||
'media_path' => 'string|required|valid_path',
|
||||
'media_path' => 'string|required|path.valid',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ class AppServiceProvider extends ServiceProvider
|
|||
public function boot()
|
||||
{
|
||||
// Add some custom validation rules
|
||||
Validator::extend('valid_path', function ($attribute, $value, $parameters, $validator) {
|
||||
Validator::extend('path.valid', function ($attribute, $value, $parameters, $validator) {
|
||||
return is_dir($value) && is_readable($value);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<site-header></site-header>
|
||||
<main-wrapper></main-wrapper>
|
||||
<site-footer></site-footer>
|
||||
<overlay v-show="loading"></overlay>
|
||||
<overlay :state.sync="overlayState"></overlay>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -36,13 +36,19 @@
|
|||
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
prefs: preferenceStore.state,
|
||||
|
||||
overlayState: {
|
||||
showing: true,
|
||||
dismissable: false,
|
||||
type: 'loading',
|
||||
message: '',
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
ready() {
|
||||
this.toggleOverlay();
|
||||
this.showOverlay();
|
||||
|
||||
// Make the most important HTTP request to get all necessary data from the server.
|
||||
// Afterwards, init all mandatory stores and services.
|
||||
|
@ -50,8 +56,7 @@
|
|||
this.initStores();
|
||||
playback.init(this);
|
||||
|
||||
// Hide the overlaying loading screen.
|
||||
this.toggleOverlay();
|
||||
this.hideOverlay();
|
||||
|
||||
// Ask for user's notificatio permission.
|
||||
this.requestNotifPermission();
|
||||
|
@ -174,11 +179,32 @@
|
|||
},
|
||||
|
||||
/**
|
||||
* Show or hide the loading overlay.
|
||||
* Shows the overlay.
|
||||
*
|
||||
* @param {String} message The message to display.
|
||||
* @param {String} type (loading|success|info|warning|error)
|
||||
* @param {Boolean} dismissable Whether to show the Close button
|
||||
*/
|
||||
toggleOverlay() {
|
||||
this.loading = !this.loading;
|
||||
}
|
||||
showOverlay(message = 'Just a little patience…', type = 'loading', dismissable = false) {
|
||||
this.overlayState.message = message;
|
||||
this.overlayState.type = type;
|
||||
this.overlayState.dismissable = dismissable;
|
||||
this.overlayState.showing = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Hides the overlay.
|
||||
*/
|
||||
hideOverlay() {
|
||||
this.overlayState.showing = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Shows the close button, allowing the user to close the overlay.
|
||||
*/
|
||||
setOverlayDimissable() {
|
||||
this.overlayState.dismissable = true;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
|
||||
<script>
|
||||
import settingStore from '../../../stores/setting';
|
||||
import utils from '../../../services/utils';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
|
@ -39,7 +40,7 @@
|
|||
* Save the settings.
|
||||
*/
|
||||
save() {
|
||||
this.$root.toggleOverlay();
|
||||
this.$root.showOverlay();
|
||||
|
||||
settingStore.update(() => {
|
||||
// Data changed.
|
||||
|
@ -48,6 +49,12 @@
|
|||
// We need refresh the page.
|
||||
// Goodbye.
|
||||
document.location.reload();
|
||||
}, (error, status) => {
|
||||
if (status === 422) {
|
||||
error = utils.parseValidationError(error)[0];
|
||||
}
|
||||
|
||||
this.$root.showOverlay(`Error: ${error}`, 'error', true);
|
||||
});
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,7 +1,16 @@
|
|||
<template>
|
||||
<div id="overlay">
|
||||
<sound-bar></sound-bar>
|
||||
<span class="gnr">Just a little patience…</span>
|
||||
<div id="overlay" v-show="state.showing" class="{{ state.type }}">
|
||||
<div class="display">
|
||||
<sound-bar v-show="state.type === 'loading'"></sound-bar>
|
||||
<i class="fa fa-exclamation-circle" v-show="state.type === 'error'"></i>
|
||||
<i class="fa fa-exclamation-triangle" v-show="state.type === 'warning'"></i>
|
||||
<i class="fa fa-info-circle" v-show="state.type === 'info'"></i>
|
||||
<i class="fa fa-check-circle" v-show="state.type === 'success'"></i>
|
||||
|
||||
<span>{{{ state.message }}}</span>
|
||||
</div>
|
||||
|
||||
<button v-show="state.dismissable" @click.prevent="state.showing = false">Close</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -9,6 +18,7 @@
|
|||
import soundBar from './sound-bar.vue';
|
||||
|
||||
export default {
|
||||
props: ['state'],
|
||||
components: { soundBar },
|
||||
};
|
||||
</script>
|
||||
|
@ -27,9 +37,39 @@
|
|||
background-color: rgba(0, 0, 0, 1);
|
||||
|
||||
@include vertical-center();
|
||||
flex-direction: column;
|
||||
|
||||
.gnr {
|
||||
opacity: .7;
|
||||
.display {
|
||||
@include vertical-center();
|
||||
|
||||
i {
|
||||
margin-right: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
font-size: 12px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
&.error {
|
||||
color: $colorRed;
|
||||
}
|
||||
|
||||
&.success {
|
||||
color: $colorGreen;
|
||||
}
|
||||
|
||||
&.info {
|
||||
color: $colorBlue;
|
||||
}
|
||||
|
||||
&.loading {
|
||||
color: $color2ndText;
|
||||
}
|
||||
|
||||
&.warning {
|
||||
color: $colorOrange;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -10,13 +10,13 @@ import { extend } from 'lodash';
|
|||
*/
|
||||
export default {
|
||||
request(method, url, data, cb = null, options = {}) {
|
||||
options = extend(options, {
|
||||
options = extend({
|
||||
error: (data, status, request) => {
|
||||
if (status === 401) {
|
||||
document.location.href = "/login";
|
||||
}
|
||||
},
|
||||
});
|
||||
}, options);
|
||||
|
||||
switch (method) {
|
||||
case 'get':
|
||||
|
|
|
@ -26,4 +26,15 @@ export default {
|
|||
|
||||
return (h === '00' ? '' : h + ':') + i + ':' + s;
|
||||
},
|
||||
|
||||
/**
|
||||
* Parse the validation error from the server into a flattened array of messages.
|
||||
*
|
||||
* @param {Object} error The error object in JSON format.
|
||||
*
|
||||
* @return {Array}
|
||||
*/
|
||||
parseValidationError(error) {
|
||||
return Object.keys(error).reduce((messages, field) => messages.concat(error[field]), []);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -17,11 +17,11 @@ export default {
|
|||
return this.state.settings;
|
||||
},
|
||||
|
||||
update(cb = null) {
|
||||
update(cb = null, error = null) {
|
||||
http.post('settings', this.all(), msg => {
|
||||
if (cb) {
|
||||
cb();
|
||||
}
|
||||
});
|
||||
}, { error });
|
||||
},
|
||||
};
|
||||
|
|
|
@ -12,4 +12,23 @@ describe('services/utils', () => {
|
|||
utils.secondsToHis(314).should.equal('05:14');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#parseValidationError', () => {
|
||||
it('correctly parses single-level validation error', () => {
|
||||
let error = {
|
||||
err_1: ['Foo'],
|
||||
};
|
||||
|
||||
utils.parseValidationError(error).should.eql(['Foo']);
|
||||
});
|
||||
|
||||
it('correctly parses multi-level validation error', () => {
|
||||
let error = {
|
||||
err_1: ['Foo', 'Bar'],
|
||||
err_2: ['Baz', 'Qux'],
|
||||
};
|
||||
|
||||
utils.parseValidationError(error).should.eql(['Foo', 'Bar', 'Baz', 'Qux']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -10,6 +10,7 @@ $colorHeart: #bf2043;
|
|||
$colorGreen: #56a052;
|
||||
$colorBlue: #4c769a;
|
||||
$colorRed: #c34848;
|
||||
$colorOrange: #ff7d2e;
|
||||
|
||||
$colorSidebarBgr: #212121;
|
||||
$colorExtraBgr: #212121;
|
||||
|
@ -19,7 +20,7 @@ $colorPlayerControlsBgr: transparent;
|
|||
$colorLink: #aaa;
|
||||
$colorLinkHovered: #fff;
|
||||
|
||||
$colorHighlight: #ff7d2e;
|
||||
$colorHighlight: $colorOrange;
|
||||
|
||||
$fontFamily: 'Roboto', sans-serif;
|
||||
$fontSize: 13px;
|
||||
|
|
|
@ -77,6 +77,10 @@ return [
|
|||
'unique' => 'The :attribute has already been taken.',
|
||||
'url' => 'The :attribute format is invalid.',
|
||||
|
||||
'path' => [
|
||||
'valid' => 'The :attribute is not a valid or readable path.',
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Custom Validation Language Lines
|
||||
|
@ -89,9 +93,7 @@ return [
|
|||
*/
|
||||
|
||||
'custom' => [
|
||||
'attribute-name' => [
|
||||
'rule-name' => 'custom-message',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|
|
Loading…
Reference in a new issue