dsh-music-panel 0.3.6 → 0.3.7
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 +9 -0
- package/README.md +4 -4
- package/client.js +308 -139
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -22,16 +22,16 @@ DeepSeek Harness 的本地音乐插件:左侧栏一个音符入口,点开是
|
|
|
22
22
|
- **左侧栏入口**:`sidebar.panellist` 注册 `music` 图标,点开切换到独立主面板(与「插件市场」「定时任务」同一套宿主机制)。
|
|
23
23
|
- **歌曲列表**:封面缩略图、标题、歌手(带 CD 徽标)、专辑、时长、采样率;当前曲高亮。
|
|
24
24
|
- **播放控制**:列表循环 / 单曲循环 / 不循环,随机播放,上一首、下一首、进度拖动、音量与静音。
|
|
25
|
-
-
|
|
25
|
+
- **播放队列**:底部「队列」按钮或输入框发送按钮左侧的播放列表按钮展开当前队列(顶部带迷你播放器),点击任意条目跳转;「更多」里显示格式 / 码率 / 采样率,可复制文件路径。
|
|
26
26
|
- **记忆播放**:启动 / 刷新后恢复上次的队列、当前曲目与播放位置(默认停在暂停,点 ▶ 继续);设置里可选「恢复后自动播放」和「每首歌记住播放位置」。
|
|
27
27
|
- **统计**:主面板「统计」页按最近一周 / 最近一月 / 全部时间查看每首歌的累计播放时长、播放次数与最近播放,可按时长 / 次数 / 最近排序。播放次数按 Last.fm 规则(曲目 > 30 秒且播放到一半或 4 分钟)记录,播放时长只累计真正在播放的时间。
|
|
28
28
|
- **列表操作**:搜索、排序(标题 / 文件名 / 专辑 / 艺术家 / 大小 / 年份 / 文件夹 / 播放次数 / 时长 / 修改时间 / 添加时间,点击同项切换升降序)、多选后「播放选中」或加入播放列表、清空列表。
|
|
29
|
-
- **设置面板**:主面板右上角齿轮打开,含「外观 / 歌词 / 音乐库 /
|
|
30
|
-
-
|
|
29
|
+
- **设置面板**:主面板右上角齿轮打开,含「外观 / 歌词 / 音乐库 / 播放」四个标签页:外观调整输入框播放列表按钮与圆形封面显示,歌词调整来源顺序、对齐、字号、翻译与状态栏歌词,音乐库管理音乐文件夹与子文件夹深度(保存后主面板自动重新扫描),播放控制启动恢复、自动播放与每首歌断点。
|
|
30
|
+
- **外观设置**:输入框播放列表按钮(发送按钮左侧,可关闭)、播放页圆形封面 / 黑胶底盘 / 封面旋转。
|
|
31
31
|
- **歌词设置**:歌词来源顺序(内嵌 / LRC,可调)、对齐方式、字体大小与粗细、模糊非当前歌词、翻译开关与翻译字号缩放、状态栏歌词(输入框内或输入框上方显示当前唱词)。
|
|
32
32
|
- **歌词校准**:播放页 → 歌词设置里按 ±0.5s 调整偏移;「写回文件」把校准后的 LRC 写回 MP3(ID3 USLT)/ FLAC(Vorbis LYRICS)内嵌歌词,写前自动备份为 `<文件>.lyrics.bak`。
|
|
33
33
|
- **收藏与最近播放**:底栏 / 歌曲行爱心加入「我喜欢」tab;统计页可切换「收听排行 / 最近播放」。
|
|
34
|
-
-
|
|
34
|
+
- **队列编辑**:播放队列支持拖动排序与移除单曲。
|
|
35
35
|
- **搜索**:标题 / 歌手 / 专辑 / 文件夹 / 路径 + 全拼 / 首字母(如 `zhoujielun`、`zjl`),并记住最近 8 条搜索。
|
|
36
36
|
- **多标签页互斥**:用 Web Locks 选主,只有一个标签页发声与计时;其他标签页底栏提示「另一个标签页正在播放」。
|
|
37
37
|
- **播放列表**:顶栏「全部歌曲」与每个歌单各占一个 tab,**页面结构完全一致**(搜索、排序、多选、播放全部、循环/随机、队列);「全部歌曲」是不可编辑集合,歌单可点标题改名、删除、逐首移除。右侧 `+` 新建歌单(自动命名,重名递增)并立即选中。歌单存于 `<DSH_HOME>/music/playlists.json`。
|
package/client.js
CHANGED
|
@@ -33,6 +33,7 @@ window.__ModuleLoader__.load({
|
|
|
33
33
|
/** Defaults for every appearance / lyric preference. */
|
|
34
34
|
var PREF_DEFAULTS = {
|
|
35
35
|
miniPlayer: true,
|
|
36
|
+
composerQueueButton: true,
|
|
36
37
|
circularCover: true,
|
|
37
38
|
vinylRing: false,
|
|
38
39
|
coverSpin: true,
|
|
@@ -210,6 +211,7 @@ window.__ModuleLoader__.load({
|
|
|
210
211
|
'settings.pickEmpty': '没有选中文件夹。',
|
|
211
212
|
'settings.pickFailed': '无法读取文件夹路径,请重试或手动填写。',
|
|
212
213
|
'settings.miniPlayer': '迷你浮窗',
|
|
214
|
+
'settings.composerQueueButton': '输入框播放列表按钮',
|
|
213
215
|
'settings.circularCover': '播放页圆形封面',
|
|
214
216
|
'settings.vinylRing': '圆形封面黑胶底盘',
|
|
215
217
|
'settings.coverSpin': '圆形封面旋转',
|
|
@@ -385,6 +387,7 @@ window.__ModuleLoader__.load({
|
|
|
385
387
|
'settings.pickEmpty': 'No folder selected.',
|
|
386
388
|
'settings.pickFailed': 'Could not read the folder path. Try again or type it manually.',
|
|
387
389
|
'settings.miniPlayer': 'Mini player window',
|
|
390
|
+
'settings.composerQueueButton': 'Playlist button in composer',
|
|
388
391
|
'settings.circularCover': 'Circular cover on player page',
|
|
389
392
|
'settings.vinylRing': 'Vinyl ring behind the cover',
|
|
390
393
|
'settings.coverSpin': 'Rotate the cover',
|
|
@@ -2031,6 +2034,278 @@ window.__ModuleLoader__.load({
|
|
|
2031
2034
|
|
|
2032
2035
|
// ── mini player (draggable floating window) ────────────────────────────
|
|
2033
2036
|
|
|
2037
|
+
// ── shared playback list (queue overlay) ──────────────────────────────
|
|
2038
|
+
// Opened from the composer button and the player bar. The mini player is
|
|
2039
|
+
// no longer a standalone floating window: it lives in this overlay header.
|
|
2040
|
+
|
|
2041
|
+
var queueOverlayOpen = false
|
|
2042
|
+
var queueOverlayListeners = []
|
|
2043
|
+
|
|
2044
|
+
function queueOverlaySnapshot() {
|
|
2045
|
+
return queueOverlayOpen
|
|
2046
|
+
}
|
|
2047
|
+
|
|
2048
|
+
function toggleQueueOverlay(next) {
|
|
2049
|
+
queueOverlayOpen = next === undefined ? !queueOverlayOpen : next === true
|
|
2050
|
+
queueOverlayListeners.slice().forEach(function (listener) {
|
|
2051
|
+
listener()
|
|
2052
|
+
})
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2055
|
+
function subscribeQueueOverlay(listener) {
|
|
2056
|
+
queueOverlayListeners = queueOverlayListeners.concat([listener])
|
|
2057
|
+
return function () {
|
|
2058
|
+
queueOverlayListeners = queueOverlayListeners.filter(function (item) {
|
|
2059
|
+
return item !== listener
|
|
2060
|
+
})
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
|
|
2064
|
+
/** [{ track, originalIndex }] in actual playback order. */
|
|
2065
|
+
function buildQueueView(snap, playOrder) {
|
|
2066
|
+
if (snap.order === 'random' && Array.isArray(playOrder) && playOrder.length === snap.queue.length) {
|
|
2067
|
+
return playOrder.map(function (originalIndex) {
|
|
2068
|
+
return { track: snap.queue[originalIndex], originalIndex: originalIndex }
|
|
2069
|
+
})
|
|
2070
|
+
}
|
|
2071
|
+
return snap.queue.map(function (track, originalIndex) {
|
|
2072
|
+
return { track: track, originalIndex: originalIndex }
|
|
2073
|
+
})
|
|
2074
|
+
}
|
|
2075
|
+
|
|
2076
|
+
function QueueNowPlaying() {
|
|
2077
|
+
var [snap, setSnap] = useState(player.getSnapshot())
|
|
2078
|
+
useEffect(function () {
|
|
2079
|
+
return player.subscribe(function () {
|
|
2080
|
+
setSnap(player.getSnapshot())
|
|
2081
|
+
})
|
|
2082
|
+
}, [])
|
|
2083
|
+
var track = snap.index >= 0 && snap.queue[snap.index] ? snap.queue[snap.index] : null
|
|
2084
|
+
if (track === null) {
|
|
2085
|
+
return h('div', { className: 'mu-qoverlay-now is-empty' }, t('page.noTrack'))
|
|
2086
|
+
}
|
|
2087
|
+
return h(
|
|
2088
|
+
'div',
|
|
2089
|
+
{ className: 'mu-qoverlay-now' },
|
|
2090
|
+
h(Cover, { track: track, size: 44, radius: 8 }),
|
|
2091
|
+
h(
|
|
2092
|
+
'div',
|
|
2093
|
+
{ className: 'mu-qoverlay-now-main' },
|
|
2094
|
+
h('div', { className: 'mu-qoverlay-now-title mu-ellipsis' }, track.title),
|
|
2095
|
+
h('div', { className: 'mu-qoverlay-now-artist mu-ellipsis' }, track.artist || t('page.unknownArtist')),
|
|
2096
|
+
),
|
|
2097
|
+
h(
|
|
2098
|
+
'div',
|
|
2099
|
+
{ className: 'mu-qoverlay-now-controls' },
|
|
2100
|
+
h(
|
|
2101
|
+
'button',
|
|
2102
|
+
{ type: 'button', className: 'mu-icon-btn', title: t('page.prev'), onClick: function () { player.prev() } },
|
|
2103
|
+
icon('prev', 16),
|
|
2104
|
+
),
|
|
2105
|
+
h(
|
|
2106
|
+
'button',
|
|
2107
|
+
{
|
|
2108
|
+
type: 'button',
|
|
2109
|
+
className: 'mu-icon-btn',
|
|
2110
|
+
title: snap.playing ? t('page.pause') : t('page.play'),
|
|
2111
|
+
onClick: function () { player.toggle() },
|
|
2112
|
+
},
|
|
2113
|
+
icon(snap.playing ? 'pause' : 'play', 18),
|
|
2114
|
+
),
|
|
2115
|
+
h(
|
|
2116
|
+
'button',
|
|
2117
|
+
{ type: 'button', className: 'mu-icon-btn', title: t('page.next'), onClick: function () { player.next() } },
|
|
2118
|
+
icon('next', 16),
|
|
2119
|
+
),
|
|
2120
|
+
),
|
|
2121
|
+
)
|
|
2122
|
+
}
|
|
2123
|
+
|
|
2124
|
+
function QueueOverlay() {
|
|
2125
|
+
var [open, setOpen] = useState(queueOverlaySnapshot())
|
|
2126
|
+
var [snap, setSnap] = useState(player.getSnapshot())
|
|
2127
|
+
var [dragFrom, setDragFrom] = useState(null)
|
|
2128
|
+
var [dragOver, setDragOver] = useState(null)
|
|
2129
|
+
var ref = useRef(null)
|
|
2130
|
+
|
|
2131
|
+
useEffect(function () {
|
|
2132
|
+
return subscribeQueueOverlay(function () {
|
|
2133
|
+
setOpen(queueOverlaySnapshot())
|
|
2134
|
+
})
|
|
2135
|
+
}, [])
|
|
2136
|
+
useEffect(function () {
|
|
2137
|
+
return player.subscribe(function () {
|
|
2138
|
+
setSnap(player.getSnapshot())
|
|
2139
|
+
})
|
|
2140
|
+
}, [])
|
|
2141
|
+
useEffect(function () {
|
|
2142
|
+
if (!open) return undefined
|
|
2143
|
+
function onKey(event) {
|
|
2144
|
+
if (event.key === 'Escape') toggleQueueOverlay(false)
|
|
2145
|
+
}
|
|
2146
|
+
function onPointerDown(event) {
|
|
2147
|
+
if (ref.current !== null && ref.current.contains(event.target)) return
|
|
2148
|
+
toggleQueueOverlay(false)
|
|
2149
|
+
}
|
|
2150
|
+
document.addEventListener('keydown', onKey)
|
|
2151
|
+
document.addEventListener('mousedown', onPointerDown)
|
|
2152
|
+
return function () {
|
|
2153
|
+
document.removeEventListener('keydown', onKey)
|
|
2154
|
+
document.removeEventListener('mousedown', onPointerDown)
|
|
2155
|
+
}
|
|
2156
|
+
}, [open])
|
|
2157
|
+
|
|
2158
|
+
if (!open) return null
|
|
2159
|
+
var queueView = buildQueueView(snap, player.getPlayOrder())
|
|
2160
|
+
var rows = queueView.map(function (entry, position) {
|
|
2161
|
+
var track = entry.track
|
|
2162
|
+
var originalIndex = entry.originalIndex
|
|
2163
|
+
var visibleCurrent = originalIndex === snap.index
|
|
2164
|
+
return h(
|
|
2165
|
+
'div',
|
|
2166
|
+
{
|
|
2167
|
+
key: position + ':' + track.path,
|
|
2168
|
+
className:
|
|
2169
|
+
'mu-queue-row' +
|
|
2170
|
+
(visibleCurrent ? ' is-current' : '') +
|
|
2171
|
+
(dragFrom === position ? ' is-dragging' : '') +
|
|
2172
|
+
(dragOver === position && dragFrom !== position ? ' is-drop-target' : ''),
|
|
2173
|
+
draggable: true,
|
|
2174
|
+
onClick: function () {
|
|
2175
|
+
player.select(snap.queue, originalIndex)
|
|
2176
|
+
},
|
|
2177
|
+
onDragStart: function (event) {
|
|
2178
|
+
setDragFrom(position)
|
|
2179
|
+
setDragOver(position)
|
|
2180
|
+
if (event.dataTransfer !== null && event.dataTransfer !== undefined) {
|
|
2181
|
+
event.dataTransfer.effectAllowed = 'move'
|
|
2182
|
+
try {
|
|
2183
|
+
event.dataTransfer.setData('text/plain', String(position))
|
|
2184
|
+
} catch (error) {
|
|
2185
|
+
// ignore
|
|
2186
|
+
}
|
|
2187
|
+
}
|
|
2188
|
+
},
|
|
2189
|
+
onDragOver: function (event) {
|
|
2190
|
+
event.preventDefault()
|
|
2191
|
+
if (event.dataTransfer !== null && event.dataTransfer !== undefined) {
|
|
2192
|
+
event.dataTransfer.dropEffect = 'move'
|
|
2193
|
+
}
|
|
2194
|
+
if (dragOver !== position) setDragOver(position)
|
|
2195
|
+
},
|
|
2196
|
+
onDragLeave: function () {
|
|
2197
|
+
if (dragOver === position) setDragOver(null)
|
|
2198
|
+
},
|
|
2199
|
+
onDrop: function (event) {
|
|
2200
|
+
event.preventDefault()
|
|
2201
|
+
var from = dragFrom
|
|
2202
|
+
if (from !== null && from !== position) {
|
|
2203
|
+
var order = queueView.map(function (item) {
|
|
2204
|
+
return item.originalIndex
|
|
2205
|
+
})
|
|
2206
|
+
var moved = order.splice(from, 1)[0]
|
|
2207
|
+
order.splice(position, 0, moved)
|
|
2208
|
+
player.reorderQueue(order)
|
|
2209
|
+
}
|
|
2210
|
+
setDragFrom(null)
|
|
2211
|
+
setDragOver(null)
|
|
2212
|
+
},
|
|
2213
|
+
onDragEnd: function () {
|
|
2214
|
+
setDragFrom(null)
|
|
2215
|
+
setDragOver(null)
|
|
2216
|
+
},
|
|
2217
|
+
},
|
|
2218
|
+
h('span', { className: 'mu-queue-grip', title: t('page.dragSort') }, '⋮⋮'),
|
|
2219
|
+
h(
|
|
2220
|
+
'span',
|
|
2221
|
+
{ className: 'mu-queue-index' },
|
|
2222
|
+
visibleCurrent && snap.playing ? '▶' : String(position + 1),
|
|
2223
|
+
),
|
|
2224
|
+
h('span', { className: 'mu-ellipsis' }, track.title),
|
|
2225
|
+
h('span', { className: 'mu-queue-artist mu-ellipsis' }, track.artist || ''),
|
|
2226
|
+
h(
|
|
2227
|
+
'span',
|
|
2228
|
+
{ className: 'mu-queue-actions' },
|
|
2229
|
+
h(
|
|
2230
|
+
'button',
|
|
2231
|
+
{
|
|
2232
|
+
type: 'button',
|
|
2233
|
+
className: 'mu-queue-btn',
|
|
2234
|
+
title: t('page.removeTrack'),
|
|
2235
|
+
onClick: function (event) {
|
|
2236
|
+
event.stopPropagation()
|
|
2237
|
+
player.removeQueueAt(originalIndex)
|
|
2238
|
+
},
|
|
2239
|
+
},
|
|
2240
|
+
'×',
|
|
2241
|
+
),
|
|
2242
|
+
),
|
|
2243
|
+
)
|
|
2244
|
+
})
|
|
2245
|
+
|
|
2246
|
+
return h(
|
|
2247
|
+
'div',
|
|
2248
|
+
{ className: 'mu-qoverlay', ref: ref },
|
|
2249
|
+
h(QueueNowPlaying, null),
|
|
2250
|
+
h(
|
|
2251
|
+
'div',
|
|
2252
|
+
{ className: 'mu-qoverlay-head' },
|
|
2253
|
+
h('span', null, t('page.queue')),
|
|
2254
|
+
h('span', { className: 'mu-queue-mode' }, playModeLabel(playModeOf(snap))),
|
|
2255
|
+
h(
|
|
2256
|
+
'button',
|
|
2257
|
+
{ type: 'button', className: 'mu-icon-btn mu-icon-btn-sm', onClick: function () { toggleQueueOverlay(false) } },
|
|
2258
|
+
icon('x', 14),
|
|
2259
|
+
),
|
|
2260
|
+
),
|
|
2261
|
+
h(
|
|
2262
|
+
'div',
|
|
2263
|
+
{ className: 'mu-qoverlay-list' },
|
|
2264
|
+
snap.queue.length === 0 ? h('div', { className: 'mu-state-sub' }, t('page.queueEmpty')) : rows,
|
|
2265
|
+
),
|
|
2266
|
+
)
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2269
|
+
function ComposerQueueButton() {
|
|
2270
|
+
var [snap, setSnap] = useState(player.getSnapshot())
|
|
2271
|
+
var [enabled, setEnabled] = useState(function () {
|
|
2272
|
+
return readPrefs().composerQueueButton !== false
|
|
2273
|
+
})
|
|
2274
|
+
var [open, setOpen] = useState(queueOverlaySnapshot())
|
|
2275
|
+
useEffect(function () {
|
|
2276
|
+
return player.subscribe(function () {
|
|
2277
|
+
setSnap(player.getSnapshot())
|
|
2278
|
+
})
|
|
2279
|
+
}, [])
|
|
2280
|
+
useEffect(function () {
|
|
2281
|
+
function onChange() {
|
|
2282
|
+
setEnabled(readPrefs().composerQueueButton !== false)
|
|
2283
|
+
}
|
|
2284
|
+
window.addEventListener(PREFS_EVENT, onChange)
|
|
2285
|
+
return function () {
|
|
2286
|
+
window.removeEventListener(PREFS_EVENT, onChange)
|
|
2287
|
+
}
|
|
2288
|
+
}, [])
|
|
2289
|
+
useEffect(function () {
|
|
2290
|
+
return subscribeQueueOverlay(function () {
|
|
2291
|
+
setOpen(queueOverlaySnapshot())
|
|
2292
|
+
})
|
|
2293
|
+
}, [])
|
|
2294
|
+
if (!enabled) return null
|
|
2295
|
+
return h(
|
|
2296
|
+
'button',
|
|
2297
|
+
{
|
|
2298
|
+
type: 'button',
|
|
2299
|
+
className: 'mu-composer-queue-btn' + (open ? ' is-on' : '') + (snap.playing ? ' is-playing' : ''),
|
|
2300
|
+
title: t('page.queue'),
|
|
2301
|
+
onClick: function () {
|
|
2302
|
+
toggleQueueOverlay()
|
|
2303
|
+
},
|
|
2304
|
+
},
|
|
2305
|
+
icon('list', 16),
|
|
2306
|
+
)
|
|
2307
|
+
}
|
|
2308
|
+
|
|
2034
2309
|
function MiniPlayer() {
|
|
2035
2310
|
var [enabled, setEnabled] = useState(function () {
|
|
2036
2311
|
return readPrefs().miniPlayer === true
|
|
@@ -3108,18 +3383,6 @@ window.__ModuleLoader__.load({
|
|
|
3108
3383
|
var prefs = useRef(readPrefs()).current
|
|
3109
3384
|
var [snap, setSnap] = useState(player.getSnapshot())
|
|
3110
3385
|
var leaderNow = player.isLeader()
|
|
3111
|
-
var playOrder = player.getPlayOrder()
|
|
3112
|
-
// In random mode the queue panel lists songs in the order they will
|
|
3113
|
-
// actually play ("from top to bottom"), while edits still target the
|
|
3114
|
-
// original queue indices.
|
|
3115
|
-
var queueView =
|
|
3116
|
-
snap.order === 'random' && Array.isArray(playOrder) && playOrder.length === snap.queue.length
|
|
3117
|
-
? playOrder.map(function (originalIndex) {
|
|
3118
|
-
return { track: snap.queue[originalIndex], originalIndex: originalIndex }
|
|
3119
|
-
})
|
|
3120
|
-
: snap.queue.map(function (track, originalIndex) {
|
|
3121
|
-
return { track: track, originalIndex: originalIndex }
|
|
3122
|
-
})
|
|
3123
3386
|
var [config, setConfig] = useState(null)
|
|
3124
3387
|
var [tracks, setTracks] = useState([])
|
|
3125
3388
|
var [loading, setLoading] = useState(true)
|
|
@@ -3164,9 +3427,12 @@ window.__ModuleLoader__.load({
|
|
|
3164
3427
|
var [sortDir, setSortDir] = useState(prefs.sortDir === 'desc' ? 'desc' : 'asc')
|
|
3165
3428
|
var [sortOpen, setSortOpen] = useState(false)
|
|
3166
3429
|
var [selected, setSelected] = useState([])
|
|
3167
|
-
var [queueOpen, setQueueOpen] = useState(
|
|
3168
|
-
|
|
3169
|
-
|
|
3430
|
+
var [queueOpen, setQueueOpen] = useState(queueOverlaySnapshot())
|
|
3431
|
+
useEffect(function () {
|
|
3432
|
+
return subscribeQueueOverlay(function () {
|
|
3433
|
+
setQueueOpen(queueOverlaySnapshot())
|
|
3434
|
+
})
|
|
3435
|
+
}, [])
|
|
3170
3436
|
var [moreOpen, setMoreOpen] = useState(false)
|
|
3171
3437
|
var [toast, setToast] = useState('')
|
|
3172
3438
|
var toastTimerRef = useRef(null)
|
|
@@ -3937,11 +4203,10 @@ window.__ModuleLoader__.load({
|
|
|
3937
4203
|
return h(
|
|
3938
4204
|
'div',
|
|
3939
4205
|
{ className: 'mu-root' },
|
|
3940
|
-
|
|
4206
|
+
moreOpen
|
|
3941
4207
|
? h('div', {
|
|
3942
4208
|
className: 'mu-scrim',
|
|
3943
4209
|
onClick: function () {
|
|
3944
|
-
setQueueOpen(false)
|
|
3945
4210
|
setMoreOpen(false)
|
|
3946
4211
|
},
|
|
3947
4212
|
})
|
|
@@ -4257,8 +4522,8 @@ window.__ModuleLoader__.load({
|
|
|
4257
4522
|
? h(
|
|
4258
4523
|
'div',
|
|
4259
4524
|
{ className: 'mu-prefs-body' },
|
|
4260
|
-
settingToggle(t('settings.
|
|
4261
|
-
updatePref('
|
|
4525
|
+
settingToggle(t('settings.composerQueueButton'), prefsUi.composerQueueButton, function (value) {
|
|
4526
|
+
updatePref('composerQueueButton', value)
|
|
4262
4527
|
}),
|
|
4263
4528
|
settingToggle(t('settings.circularCover'), prefsUi.circularCover, function (value) {
|
|
4264
4529
|
updatePref('circularCover', value)
|
|
@@ -4808,7 +5073,7 @@ window.__ModuleLoader__.load({
|
|
|
4808
5073
|
className: 'mu-icon-btn' + (queueOpen ? ' is-on' : ''),
|
|
4809
5074
|
title: t('page.queue'),
|
|
4810
5075
|
onClick: function () {
|
|
4811
|
-
|
|
5076
|
+
toggleQueueOverlay()
|
|
4812
5077
|
setMoreOpen(false)
|
|
4813
5078
|
},
|
|
4814
5079
|
},
|
|
@@ -4842,7 +5107,7 @@ window.__ModuleLoader__.load({
|
|
|
4842
5107
|
title: t('page.more'),
|
|
4843
5108
|
onClick: function () {
|
|
4844
5109
|
setMoreOpen(!moreOpen)
|
|
4845
|
-
|
|
5110
|
+
toggleQueueOverlay(false)
|
|
4846
5111
|
},
|
|
4847
5112
|
},
|
|
4848
5113
|
icon('more', 18),
|
|
@@ -4851,122 +5116,6 @@ window.__ModuleLoader__.load({
|
|
|
4851
5116
|
),
|
|
4852
5117
|
),
|
|
4853
5118
|
|
|
4854
|
-
queueOpen
|
|
4855
|
-
? h(
|
|
4856
|
-
'div',
|
|
4857
|
-
{ className: 'mu-pop mu-queue' },
|
|
4858
|
-
h(
|
|
4859
|
-
'div',
|
|
4860
|
-
{ className: 'mu-pop-head' },
|
|
4861
|
-
h('span', null, t('page.queue')),
|
|
4862
|
-
h('span', { className: 'mu-queue-mode' }, loopLabel()),
|
|
4863
|
-
h(
|
|
4864
|
-
'button',
|
|
4865
|
-
{
|
|
4866
|
-
type: 'button',
|
|
4867
|
-
className: 'mu-icon-btn mu-icon-btn-sm',
|
|
4868
|
-
onClick: function () {
|
|
4869
|
-
setQueueOpen(false)
|
|
4870
|
-
},
|
|
4871
|
-
},
|
|
4872
|
-
icon('x', 14),
|
|
4873
|
-
),
|
|
4874
|
-
),
|
|
4875
|
-
h(
|
|
4876
|
-
'div',
|
|
4877
|
-
{ className: 'mu-pop-body' },
|
|
4878
|
-
snap.queue.length === 0
|
|
4879
|
-
? h('div', { className: 'mu-state-sub' }, t('page.queueEmpty'))
|
|
4880
|
-
: queueView.map(function (entry, position) {
|
|
4881
|
-
var track = entry.track
|
|
4882
|
-
var originalIndex = entry.originalIndex
|
|
4883
|
-
var visibleCurrent = originalIndex === snap.index
|
|
4884
|
-
return h(
|
|
4885
|
-
'div',
|
|
4886
|
-
{
|
|
4887
|
-
key: position + ':' + track.path,
|
|
4888
|
-
className:
|
|
4889
|
-
'mu-queue-row' +
|
|
4890
|
-
(visibleCurrent ? ' is-current' : '') +
|
|
4891
|
-
(queueDragFrom === position ? ' is-dragging' : '') +
|
|
4892
|
-
(queueDragOver === position && queueDragFrom !== position ? ' is-drop-target' : ''),
|
|
4893
|
-
draggable: true,
|
|
4894
|
-
onClick: function () {
|
|
4895
|
-
player.select(snap.queue, originalIndex)
|
|
4896
|
-
},
|
|
4897
|
-
onDragStart: function (event) {
|
|
4898
|
-
setQueueDragFrom(position)
|
|
4899
|
-
setQueueDragOver(position)
|
|
4900
|
-
if (event.dataTransfer !== null && event.dataTransfer !== undefined) {
|
|
4901
|
-
event.dataTransfer.effectAllowed = 'move'
|
|
4902
|
-
// Firefox needs data for a drag to start.
|
|
4903
|
-
try {
|
|
4904
|
-
event.dataTransfer.setData('text/plain', String(position))
|
|
4905
|
-
} catch (error) {
|
|
4906
|
-
// ignore
|
|
4907
|
-
}
|
|
4908
|
-
}
|
|
4909
|
-
},
|
|
4910
|
-
onDragOver: function (event) {
|
|
4911
|
-
event.preventDefault()
|
|
4912
|
-
if (event.dataTransfer !== null && event.dataTransfer !== undefined) {
|
|
4913
|
-
event.dataTransfer.dropEffect = 'move'
|
|
4914
|
-
}
|
|
4915
|
-
if (queueDragOver !== position) setQueueDragOver(position)
|
|
4916
|
-
},
|
|
4917
|
-
onDragLeave: function () {
|
|
4918
|
-
if (queueDragOver === position) setQueueDragOver(null)
|
|
4919
|
-
},
|
|
4920
|
-
onDrop: function (event) {
|
|
4921
|
-
event.preventDefault()
|
|
4922
|
-
var from = queueDragFrom
|
|
4923
|
-
if (from !== null && from !== position) {
|
|
4924
|
-
var order = queueView.map(function (item) {
|
|
4925
|
-
return item.originalIndex
|
|
4926
|
-
})
|
|
4927
|
-
var moved = order.splice(from, 1)[0]
|
|
4928
|
-
order.splice(position, 0, moved)
|
|
4929
|
-
player.reorderQueue(order)
|
|
4930
|
-
}
|
|
4931
|
-
setQueueDragFrom(null)
|
|
4932
|
-
setQueueDragOver(null)
|
|
4933
|
-
},
|
|
4934
|
-
onDragEnd: function () {
|
|
4935
|
-
setQueueDragFrom(null)
|
|
4936
|
-
setQueueDragOver(null)
|
|
4937
|
-
},
|
|
4938
|
-
},
|
|
4939
|
-
h('span', { className: 'mu-queue-grip', title: t('page.dragSort') }, '⋮⋮'),
|
|
4940
|
-
h(
|
|
4941
|
-
'span',
|
|
4942
|
-
{ className: 'mu-queue-index' },
|
|
4943
|
-
visibleCurrent && snap.playing ? '▶' : String(position + 1),
|
|
4944
|
-
),
|
|
4945
|
-
h('span', { className: 'mu-ellipsis' }, track.title),
|
|
4946
|
-
h('span', { className: 'mu-queue-artist mu-ellipsis' }, track.artist || ''),
|
|
4947
|
-
h(
|
|
4948
|
-
'span',
|
|
4949
|
-
{ className: 'mu-queue-actions' },
|
|
4950
|
-
h(
|
|
4951
|
-
'button',
|
|
4952
|
-
{
|
|
4953
|
-
type: 'button',
|
|
4954
|
-
className: 'mu-queue-btn',
|
|
4955
|
-
title: t('page.removeTrack'),
|
|
4956
|
-
onClick: function (event) {
|
|
4957
|
-
event.stopPropagation()
|
|
4958
|
-
player.removeQueueAt(originalIndex)
|
|
4959
|
-
},
|
|
4960
|
-
},
|
|
4961
|
-
'×',
|
|
4962
|
-
),
|
|
4963
|
-
),
|
|
4964
|
-
)
|
|
4965
|
-
}),
|
|
4966
|
-
),
|
|
4967
|
-
)
|
|
4968
|
-
: null,
|
|
4969
|
-
|
|
4970
5119
|
moreOpen
|
|
4971
5120
|
? h(
|
|
4972
5121
|
'div',
|
|
@@ -5320,6 +5469,20 @@ window.__ModuleLoader__.load({
|
|
|
5320
5469
|
'.mu-bar-right{display:flex;align-items:center;gap:6px;justify-self:end}',
|
|
5321
5470
|
'.mu-quality{font-size:12px;color:var(--dsw-alias-label-tertiary,#a1a1aa);font-variant-numeric:tabular-nums;white-space:nowrap}',
|
|
5322
5471
|
'.mu-pop{position:absolute;right:16px;bottom:84px;z-index:40;width:330px;max-height:58%;display:flex;flex-direction:column;border-radius:var(--dsw-radius-md,8px);background:var(--dsw-alias-bg-layer-1,#1a1a1e);border:.5px solid var(--dsw-alias-border-l2,rgba(255,255,255,.1));box-shadow:var(--dsw-elevation-prominent,0 16px 48px rgba(0,0,0,.5));overflow:hidden}',
|
|
5472
|
+
'.mu-qoverlay{position:fixed;right:24px;bottom:96px;z-index:70;width:360px;max-width:calc(100vw - 48px);max-height:62vh;display:flex;flex-direction:column;border:.5px solid var(--dsw-alias-border-l2,rgba(255,255,255,.1));border-radius:14px;background:var(--dsw-alias-bg-layer-1,#1a1a1e);box-shadow:var(--dsw-elevation-prominent,0 16px 48px rgba(0,0,0,.5));overflow:hidden}',
|
|
5473
|
+
'.mu-qoverlay-now{display:flex;align-items:center;gap:10px;padding:10px 12px;border-bottom:.5px solid var(--dsw-alias-border-l1,rgba(255,255,255,.08))}',
|
|
5474
|
+
'.mu-qoverlay-now.is-empty{padding:14px 12px;color:var(--dsw-alias-label-tertiary,#8a8a93);font-size:12px}',
|
|
5475
|
+
'.mu-qoverlay-now-main{flex:1;min-width:0}',
|
|
5476
|
+
'.mu-qoverlay-now-title{font-size:13px;font-weight:600}',
|
|
5477
|
+
'.mu-qoverlay-now-artist{font-size:11px;color:var(--dsw-alias-label-secondary,#a1a1aa);margin-top:1px}',
|
|
5478
|
+
'.mu-qoverlay-now-controls{display:flex;align-items:center;gap:2px}',
|
|
5479
|
+
'.mu-qoverlay-head{display:flex;align-items:center;gap:8px;padding:8px 12px;border-bottom:.5px solid var(--dsw-alias-border-l1,rgba(255,255,255,.08));font-size:12px;font-weight:600}',
|
|
5480
|
+
'.mu-qoverlay-head .mu-icon-btn{margin-left:auto}',
|
|
5481
|
+
'.mu-qoverlay-list{flex:1;min-height:0;overflow-y:auto;padding:6px}',
|
|
5482
|
+
'.mu-composer-queue-btn{position:relative;display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;padding:0;border:0;border-radius:6px;background:transparent;color:var(--dsw-alias-label-secondary,#a1a1aa);cursor:pointer;flex:none}',
|
|
5483
|
+
'.mu-composer-queue-btn:hover{color:var(--dsw-alias-label-primary,#f4f4f5);background:var(--dsw-alias-interactive-bg-hover,rgba(255,255,255,.07))}',
|
|
5484
|
+
'.mu-composer-queue-btn.is-on{color:var(--dsw-alias-brand-primary,#f4f4f5)}',
|
|
5485
|
+
'.mu-composer-queue-btn.is-playing::after{content:"";position:absolute;top:4px;right:4px;width:5px;height:5px;border-radius:50%;background:var(--dsw-alias-brand-primary,#f4f4f5)}',
|
|
5323
5486
|
'.mu-pop-head{display:flex;align-items:center;justify-content:space-between;padding:10px 12px 6px;font-size:12px;font-weight:600}',
|
|
5324
5487
|
'.mu-pop-body{overflow-y:auto;padding:0 6px 8px}',
|
|
5325
5488
|
'.mu-queue-row{display:grid;grid-template-columns:14px 26px minmax(0,1fr) minmax(0,70px) auto;align-items:center;gap:8px;padding:5px 8px;border-radius:var(--dsw-radius-sm,6px);font-size:12px;cursor:pointer}',
|
|
@@ -5718,8 +5881,14 @@ window.__ModuleLoader__.load({
|
|
|
5718
5881
|
},
|
|
5719
5882
|
)
|
|
5720
5883
|
|
|
5721
|
-
//
|
|
5722
|
-
|
|
5884
|
+
// Shared playback list overlay (the mini player lives in its header)
|
|
5885
|
+
// and the composer shortcut button before the submit action.
|
|
5886
|
+
mount('shell.overlay', { name: 'shell.overlay', id: 'music-queue', order: 16 }, QueueOverlay)
|
|
5887
|
+
mount(
|
|
5888
|
+
'conversation.input.right',
|
|
5889
|
+
{ name: 'conversation.input.right', id: 'music-queue-button', order: 10 },
|
|
5890
|
+
ComposerQueueButton,
|
|
5891
|
+
)
|
|
5723
5892
|
|
|
5724
5893
|
},
|
|
5725
5894
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-music-panel",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.7",
|
|
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",
|