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

Fix lints

Kasper (May 23, 2025, 2:04 PM +0200) 54040e0a db4a1ce9

+70 -45
+4 -1
eslint.config.mjs
··· 36 36 'error', 37 37 { 38 38 selector: 'variableLike', 39 - format: ['snake_case', 'UPPER_CASE'], 39 + format: ['snake_case', 'UPPER_CASE', 'PascalCase'], 40 40 leadingUnderscore: 'allow', 41 41 }, 42 42 { ··· 59 59 varsIgnorePattern: '^_', 60 60 }, 61 61 ], 62 + eqeqeq: ['error', 'always'], 63 + 'svelte/button-has-type': 'error', 64 + 'svelte/require-each-key': 'off', // Unnecessary each key probably has performance downside 62 65 }, 63 66 }, 64 67 ]
+2 -1
src/App.svelte
··· 215 215 <div class="drag-overlay" transition:fade={{ duration: 100 }}> 216 216 <h1>Drop files to import</h1> 217 217 </div> 218 + <!-- svelte-ignore a11y_interactive_supports_focus --> 218 219 <div 219 220 class="dropzone" 220 221 on:dragleave={drag_leave} ··· 223 224 role="dialog" 224 225 aria-label="Drop files to import" 225 226 aria-dropeffect="copy" 226 - /> 227 + ></div> 227 228 {/if} 228 229 </main> 229 230
+2 -2
src/components/ArtistList.svelte
··· 3 3 import fuzzysort from 'fuzzysort' 4 4 import Header from './Header.svelte' 5 5 6 - $: all_artists = get_artists() 6 + let all_artists = get_artists() 7 7 $: artists = fuzzysort.go($filter, all_artists, { all: true }) 8 8 </script> 9 9 ··· 14 14 15 15 <div class="size-full overflow-y-auto text-sm"> 16 16 {#each artists as artist} 17 - <p class="block py-1 px-3 text-current"> 17 + <p class="block px-3 py-1 text-current"> 18 18 {#if artist.target} 19 19 {artist.target} 20 20 {:else}
+1 -1
src/components/CheckForUpdates.svelte
··· 54 54 Ferrum {latest_update.channel.version} is available. You are currently on {latest_update.app_version} 55 55 </p> 56 56 <p class="pb-1 font-semibold">Release notes:</p> 57 - <p class="max-h-72 overflow-y-auto whitespace-pre-wrap rounded bg-black/30 py-2 px-3"> 57 + <p class="max-h-72 overflow-y-auto rounded bg-black/30 px-3 py-2 whitespace-pre-wrap"> 58 58 {latest_update.body} 59 59 </p> 60 60 </div>
+4 -4
src/components/DragGhost.svelte
··· 1 1 <script lang="ts" context="module"> 2 2 export let drag_el: HTMLElement 3 - let drag_el_div: HTMLElement 4 - export function set_inner_text(text: string) { 5 - drag_el_div.innerText = text 3 + let text = '' 4 + export function set_inner_text(new_text: string) { 5 + text = new_text 6 6 } 7 7 </script> 8 8 9 9 <div class="drag-ghost" bind:this={drag_el}> 10 - <div bind:this={drag_el_div} /> 10 + <div>{text}</div> 11 11 </div> 12 12 13 13 <style lang="sass">
+3 -3
src/components/Header.svelte
··· 8 8 export let description: string | undefined 9 9 </script> 10 10 11 - <div class="relative pt-4 px-5 pb-5"> 11 + <div class="relative px-5 pt-4 pb-5"> 12 12 <div 13 13 class="absolute top-0 left-0 h-10 w-full" 14 14 class:dragbar={$modal_count === 0 && is_mac} 15 15 class:queue-visible={$queue_visible} 16 - /> 17 - <h3 class="m-0 pb-0.5 text-[19px] font-medium leading-none"> 16 + ></div> 17 + <h3 class="m-0 pb-0.5 text-[19px] leading-none font-medium"> 18 18 {title} 19 19 <div class="text-[13px] leading-4 opacity-70">{subtitle}</div> 20 20 </h3>
+1 -2
src/components/Modal.svelte
··· 37 37 }} 38 38 /> 39 39 40 - <div class="dragbar absolute top-0 left-0 w-full" /> 40 + <div class="dragbar absolute top-0 left-0 w-full"></div> 41 41 42 - <!-- svelte-ignore a11y-no-noninteractive-element-interactions --> 43 42 <dialog 44 43 class="modal m-auto" 45 44 bind:this={dialog_el}
+24 -3
src/components/Player.svelte
··· 92 92 <div class="middle"> 93 93 <div class="controls"> 94 94 <button 95 + type="button" 96 + aria-label="Shuffle" 95 97 class="side-controls shuffle" 96 98 class:on={$shuffle} 97 99 tabindex="-1" ··· 111 113 > 112 114 </div> 113 115 </button> 114 - <button class="previous" on:click={previous} tabindex="-1" on:mousedown|preventDefault> 116 + <button 117 + type="button" 118 + aria-label="Previous" 119 + class="previous" 120 + on:click={previous} 121 + tabindex="-1" 122 + on:mousedown|preventDefault 123 + > 115 124 <svg 116 125 xmlns="http://www.w3.org/2000/svg" 117 126 class="parent-active-zoom" ··· 126 135 </button> 127 136 128 137 <button 138 + type="button" 129 139 class="play-pause" 130 140 on:click={play_pause} 131 141 class:cannot-play={$tracks_page_item_ids.length === 0} ··· 159 169 {/if} 160 170 </button> 161 171 162 - <button class="next" on:click={skip_to_next} tabindex="-1" on:mousedown|preventDefault> 172 + <button 173 + type="button" 174 + aria-label="Next" 175 + class="next" 176 + on:click={skip_to_next} 177 + tabindex="-1" 178 + on:mousedown|preventDefault 179 + > 163 180 <svg 164 181 xmlns="http://www.w3.org/2000/svg" 165 182 class="parent-active-zoom" ··· 174 191 </button> 175 192 176 193 <button 194 + type="button" 195 + aria-label="Repeat" 177 196 class="side-controls repeat" 178 197 class:on={$repeat} 179 198 tabindex="-1" ··· 210 229 </div> 211 230 </div> 212 231 <div class="right"> 213 - <button class="volume-icon" tabindex="-1" on:click={volume.toggle}> 232 + <button type="button" class="volume-icon" tabindex="-1" on:click={volume.toggle}> 214 233 {#if $volume > 0.5} 215 234 <svg 216 235 class="high parent-active-zoom" ··· 254 273 </button> 255 274 <Slider class="mr-2 w-[110px]" bind:value={$volume} max={1} /> 256 275 <button 276 + type="button" 277 + aria-label="Toggle queue" 257 278 tabindex="-1" 258 279 on:mousedown|preventDefault 259 280 on:click={toggle_queue_visibility}
+2 -5
src/components/PlaylistInfo.svelte
··· 37 37 > 38 38 <main class="space-y-1"> 39 39 <input type="text" bind:value={info.name} placeholder="Title" /> 40 - <textarea 41 - rows={rows(info.description)} 42 - bind:value={info.description} 43 - placeholder="Description" 44 - /> 40 + <textarea rows={rows(info.description)} bind:value={info.description} placeholder="Description" 41 + ></textarea> 45 42 </main> 46 43 <svelte:fragment slot="buttons"> 47 44 <Button secondary on:click={cancel}>Cancel</Button>
+4 -3
src/components/Queue.svelte
··· 181 181 </script> 182 182 183 183 <aside transition:fly={{ x: '100%', duration: 150, opacity: 1 }}> 184 - <div class="shadow" /> 184 + <div class="shadow"></div> 185 185 <!-- svelte-ignore a11y-no-static-element-interactions --> 186 186 <div 187 187 bind:this={queue_element} ··· 202 202 <div class="relative"> 203 203 <div class="sticky top-0 z-1 flex flex h-[40px] items-center bg-black/50 backdrop-blur-md"> 204 204 <button 205 + type="button" 205 206 on:click={() => { 206 207 show_history = !show_history 207 208 tick().then(() => { ··· 229 230 > 230 231 History 231 232 </button> 232 - <div class="h-full w-0 grow" style:-webkit-app-region="drag" /> 233 + <div class="h-full w-0 grow" style:-webkit-app-region="drag"></div> 233 234 </div> 234 235 {#if show_history} 235 236 <VirtualListBlock ··· 391 392 bind:this={drag_line} 392 393 class="drag-line pointer-events-none absolute z-10 h-[2px] w-full bg-[var(--drag-line-color)]" 393 394 class:hidden={drag_to_index === null} 394 - /> 395 + ></div> 395 396 </div> 396 397 </aside> 397 398
+10 -5
src/components/Sidebar.svelte
··· 91 91 <!-- NOTE: aside is used as css selector in SidebarItems --> 92 92 <aside on:mousedown|self|preventDefault role="none"> 93 93 {#if is_mac} 94 - <div class="titlebar" on:mousedown|self|preventDefault role="none" /> 94 + <div class="titlebar" on:mousedown|self|preventDefault role="none"></div> 95 95 {/if} 96 96 <div class="content" bind:this={content_element}> 97 97 <Filter ··· 118 118 if (e.key === 'Escape') { 119 119 e.preventDefault() 120 120 tracklist_actions.focus() 121 - } else if (e.key == 'Home' || e.key == 'End' || e.key == 'PageUp' || e.key == 'PageDown') { 121 + } else if ( 122 + e.key === 'Home' || 123 + e.key === 'End' || 124 + e.key === 'PageUp' || 125 + e.key === 'PageDown' 126 + ) { 122 127 e.preventDefault() 123 128 } else { 124 129 $item_handle?.handleKey(e) ··· 132 137 on:drop|self={drop} 133 138 > 134 139 <!-- svelte-ignore a11y-no-noninteractive-tabindex --> 135 - <div class="focuser" tabindex="0" on:focus={focuser} /> 136 - <div class="spacer" /> 140 + <div class="focuser" tabindex="0" on:focus={focuser}></div> 141 + <div class="spacer"></div> 137 142 <SidebarItems 138 143 parent_path={null} 139 144 children={special_playlists_nav} ··· 143 148 } 144 149 }} 145 150 /> 146 - <div class="spacer" /> 151 + <div class="spacer"></div> 147 152 <SidebarItems 148 153 parent_path="/playlist/root" 149 154 children={($track_lists_details_map['root'].children || []).map((child_id) => ({
+2 -2
src/components/SidebarItems.svelte
··· 278 278 }} 279 279 on:contextmenu={() => tracklist_context_menu(child_list.id, false)} 280 280 > 281 - <div class="arrow" /> 281 + <div class="arrow"></div> 282 282 <div 283 283 class="text" 284 284 role="link" ··· 315 315 on:mousedown={() => navigate(child_list.path)} 316 316 class:active={child_list.path === $url_pathname} 317 317 > 318 - <div class="arrow" /> 318 + <div class="arrow"></div> 319 319 <div class="text"> 320 320 {child_list.name} 321 321 </div>
+2 -2
src/components/TrackInfo.svelte
··· 421 421 <div class="artist">{artist}</div> 422 422 </div> 423 423 </div> 424 - <div class="spacer" /> 424 + <div class="spacer"></div> 425 425 <div class="row"> 426 426 <div class="label">Title</div> 427 427 <!-- svelte-ignore a11y-autofocus --> ··· 487 487 <div class="label">Comments</div> 488 488 <input type="text" bind:value={comments} /> 489 489 </div> 490 - <div class="spacer" /> 490 + <div class="spacer"></div> 491 491 </main> 492 492 <svelte:fragment slot="buttons"> 493 493 <Button secondary on:click={cancel}>Cancel</Button>
+2 -2
src/components/TrackList.svelte
··· 453 453 class="col-drag-line" 454 454 class:hidden={col_drag_to_index === null} 455 455 bind:this={col_drag_line} 456 - /> 456 + ></div> 457 457 </div> 458 458 <!-- svelte-ignore a11y-no-noninteractive-tabindex --> 459 459 <!-- svelte-ignore a11y-no-static-element-interactions --> ··· 528 528 </div> 529 529 {/if} 530 530 </VirtualListBlock> 531 - <div class="drag-line" class:hidden={drag_to_index === null} bind:this={drag_line} /> 531 + <div class="drag-line" class:hidden={drag_to_index === null} bind:this={drag_line}></div> 532 532 </div> 533 533 </div> 534 534
+4 -5
src/components/VirtualListBlock.svelte
··· 13 13 <script lang="ts" generics="T"> 14 14 import { onDestroy } from 'svelte' 15 15 16 - // eslint-disable-next-line no-undef 17 - type X = T 18 - 19 - export let items: X[] 16 + export let items: T[] 20 17 export let item_height: number 21 18 /** Must be a positioned element, like `position: relative` */ 22 19 export let scroll_container: HTMLElement 23 - export let get_key: (item: X, i: number) => number | string 20 + export let get_key: (item: T, i: number) => number | string 24 21 export let buffer = 3 25 22 26 23 $: height = items.length * item_height ··· 32 29 let visible_count = 0 33 30 34 31 // Workaround for svelte not updating the indexes when the keys change 32 + let visible_count_obj = { length: visible_count } 35 33 $: visible_count_obj = { length: visible_count } 36 34 37 35 $: { ··· 96 94 dummy.style.top = index * item_height + 'px' 97 95 // For some reason we apply the offset to the bottom 98 96 dummy.style.scrollMarginBottom = offset + 'px' 97 + // eslint-disable-next-line svelte/no-dom-manipulating 99 98 main_element.prepend(dummy) 100 99 dummy.scrollIntoView({ behavior: 'instant', block: 'nearest' }) 101 100 dummy.remove()
-1
src/lib/data.ts
··· 21 21 export const is_mac = window.is_mac 22 22 export const is_windws = window.is_windows 23 23 const inner_addon = window.addon 24 - // eslint-disable-next-line @typescript-eslint/naming-convention 25 24 export const ItunesImport = inner_addon.ItunesImport 26 25 27 26 call((addon) => addon.load_data(is_dev, local_data_path, library_path))
+1 -1
src/lib/helpers.ts
··· 28 28 alt?: boolean 29 29 cmd_or_ctrl?: boolean 30 30 } 31 - const is_mac = navigator.userAgent.indexOf('Mac') != -1 31 + const is_mac = navigator.userAgent.indexOf('Mac') !== -1 32 32 33 33 function check_modifiers(e: KeyboardEvent | MouseEvent, options: ShortcutOptions) { 34 34 const target = {
+1 -1
src/lib/queue.ts
··· 63 63 random_index 64 64 65 65 // While there remain elements to shuffle. 66 - while (current_index != 0) { 66 + while (current_index !== 0) { 67 67 // Pick a remaining element. 68 68 random_index = Math.floor(Math.random() * current_index) 69 69 current_index--
+1 -1
src/main.ts
··· 1 1 import './app.css' 2 2 import App from './App.svelte' 3 - import { mount } from "svelte"; 3 + import { mount } from 'svelte' 4 4 5 5 const app = mount(App, { 6 6 target: document.body,