[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.

Allow dragging into root playlist

Kasper (Jul 26, 2022, 12:11 PM +0200) bec8e1b0 91019aca

+91 -63
+1
native/src/library_types.rs
··· 280 280 } 281 281 282 282 #[derive(Serialize, Deserialize, Clone, Debug)] 283 + #[non_exhaustive] 283 284 pub enum SpecialTrackListName { 284 285 Root, 285 286 }
+22 -18
native/src/playlists.rs
··· 172 172 return ctx.env.get_undefined(); 173 173 } 174 174 175 + fn get_children_if_user_editable<'a>( 176 + library: &'a mut Library, 177 + id: &'a str, 178 + ) -> UniResult<&'a mut Vec<String>> { 179 + let children = match library.trackLists.get_mut(id) { 180 + Some(TrackList::Folder(folder)) => &mut folder.children, 181 + Some(TrackList::Special(special)) => match special.name { 182 + SpecialTrackListName::Root => &mut special.children, 183 + }, 184 + None => throw!("Attempted to move from/to non-existant folder"), 185 + _ => throw!("Attempted to move from/to non-folder"), 186 + }; 187 + Ok(children) 188 + } 189 + 175 190 #[js_function(3)] 176 191 pub fn move_playlist(ctx: CallContext) -> NResult<JsUndefined> { 177 192 let data: &mut Data = get_data(&ctx)?; ··· 185 200 _ => {} 186 201 }; 187 202 188 - match data.library.trackLists.get(&to_id) { 189 - Some(TrackList::Folder(folder)) => folder, 190 - None => throw!("Attempted to move to non-existant folder"), 191 - _ => throw!("Attempted to move to non-folder"), 192 - }; 203 + // check that the to_id is valid before we remove it from from_id 204 + get_children_if_user_editable(&mut data.library, &to_id)?; 193 205 194 - let from_folder = match data.library.trackLists.get_mut(&from_id) { 195 - Some(TrackList::Folder(folder)) => folder, 196 - None => throw!("Attempted to move from non-existant folder"), 197 - _ => throw!("Attempted to move from non-folder"), 198 - }; 199 - let mut children = from_folder.children.iter(); 200 - let i = match children.position(|child_id| child_id == &id) { 206 + let children = get_children_if_user_editable(&mut data.library, &from_id)?; 207 + let i = match children.iter().position(|child_id| child_id == &id) { 201 208 None => throw!("Could not find playlist"), 202 209 Some(i) => i, 203 210 }; 204 - from_folder.children.remove(i); 211 + children.remove(i); 205 212 206 - let to_folder = match data.library.trackLists.get_mut(&to_id) { 207 - Some(TrackList::Folder(folder)) => folder, 208 - _ => throw!("Unexpected"), 209 - }; 210 - to_folder.children.push(id.clone()); 213 + let to_folder_children = get_children_if_user_editable(&mut data.library, &to_id)?; 214 + to_folder_children.push(id.clone()); 211 215 212 216 return ctx.env.get_undefined(); 213 217 }
+2
src/App.svelte
··· 12 12 import { iTunesImport, showOpenDialog } from './lib/window' 13 13 import { isMac, paths, importTracks } from './lib/data' 14 14 import { playPause } from './lib/player' 15 + import DragGhost from './components/DragGhost.svelte' 15 16 16 17 let pageStatus = '' 17 18 let pageStatusWarnings = '' ··· 159 160 </main> 160 161 <TrackInfo /> 161 162 <PlaylistInfo /> 163 + <DragGhost /> 162 164 {#if isMac} 163 165 <div class="titlebar" on:mousedown|self|preventDefault /> 164 166 {/if}
+25
src/components/DragGhost.svelte
··· 1 + <script lang="ts" context="module"> 2 + export let dragEl: HTMLElement 3 + let dragElDiv: HTMLElement 4 + export function setInnerText(text: string) { 5 + dragElDiv.innerText = text 6 + } 7 + </script> 8 + 9 + <div class="drag-ghost" bind:this={dragEl}> 10 + <div bind:this={dragElDiv} /> 11 + </div> 12 + 13 + <style lang="sass"> 14 + .drag-ghost 15 + font-size: 14px 16 + top: -1000px 17 + position: absolute 18 + background-color: transparent 19 + padding-left: 3px 20 + div 21 + background-color: var(--drag-bg-color) 22 + padding: 4px 8px 23 + max-width: 300px 24 + border-radius: 3px 25 + </style>
+34 -5
src/components/Sidebar.svelte
··· 1 1 <script lang="ts"> 2 2 import SidebarItems, { hideFolder, showFolder, SidebarItemHandle } from './SidebarItems.svelte' 3 3 import Filter from './Filter.svelte' 4 - import { isMac, trackLists, page } from '../lib/data' 4 + import { isMac, trackLists, page, movePlaylist } from '../lib/data' 5 5 import { ipcRenderer } from '../lib/window' 6 6 import { open as openNewPlaylistModal } from './PlaylistInfo.svelte' 7 7 import { writable } from 'svelte/store' 8 8 import { setContext } from 'svelte' 9 + import { dragged } from '../lib/drag-drop' 9 10 10 11 const special = { 11 12 children: ['root'], ··· 54 55 function open(id: string) { 55 56 if ($page.id !== id) page.openPlaylist(id) 56 57 } 58 + 59 + let rootDroppable = false 57 60 </script> 58 61 59 62 <div class="sidebar" on:mousedown|self|preventDefault> ··· 62 65 {/if} 63 66 <div class="content"> 64 67 <Filter /> 65 - <div class="items" tabindex="0" on:keydown={handleKeydown} bind:this={viewport}> 68 + <div 69 + class="items" 70 + tabindex="0" 71 + on:keydown={handleKeydown} 72 + bind:this={viewport} 73 + class:droppable={rootDroppable} 74 + on:dragover|self={(e) => { 75 + if (e.currentTarget && e.dataTransfer?.types[0] === 'ferrum.playlist') { 76 + rootDroppable = true 77 + e.preventDefault() 78 + } 79 + }} 80 + on:dragleave|self={() => { 81 + rootDroppable = false 82 + }} 83 + on:drop|self={(e) => { 84 + if (e.currentTarget && e.dataTransfer?.types[0] === 'ferrum.playlist' && dragged.playlist) { 85 + movePlaylist(dragged.playlist.id, dragged.playlist.fromFolder, 'root') 86 + rootDroppable = false 87 + } 88 + }} 89 + > 66 90 <div class="spacer" /> 67 91 <SidebarItems 68 92 trackList={special} ··· 73 97 }} 74 98 parentId={null} 75 99 /> 76 - <div class="spacer" on:contextmenu={onContextMenu} /> 100 + <div class="spacer pointer-events-none" on:contextmenu={onContextMenu} /> 77 101 <SidebarItems 78 102 trackList={$trackLists.root} 79 103 on:selectUp={() => { ··· 81 105 }} 82 106 parentId={$trackLists.root.id} 83 107 /> 84 - <div class="spacer" on:contextmenu={onContextMenu} /> 85 - <div class="bottom-space" on:contextmenu={onContextMenu} /> 108 + <div class="spacer pointer-events-none" on:contextmenu={onContextMenu} /> 109 + <div class="bottom-space pointer-events-none" on:contextmenu={onContextMenu} /> 86 110 </div> 87 111 </div> 88 112 </div> 89 113 90 114 <style lang="sass"> 115 + .pointer-events-none 116 + pointer-events: none 91 117 .sidebar 92 118 width: 230px 93 119 min-width: 230px ··· 118 144 flex-direction: column 119 145 .bottom-space 120 146 flex-grow: 1 147 + .droppable 148 + box-shadow: inset 0px 0px 0px 2px var(--accent-1) 149 + background-color: hsla(var(--hue), 74%, 53%, 0.1) 121 150 </style>
+3 -20
src/components/SidebarItems.svelte
··· 30 30 import { createEventDispatcher, SvelteComponent } from 'svelte' 31 31 import { getContext } from 'svelte' 32 32 import { dragged } from '../lib/drag-drop' 33 + import * as dragGhost from './DragGhost.svelte' 33 34 34 35 export let parentId: string | null 35 36 export let show = true ··· 115 116 let dragTrackOntoIndex = null as number | null 116 117 let dragPlaylistOntoIndex = null as number | null 117 118 118 - let dragEl: HTMLElement 119 - let dragElDiv: HTMLElement 120 119 let playlistId: string | null = null 121 120 function onDragStart(e: DragEvent, tracklist: TrackList) { 122 121 if (e.dataTransfer && tracklist.type !== 'special' && parentId) { 123 122 e.dataTransfer.effectAllowed = 'move' 124 123 playlistId = tracklist.id 125 - dragElDiv.innerText = tracklist.name 124 + dragGhost.setInnerText(tracklist.name) 126 125 dragged.playlist = { 127 126 id: playlistId, 128 127 fromFolder: parentId, 129 128 } 130 - e.dataTransfer.setDragImage(dragEl, 0, 0) 129 + e.dataTransfer.setDragImage(dragGhost.dragEl, 0, 0) 131 130 e.dataTransfer.setData('ferrum.playlist', '') 132 131 } 133 132 } 134 133 </script> 135 134 136 - <div class="drag-ghost" bind:this={dragEl}> 137 - <div bind:this={dragElDiv} /> 138 - </div> 139 - 140 135 <div class="sub" class:show> 141 136 {#each childLists as childList, i} 142 137 {#if childList.type === 'folder'} ··· 154 149 e.dataTransfer?.types[0] === 'ferrum.playlist' && 155 150 dragged.playlist 156 151 ) { 157 - console.log('move') 158 152 movePlaylist(dragged.playlist.id, dragged.playlist.fromFolder, childList.id) 159 153 } 160 154 dragPlaylistOntoIndex = null ··· 315 309 line-height: 24px 316 310 flex-grow: 1 317 311 position: relative 318 - .drag-ghost 319 - font-size: 14px 320 - top: -1000px 321 - position: absolute 322 - background-color: transparent 323 - padding-left: 3px 324 - div 325 - background-color: var(--drag-bg-color) 326 - padding: 4px 8px 327 - max-width: 300px 328 - border-radius: 3px 329 312 </style>
+4 -20
src/components/TrackList.svelte
··· 233 233 newPlaybackInstance(page.getTrackIds(), index) 234 234 } 235 235 236 - let dragEl: HTMLElement 237 - let dragElDiv: HTMLElement 238 236 let dragLine: HTMLElement 239 237 let dragging = false 240 238 let indexes: number[] = [] 239 + import * as dragGhost from './DragGhost.svelte' 241 240 function onDragStart(e: DragEvent) { 242 241 if (e.dataTransfer) { 243 242 indexes = [] ··· 250 249 e.dataTransfer.effectAllowed = 'move' 251 250 if (indexes.length === 1) { 252 251 const track = page.getTrack(indexes[0]) 253 - dragElDiv.innerText = track.artist + ' - ' + track.name 252 + dragGhost.setInnerText(track.artist + ' - ' + track.name) 254 253 } else { 255 - dragElDiv.innerText = indexes.length + ' items' 254 + dragGhost.setInnerText(indexes.length + ' items') 256 255 } 257 256 dragged.tracks.indexes = indexes 258 257 dragged.tracks.ids = indexes.map((i) => page.getTrackId(i)) 259 - e.dataTransfer.setDragImage(dragEl, 0, 0) 258 + e.dataTransfer.setDragImage(dragGhost.dragEl, 0, 0) 260 259 e.dataTransfer.setData('ferrum.tracks', '') 261 260 } 262 261 } ··· 325 324 </script> 326 325 327 326 <svelte:window on:dragover={globalDragOverHandler} /> 328 - 329 - <div class="drag-ghost" bind:this={dragEl}> 330 - <div bind:this={dragElDiv} /> 331 - </div> 332 327 333 328 <div class="tracklist" on:dragleave={() => (dragToIndex = null)}> 334 329 <div class="header"> ··· 556 551 .year 557 552 width: 0px 558 553 min-width: 35px 559 - .drag-ghost 560 - font-size: 14px 561 - top: -1000px 562 - position: absolute 563 - background-color: transparent 564 - padding-left: 3px 565 - div 566 - background-color: var(--drag-bg-color) 567 - padding: 4px 8px 568 - max-width: 300px 569 - border-radius: 3px 570 554 .drag-line 571 555 position: absolute 572 556 width: 100%