Skip to content

Commit 9f1ae7b

Browse files
Add "Show in Playlist" context menu item
1 parent 367b730 commit 9f1ae7b

File tree

12 files changed

+131
-15
lines changed

12 files changed

+131
-15
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Changelog
22

33
## Next
4+
- Add "Show in Playlist" context menu item
45
- Remember queue on restart
56
- Keep scroll position when opening play history
67
- Android: Add track streaming search links for Spotify and YouTube Music

ferrum-addon/addon.d.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ export declare function get_track_list(id: string): TrackList
6464

6565
export declare function get_track_lists_details(): Record<string, TrackListDetails>
6666

67+
export declare function get_track_playlist_ids(trackId: TrackID): Array<TrackID>
68+
6769
export declare function get_tracks_page(options: TracksPageOptions): TracksPage
6870

6971
export declare function import_file(path: string, now: MsSinceUnixEpoch): void
@@ -123,7 +125,7 @@ export interface Playlist {
123125
originalId?: string
124126
dateImported?: MsSinceUnixEpoch
125127
dateCreated?: MsSinceUnixEpoch
126-
tracks: Array<ItemId>
128+
tracks: string[]
127129
}
128130

129131
export declare function playlist_filter_duplicates(playlistId: TrackID, ids: Array<string>): Array<TrackID>

src-native/library_types.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,7 @@ pub struct Playlist {
473473
deserialize_with = "deserialize_playlist_ids",
474474
serialize_with = "serialize_playlist_ids"
475475
)]
476+
#[cfg_attr(feature = "napi", napi(ts_type = "string[]"))]
476477
pub tracks: Vec<ItemId>,
477478
}
478479
impl Playlist {

src-native/playlists.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use crate::str_to_option;
88
use anyhow::{Context, Result, bail};
99
use linked_hash_map::LinkedHashMap;
1010
use napi::{Env, Unknown};
11+
use rayon::prelude::*;
1112
use std::collections::{HashMap, HashSet};
1213
use std::path::PathBuf;
1314

@@ -172,6 +173,34 @@ pub fn filter_duplicates(playlist_id: TrackID, ids: Vec<String>, env: Env) -> Re
172173
Ok(track_ids)
173174
}
174175

