diff --git a/ferrum-addon/addon.d.ts b/ferrum-addon/addon.d.ts index 95c4494..f57f769 100644 --- a/ferrum-addon/addon.d.ts +++ b/ferrum-addon/addon.d.ts @@ -145,9 +145,9 @@ export declare function get_track_list(id: string): TrackList /** Returns the deleted track lists, including folder children */ export declare function delete_track_list(id: string): void export declare function add_tracks_to_playlist(playlistId: string, trackIds: Array): void -export declare function playlist_filter_duplicates(playlistId: string, ids: Array): Array -export declare function remove_from_playlist(playlistId: string, itemIds: Array): void -export declare function delete_tracks(itemIds: Array): void +export declare function playlist_filter_duplicates(playlistId: TrackID, ids: Array): Array +export declare function remove_from_playlist(playlistId: TrackID, itemIds: Array): void +export declare function delete_tracks_with_item_ids(itemIds: Array): void export declare function new_playlist(name: string, description: string, isFolder: boolean, parentId: string): void export declare function update_playlist(id: string, name: string, description: string): void export declare function move_playlist(id: string, fromId: string, toId: string, toIndex: number): void diff --git a/ferrum-addon/main.d.ts b/ferrum-addon/main.d.ts index f65b97c..ad76f3d 100644 --- a/ferrum-addon/main.d.ts +++ b/ferrum-addon/main.d.ts @@ -4,6 +4,7 @@ export * from './addon.d' declare module 'ferrum-addon/addon' { + export type ItemId = number export type TrackID = string export type TrackListID = string export type PercentInteger = number diff --git a/src-native/playlists.rs b/src-native/playlists.rs index bb299ce..a39773d 100644 --- a/src-native/playlists.rs +++ b/src-native/playlists.rs @@ -158,7 +158,7 @@ pub fn add_tracks(playlist_id: String, track_ids: Vec, env: Env) -> Resu #[napi(js_name = "playlist_filter_duplicates")] #[allow(dead_code)] -pub fn filter_duplicates(playlist_id: String, ids: Vec, env: Env) -> Result> { +pub fn filter_duplicates(playlist_id: TrackID, ids: Vec, env: Env) -> Result> { let data: &mut Data = get_data(&env)?; let mut track_ids: HashSet = HashSet::from_iter(ids); let playlist = match data.library.get_tracklist_mut(&playlist_id)? { @@ -176,7 +176,7 @@ pub fn filter_duplicates(playlist_id: String, ids: Vec, env: Env) -> Res #[napi(js_name = "remove_from_playlist")] #[allow(dead_code)] -pub fn remove_from_playlist(playlist_id: String, item_ids: Vec, env: Env) -> Result<()> { +pub fn remove_from_playlist(playlist_id: TrackID, item_ids: Vec, env: Env) -> Result<()> { let data: &mut Data = get_data(&env)?; let playlist = match data.library.get_tracklist_mut(&playlist_id)? { TrackList::Playlist(playlist) => playlist, @@ -217,9 +217,9 @@ fn delete_file(path: &PathBuf) -> UniResult<()> { } } -#[napi(js_name = "delete_tracks")] +#[napi(js_name = "delete_tracks_with_item_ids")] #[allow(dead_code)] -pub fn delete_tracks_with_item_id(item_ids: Vec, env: Env) -> Result<()> { +pub fn delete_tracks_with_item_ids(item_ids: Vec, env: Env) -> Result<()> { let data: &mut Data = get_data(&env)?; let library = &mut data.library; let track_ids = get_track_ids_from_item_ids(&item_ids); diff --git a/src/App.svelte b/src/App.svelte index b91e5b2..1218340 100755 --- a/src/App.svelte +++ b/src/App.svelte @@ -9,15 +9,7 @@ import PlaylistInfoModal from './components/PlaylistInfo.svelte' import { queue_visible } from './lib/queue' import { ipc_listen, ipc_renderer } from '@/lib/window' - import { - import_tracks, - type PlaylistInfo, - methods, - page, - is_mac, - view_as_songs, - view_as_artists, - } from './lib/data' + import { import_tracks, type PlaylistInfo, methods } from './lib/data' import { play_pause } from './lib/player' import DragGhost from './components/DragGhost.svelte' import ItunesImport from './components/ItunesImport.svelte' @@ -198,32 +190,6 @@
-
-
-

- {#if $page.tracklist.id === 'root'} - {#if $page.viewAs === view_as_songs} - Songs -
{$page.length} songs
- {:else if $page.viewAs === view_as_artists} - Artists -
- {page.get_artists().length} artists -
- {/if} - {:else if $page.tracklist.type !== 'special'} - {$page.tracklist.name} -
{$page.length} songs
- {/if} -

- {#if 'description' in $page.tracklist && $page.tracklist.description !== ''} -
{$page.tracklist.description}
- {/if} -
@@ -304,10 +270,6 @@ height: 100% top: 0px left: 0px - .dragbar - -webkit-app-region: drag - &.queue-visible - width: calc(100% - var(--right-sidebar-width)) .drag-overlay display: flex align-items: center diff --git a/src/components/ArtistList.svelte b/src/components/ArtistList.svelte index 1c20e91..852889b 100644 --- a/src/components/ArtistList.svelte +++ b/src/components/ArtistList.svelte @@ -1,11 +1,13 @@ +

(Work in progress)

diff --git a/src/components/Header.svelte b/src/components/Header.svelte new file mode 100644 index 0000000..4a57057 --- /dev/null +++ b/src/components/Header.svelte @@ -0,0 +1,33 @@ + + +
+
+

+ {title} +
{subtitle}
+

+ {#if description} +
{description}
+ {/if} +
+ + diff --git a/src/components/ItunesImport.svelte b/src/components/ItunesImport.svelte index fb7cdf6..d369b62 100644 --- a/src/components/ItunesImport.svelte +++ b/src/components/ItunesImport.svelte @@ -1,10 +1,10 @@ + +{#if tracklist.id === 'root'} +
+{:else} +
+{/if}
(drag_to_index = null)} - class:no-selection={$selection.count === 0} >
(col_drag_to_index = null)} @@ -379,10 +407,12 @@
sort_by(column.key)} + on:click={() => { + sort_key.set(column.key) + }} draggable="true" on:dragstart={(e) => on_col_drag_start(e, i)} on:dragend={col_drag_end_handler} @@ -408,10 +438,9 @@ tabindex="0" on:keydown={scroll_container_keydown} > - i)} + items={track_indexes} item_height={24} {scroll_container} let:item={i} @@ -425,16 +454,16 @@ class="row" role="row" on:dblclick={(e) => double_click(e, i)} - on:mousedown={(e) => selection.handleMouseDown(e, i)} - on:contextmenu={(e) => selection.handleContextMenu(e, i)} - on:click={(e) => selection.handleClick(e, i)} + on:mousedown={(e) => selection.handle_mouse_down(e, i)} + on:contextmenu={(e) => selection.handle_contextmenu(e, i)} + on:click={(e) => selection.handle_click(e, i)} draggable="true" on:dragstart={on_drag_start} on:dragover={(e) => on_drag_over(e, i)} on:drop={drop_handler} on:dragend={drag_end_handler} class:odd={i % 2 === 0} - class:selected={$selection.list[i] === true} + class:selected={$selection.has(track.id)} class:playing={track.id === $playing_id} > {#each columns as column} diff --git a/src/lib/data.ts b/src/lib/data.ts index 9ab5c92..e4f57f5 100644 --- a/src/lib/data.ts +++ b/src/lib/data.ts @@ -5,12 +5,12 @@ import type { TrackID, TrackList, TrackListID, + ItemId, TrackMd, + TracksPageOptions, ViewAs, ViewOptions, } from '../../ferrum-addon' -import { selection as pageSelection } from './page' -import { queue } from './queue' export const is_dev = window.is_dev export const local_data_path = window.local_data_path @@ -66,8 +66,7 @@ export function call>(cb: (addon: typeof inner_addon return result } } catch (err) { - console.log('errorPopup') - + console.error('errorPopup:', err) error_popup(err) throw err } @@ -89,48 +88,47 @@ export async function add_track_to_playlist( track_ids: TrackID[], check_duplicates = true, ) { - if (check_duplicates) { - const filtered_ids = call((addon) => addon.playlist_filter_duplicates(playlist_id, track_ids)) - const duplicates = track_ids.length - filtered_ids.length - - if (duplicates > 0) { - const result = await ipc_renderer.invoke('showMessageBox', false, { - type: 'question', - message: 'Already added', - detail: - duplicates > 1 - ? `${duplicates} songs are already in this playlist` - : `${duplicates} song is already in this playlist`, - buttons: ['Add anyway', 'Cancel', 'Skip'], - defaultId: 0, - }) - if (result.response === 1) { - return - } else if (result.response === 2) { - track_ids = filtered_ids - } - } - } - if (track_ids.length >= 1) { - call((addon) => addon.add_tracks_to_playlist(playlist_id, track_ids)) - if (page.get().tracklist.id === playlist_id) { - page.refresh_ids_and_keep_selection() - } - methods.save() - } + // if (check_duplicates) { + // const filtered_ids = call((addon) => addon.playlist_filter_duplicates(playlist_id, track_ids)) + // const duplicates = track_ids.length - filtered_ids.length + // if (duplicates > 0) { + // const result = await ipc_renderer.invoke('showMessageBox', false, { + // type: 'question', + // message: 'Already added', + // detail: + // duplicates > 1 + // ? `${duplicates} songs are already in this playlist` + // : `${duplicates} song is already in this playlist`, + // buttons: ['Add anyway', 'Cancel', 'Skip'], + // defaultId: 0, + // }) + // if (result.response === 1) { + // return + // } else if (result.response === 2) { + // track_ids = filtered_ids + // } + // } + // } + // if (track_ids.length >= 1) { + // call((addon) => addon.add_tracks_to_playlist(playlist_id, track_ids)) + // if (page.get().tracklist.id === playlist_id) { + // page.refresh_ids_and_keep_selection() + // } + // methods.save() + // } } -export function remove_from_open_playlist(indexes: number[]) { - call((addon) => addon.remove_from_open_playlist(indexes)) - page.refresh_ids_and_keep_selection() - pageSelection.clear() - methods.save() +export function remove_from_playlist(playlist_id: TrackListID, item_ids: ItemId[]) { + // call((addon) => addon.remove_from_playlist(playlist_id, item_ids)) + // page.refresh_ids_and_keep_selection() + // pageSelection.clear() + // methods.save() } -export function delete_tracks_in_open(indexes: number[]) { - call((addon) => addon.delete_tracks_in_open(indexes)) - page.refresh_ids_and_keep_selection() - pageSelection.clear() - queue.removeDeleted() - methods.save() +export function delete_tracks_with_item_ids(item_ids: ItemId[]) { + // call((addon) => addon.delete_tracks_with_item_ids(item_ids)) + // page.refresh_ids_and_keep_selection() + // pageSelection.clear() + // queue.removeDeleted() + // methods.save() } export type PlaylistInfo = { name: string @@ -146,10 +144,10 @@ export function new_playlist(info: PlaylistInfo) { methods.save() } export function update_playlist(id: string, name: string, description: string) { - call((addon) => addon.update_playlist(id, name, description)) - track_lists_details_map.refresh() - page.refresh_ids_and_keep_selection() - methods.save() + // call((addon) => addon.update_playlist(id, name, description)) + // track_lists_details_map.refresh() + // page.refresh_ids_and_keep_selection() + // methods.save() } export function move_playlist( id: TrackListID, @@ -165,27 +163,27 @@ export function move_playlist( export const paths = call((addon) => addon.get_paths()) export async function import_tracks(paths: string[]) { - let err_state = null - const now = Date.now() - for (const path of paths) { - try { - inner_addon.import_file(path, now) - } catch (err) { - if (err_state === 'skip') continue - const result = await ipc_renderer.invoke('showMessageBox', false, { - type: 'error', - message: 'Error importing track ' + path, - detail: get_error_message(err), - buttons: err_state ? ['OK', 'Skip all errors'] : ['OK'], - defaultId: 0, - }) - if (result.response === 1) err_state = 'skip' - else err_state = 'skippable' - } - } - page.refresh_ids_and_keep_selection() - pageSelection.clear() - methods.save() + // let err_state = null + // const now = Date.now() + // for (const path of paths) { + // try { + // inner_addon.import_file(path, now) + // } catch (err) { + // if (err_state === 'skip') continue + // const result = await ipc_renderer.invoke('showMessageBox', false, { + // type: 'error', + // message: 'Error importing track ' + path, + // detail: get_error_message(err), + // buttons: err_state ? ['OK', 'Skip all errors'] : ['OK'], + // defaultId: 0, + // }) + // if (result.response === 1) err_state = 'skip' + // else err_state = 'skippable' + // } + // } + // page.refresh_ids_and_keep_selection() + // pageSelection.clear() + // methods.save() } export const methods = { @@ -195,6 +193,9 @@ export const methods = { getTrack: (id: TrackID) => { return call((data) => data.get_track(id)) }, + get_tracks_page: (options: TracksPageOptions) => { + return call((data) => data.get_tracks_page(options)) + }, trackExists: (id: TrackID) => { return call((data) => data.track_exists(id)) }, @@ -202,29 +203,34 @@ export const methods = { return call((data) => data.get_track_list(id)) as TrackList }, deleteTrackList: (id: TrackListID) => { - call((data) => data.delete_track_list(id)) - page.refresh_ids_and_keep_selection() - pageSelection.clear() - track_lists_details_map.refresh() - methods.save() + // console.log('deleteTL') + // call((data) => data.delete_track_list(id)) + // console.log('id ', '===', ' current_playlist_id') + // if (id === current_playlist_id) { + // navigate('/playlist/root') + // } + // page.refresh_ids_and_keep_selection() + // pageSelection.clear() + // track_lists_details_map.refresh() + // methods.save() }, save: () => { return call((addon) => addon.save()) }, addPlay: (id: TrackID) => { - call((data) => data.add_play(id)) - page.refresh_ids_and_keep_selection() - methods.save() + // call((data) => data.add_play(id)) + // page.refresh_ids_and_keep_selection() + // methods.save() }, addSkip: (id: TrackID) => { - call((data) => data.add_skip(id)) - page.refresh_ids_and_keep_selection() - methods.save() + // call((data) => data.add_skip(id)) + // page.refresh_ids_and_keep_selection() + // methods.save() }, addPlayTime: (id: TrackID, start_time: MsSinceUnixEpoch, duration_ms: number) => { - call((data) => data.add_play_time(id, start_time, duration_ms)) - page.refresh_ids_and_keep_selection() - methods.save() + // call((data) => data.add_play_time(id, start_time, duration_ms)) + // page.refresh_ids_and_keep_selection() + // methods.save() }, readCoverAsync(id: TrackID, index: number) { return inner_addon.read_cover_async(id, index).catch((error) => { @@ -233,10 +239,10 @@ export const methods = { }) }, updateTrackInfo: (id: TrackID, md: TrackMd) => { - call((data) => data.update_track_info(id, md)) - track_metadata_updated.emit() - page.refresh_ids_and_keep_selection() - methods.save() + // call((data) => data.update_track_info(id, md)) + // tracks_updated.emit() + // page.refresh_ids_and_keep_selection() + // methods.save() }, loadTags: (id: TrackID) => { return call((data) => data.load_tags(id)) @@ -267,18 +273,7 @@ export const methods = { }, } -export const filter = (() => { - const { subscribe, set } = writable('') - return { - subscribe: subscribe, - set: (query: string) => { - call((data) => data.filter_open_playlist(query)) - page.set(page.get()) - pageSelection.clear() - set(query) - }, - } -})() +export const filter = writable('') function create_refresh_store() { const store = writable(0) @@ -289,62 +284,13 @@ function create_refresh_store() { }, } } -export const track_metadata_updated = create_refresh_store() +export const tracks_updated = create_refresh_store() -export const page = (() => { - function get() { - const info = call((addon) => addon.get_page_info()) - return info - } - function refresh_ids_and_keep_selection() { - call((addon) => addon.refresh_page()) - set(get()) - } - - const { subscribe, set, update } = writable(get()) - return { - subscribe, - get, - set, - update, - refresh_ids_and_keep_selection: refresh_ids_and_keep_selection, - open_playlist(id: string, view_as: ViewAs) { - call((data) => data.open_playlist(id, view_as)) - refresh_ids_and_keep_selection() - pageSelection.clear() - filter.set('') - }, - sort_by(key: string) { - call((addon) => addon.sort(key, true)) - refresh_ids_and_keep_selection() - pageSelection.clear() - }, - set_group_album_tracks(value: boolean) { - call((addon) => addon.set_group_album_tracks(value)) - refresh_ids_and_keep_selection() - pageSelection.clear() - }, - get_artists() { - return call((addon) => addon.get_artists()) - }, - get_track(index: number) { - return call((addon) => addon.get_page_track(index)) - }, - get_track_id(index: number) { - return call((data) => data.get_page_track_id(index)) - }, - get_track_ids() { - return call((data) => data.get_page_track_ids()) - }, - move_tracks: (indexes: number[], to_index: number) => { - const new_selection = call((data) => data.move_tracks(indexes, to_index)) - call((data) => data.refresh_page()) - refresh_ids_and_keep_selection() - pageSelection.clear() - for (let i = new_selection.from; i <= new_selection.to; i++) { - pageSelection.add(i) - } - methods.save() - }, - } -})() +export function get_artists() { + return call((addon) => addon.get_artists()) +} +export function move_tracks(playlist_id: TrackListID, indexes: number[], to_index: number) { + call((data) => data.move_tracks(playlist_id, indexes, to_index)) + tracks_updated.emit() + methods.save() +} diff --git a/src/lib/menus.ts b/src/lib/menus.ts index ab44974..6deffd7 100644 --- a/src/lib/menus.ts +++ b/src/lib/menus.ts @@ -2,7 +2,7 @@ import { add_track_to_playlist, methods, paths, - remove_from_open_playlist, + remove_from_playlist, track_lists_details_map, } from '@/lib/data' import { flatten_child_lists } from '@/lib/helpers' @@ -11,6 +11,7 @@ import type { TrackID } from '../../ferrum-addon' import { get } from 'svelte/store' import { append_to_user_queue, prepend_to_user_queue } from './queue' import type { ShowTrackMenuOptions } from '@/electron/typed_ipc' +import { current_playlist_id } from '@/components/TrackList.svelte' export async function show_track_menu( all_id: string[], @@ -45,5 +46,6 @@ ipc_renderer.on('context.revealTrackFile', (e, id: TrackID) => { ipc_renderer.invoke('revealTrackFile', paths.tracksDir, track.file) }) ipc_renderer.on('context.Remove from Playlist', (e, indexes: number[]) => { - remove_from_open_playlist(indexes) + const playlist_id = get(current_playlist_id) + remove_from_playlist(playlist_id, indexes) }) diff --git a/src/lib/page.ts b/src/lib/page.ts index 4b3cbff..0195396 100644 --- a/src/lib/page.ts +++ b/src/lib/page.ts @@ -1,8 +1,3 @@ -import { get } from 'svelte/store' -import { page } from './data' -import { show_track_menu } from './menus' -import { new_selection } from './selection' - export const tracklist_actions = { scroll_to_index(_index: number) {}, focus() { @@ -10,16 +5,3 @@ export const tracklist_actions = { if (el instanceof HTMLElement) el.focus() }, } -export const selection = new_selection({ - get_item_count() { - return get(page).length - }, - scroll_to_item(i) { - tracklist_actions.scroll_to_index?.(i) - }, - async on_context_menu() { - const indexes = selection.getSelectedIndexes() - const ids = page.get_track_ids() - await show_track_menu(ids, indexes, { editable: get(page).tracklist.type === 'playlist' }) - }, -}) diff --git a/src/lib/selection-new.ts b/src/lib/selection-new.ts new file mode 100644 index 0000000..54c6148 --- /dev/null +++ b/src/lib/selection-new.ts @@ -0,0 +1,312 @@ +import { writable, type Updater } from 'svelte/store' + +type SelectionOptions = { + scroll_to_item: (index: number) => void + on_context_menu: () => void +} + +export class Selection { + /** Currently selected items */ + selection = new Set() + selection_store = writable(this.selection) + subscribe = this.selection_store.subscribe + /** Full list of items that can be selected */ + all: T[] = [] + /** The last added index */ + last_added: { index: number; item: T } | null = null + /** An anchor index for shift selection. */ + shift_anchor: { index: number; item: T } | null = null + scroll_to_item: SelectionOptions['scroll_to_item'] + on_context_menu: SelectionOptions['on_context_menu'] + + constructor(options: SelectionOptions) { + this.scroll_to_item = options.scroll_to_item + this.on_context_menu = options.on_context_menu + } + + set(new_selection: Set) { + this.selection = new_selection + this.selection_store.set(new_selection) + } + + update(updater: Updater>) { + this.selection = updater(this.selection) + this.selection_store.set(this.selection) + } + + clear() { + this.selection = new Set() + this.last_added = null + this.shift_anchor = null + } + + /** Update the list of items that can be selected. + * Items that no longer exist are de-selected. */ + update_all_items(all: T[]) { + let new_selection = new Set() + for (const item of all) { + if (this.selection.has(item)) { + new_selection.add(item) + } + } + this.all = all + this.selection = new_selection + if (this.last_added !== null && !this.selection.has(this.last_added.item)) { + this.last_added = null + } + if (this.shift_anchor !== null && !this.selection.has(this.shift_anchor.item)) { + this.shift_anchor = null + } + } + + get_shift_anchor() { + if (this.shift_anchor !== null) return this.shift_anchor + else return this.last_added + } + + add_index(index: number) { + this.selection.add(this.all[index]) + this.last_added = { index, item: this.all[index] } + } + + add_range(from_index: number, to_index: number) { + // Direction here determines this.last_added + if (from_index < to_index) { + for (let i = from_index; i <= to_index; i++) { + this.add_index(i) + } + } else { + for (let i = from_index; i >= to_index; i--) { + this.add_index(i) + } + } + } + + remove_range(from_i: number, to_i: number) { + if (from_i < to_i) { + for (let i = from_i; i <= to_i; i++) { + this.selection.delete(this.all[i]) + } + } else { + for (let i = from_i; i >= to_i; i--) { + this.selection.delete(this.all[i]) + } + } + } + + /** Shift-select to index */ + shift_select_to(to_index: number) { + const anchor = this.get_shift_anchor() + const last_added = this.last_added + if (last_added === null || anchor === null) { + return this.selection + } + + if (anchor.index < to_index) { + if (to_index < last_added.index) { + // Retract selection closer to anchor + this.remove_range(to_index + 1, last_added.index) + } else if (last_added.index < anchor.index) { + // New shift selection is on the other side of anchor + this.remove_range(anchor.index - 1, last_added.index) + this.add_range(anchor.index, to_index) + } else { + this.add_range(last_added.index, to_index) + } + this.last_added = { index: to_index, item: this.all[to_index] } + } else { + if (to_index > last_added.index) { + // Retract selection closer to anchor + this.remove_range(to_index - 1, last_added.index) + } else if (last_added.index > anchor.index) { + // New shift selection is on the other side of anchor + this.remove_range(anchor.index + 1, last_added.index) + this.add_range(anchor.index, to_index) + } else { + this.add_range(last_added.index, to_index) + } + this.last_added = { index: to_index, item: this.all[to_index] } + } + this.shift_anchor = anchor + } + + /** Get first selected index, or `null` if selection is empty */ + find_first_index() { + const item_i = this.all.findIndex((item) => this.selection.has(item)) + if (item_i === -1) { + return null + } + return item_i + } + + /** Replace selection with the previous index, like perssing `ArrowUp` in a list. */ + go_backward() { + // selection.clear() + // store.update((selection) => { + // if (selection.count === 0) { + // addIndex(selection, maxIndex) + // } else if (selection.lastAdded !== null) { + // const newIndex = selection.lastAdded - 1 + // selection = createEmpty() + // addIndex(selection, Math.max(0, newIndex)) + // } + // return selection + // }) + } + + /** Replace selection with the previous index, like perssing `ArrowDown` in a list. */ + go_forward() { + // store.update((selection) => { + // if (selection.count === 0) { + // addIndex(selection, 0) + // } else if (selection.lastAdded !== null) { + // const newIndex = selection.lastAdded + 1 + // selection = createEmpty() + // addIndex(selection, Math.min(newIndex, maxIndex)) + // } + // return selection + // }) + } + /** Expand or shrink selection backwards (shift+up) */ + shift_select_backward() { + // store.update((selection) => { + // const anchor = getShiftAnchor(selection) + // selection.shiftAnchor = anchor + // if (anchor === null || selection.lastAdded === null) { + // return selection + // } + // if (selection.lastAdded <= anchor) { + // // add prev to selection + // for (let i = selection.lastAdded; i >= 0; i--) { + // if (selection.list[i] !== true) { + // addIndex(selection, i) + // return selection + // } + // } + // } else { + // // remove first from selection + // remove(selection, selection.lastAdded) + // selection.lastAdded -= 1 + // } + // return selection + // }) + } + /** + * Expand or shrink selection forwards (shift+down). + * - `maxIndex`: The maximum index to expand to + */ + shift_select_forward(maxIndex: number) { + // store.update((selection) => { + // const anchor = getShiftAnchor(selection) + // selection.shiftAnchor = anchor + // if (anchor === null || selection.lastAdded === null) { + // return selection + // } + // if (selection.lastAdded >= anchor) { + // // add next to selection + // for (let i = selection.lastAdded; i <= maxIndex; i++) { + // if (selection.list[i] !== true) { + // addIndex(selection, i) + // return selection + // } + // } + // } else { + // // remove last from selection + // remove(selection, selection.lastAdded) + // selection.lastAdded += 1 + // } + // return selection + // }) + } + toggle(index: number) { + // store.update((selection) => { + // if (selection.list[index]) { + // if (selection.lastAdded === index) { + // selection.lastAdded = null + // } + // remove(selection, index) + // } else { + // addIndex(selection, index) + // } + // selection.shiftAnchor = null + // return selection + // }) + } + + // mouse_down_select(e: MouseEvent, index: number) { + // const isSelected = store.get().list[index] + // if (check_mouse_shortcut(e) && !isSelected) { + // selection.clear() + // selection.add(index) + // } else if (check_mouse_shortcut(e, { cmd_or_ctrl: true }) && !isSelected) { + // selection.add(index) + // } else if (check_mouse_shortcut(e, { shift: true })) { + // selection.shift_select_to(index) + // } + // } + + handle_mouse_down(e: MouseEvent, index: number) { + // if (e.button !== 0) { + // return + // } + // if (store.get().list[index]) { + // possible_row_click = true + // } + // mouse_down_select(e, index) + } + handle_contextmenu(e: MouseEvent, index: number) { + // mouse_down_select(e, index) + // options.on_context_menu() + } + handle_click(e: MouseEvent, index: number) { + // if (possible_row_click && e.button === 0) { + // if (check_mouse_shortcut(e)) { + // selection.clear() + // selection.add(index) + // } else if (check_mouse_shortcut(e, { cmd_or_ctrl: true })) { + // selection.toggle(index) + // } + // } + // possible_row_click = false + } + handle_keydown(e: KeyboardEvent) { + // if (check_shortcut(e, 'Escape')) { + // selection.clear() + // } else if (check_shortcut(e, 'A', { cmd_or_ctrl: true })) { + // selection.add(0, options.getItemCount() - 1) + // } else if (check_shortcut(e, 'ArrowUp')) { + // selection.goBackward(options.getItemCount() - 1) + // options.scroll_to_item(store.get().lastAdded || 0) + // } else if (check_shortcut(e, 'ArrowUp', { shift: true })) { + // selection.shiftSelectBackward() + // options.scroll_to_item(store.get().lastAdded || 0) + // } else if (check_shortcut(e, 'ArrowUp', { alt: true })) { + // selection.clear() + // selection.add(0) + // options.scroll_to_item(0) + // } else if (check_shortcut(e, 'ArrowUp', { shift: true, alt: true })) { + // selection.shiftSelectTo(0) + // options.scroll_to_item(store.get().lastAdded || 0) + // } else if (check_shortcut(e, 'ArrowDown')) { + // selection.goForward(options.getItemCount() - 1) + // options.scroll_to_item(store.get().lastAdded || 0) + // } else if (check_shortcut(e, 'ArrowDown', { shift: true })) { + // selection.shiftSelectForward(options.getItemCount() - 1) + // options.scroll_to_item(store.get().lastAdded || 0) + // } else if (check_shortcut(e, 'ArrowDown', { alt: true })) { + // selection.clear() + // selection.add(options.getItemCount() - 1) + // options.scroll_to_item(store.get().lastAdded || 0) + // } else if (check_shortcut(e, 'ArrowDown', { shift: true, alt: true })) { + // selection.shiftSelectTo(options.getItemCount() - 1) + // options.scroll_to_item(store.get().lastAdded || 0) + // } else { + // return + // } + // e.preventDefault() + } +} + +export function new_selection(options: SelectionOptions) { + return new Selection(options) +}