[READ-ONLY] Mirror of https://github.com/probablykasper/ferrum. Music library app for Mac, Linux and Windows ferrum.kasper.space
electron linux macos music music-library music-player napi windows
0

Configure Feed

Select the types of activity you want to include in your feed.

Add queue history

Kasper (Sep 6, 2024, 3:30 PM +0200) e8ad8f7d 4191786f

+119 -61
+1
CHANGELOG.md
··· 1 1 # Changelog 2 2 3 3 ## Next 4 + - Add playback history to queue panel 4 5 - Update dependencies 5 6 - Drop support for Windows 7 and 8 6 7 - Drop support for macOS 10.13 and 10.14
+2 -13
src/App.svelte
··· 9 9 import PlaylistInfoModal from './components/PlaylistInfo.svelte' 10 10 import { queueVisible } from './lib/queue' 11 11 import { ipcListen, ipcRenderer } from '@/lib/window' 12 - import { isMac, importTracks, type PlaylistInfo, methods } from './lib/data' 12 + import { importTracks, type PlaylistInfo, methods } from './lib/data' 13 13 import { playPause } from './lib/player' 14 14 import DragGhost from './components/DragGhost.svelte' 15 15 import ItunesImport from './components/ItunesImport.svelte' ··· 205 205 206 206 <DragGhost /> 207 207 208 - {#if isMac} 209 - <div class="titlebar" on:mousedown|self|preventDefault role="none" /> 210 - {/if} 211 - 212 208 <style lang="sass"> 213 209 :global(:root) 214 210 --cubic-out: cubic-bezier(0.33, 1, 0.68, 1) ··· 225 221 --icon-highlight-color: #00ffff 226 222 --titlebar-height: 22px 227 223 --hue: 225 224 + --right-sidebar-width: 250px 228 225 :global(html), :global(body) 229 226 background-color: #0D1115 230 227 height: 100% ··· 267 264 display: flex 268 265 flex-direction: row 269 266 flex-grow: 1 270 - .titlebar 271 - height: var(--titlebar-height) 272 - width: 100% 273 - top: 0px 274 - left: 0px 275 - position: absolute 276 - -webkit-app-region: drag 277 - z-index: 500 278 267 </style>
+58 -10
src/components/Queue.svelte
··· 30 30 } 31 31 }) 32 32 33 - $: items = getQueue($queue) 33 + let show_history = false 34 + $: items = getQueue($queue, show_history) 35 + $: up_next_index = $queue.past.length + ($queue.current ? 1 : 0) 34 36 35 - function getQueue(queue: Queue) { 37 + function getQueue(queue: Queue, show_history: boolean) { 36 38 // reset selection/dragging if queue gets updated 37 39 selection.clear() 38 40 draggedIndexes = [] ··· 40 42 const newItems: (number | string)[] = [] 41 43 let i = 0 42 44 45 + if (queue.past.length) { 46 + newItems.push('History') 47 + if (show_history) { 48 + for (const _ of queue.past) { 49 + newItems.push(i) 50 + i++ 51 + } 52 + } else { 53 + i += queue.past.length 54 + } 55 + } 56 + if (queue.current) { 57 + i += 1 58 + } 59 + 43 60 if (queue.userQueue.length) { 44 61 newItems.push('Up Next') 45 - queue.userQueue.forEach(() => { 62 + for (const _ of queue.userQueue) { 46 63 newItems.push(i) 47 64 i++ 48 - }) 65 + } 49 66 } 50 67 51 68 if (queue.autoQueue.length) { 52 69 newItems.push('Autoplay') 53 - queue.autoQueue.forEach(() => { 70 + for (const _ of queue.autoQueue) { 54 71 newItems.push(i) 55 72 i++ 56 - }) 73 + } 57 74 } 58 75 59 76 if (newItems.length === 0) { ··· 77 94 }, 78 95 async onContextMenu() { 79 96 const indexes = selection.getSelectedIndexes() 80 - const allItems = [...$queue.userQueue, ...$queue.autoQueue] 97 + const current_array = $queue.current ? [$queue.current.item] : [] 98 + const allItems = [...$queue.past, ...current_array, ...$queue.userQueue, ...$queue.autoQueue] 81 99 const allIds = allItems.map((item) => item.id) 82 100 await showTrackMenu(allIds, indexes, undefined, true) 83 101 }, ··· 152 170 let dragToIndex: null | number = null 153 171 let dragTopOfItem = false 154 172 function onDragOver(e: DragEvent, index: number) { 155 - if (e.currentTarget && e.dataTransfer?.types[0] === 'ferrum.tracks') { 173 + if (e.currentTarget && e.dataTransfer?.types[0] === 'ferrum.tracks' && index >= up_next_index) { 156 174 e.preventDefault() 157 175 const rect = (e.currentTarget as HTMLElement).getBoundingClientRect() 158 176 if (e.pageY < rect.bottom - rect.height / 2) { ··· 220 238 on:keydown={keydown} 221 239 on:mousedown-self={selection.clear} 222 240 > 223 - {#if typeof item === 'string'} 241 + {#if item === 'History'} 242 + <button 243 + class="row history-button" 244 + type="button" 245 + on:click={() => { 246 + show_history = !show_history 247 + }} 248 + > 249 + <svg 250 + xmlns="http://www.w3.org/2000/svg" 251 + class:rotate-90={show_history} 252 + height="24px" 253 + viewBox="0 0 24 24" 254 + width="24px" 255 + fill="#e8eaed" 256 + ><path d="M0 0h24v24H0z" fill="none" /><path 257 + d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z" 258 + /></svg 259 + > 260 + <h4>{item}</h4> 261 + </button> 262 + {:else if typeof item === 'string'} 224 263 <h4 class="row" on:dragover={() => (dragToIndex = null)}>{item}</h4> 225 264 {:else} 226 265 <!-- @const here to fix bugged type guard --> ··· 269 308 box-shadow: inset -20px 0 20px -20px #000000 270 309 .content 271 310 height: 100% 272 - width: 250px 311 + width: var(--right-sidebar-width) 273 312 background-color: #000000 274 313 pointer-events: all 275 314 overflow-y: scroll ··· 291 330 display: none 292 331 &.show 293 332 display: block 333 + .history-button 334 + width: 100% 335 + font-size: inherit 336 + background: none 337 + border: none 338 + padding: 0 339 + cursor: pointer 340 + .rotate-90 341 + transform: rotate(90deg) 294 342 </style>
+3 -2
src/components/Sidebar.svelte
··· 78 78 <!-- NOTE: aside is used as css selector in SidebarItems --> 79 79 <aside on:mousedown|self|preventDefault role="none"> 80 80 {#if isMac} 81 - <div class="titlebar-spacer" /> 81 + <div class="titlebar" on:mousedown|self|preventDefault role="none" /> 82 82 {/if} 83 83 <div class="content" bind:this={contentElement}> 84 84 <Filter ··· 141 141 flex-direction: column 142 142 background-color: hsla(0, 0%, 0%, 0.7) 143 143 box-shadow: inset -6px 0px 6px -6px #000000 144 - .titlebar-spacer 144 + .titlebar 145 + -webkit-app-region: drag 145 146 height: var(--titlebar-height) 146 147 flex-shrink: 0 147 148 .content
+13 -19
src/components/TrackList.svelte
··· 1 1 <script lang="ts"> 2 - import { page, removeFromOpenPlaylist, filter, deleteTracksInOpen, paths } from '../lib/data' 2 + import { 3 + page, 4 + removeFromOpenPlaylist, 5 + filter, 6 + deleteTracksInOpen, 7 + paths, 8 + isMac, 9 + } from '../lib/data' 3 10 import VirtualList from './VirtualList.svelte' 4 11 import { newPlaybackInstance, playingId } from '../lib/player' 5 12 import { ··· 9 16 checkShortcut, 10 17 assertUnreachable, 11 18 } from '../lib/helpers' 12 - import { appendToUserQueue, prependToUserQueue } from '../lib/queue' 19 + import { appendToUserQueue, prependToUserQueue, queueVisible } from '../lib/queue' 13 20 import { selection, scrollToIndex, main_area } from '../lib/page' 14 21 import { ipcListen, ipcRenderer } from '../lib/window' 15 22 import { onDestroy } from 'svelte' ··· 196 203 $: if ($page && virtualList) { 197 204 virtualList.refresh() 198 205 } 199 - 200 - function electronDragRegion(el: HTMLElement) { 201 - const unsubscribe = modalCount.subscribe((count) => { 202 - if (count === 0) { 203 - el.style.setProperty('-webkit-app-region', 'drag') 204 - } else { 205 - el.style.setProperty('-webkit-app-region', 'no-drag') 206 - } 207 - }) 208 - return { 209 - destroy() { 210 - unsubscribe() 211 - }, 212 - } 213 - } 214 206 </script> 215 207 216 208 <div ··· 221 213 class:no-selection={$selection.count === 0} 222 214 > 223 215 <div class="header"> 224 - <div class="dragbar" use:electronDragRegion /> 216 + <div class:dragbar={$modalCount === 0 && isMac} class:queue-visible={$queueVisible} /> 225 217 <h3 class="title"> 226 218 {#if $page.tracklist.id === 'root'} 227 219 Songs ··· 417 409 .dragbar 418 410 -webkit-app-region: drag 419 411 position: absolute 420 - height: 35px 412 + height: 40px 421 413 width: 100% 422 414 top: 0px 423 415 left: 0px 416 + &.queue-visible 417 + width: calc(100% - var(--right-sidebar-width)) 424 418 .title 425 419 margin: 0px 426 420 font-weight: 500
+2
src/lib/menus.ts
··· 32 32 } 33 33 34 34 ipcRenderer.on('context.Play Next', (e, ids: TrackID[]) => { 35 + console.log('ctxpl') 36 + 35 37 prependToUserQueue(ids) 36 38 }) 37 39 ipcRenderer.on('context.Add to Queue', (e, ids: TrackID[]) => {
+38 -17
src/lib/queue.ts
··· 120 120 return queue.get().current?.item ?? null 121 121 } 122 122 export function getQueueLength() { 123 - const { userQueue, autoQueue } = queue.get() 124 - return userQueue.length + autoQueue.length 123 + const { past, current, userQueue, autoQueue } = queue.get() 124 + return past.length + Number(!!current) + userQueue.length + autoQueue.length 125 125 } 126 126 export function getByQueueIndex(index: number) { 127 - const { userQueue, autoQueue } = queue.get() 127 + const { past, current, userQueue, autoQueue } = queue.get() 128 + if (index < past.length) { 129 + return past[index] 130 + } 131 + index -= past.length 132 + if (current && index === 0) { 133 + return current.item 134 + } 135 + index -= Number(!!current) 128 136 if (index < userQueue.length) { 129 137 return userQueue[index] 130 - } else { 131 - return autoQueue[index - userQueue.length] 132 138 } 139 + index -= userQueue.length 140 + return autoQueue[index] 133 141 } 134 142 135 143 export function prependToUserQueue(trackIds: TrackID[]) { ··· 148 156 } 149 157 150 158 export function moveIndexes(indexes: number[], newIndex: number, top = false) { 151 - const ids: QueueItem[] = [] 159 + const items: QueueItem[] = [] 152 160 queue.update((q) => { 153 161 // Sort descending. We need to remove the last indexes first to not mess up the indexes 154 162 for (const index of indexes.sort((a, b) => b - a)) { 155 - ids.push(removeIndex(q, index)) 156 - if (index < newIndex) { 157 - newIndex-- 163 + const removed_item = removeIndex(q, index) 164 + if (removed_item) { 165 + items.push(removed_item) 166 + if (index < newIndex) { 167 + newIndex-- 168 + } 169 + } else { 170 + items.push(newQueueItem(getByQueueIndex(index).id)) 158 171 } 159 172 } 160 173 return q 161 174 }) 162 175 // We sorted the indexes descending, so now reverse them 163 - return insertItems(ids.reverse(), newIndex, top) 176 + return insertItems(items.reverse(), newIndex, top) 164 177 } 165 178 166 - export function insertItems(items: QueueItem[], index: number, top = false) { 179 + function insertItems(items: QueueItem[], index: number, top = false) { 167 180 queue.update((q) => { 181 + const user_queue_index = q.past.length + Number(!!q.current) 182 + index -= user_queue_index 168 183 const snapTop = index === q.userQueue.length && top 169 184 if (index < q.userQueue.length || snapTop) { 170 185 q.userQueue.splice(index, 0, ...items) 171 - } else { 172 - q.autoQueue.splice(index - q.userQueue.length, 0, ...items) 186 + return q 173 187 } 188 + index -= q.userQueue.length 189 + q.autoQueue.splice(index, 0, ...items) 174 190 return q 175 191 }) 176 192 return { ··· 183 199 return insertItems(ids.map(newQueueItem), index, top) 184 200 } 185 201 186 - function removeIndex(q: Queue, index: number): QueueItem { 202 + function removeIndex(q: Queue, index: number): QueueItem | null { 203 + const up_next_index = q.past.length + Number(!!q.current) 204 + if (index < up_next_index) { 205 + return null 206 + } 207 + index -= up_next_index 187 208 if (index < q.userQueue.length) { 188 209 const [removed] = q.userQueue.splice(index, 1) 189 210 return removed 190 - } else { 191 - const [removed] = q.autoQueue.splice(index - q.userQueue.length, 1) 192 - return removed 193 211 } 212 + index -= q.userQueue.length 213 + const [removed] = q.autoQueue.splice(index, 1) 214 + return removed 194 215 } 195 216 196 217 export function removeIndexes(indexes: number[]) {
+2
src/lib/selection.ts
··· 270 270 mouseDownSelect(e, index) 271 271 }, 272 272 handleContextMenu(e: MouseEvent, index: number) { 273 + console.log('handleContextMenu', index) 274 + 273 275 mouseDownSelect(e, index) 274 276 options.onContextMenu() 275 277 },