dsh-music-panel 0.3.2 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/client.js +97 -55
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.4
|
|
4
|
+
|
|
5
|
+
### 变更
|
|
6
|
+
|
|
7
|
+
- 随机播放时,播放队列(播放列表)按实际播放顺序(乱序)展示,从上往下播放;切回顺序播放恢复原顺序
|
|
8
|
+
- 切换到随机播放时重新洗牌
|
|
9
|
+
- 双击歌曲 / 播放全部时,入队范围是当前歌单的完整曲目(忽略搜索过滤),从双击的歌曲开始
|
|
10
|
+
|
|
11
|
+
## 0.3.3
|
|
12
|
+
|
|
13
|
+
### 变更
|
|
14
|
+
|
|
15
|
+
- 移除主面板顶部「清空列表」按钮
|
|
16
|
+
- 正在播放行:品牌色高亮、3px 左侧竖条、标题品牌色加粗、播放均衡器动画(暂停时静止),封面遮罩常显暂停/播放图标
|
|
17
|
+
|
|
3
18
|
## 0.3.2
|
|
4
19
|
|
|
5
20
|
### 变更
|
package/client.js
CHANGED
|
@@ -1609,6 +1609,10 @@ window.__ModuleLoader__.load({
|
|
|
1609
1609
|
getTime: function () {
|
|
1610
1610
|
return { currentTime: state.currentTime, duration: state.duration, buffered: state.buffered }
|
|
1611
1611
|
},
|
|
1612
|
+
/** Queue indices in the current playback order (random mode). */
|
|
1613
|
+
getPlayOrder: function () {
|
|
1614
|
+
return randomOrder.slice()
|
|
1615
|
+
},
|
|
1612
1616
|
/** Restore the last queue/position once (called by the resident host). */
|
|
1613
1617
|
tryRestore: tryRestore,
|
|
1614
1618
|
/** Flush statistics + resume state when the page is hidden/closed. */
|
|
@@ -1823,8 +1827,14 @@ window.__ModuleLoader__.load({
|
|
|
1823
1827
|
emit()
|
|
1824
1828
|
},
|
|
1825
1829
|
setOrder: function (value) {
|
|
1826
|
-
|
|
1827
|
-
|
|
1830
|
+
var next = value === 'random' ? 'random' : 'list'
|
|
1831
|
+
if (next === 'random' && state.queue.length > 1) {
|
|
1832
|
+
// Fresh shuffle whenever random is selected, so the queue panel's
|
|
1833
|
+
// displayed order is the actual playback order.
|
|
1834
|
+
randomOrder = randomOrderOf(state.queue.length)
|
|
1835
|
+
}
|
|
1836
|
+
state = Object.assign({}, state, { order: next })
|
|
1837
|
+
savePrefs({ order: next })
|
|
1828
1838
|
emit()
|
|
1829
1839
|
},
|
|
1830
1840
|
clear: function () {
|
|
@@ -3068,6 +3078,18 @@ window.__ModuleLoader__.load({
|
|
|
3068
3078
|
var prefs = useRef(readPrefs()).current
|
|
3069
3079
|
var [snap, setSnap] = useState(player.getSnapshot())
|
|
3070
3080
|
var leaderNow = player.isLeader()
|
|
3081
|
+
var playOrder = player.getPlayOrder()
|
|
3082
|
+
// In random mode the queue panel lists songs in the order they will
|
|
3083
|
+
// actually play ("from top to bottom"), while edits still target the
|
|
3084
|
+
// original queue indices.
|
|
3085
|
+
var queueView =
|
|
3086
|
+
snap.order === 'random' && Array.isArray(playOrder) && playOrder.length === snap.queue.length
|
|
3087
|
+
? playOrder.map(function (originalIndex) {
|
|
3088
|
+
return { track: snap.queue[originalIndex], originalIndex: originalIndex }
|
|
3089
|
+
})
|
|
3090
|
+
: snap.queue.map(function (track, originalIndex) {
|
|
3091
|
+
return { track: track, originalIndex: originalIndex }
|
|
3092
|
+
})
|
|
3071
3093
|
var [config, setConfig] = useState(null)
|
|
3072
3094
|
var [tracks, setTracks] = useState([])
|
|
3073
3095
|
var [loading, setLoading] = useState(true)
|
|
@@ -3393,11 +3415,12 @@ window.__ModuleLoader__.load({
|
|
|
3393
3415
|
}
|
|
3394
3416
|
|
|
3395
3417
|
function activate(track) {
|
|
3396
|
-
var at =
|
|
3418
|
+
var at = sortedCollection.findIndex(function (item) {
|
|
3397
3419
|
return item.path === track.path
|
|
3398
3420
|
})
|
|
3399
3421
|
if (at < 0) return
|
|
3400
|
-
|
|
3422
|
+
// Queue the whole playlist (ignores the search filter), starting here.
|
|
3423
|
+
player.playList(sortedCollection, at)
|
|
3401
3424
|
}
|
|
3402
3425
|
|
|
3403
3426
|
function clearList() {
|
|
@@ -3521,6 +3544,39 @@ window.__ModuleLoader__.load({
|
|
|
3521
3544
|
[deleteConfirm],
|
|
3522
3545
|
)
|
|
3523
3546
|
|
|
3547
|
+
// Full collection in display order (search is a filter on top of it).
|
|
3548
|
+
// Double-clicking a song queues this whole playlist, not just the
|
|
3549
|
+
// filtered results, which is what desktop players do.
|
|
3550
|
+
var sortedCollection = useMemo(
|
|
3551
|
+
function () {
|
|
3552
|
+
// "Liked" is ordered by when it was liked (newest first) unless the
|
|
3553
|
+
// user picks another sort, matching NetEase/Spotify liked songs.
|
|
3554
|
+
if (view === 'favorites' && sortBy === 'title' && sortDir === 'asc') {
|
|
3555
|
+
return collectionTracks
|
|
3556
|
+
}
|
|
3557
|
+
var direction = sortDir === 'desc' ? -1 : 1
|
|
3558
|
+
var keyed = collectionTracks.map(function (track, position) {
|
|
3559
|
+
return { track: track, position: position }
|
|
3560
|
+
})
|
|
3561
|
+
keyed.sort(function (left, right) {
|
|
3562
|
+
var a = left.track
|
|
3563
|
+
var b = right.track
|
|
3564
|
+
var aValue = sortValueOf(a, sortBy)
|
|
3565
|
+
var bValue = sortValueOf(b, sortBy)
|
|
3566
|
+
var compared =
|
|
3567
|
+
typeof aValue === 'number' && typeof bValue === 'number'
|
|
3568
|
+
? aValue - bValue
|
|
3569
|
+
: String(aValue).localeCompare(String(bValue), LANG)
|
|
3570
|
+
if (compared === 0) compared = left.position - right.position
|
|
3571
|
+
return compared * direction
|
|
3572
|
+
})
|
|
3573
|
+
return keyed.map(function (entry) {
|
|
3574
|
+
return entry.track
|
|
3575
|
+
})
|
|
3576
|
+
},
|
|
3577
|
+
[collectionTracks, sortBy, sortDir, view],
|
|
3578
|
+
)
|
|
3579
|
+
|
|
3524
3580
|
var visible = useMemo(
|
|
3525
3581
|
function () {
|
|
3526
3582
|
var needles = query
|
|
@@ -3528,8 +3584,8 @@ window.__ModuleLoader__.load({
|
|
|
3528
3584
|
.toLowerCase()
|
|
3529
3585
|
.split(/\s+/)
|
|
3530
3586
|
.filter(Boolean)
|
|
3531
|
-
|
|
3532
|
-
|
|
3587
|
+
if (needles.length === 0) return sortedCollection
|
|
3588
|
+
return sortedCollection.filter(function (track) {
|
|
3533
3589
|
// Search across metadata, folder/path and pinyin (full + initials),
|
|
3534
3590
|
// the way desktop players let you type "zhoujielun" / "zjl".
|
|
3535
3591
|
var haystack = [
|
|
@@ -3550,32 +3606,8 @@ window.__ModuleLoader__.load({
|
|
|
3550
3606
|
return haystack.indexOf(needle) >= 0
|
|
3551
3607
|
})
|
|
3552
3608
|
})
|
|
3553
|
-
// "Liked" is ordered by when it was liked (newest first) unless the
|
|
3554
|
-
// user picks another sort, matching NetEase/Spotify liked songs.
|
|
3555
|
-
if (view === 'favorites' && sortBy === 'title' && sortDir === 'asc') {
|
|
3556
|
-
return list
|
|
3557
|
-
}
|
|
3558
|
-
var direction = sortDir === 'desc' ? -1 : 1
|
|
3559
|
-
var keyed = list.map(function (track, position) {
|
|
3560
|
-
return { track: track, position: position }
|
|
3561
|
-
})
|
|
3562
|
-
keyed.sort(function (left, right) {
|
|
3563
|
-
var a = left.track
|
|
3564
|
-
var b = right.track
|
|
3565
|
-
var aValue = sortValueOf(a, sortBy)
|
|
3566
|
-
var bValue = sortValueOf(b, sortBy)
|
|
3567
|
-
var compared =
|
|
3568
|
-
typeof aValue === 'number' && typeof bValue === 'number'
|
|
3569
|
-
? aValue - bValue
|
|
3570
|
-
: String(aValue).localeCompare(String(bValue), LANG)
|
|
3571
|
-
if (compared === 0) compared = left.position - right.position
|
|
3572
|
-
return compared * direction
|
|
3573
|
-
})
|
|
3574
|
-
return keyed.map(function (entry) {
|
|
3575
|
-
return entry.track
|
|
3576
|
-
})
|
|
3577
3609
|
},
|
|
3578
|
-
[
|
|
3610
|
+
[sortedCollection, query],
|
|
3579
3611
|
)
|
|
3580
3612
|
|
|
3581
3613
|
var selectedSet = useMemo(function () {
|
|
@@ -3641,8 +3673,16 @@ window.__ModuleLoader__.load({
|
|
|
3641
3673
|
h(
|
|
3642
3674
|
'div',
|
|
3643
3675
|
{ className: 'mu-row-title' },
|
|
3676
|
+
isCurrent
|
|
3677
|
+
? h(
|
|
3678
|
+
'span',
|
|
3679
|
+
{ className: 'mu-eq' + (snap.playing ? ' is-playing' : ''), 'aria-hidden': 'true' },
|
|
3680
|
+
h('i', null),
|
|
3681
|
+
h('i', null),
|
|
3682
|
+
h('i', null),
|
|
3683
|
+
)
|
|
3684
|
+
: null,
|
|
3644
3685
|
track.title,
|
|
3645
|
-
isCurrent ? h('span', { className: 'mu-playing-dot' }) : null,
|
|
3646
3686
|
),
|
|
3647
3687
|
h(
|
|
3648
3688
|
'div',
|
|
@@ -4504,22 +4544,12 @@ window.__ModuleLoader__.load({
|
|
|
4504
4544
|
type: 'button',
|
|
4505
4545
|
className: 'mu-chip',
|
|
4506
4546
|
onClick: function () {
|
|
4507
|
-
if (
|
|
4547
|
+
if (sortedCollection.length > 0) player.playList(sortedCollection, 0)
|
|
4508
4548
|
},
|
|
4509
4549
|
},
|
|
4510
4550
|
icon('play', 13),
|
|
4511
4551
|
h('span', null, t('page.playAll')),
|
|
4512
4552
|
),
|
|
4513
|
-
h(
|
|
4514
|
-
'button',
|
|
4515
|
-
{
|
|
4516
|
-
type: 'button',
|
|
4517
|
-
className: 'mu-chip mu-chip-round',
|
|
4518
|
-
title: t('page.clear'),
|
|
4519
|
-
onClick: clearList,
|
|
4520
|
-
},
|
|
4521
|
-
icon('x', 15),
|
|
4522
|
-
),
|
|
4523
4553
|
selected.length > 0
|
|
4524
4554
|
? h(
|
|
4525
4555
|
'span',
|
|
@@ -4828,20 +4858,23 @@ window.__ModuleLoader__.load({
|
|
|
4828
4858
|
{ className: 'mu-pop-body' },
|
|
4829
4859
|
snap.queue.length === 0
|
|
4830
4860
|
? h('div', { className: 'mu-state-sub' }, t('page.queueEmpty'))
|
|
4831
|
-
:
|
|
4861
|
+
: queueView.map(function (entry, position) {
|
|
4862
|
+
var track = entry.track
|
|
4863
|
+
var originalIndex = entry.originalIndex
|
|
4864
|
+
var visibleCurrent = originalIndex === snap.index
|
|
4832
4865
|
return h(
|
|
4833
4866
|
'div',
|
|
4834
4867
|
{
|
|
4835
4868
|
key: position + ':' + track.path,
|
|
4836
|
-
className: 'mu-queue-row' + (
|
|
4869
|
+
className: 'mu-queue-row' + (visibleCurrent ? ' is-current' : ''),
|
|
4837
4870
|
onClick: function () {
|
|
4838
|
-
player.select(snap.queue,
|
|
4871
|
+
player.select(snap.queue, originalIndex)
|
|
4839
4872
|
},
|
|
4840
4873
|
},
|
|
4841
4874
|
h(
|
|
4842
4875
|
'span',
|
|
4843
4876
|
{ className: 'mu-queue-index' },
|
|
4844
|
-
|
|
4877
|
+
visibleCurrent && snap.playing ? '▶' : String(position + 1),
|
|
4845
4878
|
),
|
|
4846
4879
|
h('span', { className: 'mu-ellipsis' }, track.title),
|
|
4847
4880
|
h('span', { className: 'mu-queue-artist mu-ellipsis' }, track.artist || ''),
|
|
@@ -4854,10 +4887,10 @@ window.__ModuleLoader__.load({
|
|
|
4854
4887
|
type: 'button',
|
|
4855
4888
|
className: 'mu-queue-btn',
|
|
4856
4889
|
title: t('page.moveUp'),
|
|
4857
|
-
disabled:
|
|
4890
|
+
disabled: originalIndex === 0,
|
|
4858
4891
|
onClick: function (event) {
|
|
4859
4892
|
event.stopPropagation()
|
|
4860
|
-
player.moveQueue(
|
|
4893
|
+
player.moveQueue(originalIndex, originalIndex - 1)
|
|
4861
4894
|
},
|
|
4862
4895
|
},
|
|
4863
4896
|
'↑',
|
|
@@ -4868,10 +4901,10 @@ window.__ModuleLoader__.load({
|
|
|
4868
4901
|
type: 'button',
|
|
4869
4902
|
className: 'mu-queue-btn',
|
|
4870
4903
|
title: t('page.moveDown'),
|
|
4871
|
-
disabled:
|
|
4904
|
+
disabled: originalIndex === snap.queue.length - 1,
|
|
4872
4905
|
onClick: function (event) {
|
|
4873
4906
|
event.stopPropagation()
|
|
4874
|
-
player.moveQueue(
|
|
4907
|
+
player.moveQueue(originalIndex, originalIndex + 1)
|
|
4875
4908
|
},
|
|
4876
4909
|
},
|
|
4877
4910
|
'↓',
|
|
@@ -4884,7 +4917,7 @@ window.__ModuleLoader__.load({
|
|
|
4884
4917
|
title: t('page.removeTrack'),
|
|
4885
4918
|
onClick: function (event) {
|
|
4886
4919
|
event.stopPropagation()
|
|
4887
|
-
player.removeQueueAt(
|
|
4920
|
+
player.removeQueueAt(originalIndex)
|
|
4888
4921
|
},
|
|
4889
4922
|
},
|
|
4890
4923
|
'×',
|
|
@@ -5180,12 +5213,16 @@ window.__ModuleLoader__.load({
|
|
|
5180
5213
|
'.mu-btn-danger{background:var(--dsw-alias-state-error-primary,#c0392b);color:var(--dsw-alias-label-primary-foreground,#fff);border-color:transparent}',
|
|
5181
5214
|
'.mu-btn-danger:hover{opacity:.9}',
|
|
5182
5215
|
'.mu-row:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(255,255,255,.05))}',
|
|
5183
|
-
'.mu-row.is-current{background:rgba(244,244,245,.
|
|
5184
|
-
'.mu-row.is-current
|
|
5216
|
+
'.mu-row.is-current{background:rgba(244,244,245,.12);background:color-mix(in srgb,var(--dsw-alias-brand-primary,#f4f4f5) 12%,transparent);box-shadow:inset 3px 0 0 var(--dsw-alias-brand-primary,#f4f4f5);border-radius:var(--dsw-radius-md,8px)}',
|
|
5217
|
+
'.mu-row.is-current:hover{background:rgba(244,244,245,.16);background:color-mix(in srgb,var(--dsw-alias-brand-primary,#f4f4f5) 16%,transparent)}',
|
|
5218
|
+
'.mu-row.is-current .mu-row-title{color:var(--dsw-alias-brand-primary,#f4f4f5);font-weight:600}',
|
|
5219
|
+
'.mu-row.is-current .mu-row-sub,.mu-row.is-current .mu-row-album,.mu-row.is-current .mu-row-dur{color:var(--dsw-alias-label-secondary,#a1a1aa)}',
|
|
5185
5220
|
'.mu-row-play{position:relative;width:var(--mu-row-size,55px);height:var(--mu-row-size,55px);border:0;padding:0;background:transparent;cursor:pointer;border-radius:var(--dsw-radius-sm,6px);display:flex;align-items:center;justify-content:center}',
|
|
5186
5221
|
'.mu-row-playmask{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;border-radius:var(--dsw-radius-sm,6px);background:rgba(0,0,0,.45);color:#fff;opacity:0;transition:opacity .12s ease}',
|
|
5187
5222
|
'.mu-row-play:hover .mu-row-playmask,.mu-row.is-current .mu-row-playmask{opacity:1}',
|
|
5223
|
+
'.mu-row.is-current .mu-row-playmask{background:rgba(0,0,0,.35)}',
|
|
5188
5224
|
'.mu-row.is-selected{background:var(--dsw-alias-interactive-bg-hover,rgba(255,255,255,.05))}',
|
|
5225
|
+
'.mu-row.is-current.is-selected{background:rgba(244,244,245,.12);background:color-mix(in srgb,var(--dsw-alias-brand-primary,#f4f4f5) 12%,transparent);box-shadow:inset 3px 0 0 var(--dsw-alias-brand-primary,#f4f4f5)}',
|
|
5189
5226
|
'.mu-row-actions{display:flex;align-items:center;justify-content:flex-end;gap:6px}',
|
|
5190
5227
|
'.mu-check{width:20px;height:20px;margin:0;padding:0;border:1.5px solid var(--dsw-alias-border-l3,rgba(255,255,255,.24));border-radius:50%;background:transparent;color:#fff;cursor:pointer;display:flex;align-items:center;justify-content:center;flex:none;opacity:0;transition:opacity .12s ease}',
|
|
5191
5228
|
'.mu-row:hover .mu-check,.mu-row.is-selected .mu-check,.mu-check:focus-visible{opacity:1}',
|
|
@@ -5195,7 +5232,12 @@ window.__ModuleLoader__.load({
|
|
|
5195
5232
|
'.mu-cover img{width:100%;height:100%;object-fit:cover;display:block}',
|
|
5196
5233
|
'.mu-row-main{min-width:0}',
|
|
5197
5234
|
'.mu-row-title{display:flex;align-items:center;gap:8px;font-size:14px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}',
|
|
5198
|
-
'.mu-
|
|
5235
|
+
'.mu-eq{display:inline-flex;align-items:flex-end;gap:2px;height:12px;margin-right:8px;flex:none}',
|
|
5236
|
+
'.mu-eq i{display:block;width:2px;height:4px;border-radius:1px;background:var(--dsw-alias-brand-primary,#f4f4f5);opacity:.9}',
|
|
5237
|
+
'.mu-eq.is-playing i{animation:mu-eq-bounce .9s ease-in-out infinite}',
|
|
5238
|
+
'.mu-eq.is-playing i:nth-child(2){animation-delay:.15s}',
|
|
5239
|
+
'.mu-eq.is-playing i:nth-child(3){animation-delay:.3s}',
|
|
5240
|
+
'@keyframes mu-eq-bounce{0%,100%{height:4px}50%{height:12px}}',
|
|
5199
5241
|
'.mu-row-sub{display:flex;align-items:center;gap:6px;margin-top:1px;font-size:12px;color:var(--dsw-alias-label-secondary,#a1a1aa);min-width:0}',
|
|
5200
5242
|
'.mu-cd{flex:none;padding:0 4px;border-radius:var(--dsw-radius-xs,4px);border:.5px solid var(--dsw-alias-border-l2,rgba(255,255,255,.2));color:var(--dsw-alias-label-tertiary,#8a8a93);font-size:9px;font-weight:600;line-height:13px;letter-spacing:.4px}',
|
|
5201
5243
|
'.mu-ellipsis{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-music-panel",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Local music library for DeepSeek Harness: scan a folder, browse tracks with cover art and tags, and play through a sidebar-panel player with loop, shuffle and volume. · DSH 本地音乐插件:扫描音乐目录、读取封面与标签、侧栏入口 + 全屏播放器。",
|
|
6
6
|
"license": "MIT",
|