mirror of
https://github.com/koel/koel
synced 2024-11-14 00:17:13 +00:00
added support for song track numbers as well as subsorting song lists
with a second sort key. track numbers are also editable via the song edit modal interface.
This commit is contained in:
parent
8c7dae6a32
commit
940cd1a914
7 changed files with 57 additions and 7 deletions
|
@ -133,10 +133,12 @@ class Song extends Model
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we're updating only one song, take into account the title and lyrics.
|
// If we're updating only one song, take into account the title and lyrics
|
||||||
|
// and track number.
|
||||||
if (count($ids) === 1) {
|
if (count($ids) === 1) {
|
||||||
$song->title = trim($data['title']) ?: $song->title;
|
$song->title = trim($data['title']) ?: $song->title;
|
||||||
$song->lyrics = trim($data['lyrics']);
|
$song->lyrics = trim($data['lyrics']);
|
||||||
|
$song->track = trim($data['track']);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If "newArtist" is provided, we'll see if such an artist name is found in our database.
|
// If "newArtist" is provided, we'll see if such an artist name is found in our database.
|
||||||
|
|
|
@ -229,11 +229,19 @@ class Media
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$track = array_get($info, 'comments.track_number', [0])[0];
|
||||||
|
if (preg_match('#(\d+)/#', $track, $matches)) {
|
||||||
|
$track = $matches[1];
|
||||||
|
} elseif ((int) $track) {
|
||||||
|
$track = (int) $track;
|
||||||
|
}
|
||||||
|
|
||||||
$props = [
|
$props = [
|
||||||
'artist' => '',
|
'artist' => '',
|
||||||
'album' => '',
|
'album' => '',
|
||||||
'title' => '',
|
'title' => '',
|
||||||
'length' => $info['playtime_seconds'],
|
'length' => $info['playtime_seconds'],
|
||||||
|
'track' => $track,
|
||||||
'lyrics' => '',
|
'lyrics' => '',
|
||||||
'cover' => array_get($info, 'comments.picture', [null])[0],
|
'cover' => array_get($info, 'comments.picture', [null])[0],
|
||||||
'path' => $file->getPathname(),
|
'path' => $file->getPathname(),
|
||||||
|
|
|
@ -35,6 +35,7 @@ $factory->define(App\Models\Song::class, function ($faker) {
|
||||||
return [
|
return [
|
||||||
'title' => $faker->sentence,
|
'title' => $faker->sentence,
|
||||||
'length' => $faker->randomFloat(2, 10, 500),
|
'length' => $faker->randomFloat(2, 10, 500),
|
||||||
|
'track' => $faker->randomNumber(),
|
||||||
'lyrics' => $faker->paragraph(),
|
'lyrics' => $faker->paragraph(),
|
||||||
'path' => '/tmp/'.uniqid().'.mp3',
|
'path' => '/tmp/'.uniqid().'.mp3',
|
||||||
'mtime' => time(),
|
'mtime' => time(),
|
||||||
|
|
|
@ -17,6 +17,7 @@ class CreateSongsTable extends Migration
|
||||||
$table->integer('album_id')->unsigned();
|
$table->integer('album_id')->unsigned();
|
||||||
$table->string('title');
|
$table->string('title');
|
||||||
$table->float('length');
|
$table->float('length');
|
||||||
|
$table->integer('track');
|
||||||
$table->text('lyrics');
|
$table->text('lyrics');
|
||||||
$table->text('path');
|
$table->text('path');
|
||||||
$table->integer('mtime');
|
$table->integer('mtime');
|
||||||
|
|
|
@ -280,7 +280,7 @@
|
||||||
*
|
*
|
||||||
* @source https://github.com/vuejs/vue/blob/dev/src/filters/array-filters.js
|
* @source https://github.com/vuejs/vue/blob/dev/src/filters/array-filters.js
|
||||||
*/
|
*/
|
||||||
Vue.filter('caseInsensitiveOrderBy', (arr, sortKey, reverse) => {
|
Vue.filter('caseInsensitiveOrderBy', (arr, sortKey, reverse, subSortKey) => {
|
||||||
if (!sortKey) {
|
if (!sortKey) {
|
||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
@ -289,17 +289,45 @@
|
||||||
|
|
||||||
// sort on a copy to avoid mutating original array
|
// sort on a copy to avoid mutating original array
|
||||||
return arr.slice().sort((a, b) => {
|
return arr.slice().sort((a, b) => {
|
||||||
|
let aSub = subSortKey ? Vue.util.isObject(a) ? Vue.parsers.path.getPath(a, subSortKey) : 0 : 0;
|
||||||
|
let bSub = subSortKey ? Vue.util.isObject(b) ? Vue.parsers.path.getPath(b, subSortKey) : 0 : 0;
|
||||||
a = Vue.util.isObject(a) ? Vue.parsers.path.getPath(a, sortKey) : a;
|
a = Vue.util.isObject(a) ? Vue.parsers.path.getPath(a, sortKey) : a;
|
||||||
b = Vue.util.isObject(b) ? Vue.parsers.path.getPath(b, sortKey) : b;
|
b = Vue.util.isObject(b) ? Vue.parsers.path.getPath(b, sortKey) : b;
|
||||||
|
|
||||||
if (_.isNumber(a) && _.isNumber(b)) {
|
if (_.isNumber(a) && _.isNumber(b)) {
|
||||||
return a === b ? 0 : a > b ? order : -order;
|
if (a === b) {
|
||||||
|
if (_.isNumber(aSub) && _.isNumber(bSub)) {
|
||||||
|
return aSub === bSub ? 0 : aSub > bSub ? order : -order;
|
||||||
|
} else if (aSub !== undefined && bSub !== undefined) {
|
||||||
|
aSub = aSub.toLowerCase();
|
||||||
|
bSub = bSub.toLowerCase();
|
||||||
|
|
||||||
|
return aSub === bSub ? 0 : aSub > bSub ? order : -order;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return a > b ? order : -order;
|
||||||
}
|
}
|
||||||
|
|
||||||
a = a === undefined ? a : a.toLowerCase();
|
a = a === undefined ? a : a.toLowerCase();
|
||||||
b = b === undefined ? b : b.toLowerCase();
|
b = b === undefined ? b : b.toLowerCase();
|
||||||
|
|
||||||
return a === b ? 0 : a > b ? order : -order;
|
if (a === b) {
|
||||||
|
if (_.isNumber(aSub) && _.isNumber(bSub)) {
|
||||||
|
return aSub === bSub ? 0 : aSub > bSub ? order : -order;
|
||||||
|
} else if (aSub !== undefined && bSub !== undefined) {
|
||||||
|
aSub = aSub.toLowerCase();
|
||||||
|
bSub = bSub.toLowerCase();
|
||||||
|
|
||||||
|
return aSub === bSub ? 0 : aSub > bSub ? order : -order;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return a > b ? order : -order;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,10 @@
|
||||||
:options="albumTypeaheadOptions"
|
:options="albumTypeaheadOptions"
|
||||||
:value.sync="formData.albumName"></typeahead>
|
:value.sync="formData.albumName"></typeahead>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-row" v-show="editSingle">
|
||||||
|
<label>Track</label>
|
||||||
|
<input type="text" v-model="formData.track">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-show="currentView === 'lyrics' && editSingle">
|
<div v-show="currentView === 'lyrics' && editSingle">
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
|
@ -106,6 +110,7 @@
|
||||||
albumName: '',
|
albumName: '',
|
||||||
artistName: '',
|
artistName: '',
|
||||||
lyrics: '',
|
lyrics: '',
|
||||||
|
track: '',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -197,9 +202,11 @@
|
||||||
songStore.getInfo(this.songs[0], () => {
|
songStore.getInfo(this.songs[0], () => {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
this.formData.lyrics = utils.br2nl(this.songs[0].lyrics);
|
this.formData.lyrics = utils.br2nl(this.songs[0].lyrics);
|
||||||
|
this.formData.track = this.songs[0].track;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.formData.lyrics = utils.br2nl(this.songs[0].lyrics);
|
this.formData.lyrics = utils.br2nl(this.songs[0].lyrics);
|
||||||
|
this.formData.track = this.songs[0].track;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.formData.albumName = this.inSameAlbum ? this.songs[0].album.name : '';
|
this.formData.albumName = this.inSameAlbum ? this.songs[0].album.name : '';
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
<i class="fa fa-angle-down" v-show="sortKey === 'album.artist.name' && order > 0"></i>
|
<i class="fa fa-angle-down" v-show="sortKey === 'album.artist.name' && order > 0"></i>
|
||||||
<i class="fa fa-angle-up" v-show="sortKey === 'album.artist.name' && order < 0"></i>
|
<i class="fa fa-angle-up" v-show="sortKey === 'album.artist.name' && order < 0"></i>
|
||||||
</th>
|
</th>
|
||||||
<th @click="sort('album.name')">Album
|
<th @click="sort('album.name', 'track')">Album
|
||||||
<i class="fa fa-angle-down" v-show="sortKey === 'album.name' && order > 0"></i>
|
<i class="fa fa-angle-down" v-show="sortKey === 'album.name' && order > 0"></i>
|
||||||
<i class="fa fa-angle-up" v-show="sortKey === 'album.name' && order < 0"></i>
|
<i class="fa fa-angle-up" v-show="sortKey === 'album.name' && order < 0"></i>
|
||||||
</th>
|
</th>
|
||||||
|
@ -33,10 +33,11 @@
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr
|
<tr
|
||||||
v-for="item in items
|
v-for="item in items
|
||||||
| caseInsensitiveOrderBy sortKey order
|
| caseInsensitiveOrderBy sortKey order subSortKey
|
||||||
| filterSongBy q
|
| filterSongBy q
|
||||||
| limitBy numOfItems"
|
| limitBy numOfItems"
|
||||||
is="song-item"
|
is="song-item"
|
||||||
|
data-track="{{ item.track }}"
|
||||||
data-song-id="{{ item.id }}"
|
data-song-id="{{ item.id }}"
|
||||||
track-by="id"
|
track-by="id"
|
||||||
:song="item"
|
:song="item"
|
||||||
|
@ -81,6 +82,7 @@
|
||||||
lastSelectedRow: null,
|
lastSelectedRow: null,
|
||||||
q: '', // The filter query
|
q: '', // The filter query
|
||||||
sortKey: this.type === 'top-songs' ? 'playCount' : '',
|
sortKey: this.type === 'top-songs' ? 'playCount' : '',
|
||||||
|
subSortKey: 0,
|
||||||
order: this.type === 'top-songs' ? -1 : 1,
|
order: this.type === 'top-songs' ? -1 : 1,
|
||||||
componentCache: {},
|
componentCache: {},
|
||||||
};
|
};
|
||||||
|
@ -109,12 +111,13 @@
|
||||||
*
|
*
|
||||||
* @param {String} key The sort key. Can be 'title', 'album', 'artist', or 'fmtLength'
|
* @param {String} key The sort key. Can be 'title', 'album', 'artist', or 'fmtLength'
|
||||||
*/
|
*/
|
||||||
sort(key) {
|
sort(key, subSortKey) {
|
||||||
if (this.sortable === false) {
|
if (this.sortable === false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.sortKey = key;
|
this.sortKey = key;
|
||||||
|
this.subSortKey = subSortKey ? subSortKey : 0;
|
||||||
this.order = 0 - this.order;
|
this.order = 0 - this.order;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue