diff --git a/CHANGELOG.md b/CHANGELOG.md index a680e3612..754530319 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,27 +6,31 @@ https://github.com/jcorporation/myMPD/ ## myMPD v10.4.0 (not yet released) +This release improves the queue and playlist management in many ways. + ### API changes - MYMPD_API_QUEUE_RM_SONG -> MYMPD_API_QUEUE_RM_IDS -- MYMPD_API_QUEUE_MOVE_SONG -> MYMPD_API_QUEUE_MOVE_IDS +- MYMPD_API_QUEUE_MOVE_SONG -> MYMPD_API_QUEUE_MOVE_POSITION +- MYMPD_API_QUEUE_MOVE_RELATIVE: new - MYMPD_API_QUEUE_APPEND_URI -> MYMPD_API_QUEUE_APPEND_URIS - MYMPD_API_QUEUE_INSERT_URI -> MYMPD_API_QUEUE_INSERT_URIS - MYMPD_API_QUEUE_REPLACE_URI -> MYMPD_API_QUEUE_REPLACE_URIS - MYMPD_API_QUEUE_APPEND_PLAYLIST -> MYMPD_API_QUEUE_APPEND_PLAYLISTS - MYMPD_API_QUEUE_INSERT_PLAYLIST -> MYMPD_API_QUEUE_INSERT_PLAYLISTS - MYMPD_API_QUEUE_REPLACE_PLAYLIST -> MYMPD_API_QUEUE_REPLACE_PLAYLISTS -- MYMPD_API_QUEUE_PRIO_SET parameters changed -- MYMPD_API_QUEUE_PRIO_SET_HIGHEST parameters changed +- MYMPD_API_QUEUE_PRIO_SET: parameters changed +- MYMPD_API_QUEUE_PRIO_SET_HIGHEST: parameters changed - MYMPD_API_PLAYLIST_CONTENT_RM_SONG -> MYMPD_API_PLAYLIST_CONTENT_RM_POSITIONS - MYMPD_API_PLAYLIST_CONTENT_MOVE_SONG -> MYMPD_API_PLAYLIST_CONTENT_MOVE_POSITION - MYMPD_API_PLAYLIST_CONTENT_APPEND_URI -> MYMPD_API_PLAYLIST_CONTENT_APPEND_URIS - MYMPD_API_PLAYLIST_CONTENT_INSERT_URI -> MYMPD_API_PLAYLIST_CONTENT_INSERT_URIS - MYMPD_API_PLAYLIST_CONTENT_REPLACE_URI -> MYMPD_API_PLAYLIST_CONTENT_REPLACE_URIS -- MYMPD_API_PLAYLIST_RM parameters changed +- MYMPD_API_PLAYLIST_RM: parameters changed ### Changelog +- Feat: Add multiple selections and actions #1001 - Feat: migrate to JavaScript fetch() API #1006 - Feat: add test target to cmake #1023 - Upd: Mongoose 7.10 #1024 diff --git a/htdocs/js/QueueCurrent.js b/htdocs/js/QueueCurrent.js index a12a12165..942a14f1a 100644 --- a/htdocs/js/QueueCurrent.js +++ b/htdocs/js/QueueCurrent.js @@ -860,30 +860,32 @@ function gotoPlayingSong() { /** * Moves a entry in the queue - * @param {number} id song id + * @param {number} from from position * @param {number} to to position * @returns {void} */ -function queueMoveId(id, to) { - sendAPI("MYMPD_API_QUEUE_MOVE_IDS", { - "songIds": [id], +function queueMoveSong(from, to) { + sendAPI("MYMPD_API_QUEUE_MOVE_POSITION", { + "from": from, "to": to }, null, false); } /** * Plays the selected song after the current song. - * Uses the priority if MPS is in random mode, else moves the song after current playing song. + * Sets the priority if MPD is in random mode, else moves the song after current playing song. * @param {number} songId current playing song id (for priority mode) - * @param {number} songPos current playing song position (for move mode) * @returns {void} */ //eslint-disable-next-line no-unused-vars -function playAfterCurrent(songId, songPos) { +function playAfterCurrent(songId) { if (settings.partition.random === false) { //not in random mode - move song after current playing song - const newSongPos = currentState.songPos !== undefined ? currentState.songPos + 1 : 0; - queueMoveId(songId, newSongPos); + sendAPI("MYMPD_API_QUEUE_MOVE_RELATIVE", { + "songIds": [songId], + "to": 0, + "whence": 1 + }, null, false); } else { //in random mode - set song priority diff --git a/htdocs/js/apidoc.js b/htdocs/js/apidoc.js index 1f8775366..0050a26b1 100644 --- a/htdocs/js/apidoc.js +++ b/htdocs/js/apidoc.js @@ -391,13 +391,21 @@ const APImethods = { } } }, - "MYMPD_API_QUEUE_MOVE_IDS": { - "desc": "Moves song ids in the queue.", + "MYMPD_API_QUEUE_MOVE_POSITION": { + "desc": "Moves entries in the queue.", "params": { - "songIds": APIparams.songIds, + "from": APIparams.from, "to": APIparams.to } }, + "MYMPD_API_QUEUE_MOVE_RELATIVE": { + "desc": "Moves entries in the queue.", + "params": { + "from": APIparams.songIds, + "to": APIparams.to, + "whence": APIparams.whence + } + }, "MYMPD_API_QUEUE_INSERT_PLAYLISTS": { "desc": "Adds the playlist to distinct position in the queue.", "params": { diff --git a/htdocs/js/contextMenu.js b/htdocs/js/contextMenu.js index 0b7df3338..d7c119d46 100644 --- a/htdocs/js/contextMenu.js +++ b/htdocs/js/contextMenu.js @@ -560,9 +560,10 @@ function createMenuLists(target, contextMenuTitle, contextMenuBody) { addMenuItemsSongActions(dataNode, contextMenuBody, uri, type, name); addDivider(contextMenuBody); if (currentState.currentSongId !== -1 && - songid !== currentState.currentSongId) + songid !== currentState.currentSongId && + features.featWhence === true) { - addMenuItem(contextMenuBody, {"cmd": "playAfterCurrent", "options": [songid, songpos]}, 'Play after current playing song'); + addMenuItem(contextMenuBody, {"cmd": "playAfterCurrent", "options": [songid]}, 'Play after current playing song'); } if (settings.partition.random === true) { addMenuItem(contextMenuBody, {"cmd": "showSetSongPriority", "options": [songid]}, 'Set priority'); diff --git a/htdocs/js/tables.js b/htdocs/js/tables.js index e948d701b..f36b5ceb7 100644 --- a/htdocs/js/tables.js +++ b/htdocs/js/tables.js @@ -25,7 +25,9 @@ function dragAndDropTable(tableId) { }, false); tableBody.addEventListener('dragleave', function(event) { event.preventDefault(); - if (dragEl === undefined || dragEl.nodeName !== 'TR') { + if (dragEl === undefined || + dragEl.nodeName !== 'TR') + { return; } let target = event.target; @@ -38,7 +40,9 @@ function dragAndDropTable(tableId) { }, false); tableBody.addEventListener('dragover', function(event) { event.preventDefault(); - if (dragEl === undefined || dragEl.nodeName !== 'TR') { + if (dragEl === undefined || + dragEl.nodeName !== 'TR') + { return; } const tr = tableBody.querySelectorAll('.dragover'); @@ -56,7 +60,9 @@ function dragAndDropTable(tableId) { }, false); tableBody.addEventListener('dragend', function(event) { event.preventDefault(); - if (dragEl === undefined || dragEl.nodeName !== 'TR') { + if (dragEl === undefined || + dragEl.nodeName !== 'TR') + { return; } const tr = tableBody.querySelectorAll('.dragover'); @@ -71,7 +77,9 @@ function dragAndDropTable(tableId) { tableBody.addEventListener('drop', function(event) { event.stopPropagation(); event.preventDefault(); - if (dragEl === undefined || dragEl.nodeName !== 'TR') { + if (dragEl === undefined || + dragEl.nodeName !== 'TR') + { return; } let target = event.target; @@ -79,6 +87,7 @@ function dragAndDropTable(tableId) { target = event.target.parentNode; } const newSongPos = getData(target, 'songpos'); + const oldSongPos = getDataId(event.dataTransfer.getData('Text'), 'songpos'); document.getElementById(event.dataTransfer.getData('Text')).remove(); dragEl.classList.remove('opacity05'); // @ts-ignore @@ -90,12 +99,10 @@ function dragAndDropTable(tableId) { document.getElementById(tableId).classList.add('opacity05'); switch(app.id) { case 'QueueCurrent': { - const songId = getDataId(event.dataTransfer.getData('Text'), 'songid'); - queueMoveId(songId, newSongPos); + queueMoveSong(oldSongPos, newSongPos); break; } case 'BrowsePlaylistsDetail': { - const oldSongPos = getDataId(event.dataTransfer.getData('Text'), 'songpos'); playlistMoveSong(oldSongPos, newSongPos); break; } @@ -123,7 +130,9 @@ function dragAndDropTableHeader(tableName) { }, false); tableHeader.addEventListener('dragleave', function(event) { event.preventDefault(); - if (dragEl === undefined || dragEl.nodeName !== 'TH') { + if (dragEl === undefined || + dragEl.nodeName !== 'TH') + { return; } if (event.target.nodeName === 'TH') { @@ -132,7 +141,9 @@ function dragAndDropTableHeader(tableName) { }, false); tableHeader.addEventListener('dragover', function(event) { event.preventDefault(); - if (dragEl === undefined || dragEl.nodeName !== 'TH') { + if (dragEl === undefined || + dragEl.nodeName !== 'TH') + { return; } const th = tableHeader.querySelectorAll('.dragover-th'); @@ -146,7 +157,9 @@ function dragAndDropTableHeader(tableName) { }, false); tableHeader.addEventListener('dragend', function(event) { event.preventDefault(); - if (dragEl === undefined || dragEl.nodeName !== 'TH') { + if (dragEl === undefined || + dragEl.nodeName !== 'TH') + { return; } const th = tableHeader.querySelectorAll('.dragover-th'); @@ -161,7 +174,9 @@ function dragAndDropTableHeader(tableName) { tableHeader.addEventListener('drop', function(event) { event.stopPropagation(); event.preventDefault(); - if (dragEl === undefined || dragEl.nodeName !== 'TH') { + if (dragEl === undefined || + dragEl.nodeName !== 'TH') + { return; } this.querySelector('[data-col=' + event.dataTransfer.getData('Text') + ']').remove(); diff --git a/src/lib/api.h b/src/lib/api.h index cb5f1698e..69006fe1c 100644 --- a/src/lib/api.h +++ b/src/lib/api.h @@ -123,7 +123,8 @@ X(MYMPD_API_QUEUE_INSERT_SEARCH) \ X(MYMPD_API_QUEUE_INSERT_URIS) \ X(MYMPD_API_QUEUE_LIST) \ - X(MYMPD_API_QUEUE_MOVE_IDS) \ + X(MYMPD_API_QUEUE_MOVE_POSITION) \ + X(MYMPD_API_QUEUE_MOVE_RELATIVE) \ X(MYMPD_API_QUEUE_PRIO_SET) \ X(MYMPD_API_QUEUE_PRIO_SET_HIGHEST) \ X(MYMPD_API_QUEUE_REPLACE_PLAYLISTS) \ diff --git a/src/mympd_api/mympd_api_handler.c b/src/mympd_api/mympd_api_handler.c index 074d5a622..7b4a82b06 100644 --- a/src/mympd_api/mympd_api_handler.c +++ b/src/mympd_api/mympd_api_handler.c @@ -898,19 +898,37 @@ void mympd_api_handler(struct t_partition_state *partition_state, struct t_work_ response->data = mympd_respond_with_error_or_ok(partition_state, response->data, request->cmd_id, request->id, rc, "mpd_run_delete_range", &result); } break; - case MYMPD_API_QUEUE_MOVE_IDS: { + case MYMPD_API_QUEUE_MOVE_POSITION: + if (json_get_uint(request->data, "$.params.from", 0, MPD_PLAYLIST_LENGTH_MAX, &uint_buf1, &error) == true && + json_get_uint(request->data, "$.params.to", 0, MPD_PLAYLIST_LENGTH_MAX, &uint_buf2, &error) == true) + { + if (uint_buf1 < uint_buf2) { + // decrease to position + uint_buf2--; + } + rc = mpd_run_move(partition_state->conn, uint_buf1, uint_buf2); + response->data = mympd_respond_with_error_or_ok(partition_state, response->data, request->cmd_id, request->id, rc, "mpd_run_move", &result); + } + break; + case MYMPD_API_QUEUE_MOVE_RELATIVE: { + if (mympd_state->mpd_state->feat_whence == false) { + response->data = jsonrpc_respond_message(response->data, request->cmd_id, request->id, + JSONRPC_FACILITY_QUEUE, JSONRPC_SEVERITY_ERROR, "Method not supported"); + break; + } struct t_list song_ids; list_init(&song_ids); if (json_get_array_llong(request->data, "$.params.songIds", &song_ids, MPD_COMMANDS_MAX, &error) == true && - json_get_uint(request->data, "$.params.to", 0, MPD_PLAYLIST_LENGTH_MAX, &uint_buf1, &error) == true) + json_get_uint(request->data, "$.params.to", 0, MPD_PLAYLIST_LENGTH_MAX, &uint_buf1, &error) == true && + json_get_uint(request->data, "$.params.whence", 0, 2, &uint_buf2, &error) == true) { if (song_ids.length == 0) { response->data = jsonrpc_respond_message(response->data, request->cmd_id, request->id, JSONRPC_FACILITY_QUEUE, JSONRPC_SEVERITY_ERROR, "No MPD queue song ids provided"); } else { - rc = mympd_api_queue_move_ids(partition_state, &song_ids, uint_buf1); - response->data = mympd_respond_with_error_or_ok(partition_state, response->data, request->cmd_id, request->id, rc, "mpd_run_move", &result); + rc = mympd_api_queue_move_relative(partition_state, &song_ids, uint_buf1, uint_buf2); + response->data = mympd_respond_with_error_or_ok(partition_state, response->data, request->cmd_id, request->id, rc, "mympd_api_queue_move_relative", &result); } } list_clear(&song_ids); @@ -920,14 +938,14 @@ void mympd_api_handler(struct t_partition_state *partition_state, struct t_work_ struct t_list song_ids; list_init(&song_ids); if (json_get_array_llong(request->data, "$.params.songIds", &song_ids, MPD_COMMANDS_MAX, &error) == true && - json_get_uint(request->data, "$.params.priority", 0, MPD_QUEUE_PRIO_MAX, &uint_buf2, &error) == true) + json_get_uint(request->data, "$.params.priority", 0, MPD_QUEUE_PRIO_MAX, &uint_buf1, &error) == true) { if (song_ids.length == 0) { response->data = jsonrpc_respond_message(response->data, request->cmd_id, request->id, JSONRPC_FACILITY_QUEUE, JSONRPC_SEVERITY_ERROR, "No MPD queue song ids provided"); } else { - rc = mympd_api_queue_prio_set(partition_state, &song_ids, uint_buf2); + rc = mympd_api_queue_prio_set(partition_state, &song_ids, uint_buf1); response->data = mympd_respond_with_error_or_ok(partition_state, response->data, request->cmd_id, request->id, rc, "mpd_send_prio_id", &result); } } @@ -1223,6 +1241,7 @@ void mympd_api_handler(struct t_partition_state *partition_state, struct t_work_ json_get_uint(request->data, "$.params.to", 0, MPD_PLAYLIST_LENGTH_MAX, &uint_buf2, &error) == true) { if (uint_buf1 < uint_buf2) { + // decrease to position uint_buf2--; } rc = mpd_run_playlist_move(partition_state->conn, sds_buf1, uint_buf1, uint_buf2); diff --git a/src/mympd_api/queue.c b/src/mympd_api/queue.c index f38af788f..8a7314e18 100644 --- a/src/mympd_api/queue.c +++ b/src/mympd_api/queue.c @@ -151,31 +151,6 @@ bool mympd_api_queue_play_newly_inserted(struct t_partition_state *partition_sta return mympd_check_rc_error_and_recover(partition_state, rc, "mpd_run_play_id"); } -/** - * Moves the song ids to pos in queue. - * @param partition_state pointer to partition state - * @param song_ids song ids in the queue - * @param to position to move - * @return true on success, else false - */ -bool mympd_api_queue_move_ids(struct t_partition_state *partition_state, struct t_list *song_ids, unsigned to) { - if (mpd_command_list_begin(partition_state->conn, false) == true) { - struct t_list_node *current = song_ids->head; - while (current != NULL) { - bool rc = mpd_send_move_id(partition_state->conn, (unsigned)current->value_i, to); - if (rc == false) { - MYMPD_LOG_ERROR("Error adding command to command list mpd_send_move_id"); - break; - } - current = current->next; - to++; - } - return mpd_command_list_end(partition_state->conn) && - mpd_response_finish(partition_state->conn); - } - return false; -} - /** * Sets the priority of a song in the queue. * The priority has only an effect in random mode. @@ -266,6 +241,32 @@ bool mympd_api_queue_append_plist(struct t_partition_state *partition_state, str return false; } +/** + * Moves song ids to relative position after current song + * @param partition_state pointer to partition state + * @param song_ids song ids to move + * @param to relative position + * @param whence how to interpret the to parameter + * @return bool true on success, else false + */ +bool mympd_api_queue_move_relative(struct t_partition_state *partition_state, struct t_list *song_ids, unsigned to, unsigned whence) { + if (mpd_command_list_begin(partition_state->conn, false) == true) { + struct t_list_node *current = song_ids->head; + while (current != NULL) { + bool rc = mpd_send_move_id_whence(partition_state->conn, (unsigned)current->value_i, to, whence); + if (rc == false) { + MYMPD_LOG_ERROR("Error adding command to command list mpd_send_move_id_whence"); + break; + } + current = current->next; + to++; + } + return mpd_command_list_end(partition_state->conn) && + mpd_response_finish(partition_state->conn); + } + return false; +} + /** * Insert playlists into the queue * @param partition_state pointer to partition state diff --git a/src/mympd_api/queue.h b/src/mympd_api/queue.h index 0da4a615f..b9dca5e75 100644 --- a/src/mympd_api/queue.h +++ b/src/mympd_api/queue.h @@ -31,5 +31,5 @@ bool mympd_api_queue_replace(struct t_partition_state *partition_state, struct t bool mympd_api_queue_append_plist(struct t_partition_state *partition_state, struct t_list *plists); bool mympd_api_queue_insert_plist(struct t_partition_state *partition_state, struct t_list *plists, unsigned to, unsigned whence); bool mympd_api_queue_replace_plist(struct t_partition_state *partition_state, struct t_list *plists); -bool mympd_api_queue_move_ids(struct t_partition_state *partition_state, struct t_list *song_ids, unsigned to); +bool mympd_api_queue_move_relative(struct t_partition_state *partition_state, struct t_list *song_ids, unsigned to, unsigned whence); #endif