"use strict";
// SPDX-License-Identifier: GPL-3.0-or-later
// myMPD (c) 2018-2024 Juergen Mang <mail@jcgames.de>
// https://github.com/jcorporation/mympd
/** @module viewsTables_js */
/**
* Initializes a table body for drag and drop of rows
* @param {string} tableId table id
* @returns {void}
*/
function dragAndDropTable(tableId) {
const tableBody = document.querySelector('#' + tableId + ' > tbody');
tableBody.addEventListener('dragstart', function(event) {
if (event.target.nodeName === 'TR') {
event.target.classList.add('opacity05');
// @ts-ignore
event.dataTransfer.setDragImage(event.target, 0, 0);
event.dataTransfer.effectAllowed = 'move';
dragEl = event.target;
}
}, false);
tableBody.addEventListener('dragenter', function(event) {
const target = event.target.nodeName === 'TD'
? event.target.parentNode
: event.target;
if (dragEl !== undefined &&
dragEl.nodeName === target.nodeName)
{
target.classList.add('dragover');
}
}, false);
tableBody.addEventListener('dragleave', function(event) {
const target = event.target.nodeName === 'TD'
? event.target.parentNode
: event.target;
if (dragEl !== undefined &&
dragEl.nodeName === target.nodeName)
{
target.classList.remove('dragover');
}
}, false);
tableBody.addEventListener('dragover', function(event) {
event.preventDefault();
event.dataTransfer.dropEffect = 'move';
}, false);
tableBody.addEventListener('drop', function(event) {
event.stopPropagation();
event.preventDefault();
if (dragEl === undefined ||
dragEl.nodeName !== 'TR')
{
return;
}
const target = event.target.closest('TR');
target.classList.remove('dragover');
const newPos = getData(target, 'pos');
const oldPos = getData(dragEl, 'pos');
if (oldPos === newPos) {
return;
}
// set dragged element uri to undefined to force table row replacement
setData(dragEl, 'uri', undefined);
elHide(dragEl);
// apply new order
setUpdateViewId(tableId);
switch(app.id) {
case 'QueueCurrent': {
queueMoveSong(oldPos, newPos);
break;
}
case 'BrowsePlaylistDetail': {
currentPlaylistMoveSong(oldPos, newPos);
break;
}
// No Default
}
}, false);
tableBody.addEventListener('dragend', function() {
dragEl.classList.remove('opacity05');
dragEl = undefined;
}, false);
}
/**
* Sets the table header columns
* @param {string} tableName table name
* @returns {void}
*/
function setCols(tableName) {
if (tableName === 'Search' &&
app.cards.Search.sort.tag === 'Title')
{
if (settings.tagList.includes('Title')) {
app.cards.Search.sort.tag = 'Title';
}
else if (features.featTags === false) {
app.cards.Search.sort.tag = 'Filename';
}
else {
app.cards.Search.sort.tag = '';
}
}
const thead = document.querySelector('#' + tableName + 'List > thead > tr');
elClear(thead);
for (let i = 0, j = settings['view' + tableName].fields.length; i < j; i++) {
let hname = settings['view' + tableName].fields[i];
if (hname === 'Track' ||
hname === 'Pos')
{
hname = '#';
}
const th = elCreateTextTn('th', {"data-col": settings['view' + tableName].fields[i]}, hname);
thead.appendChild(th);
}
//append action column
const th = elCreateEmpty('th', {"data-col": "Action"});
th.appendChild(
pEl.selectAllBtn.cloneNode(true)
);
thead.appendChild(th);
}
/**
* Replaces a table row and tries to keep the selection state
* @param {boolean} mode the selection mode
* @param {HTMLElement} row row to replace
* @param {HTMLElement} el replacement row
* @returns {void}
*/
function replaceTblRow(mode, row, el) {
if (getData(row, 'uri') === getData(el, 'uri')) {
if (mode === true &&
row.lastElementChild.lastElementChild.textContent === ligatures.checked)
{
el.lastElementChild.lastElementChild.textContent = ligatures.checked;
el.classList.add('selected');
}
if (row.classList.contains('queue-playing')) {
el.classList.add('queue-playing');
el.style.background = row.style.background;
}
}
row.replaceWith(el);
}
/**
* Adds a row with discnumber to the table
* @param {number} disc discnumber
* @param {string} albumId the albumid
* @param {string} albumName the album name
* @param {number} colspan column count
* @returns {HTMLElement} the created row
*/
function addDiscRow(disc, albumId, albumName, colspan) {
const actionTd = elCreateEmpty('td', {"data-col": "Action"});
addActionLinks(actionTd, 'disc');
const row = elCreateNodes('tr', {"class": ["not-clickable"]}, [
elCreateNode('td', {},
elCreateText('span', {"class": ["mi"]}, 'album')
),
elCreateTextTnNr('td', {"colspan": (colspan - 1)}, 'Discnum', disc),
actionTd
]);
setData(row, 'Disc', disc);
setData(row, 'AlbumId', albumId);
setData(row, 'type', 'disc');
setData(row, 'name', albumName + ' (' + tn('Disc') + ' ' + disc + ')');
return row;
}
/**
* Determines whether works shoud be shown for the current view.
* @param {string} view table name
* @returns {boolean} true if work row should be shown, else false
*/
function showWorkRow(view) {
return view === 'BrowseDatabaseAlbumDetail' &&
settings.webuiSettings.showWorkTagAlbumDetail;
}
/**
* Adds a row with the work to the table.
* @param {string} work The work name
* @param {string} albumId the albumid
* @param {string} albumName the album name
* @param {number} colspan column count
* @returns {HTMLElement} the created row
*/
function addWorkRow(work, albumId, albumName, colspan) {
const actionTd = elCreateEmpty('td', {"data-col": "Action"});
addActionLinks(actionTd, 'work');
const row = elCreateNodes('tr', {"class": ["not-clickable"]}, [
elCreateNode('td', {},
elCreateText('span', {"class": ["mi"]}, 'music_note')
),
elCreateText('td', {"colspan": (colspan - 1)}, work),
actionTd
]);
setData(row, 'Work', work);
setData(row, 'AlbumId', albumId);
setData(row, 'type', 'work');
setData(row, 'name', albumName + ' (' + work + ')');
return row;
}
/**
* Updates the table from the jsonrpc response
* @param {object} obj jsonrpc response
* @param {string} list table name to populate
* @param {Function} [perRowCallback] callback per row
* @param {Function} [createRowCellsCallback] callback to create the row
* @returns {void}
*/
function updateTable(obj, list, perRowCallback, createRowCellsCallback) {
const table = elGetById(list + 'List');
const mode = table.getAttribute('data-mode') === 'select'
? true
: false;
const tbody = table.querySelector('tbody');
const colspan = settings['view' + list].fields !== undefined
? settings['view' + list].fields.length
: 0;
let tr = tbody.querySelectorAll('tr');
const smallWidth = uiSmallWidthTagRows();
if (smallWidth === true) {
table.classList.add('smallWidth');
}
else {
table.classList.remove('smallWidth');
}
const actionTd = elCreateEmpty('td', {"data-col": "Action"});
addActionLinks(actionTd);
//disc handling for album view
let z = 0;
let lastDisc = obj.result.data.length > 0 && obj.result.data[0].Disc !== undefined
? Number(obj.result.data[0].Disc)
: 0;
let lastWork = obj.result.data.length > 0 && obj.result.data[0].Work !== undefined ?
obj.result.data[0].Work : '';
if (obj.result.Discs !== undefined &&
obj.result.Discs > 1)
{
const row = addDiscRow(1, obj.result.AlbumId, obj.result.Album, colspan);
if (z < tr.length) {
replaceTblRow(mode, tr[z], row);
}
else {
tbody.append(row);
}
z++;
}
if (showWorkRow(list) && lastWork !== '') {
const row = addWorkRow(lastWork, obj.result.AlbumId, obj.result.Album, colspan);
if (z < tr.length) {
replaceTblRow(mode, tr[z], row);
}
else {
tbody.append(row);
}
z++;
}
for (let i = 0; i < obj.result.returnedEntities; i++) {
//disc handling for album view
if (obj.result.data[0].Disc !== undefined &&
lastDisc < Number(obj.result.data[i].Disc))
{
const row = addDiscRow(obj.result.data[i].Disc, obj.result.AlbumId, obj.result.Album, colspan);
if (i + z < tr.length) {
replaceTblRow(mode, tr[i + z], row);
}
else {
tbody.append(row);
}
z++;
lastDisc = obj.result.data[i].Disc;
}
if (showWorkRow(list) && obj.result.data[0].Work !== undefined &&
lastWork !== obj.result.data[i].Work) {
const row = addWorkRow(obj.result.data[i].Work, obj.result.AlbumId, obj.result.Album, colspan);
if (i + z < tr.length) {
replaceTblRow(mode, tr[i + z], row);
} else {
tbody.append(row);
}
z++;
lastWork = obj.result.data[i].Work;
}
const row = elCreateEmpty('tr', {});
if (perRowCallback !== undefined &&
typeof(perRowCallback) === 'function')
{
perRowCallback(row, obj.result.data[i], obj.result);
}
//data row
setEntryData(row, obj.result.data[i]);
if (createRowCellsCallback !== undefined &&
typeof(createRowCellsCallback) === 'function')
{
//custom row content
createRowCellsCallback(row, obj.result.data[i], obj.result);
}
else {
//default row content
tableRow(row, obj.result.data[i], list, colspan, smallWidth, actionTd);
}
if (i + z < tr.length) {
replaceTblRow(mode, tr[i + z], row);
}
else {
tbody.append(row);
}
}
//remove obsolete lines
tr = tbody.querySelectorAll('tr');
for (let i = tr.length - 1; i >= obj.result.returnedEntities + z; i --) {
tr[i].remove();
}
setPagination(obj.result.totalEntities, obj.result.returnedEntities);
if (obj.result.returnedEntities === 0) {
tbody.appendChild(emptyMsgEl(colspan + 1, 'table'));
}
unsetUpdateView(table);
setScrollViewHeight(table);
scrollToPosY(table.parentNode, app.current.scrollPos);
}
/**
* Creates the columns in the row
* @param {HTMLElement} row the row to populate
* @param {object} data data to populate
* @param {string} list table name
* @param {number} colspan number of columns
* @param {boolean} smallWidth true = print data in rows, false = print data in columns
* @param {Element} actionTd action table cell element
* @returns {void}
*/
function tableRow(row, data, list, colspan, smallWidth, actionTd) {
if (smallWidth === true) {
const td = elCreateEmpty('td', {"colspan": colspan});
for (let c = 0, d = settings['view' + list].fields.length; c < d; c++) {
td.appendChild(
elCreateNodes('div', {"class": ["row"]}, [
elCreateTextTn('small', {"class": ["col-3"]}, settings['view' + list].fields[c]),
elCreateNode('span', {"data-col": settings['view' + list].fields[c], "class": ["col-9"]},
printValue(settings['view' + list].fields[c], data[settings['view' + list].fields[c]], data)
)
])
);
}
row.appendChild(td);
}
else {
for (let c = 0, d = settings['view' + list].fields.length; c < d; c++) {
row.appendChild(
elCreateNode('td', {"data-col": settings['view' + list].fields[c]},
printValue(settings['view' + list].fields[c], data[settings['view' + list].fields[c]], data)
)
);
}
}
row.appendChild(actionTd.cloneNode(true));
}
/**
* Checks if we should display data in rows or cols
* @returns {boolean} true if window is small and the uiSmallWidthTagRows settings is true, else false
*/
function uiSmallWidthTagRows() {
if (settings.webuiSettings.smallWidthTagRows === true) {
return window.innerWidth < 576
? true
: false;
}
return false;
}
/**
* Updates the table footer
* @param {Element} tfoot Element to insert the footer row
* @param {Element} content Dom node to insert
* @returns {void}
*/
function addTblFooter(tfoot, content) {
const colspan = settings['view' + app.id].fields.length + 1;
tfoot.appendChild(
elCreateNode('tr', {"class": ["not-clickable"]},
elCreateNode('td', {"colspan": colspan}, content)
)
);
}