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

WIP datagrid

Kasper (May 26, 2025, 10:45 AM +0200) d093dbe9 ea118a03

+506 -321
+145
package-lock.json
··· 9 9 "version": "0.19.3", 10 10 "devDependencies": { 11 11 "@napi-rs/cli": "^2.18.4", 12 + "@revolist/revogrid": "^4.15.8", 13 + "@stencil/core": "^4.31.0", 12 14 "@sveltejs/vite-plugin-svelte": "^5.0.3", 13 15 "@tailwindcss/vite": "^4.1.7", 14 16 "@tsconfig/svelte": "^5.0.4", ··· 2040 2042 "node": ">=14" 2041 2043 } 2042 2044 }, 2045 + "node_modules/@revolist/revogrid": { 2046 + "version": "4.15.8", 2047 + "resolved": "https://registry.npmjs.org/@revolist/revogrid/-/revogrid-4.15.8.tgz", 2048 + "integrity": "sha512-XP6EvH2D+VukmGDunGQieOTX3rjXmfx4YCEMerzzZMjD1lrztUQFqOxkPR9U4E5KvhU79nn/K9WPo8u7b6VCVw==", 2049 + "dev": true, 2050 + "license": "MIT" 2051 + }, 2043 2052 "node_modules/@rollup/rollup-android-arm-eabi": { 2044 2053 "version": "4.41.0", 2045 2054 "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.0.tgz", ··· 2332 2341 "funding": { 2333 2342 "url": "https://github.com/sindresorhus/is?sponsor=1" 2334 2343 } 2344 + }, 2345 + "node_modules/@stencil/core": { 2346 + "version": "4.31.0", 2347 + "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.31.0.tgz", 2348 + "integrity": "sha512-Ei9MFJ6LPD9BMFs+klkHylbVOOYhG10Jv4bvoFf3GMH15kA41rSYkEdr4DiX84ZdErQE2qtFV/2SUyWoXh0AhA==", 2349 + "dev": true, 2350 + "license": "MIT", 2351 + "bin": { 2352 + "stencil": "bin/stencil" 2353 + }, 2354 + "engines": { 2355 + "node": ">=16.0.0", 2356 + "npm": ">=7.10.0" 2357 + }, 2358 + "optionalDependencies": { 2359 + "@rollup/rollup-darwin-arm64": "4.34.9", 2360 + "@rollup/rollup-darwin-x64": "4.34.9", 2361 + "@rollup/rollup-linux-arm64-gnu": "4.34.9", 2362 + "@rollup/rollup-linux-arm64-musl": "4.34.9", 2363 + "@rollup/rollup-linux-x64-gnu": "4.34.9", 2364 + "@rollup/rollup-linux-x64-musl": "4.34.9", 2365 + "@rollup/rollup-win32-arm64-msvc": "4.34.9", 2366 + "@rollup/rollup-win32-x64-msvc": "4.34.9" 2367 + } 2368 + }, 2369 + "node_modules/@stencil/core/node_modules/@rollup/rollup-darwin-arm64": { 2370 + "version": "4.34.9", 2371 + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.9.tgz", 2372 + "integrity": "sha512-0CY3/K54slrzLDjOA7TOjN1NuLKERBgk9nY5V34mhmuu673YNb+7ghaDUs6N0ujXR7fz5XaS5Aa6d2TNxZd0OQ==", 2373 + "cpu": [ 2374 + "arm64" 2375 + ], 2376 + "dev": true, 2377 + "license": "MIT", 2378 + "optional": true, 2379 + "os": [ 2380 + "darwin" 2381 + ] 2382 + }, 2383 + "node_modules/@stencil/core/node_modules/@rollup/rollup-darwin-x64": { 2384 + "version": "4.34.9", 2385 + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.9.tgz", 2386 + "integrity": "sha512-eOojSEAi/acnsJVYRxnMkPFqcxSMFfrw7r2iD9Q32SGkb/Q9FpUY1UlAu1DH9T7j++gZ0lHjnm4OyH2vCI7l7Q==", 2387 + "cpu": [ 2388 + "x64" 2389 + ], 2390 + "dev": true, 2391 + "license": "MIT", 2392 + "optional": true, 2393 + "os": [ 2394 + "darwin" 2395 + ] 2396 + }, 2397 + "node_modules/@stencil/core/node_modules/@rollup/rollup-linux-arm64-gnu": { 2398 + "version": "4.34.9", 2399 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.9.tgz", 2400 + "integrity": "sha512-6TZjPHjKZUQKmVKMUowF3ewHxctrRR09eYyvT5eFv8w/fXarEra83A2mHTVJLA5xU91aCNOUnM+DWFMSbQ0Nxw==", 2401 + "cpu": [ 2402 + "arm64" 2403 + ], 2404 + "dev": true, 2405 + "license": "MIT", 2406 + "optional": true, 2407 + "os": [ 2408 + "linux" 2409 + ] 2410 + }, 2411 + "node_modules/@stencil/core/node_modules/@rollup/rollup-linux-arm64-musl": { 2412 + "version": "4.34.9", 2413 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.9.tgz", 2414 + "integrity": "sha512-LD2fytxZJZ6xzOKnMbIpgzFOuIKlxVOpiMAXawsAZ2mHBPEYOnLRK5TTEsID6z4eM23DuO88X0Tq1mErHMVq0A==", 2415 + "cpu": [ 2416 + "arm64" 2417 + ], 2418 + "dev": true, 2419 + "license": "MIT", 2420 + "optional": true, 2421 + "os": [ 2422 + "linux" 2423 + ] 2424 + }, 2425 + "node_modules/@stencil/core/node_modules/@rollup/rollup-linux-x64-gnu": { 2426 + "version": "4.34.9", 2427 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.9.tgz", 2428 + "integrity": "sha512-FwBHNSOjUTQLP4MG7y6rR6qbGw4MFeQnIBrMe161QGaQoBQLqSUEKlHIiVgF3g/mb3lxlxzJOpIBhaP+C+KP2A==", 2429 + "cpu": [ 2430 + "x64" 2431 + ], 2432 + "dev": true, 2433 + "license": "MIT", 2434 + "optional": true, 2435 + "os": [ 2436 + "linux" 2437 + ] 2438 + }, 2439 + "node_modules/@stencil/core/node_modules/@rollup/rollup-linux-x64-musl": { 2440 + "version": "4.34.9", 2441 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.9.tgz", 2442 + "integrity": "sha512-cYRpV4650z2I3/s6+5/LONkjIz8MBeqrk+vPXV10ORBnshpn8S32bPqQ2Utv39jCiDcO2eJTuSlPXpnvmaIgRA==", 2443 + "cpu": [ 2444 + "x64" 2445 + ], 2446 + "dev": true, 2447 + "license": "MIT", 2448 + "optional": true, 2449 + "os": [ 2450 + "linux" 2451 + ] 2452 + }, 2453 + "node_modules/@stencil/core/node_modules/@rollup/rollup-win32-arm64-msvc": { 2454 + "version": "4.34.9", 2455 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.9.tgz", 2456 + "integrity": "sha512-z4mQK9dAN6byRA/vsSgQiPeuO63wdiDxZ9yg9iyX2QTzKuQM7T4xlBoeUP/J8uiFkqxkcWndWi+W7bXdPbt27Q==", 2457 + "cpu": [ 2458 + "arm64" 2459 + ], 2460 + "dev": true, 2461 + "license": "MIT", 2462 + "optional": true, 2463 + "os": [ 2464 + "win32" 2465 + ] 2466 + }, 2467 + "node_modules/@stencil/core/node_modules/@rollup/rollup-win32-x64-msvc": { 2468 + "version": "4.34.9", 2469 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.9.tgz", 2470 + "integrity": "sha512-AyleYRPU7+rgkMWbEh71fQlrzRfeP6SyMnRf9XX4fCdDPAJumdSBqYEcWPMzVQ4ScAl7E4oFfK0GUVn77xSwbw==", 2471 + "cpu": [ 2472 + "x64" 2473 + ], 2474 + "dev": true, 2475 + "license": "MIT", 2476 + "optional": true, 2477 + "os": [ 2478 + "win32" 2479 + ] 2335 2480 }, 2336 2481 "node_modules/@sveltejs/acorn-typescript": { 2337 2482 "version": "1.0.5",
+5
package.json
··· 66 66 "napi": { 67 67 "name": "addon" 68 68 }, 69 + "//": [ 70 + "@stencil/core is required by @revolist/revogrid" 71 + ], 69 72 "devDependencies": { 70 73 "@napi-rs/cli": "^2.18.4", 74 + "@revolist/revogrid": "^4.15.8", 75 + "@stencil/core": "^4.31.0", 71 76 "@sveltejs/vite-plugin-svelte": "^5.0.3", 72 77 "@tailwindcss/vite": "^4.1.7", 73 78 "@tsconfig/svelte": "^5.0.4",
+356 -321
src/components/TrackList.svelte
··· 47 47 import { SvelteSelection } from '@/lib/selection' 48 48 import { get_flattened_tracklists, handle_selected_tracks_action } from '@/lib/menus' 49 49 import type { SelectedTracksAction } from '@/electron/typed_ipc' 50 + import { defineCustomElement } from '@revolist/revogrid/standalone' 51 + import type { ColumnRegular } from '@revolist/revogrid' 50 52 51 - let tracklist_element: HTMLDivElement 53 + defineCustomElement() 54 + 55 + // let tracklist_element: HTMLDivElement 52 56 53 57 export let params: { playlist_id: string } 54 58 $: $current_playlist_id = params.playlist_id ··· 107 111 }) 108 112 $: selection.update_all_items(tracks_page.itemIds) 109 113 110 - const track_action_unlisten = ipc_listen('selected_tracks_action', (_, action) => { 111 - if (tracklist_element.contains(document.activeElement)) { 112 - handle_action(action) 113 - } 114 - }) 115 - onDestroy(track_action_unlisten) 114 + // const track_action_unlisten = ipc_listen('selected_tracks_action', (_, action) => { 115 + // if (tracklist_element.contains(document.activeElement)) { 116 + // handle_action(action) 117 + // } 118 + // }) 119 + // onDestroy(track_action_unlisten) 116 120 117 121 ipc_renderer.on('Group Album Tracks', (_, checked) => { 118 122 group_album_tracks.set(checked) 119 123 }) 120 124 121 - function double_click(e: MouseEvent, index: number) { 122 - if (e.button === 0 && check_mouse_shortcut(e)) { 123 - play_row(index) 124 - } 125 - } 125 + // function double_click(e: MouseEvent, index: number) { 126 + // if (e.button === 0 && check_mouse_shortcut(e)) { 127 + // play_row(index) 128 + // } 129 + // } 126 130 async function delete_tracks(item_ids: ItemId[]) { 127 131 const s = selection.items.size > 1 ? 's' : '' 128 132 const result = await ipc_renderer.invoke('showMessageBox', false, { ··· 135 139 delete_tracks_with_item_ids(item_ids) 136 140 } 137 141 } 138 - async function keydown(e: KeyboardEvent) { 139 - if (check_shortcut(e, 'Enter')) { 140 - let first_item_id = selection.find_first_index() 141 - if (first_item_id !== null) { 142 - play_row(first_item_id) 143 - } else if (!$playing_id) { 144 - play_row(0) 145 - } 146 - } else if ( 147 - check_shortcut(e, 'Backspace') && 148 - selection.items.size > 0 && 149 - $filter === '' && 150 - tracks_page.playlistKind === 'playlist' 151 - ) { 152 - e.preventDefault() 153 - const s = selection.items.size > 1 ? 's' : '' 154 - const result = ipc_renderer.invoke('showMessageBox', false, { 155 - type: 'info', 156 - message: `Remove ${selection.items.size} song${s} from the list?`, 157 - buttons: ['Remove Song' + s, 'Cancel'], 158 - defaultId: 0, 159 - }) 160 - if ((await result).response === 0) { 161 - remove_from_playlist(params.playlist_id, Array.from(selection.items)) 162 - } 163 - } else if (check_shortcut(e, 'Backspace', { cmd_or_ctrl: true }) && $selection.size > 0) { 164 - e.preventDefault() 165 - handle_action('Delete from Library') 166 - } else { 167 - selection.handle_keydown(e) 168 - return 169 - } 170 - e.preventDefault() 171 - } 142 + // async function keydown(e: KeyboardEvent) { 143 + // if (check_shortcut(e, 'Enter')) { 144 + // let first_item_id = selection.find_first_index() 145 + // if (first_item_id !== null) { 146 + // play_row(first_item_id) 147 + // } else if (!$playing_id) { 148 + // play_row(0) 149 + // } 150 + // } else if ( 151 + // check_shortcut(e, 'Backspace') && 152 + // selection.items.size > 0 && 153 + // $filter === '' && 154 + // tracks_page.playlistKind === 'playlist' 155 + // ) { 156 + // e.preventDefault() 157 + // const s = selection.items.size > 1 ? 's' : '' 158 + // const result = ipc_renderer.invoke('showMessageBox', false, { 159 + // type: 'info', 160 + // message: `Remove ${selection.items.size} song${s} from the list?`, 161 + // buttons: ['Remove Song' + s, 'Cancel'], 162 + // defaultId: 0, 163 + // }) 164 + // if ((await result).response === 0) { 165 + // remove_from_playlist(params.playlist_id, Array.from(selection.items)) 166 + // } 167 + // } else if (check_shortcut(e, 'Backspace', { cmd_or_ctrl: true }) && $selection.size > 0) { 168 + // e.preventDefault() 169 + // handle_action('Delete from Library') 170 + // } else { 171 + // selection.handle_keydown(e) 172 + // return 173 + // } 174 + // e.preventDefault() 175 + // } 172 176 173 - function play_row(index: number) { 174 - const all_track_ids = get_track_ids(tracks_page.itemIds) 175 - new_playback_instance(all_track_ids, index) 176 - } 177 + // function play_row(index: number) { 178 + // const all_track_ids = get_track_ids(tracks_page.itemIds) 179 + // new_playback_instance(all_track_ids, index) 180 + // } 177 181 178 - let drag_line: HTMLElement 179 - let drag_item_ids: ItemId[] = [] 180 - function on_drag_start(e: DragEvent) { 181 - if (e.dataTransfer) { 182 - drag_item_ids = Array.from(selection.items) 183 - e.dataTransfer.effectAllowed = 'move' 184 - if (drag_item_ids.length === 1) { 185 - const { track } = get_track_by_item_id(drag_item_ids[0]) 186 - dragGhost.set_inner_text(track.artist + ' - ' + track.name) 187 - } else { 188 - dragGhost.set_inner_text(drag_item_ids.length + ' items') 189 - } 190 - dragged.tracks = { 191 - ids: get_track_ids(drag_item_ids), 192 - playlist_indexes: drag_item_ids, 193 - } 194 - e.dataTransfer.setDragImage(dragGhost.drag_el, 0, 0) 195 - e.dataTransfer.setData('ferrum.tracks', '') 196 - } 197 - } 198 - let drag_to_index: null | number = null 199 - function on_drag_over(e: DragEvent, index: number) { 200 - if ( 201 - !$sort_desc || 202 - $sort_key !== 'index' || 203 - $filter || 204 - tracks_page.playlistKind !== 'playlist' 205 - ) { 206 - drag_to_index = null 207 - return 208 - } 209 - if ( 210 - dragged.tracks?.playlist_indexes && 211 - e.currentTarget instanceof HTMLElement && 212 - e.dataTransfer?.types[0] === 'ferrum.tracks' 213 - ) { 214 - e.preventDefault() 215 - const rect = e.currentTarget.getBoundingClientRect() 216 - const container_rect = scroll_container.getBoundingClientRect() 217 - if (e.pageY < rect.bottom - rect.height / 2) { 218 - const top = rect.top - container_rect.top + scroll_container.scrollTop - 1 219 - drag_line.style.top = Math.max(0, top) + 'px' 220 - drag_to_index = index 221 - } else { 222 - const top = rect.bottom - container_rect.top + scroll_container.scrollTop - 1 223 - const max_top = scroll_container.scrollHeight - 2 224 - drag_line.style.top = Math.min(max_top, top) + 'px' 225 - drag_to_index = index + 1 226 - } 227 - } 228 - } 229 - async function drop_handler() { 230 - if (drag_to_index !== null) { 231 - move_tracks(params.playlist_id, drag_item_ids, drag_to_index) 232 - drag_to_index = null 233 - } 234 - } 235 - function drag_end_handler() { 236 - drag_to_index = null 237 - } 182 + // let drag_line: HTMLElement 183 + // let drag_item_ids: ItemId[] = [] 184 + // function on_drag_start(e: DragEvent) { 185 + // if (e.dataTransfer) { 186 + // drag_item_ids = Array.from(selection.items) 187 + // e.dataTransfer.effectAllowed = 'move' 188 + // if (drag_item_ids.length === 1) { 189 + // const { track } = get_track_by_item_id(drag_item_ids[0]) 190 + // dragGhost.set_inner_text(track.artist + ' - ' + track.name) 191 + // } else { 192 + // dragGhost.set_inner_text(drag_item_ids.length + ' items') 193 + // } 194 + // dragged.tracks = { 195 + // ids: get_track_ids(drag_item_ids), 196 + // playlist_indexes: drag_item_ids, 197 + // } 198 + // e.dataTransfer.setDragImage(dragGhost.drag_el, 0, 0) 199 + // e.dataTransfer.setData('ferrum.tracks', '') 200 + // } 201 + // } 202 + // let drag_to_index: null | number = null 203 + // function on_drag_over(e: DragEvent, index: number) { 204 + // if ( 205 + // !$sort_desc || 206 + // $sort_key !== 'index' || 207 + // $filter || 208 + // tracks_page.playlistKind !== 'playlist' 209 + // ) { 210 + // drag_to_index = null 211 + // return 212 + // } 213 + // if ( 214 + // dragged.tracks?.playlist_indexes && 215 + // e.currentTarget instanceof HTMLElement && 216 + // e.dataTransfer?.types[0] === 'ferrum.tracks' 217 + // ) { 218 + // e.preventDefault() 219 + // const rect = e.currentTarget.getBoundingClientRect() 220 + // const container_rect = scroll_container.getBoundingClientRect() 221 + // if (e.pageY < rect.bottom - rect.height / 2) { 222 + // const top = rect.top - container_rect.top + scroll_container.scrollTop - 1 223 + // drag_line.style.top = Math.max(0, top) + 'px' 224 + // drag_to_index = index 225 + // } else { 226 + // const top = rect.bottom - container_rect.top + scroll_container.scrollTop - 1 227 + // const max_top = scroll_container.scrollHeight - 2 228 + // drag_line.style.top = Math.min(max_top, top) + 'px' 229 + // drag_to_index = index + 1 230 + // } 231 + // } 232 + // } 233 + // async function drop_handler() { 234 + // if (drag_to_index !== null) { 235 + // move_tracks(params.playlist_id, drag_item_ids, drag_to_index) 236 + // drag_to_index = null 237 + // } 238 + // } 239 + // function drag_end_handler() { 240 + // drag_to_index = null 241 + // } 238 242 239 243 function get_item(item_id: ItemId) { 240 244 try { ··· 244 248 } 245 249 } 246 250 247 - let virtual_list: VirtualListBlock<ItemId> 251 + // let virtual_list: VirtualListBlock<ItemId> 248 252 249 - $: if (virtual_list) { 250 - virtual_list.refresh() 251 - } 253 + // $: if (virtual_list) { 254 + // virtual_list.refresh() 255 + // } 252 256 253 - let scroll_container: HTMLElement 254 - onMount(() => { 255 - tracklist_actions.scroll_to_index = virtual_list.scroll_to_index 256 - }) 257 + // let scroll_container: HTMLElement 258 + // onMount(() => { 259 + // tracklist_actions.scroll_to_index = virtual_list.scroll_to_index 260 + // }) 257 261 258 - type Column = { 259 - name: string 260 - key: 'index' | 'image' | keyof Track 261 - width?: string 262 - } 263 - const all_columns: Column[] = [ 262 + const all_columns: ColumnRegular[] = [ 264 263 // sorted alphabetically 265 - { name: '#', key: 'index' }, 266 - // { name: 'Size', key: 'size' }, 267 - { name: 'Album', key: 'albumName', width: '90%' }, 268 - { name: 'Album Artist', key: 'albumArtist', width: '90%' }, 269 - { name: 'Artist', key: 'artist', width: '120%' }, 270 - // { name: 'Bitrate', key: 'bitrate' }, 271 - { name: 'BPM', key: 'bpm' }, 272 - { name: 'Comments', key: 'comments', width: '65%' }, 273 - // { name: 'Compilation', key: 'compilation' }, 274 - { name: 'Composer', key: 'composer', width: '65%' }, 275 - { name: 'Date Added', key: 'dateAdded' }, 276 - // { name: 'DateImported', key: 'dateImported' }, 277 - // { name: 'DateModified', key: 'dateModified' }, 278 - // { name: 'Disabled', key: 'disabled' }, 279 - // { name: 'DiscCount', key: 'discCount' }, 280 - // { name: 'DiscNum', key: 'discNum' }, 281 - // { name: 'Disliked', key: 'disliked' }, 282 - { name: 'Time', key: 'duration' }, 283 - { name: 'Genre', key: 'genre', width: '65%' }, 284 - { name: 'Grouping', key: 'grouping', width: '65%' }, 285 - { name: 'Image', key: 'image' }, 286 - // { name: 'ImportedFrom', key: 'importedFrom' }, 287 - // { name: 'Liked', key: 'liked' }, 288 - { name: 'Name', key: 'name', width: '170%' }, 289 - { name: 'Plays', key: 'playCount' }, 290 - // { name: 'Rating', key: 'rating' }, 291 - // { name: 'SampleRate', key: 'sampleRate' }, 292 - { name: 'Skips', key: 'skipCount' }, 293 - // { name: 'Sort Album', key: 'sortAlbumName', width: '65%' }, 294 - // { name: 'Sort Album Artist', key: 'sortAlbumArtist', width: '65%' }, 295 - // { name: 'Sort Artist', key: 'sortArtist', width: '65%' }, 296 - // { name: 'Sort Composer', key: 'sortComposer', width: '65%' }, 297 - // { name: 'Sort Name', key: 'sortName', width: '65%' }, 298 - // { name: 'TrackCount', key: 'trackCount' }, 299 - // { name: 'TrackNum', key: 'trackNum' }, 300 - // { name: 'Volume', key: 'volume' }, 301 - { name: 'Year', key: 'year' }, 264 + { name: '#', prop: 'index' }, 265 + // { name: 'Size', prop: 'size' }, 266 + { name: 'Album', prop: 'albumName', width: '90%' }, 267 + { name: 'Album Artist', prop: 'albumArtist', width: '90%' }, 268 + { name: 'Artist', prop: 'artist', width: '120%' }, 269 + // { name: 'Bitrate', prop: 'bitrate' }, 270 + { name: 'BPM', prop: 'bpm' }, 271 + { name: 'Comments', prop: 'comments', width: '65%' }, 272 + // { name: 'Compilation', prop: 'compilation' }, 273 + { name: 'Composer', prop: 'composer', width: '65%' }, 274 + { name: 'Date Added', prop: 'dateAdded' }, 275 + // { name: 'DateImported', prop: 'dateImported' }, 276 + // { name: 'DateModified', prop: 'dateModified' }, 277 + // { name: 'Disabled', prop: 'disabled' }, 278 + // { name: 'DiscCount', prop: 'discCount' }, 279 + // { name: 'DiscNum', prop: 'discNum' }, 280 + // { name: 'Disliked', prop: 'disliked' }, 281 + { name: 'Time', prop: 'duration' }, 282 + { name: 'Genre', prop: 'genre', width: '65%' }, 283 + { name: 'Grouping', prop: 'grouping', width: '65%' }, 284 + { name: 'Image', prop: 'image' }, 285 + // { name: 'ImportedFrom', prop: 'importedFrom' }, 286 + // { name: 'Liked', prop: 'liked' }, 287 + { name: 'Name', prop: 'name', width: '170%' }, 288 + { name: 'Plays', prop: 'playCount' }, 289 + // { name: 'Rating', prop: 'rating' }, 290 + // { name: 'SampleRate', prop: 'sampleRate' }, 291 + { name: 'Skips', prop: 'skipCount' }, 292 + // { name: 'Sort Album', prop: 'sortAlbumName', width: '65%' }, 293 + // { name: 'Sort Album Artist', prop: 'sortAlbumArtist', width: '65%' }, 294 + // { name: 'Sort Artist', prop: 'sortArtist', width: '65%' }, 295 + // { name: 'Sort Composer', prop: 'sortComposer', width: '65%' }, 296 + // { name: 'Sort Name', prop: 'sortName', width: '65%' }, 297 + // { name: 'TrackCount', prop: 'trackCount' }, 298 + // { name: 'TrackNum', prop: 'trackNum' }, 299 + // { name: 'Volume', prop: 'volume' }, 300 + { name: 'Year', prop: 'year' }, 302 301 ] 303 - const default_columns: Column['key'][] = [ 302 + const default_columns: ('index' | 'image' | keyof Track)[] = [ 304 303 'index', 305 304 'image', 306 305 'name', ··· 313 312 'dateAdded', 314 313 'year', 315 314 ] 316 - let columns: Column[] = load_columns() 317 - function load_columns(): Column[] { 315 + let columns: ColumnRegular[] = load_columns() 316 + function load_columns(): ColumnRegular[] { 318 317 let loaded_columns = view_options.columns 319 318 if (loaded_columns.length === 0) { 320 319 loaded_columns = [...default_columns] 321 320 } 322 - return loaded_columns 323 - .map((key) => all_columns.find((col) => col.key === key)) 321 + const columns = loaded_columns 322 + .map((key) => all_columns.find((col) => col.prop === key)) 324 323 .filter((col) => col !== undefined) 324 + return columns.map((col) => { 325 + return { 326 + ...col, 327 + cellTemplate(create_element, props, additional_data) { 328 + return create_element('div', {}, props.model[props.prop]) 329 + }, 330 + } 331 + }) 325 332 } 326 333 function save_columns() { 327 334 view_options.columns = columns.map((col) => col.key) ··· 330 337 } 331 338 save_view_options(view_options) 332 339 } 333 - function on_column_context_menu() { 334 - ipc_renderer.invoke('show_columns_menu', { 335 - menu: all_columns.map((col) => { 336 - return { 337 - id: col.key, 338 - label: col.name, 339 - type: 'checkbox', 340 - checked: !!columns.find((c) => c.key === col.key), 341 - } 342 - }), 343 - }) 344 - } 340 + // function on_column_context_menu() { 341 + // ipc_renderer.invoke('show_columns_menu', { 342 + // menu: all_columns.map((col) => { 343 + // return { 344 + // id: col.key, 345 + // label: col.name, 346 + // type: 'checkbox', 347 + // checked: !!columns.find((c) => c.key === col.key), 348 + // } 349 + // }), 350 + // }) 351 + // } 345 352 onDestroy( 346 353 ipc_listen('context.toggle_column', (_, item) => { 347 354 if (item.checked) { ··· 358 365 }), 359 366 ) 360 367 361 - let col_container: HTMLElement 362 - let col_drag_line: HTMLElement 363 - let col_drag_index: number | null = null 364 - function on_col_drag_start(e: DragEvent, index: number) { 365 - if (e.dataTransfer) { 366 - col_drag_index = index 367 - } 368 - } 369 - let col_drag_to_index: null | number = null 370 - function on_col_drag_over(e: DragEvent, index: number) { 371 - if (col_drag_index !== null && e.currentTarget instanceof HTMLElement) { 372 - e.preventDefault() 373 - const rect = e.currentTarget.getBoundingClientRect() 374 - const container_rect = col_container.getBoundingClientRect() 375 - if (e.pageX < rect.right - rect.width / 2) { 376 - const offset = index === 0 ? 0 : -1 377 - col_drag_line.style.left = rect.left - container_rect.left + offset + 'px' 378 - col_drag_to_index = index 379 - } else { 380 - const offset = index === columns.length - 1 ? -2 : -1 381 - col_drag_line.style.left = rect.right - container_rect.left + offset + 'px' 382 - col_drag_to_index = index + 1 368 + const grid = document.createElement('revo-grid') 369 + grid.setAttribute('theme', 'darkCompact') 370 + grid.readonly = true 371 + grid.columns = load_columns() 372 + grid.rowSize = 24 373 + grid.style.lineHeight = '24px' 374 + const tracks = tracks_page.itemIds 375 + .map((item_id, i) => { 376 + return { 377 + ...get_item(item_id).track, 378 + index: i + 1, 383 379 } 384 - } 385 - } 386 - async function col_drop_handler() { 387 - if (col_drag_index !== null && col_drag_to_index !== null) { 388 - const move_to_left = col_drag_index < col_drag_to_index 389 - const removed_column = columns.splice(col_drag_index, 1)[0] 390 - columns.splice(col_drag_to_index - Number(move_to_left), 0, removed_column) 391 - columns = columns 392 - save_columns() 380 + }) 381 + .filter((track) => track !== null) 382 + grid.source = tracks 383 + grid.canFocus = false 384 + grid.resize = false 385 + grid.canDrag = false 386 + grid.canMoveColumns = false 387 + grid.hideAttribution = true 388 + 389 + // let col_container: HTMLElement 390 + // let col_drag_line: HTMLElement 391 + // let col_drag_index: number | null = null 392 + // function on_col_drag_start(e: DragEvent, index: number) { 393 + // if (e.dataTransfer) { 394 + // col_drag_index = index 395 + // } 396 + // } 397 + // let col_drag_to_index: null | number = null 398 + // function on_col_drag_over(e: DragEvent, index: number) { 399 + // if (col_drag_index !== null && e.currentTarget instanceof HTMLElement) { 400 + // e.preventDefault() 401 + // const rect = e.currentTarget.getBoundingClientRect() 402 + // const container_rect = col_container.getBoundingClientRect() 403 + // if (e.pageX < rect.right - rect.width / 2) { 404 + // const offset = index === 0 ? 0 : -1 405 + // col_drag_line.style.left = rect.left - container_rect.left + offset + 'px' 406 + // col_drag_to_index = index 407 + // } else { 408 + // const offset = index === columns.length - 1 ? -2 : -1 409 + // col_drag_line.style.left = rect.right - container_rect.left + offset + 'px' 410 + // col_drag_to_index = index + 1 411 + // } 412 + // } 413 + // } 414 + // async function col_drop_handler() { 415 + // if (col_drag_index !== null && col_drag_to_index !== null) { 416 + // const move_to_left = col_drag_index < col_drag_to_index 417 + // const removed_column = columns.splice(col_drag_index, 1)[0] 418 + // columns.splice(col_drag_to_index - Number(move_to_left), 0, removed_column) 419 + // columns = columns 420 + // save_columns() 393 421 394 - col_drag_to_index = null 395 - } 396 - } 397 - function col_drag_end_handler() { 398 - col_drag_to_index = null 399 - } 422 + // col_drag_to_index = null 423 + // } 424 + // } 425 + // function col_drag_end_handler() { 426 + // col_drag_to_index = null 427 + // } 400 428 </script> 401 429 402 430 <Header ··· 405 433 description={tracks_page.playlistDescription} 406 434 /> 407 435 <div 436 + class="grow" 437 + {@attach (element) => { 438 + element.appendChild(grid) 439 + }} 440 + ></div> 441 + 442 + <!-- <div 408 443 bind:this={tracklist_element} 409 444 class="tracklist h-full" 410 445 role="table" 411 446 on:dragleave={() => (drag_to_index = null)} 412 447 > 413 - <!-- svelte-ignore a11y-interactive-supports-focus --> 414 448 <div 415 449 class="row table-header border-b border-b-slate-500/30" 416 450 class:desc={$sort_desc} ··· 420 454 bind:this={col_container} 421 455 > 422 456 {#each columns as column, i} 423 - <!-- svelte-ignore a11y-interactive-supports-focus --> 424 - <!-- svelte-ignore a11y-click-events-have-key-events --> 425 457 <div 426 458 class="c {column.key}" 427 459 class:sort={$sort_key === column.key} ··· 455 487 bind:this={col_drag_line} 456 488 ></div> 457 489 </div> 458 - <!-- svelte-ignore a11y-no-noninteractive-tabindex --> 459 - <!-- svelte-ignore a11y-no-static-element-interactions --> 460 490 <div 461 491 bind:this={scroll_container} 462 492 class="main-focus-element relative h-full overflow-y-auto outline-none" ··· 477 507 > 478 508 {@const { id: track_id, track } = get_item(item_id)} 479 509 {#if track !== null} 480 - <!-- svelte-ignore a11y-click-events-have-key-events --> 481 - <!-- svelte-ignore a11y-interactive-supports-focus --> 482 510 <div 483 511 class="row" 484 512 role="row" ··· 530 558 </VirtualListBlock> 531 559 <div class="drag-line" class:hidden={drag_to_index === null} bind:this={drag_line}></div> 532 560 </div> 533 - </div> 561 + </div> --> 534 562 535 563 <style lang="sass"> 536 - .odd 537 - background-color: hsla(0, 0%, 90%, 0.06) 538 - .selected 539 - background-color: hsla(var(--hue), 20%, 42%, 0.8) 540 - :global(:focus) 541 - .selected 542 - background-color: hsla(var(--hue), 70%, 46%, 1) 543 - .tracklist 544 - display: flex 545 - flex-direction: column 546 - min-width: 0px 547 - width: 100% 548 - background-color: rgba(0, 0, 0, 0.01) 549 - overflow: hidden 550 - .table-header 551 - .c 552 - overflow: visible 553 - * 554 - pointer-events: none 555 - .c.sort span 556 - font-weight: 500 557 - .c.sort span::after 558 - content: '▲' 559 - padding-left: 1px 560 - transform: scale(0.8, 0.5) 561 - display: inline-block 562 - &.desc .c.sort span::after 563 - content: '▼' 564 - .row 565 - display: flex 566 - max-width: 100% 567 - $row-height: 24px 568 - height: $row-height 569 - font-size: 12px 570 - line-height: $row-height 571 - box-sizing: border-box 572 - position: relative 573 - &.playing.selected 574 - color: #ffffff 575 - &.playing 576 - color: #00ffff 577 - .c 578 - display: inline-block 579 - vertical-align: top 580 - width: 100% 581 - white-space: nowrap 582 - overflow: hidden 583 - text-overflow: ellipsis 584 - padding-left: 5px 585 - padding-right: 5px 586 - &:first-child 587 - padding-left: 10px 588 - &:last-child 589 - padding-right: 0px 590 - &.index, &.playCount, &.skipCount, &.duration 591 - padding-left: 0px 592 - padding-right: 10px 593 - text-align: right 594 - flex-shrink: 0 595 - .selected .index svg.playing-icon 596 - fill: var(--icon-color) 597 - .index 598 - width: 46px 599 - svg.playing-icon 600 - fill: #00ffff 601 - width: 16px 602 - height: 100% 603 - .image 604 - width: 18px 605 - flex-shrink: 0 606 - box-sizing: content-box 607 - .playCount, .skipCount 608 - width: 52px 609 - .duration 610 - width: 50px 611 - .dateAdded 612 - width: 140px 613 - flex-shrink: 0 614 - font-variant-numeric: tabular-nums 615 - .year 616 - width: 0px 617 - min-width: 47px 618 - .bpm 619 - width: 0px 620 - min-width: 43px 621 - .drag-line 622 - position: absolute 623 - width: 100% 624 - height: 2px 625 - background-color: var(--drag-line-color) 626 - pointer-events: none 627 - .col-drag-line 628 - position: absolute 629 - width: 2px 630 - height: 100vh 631 - background-color: var(--drag-line-color) 632 - pointer-events: none 633 - z-index: 5 564 + :global(revo-grid[theme=darkCompact]) 565 + --revo-grid-focused-bg: hsla(var(--hue), 70%, 46%, 1) 566 + :global(revogr-data .rgRow) 567 + line-height: 24px 568 + font-size: 12px 569 + :global(revogr-header .rgHeaderCell .resizable) 570 + display: none 571 + // .odd 572 + // background-color: hsla(0, 0%, 90%, 0.06) 573 + // .selected 574 + // background-color: hsla(var(--hue), 20%, 42%, 0.8) 575 + // :global(:focus) 576 + // .selected 577 + // background-color: hsla(var(--hue), 70%, 46%, 1) 578 + // .tracklist 579 + // display: flex 580 + // flex-direction: column 581 + // min-width: 0px 582 + // width: 100% 583 + // background-color: rgba(0, 0, 0, 0.01) 584 + // overflow: hidden 585 + // .table-header 586 + // .c 587 + // overflow: visible 588 + // * 589 + // pointer-events: none 590 + // .c.sort span 591 + // font-weight: 500 592 + // .c.sort span::after 593 + // content: '▲' 594 + // padding-left: 1px 595 + // transform: scale(0.8, 0.5) 596 + // display: inline-block 597 + // &.desc .c.sort span::after 598 + // content: '▼' 599 + // .row 600 + // display: flex 601 + // max-width: 100% 602 + // $row-height: 24px 603 + // height: $row-height 604 + // font-size: 12px 605 + // line-height: $row-height 606 + // box-sizing: border-box 607 + // position: relative 608 + // &.playing.selected 609 + // color: #ffffff 610 + // &.playing 611 + // color: #00ffff 612 + // .c 613 + // display: inline-block 614 + // vertical-align: top 615 + // width: 100% 616 + // white-space: nowrap 617 + // overflow: hidden 618 + // text-overflow: ellipsis 619 + // padding-left: 5px 620 + // padding-right: 5px 621 + // &:first-child 622 + // padding-left: 10px 623 + // &:last-child 624 + // padding-right: 0px 625 + // &.index, &.playCount, &.skipCount, &.duration 626 + // padding-left: 0px 627 + // padding-right: 10px 628 + // text-align: right 629 + // flex-shrink: 0 630 + // .selected .index svg.playing-icon 631 + // fill: var(--icon-color) 632 + // .index 633 + // width: 46px 634 + // svg.playing-icon 635 + // fill: #00ffff 636 + // width: 16px 637 + // height: 100% 638 + // .image 639 + // width: 18px 640 + // flex-shrink: 0 641 + // box-sizing: content-box 642 + // .playCount, .skipCount 643 + // width: 52px 644 + // .duration 645 + // width: 50px 646 + // .dateAdded 647 + // width: 140px 648 + // flex-shrink: 0 649 + // font-variant-numeric: tabular-nums 650 + // .year 651 + // width: 0px 652 + // min-width: 47px 653 + // .bpm 654 + // width: 0px 655 + // min-width: 43px 656 + // .drag-line 657 + // position: absolute 658 + // width: 100% 659 + // height: 2px 660 + // background-color: var(--drag-line-color) 661 + // pointer-events: none 662 + // .col-drag-line 663 + // position: absolute 664 + // width: 2px 665 + // height: 100vh 666 + // background-color: var(--drag-line-color) 667 + // pointer-events: none 668 + // z-index: 5 634 669 </style>