Source: viewBrowseDatabase.js

  1. "use strict";
  2. // SPDX-License-Identifier: GPL-3.0-or-later
  3. // myMPD (c) 2018-2025 Juergen Mang <mail@jcgames.de>
  4. // https://github.com/jcorporation/mympd
  5. /** @module viewBrowseDatabase_js */
  6. /**
  7. * BrowseDatabaseAlbumList handler
  8. * @returns {void}
  9. */
  10. function handleBrowseDatabaseAlbumList() {
  11. handleSearchExpression('BrowseDatabaseAlbumList');
  12. selectTag('BrowseDatabaseAlbumListTagDropdown', 'btnBrowseDatabaseAlbumListTagDesc', app.current.tag);
  13. toggleBtnChkId('BrowseDatabaseAlbumListSortDesc', app.current.sort.desc);
  14. selectTag('BrowseDatabaseAlbumListSortTags', undefined, app.current.sort.tag);
  15. sendAPI("MYMPD_API_DATABASE_ALBUM_LIST", {
  16. "offset": app.current.offset,
  17. "limit": app.current.limit,
  18. "expression": app.current.search,
  19. "sort": app.current.sort.tag,
  20. "sortdesc": app.current.sort.desc,
  21. "fields": settings.viewBrowseDatabaseAlbumListFetch.fields
  22. }, parseDatabaseAlbumList, true);
  23. }
  24. /**
  25. * BrowseDatabaseTagList handler
  26. * @returns {void}
  27. */
  28. function handleBrowseDatabaseTagList() {
  29. handleSearchSimple('BrowseDatabaseTag');
  30. selectTag('BrowseDatabaseTagListTagDropdown', 'btnBrowseDatabaseTagListTagDesc', app.current.tag);
  31. mirrorBtnId('BrowseDatabaseTagListSortDesc', app.current.sort.desc);
  32. sendAPI("MYMPD_API_DATABASE_TAG_LIST", {
  33. "offset": app.current.offset,
  34. "limit": app.current.limit,
  35. "searchstr": app.current.search,
  36. "tag": app.current.tag,
  37. "sortdesc": app.current.sort.desc
  38. }, parseDatabaseTagList, true);
  39. }
  40. /**
  41. * Handles BrowseDatabaseAlbumDetail
  42. * @returns {void}
  43. */
  44. function handleBrowseDatabaseAlbumDetail() {
  45. sendAPI("MYMPD_API_DATABASE_ALBUM_DETAIL", {
  46. "albumid": app.current.filter,
  47. "fields": settings.viewBrowseDatabaseAlbumDetailFetch.fields
  48. }, parseAlbumDetails, true);
  49. }
  50. /**
  51. * Initializes the browse database elements
  52. * @returns {void}
  53. */
  54. function initViewBrowseDatabase() {
  55. initSearchSimple('BrowseDatabaseTag');
  56. elGetById('BrowseDatabaseTagListSortDesc').addEventListener('click', function(event) {
  57. event.stopPropagation();
  58. event.preventDefault();
  59. app.current.sort.desc = app.current.sort.desc === true ? false : true;
  60. appGoto(app.current.card, app.current.tab, app.current.view, 0, app.current.limit, app.current.filter, app.current.sort, app.current.tag, app.current.search);
  61. }, false);
  62. initSortBtns('BrowseDatabaseAlbumList');
  63. initSearchExpression('BrowseDatabaseAlbumList');
  64. setView('BrowseDatabaseAlbumList');
  65. setView('BrowseDatabaseAlbumDetail');
  66. setView('BrowseDatabaseTagList');
  67. }
  68. /**
  69. * Click event handler for database tag list
  70. * @param {MouseEvent} event click event
  71. * @param {HTMLElement} target calculated target
  72. * @returns {void}
  73. */
  74. function viewBrowseDatabaseTagListListClickHandler(event, target) {
  75. event.preventDefault();
  76. event.stopPropagation();
  77. app.current.search = '';
  78. const tag = getData(target, 'tag');
  79. if (settings.tagListAlbum.includes(tag)) {
  80. elGetById('BrowseDatabaseTagSearchStr').value = '';
  81. // clear album search input
  82. elGetById('BrowseDatabaseAlbumListSearchStr').value = '';
  83. gotoBrowse(event);
  84. }
  85. else {
  86. elGetById('SearchSearchStr').value = '';
  87. const value = getData(event.target.parentNode, 'name');
  88. gotoSearch(tag, value);
  89. }
  90. }
  91. /**
  92. * Click event handler for database album list
  93. * @param {MouseEvent} event click event
  94. * @param {HTMLElement} target calculated target
  95. * @returns {void}
  96. */
  97. function viewBrowseDatabaseAlbumListListClickHandler(event, target) {
  98. appGoto('Browse', 'Database', 'AlbumDetail', 0, undefined, getData(target, 'AlbumId'));
  99. }
  100. /**
  101. * Click event handler for database album detail song list
  102. * @param {MouseEvent} event click event
  103. * @param {HTMLElement} target calculated target
  104. * @returns {void}
  105. */
  106. function viewBrowseDatabaseAlbumDetailListClickHandler(event, target) {
  107. clickSong(getData(target, 'uri'), event);
  108. }
  109. /**
  110. * Parses the MYMPD_API_DATABASE_ALBUM_LIST response
  111. * @param {object} obj jsonrpc response object
  112. * @returns {void}
  113. */
  114. function parseDatabaseAlbumList(obj) {
  115. const cardContainer = elGetById('BrowseDatabaseAlbumListList');
  116. if (checkResult(obj, cardContainer, undefined) === false) {
  117. return;
  118. }
  119. if (settings['view' + app.id].mode === 'table') {
  120. const tfoot = cardContainer.querySelector('tfoot');
  121. elClear(tfoot);
  122. updateTable(obj, app.id, function(row, data) {
  123. parseDatabaseAlbumListUpdate(row, data);
  124. });
  125. addTblFooter(tfoot,
  126. elCreateTextTnNr('span', {}, 'Num entries', obj.result.totalEntities)
  127. );
  128. return;
  129. }
  130. if (settings['view' + app.id].mode === 'grid') {
  131. updateGrid(obj, app.id, function(card, data) {
  132. parseDatabaseAlbumListUpdate(card, data);
  133. });
  134. return;
  135. }
  136. updateList(obj, app.id, function(card, data) {
  137. parseDatabaseAlbumListUpdate(card, data);
  138. });
  139. }
  140. /**
  141. * Callback function for row or card
  142. * @param {HTMLElement} card Row or card
  143. * @param {object} data Data object
  144. * @returns {void}
  145. */
  146. function parseDatabaseAlbumListUpdate(card, data) {
  147. setData(card, 'uri', data.FirstSongUri.replace(/\/[^/]+$/, ''));
  148. setData(card, 'type', 'album');
  149. setData(card, 'name', data.Album);
  150. setData(card, 'Album', data.Album);
  151. setData(card, tagAlbumArtist, data[tagAlbumArtist]);
  152. setData(card, 'AlbumId', data.AlbumId);
  153. card.setAttribute('title', tn('Show album'));
  154. }
  155. /**
  156. * Parsed the MYMPD_API_DATABASE_TAG_LIST response
  157. * @param {object} obj jsonrpc response object
  158. * @returns {void}
  159. */
  160. function parseDatabaseTagList(obj) {
  161. const cardContainer = elGetById('BrowseDatabaseTagListList');
  162. if (checkResult(obj, cardContainer, undefined) === false) {
  163. return;
  164. }
  165. elGetById('BrowseDatabaseTagListTitle').textContent = tn(obj.result.tag);
  166. if (settings['view' + app.id].mode === 'table') {
  167. const tfoot = cardContainer.querySelector('tfoot');
  168. const colspan = settings['view' + app.id].fields.length;
  169. const smallWidth = uiSmallWidthTagRows();
  170. const actionTd = elCreateEmpty('td', {"data-col": "Action"});
  171. addActionLinks(actionTd, obj.result.tag);
  172. elClear(tfoot);
  173. updateTable(obj, app.id, function(row, data, result) {
  174. parseDatabaseTagListUpdate(row, data, result);
  175. }, function(row, data) {
  176. tableRow(row, data, app.id, colspan, smallWidth, actionTd);
  177. });
  178. addTblFooter(tfoot,
  179. elCreateTextTnNr('span', {}, 'Num entries', obj.result.totalEntities)
  180. );
  181. return;
  182. }
  183. if (settings['view' + app.id].mode === 'grid') {
  184. updateGrid(obj, app.id, function(card, data, result) {
  185. parseDatabaseTagListUpdate(card, data, result);
  186. }, undefined, function(footer, data, result) {
  187. addActionLinks(footer, result.tag);
  188. });
  189. return;
  190. }
  191. updateList(obj, app.id, function(card, data, result) {
  192. parseDatabaseTagListUpdate(card, data, result);
  193. }, undefined, function(footer, data, result) {
  194. addActionLinks(footer, result.tag);
  195. });
  196. }
  197. /**
  198. * Callback function for row or card
  199. * @param {HTMLElement} card Row or card
  200. * @param {object} data Data object
  201. * @param {object} result Jsonrpc result
  202. * @returns {void}
  203. */
  204. function parseDatabaseTagListUpdate(card, data, result) {
  205. if (result.pics === true) {
  206. data.Thumbnail = getImageUri('/tagart?tag=' + myEncodeURIComponent(result.tag) + '&value=' + myEncodeURIComponent(data.Value));
  207. }
  208. setData(card, 'tag', result.tag);
  209. setData(card, 'name', data.Value);
  210. const rowTitle = tn(settings.tagListAlbum.includes(result.tag) ? 'Show albums' : 'Show songs');
  211. card.setAttribute('title', rowTitle);
  212. card.classList.add('no-contextmenu');
  213. }
  214. /**
  215. * Shows the edit sticker modal for the current album
  216. * @param {Event} event triggering click event
  217. * @returns {void}
  218. */
  219. //eslint-disable-next-line no-unused-vars
  220. function showAlbumSticker(event) {
  221. event.preventDefault();
  222. const uri = getDataId('viewDatabaseAlbumDetailCover', 'AlbumId');
  223. showStickerModal(uri, 'mympd_album');
  224. }
  225. /**
  226. * Parses the MYMPD_API_DATABASE_ALBUM_DETAIL response
  227. * @param {object} obj jsonrpc response object
  228. * @returns {void}
  229. */
  230. function parseAlbumDetails(obj) {
  231. const table = elGetById('BrowseDatabaseAlbumDetailList');
  232. const tfoot = table.querySelector('tfoot');
  233. const colspan = settings.viewBrowseDatabaseAlbumDetail.fields.length;
  234. const infoEl = elGetById('viewDatabaseAlbumDetailInfoTags');
  235. if (checkResultId(obj, 'BrowseDatabaseAlbumDetailList', 'grid') === false) {
  236. elClear(infoEl);
  237. return;
  238. }
  239. const coverEl = elGetById('viewDatabaseAlbumDetailCover');
  240. coverEl.style.backgroundImage = getCssImageUri('/albumart?offset=0&uri=' + myEncodeURIComponent(obj.result.data[0].uri));
  241. setData(coverEl, 'images', obj.result.images);
  242. setData(coverEl, 'embeddedImageCount', obj.result.embeddedImageCount);
  243. setData(coverEl, 'uri', obj.result.data[0].uri);
  244. setData(coverEl, 'AlbumId', obj.result.AlbumId);
  245. if (features.featStickers === true) {
  246. const feedbackGrp = elGetById('BrowseDatabaseAlbumDetailFeedback').firstElementChild;
  247. setData(feedbackGrp, 'uri', obj.result.AlbumId);
  248. setFeedback(feedbackGrp, obj.result.like, obj.result.rating);
  249. }
  250. elClear(infoEl);
  251. infoEl.appendChild(
  252. elCreateText('h1', {}, obj.result.Album)
  253. );
  254. for (const col of settings.viewBrowseDatabaseAlbumDetailInfo.fields) {
  255. infoEl.appendChild(
  256. elCreateNodes('div', {"class": ["col-xl-6"]}, [
  257. elCreateTextTn('small', {}, col),
  258. elCreateNode('p', {},
  259. printValue(col, obj.result[col])
  260. )
  261. ])
  262. );
  263. }
  264. if (obj.result.bookletPath !== '') {
  265. infoEl.appendChild(
  266. elCreateNodes('div', {"class": ["col-xl-6"]}, [
  267. elCreateText('span', {"class": ["mi", "me-2"]}, 'description'),
  268. elCreateTextTn('a', {"target": "_blank", "href": subdir + myEncodeURI(obj.result.bookletPath)}, 'Download booklet')
  269. ])
  270. );
  271. }
  272. if (obj.result.infoTxtPath !== '') {
  273. const infoTxtEl = elCreateNodes('div', {"class": ["col-xl-6"]}, [
  274. elCreateText('span', {"class": ["mi", "me-2"]}, 'article'),
  275. elCreateTextTn('span', {"class": ["clickable"]}, 'Album info')
  276. ]);
  277. setData(infoTxtEl, 'uri', obj.result.infoTxtPath);
  278. infoTxtEl.addEventListener('click', function(event) {
  279. showInfoTxt(event.target);
  280. }, false);
  281. infoEl.appendChild(infoTxtEl);
  282. }
  283. const mbField = addMusicbrainzFields(obj.result, false);
  284. if (mbField !== null) {
  285. infoEl.appendChild(mbField);
  286. }
  287. if (obj.result.lastPlayedSong.uri !== '' &&
  288. obj.result.lastPlayedSong.pos < obj.result.totalEntities - 1)
  289. {
  290. const resumeBtn = pEl.resumeBtn.cloneNode(true);
  291. resumeBtn.classList.add('ms-3', 'dropdown');
  292. resumeBtn.classList.remove('dropup');
  293. setData(resumeBtn, 'pos', obj.result.lastPlayedSong.pos);
  294. new BSN.Dropdown(resumeBtn.firstElementChild);
  295. resumeBtn.lastElementChild.firstElementChild.addEventListener('click', function(event) {
  296. clickResumeAlbum(event);
  297. }, false);
  298. infoEl.appendChild(
  299. elCreateNodes('div', {"class": ["col-xl-6"]}, [
  300. elCreateTextTn('small', {}, 'Last played'),
  301. elCreateNodes('p', {}, [
  302. document.createTextNode(obj.result.lastPlayedSong.title),
  303. resumeBtn
  304. ])
  305. ])
  306. );
  307. }
  308. const rowTitle = tn(settingsWebuiFields.clickSong.validValues[settings.webuiSettings.clickSong]);
  309. updateTable(obj, 'BrowseDatabaseAlbumDetail', function(row, data) {
  310. setData(row, 'type', 'song');
  311. setData(row, 'name', data.Title);
  312. setData(row, 'uri', data.uri);
  313. row.setAttribute('title', rowTitle);
  314. });
  315. elReplaceChild(tfoot,
  316. elCreateNode('tr', {"class": ["not-clickable"]},
  317. elCreateNode('td', {"colspan": colspan + 1},
  318. elCreateNodes('small', {}, [
  319. elCreateTextTnNr('span', {}, 'Num songs', obj.result.returnedEntities),
  320. elCreateText('span', {}, smallSpace + nDash + smallSpace + fmtDuration(obj.result.Duration))
  321. ])
  322. )
  323. )
  324. );
  325. }
  326. /**
  327. * Go's to the last browse database grid view
  328. * @returns {void}
  329. */
  330. //eslint-disable-next-line no-unused-vars
  331. function backToAlbumGrid() {
  332. appGoto('Browse', 'Database', 'AlbumList');
  333. }
  334. /**
  335. * Wrapper for add buttons in the album detail view
  336. * @param {string} action action to perform
  337. * @returns {void}
  338. */
  339. //eslint-disable-next-line no-unused-vars
  340. function currentAlbumAdd(action) {
  341. switch(action) {
  342. case 'appendQueue':
  343. appendQueue('album', [app.current.filter]);
  344. break;
  345. case 'appendPlayQueue':
  346. appendPlayQueue('album', [app.current.filter]);
  347. break;
  348. case 'insertAfterCurrentQueue':
  349. insertAfterCurrentQueue('album', [app.current.filter]);
  350. break;
  351. case 'replaceQueue':
  352. replaceQueue('album', [app.current.filter]);
  353. break;
  354. case 'replacePlayQueue':
  355. replacePlayQueue('album', [app.current.filter]);
  356. break;
  357. case 'addPlaylist':
  358. showAddToPlaylist('album', [app.current.filter]);
  359. break;
  360. case 'addAlbumToHome': {
  361. const name = document.querySelector('#viewDatabaseAlbumDetailInfoTags > h1').textContent;
  362. const images = getDataId('viewDatabaseAlbumDetailCover', 'images');
  363. addAlbumToHome(app.current.filter, name, (images.length > 0 ? images[0]: ''));
  364. break;
  365. }
  366. default:
  367. logError('Invalid action: ' + action);
  368. }
  369. }