176+
#[napi(js_name = "get_track_playlist_ids")]
177+
#[allow(dead_code)]
178+
pub fn get_track_playlist_ids(track_id: TrackID, env: Env) -> Result<Vec<TrackID>> {
179+
let data: &Data = get_data(&env);
180+
Ok(get_track_playlist_ids_in_library(&data.library, &track_id))
181+
}
182+
183+
pub fn get_track_playlist_ids_in_library(library: &Library, track_id: &str) -> Vec<TrackID> {
184+
let track_id_map = TRACK_ID_MAP.read().unwrap();
185+
library
186+
.trackLists
187+
.iter()
188+
.collect::<Vec<_>>()
189+
.par_iter()
190+
.filter_map(|(playlist_id, tracklist)| {
191+
let TrackList::Playlist(playlist) = tracklist else {
192+
return None;
193+
};
194+
for item_id in &playlist.tracks {
195+
if track_id_map[*item_id as usize].as_str() == track_id {
196+
return Some(playlist_id.to_string());
197+
}
198+
}
199+
None
200+
})
201+
.collect()
202+
}
203+
175204
#[napi(js_name = "remove_from_playlist")]
176205
#[allow(dead_code)]
177206
pub fn remove_from_playlist(playlist_id: TrackID, item_ids: Vec<ItemId>, env: Env) -> Result<()> {

src/components/Player.svelte

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@
1717
import { dragged } from '$lib/drag-drop'
1818
import * as dragGhost from './DragGhost.svelte'
1919
import Slider from './Slider.svelte'
20-
import { get_flattened_tracklists, handle_selected_tracks_action } from '$lib/menus'
20+
import {
21+
get_show_in_playlists_tree,
22+
get_tracklists_tree,
23+
handle_selected_tracks_action,
24+
} from '$lib/menus'
2125
import { ipc_renderer } from '$lib/window'
2226
import { tracks_page_item_ids } from './TrackList.svelte'
2327
@@ -29,7 +33,8 @@
2933
const action = await ipc_renderer.invoke('show_tracks_menu', {
3034
is_editable_playlist: false,
3135
queue: false,
32-
lists: get_flattened_tracklists(),
36+
lists: get_tracklists_tree(),
37+
show_in_playlists: get_show_in_playlists_tree([playing.id]),
3338
})
3439
if (action !== null) {
3540
handle_selected_tracks_action({

src/components/Queue.svelte

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@
1818
import { cubicOut } from 'svelte/easing'
1919
import VirtualListBlock, { scroll_container_keydown } from './VirtualListBlock.svelte'
2020
import type { SelectedTracksAction } from '$electron/typed_ipc'
21-
import { get_flattened_tracklists, handle_selected_tracks_action } from '$lib/menus'
21+
import {
22+
get_show_in_playlists_tree,
23+
get_tracklists_tree,
24+
handle_selected_tracks_action,
25+
} from '$lib/menus'
2226
import { SvelteSelection } from '$lib/selection'
2327
2428
let object_urls: string[] = []
@@ -71,10 +75,14 @@
7175
autoplay_list.scroll_to_index(index, 40)
7276
},
7377
async on_contextmenu() {
78+
const indexes = selection.get_selected_indexes()
79+
const visible_track_ids = get_visible_track_ids()
80+
const selected_visible_track_ids = indexes.map((i) => visible_track_ids[i])
7481
const action = await ipc_renderer.invoke('show_tracks_menu', {
7582
is_editable_playlist: false,
7683
queue: true,
77-
lists: get_flattened_tracklists(),
84+
lists: get_tracklists_tree(),
85+
show_in_playlists: get_show_in_playlists_tree(selected_visible_track_ids),
7886
})
7987
if (action !== null) {
8088
handle_action(action)
@@ -83,6 +91,15 @@
8391
})
8492
$: selection.update_all_items(visible_qids)
8593
94+
function get_visible_track_ids() {
95+
return [
96+
...(show_history ? $queue.past : []),
97+
...(show_history && $queue.current ? [$queue.current.item] : []),
98+
...$queue.user_queue,
99+
...$queue.auto_queue,
100+
].map((item) => item.id)
101+
}
102+
86103
function remove_from_queue() {
87104
if (selection.items.size >= 1) {
88105
const indexes = selection.get_selected_indexes().map((i) => i + first_visible_index)
@@ -127,12 +144,7 @@
127144
function handle_action(action: SelectedTracksAction) {
128145
const first_index = selection.find_first_index()
129146
const indexes = selection.get_selected_indexes()
130-
const visible_track_ids = [
131-
...(show_history ? $queue.past : []),
132-
...(show_history && $queue.current ? [$queue.current.item] : []),
133-
...$queue.user_queue,
134-
...$queue.auto_queue,
135-
].map((item) => item.id)
147+
const visible_track_ids = get_visible_track_ids()
136148
const selected_visible_track_ids = indexes.map((i) => visible_track_ids[i])
137149
handle_selected_tracks_action({
138150
action,

src/components/TrackList.svelte

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,11 @@
4545
import Header from './Header.svelte'
4646
import { writable } from 'svelte/store'
4747
import { SvelteSelection } from '$lib/selection'
48-
import { get_flattened_tracklists, handle_selected_tracks_action } from '$lib/menus'
48+
import {
49+
get_show_in_playlists_tree,
50+
get_tracklists_tree,
51+
handle_selected_tracks_action,
52+
} from '$lib/menus'
4953
import type { SelectedTracksAction } from '$electron/typed_ipc'
5054
import { RefreshLevel, VirtualGrid, type Column } from '$lib/virtual-grid.svelte'
5155
@@ -96,10 +100,12 @@
96100
tracklist_actions.scroll_to_index?.(index)
97101
},
98102
async on_contextmenu() {
103+
const selected_track_ids = get_track_ids(selection.items_as_array())
99104
const action = await ipc_renderer.invoke('show_tracks_menu', {
100105
is_editable_playlist: tracks_page.playlistKind === 'playlist',
101106
queue: false,
102-
lists: get_flattened_tracklists(),
107+
lists: get_tracklists_tree(),
108+
show_in_playlists: get_show_in_playlists_tree(selected_track_ids),
103109
})
104110
if (action !== null) {
105111
handle_action(action)
@@ -503,6 +509,11 @@
503509
504510
onMount(() => {
505511
tracklist_actions.scroll_to_index = virtual_grid.scroll_to_index.bind(virtual_grid)
512+
tracklist_actions.go_to_index = (index) => {
513+
virtual_grid.scroll_to_index(index)
514+
selection.clear()
515+
selection.add_index(index)
516+
}
506517
})
507518
</script>
508519

src/electron/ipc.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,15 @@ ipc_main.handle('show_tracks_menu', (e, options) => {
8989
click: () => resolve('Get Info'),
9090
},
9191
{ type: 'separator' },
92+
{
93+
label: 'Show in Playlist',
94+
submenu: options.show_in_playlists.map((item) => {
95+
return {
96+
...item,
97+
click: () => resolve({ action: 'Show in Playlist', playlist_id: item.id }),
98+
}
99+
}),
100+
},
92101
{
93102
label: (() => {
94103
if (is.mac) return 'Reveal in Finder'

src/electron/typed_ipc.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ export type SelectedTracksAction =
9797
| 'Play Next'
9898
| 'Add to Queue'
9999
| { action: 'Add to Playlist'; playlist_id: string }
100+
| { action: 'Show in Playlist'; playlist_id: string }
100101
| 'Get Info'
101102
| 'reveal_track_file'
102103
| 'Remove from Playlist'
@@ -141,11 +142,18 @@ type Events = {
141142
}
142143

143144
export type ShowTrackMenuOptions = {
144-
lists: { label: string; enabled: boolean; id: string }[]
145+
lists: TrackMenuNode[]
146+
show_in_playlists: TrackMenuNode[]
145147
is_editable_playlist: boolean
146148
queue: boolean
147149
}
148150

151+
export type TrackMenuNode = {
152+
label: string
153+
enabled: boolean
154+
id: string
155+
}
156+
149157
type Commands = {
150158
app_loaded: () => void
151159
check_for_updates: () => Promise<{

src/lib/data.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,9 @@ export function get_track_by_item_id(item_id: ItemId) {
157157
export function get_track_ids(item_ids: ItemId[]) {
158158
return strict_call((addon) => addon.get_track_ids(item_ids))
159159
}
160+
export function get_track_playlist_ids(track_id: TrackID) {
161+
return strict_call((addon) => addon.get_track_playlist_ids(track_id))
162+
}
160163
export function get_tracks_page(options: TracksPageOptions) {
161164
return strict_call((addon) => addon.get_tracks_page(options))
162165
}

0 commit comments

Comments
 (0)